summaryrefslogtreecommitdiffstats
path: root/src/spdk/lib/iscsi/conn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/spdk/lib/iscsi/conn.c')
-rw-r--r--src/spdk/lib/iscsi/conn.c1470
1 files changed, 1470 insertions, 0 deletions
diff --git a/src/spdk/lib/iscsi/conn.c b/src/spdk/lib/iscsi/conn.c
new file mode 100644
index 00000000..d5cd5d1e
--- /dev/null
+++ b/src/spdk/lib/iscsi/conn.c
@@ -0,0 +1,1470 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>.
+ * Copyright (c) Intel Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "spdk/stdinc.h"
+
+#include "spdk/endian.h"
+#include "spdk/env.h"
+#include "spdk/event.h"
+#include "spdk/thread.h"
+#include "spdk/queue.h"
+#include "spdk/trace.h"
+#include "spdk/net.h"
+#include "spdk/sock.h"
+#include "spdk/string.h"
+
+#include "spdk_internal/log.h"
+
+#include "iscsi/task.h"
+#include "iscsi/conn.h"
+#include "iscsi/tgt_node.h"
+#include "iscsi/portal_grp.h"
+
+#define SPDK_ISCSI_CONNECTION_MEMSET(conn) \
+ memset(&(conn)->portal, 0, sizeof(*(conn)) - \
+ offsetof(struct spdk_iscsi_conn, portal));
+
+static int g_connections_per_lcore;
+static uint32_t *g_num_connections;
+
+struct spdk_iscsi_conn *g_conns_array = MAP_FAILED;
+static int g_conns_array_fd = -1;
+static char g_shm_name[64];
+
+static pthread_mutex_t g_conns_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static struct spdk_poller *g_shutdown_timer = NULL;
+
+static uint32_t spdk_iscsi_conn_allocate_reactor(const struct spdk_cpuset *cpumask);
+
+static void spdk_iscsi_conn_full_feature_migrate(void *arg1, void *arg2);
+static void spdk_iscsi_conn_stop(struct spdk_iscsi_conn *conn);
+static void spdk_iscsi_conn_sock_cb(void *arg, struct spdk_sock_group *group,
+ struct spdk_sock *sock);
+
+static struct spdk_iscsi_conn *
+allocate_conn(void)
+{
+ struct spdk_iscsi_conn *conn;
+ int i;
+
+ pthread_mutex_lock(&g_conns_mutex);
+ for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) {
+ conn = &g_conns_array[i];
+ if (!conn->is_valid) {
+ SPDK_ISCSI_CONNECTION_MEMSET(conn);
+ conn->is_valid = 1;
+ pthread_mutex_unlock(&g_conns_mutex);
+ return conn;
+ }
+ }
+ pthread_mutex_unlock(&g_conns_mutex);
+
+ return NULL;
+}
+
+static void
+free_conn(struct spdk_iscsi_conn *conn)
+{
+ free(conn->portal_host);
+ free(conn->portal_port);
+ conn->is_valid = 0;
+}
+
+static struct spdk_iscsi_conn *
+spdk_find_iscsi_connection_by_id(int cid)
+{
+ if (g_conns_array[cid].is_valid == 1) {
+ return &g_conns_array[cid];
+ } else {
+ return NULL;
+ }
+}
+
+int spdk_initialize_iscsi_conns(void)
+{
+ size_t conns_size = sizeof(struct spdk_iscsi_conn) * MAX_ISCSI_CONNECTIONS;
+ uint32_t i, last_core;
+
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_init\n");
+
+ snprintf(g_shm_name, sizeof(g_shm_name), "/spdk_iscsi_conns.%d", spdk_app_get_shm_id());
+ g_conns_array_fd = shm_open(g_shm_name, O_RDWR | O_CREAT, 0600);
+ if (g_conns_array_fd < 0) {
+ SPDK_ERRLOG("could not shm_open %s\n", g_shm_name);
+ goto err;
+ }
+
+ if (ftruncate(g_conns_array_fd, conns_size) != 0) {
+ SPDK_ERRLOG("could not ftruncate\n");
+ goto err;
+ }
+ g_conns_array = mmap(0, conns_size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ g_conns_array_fd, 0);
+
+ if (g_conns_array == MAP_FAILED) {
+ fprintf(stderr, "could not mmap cons array file %s (%d)\n", g_shm_name, errno);
+ goto err;
+ }
+
+ memset(g_conns_array, 0, conns_size);
+
+ for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) {
+ g_conns_array[i].id = i;
+ }
+
+ last_core = spdk_env_get_last_core();
+ g_num_connections = calloc(last_core + 1, sizeof(uint32_t));
+ if (!g_num_connections) {
+ SPDK_ERRLOG("Could not allocate array size=%u for g_num_connections\n",
+ last_core + 1);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ if (g_conns_array != MAP_FAILED) {
+ munmap(g_conns_array, conns_size);
+ g_conns_array = MAP_FAILED;
+ }
+
+ if (g_conns_array_fd >= 0) {
+ close(g_conns_array_fd);
+ g_conns_array_fd = -1;
+ shm_unlink(g_shm_name);
+ }
+
+ return -1;
+}
+
+static void
+spdk_iscsi_poll_group_add_conn_sock(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_poll_group *poll_group;
+ int rc;
+
+ assert(conn->lcore == spdk_env_get_current_core());
+
+ poll_group = &g_spdk_iscsi.poll_group[conn->lcore];
+
+ rc = spdk_sock_group_add_sock(poll_group->sock_group, conn->sock, spdk_iscsi_conn_sock_cb, conn);
+ if (rc < 0) {
+ SPDK_ERRLOG("Failed to add sock=%p of conn=%p\n", conn->sock, conn);
+ }
+}
+
+static void
+spdk_iscsi_poll_group_remove_conn_sock(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_poll_group *poll_group;
+ int rc;
+
+ assert(conn->lcore == spdk_env_get_current_core());
+
+ poll_group = &g_spdk_iscsi.poll_group[conn->lcore];
+
+ rc = spdk_sock_group_remove_sock(poll_group->sock_group, conn->sock);
+ if (rc < 0) {
+ SPDK_ERRLOG("Failed to remove sock=%p of conn=%p\n", conn->sock, conn);
+ }
+}
+
+static void
+spdk_iscsi_poll_group_add_conn(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_poll_group *poll_group;
+
+ assert(conn->lcore == spdk_env_get_current_core());
+
+ poll_group = &g_spdk_iscsi.poll_group[conn->lcore];
+
+ conn->is_stopped = false;
+ STAILQ_INSERT_TAIL(&poll_group->connections, conn, link);
+ spdk_iscsi_poll_group_add_conn_sock(conn);
+}
+
+static void
+spdk_iscsi_poll_group_remove_conn(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_poll_group *poll_group;
+
+ assert(conn->lcore == spdk_env_get_current_core());
+
+ poll_group = &g_spdk_iscsi.poll_group[conn->lcore];
+
+ conn->is_stopped = true;
+ STAILQ_REMOVE(&poll_group->connections, conn, spdk_iscsi_conn, link);
+}
+
+/**
+ * \brief Create an iSCSI connection from the given parameters and schedule it
+ * on a reactor.
+ *
+ * \code
+ *
+ * # identify reactor where the new connections work item will be scheduled
+ * reactor = spdk_iscsi_conn_allocate_reactor()
+ * allocate spdk_iscsi_conn object
+ * initialize spdk_iscsi_conn object
+ * schedule iSCSI connection work item on reactor
+ *
+ * \endcode
+ */
+int
+spdk_iscsi_conn_construct(struct spdk_iscsi_portal *portal,
+ struct spdk_sock *sock)
+{
+ struct spdk_iscsi_conn *conn;
+ int bufsize, i, rc;
+
+ conn = allocate_conn();
+ if (conn == NULL) {
+ SPDK_ERRLOG("Could not allocate connection.\n");
+ return -1;
+ }
+
+ pthread_mutex_lock(&g_spdk_iscsi.mutex);
+ conn->timeout = g_spdk_iscsi.timeout;
+ conn->nopininterval = g_spdk_iscsi.nopininterval;
+ conn->nopininterval *= spdk_get_ticks_hz(); /* seconds to TSC */
+ conn->nop_outstanding = false;
+ conn->data_out_cnt = 0;
+ conn->data_in_cnt = 0;
+ pthread_mutex_unlock(&g_spdk_iscsi.mutex);
+ conn->MaxRecvDataSegmentLength = 8192; // RFC3720(12.12)
+
+ conn->portal = portal;
+ conn->pg_tag = portal->group->tag;
+ conn->portal_host = strdup(portal->host);
+ conn->portal_port = strdup(portal->port);
+ conn->portal_cpumask = portal->cpumask;
+ conn->sock = sock;
+
+ conn->state = ISCSI_CONN_STATE_INVALID;
+ conn->login_phase = ISCSI_SECURITY_NEGOTIATION_PHASE;
+ conn->ttt = 0;
+
+ conn->partial_text_parameter = NULL;
+
+ for (i = 0; i < MAX_CONNECTION_PARAMS; i++) {
+ conn->conn_param_state_negotiated[i] = false;
+ }
+
+ for (i = 0; i < MAX_SESSION_PARAMS; i++) {
+ conn->sess_param_state_negotiated[i] = false;
+ }
+
+ for (i = 0; i < DEFAULT_MAXR2T; i++) {
+ conn->outstanding_r2t_tasks[i] = NULL;
+ }
+
+ TAILQ_INIT(&conn->write_pdu_list);
+ TAILQ_INIT(&conn->snack_pdu_list);
+ TAILQ_INIT(&conn->queued_r2t_tasks);
+ TAILQ_INIT(&conn->active_r2t_tasks);
+ TAILQ_INIT(&conn->queued_datain_tasks);
+ memset(&conn->open_lun_descs, 0, sizeof(conn->open_lun_descs));
+
+ rc = spdk_sock_getaddr(sock, conn->target_addr, sizeof conn->target_addr, NULL,
+ conn->initiator_addr, sizeof conn->initiator_addr, NULL);
+ if (rc < 0) {
+ SPDK_ERRLOG("spdk_sock_getaddr() failed\n");
+ goto error_return;
+ }
+
+ bufsize = 2 * 1024 * 1024;
+ rc = spdk_sock_set_recvbuf(conn->sock, bufsize);
+ if (rc != 0) {
+ SPDK_ERRLOG("spdk_sock_set_recvbuf failed\n");
+ }
+
+ bufsize = 32 * 1024 * 1024 / g_spdk_iscsi.MaxConnections;
+ if (bufsize > 2 * 1024 * 1024) {
+ bufsize = 2 * 1024 * 1024;
+ }
+ rc = spdk_sock_set_sendbuf(conn->sock, bufsize);
+ if (rc != 0) {
+ SPDK_ERRLOG("spdk_sock_set_sendbuf failed\n");
+ }
+
+ /* set low water mark */
+ rc = spdk_sock_set_recvlowat(conn->sock, 1);
+ if (rc != 0) {
+ SPDK_ERRLOG("spdk_sock_set_recvlowat() failed\n");
+ goto error_return;
+ }
+
+ /* set default params */
+ rc = spdk_iscsi_conn_params_init(&conn->params);
+ if (rc < 0) {
+ SPDK_ERRLOG("iscsi_conn_params_init() failed\n");
+error_return:
+ spdk_iscsi_param_free(conn->params);
+ free_conn(conn);
+ return -1;
+ }
+ conn->logout_timer = NULL;
+ conn->shutdown_timer = NULL;
+ SPDK_NOTICELOG("Launching connection on acceptor thread\n");
+ conn->pending_task_cnt = 0;
+ conn->pending_activate_event = false;
+
+ conn->lcore = spdk_env_get_current_core();
+ __sync_fetch_and_add(&g_num_connections[conn->lcore], 1);
+
+ spdk_iscsi_poll_group_add_conn(conn);
+ return 0;
+}
+
+void
+spdk_iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu)
+{
+ if (pdu->task) {
+ if (pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN) {
+ if (pdu->task->scsi.offset > 0) {
+ conn->data_in_cnt--;
+ if (pdu->bhs.flags & ISCSI_DATAIN_STATUS) {
+ /* Free the primary task after the last subtask done */
+ conn->data_in_cnt--;
+ spdk_iscsi_task_put(spdk_iscsi_task_get_primary(pdu->task));
+ }
+ }
+ } else if (pdu->bhs.opcode == ISCSI_OP_SCSI_RSP &&
+ pdu->task->scsi.status != SPDK_SCSI_STATUS_GOOD) {
+ if (pdu->task->scsi.offset > 0) {
+ spdk_iscsi_task_put(spdk_iscsi_task_get_primary(pdu->task));
+ }
+ }
+ spdk_iscsi_task_put(pdu->task);
+ }
+ spdk_put_pdu(pdu);
+}
+
+static int spdk_iscsi_conn_free_tasks(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_pdu *pdu, *tmp_pdu;
+ struct spdk_iscsi_task *iscsi_task, *tmp_iscsi_task;
+
+ TAILQ_FOREACH_SAFE(pdu, &conn->write_pdu_list, tailq, tmp_pdu) {
+ TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq);
+ spdk_iscsi_conn_free_pdu(conn, pdu);
+ }
+
+ TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, tmp_pdu) {
+ TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq);
+ if (pdu->task) {
+ spdk_iscsi_task_put(pdu->task);
+ }
+ spdk_put_pdu(pdu);
+ }
+
+ TAILQ_FOREACH_SAFE(iscsi_task, &conn->queued_datain_tasks, link, tmp_iscsi_task) {
+ if (!iscsi_task->is_queued) {
+ TAILQ_REMOVE(&conn->queued_datain_tasks, iscsi_task, link);
+ spdk_iscsi_task_put(iscsi_task);
+ }
+ }
+
+ if (conn->pending_task_cnt) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void spdk_iscsi_conn_free(struct spdk_iscsi_conn *conn)
+{
+ if (conn == NULL) {
+ return;
+ }
+
+ spdk_iscsi_param_free(conn->params);
+
+ /*
+ * Each connection pre-allocates its next PDU - make sure these get
+ * freed here.
+ */
+ spdk_put_pdu(conn->pdu_in_progress);
+
+ free_conn(conn);
+}
+
+static void spdk_iscsi_remove_conn(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_sess *sess;
+ int idx;
+ uint32_t i, j;
+
+ idx = -1;
+ sess = conn->sess;
+ conn->sess = NULL;
+ if (sess == NULL) {
+ spdk_iscsi_conn_free(conn);
+ return;
+ }
+
+ for (i = 0; i < sess->connections; i++) {
+ if (sess->conns[i] == conn) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (sess->connections < 1) {
+ SPDK_ERRLOG("zero connection\n");
+ sess->connections = 0;
+ } else {
+ if (idx < 0) {
+ SPDK_ERRLOG("remove conn not found\n");
+ } else {
+ for (j = idx; j < sess->connections - 1; j++) {
+ sess->conns[j] = sess->conns[j + 1];
+ }
+ sess->conns[sess->connections - 1] = NULL;
+ }
+ sess->connections--;
+ }
+
+ SPDK_NOTICELOG("Terminating connections(tsih %d): %d\n", sess->tsih, sess->connections);
+
+ if (sess->connections == 0) {
+ /* cleanup last connection */
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI,
+ "cleanup last conn free sess\n");
+ spdk_free_sess(sess);
+ }
+
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "cleanup free conn\n");
+ spdk_iscsi_conn_free(conn);
+}
+
+static void
+spdk_iscsi_conn_cleanup_backend(struct spdk_iscsi_conn *conn)
+{
+ int rc;
+ struct spdk_iscsi_tgt_node *target;
+
+ if (conn->sess->connections > 1) {
+ /* connection specific cleanup */
+ } else if (!g_spdk_iscsi.AllowDuplicateIsid) {
+ /* clean up all tasks to all LUNs for session */
+ target = conn->sess->target;
+ if (target != NULL) {
+ rc = spdk_iscsi_tgt_node_cleanup_luns(conn, target);
+ if (rc < 0) {
+ SPDK_ERRLOG("target abort failed\n");
+ }
+ }
+ }
+}
+
+static void
+_spdk_iscsi_conn_free(struct spdk_iscsi_conn *conn)
+{
+ pthread_mutex_lock(&g_conns_mutex);
+ spdk_iscsi_remove_conn(conn);
+ pthread_mutex_unlock(&g_conns_mutex);
+}
+
+static int
+_spdk_iscsi_conn_check_shutdown(void *arg)
+{
+ struct spdk_iscsi_conn *conn = arg;
+ int rc;
+
+ rc = spdk_iscsi_conn_free_tasks(conn);
+ if (rc < 0) {
+ return -1;
+ }
+
+ spdk_poller_unregister(&conn->shutdown_timer);
+
+ spdk_iscsi_conn_stop(conn);
+ _spdk_iscsi_conn_free(conn);
+
+ return -1;
+}
+
+static void
+_spdk_iscsi_conn_destruct(struct spdk_iscsi_conn *conn)
+{
+ int rc;
+
+ spdk_clear_all_transfer_task(conn, NULL);
+ spdk_iscsi_poll_group_remove_conn_sock(conn);
+ spdk_sock_close(&conn->sock);
+ spdk_poller_unregister(&conn->logout_timer);
+ spdk_poller_unregister(&conn->flush_poller);
+
+ rc = spdk_iscsi_conn_free_tasks(conn);
+ if (rc < 0) {
+ /* The connection cannot be freed yet. Check back later. */
+ conn->shutdown_timer = spdk_poller_register(_spdk_iscsi_conn_check_shutdown, conn, 1000);
+ } else {
+ spdk_iscsi_conn_stop(conn);
+ _spdk_iscsi_conn_free(conn);
+ }
+}
+
+static int
+_spdk_iscsi_conn_check_pending_tasks(void *arg)
+{
+ struct spdk_iscsi_conn *conn = arg;
+
+ if (conn->dev != NULL && spdk_scsi_dev_has_pending_tasks(conn->dev)) {
+ return -1;
+ }
+
+ spdk_poller_unregister(&conn->shutdown_timer);
+
+ _spdk_iscsi_conn_destruct(conn);
+
+ return -1;
+}
+
+void
+spdk_iscsi_conn_destruct(struct spdk_iscsi_conn *conn)
+{
+ conn->state = ISCSI_CONN_STATE_EXITED;
+
+ if (conn->sess != NULL && conn->pending_task_cnt > 0) {
+ spdk_iscsi_conn_cleanup_backend(conn);
+ }
+
+ if (conn->dev != NULL && spdk_scsi_dev_has_pending_tasks(conn->dev)) {
+ conn->shutdown_timer = spdk_poller_register(_spdk_iscsi_conn_check_pending_tasks, conn, 1000);
+ } else {
+ _spdk_iscsi_conn_destruct(conn);
+ }
+}
+
+static int
+spdk_iscsi_get_active_conns(void)
+{
+ struct spdk_iscsi_conn *conn;
+ int num = 0;
+ int i;
+
+ pthread_mutex_lock(&g_conns_mutex);
+ for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) {
+ conn = spdk_find_iscsi_connection_by_id(i);
+ if (conn == NULL) {
+ continue;
+ }
+ num++;
+ }
+ pthread_mutex_unlock(&g_conns_mutex);
+ return num;
+}
+
+static void
+spdk_iscsi_conns_cleanup(void)
+{
+ free(g_num_connections);
+ munmap(g_conns_array, sizeof(struct spdk_iscsi_conn) *
+ MAX_ISCSI_CONNECTIONS);
+ shm_unlink(g_shm_name);
+ if (g_conns_array_fd >= 0) {
+ close(g_conns_array_fd);
+ g_conns_array_fd = -1;
+ }
+}
+
+static void
+spdk_iscsi_conn_check_shutdown_cb(void *arg1, void *arg2)
+{
+ spdk_iscsi_conns_cleanup();
+ spdk_shutdown_iscsi_conns_done();
+}
+
+static int
+spdk_iscsi_conn_check_shutdown(void *arg)
+{
+ struct spdk_event *event;
+
+ if (spdk_iscsi_get_active_conns() == 0) {
+ spdk_poller_unregister(&g_shutdown_timer);
+ event = spdk_event_allocate(spdk_env_get_current_core(),
+ spdk_iscsi_conn_check_shutdown_cb, NULL, NULL);
+ spdk_event_call(event);
+ }
+
+ return -1;
+}
+
+static void
+spdk_iscsi_conn_close_lun(struct spdk_iscsi_conn *conn, int lun_id)
+{
+ struct spdk_scsi_desc *desc;
+
+ desc = conn->open_lun_descs[lun_id];
+ if (desc != NULL) {
+ spdk_scsi_lun_free_io_channel(desc);
+ spdk_scsi_lun_close(desc);
+ conn->open_lun_descs[lun_id] = NULL;
+ }
+}
+
+static void
+spdk_iscsi_conn_close_luns(struct spdk_iscsi_conn *conn)
+{
+ int i;
+
+ for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) {
+ spdk_iscsi_conn_close_lun(conn, i);
+ }
+}
+
+static void
+_iscsi_conn_remove_lun(void *arg1, void *arg2)
+{
+ struct spdk_iscsi_conn *conn = arg1;
+ struct spdk_scsi_lun *lun = arg2;
+ int lun_id = spdk_scsi_lun_get_id(lun);
+ struct spdk_iscsi_pdu *pdu, *tmp_pdu;
+ struct spdk_iscsi_task *iscsi_task, *tmp_iscsi_task;
+
+ /* If a connection is already in stating status, just return */
+ if (conn->state >= ISCSI_CONN_STATE_EXITING) {
+ return;
+ }
+
+ spdk_clear_all_transfer_task(conn, lun);
+ TAILQ_FOREACH_SAFE(pdu, &conn->write_pdu_list, tailq, tmp_pdu) {
+ if (pdu->task && (lun == pdu->task->scsi.lun)) {
+ TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq);
+ spdk_iscsi_conn_free_pdu(conn, pdu);
+ }
+ }
+
+ TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, tmp_pdu) {
+ if (pdu->task && (lun == pdu->task->scsi.lun)) {
+ TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq);
+ spdk_iscsi_task_put(pdu->task);
+ spdk_put_pdu(pdu);
+ }
+ }
+
+ TAILQ_FOREACH_SAFE(iscsi_task, &conn->queued_datain_tasks, link, tmp_iscsi_task) {
+ if ((!iscsi_task->is_queued) && (lun == iscsi_task->scsi.lun)) {
+ TAILQ_REMOVE(&conn->queued_datain_tasks, iscsi_task, link);
+ spdk_iscsi_task_put(iscsi_task);
+ }
+ }
+
+ spdk_iscsi_conn_close_lun(conn, lun_id);
+}
+
+static void
+spdk_iscsi_conn_remove_lun(struct spdk_scsi_lun *lun, void *remove_ctx)
+{
+ struct spdk_iscsi_conn *conn = remove_ctx;
+ struct spdk_event *event;
+
+ event = spdk_event_allocate(conn->lcore, _iscsi_conn_remove_lun,
+ conn, lun);
+ spdk_event_call(event);
+}
+
+static void
+spdk_iscsi_conn_open_luns(struct spdk_iscsi_conn *conn)
+{
+ int i, rc;
+ struct spdk_scsi_lun *lun;
+ struct spdk_scsi_desc *desc;
+
+ for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) {
+ lun = spdk_scsi_dev_get_lun(conn->dev, i);
+ if (lun == NULL) {
+ continue;
+ }
+
+ rc = spdk_scsi_lun_open(lun, spdk_iscsi_conn_remove_lun, conn, &desc);
+ if (rc != 0) {
+ goto error;
+ }
+
+ rc = spdk_scsi_lun_allocate_io_channel(desc);
+ if (rc != 0) {
+ spdk_scsi_lun_close(desc);
+ goto error;
+ }
+
+ conn->open_lun_descs[i] = desc;
+ }
+
+ return;
+
+error:
+ spdk_iscsi_conn_close_luns(conn);
+}
+
+/**
+ * This function will stop executing the specified connection.
+ */
+static void
+spdk_iscsi_conn_stop(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_tgt_node *target;
+
+ if (conn->state == ISCSI_CONN_STATE_EXITED && conn->sess != NULL &&
+ conn->sess->session_type == SESSION_TYPE_NORMAL &&
+ conn->full_feature) {
+ target = conn->sess->target;
+ pthread_mutex_lock(&target->mutex);
+ target->num_active_conns--;
+ pthread_mutex_unlock(&target->mutex);
+
+ spdk_iscsi_conn_close_luns(conn);
+ }
+
+ assert(conn->lcore == spdk_env_get_current_core());
+
+ __sync_fetch_and_sub(&g_num_connections[conn->lcore], 1);
+ spdk_iscsi_poll_group_remove_conn(conn);
+}
+
+void spdk_shutdown_iscsi_conns(void)
+{
+ struct spdk_iscsi_conn *conn;
+ int i;
+
+ pthread_mutex_lock(&g_conns_mutex);
+
+ for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) {
+ conn = spdk_find_iscsi_connection_by_id(i);
+ if (conn == NULL) {
+ continue;
+ }
+
+ /* Do not set conn->state if the connection has already started exiting.
+ * This ensures we do not move a connection from EXITED state back to EXITING.
+ */
+ if (conn->state < ISCSI_CONN_STATE_EXITING) {
+ conn->state = ISCSI_CONN_STATE_EXITING;
+ }
+ }
+
+ pthread_mutex_unlock(&g_conns_mutex);
+ g_shutdown_timer = spdk_poller_register(spdk_iscsi_conn_check_shutdown, NULL,
+ 1000);
+}
+
+int
+spdk_iscsi_drop_conns(struct spdk_iscsi_conn *conn, const char *conn_match,
+ int drop_all)
+{
+ struct spdk_iscsi_conn *xconn;
+ const char *xconn_match;
+ int i, num;
+
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_drop_conns\n");
+
+ num = 0;
+ pthread_mutex_lock(&g_conns_mutex);
+ for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) {
+ xconn = spdk_find_iscsi_connection_by_id(i);
+
+ if (xconn == NULL) {
+ continue;
+ }
+
+ if (xconn == conn) {
+ continue;
+ }
+
+ if (!drop_all && xconn->initiator_port == NULL) {
+ continue;
+ }
+
+ xconn_match =
+ drop_all ? xconn->initiator_name : spdk_scsi_port_get_name(xconn->initiator_port);
+
+ if (!strcasecmp(conn_match, xconn_match) &&
+ conn->target == xconn->target) {
+
+ if (num == 0) {
+ /*
+ * Only print this message before we report the
+ * first dropped connection.
+ */
+ SPDK_ERRLOG("drop old connections %s by %s\n",
+ conn->target->name, conn_match);
+ }
+
+ SPDK_ERRLOG("exiting conn by %s (%s)\n",
+ xconn_match, xconn->initiator_addr);
+ if (xconn->sess != NULL) {
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "TSIH=%u\n", xconn->sess->tsih);
+ } else {
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "TSIH=xx\n");
+ }
+
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CID=%u\n", xconn->cid);
+
+ /* Do not set xconn->state if the connection has already started exiting.
+ * This ensures we do not move a connection from EXITED state back to EXITING.
+ */
+ if (xconn->state < ISCSI_CONN_STATE_EXITING) {
+ xconn->state = ISCSI_CONN_STATE_EXITING;
+ }
+ num++;
+ }
+ }
+
+ pthread_mutex_unlock(&g_conns_mutex);
+
+ if (num != 0) {
+ SPDK_ERRLOG("exiting %d conns\n", num);
+ }
+
+ return 0;
+}
+
+/**
+ * \brief Reads data for the specified iSCSI connection from its TCP socket.
+ *
+ * The TCP socket is marked as non-blocking, so this function may not read
+ * all data requested.
+ *
+ * Returns SPDK_ISCSI_CONNECTION_FATAL if the recv() operation indicates a fatal
+ * error with the TCP connection (including if the TCP connection was closed
+ * unexpectedly.
+ *
+ * Otherwise returns the number of bytes successfully read.
+ */
+int
+spdk_iscsi_conn_read_data(struct spdk_iscsi_conn *conn, int bytes,
+ void *buf)
+{
+ int ret;
+
+ if (bytes == 0) {
+ return 0;
+ }
+
+ ret = spdk_sock_recv(conn->sock, buf, bytes);
+
+ if (ret > 0) {
+ spdk_trace_record(TRACE_ISCSI_READ_FROM_SOCKET_DONE, conn->id, ret, 0, 0);
+ return ret;
+ }
+
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ }
+
+ /* For connect reset issue, do not output error log */
+ if (errno == ECONNRESET) {
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_sock_recv() failed, errno %d: %s\n",
+ errno, spdk_strerror(errno));
+ } else {
+ SPDK_ERRLOG("spdk_sock_recv() failed, errno %d: %s\n",
+ errno, spdk_strerror(errno));
+ }
+ }
+
+ /* connection closed */
+ return SPDK_ISCSI_CONNECTION_FATAL;
+}
+
+void
+spdk_iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task)
+{
+ struct spdk_iscsi_task *task = spdk_iscsi_task_from_scsi_task(scsi_task);
+
+ spdk_iscsi_task_mgmt_response(task->conn, task);
+ spdk_iscsi_task_put(task);
+}
+
+static void
+process_completed_read_subtask_list(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_task *primary)
+{
+ struct spdk_iscsi_task *subtask, *tmp;
+
+ TAILQ_FOREACH_SAFE(subtask, &primary->subtask_list, subtask_link, tmp) {
+ if (subtask->scsi.offset == primary->bytes_completed) {
+ TAILQ_REMOVE(&primary->subtask_list, subtask, subtask_link);
+ primary->bytes_completed += subtask->scsi.length;
+ spdk_iscsi_task_response(conn, subtask);
+ spdk_iscsi_task_put(subtask);
+ } else {
+ break;
+ }
+ }
+}
+
+static void
+process_read_task_completion(struct spdk_iscsi_conn *conn,
+ struct spdk_iscsi_task *task,
+ struct spdk_iscsi_task *primary)
+{
+ struct spdk_iscsi_task *tmp;
+
+ if (task->scsi.status != SPDK_SCSI_STATUS_GOOD) {
+ TAILQ_FOREACH(tmp, &primary->subtask_list, subtask_link) {
+ spdk_scsi_task_copy_status(&tmp->scsi, &task->scsi);
+ }
+ }
+
+ if ((task != primary) &&
+ (task->scsi.offset != primary->bytes_completed)) {
+ TAILQ_FOREACH(tmp, &primary->subtask_list, subtask_link) {
+ if (task->scsi.offset < tmp->scsi.offset) {
+ TAILQ_INSERT_BEFORE(tmp, task, subtask_link);
+ return;
+ }
+ }
+
+ TAILQ_INSERT_TAIL(&primary->subtask_list, task, subtask_link);
+ return;
+ }
+
+ primary->bytes_completed += task->scsi.length;
+ spdk_iscsi_task_response(conn, task);
+
+ if ((task != primary) ||
+ (task->scsi.transfer_len == task->scsi.length)) {
+ spdk_iscsi_task_put(task);
+ }
+ process_completed_read_subtask_list(conn, primary);
+
+ spdk_iscsi_conn_handle_queued_datain_tasks(conn);
+}
+
+void
+spdk_iscsi_task_cpl(struct spdk_scsi_task *scsi_task)
+{
+ struct spdk_iscsi_task *primary;
+ struct spdk_iscsi_task *task = spdk_iscsi_task_from_scsi_task(scsi_task);
+ struct spdk_iscsi_conn *conn = task->conn;
+ struct spdk_iscsi_pdu *pdu = task->pdu;
+
+ spdk_trace_record(TRACE_ISCSI_TASK_DONE, conn->id, 0, (uintptr_t)task, 0);
+
+ task->is_queued = false;
+ primary = spdk_iscsi_task_get_primary(task);
+
+ if (spdk_iscsi_task_is_read(primary)) {
+ process_read_task_completion(conn, task, primary);
+ } else {
+ primary->bytes_completed += task->scsi.length;
+ if (task != primary) {
+ if (task->scsi.status == SPDK_SCSI_STATUS_GOOD) {
+ primary->scsi.data_transferred += task->scsi.data_transferred;
+ } else {
+ spdk_scsi_task_copy_status(&primary->scsi, &task->scsi);
+ }
+ }
+
+ if (primary->bytes_completed == primary->scsi.transfer_len) {
+ spdk_del_transfer_task(conn, primary->tag);
+ spdk_iscsi_task_response(conn, primary);
+ /*
+ * Check if this is the last task completed for an iSCSI write
+ * that required child subtasks. If task != primary, we know
+ * for sure that it was part of an iSCSI write with child subtasks.
+ * The trickier case is when the last task completed was the initial
+ * task - in this case the task will have a smaller length than
+ * the overall transfer length.
+ */
+ if (task != primary || task->scsi.length != task->scsi.transfer_len) {
+ TAILQ_REMOVE(&conn->active_r2t_tasks, primary, link);
+ spdk_iscsi_task_put(primary);
+ }
+ }
+ spdk_iscsi_task_put(task);
+ }
+ if (!task->parent) {
+ spdk_trace_record(TRACE_ISCSI_PDU_COMPLETED, 0, 0, (uintptr_t)pdu, 0);
+ }
+}
+
+static int
+spdk_iscsi_get_pdu_length(struct spdk_iscsi_pdu *pdu, int header_digest,
+ int data_digest)
+{
+ int data_len, enable_digest, total;
+
+ enable_digest = 1;
+ if (pdu->bhs.opcode == ISCSI_OP_LOGIN_RSP) {
+ enable_digest = 0;
+ }
+
+ total = ISCSI_BHS_LEN;
+
+ total += (4 * pdu->bhs.total_ahs_len);
+
+ if (enable_digest && header_digest) {
+ total += ISCSI_DIGEST_LEN;
+ }
+
+ data_len = DGET24(pdu->bhs.data_segment_len);
+ if (data_len > 0) {
+ total += ISCSI_ALIGN(data_len);
+ if (enable_digest && data_digest) {
+ total += ISCSI_DIGEST_LEN;
+ }
+ }
+
+ return total;
+}
+
+void
+spdk_iscsi_conn_handle_nop(struct spdk_iscsi_conn *conn)
+{
+ uint64_t tsc;
+
+ /**
+ * This function will be executed by nop_poller of iSCSI polling group, so
+ * we need to check the connection state first, then do the nop interval
+ * expiration check work.
+ */
+ if ((conn->state == ISCSI_CONN_STATE_EXITED) ||
+ (conn->state == ISCSI_CONN_STATE_EXITING)) {
+ return;
+ }
+
+ /* Check for nop interval expiration */
+ tsc = spdk_get_ticks();
+ if (conn->nop_outstanding) {
+ if ((tsc - conn->last_nopin) > (conn->timeout * spdk_get_ticks_hz())) {
+ SPDK_ERRLOG("Timed out waiting for NOP-Out response from initiator\n");
+ SPDK_ERRLOG(" tsc=0x%lx, last_nopin=0x%lx\n", tsc, conn->last_nopin);
+ SPDK_ERRLOG(" initiator=%s, target=%s\n", conn->initiator_name,
+ conn->target_short_name);
+ conn->state = ISCSI_CONN_STATE_EXITING;
+ }
+ } else if (tsc - conn->last_nopin > conn->nopininterval) {
+ spdk_iscsi_send_nopin(conn);
+ }
+}
+
+/**
+ * \brief Makes one attempt to flush response PDUs back to the initiator.
+ *
+ * Builds a list of iovecs for response PDUs that must be sent back to the
+ * initiator and passes it to writev().
+ *
+ * Since the socket is non-blocking, writev() may not be able to flush all
+ * of the iovecs, and may even partially flush one of the iovecs. In this
+ * case, the partially flushed PDU will remain on the write_pdu_list with
+ * an offset pointing to the next byte to be flushed.
+ *
+ * Returns 0 if all PDUs were flushed.
+ *
+ * Returns 1 if some PDUs could not be flushed due to lack of send buffer
+ * space.
+ *
+ * Returns -1 if an exception error occurred indicating the TCP connection
+ * should be closed.
+ */
+static int
+spdk_iscsi_conn_flush_pdus_internal(struct spdk_iscsi_conn *conn)
+{
+ const int array_size = 32;
+ struct iovec iovec_array[array_size];
+ struct iovec *iov = iovec_array;
+ int iovec_cnt = 0;
+ int bytes = 0;
+ int total_length = 0;
+ uint32_t writev_offset;
+ struct spdk_iscsi_pdu *pdu;
+ int pdu_length;
+
+ pdu = TAILQ_FIRST(&conn->write_pdu_list);
+
+ if (pdu == NULL) {
+ return 0;
+ }
+
+ /*
+ * Build up a list of iovecs for the first few PDUs in the
+ * connection's write_pdu_list.
+ */
+ while (pdu != NULL && ((array_size - iovec_cnt) >= 5)) {
+ pdu_length = spdk_iscsi_get_pdu_length(pdu,
+ conn->header_digest,
+ conn->data_digest);
+ iovec_cnt += spdk_iscsi_build_iovecs(conn,
+ &iovec_array[iovec_cnt],
+ pdu);
+ total_length += pdu_length;
+ pdu = TAILQ_NEXT(pdu, tailq);
+ }
+
+ /*
+ * Check if the first PDU was partially written out the last time
+ * this function was called, and if so adjust the iovec array
+ * accordingly.
+ */
+ writev_offset = TAILQ_FIRST(&conn->write_pdu_list)->writev_offset;
+ total_length -= writev_offset;
+ while (writev_offset > 0) {
+ if (writev_offset >= iov->iov_len) {
+ writev_offset -= iov->iov_len;
+ iov++;
+ iovec_cnt--;
+ } else {
+ iov->iov_len -= writev_offset;
+ iov->iov_base = (char *)iov->iov_base + writev_offset;
+ writev_offset = 0;
+ }
+ }
+
+ spdk_trace_record(TRACE_ISCSI_FLUSH_WRITEBUF_START, conn->id, total_length, 0, iovec_cnt);
+
+ bytes = spdk_sock_writev(conn->sock, iov, iovec_cnt);
+ if (bytes == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ return 1;
+ } else {
+ SPDK_ERRLOG("spdk_sock_writev() failed, errno %d: %s\n",
+ errno, spdk_strerror(errno));
+ return -1;
+ }
+ }
+
+ spdk_trace_record(TRACE_ISCSI_FLUSH_WRITEBUF_DONE, conn->id, bytes, 0, 0);
+
+ pdu = TAILQ_FIRST(&conn->write_pdu_list);
+
+ /*
+ * Free any PDUs that were fully written. If a PDU was only
+ * partially written, update its writev_offset so that next
+ * time only the unwritten portion will be sent to writev().
+ */
+ while (bytes > 0) {
+ pdu_length = spdk_iscsi_get_pdu_length(pdu,
+ conn->header_digest,
+ conn->data_digest);
+ pdu_length -= pdu->writev_offset;
+
+ if (bytes >= pdu_length) {
+ bytes -= pdu_length;
+ TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq);
+
+ if ((conn->full_feature) &&
+ (conn->sess->ErrorRecoveryLevel >= 1) &&
+ spdk_iscsi_is_deferred_free_pdu(pdu)) {
+ SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "stat_sn=%d\n",
+ from_be32(&pdu->bhs.stat_sn));
+ TAILQ_INSERT_TAIL(&conn->snack_pdu_list, pdu,
+ tailq);
+ } else {
+ spdk_iscsi_conn_free_pdu(conn, pdu);
+ }
+
+ pdu = TAILQ_FIRST(&conn->write_pdu_list);
+ } else {
+ pdu->writev_offset += bytes;
+ bytes = 0;
+ }
+ }
+
+ return TAILQ_EMPTY(&conn->write_pdu_list) ? 0 : 1;
+}
+
+/**
+ * \brief Flushes response PDUs back to the initiator.
+ *
+ * This function may return without all PDUs having flushed to the
+ * underlying TCP socket buffer - for example, in the case where the
+ * socket buffer is already full.
+ *
+ * During normal RUNNING connection state, if not all PDUs are flushed,
+ * then subsequent calls to this routine will eventually flush
+ * remaining PDUs.
+ *
+ * During other connection states (EXITING or LOGGED_OUT), this
+ * function will spin until all PDUs have successfully been flushed.
+ *
+ * Returns 0 for success and when all PDUs were able to be flushed.
+ *
+ * Returns 1 for success but when some PDUs could not be flushed due
+ * to lack of TCP buffer space.
+ *
+ * Returns -1 for an exceptional error indicating the TCP connection
+ * should be closed.
+ */
+static int
+spdk_iscsi_conn_flush_pdus(void *_conn)
+{
+ struct spdk_iscsi_conn *conn = _conn;
+ int rc;
+
+ if (conn->state == ISCSI_CONN_STATE_RUNNING) {
+ rc = spdk_iscsi_conn_flush_pdus_internal(conn);
+ if (rc == 0 && conn->flush_poller != NULL) {
+ spdk_poller_unregister(&conn->flush_poller);
+ } else if (rc == 1 && conn->flush_poller == NULL) {
+ conn->flush_poller = spdk_poller_register(spdk_iscsi_conn_flush_pdus, conn, 50);
+ }
+ } else {
+ /*
+ * If the connection state is not RUNNING, then
+ * keep trying to flush PDUs until our list is
+ * empty - to make sure all data is sent before
+ * closing the connection.
+ */
+ do {
+ rc = spdk_iscsi_conn_flush_pdus_internal(conn);
+ } while (rc == 1);
+ }
+
+ if (rc < 0 && conn->state < ISCSI_CONN_STATE_EXITING) {
+ /*
+ * If the poller has already started destruction of the connection,
+ * i.e. the socket read failed, then the connection state may already
+ * be EXITED. We don't want to set it back to EXITING in that case.
+ */
+ conn->state = ISCSI_CONN_STATE_EXITING;
+ }
+
+ return -1;
+}
+
+void
+spdk_iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu)
+{
+ TAILQ_INSERT_TAIL(&conn->write_pdu_list, pdu, tailq);
+ spdk_iscsi_conn_flush_pdus(conn);
+}
+
+#define GET_PDU_LOOP_COUNT 16
+
+static int
+spdk_iscsi_conn_handle_incoming_pdus(struct spdk_iscsi_conn *conn)
+{
+ struct spdk_iscsi_pdu *pdu;
+ int i, rc;
+
+ /* Read new PDUs from network */
+ for (i = 0; i < GET_PDU_LOOP_COUNT; i++) {
+ rc = spdk_iscsi_read_pdu(conn, &pdu);
+ if (rc == 0) {
+ break;
+ } else if (rc == SPDK_ISCSI_CONNECTION_FATAL) {
+ return rc;
+ }
+
+ if (conn->state == ISCSI_CONN_STATE_LOGGED_OUT) {
+ SPDK_ERRLOG("pdu received after logout\n");
+ spdk_put_pdu(pdu);
+ return SPDK_ISCSI_CONNECTION_FATAL;
+ }
+
+ rc = spdk_iscsi_execute(conn, pdu);
+ spdk_put_pdu(pdu);
+ if (rc != 0) {
+ SPDK_ERRLOG("spdk_iscsi_execute() fatal error on %s(%s)\n",
+ conn->target_port != NULL ? spdk_scsi_port_get_name(conn->target_port) : "NULL",
+ conn->initiator_port != NULL ? spdk_scsi_port_get_name(conn->initiator_port) : "NULL");
+ return rc;
+ }
+
+ spdk_trace_record(TRACE_ISCSI_TASK_EXECUTED, 0, 0, (uintptr_t)pdu, 0);
+ if (conn->is_stopped) {
+ break;
+ }
+ }
+
+ return i;
+}
+
+static void
+spdk_iscsi_conn_sock_cb(void *arg, struct spdk_sock_group *group, struct spdk_sock *sock)
+{
+ struct spdk_iscsi_conn *conn = arg;
+ int rc;
+
+ assert(conn != NULL);
+
+ if ((conn->state == ISCSI_CONN_STATE_EXITED) ||
+ (conn->state == ISCSI_CONN_STATE_EXITING)) {
+ return;
+ }
+
+ /* Handle incoming PDUs */
+ rc = spdk_iscsi_conn_handle_incoming_pdus(conn);
+ if (rc < 0) {
+ conn->state = ISCSI_CONN_STATE_EXITING;
+ spdk_iscsi_conn_flush_pdus(conn);
+ }
+}
+
+static void
+spdk_iscsi_conn_full_feature_migrate(void *arg1, void *arg2)
+{
+ struct spdk_iscsi_conn *conn = arg1;
+
+ if (conn->sess->session_type == SESSION_TYPE_NORMAL) {
+ spdk_iscsi_conn_open_luns(conn);
+ }
+
+ /* The poller has been unregistered, so now we can re-register it on the new core. */
+ conn->lcore = spdk_env_get_current_core();
+ spdk_iscsi_poll_group_add_conn(conn);
+}
+
+void
+spdk_iscsi_conn_migration(struct spdk_iscsi_conn *conn)
+{
+ int lcore;
+ struct spdk_event *event;
+ struct spdk_iscsi_tgt_node *target;
+
+ lcore = spdk_iscsi_conn_allocate_reactor(conn->portal->cpumask);
+ if (conn->sess->session_type == SESSION_TYPE_NORMAL) {
+ target = conn->sess->target;
+ pthread_mutex_lock(&target->mutex);
+ target->num_active_conns++;
+ if (target->num_active_conns == 1) {
+ /**
+ * This is the only active connection for this target node.
+ * Save the lcore in the target node so it can be used for
+ * any other connections to this target node.
+ */
+ target->lcore = lcore;
+ } else {
+ /**
+ * There are other active connections for this target node.
+ * Ignore the lcore specified by the allocator and use the
+ * the target node's lcore to ensure this connection runs on
+ * the same lcore as other connections for this target node.
+ */
+ lcore = target->lcore;
+ }
+ pthread_mutex_unlock(&target->mutex);
+ }
+
+ spdk_iscsi_poll_group_remove_conn_sock(conn);
+ spdk_poller_unregister(&conn->flush_poller);
+ spdk_iscsi_conn_stop(conn);
+
+ __sync_fetch_and_add(&g_num_connections[lcore], 1);
+ conn->last_nopin = spdk_get_ticks();
+ event = spdk_event_allocate(lcore, spdk_iscsi_conn_full_feature_migrate,
+ conn, NULL);
+ spdk_event_call(event);
+}
+
+void
+spdk_iscsi_conn_set_min_per_core(int count)
+{
+ g_connections_per_lcore = count;
+}
+
+int
+spdk_iscsi_conn_get_min_per_core(void)
+{
+ return g_connections_per_lcore;
+}
+
+static uint32_t
+spdk_iscsi_conn_allocate_reactor(const struct spdk_cpuset *cpumask)
+{
+ uint32_t i, selected_core;
+ int32_t num_pollers, min_pollers;
+
+ min_pollers = INT_MAX;
+ selected_core = spdk_env_get_first_core();
+
+ SPDK_ENV_FOREACH_CORE(i) {
+ if (!spdk_cpuset_get_cpu(cpumask, i)) {
+ continue;
+ }
+
+ /* This core is running. Check how many pollers it already has. */
+ num_pollers = g_num_connections[i];
+
+ if ((num_pollers > 0) && (num_pollers < g_connections_per_lcore)) {
+ /* Fewer than the maximum connections per core,
+ * but at least 1. Use this core.
+ */
+ return i;
+ } else if (num_pollers < min_pollers) {
+ /* Track the core that has the minimum number of pollers
+ * to be used if no cores meet our criteria
+ */
+ selected_core = i;
+ min_pollers = num_pollers;
+ }
+ }
+
+ return selected_core;
+}
+
+static int
+logout_timeout(void *arg)
+{
+ struct spdk_iscsi_conn *conn = arg;
+
+ spdk_iscsi_conn_destruct(conn);
+
+ return -1;
+}
+
+void
+spdk_iscsi_conn_logout(struct spdk_iscsi_conn *conn)
+{
+ conn->state = ISCSI_CONN_STATE_LOGGED_OUT;
+ conn->logout_timer = spdk_poller_register(logout_timeout, conn, ISCSI_LOGOUT_TIMEOUT * 1000000);
+}
+
+SPDK_TRACE_REGISTER_FN(iscsi_conn_trace)
+{
+ spdk_trace_register_owner(OWNER_ISCSI_CONN, 'c');
+ spdk_trace_register_object(OBJECT_ISCSI_PDU, 'p');
+ spdk_trace_register_description("ISCSI_READ_FROM_SOCKET_DONE", "",
+ TRACE_ISCSI_READ_FROM_SOCKET_DONE,
+ OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, "");
+ spdk_trace_register_description("ISCSI_FLUSH_WRITEBUF_START", "", TRACE_ISCSI_FLUSH_WRITEBUF_START,
+ OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, "iovec: ");
+ spdk_trace_register_description("ISCSI_FLUSH_WRITEBUF_DONE", "", TRACE_ISCSI_FLUSH_WRITEBUF_DONE,
+ OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, "");
+ spdk_trace_register_description("ISCSI_READ_PDU", "", TRACE_ISCSI_READ_PDU,
+ OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 1, 0, "opc: ");
+ spdk_trace_register_description("ISCSI_TASK_DONE", "", TRACE_ISCSI_TASK_DONE,
+ OWNER_ISCSI_CONN, OBJECT_SCSI_TASK, 0, 0, "");
+ spdk_trace_register_description("ISCSI_TASK_QUEUE", "", TRACE_ISCSI_TASK_QUEUE,
+ OWNER_ISCSI_CONN, OBJECT_SCSI_TASK, 1, 1, "pdu: ");
+ spdk_trace_register_description("ISCSI_TASK_EXECUTED", "", TRACE_ISCSI_TASK_EXECUTED,
+ OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 0, 0, "");
+ spdk_trace_register_description("ISCSI_PDU_COMPLETED", "", TRACE_ISCSI_PDU_COMPLETED,
+ OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 0, 0, "");
+}