diff options
Diffstat (limited to 'drivers/scsi/libiscsi.c')
-rw-r--r-- | drivers/scsi/libiscsi.c | 313 |
1 files changed, 212 insertions, 101 deletions
diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c index 797abf4f5399..d95f4bcdeb2e 100644 --- a/drivers/scsi/libiscsi.c +++ b/drivers/scsi/libiscsi.c @@ -83,7 +83,9 @@ MODULE_PARM_DESC(debug_libiscsi_eh, "%s " dbg_fmt, __func__, ##arg); \ } while (0); -inline void iscsi_conn_queue_work(struct iscsi_conn *conn) +#define ISCSI_CMD_COMPL_WAIT 5 + +inline void iscsi_conn_queue_xmit(struct iscsi_conn *conn) { struct Scsi_Host *shost = conn->session->host; struct iscsi_host *ihost = shost_priv(shost); @@ -91,7 +93,17 @@ inline void iscsi_conn_queue_work(struct iscsi_conn *conn) if (ihost->workq) queue_work(ihost->workq, &conn->xmitwork); } -EXPORT_SYMBOL_GPL(iscsi_conn_queue_work); +EXPORT_SYMBOL_GPL(iscsi_conn_queue_xmit); + +inline void iscsi_conn_queue_recv(struct iscsi_conn *conn) +{ + struct Scsi_Host *shost = conn->session->host; + struct iscsi_host *ihost = shost_priv(shost); + + if (ihost->workq && !test_bit(ISCSI_CONN_FLAG_SUSPEND_RX, &conn->flags)) + queue_work(ihost->workq, &conn->recvwork); +} +EXPORT_SYMBOL_GPL(iscsi_conn_queue_recv); static void __iscsi_update_cmdsn(struct iscsi_session *session, uint32_t exp_cmdsn, uint32_t max_cmdsn) @@ -472,12 +484,18 @@ static void iscsi_free_task(struct iscsi_task *task) } } -void __iscsi_get_task(struct iscsi_task *task) +bool iscsi_get_task(struct iscsi_task *task) { - refcount_inc(&task->refcount); + return refcount_inc_not_zero(&task->refcount); } -EXPORT_SYMBOL_GPL(__iscsi_get_task); +EXPORT_SYMBOL_GPL(iscsi_get_task); +/** + * __iscsi_put_task - drop the refcount on a task + * @task: iscsi_task to drop the refcount on + * + * The back_lock must be held when calling in case it frees the task. + */ void __iscsi_put_task(struct iscsi_task *task) { if (refcount_dec_and_test(&task->refcount)) @@ -489,10 +507,11 @@ void iscsi_put_task(struct iscsi_task *task) { struct iscsi_session *session = task->conn->session; - /* regular RX path uses back_lock */ - spin_lock_bh(&session->back_lock); - __iscsi_put_task(task); - spin_unlock_bh(&session->back_lock); + if (refcount_dec_and_test(&task->refcount)) { + spin_lock_bh(&session->back_lock); + iscsi_free_task(task); + spin_unlock_bh(&session->back_lock); + } } EXPORT_SYMBOL_GPL(iscsi_put_task); @@ -557,16 +576,19 @@ static bool cleanup_queued_task(struct iscsi_task *task) struct iscsi_conn *conn = task->conn; bool early_complete = false; - /* Bad target might have completed task while it was still running */ + /* + * We might have raced where we handled a R2T early and got a response + * but have not yet taken the task off the requeue list, then a TMF or + * recovery happened and so we can still see it here. + */ if (task->state == ISCSI_TASK_COMPLETED) early_complete = true; if (!list_empty(&task->running)) { list_del_init(&task->running); /* - * If it's on a list but still running, this could be from - * a bad target sending a rsp early, cleanup from a TMF, or - * session recovery. + * If it's on a list but still running this could be cleanup + * from a TMF or session recovery. */ if (task->state == ISCSI_TASK_RUNNING || task->state == ISCSI_TASK_COMPLETED) @@ -587,20 +609,17 @@ static bool cleanup_queued_task(struct iscsi_task *task) } /* - * session frwd lock must be held and if not called for a task that is still - * pending or from the xmit thread, then xmit thread must be suspended + * session back and frwd lock must be held and if not called for a task that + * is still pending or from the xmit thread, then xmit thread must be suspended */ -static void fail_scsi_task(struct iscsi_task *task, int err) +static void __fail_scsi_task(struct iscsi_task *task, int err) { struct iscsi_conn *conn = task->conn; struct scsi_cmnd *sc; int state; - spin_lock_bh(&conn->session->back_lock); - if (cleanup_queued_task(task)) { - spin_unlock_bh(&conn->session->back_lock); + if (cleanup_queued_task(task)) return; - } if (task->state == ISCSI_TASK_PENDING) { /* @@ -619,7 +638,15 @@ static void fail_scsi_task(struct iscsi_task *task, int err) sc->result = err << 16; scsi_set_resid(sc, scsi_bufflen(sc)); iscsi_complete_task(task, state); - spin_unlock_bh(&conn->session->back_lock); +} + +static void fail_scsi_task(struct iscsi_task *task, int err) +{ + struct iscsi_session *session = task->conn->session; + + spin_lock_bh(&session->back_lock); + __fail_scsi_task(task, err); + spin_unlock_bh(&session->back_lock); } static int iscsi_prep_mgmt_task(struct iscsi_conn *conn, @@ -668,12 +695,18 @@ static int iscsi_prep_mgmt_task(struct iscsi_conn *conn, return 0; } +/** + * iscsi_alloc_mgmt_task - allocate and setup a mgmt task. + * @conn: iscsi conn that the task will be sent on. + * @hdr: iscsi pdu that will be sent. + * @data: buffer for data segment if needed. + * @data_size: length of data in bytes. + */ static struct iscsi_task * -__iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, +iscsi_alloc_mgmt_task(struct iscsi_conn *conn, struct iscsi_hdr *hdr, char *data, uint32_t data_size) { struct iscsi_session *session = conn->session; - struct iscsi_host *ihost = shost_priv(session->host); uint8_t opcode = hdr->opcode & ISCSI_OPCODE_MASK; struct iscsi_task *task; itt_t itt; @@ -754,28 +787,57 @@ __iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, task->conn->session->age); } - if (unlikely(READ_ONCE(conn->ping_task) == INVALID_SCSI_TASK)) - WRITE_ONCE(conn->ping_task, task); + return task; + +free_task: + iscsi_put_task(task); + return NULL; +} + +/** + * iscsi_send_mgmt_task - Send task created with iscsi_alloc_mgmt_task. + * @task: iscsi task to send. + * + * On failure this returns a non-zero error code, and the driver must free + * the task with iscsi_put_task; + */ +static int iscsi_send_mgmt_task(struct iscsi_task *task) +{ + struct iscsi_conn *conn = task->conn; + struct iscsi_session *session = conn->session; + struct iscsi_host *ihost = shost_priv(conn->session->host); + int rc = 0; if (!ihost->workq) { - if (iscsi_prep_mgmt_task(conn, task)) - goto free_task; + rc = iscsi_prep_mgmt_task(conn, task); + if (rc) + return rc; - if (session->tt->xmit_task(task)) - goto free_task; + rc = session->tt->xmit_task(task); + if (rc) + return rc; } else { list_add_tail(&task->running, &conn->mgmtqueue); - iscsi_conn_queue_work(conn); + iscsi_conn_queue_xmit(conn); } - return task; + return 0; +} -free_task: - /* regular RX path uses back_lock */ - spin_lock(&session->back_lock); - __iscsi_put_task(task); - spin_unlock(&session->back_lock); - return NULL; +static int __iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, + char *data, uint32_t data_size) +{ + struct iscsi_task *task; + int rc; + + task = iscsi_alloc_mgmt_task(conn, hdr, data, data_size); + if (!task) + return -ENOMEM; + + rc = iscsi_send_mgmt_task(task); + if (rc) + iscsi_put_task(task); + return rc; } int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr, @@ -786,7 +848,7 @@ int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr, int err = 0; spin_lock_bh(&session->frwd_lock); - if (!__iscsi_conn_send_pdu(conn, hdr, data, data_size)) + if (__iscsi_conn_send_pdu(conn, hdr, data, data_size)) err = -EPERM; spin_unlock_bh(&session->frwd_lock); return err; @@ -959,7 +1021,6 @@ static int iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr) if (!rhdr) { if (READ_ONCE(conn->ping_task)) return -EINVAL; - WRITE_ONCE(conn->ping_task, INVALID_SCSI_TASK); } memset(&hdr, 0, sizeof(struct iscsi_nopout)); @@ -973,10 +1034,18 @@ static int iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr) } else hdr.ttt = RESERVED_ITT; - task = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0); - if (!task) { + task = iscsi_alloc_mgmt_task(conn, (struct iscsi_hdr *)&hdr, NULL, 0); + if (!task) + return -ENOMEM; + + if (!rhdr) + WRITE_ONCE(conn->ping_task, task); + + if (iscsi_send_mgmt_task(task)) { if (!rhdr) WRITE_ONCE(conn->ping_task, NULL); + iscsi_put_task(task); + iscsi_conn_printk(KERN_ERR, conn, "Could not send nopout\n"); return -EIO; } else if (!rhdr) { @@ -1434,11 +1503,17 @@ static int iscsi_xmit_task(struct iscsi_conn *conn, struct iscsi_task *task, { int rc; - spin_lock_bh(&conn->session->back_lock); - if (!conn->task) { - /* Take a ref so we can access it after xmit_task() */ - __iscsi_get_task(task); + /* + * Take a ref so we can access it after xmit_task(). + * + * This should never fail because the failure paths will have + * stopped the xmit thread. + */ + if (!iscsi_get_task(task)) { + WARN_ON_ONCE(1); + return 0; + } } else { /* Already have a ref from when we failed to send it last call */ conn->task = NULL; @@ -1449,7 +1524,7 @@ static int iscsi_xmit_task(struct iscsi_conn *conn, struct iscsi_task *task, * case a bad target sends a cmd rsp before we have handled the task. */ if (was_requeue) - __iscsi_put_task(task); + iscsi_put_task(task); /* * Do this after dropping the extra ref because if this was a requeue @@ -1461,10 +1536,8 @@ static int iscsi_xmit_task(struct iscsi_conn *conn, struct iscsi_task *task, * task and get woken up again. */ conn->task = task; - spin_unlock_bh(&conn->session->back_lock); return -ENODATA; } - spin_unlock_bh(&conn->session->back_lock); spin_unlock_bh(&conn->session->frwd_lock); rc = conn->session->tt->xmit_task(task); @@ -1472,20 +1545,16 @@ static int iscsi_xmit_task(struct iscsi_conn *conn, struct iscsi_task *task, if (!rc) { /* done with this task */ task->last_xfer = jiffies; - } - /* regular RX path uses back_lock */ - spin_lock(&conn->session->back_lock); - if (rc && task->state == ISCSI_TASK_RUNNING) { + } else { /* * get an extra ref that is released next time we access it * as conn->task above. */ - __iscsi_get_task(task); + iscsi_get_task(task); conn->task = task; } - __iscsi_put_task(task); - spin_unlock(&conn->session->back_lock); + iscsi_put_task(task); return rc; } @@ -1513,7 +1582,7 @@ void iscsi_requeue_task(struct iscsi_task *task) */ iscsi_put_task(task); } - iscsi_conn_queue_work(conn); + iscsi_conn_queue_xmit(conn); spin_unlock_bh(&conn->session->frwd_lock); } EXPORT_SYMBOL_GPL(iscsi_requeue_task); @@ -1567,6 +1636,28 @@ check_mgmt: goto done; } +check_requeue: + while (!list_empty(&conn->requeue)) { + /* + * we always do fastlogout - conn stop code will clean up. + */ + if (conn->session->state == ISCSI_STATE_LOGGING_OUT) + break; + + task = list_entry(conn->requeue.next, struct iscsi_task, + running); + + if (iscsi_check_tmf_restrictions(task, ISCSI_OP_SCSI_DATA_OUT)) + break; + + list_del_init(&task->running); + rc = iscsi_xmit_task(conn, task, true); + if (rc) + goto done; + if (!list_empty(&conn->mgmtqueue)) + goto check_mgmt; + } + /* process pending command queue */ while (!list_empty(&conn->cmdqueue)) { task = list_entry(conn->cmdqueue.next, struct iscsi_task, @@ -1594,28 +1685,10 @@ check_mgmt: */ if (!list_empty(&conn->mgmtqueue)) goto check_mgmt; + if (!list_empty(&conn->requeue)) + goto check_requeue; } - while (!list_empty(&conn->requeue)) { - /* - * we always do fastlogout - conn stop code will clean up. - */ - if (conn->session->state == ISCSI_STATE_LOGGING_OUT) - break; - - task = list_entry(conn->requeue.next, struct iscsi_task, - running); - - if (iscsi_check_tmf_restrictions(task, ISCSI_OP_SCSI_DATA_OUT)) - break; - - list_del_init(&task->running); - rc = iscsi_xmit_task(conn, task, true); - if (rc) - goto done; - if (!list_empty(&conn->mgmtqueue)) - goto check_mgmt; - } spin_unlock_bh(&conn->session->frwd_lock); return -ENODATA; @@ -1782,7 +1855,7 @@ int iscsi_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *sc) } } else { list_add_tail(&task->running, &conn->cmdqueue); - iscsi_conn_queue_work(conn); + iscsi_conn_queue_xmit(conn); } session->queued_cmdsn++; @@ -1843,11 +1916,8 @@ static int iscsi_exec_task_mgmt_fn(struct iscsi_conn *conn, __must_hold(&session->frwd_lock) { struct iscsi_session *session = conn->session; - struct iscsi_task *task; - task = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)hdr, - NULL, 0); - if (!task) { + if (__iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)hdr, NULL, 0)) { spin_unlock_bh(&session->frwd_lock); iscsi_conn_printk(KERN_ERR, conn, "Could not send TMF.\n"); iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); @@ -1895,6 +1965,7 @@ static void fail_scsi_tasks(struct iscsi_conn *conn, u64 lun, int error) struct iscsi_task *task; int i; +restart_cmd_loop: spin_lock_bh(&session->back_lock); for (i = 0; i < session->cmds_max; i++) { task = session->cmds[i]; @@ -1903,22 +1974,25 @@ static void fail_scsi_tasks(struct iscsi_conn *conn, u64 lun, int error) if (lun != -1 && lun != task->sc->device->lun) continue; - - __iscsi_get_task(task); - spin_unlock_bh(&session->back_lock); + /* + * The cmd is completing but if this is called from an eh + * callout path then when we return scsi-ml owns the cmd. Wait + * for the completion path to finish freeing the cmd. + */ + if (!iscsi_get_task(task)) { + spin_unlock_bh(&session->back_lock); + spin_unlock_bh(&session->frwd_lock); + udelay(ISCSI_CMD_COMPL_WAIT); + spin_lock_bh(&session->frwd_lock); + goto restart_cmd_loop; + } ISCSI_DBG_SESSION(session, "failing sc %p itt 0x%x state %d\n", task->sc, task->itt, task->state); - fail_scsi_task(task, error); - - spin_unlock_bh(&session->frwd_lock); - iscsi_put_task(task); - spin_lock_bh(&session->frwd_lock); - - spin_lock_bh(&session->back_lock); + __fail_scsi_task(task, error); + __iscsi_put_task(task); } - spin_unlock_bh(&session->back_lock); } @@ -1943,7 +2017,7 @@ EXPORT_SYMBOL_GPL(iscsi_suspend_queue); /** * iscsi_suspend_tx - suspend iscsi_data_xmit - * @conn: iscsi conn tp stop processing IO on. + * @conn: iscsi conn to stop processing IO on. * * This function sets the suspend bit to prevent iscsi_data_xmit * from sending new IO, and if work is queued on the xmit thread @@ -1956,15 +2030,30 @@ void iscsi_suspend_tx(struct iscsi_conn *conn) set_bit(ISCSI_CONN_FLAG_SUSPEND_TX, &conn->flags); if (ihost->workq) - flush_workqueue(ihost->workq); + flush_work(&conn->xmitwork); } EXPORT_SYMBOL_GPL(iscsi_suspend_tx); static void iscsi_start_tx(struct iscsi_conn *conn) { clear_bit(ISCSI_CONN_FLAG_SUSPEND_TX, &conn->flags); - iscsi_conn_queue_work(conn); + iscsi_conn_queue_xmit(conn); +} + +/** + * iscsi_suspend_rx - Prevent recvwork from running again. + * @conn: iscsi conn to stop. + */ +void iscsi_suspend_rx(struct iscsi_conn *conn) +{ + struct Scsi_Host *shost = conn->session->host; + struct iscsi_host *ihost = shost_priv(shost); + + set_bit(ISCSI_CONN_FLAG_SUSPEND_RX, &conn->flags); + if (ihost->workq) + flush_work(&conn->recvwork); } +EXPORT_SYMBOL_GPL(iscsi_suspend_rx); /* * We want to make sure a ping is in flight. It has timed out. @@ -2008,7 +2097,16 @@ enum blk_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *sc) spin_unlock(&session->back_lock); goto done; } - __iscsi_get_task(task); + if (!iscsi_get_task(task)) { + /* + * Racing with the completion path right now, so give it more + * time so that path can complete it like normal. + */ + rc = BLK_EH_RESET_TIMER; + task = NULL; + spin_unlock(&session->back_lock); + goto done; + } spin_unlock(&session->back_lock); if (session->state != ISCSI_STATE_LOGGED_IN) { @@ -2257,6 +2355,7 @@ int iscsi_eh_abort(struct scsi_cmnd *sc) ISCSI_DBG_EH(session, "aborting sc %p\n", sc); +completion_check: mutex_lock(&session->eh_mutex); spin_lock_bh(&session->frwd_lock); /* @@ -2296,13 +2395,20 @@ int iscsi_eh_abort(struct scsi_cmnd *sc) return SUCCESS; } + if (!iscsi_get_task(task)) { + spin_unlock(&session->back_lock); + spin_unlock_bh(&session->frwd_lock); + mutex_unlock(&session->eh_mutex); + /* We are just about to call iscsi_free_task so wait for it. */ + udelay(ISCSI_CMD_COMPL_WAIT); + goto completion_check; + } + + ISCSI_DBG_EH(session, "aborting [sc %p itt 0x%x]\n", sc, task->itt); conn = session->leadconn; iscsi_get_conn(conn->cls_conn); conn->eh_abort_cnt++; age = session->age; - - ISCSI_DBG_EH(session, "aborting [sc %p itt 0x%x]\n", sc, task->itt); - __iscsi_get_task(task); spin_unlock(&session->back_lock); if (task->state == ISCSI_TASK_PENDING) { @@ -2828,11 +2934,12 @@ static void iscsi_notify_host_removed(struct iscsi_cls_session *cls_session) /** * iscsi_host_remove - remove host and sessions * @shost: scsi host + * @is_shutdown: true if called from a driver shutdown callout * * If there are any sessions left, this will initiate the removal and wait * for the completion. */ -void iscsi_host_remove(struct Scsi_Host *shost) +void iscsi_host_remove(struct Scsi_Host *shost, bool is_shutdown) { struct iscsi_host *ihost = shost_priv(shost); unsigned long flags; @@ -2841,7 +2948,11 @@ void iscsi_host_remove(struct Scsi_Host *shost) ihost->state = ISCSI_HOST_REMOVED; spin_unlock_irqrestore(&ihost->lock, flags); - iscsi_host_for_each_session(shost, iscsi_notify_host_removed); + if (!is_shutdown) + iscsi_host_for_each_session(shost, iscsi_notify_host_removed); + else + iscsi_host_for_each_session(shost, iscsi_force_destroy_session); + wait_event_interruptible(ihost->session_removal_wq, ihost->num_sessions == 0); if (signal_pending(current)) |