Merge git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi-misc-2.6
[safe/jmp/linux-2.6] / drivers / scsi / libiscsi.c
index 521dbf7..801c7cf 100644 (file)
@@ -404,11 +404,6 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_task *task,
                conn->session->queued_cmdsn--;
        else
                conn->session->tt->cleanup_task(conn, task);
-       /*
-        * Check if cleanup_task dropped the lock and the command completed,
-        */
-       if (!task->sc)
-               return;
 
        sc->result = err;
        if (!scsi_bidi_cmnd(sc))
@@ -633,6 +628,40 @@ out:
        __iscsi_put_task(task);
 }
 
+/**
+ * iscsi_data_in_rsp - SCSI Data-In Response processing
+ * @conn: iscsi connection
+ * @hdr:  iscsi pdu
+ * @task: scsi command task
+ **/
+static void
+iscsi_data_in_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+                 struct iscsi_task *task)
+{
+       struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)hdr;
+       struct scsi_cmnd *sc = task->sc;
+
+       if (!(rhdr->flags & ISCSI_FLAG_DATA_STATUS))
+               return;
+
+       sc->result = (DID_OK << 16) | rhdr->cmd_status;
+       conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+       if (rhdr->flags & (ISCSI_FLAG_DATA_UNDERFLOW |
+                          ISCSI_FLAG_DATA_OVERFLOW)) {
+               int res_count = be32_to_cpu(rhdr->residual_count);
+
+               if (res_count > 0 &&
+                   (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+                    res_count <= scsi_in(sc)->length))
+                       scsi_in(sc)->resid = res_count;
+               else
+                       sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+       }
+
+       conn->scsirsp_pdus_cnt++;
+       __iscsi_put_task(task);
+}
+
 static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
 {
        struct iscsi_tm_rsp *tmf = (struct iscsi_tm_rsp *)hdr;
@@ -818,12 +847,7 @@ int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
                iscsi_scsi_cmd_rsp(conn, hdr, task, data, datalen);
                break;
        case ISCSI_OP_SCSI_DATA_IN:
-               if (hdr->flags & ISCSI_FLAG_DATA_STATUS) {
-                       conn->scsirsp_pdus_cnt++;
-                       iscsi_update_cmdsn(session,
-                                          (struct iscsi_nopin*) hdr);
-                       __iscsi_put_task(task);
-               }
+               iscsi_data_in_rsp(conn, hdr, task);
                break;
        case ISCSI_OP_LOGOUT_RSP:
                iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
@@ -954,6 +978,38 @@ struct iscsi_task *iscsi_itt_to_ctask(struct iscsi_conn *conn, itt_t itt)
 }
 EXPORT_SYMBOL_GPL(iscsi_itt_to_ctask);
 
+void iscsi_session_failure(struct iscsi_cls_session *cls_session,
+                          enum iscsi_err err)
+{
+       struct iscsi_session *session = cls_session->dd_data;
+       struct iscsi_conn *conn;
+       struct device *dev;
+       unsigned long flags;
+
+       spin_lock_irqsave(&session->lock, flags);
+       conn = session->leadconn;
+       if (session->state == ISCSI_STATE_TERMINATE || !conn) {
+               spin_unlock_irqrestore(&session->lock, flags);
+               return;
+       }
+
+       dev = get_device(&conn->cls_conn->dev);
+       spin_unlock_irqrestore(&session->lock, flags);
+       if (!dev)
+               return;
+       /*
+        * if the host is being removed bypass the connection
+        * recovery initialization because we are going to kill
+        * the session.
+        */
+       if (err == ISCSI_ERR_INVALID_HOST)
+               iscsi_conn_error_event(conn->cls_conn, err);
+       else
+               iscsi_conn_failure(conn, err);
+       put_device(dev);
+}
+EXPORT_SYMBOL_GPL(iscsi_session_failure);
+
 void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
 {
        struct iscsi_session *session = conn->session;
@@ -968,9 +1024,10 @@ void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
        if (conn->stop_stage == 0)
                session->state = ISCSI_STATE_FAILED;
        spin_unlock_irqrestore(&session->lock, flags);
+
        set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
        set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
-       iscsi_conn_error(conn->cls_conn, err);
+       iscsi_conn_error_event(conn->cls_conn, err);
 }
 EXPORT_SYMBOL_GPL(iscsi_conn_failure);
 
@@ -1200,7 +1257,7 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
                        goto reject;
                case ISCSI_STATE_RECOVERY_FAILED:
                        reason = FAILURE_SESSION_RECOVERY_TIMEOUT;
