From 1313e291942721cfeffbadbad5f52547d3af1e96 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 3 Aug 2011 12:15:19 +0000 Subject: [PATCH] ib_srpt: Avoid that removing ib_srpt while I/O is ongoing sometimes triggers a hang git-svn-id: http://svn.code.sf.net/p/scst/svn/branches/2.1.0.x@3795 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- srpt/src/ib_srpt.c | 539 +++++++++++++++++++++++++-------------------- srpt/src/ib_srpt.h | 18 +- 2 files changed, 317 insertions(+), 240 deletions(-) diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 903f9eac5..7703cf196 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -172,7 +172,7 @@ static void srpt_unregister_procfs_entry(struct scst_tgt_template *tgt); #endif /*CONFIG_SCST_PROC*/ static void srpt_unmap_sg_to_ib_sge(struct srpt_rdma_ch *ch, struct srpt_send_ioctx *ioctx); -static void srpt_release_channel(struct scst_session *scst_sess); +static void srpt_free_ch(struct scst_session *sess); static struct ib_client srpt_client = { .name = DRV_NAME, @@ -180,22 +180,62 @@ static struct ib_client srpt_client = { .remove = srpt_remove_one }; +static enum rdma_ch_state srpt_set_ch_state_to_disc(struct srpt_rdma_ch *ch) +{ + unsigned long flags; + enum rdma_ch_state prev; + + spin_lock_irqsave(&ch->spinlock, flags); + prev = atomic_read(&ch->state); + switch (prev) { + case CH_CONNECTING: + case CH_LIVE: + atomic_set(&ch->state, CH_DISCONNECTING); + break; + default: + break; + } + spin_unlock_irqrestore(&ch->spinlock, flags); + + return prev; +} + +static bool srpt_set_ch_state_to_draining(struct srpt_rdma_ch *ch) +{ + unsigned long flags; + bool changed_state = false; + + spin_lock_irqsave(&ch->spinlock, flags); + switch (atomic_read(&ch->state)) { + case CH_CONNECTING: + case CH_LIVE: + case CH_DISCONNECTING: + atomic_set(&ch->state, CH_DRAINING); + changed_state = true; + break; + default: + break; + } + spin_unlock_irqrestore(&ch->spinlock, flags); + + return changed_state; +} + /** * srpt_test_and_set_channel_state() - Test and set the channel state. * * @ch: RDMA channel. * @old: channel state to compare with. - * @new: state to change the channel state to if the current state matches the - * argument 'old'. + * @new: state to change the channel state to if the current state matches @old. * - * Returns the previous channel state. + * Returns true if and only if the channel state did match @old. */ -static enum rdma_ch_state +static bool srpt_test_and_set_channel_state(struct srpt_rdma_ch *ch, enum rdma_ch_state old, enum rdma_ch_state new) { - return atomic_cmpxchg(&ch->state, old, new); + return atomic_cmpxchg(&ch->state, old, new) == old; } /** @@ -283,11 +323,14 @@ static void srpt_qp_event(struct ib_event *event, struct srpt_rdma_ch *ch) #endif break; case IB_EVENT_QP_LAST_WQE_REACHED: - if (srpt_test_and_set_channel_state(ch, RDMA_CHANNEL_LIVE, - RDMA_CHANNEL_DISCONNECTING) == RDMA_CHANNEL_LIVE) { - PRINT_INFO("disconnected session %s.", ch->sess_name); - ib_send_cm_dreq(ch->cm_id, NULL, 0); - } + TRACE_DBG("%s: received IB_EVENT_QP_LAST_WQE_REACHED", + ch->sess_name); + if (srpt_test_and_set_channel_state(ch, CH_DRAINING, + CH_RELEASING)) + wake_up_process(ch->thread); + else + TRACE_DBG("%s: state %d - ignored LAST_WQE.", + ch->sess_name, atomic_read(&ch->state)); break; default: PRINT_ERROR("received unrecognized IB QP event %d", @@ -745,7 +788,7 @@ err: srpt_free_ioctx(sdev, ring[i], dma_size, dir); kfree(ring); out: - TRACE_EXIT_RES(ring); + TRACE_EXIT_HRES(ring); return ring; } @@ -1038,6 +1081,8 @@ static int srpt_ch_qp_rtr(struct srpt_rdma_ch *ch, struct ib_qp *qp) int attr_mask; int ret; + TRACE_ENTRY(); + qp_attr.qp_state = IB_QPS_RTR; ret = ib_cm_init_qp_attr(ch->cm_id, &qp_attr, &attr_mask); if (ret) @@ -1048,6 +1093,7 @@ static int srpt_ch_qp_rtr(struct srpt_rdma_ch *ch, struct ib_qp *qp) ret = ib_modify_qp(qp, &qp_attr, attr_mask); out: + TRACE_EXIT_RES(ret); return ret; } @@ -1057,40 +1103,42 @@ out: * @qp: queue pair to change the state of. * * Returns zero upon success and a negative value upon failure. - * - * Note: currently a struct ib_qp_attr takes 136 bytes on a 64-bit system. - * If this structure ever becomes larger, it might be necessary to allocate - * it dynamically instead of on the stack. */ static int srpt_ch_qp_rts(struct srpt_rdma_ch *ch, struct ib_qp *qp) { - struct ib_qp_attr qp_attr; + struct ib_qp_attr *attr; int attr_mask; int ret; uint64_t T_tr_ns; uint32_t max_compl_time_ms; - qp_attr.qp_state = IB_QPS_RTS; - ret = ib_cm_init_qp_attr(ch->cm_id, &qp_attr, &attr_mask); + TRACE_ENTRY(); + + attr = kzalloc(sizeof *attr, GFP_KERNEL); + if (!attr) + return -ENOMEM; + + attr->qp_state = IB_QPS_RTS; + ret = ib_cm_init_qp_attr(ch->cm_id, attr, &attr_mask); if (ret) goto out; - qp_attr.max_rd_atomic = 4; + attr->max_rd_atomic = 4; /* * From IBTA C9-140: Transport Timer timeout interval * T_tr = 4.096 us * 2**(local ACK timeout) where the local ACK timeout * is a five-bit value, with zero meaning that the timer is disabled. */ - WARN_ON(qp_attr.timeout < 0 || qp_attr.timeout >= (1 << 5)); - if (qp_attr.timeout) { - T_tr_ns = 1ULL << (12 + qp_attr.timeout); - max_compl_time_ms = qp_attr.retry_cnt * 4 * T_tr_ns / 1000000; + WARN_ON(attr->timeout >= (1 << 5)); + if (attr->timeout) { + T_tr_ns = 1ULL << (12 + attr->timeout); + max_compl_time_ms = attr->retry_cnt * 4 * T_tr_ns / 1000000; TRACE_DBG("Session %s: QP local ack timeout = %d or T_tr =" " %u ms; retry_cnt = %d; max compl. time = %d ms", ch->sess_name, - qp_attr.timeout, (unsigned)(T_tr_ns / (1000 * 1000)), - qp_attr.retry_cnt, max_compl_time_ms); + attr->timeout, (unsigned)(T_tr_ns / (1000 * 1000)), + attr->retry_cnt, max_compl_time_ms); if (max_compl_time_ms >= RDMA_COMPL_TIMEOUT_S * 1000) { PRINT_ERROR("Maximum RDMA completion time (%d ms)" @@ -1100,9 +1148,33 @@ static int srpt_ch_qp_rts(struct srpt_rdma_ch *ch, struct ib_qp *qp) } } - ret = ib_modify_qp(qp, &qp_attr, attr_mask); + ret = ib_modify_qp(qp, attr, attr_mask); out: + kfree(attr); + TRACE_EXIT_RES(ret); + return ret; +} + +/** + * srpt_ch_qp_err() - Set the channel queue pair state to 'error'. + */ +static int srpt_ch_qp_err(struct srpt_rdma_ch *ch) +{ + struct ib_qp_attr *attr; + int ret; + + TRACE_ENTRY(); + + attr = kzalloc(sizeof *attr, GFP_KERNEL); + if (!attr) + return -ENOMEM; + + attr->qp_state = IB_QPS_ERR; + ret = ib_modify_qp(ch->qp, attr, IB_QP_STATE); + kfree(attr); + + TRACE_EXIT_RES(ret); return ret; } @@ -1725,14 +1797,11 @@ static void srpt_handle_new_iu(struct srpt_rdma_ch *ch, ch_state = atomic_read(&ch->state); srp_cmd = recv_ioctx->ioctx.buf; - if (unlikely(ch_state == RDMA_CHANNEL_CONNECTING)) { + if (unlikely(ch_state == CH_CONNECTING)) { list_add_tail(&recv_ioctx->wait_list, &ch->cmd_wait_list); goto out; } - if (unlikely(ch_state == RDMA_CHANNEL_DISCONNECTING)) - goto post_recv; - if (srp_cmd->opcode == SRP_CMD || srp_cmd->opcode == SRP_TSK_MGMT) { if (!send_ioctx) send_ioctx = srpt_get_send_ioctx(ch); @@ -1743,8 +1812,6 @@ static void srpt_handle_new_iu(struct srpt_rdma_ch *ch, } } - WARN_ON(ch_state != RDMA_CHANNEL_LIVE); - switch (srp_cmd->opcode) { case SRP_CMD: srpt_handle_cmd(ch, recv_ioctx, send_ioctx, context); @@ -1770,7 +1837,6 @@ static void srpt_handle_new_iu(struct srpt_rdma_ch *ch, break; } -post_recv: srpt_post_recv(ch->sport->sdev, recv_ioctx); out: return; @@ -1850,7 +1916,7 @@ static void srpt_process_send_completion(struct ib_cq *cq, while (unlikely(opcode == SRPT_SEND && !list_empty(&ch->cmd_wait_list) - && atomic_read(&ch->state) == RDMA_CHANNEL_LIVE + && atomic_read(&ch->state) == CH_LIVE && (send_ioctx = srpt_get_send_ioctx(ch)) != NULL)) { struct srpt_recv_ioctx *recv_ioctx; @@ -1862,16 +1928,19 @@ static void srpt_process_send_completion(struct ib_cq *cq, } } -static void srpt_process_completion(struct ib_cq *cq, +static bool srpt_process_completion(struct ib_cq *cq, struct srpt_rdma_ch *ch, enum scst_exec_context context) { struct ib_wc *const wc = ch->wc; int i, n; + bool keep_going; EXTRACHECKS_WARN_ON(cq != ch->cq); - ib_req_notify_cq(cq, IB_CQ_NEXT_COMP); + keep_going = atomic_read(&ch->state) <= CH_LIVE; + if (keep_going) + ib_req_notify_cq(cq, IB_CQ_NEXT_COMP); while ((n = ib_poll_cq(cq, ARRAY_SIZE(ch->wc), wc)) > 0) { for (i = 0; i < n; i++) { if (opcode_from_wr_id(wc[i].wr_id) == SRPT_RECV) @@ -1882,6 +1951,8 @@ static void srpt_process_completion(struct ib_cq *cq, &wc[i]); } } + + return keep_going; } /** @@ -1903,7 +1974,7 @@ static void srpt_completion(struct ib_cq *cq, void *ctx) atomic_inc(&ch->processing_compl); switch (thread) { case MODE_IB_COMPLETION_IN_THREAD: - wake_up_interruptible(&ch->wait_queue); + wake_up_process(ch->thread); break; case MODE_IB_COMPLETION_IN_SIRQ: srpt_process_completion(cq, ch, SCST_CONTEXT_THREAD); @@ -1924,16 +1995,19 @@ static int srpt_compl_thread(void *arg) ch = arg; BUG_ON(!ch); - PRINT_INFO("Session %s: kernel thread %s (PID %d) started", - ch->sess_name, ch->thread->comm, current->pid); while (!kthread_should_stop()) { - wait_event_interruptible(ch->wait_queue, - (srpt_process_completion(ch->cq, ch, - SCST_CONTEXT_THREAD), - kthread_should_stop())); + set_current_state(TASK_INTERRUPTIBLE); + if (!srpt_process_completion(ch->cq, ch, SCST_CONTEXT_THREAD)) + break; + schedule(); } - PRINT_INFO("Session %s: kernel thread %s (PID %d) stopped", - ch->sess_name, ch->thread->comm, current->pid); + set_current_state(TASK_RUNNING); + + TRACE_DBG("ch %s: about to invoke scst_unregister_session()", + ch->sess_name); + WARN_ON(atomic_read(&ch->state) != CH_RELEASING); + scst_unregister_session(ch->scst_sess, false, srpt_free_ch); + while (!kthread_should_stop()) { set_current_state(TASK_INTERRUPTIBLE); schedule(); @@ -2003,8 +2077,6 @@ static int srpt_create_ch_ib(struct srpt_rdma_ch *ch) goto err_destroy_qp; if (thread == MODE_IB_COMPLETION_IN_THREAD) { - init_waitqueue_head(&ch->wait_queue); - TRACE_DBG("creating IB completion thread for session %s", ch->sess_name); @@ -2032,140 +2104,123 @@ err_destroy_cq: static void srpt_destroy_ch_ib(struct srpt_rdma_ch *ch) { - if (ch->thread) - kthread_stop(ch->thread); + TRACE_ENTRY(); + + while (ib_poll_cq(ch->cq, ARRAY_SIZE(ch->wc), ch->wc) > 0) + ; ib_destroy_qp(ch->qp); ib_destroy_cq(ch->cq); -} - -/** - * srpt_unregister_channel() - Start RDMA channel disconnection. - * - * Note: The caller must hold ch->sdev->spinlock. - */ -static void srpt_unregister_channel(struct srpt_rdma_ch *ch) - __acquires(&ch->sport->sdev->spinlock) - __releases(&ch->sport->sdev->spinlock) -{ - struct srpt_device *sdev; - struct ib_qp_attr qp_attr; - int ret; - - sdev = ch->sport->sdev; - list_del(&ch->list); - atomic_set(&ch->state, RDMA_CHANNEL_DISCONNECTING); - spin_unlock_irq(&sdev->spinlock); - - qp_attr.qp_state = IB_QPS_ERR; - ret = ib_modify_qp(ch->qp, &qp_attr, IB_QP_STATE); - if (ret < 0) - PRINT_ERROR("Setting queue pair in error state failed: %d", - ret); - - while (atomic_read(&ch->processing_compl)) - ; - - /* - * At this point it is guaranteed that no new commands will be sent to - * the SCST core for channel ch, which is a requirement for - * scst_unregister_session(). - */ - - TRACE_DBG("unregistering session %p", ch->scst_sess); - scst_unregister_session(ch->scst_sess, 0, srpt_release_channel); - spin_lock_irq(&sdev->spinlock); -} - -/** - * srpt_release_channel_by_cmid() - Release a channel. - * @cm_id: Pointer to the CM ID of the channel to be released. - * - * Note: Must be called from inside srpt_cm_handler to avoid a race between - * accessing sdev->spinlock and the call to kfree(sdev) in srpt_remove_one() - * (the caller of srpt_cm_handler holds the cm_id spinlock; srpt_remove_one() - * waits until all SCST sessions for the associated IB device have been - * unregistered and SCST session registration involves a call to - * ib_destroy_cm_id(), which locks the cm_id spinlock and hence waits until - * this function has finished). - */ -static void srpt_release_channel_by_cmid(struct ib_cm_id *cm_id) -{ - struct srpt_device *sdev; - struct srpt_rdma_ch *ch; - - TRACE_ENTRY(); - - EXTRACHECKS_WARN_ON_ONCE(irqs_disabled()); - - sdev = cm_id->context; - BUG_ON(!sdev); - spin_lock_irq(&sdev->spinlock); - list_for_each_entry(ch, &sdev->rch_list, list) { - if (ch->cm_id == cm_id) { - srpt_unregister_channel(ch); - break; - } - } - spin_unlock_irq(&sdev->spinlock); TRACE_EXIT(); } /** - * srpt_find_channel() - Look up an RDMA channel. - * @cm_id: Pointer to the CM ID of the channel to be looked up. + * __srpt_close_ch() - Close an RDMA channel by setting the QP error state. * - * Return NULL if no matching RDMA channel has been found. + * Reset the QP and make sure all resources associated with the channel will + * be deallocated at an appropriate time. + * + * Returns true if and only if the channel state has been modified from + * CH_CONNECTING or CH_LIVE into CH_DISCONNECTING. + * + * Note: The caller must hold ch->sport->sdev->spinlock. */ -static struct srpt_rdma_ch *srpt_find_channel(struct srpt_device *sdev, - struct ib_cm_id *cm_id) +static bool __srpt_close_ch(struct srpt_rdma_ch *ch) { - struct srpt_rdma_ch *ch; - bool found; + struct srpt_device *sdev; + enum rdma_ch_state prev_state; + bool was_live; - EXTRACHECKS_WARN_ON_ONCE(irqs_disabled()); - BUG_ON(!sdev); + sdev = ch->sport->sdev; + was_live = false; - found = false; - spin_lock_irq(&sdev->spinlock); - list_for_each_entry(ch, &sdev->rch_list, list) { - if (ch->cm_id == cm_id) { - found = true; - break; - } + prev_state = srpt_set_ch_state_to_disc(ch); + + switch (prev_state) { + case CH_CONNECTING: + ib_send_cm_rej(ch->cm_id, IB_CM_REJ_NO_RESOURCES, NULL, 0, + NULL, 0); + /* fall through */ + case CH_LIVE: + was_live = true; + if (ib_send_cm_dreq(ch->cm_id, NULL, 0) < 0) + PRINT_ERROR("%s", "sending CM DREQ failed."); + break; + case CH_DISCONNECTING: + case CH_DRAINING: + case CH_RELEASING: + break; } - spin_unlock_irq(&sdev->spinlock); - return found ? ch : NULL; + return was_live; } /** - * srpt_release_channel() - Release all resources associated with an RDMA channel. - * - * Notes: - * - The caller must have removed the channel from the channel list before - * calling this function. - * - Must be called as a callback function via scst_unregister_session(). Never - * call this function directly because doing so would trigger several race - * conditions. - * - Do not access ch->sport or ch->sport->sdev in this function because the - * memory that was allocated for the sport and/or sdev data structures may - * already have been freed at the time this function is called. + * srpt_close_ch() - Close an RDMA channel. */ -static void srpt_release_channel(struct scst_session *scst_sess) +static void srpt_close_ch(struct srpt_rdma_ch *ch) +{ + struct srpt_device *sdev; + + sdev = ch->sport->sdev; + spin_lock_irq(&sdev->spinlock); + __srpt_close_ch(ch); + spin_unlock_irq(&sdev->spinlock); +} + +/** + * srpt_drain_channel() - Drain a channel by resetting the IB queue pair. + * @cm_id: Pointer to the CM ID of the channel to be drained. + * + * Note: Must be called from inside srpt_cm_handler to avoid a race between + * accessing sdev->spinlock and the call to kfree(sdev) in srpt_remove_one() + * (the caller of srpt_cm_handler holds the cm_id spinlock; srpt_remove_one() + * waits until all target sessions for the associated IB device have been + * unregistered and target session registration involves a call to + * ib_destroy_cm_id(), which locks the cm_id spinlock and hence waits until + * this function has finished). + */ +static void srpt_drain_channel(struct ib_cm_id *cm_id) { struct srpt_rdma_ch *ch; + int ret; + + WARN_ON_ONCE(irqs_disabled()); + + ch = cm_id->context; + if (srpt_set_ch_state_to_draining(ch)) { + ret = srpt_ch_qp_err(ch); + if (ret < 0) + PRINT_ERROR("Setting queue pair in error state" + " failed: %d", ret); + } else + TRACE_DBG("Channel already in state %d", + atomic_read(&ch->state)); +} + +/** + * srpt_free_ch() - Release all resources associated with an RDMA channel. + */ +static void srpt_free_ch(struct scst_session *sess) +{ + struct srpt_rdma_ch *ch; + struct srpt_device *sdev; TRACE_ENTRY(); - ch = scst_sess_get_tgt_priv(scst_sess); + ch = scst_sess_get_tgt_priv(sess); BUG_ON(!ch); - WARN_ON(atomic_read(&ch->state) != RDMA_CHANNEL_DISCONNECTING); + BUG_ON(ch->scst_sess != sess); + sdev = ch->sport->sdev; + BUG_ON(!sdev); - TRACE_DBG("destroying cm_id %p", ch->cm_id); - BUG_ON(!ch->cm_id); - ib_destroy_cm_id(ch->cm_id); + WARN_ON(atomic_read(&ch->state) != CH_RELEASING); + + BUG_ON(!ch->thread); + BUG_ON(ch->thread == current); + kthread_stop(ch->thread); + ch->thread = NULL; srpt_destroy_ch_ib(ch); @@ -2173,6 +2228,16 @@ static void srpt_release_channel(struct scst_session *scst_sess) ch->sport->sdev, ch->rq_size, srp_max_rsp_size, DMA_TO_DEVICE); + spin_lock_irq(&sdev->spinlock); + list_del(&ch->list); + spin_unlock_irq(&sdev->spinlock); + + TRACE_DBG("destroying cm_id %p", ch->cm_id); + BUG_ON(!ch->cm_id); + ib_destroy_cm_id(ch->cm_id); + + wake_up(&sdev->ch_releaseQ); + kfree(ch); TRACE_EXIT(); @@ -2315,7 +2380,8 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, && param->port == ch->sport->port && param->listen_id == ch->sport->sdev->cm_id && ch->cm_id) { - enum rdma_ch_state prev_state; + if (!__srpt_close_ch(ch)) + continue; /* found an existing channel */ TRACE_DBG("Found existing channel name= %s" @@ -2323,33 +2389,8 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, ch->sess_name, ch->cm_id, atomic_read(&ch->state)); - prev_state = atomic_xchg(&ch->state, - RDMA_CHANNEL_DISCONNECTING); - if (prev_state == RDMA_CHANNEL_CONNECTING) - srpt_unregister_channel(ch); - - spin_unlock_irq(&sdev->spinlock); - rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_TERMINATED; - - if (prev_state == RDMA_CHANNEL_LIVE) { - ib_send_cm_dreq(ch->cm_id, NULL, 0); - PRINT_INFO("disconnected" - " session %s because a new" - " SRP_LOGIN_REQ has been received.", - ch->sess_name); - } else if (prev_state == - RDMA_CHANNEL_CONNECTING) { - PRINT_ERROR("%s", "rejected" - " SRP_LOGIN_REQ because another login" - " request is being processed."); - ib_send_cm_rej(ch->cm_id, - IB_CM_REJ_NO_RESOURCES, - NULL, 0, NULL, 0); - } - - spin_lock_irq(&sdev->spinlock); } } @@ -2383,13 +2424,14 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, memcpy(ch->t_port_id, req->target_port_id, 16); ch->sport = &sdev->port[param->port - 1]; ch->cm_id = cm_id; + cm_id->context = ch; /* * Avoid QUEUE_FULL conditions by limiting the number of buffers used * for the SRP protocol to the SCST SCSI command queue size. */ ch->rq_size = min(SRPT_RQ_SIZE, scst_get_max_lun_commands(NULL, 0)); atomic_set(&ch->processing_compl, 0); - atomic_set(&ch->state, RDMA_CHANNEL_CONNECTING); + atomic_set(&ch->state, CH_CONNECTING); INIT_LIST_HEAD(&ch->cmd_wait_list); spin_lock_init(&ch->spinlock); @@ -2500,8 +2542,8 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, goto out; release_channel: - atomic_set(&ch->state, RDMA_CHANNEL_DISCONNECTING); - scst_unregister_session(ch->scst_sess, 0, NULL); + atomic_set(&ch->state, CH_DISCONNECTING); + scst_unregister_session(ch->scst_sess, false, NULL); ch->scst_sess = NULL; destroy_ib: @@ -2535,7 +2577,7 @@ out: static void srpt_cm_rej_recv(struct ib_cm_id *cm_id) { PRINT_INFO("Received InfiniBand REJ packet for cm_id %p.", cm_id); - srpt_release_channel_by_cmid(cm_id); + srpt_drain_channel(cm_id); } /** @@ -2549,13 +2591,12 @@ static void srpt_cm_rtu_recv(struct ib_cm_id *cm_id) struct srpt_rdma_ch *ch; int ret; - ch = srpt_find_channel(cm_id->context, cm_id); - WARN_ON(!ch); - if (!ch) - goto out; + TRACE_ENTRY(); - if (srpt_test_and_set_channel_state(ch, RDMA_CHANNEL_CONNECTING, - RDMA_CHANNEL_LIVE) == RDMA_CHANNEL_CONNECTING) { + ch = cm_id->context; + BUG_ON(!ch); + + if (srpt_test_and_set_channel_state(ch, CH_CONNECTING, CH_LIVE)) { struct srpt_recv_ioctx *ioctx, *ioctx_tmp; ret = srpt_ch_qp_rts(ch, ch->qp); @@ -2566,30 +2607,23 @@ static void srpt_cm_rtu_recv(struct ib_cm_id *cm_id) srpt_handle_new_iu(ch, ioctx, NULL, SCST_CONTEXT_THREAD); } - if (ret && srpt_test_and_set_channel_state(ch, - RDMA_CHANNEL_LIVE, - RDMA_CHANNEL_DISCONNECTING) == RDMA_CHANNEL_LIVE) { - TRACE_DBG("cm_id=%p sess_name=%s state=%d", - cm_id, ch->sess_name, - atomic_read(&ch->state)); - ib_send_cm_dreq(ch->cm_id, NULL, 0); - } + if (ret) + srpt_close_ch(ch); } -out: - ; + TRACE_EXIT(); } static void srpt_cm_timewait_exit(struct ib_cm_id *cm_id) { PRINT_INFO("Received InfiniBand TimeWait exit for cm_id %p.", cm_id); - srpt_release_channel_by_cmid(cm_id); + srpt_drain_channel(cm_id); } static void srpt_cm_rep_error(struct ib_cm_id *cm_id) { PRINT_INFO("Received InfiniBand REP error for cm_id %p.", cm_id); - srpt_release_channel_by_cmid(cm_id); + srpt_drain_channel(cm_id); } /** @@ -2599,29 +2633,25 @@ static void srpt_cm_dreq_recv(struct ib_cm_id *cm_id) { struct srpt_rdma_ch *ch; - ch = srpt_find_channel(cm_id->context, cm_id); - if (!ch) { - TRACE_DBG("Received DREQ for channel %p which is already" - " being unregistered.", cm_id); - goto out; - } + ch = cm_id->context; - TRACE_DBG("cm_id= %p ch->state= %d", cm_id, atomic_read(&ch->state)); - - switch (atomic_read(&ch->state)) { - case RDMA_CHANNEL_LIVE: - case RDMA_CHANNEL_CONNECTING: - ib_send_cm_drep(ch->cm_id, NULL, 0); - PRINT_INFO("Received DREQ and sent DREP for session %s.", - ch->sess_name); + switch (srpt_set_ch_state_to_disc(ch)) { + case CH_CONNECTING: + case CH_LIVE: + if (ib_send_cm_drep(ch->cm_id, NULL, 0) >= 0) + PRINT_INFO("Received DREQ and sent DREP for session %s", + ch->sess_name); + else + PRINT_ERROR("%s", "Sending DREP failed"); break; - case RDMA_CHANNEL_DISCONNECTING: default: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) + __WARN(); +#else + WARN_ON(true); +#endif break; } - -out: - ; } /** @@ -2630,7 +2660,7 @@ out: static void srpt_cm_drep_recv(struct ib_cm_id *cm_id) { PRINT_INFO("Received InfiniBand DREP message for cm_id %p.", cm_id); - srpt_release_channel_by_cmid(cm_id); + srpt_drain_channel(cm_id); } /** @@ -2641,7 +2671,7 @@ static void srpt_cm_drep_recv(struct ib_cm_id *cm_id) * Note: srpt_cm_handler() must only return a non-zero value when transferring * ownership of the cm_id to a channel by srpt_cm_req_recv() failed. Returning * a non-zero value in any other case will trigger a race with the - * ib_destroy_cm_id() call in srpt_release_channel(). + * ib_destroy_cm_id() call in srpt_free_ch(). */ static int srpt_cm_handler(struct ib_cm_id *cm_id, struct ib_cm_event *event) { @@ -2984,13 +3014,13 @@ static int srpt_perform_rdmas(struct srpt_rdma_ch *ch, wr.num_sge = 0; wr.wr_id = encode_wr_id(SRPT_RDMA_ABORT, ioctx->ioctx.index); wr.send_flags = IB_SEND_SIGNALED; - while (atomic_read(&ch->state) == RDMA_CHANNEL_LIVE && + while (atomic_read(&ch->state) == CH_LIVE && ib_post_send(ch->qp, &wr, &bad_wr) != 0) { PRINT_INFO("Trying to abort failed RDMA transfer [%d]", ioctx->ioctx.index); msleep(1000); } - while (atomic_read(&ch->state) != RDMA_CHANNEL_DISCONNECTING && + while (atomic_read(&ch->state) != CH_DISCONNECTING && !ioctx->rdma_aborted) { PRINT_INFO("Waiting until RDMA abort finished [%d]", ioctx->ioctx.index); @@ -3112,13 +3142,13 @@ static int srpt_rdy_to_xfer(struct scst_cmd *scmnd) BUG_ON(!ch); ch_state = atomic_read(&ch->state); - if (ch_state == RDMA_CHANNEL_DISCONNECTING) { + if (ch_state == CH_DISCONNECTING) { TRACE_DBG("cmd with tag %lld: channel disconnecting", scst_cmd_get_tag(scmnd)); srpt_set_cmd_state(ioctx, SRPT_STATE_DATA_IN); ret = SCST_TGT_RES_FATAL_ERROR; goto out; - } else if (ch_state == RDMA_CHANNEL_CONNECTING) { + } else if (ch_state == CH_CONNECTING) { ret = SCST_TGT_RES_QUEUE_FULL; goto out; } @@ -3351,6 +3381,50 @@ static int srpt_detect(struct scst_tgt_template *tp) return device_count; } +static int srpt_ch_list_empty(struct srpt_device *sdev) +{ + int res; + + spin_lock_irq(&sdev->spinlock); + res = list_empty(&sdev->rch_list); + spin_unlock_irq(&sdev->spinlock); + + return res; +} + +/** + * srpt_release_sdev() - Free channel resources associated with a target. + */ +static int srpt_release_sdev(struct srpt_device *sdev) +{ + struct srpt_rdma_ch *ch, *next_ch; + + TRACE_ENTRY(); + + WARN_ON_ONCE(irqs_disabled()); + BUG_ON(!sdev); + + spin_lock_irq(&sdev->spinlock); + list_for_each_entry_safe(ch, next_ch, &sdev->rch_list, list) + __srpt_close_ch(ch); + spin_unlock_irq(&sdev->spinlock); + + while (wait_event_timeout(sdev->ch_releaseQ, + srpt_ch_list_empty(sdev), 5 * HZ) <= 0) { + PRINT_INFO("%s: waiting for session unregistration ...", + sdev->device->name); + spin_lock_irq(&sdev->spinlock); + list_for_each_entry_safe(ch, next_ch, &sdev->rch_list, list) + PRINT_INFO("%s: %d commands in progress", + ch->sess_name, + atomic_read(&ch->scst_sess->sess_cmd_count)); + spin_unlock_irq(&sdev->spinlock); + } + + TRACE_EXIT(); + return 0; +} + /** * srpt_release() - Free the resources associated with an SCST target. * @@ -3359,7 +3433,6 @@ static int srpt_detect(struct scst_tgt_template *tp) static int srpt_release(struct scst_tgt *scst_tgt) { struct srpt_device *sdev = scst_tgt_get_tgt_priv(scst_tgt); - struct srpt_rdma_ch *ch; TRACE_ENTRY(); @@ -3375,12 +3448,7 @@ static int srpt_release(struct scst_tgt *scst_tgt) return -ENODEV; #endif - spin_lock_irq(&sdev->spinlock); - while (!list_empty(&sdev->rch_list)) { - ch = list_first_entry(&sdev->rch_list, typeof(*ch), list); - srpt_unregister_channel(ch); - } - spin_unlock_irq(&sdev->spinlock); + srpt_release_sdev(sdev); scst_tgt_set_tgt_priv(scst_tgt, NULL); @@ -3610,6 +3678,7 @@ static void srpt_add_one(struct ib_device *device) sdev->device = device; INIT_LIST_HEAD(&sdev->rch_list); + init_waitqueue_head(&sdev->ch_releaseQ); spin_lock_init(&sdev->spinlock); if (use_node_guid_in_target_name) { diff --git a/srpt/src/ib_srpt.h b/srpt/src/ib_srpt.h index 173e988f2..1998609d0 100644 --- a/srpt/src/ib_srpt.h +++ b/srpt/src/ib_srpt.h @@ -240,16 +240,24 @@ struct srpt_mgmt_ioctx { /** * enum rdma_ch_state - SRP channel state. + * @CH_CONNECTING: QP is in RTR state; waiting for RTU. + * @CH_LIVE: QP is in RTS state. + * @CH_DISCONNECTING: DREQ has been received and waiting for DREP or DREQ has + * been sent and waiting for DREP or channel is being closed + * for another reason. + * @CH_DRAINING: QP is in ERR state; waiting for last WQE event. + * @CH_RELEASING: Last WQE event has been received; releasing resources. */ enum rdma_ch_state { - RDMA_CHANNEL_CONNECTING, - RDMA_CHANNEL_LIVE, - RDMA_CHANNEL_DISCONNECTING + CH_CONNECTING, + CH_LIVE, + CH_DISCONNECTING, + CH_DRAINING, + CH_RELEASING }; /** * struct srpt_rdma_ch - RDMA channel. - * @wait_queue: Allows the kernel thread to wait for more work. * @thread: Kernel thread that processes the IB queues associated with * the channel. * @cm_id: IB CM ID associated with the channel. @@ -277,7 +285,6 @@ enum rdma_ch_state { * @sess_name: SCST session name. */ struct srpt_rdma_ch { - wait_queue_head_t wait_queue; struct task_struct *thread; struct ib_cm_id *cm_id; struct ib_qp *qp; @@ -351,6 +358,7 @@ struct srpt_device { int srq_size; struct srpt_recv_ioctx **ioctx_ring; struct list_head rch_list; + wait_queue_head_t ch_releaseQ; spinlock_t spinlock; struct srpt_port port[2]; struct ib_event_handler event_handler;