[SCSI] scsi_error: Fix lost EH commands
authorBrian King <brking@linux.vnet.ibm.com>
Tue, 30 Jan 2007 23:51:17 +0000 (17:51 -0600)
committerJames Bottomley <jejb@mulgrave.il.steeleye.com>
Sat, 3 Feb 2007 14:32:10 +0000 (08:32 -0600)
If an EH command times out today, the LLDD's abort handler
will be called to abort the command. It is assumed that this
completes successfully, which can result in the command getting
completed later resulting in an oops. Improve the current
implementation by escalating all the way to host reset if
necessary in order to clean up the EH command.

Signed-off-by: Brian King <brking@linux.vnet.ibm.com>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
drivers/scsi/scsi_error.c

index 2dce06a58c08292ab42dc001b8f84aac4605d600..b8edcf5b54511be2f10fc9690fd01fbdd4dfb99e 100644 (file)
@@ -457,6 +457,128 @@ static void scsi_eh_done(struct scsi_cmnd *scmd)
                complete(eh_action);
 }
 
+/**
+ * scsi_try_host_reset - ask host adapter to reset itself
+ * @scmd:      SCSI cmd to send hsot reset.
+ **/
+static int scsi_try_host_reset(struct scsi_cmnd *scmd)
+{
+       unsigned long flags;
+       int rtn;
+
+       SCSI_LOG_ERROR_RECOVERY(3, printk("%s: Snd Host RST\n",
+                                         __FUNCTION__));
+
+       if (!scmd->device->host->hostt->eh_host_reset_handler)
+               return FAILED;
+
+       rtn = scmd->device->host->hostt->eh_host_reset_handler(scmd);
+
+       if (rtn == SUCCESS) {
+               if (!scmd->device->host->hostt->skip_settle_delay)
+                       ssleep(HOST_RESET_SETTLE_TIME);
+               spin_lock_irqsave(scmd->device->host->host_lock, flags);
+               scsi_report_bus_reset(scmd->device->host,
+                                     scmd_channel(scmd));
+               spin_unlock_irqrestore(scmd->device->host->host_lock, flags);
+       }
+
+       return rtn;
+}
+
+/**
+ * scsi_try_bus_reset - ask host to perform a bus reset
+ * @scmd:      SCSI cmd to send bus reset.
+ **/
+static int scsi_try_bus_reset(struct scsi_cmnd *scmd)
+{
+       unsigned long flags;
+       int rtn;
+
+       SCSI_LOG_ERROR_RECOVERY(3, printk("%s: Snd Bus RST\n",
+                                         __FUNCTION__));
+
+       if (!scmd->device->host->hostt->eh_bus_reset_handler)
+               return FAILED;
+
+       rtn = scmd->device->host->hostt->eh_bus_reset_handler(scmd);
+
+       if (rtn == SUCCESS) {
+               if (!scmd->device->host->hostt->skip_settle_delay)
+                       ssleep(BUS_RESET_SETTLE_TIME);
+               spin_lock_irqsave(scmd->device->host->host_lock, flags);
+               scsi_report_bus_reset(scmd->device->host,
+                                     scmd_channel(scmd));
+               spin_unlock_irqrestore(scmd->device->host->host_lock, flags);
+       }
+
+       return rtn;
+}
+
+/**
+ * scsi_try_bus_device_reset - Ask host to perform a BDR on a dev
+ * @scmd:      SCSI cmd used to send BDR
+ *
+ * Notes:
+ *    There is no timeout for this operation.  if this operation is
+ *    unreliable for a given host, then the host itself needs to put a
+ *    timer on it, and set the host back to a consistent state prior to
+ *    returning.
+ **/
+static int scsi_try_bus_device_reset(struct scsi_cmnd *scmd)
+{
+       int rtn;
+
+       if (!scmd->device->host->hostt->eh_device_reset_handler)
+               return FAILED;
+
+       rtn = scmd->device->host->hostt->eh_device_reset_handler(scmd);
+       if (rtn == SUCCESS) {
+               scmd->device->was_reset = 1;
+               scmd->device->expecting_cc_ua = 1;
+       }
+
+       return rtn;
+}
+
+static int __scsi_try_to_abort_cmd(struct scsi_cmnd *scmd)
+{
+       if (!scmd->device->host->hostt->eh_abort_handler)
+               return FAILED;
+
+       return scmd->device->host->hostt->eh_abort_handler(scmd);
+}
+
+/**
+ * scsi_try_to_abort_cmd - Ask host to abort a running command.
+ * @scmd:      SCSI cmd to abort from Lower Level.
+ *
+ * Notes:
+ *    This function will not return until the user's completion function
+ *    has been called.  there is no timeout on this operation.  if the
+ *    author of the low-level driver wishes this operation to be timed,
+ *    they can provide this facility themselves.  helper functions in
+ *    scsi_error.c can be supplied to make this easier to do.
+ **/
+static int scsi_try_to_abort_cmd(struct scsi_cmnd *scmd)
+{
+       /*
+        * scsi_done was called just after the command timed out and before
+        * we had a chance to process it. (db)
+        */
+       if (scmd->serial_number == 0)
+               return SUCCESS;
+       return __scsi_try_to_abort_cmd(scmd);
+}
+
+static void scsi_abort_eh_cmnd(struct scsi_cmnd *scmd)
+{
+       if (__scsi_try_to_abort_cmd(scmd) != SUCCESS)
+               if (scsi_try_bus_device_reset(scmd) != SUCCESS)
+                       if (scsi_try_bus_reset(scmd) != SUCCESS)
+                               scsi_try_host_reset(scmd);
+}
+
 /**
  * scsi_send_eh_cmnd  - submit a scsi command as part of error recory
  * @scmd:       SCSI command structure to hijack
@@ -584,13 +706,7 @@ static int scsi_send_eh_cmnd(struct scsi_cmnd *scmd, unsigned char *cmnd,
                        break;
                }
        } else {
-               /*
-                * FIXME(eric) - we are not tracking whether we could
-                * abort a timed out command or not.  not sure how
-                * we should treat them differently anyways.
-                */
-               if (shost->hostt->eh_abort_handler)
-                       shost->hostt->eh_abort_handler(scmd);
+               scsi_abort_eh_cmnd(scmd);
                rtn = FAILED;
        }
 