-                       sc->result = DID_NO_CONNECT << 16;
+                       sc->result = DID_TRANSPORT_FAILFAST << 16;
                        break;
                case ISCSI_STATE_TERMINATE:
                        reason = FAILURE_SESSION_TERMINATE;
@@ -1305,7 +1362,7 @@ void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session)
 }
 EXPORT_SYMBOL_GPL(iscsi_session_recovery_timedout);
 
-int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+int iscsi_eh_target_reset(struct scsi_cmnd *sc)
 {
        struct iscsi_cls_session *cls_session;
        struct iscsi_session *session;
@@ -1319,7 +1376,7 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
        spin_lock_bh(&session->lock);
        if (session->state == ISCSI_STATE_TERMINATE) {
 failed:
-               debug_scsi("failing host reset: session terminated "
+               debug_scsi("failing target reset: session terminated "
                           "[CID %d age %d]\n", conn->id, session->age);
                spin_unlock_bh(&session->lock);
                mutex_unlock(&session->eh_mutex);
@@ -1334,7 +1391,7 @@ failed:
         */
        iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
 
-       debug_scsi("iscsi_eh_host_reset wait for relogin\n");
+       debug_scsi("iscsi_eh_target_reset wait for relogin\n");
        wait_event_interruptible(conn->ehwait,
                                 session->state == ISCSI_STATE_TERMINATE ||
                                 session->state == ISCSI_STATE_LOGGED_IN ||
@@ -1346,14 +1403,14 @@ failed:
        spin_lock_bh(&session->lock);
        if (session->state == ISCSI_STATE_LOGGED_IN)
                iscsi_session_printk(KERN_INFO, session,
-                                    "host reset succeeded\n");
+                                    "target reset succeeded\n");
        else
                goto failed;
        spin_unlock_bh(&session->lock);
        mutex_unlock(&session->eh_mutex);
        return SUCCESS;
 }
-EXPORT_SYMBOL_GPL(iscsi_eh_host_reset);
+EXPORT_SYMBOL_GPL(iscsi_eh_target_reset);
 
 static void iscsi_tmf_timedout(unsigned long data)
 {
@@ -1767,10 +1824,10 @@ int iscsi_eh_device_reset(struct scsi_cmnd *sc)
 
        iscsi_suspend_tx(conn);
 
-       spin_lock(&session->lock);
+       spin_lock_bh(&session->lock);
        fail_all_commands(conn, sc->device->lun, DID_ERROR);
        conn->tmf_state = TMF_INITIAL;
-       spin_unlock(&session->lock);
+       spin_unlock_bh(&session->lock);
 
        iscsi_start_tx(conn);
        goto done;
@@ -1876,6 +1933,7 @@ struct Scsi_Host *iscsi_host_alloc(struct scsi_host_template *sht,
                                   int dd_data_size, uint16_t qdepth)
 {
        struct Scsi_Host *shost;
+       struct iscsi_host *ihost;
 
        shost = scsi_host_alloc(sht, sizeof(struct iscsi_host) + dd_data_size);
        if (!shost)
@@ -1890,22 +1948,43 @@ struct Scsi_Host *iscsi_host_alloc(struct scsi_host_template *sht,
                qdepth = ISCSI_DEF_CMD_PER_LUN;
        }
        shost->cmd_per_lun = qdepth;
+
+       ihost = shost_priv(shost);
+       spin_lock_init(&ihost->lock);
+       ihost->state = ISCSI_HOST_SETUP;
+       ihost->num_sessions = 0;
+       init_waitqueue_head(&ihost->session_removal_wq);
        return shost;
 }
 EXPORT_SYMBOL_GPL(iscsi_host_alloc);
 
+static void iscsi_notify_host_removed(struct iscsi_cls_session *cls_session)
+{
+       iscsi_session_failure(cls_session, ISCSI_ERR_INVALID_HOST);
+}
+
 /**
  * iscsi_host_remove - remove host and sessions
  * @shost: scsi host
  *
- * This will also remove any sessions attached to the host, but if userspace
- * is managing the session at the same time this will break. TODO: add
- * refcounting to the netlink iscsi interface so a rmmod or host hot unplug
- * does not remove the memory from under us.
+ * If there are any sessions left, this will initiate the removal and wait
+ * for the completion.
  */
 void iscsi_host_remove(struct Scsi_Host *shost)
 {
-       iscsi_host_for_each_session(shost, iscsi_session_teardown);
+       struct iscsi_host *ihost = shost_priv(shost);
+       unsigned long flags;
+
+       spin_lock_irqsave(&ihost->lock, flags);
+       ihost->state = ISCSI_HOST_REMOVED;
+       spin_unlock_irqrestore(&ihost->lock, flags);
+
+       iscsi_host_for_each_session(shost, iscsi_notify_host_removed);
+       wait_event_interruptible(ihost->session_removal_wq,
+                                ihost->num_sessions == 0);
+       if (signal_pending(current))
+               flush_signals(current);
+
        scsi_remove_host(shost);
 }
 EXPORT_SYMBOL_GPL(iscsi_host_remove);
@@ -1921,6 +2000,27 @@ void iscsi_host_free(struct Scsi_Host *shost)
 }
 EXPORT_SYMBOL_GPL(iscsi_host_free);
 
+static void iscsi_host_dec_session_cnt(struct Scsi_Host *shost)
+{
+       struct iscsi_host *ihost = shost_priv(shost);
+       unsigned long flags;
+
+       shost = scsi_host_get(shost);
+       if (!shost) {
+               printk(KERN_ERR "Invalid state. Cannot notify host removal "
+                     "of session teardown event because host already "
+                     "removed.\n");
+               return;
+       }
+
+       spin_lock_irqsave(&ihost->lock, flags);
+       ihost->num_sessions--;
+       if (ihost->num_sessions == 0)
+               wake_up(&ihost->session_removal_wq);
+       spin_unlock_irqrestore(&ihost->lock, flags);
+       scsi_host_put(shost);
+}
+
 /**
  * iscsi_session_setup - create iscsi cls session and host and session
  * @iscsit: iscsi transport template
@@ -1941,9 +2041,19 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost,
                    uint16_t cmds_max, int cmd_task_size,
                    uint32_t initial_cmdsn, unsigned int id)
 {
+       struct iscsi_host *ihost = shost_priv(shost);
        struct iscsi_session *session;
        struct iscsi_cls_session *cls_session;
        int cmd_i, scsi_cmds, total_cmds = cmds_max;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ihost->lock, flags);
+       if (ihost->state == ISCSI_HOST_REMOVED) {
+               spin_unlock_irqrestore(&ihost->lock, flags);
+               return NULL;
+       }
+       ihost->num_sessions++;
+       spin_unlock_irqrestore(&ihost->lock, flags);
 
        if (!total_cmds)
                total_cmds = ISCSI_DEF_XMIT_CMDS_MAX;
@@ -1956,7 +2066,7 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost,
                printk(KERN_ERR "iscsi: invalid can_queue of %d. can_queue "
                       "must be a power of two that is at least %d.\n",
                       total_cmds, ISCSI_TOTAL_CMDS_MIN);
-               return NULL;
+               goto dec_session_count;
        }
 
        if (total_cmds > ISCSI_TOTAL_CMDS_MAX) {
@@ -1980,7 +2090,7 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost,
        cls_session = iscsi_alloc_session(shost, iscsit,
                                          sizeof(struct iscsi_session));
        if (!cls_session)
-               return NULL;
+               goto dec_session_count;
        session = cls_session->dd_data;
        session->cls_session = cls_session;
        session->host = shost;
@@ -2019,6 +2129,7 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost,
 
        if (iscsi_add_session(cls_session, id))
                goto cls_session_fail;
+
        return cls_session;
 
 cls_session_fail:
@@ -2027,6 +2138,8 @@ module_get_fail:
        iscsi_pool_free(&session->cmdpool);
 cmdpool_alloc_fail:
        iscsi_free_session(cls_session);
+dec_session_count:
+       iscsi_host_dec_session_cnt(shost);
        return NULL;
 }
 EXPORT_SYMBOL_GPL(iscsi_session_setup);
@@ -2042,6 +2155,7 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
 {
        struct iscsi_session *session = cls_session->dd_data;
        struct module *owner = cls_session->transport->owner;
+       struct Scsi_Host *shost = session->host;
 
        iscsi_pool_free(&session->cmdpool);
 
@@ -2054,6 +2168,7 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
        kfree(session->ifacename);
 
        iscsi_destroy_session(cls_session);
+       iscsi_host_dec_session_cnt(shost);
        module_put(owner);
 }
 EXPORT_SYMBOL_GPL(iscsi_session_teardown);
@@ -2333,8 +2448,10 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
         * flush queues.
         */
        spin_lock_bh(&session->lock);
-       fail_all_commands(conn, -1,
-                       STOP_CONN_RECOVER ? DID_BUS_BUSY : DID_ERROR);
+       if (flag == STOP_CONN_RECOVER)
+               fail_all_commands(conn, -1, DID_TRANSPORT_DISRUPTED);
+       else
+               fail_all_commands(conn, -1, DID_ERROR);
        flush_control_queues(session, conn);
        spin_unlock_bh(&session->lock);
        mutex_unlock(&session->eh_mutex);