Merge tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
[linux-2.6-block.git] / drivers / scsi / scsi_lib.c
index df5ac03d5d6c2eb5233ad7fcfdad37a1e487b4e6..2e28e2360c85740d0b3ebb391785ee111c78d47b 100644 (file)
@@ -184,6 +184,92 @@ void scsi_queue_insert(struct scsi_cmnd *cmd, int reason)
        __scsi_queue_insert(cmd, reason, true);
 }
 
+void scsi_failures_reset_retries(struct scsi_failures *failures)
+{
+       struct scsi_failure *failure;
+
+       failures->total_retries = 0;
+
+       for (failure = failures->failure_definitions; failure->result;
+            failure++)
+               failure->retries = 0;
+}
+EXPORT_SYMBOL_GPL(scsi_failures_reset_retries);
+
+/**
+ * scsi_check_passthrough - Determine if passthrough scsi_cmnd needs a retry.
+ * @scmd: scsi_cmnd to check.
+ * @failures: scsi_failures struct that lists failures to check for.
+ *
+ * Returns -EAGAIN if the caller should retry else 0.
+ */
+static int scsi_check_passthrough(struct scsi_cmnd *scmd,
+                                 struct scsi_failures *failures)
+{
+       struct scsi_failure *failure;
+       struct scsi_sense_hdr sshdr;
+       enum sam_status status;
+
+       if (!failures)
+               return 0;
+
+       for (failure = failures->failure_definitions; failure->result;
+            failure++) {
+               if (failure->result == SCMD_FAILURE_RESULT_ANY)
+                       goto maybe_retry;
+
+               if (host_byte(scmd->result) &&
+                   host_byte(scmd->result) == host_byte(failure->result))
+                       goto maybe_retry;
+
+               status = status_byte(scmd->result);
+               if (!status)
+                       continue;
+
+               if (failure->result == SCMD_FAILURE_STAT_ANY &&
+                   !scsi_status_is_good(scmd->result))
+                       goto maybe_retry;
+
+               if (status != status_byte(failure->result))
+                       continue;
+
+               if (status_byte(failure->result) != SAM_STAT_CHECK_CONDITION ||
+                   failure->sense == SCMD_FAILURE_SENSE_ANY)
+                       goto maybe_retry;
+
+               if (!scsi_command_normalize_sense(scmd, &sshdr))
+                       return 0;
+
+               if (failure->sense != sshdr.sense_key)
+                       continue;
+
+               if (failure->asc == SCMD_FAILURE_ASC_ANY)
+                       goto maybe_retry;
+
+               if (failure->asc != sshdr.asc)
+                       continue;
+
+               if (failure->ascq == SCMD_FAILURE_ASCQ_ANY ||
+                   failure->ascq == sshdr.ascq)
+                       goto maybe_retry;
+       }
+
+       return 0;
+
+maybe_retry:
+       if (failure->allowed) {
+               if (failure->allowed == SCMD_FAILURE_NO_LIMIT ||
+                   ++failure->retries <= failure->allowed)
+                       return -EAGAIN;
+       } else {
+               if (failures->total_allowed == SCMD_FAILURE_NO_LIMIT ||
+                   ++failures->total_retries <= failures->total_allowed)
+                       return -EAGAIN;
+       }
+
+       return 0;
+}
+
 /**
  * scsi_execute_cmd - insert request and wait for the result
  * @sdev:      scsi_device
@@ -192,7 +278,7 @@ void scsi_queue_insert(struct scsi_cmnd *cmd, int reason)
  * @buffer:    data buffer
  * @bufflen:   len of buffer
  * @timeout:   request timeout in HZ
- * @retries:   number of times to retry request
+ * @ml_retries:        number of times SCSI midlayer will retry request
  * @args:      Optional args. See struct definition for field descriptions
  *
  * Returns the scsi_cmnd result field if a command was executed, or a negative
@@ -200,7 +286,7 @@ void scsi_queue_insert(struct scsi_cmnd *cmd, int reason)
  */
 int scsi_execute_cmd(struct scsi_device *sdev, const unsigned char *cmd,
                     blk_opf_t opf, void *buffer, unsigned int bufflen,
-                    int timeout, int retries,
+                    int timeout, int ml_retries,
                     const struct scsi_exec_args *args)
 {
        static const struct scsi_exec_args default_args;
@@ -214,6 +300,7 @@ int scsi_execute_cmd(struct scsi_device *sdev, const unsigned char *cmd,
                              args->sense_len != SCSI_SENSE_BUFFERSIZE))
                return -EINVAL;
 
+retry:
        req = scsi_alloc_request(sdev->request_queue, opf, args->req_flags);
        if (IS_ERR(req))
                return PTR_ERR(req);
@@ -227,7 +314,7 @@ int scsi_execute_cmd(struct scsi_device *sdev, const unsigned char *cmd,
        scmd = blk_mq_rq_to_pdu(req);
        scmd->cmd_len = COMMAND_SIZE(cmd[0]);
        memcpy(scmd->cmnd, cmd, scmd->cmd_len);
-       scmd->allowed = retries;
+       scmd->allowed = ml_retries;
        scmd->flags |= args->scmd_flags;
        req->timeout = timeout;
        req->rq_flags |= RQF_QUIET;
@@ -237,6 +324,11 @@ int scsi_execute_cmd(struct scsi_device *sdev, const unsigned char *cmd,
         */
        blk_execute_rq(req, true);
 
+       if (scsi_check_passthrough(scmd, args->failures) == -EAGAIN) {
+               blk_mq_free_request(req);
+               goto retry;
+       }
+
        /*
         * Some devices (USB mass-storage in particular) may transfer
         * garbage data together with a residue indicating that the data
@@ -2172,11 +2264,25 @@ scsi_mode_sense(struct scsi_device *sdev, int dbd, int modepage, int subpage,
        unsigned char cmd[12];
        int use_10_for_ms;
        int header_length;
-       int result, retry_count = retries;
+       int result;
        struct scsi_sense_hdr my_sshdr;
+       struct scsi_failure failure_defs[] = {
+               {
+                       .sense = UNIT_ATTENTION,
+                       .asc = SCMD_FAILURE_ASC_ANY,
+                       .ascq = SCMD_FAILURE_ASCQ_ANY,
+                       .allowed = retries,
+                       .result = SAM_STAT_CHECK_CONDITION,
+               },
+               {}
+       };
+       struct scsi_failures failures = {
+               .failure_definitions = failure_defs,
+       };
        const struct scsi_exec_args exec_args = {
                /* caller might not be interested in sense, but we need it */
                .sshdr = sshdr ? : &my_sshdr,
+               .failures = &failures,
        };
 
        memset(data, 0, sizeof(*data));
@@ -2238,12 +2344,6 @@ scsi_mode_sense(struct scsi_device *sdev, int dbd, int modepage, int subpage,
                                        goto retry;
                                }
                        }
-                       if (scsi_status_is_check_condition(result) &&
-                           sshdr->sense_key == UNIT_ATTENTION &&
-                           retry_count) {
-                               retry_count--;
-                               goto retry;
-                       }
                }
                return -EIO;
        }
@@ -3336,3 +3436,7 @@ void scsi_build_sense(struct scsi_cmnd *scmd, int desc, u8 key, u8 asc, u8 ascq)
        scmd->result = SAM_STAT_CHECK_CONDITION;
 }
 EXPORT_SYMBOL_GPL(scsi_build_sense);
+
+#ifdef CONFIG_SCSI_LIB_KUNIT_TEST
+#include "scsi_lib_test.c"
+#endif