@@ -722,31 +838,6 @@ int scsi_eh_get_sense(struct list_head *work_q,
 }
 EXPORT_SYMBOL_GPL(scsi_eh_get_sense);
 
-/**
- * scsi_try_to_abort_cmd - Ask host to abort a running command.
- * @scmd:      SCSI cmd to abort from Lower Level.
- *
- * Notes:
- *    This function will not return until the user's completion function
- *    has been called.  there is no timeout on this operation.  if the
- *    author of the low-level driver wishes this operation to be timed,
- *    they can provide this facility themselves.  helper functions in
- *    scsi_error.c can be supplied to make this easier to do.
- **/
-static int scsi_try_to_abort_cmd(struct scsi_cmnd *scmd)
-{
-       if (!scmd->device->host->hostt->eh_abort_handler)
-               return FAILED;
-
-       /*
-        * scsi_done was called just after the command timed out and before
-        * we had a chance to process it. (db)
-        */
-       if (scmd->serial_number == 0)
-               return SUCCESS;
-       return scmd->device->host->hostt->eh_abort_handler(scmd);
-}
-
 /**
  * scsi_eh_tur - Send TUR to device.
  * @scmd:      Scsi cmd to send TUR
@@ -820,32 +911,6 @@ static int scsi_eh_abort_cmds(struct list_head *work_q,
        return list_empty(work_q);
 }
 
-/**
- * scsi_try_bus_device_reset - Ask host to perform a BDR on a dev
- * @scmd:      SCSI cmd used to send BDR       
- *
- * Notes:
- *    There is no timeout for this operation.  if this operation is
- *    unreliable for a given host, then the host itself needs to put a
- *    timer on it, and set the host back to a consistent state prior to
- *    returning.
- **/
-static int scsi_try_bus_device_reset(struct scsi_cmnd *scmd)
-{
-       int rtn;
-
-       if (!scmd->device->host->hostt->eh_device_reset_handler)
-               return FAILED;
-
-       rtn = scmd->device->host->hostt->eh_device_reset_handler(scmd);
-       if (rtn == SUCCESS) {
-               scmd->device->was_reset = 1;
-               scmd->device->expecting_cc_ua = 1;
-       }
-
-       return rtn;
-}
-
 /**
  * scsi_eh_try_stu - Send START_UNIT to device.
  * @scmd:      Scsi cmd to send START_UNIT
@@ -976,64 +1041,6 @@ static int scsi_eh_bus_device_reset(struct Scsi_Host *shost,
        return list_empty(work_q);
 }
 
-/**
- * scsi_try_bus_reset - ask host to perform a bus reset
- * @scmd:      SCSI cmd to send bus reset.
- **/
-static int scsi_try_bus_reset(struct scsi_cmnd *scmd)
-{
-       unsigned long flags;
-       int rtn;
-
-       SCSI_LOG_ERROR_RECOVERY(3, printk("%s: Snd Bus RST\n",
-                                         __FUNCTION__));
-
-       if (!scmd->device->host->hostt->eh_bus_reset_handler)
-               return FAILED;
-
-       rtn = scmd->device->host->hostt->eh_bus_reset_handler(scmd);
-
-       if (rtn == SUCCESS) {
-               if (!scmd->device->host->hostt->skip_settle_delay)
-                       ssleep(BUS_RESET_SETTLE_TIME);
-               spin_lock_irqsave(scmd->device->host->host_lock, flags);
-               scsi_report_bus_reset(scmd->device->host,
-                                     scmd_channel(scmd));
-               spin_unlock_irqrestore(scmd->device->host->host_lock, flags);
-       }
-
-       return rtn;
-}
-
-/**
- * scsi_try_host_reset - ask host adapter to reset itself
- * @scmd:      SCSI cmd to send hsot reset.
- **/
-static int scsi_try_host_reset(struct scsi_cmnd *scmd)
-{
-       unsigned long flags;
-       int rtn;
-
-       SCSI_LOG_ERROR_RECOVERY(3, printk("%s: Snd Host RST\n",
-                                         __FUNCTION__));
-
-       if (!scmd->device->host->hostt->eh_host_reset_handler)
-               return FAILED;
-
-       rtn = scmd->device->host->hostt->eh_host_reset_handler(scmd);
-
-       if (rtn == SUCCESS) {
-               if (!scmd->device->host->hostt->skip_settle_delay)
-                       ssleep(HOST_RESET_SETTLE_TIME);
-               spin_lock_irqsave(scmd->device->host->host_lock, flags);
-               scsi_report_bus_reset(scmd->device->host,
-                                     scmd_channel(scmd));
-               spin_unlock_irqrestore(scmd->device->host->host_lock, flags);
-       }
-
-       return rtn;
-}
-
 /**
  * scsi_eh_bus_reset - send a bus reset 
  * @shost:     scsi host being recovered.