Skip to content

Commit

Permalink
scsi: core: Allow passthrough to request midlayer retries
Browse files Browse the repository at this point in the history
For passthrough we don't retry any error which we get a check condition
for. This results in a lot of callers driving their own retries for all
UAs, specific UAs, NOT_READY, specific sense values or any type of failure.

This adds the core code to allow passthrough users to specify what errors
they want the SCSI midlayer to retry for them. We can then convert users to
drop a lot of their sense parsing and retry handling.

Signed-off-by: Mike Christie <michael.christie@oracle.com>
Link: https://lore.kernel.org/r/20240123002220.129141-2-michael.christie@oracle.com
Reviewed-by: John Garry <john.g.garry@oracle.com>
Acked-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
  • Loading branch information
mikechristie authored and martinkpetersen committed Jan 30, 2024
1 parent 6613476 commit 994724e
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 3 deletions.
98 changes: 95 additions & 3 deletions drivers/scsi/scsi_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -192,15 +278,15 @@ 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
* Linux error code if we didn't get that far.
*/
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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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
Expand Down
48 changes: 48 additions & 0 deletions include/scsi/scsi_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,52 @@ extern int scsi_is_sdev_device(const struct device *);
extern int scsi_is_target_device(const struct device *);
extern void scsi_sanitize_inquiry_string(unsigned char *s, int len);

/*
* scsi_execute_cmd users can set scsi_failure.result to have
* scsi_check_passthrough fail/retry a command. scsi_failure.result can be a
* specific host byte or message code, or SCMD_FAILURE_RESULT_ANY can be used
* to match any host or message code.
*/
#define SCMD_FAILURE_RESULT_ANY 0x7fffffff
/*
* Set scsi_failure.result to SCMD_FAILURE_STAT_ANY to fail/retry any failure
* scsi_status_is_good returns false for.
*/
#define SCMD_FAILURE_STAT_ANY 0xff
/*
* The following can be set to the scsi_failure sense, asc and ascq fields to
* match on any sense, ASC, or ASCQ value.
*/
#define SCMD_FAILURE_SENSE_ANY 0xff
#define SCMD_FAILURE_ASC_ANY 0xff
#define SCMD_FAILURE_ASCQ_ANY 0xff
/* Always retry a matching failure. */
#define SCMD_FAILURE_NO_LIMIT -1

struct scsi_failure {
int result;
u8 sense;
u8 asc;
u8 ascq;
/*
* Number of times scsi_execute_cmd will retry the failure. It does
* not count for the total_allowed.
*/
s8 allowed;
/* Number of times the failure has been retried. */
s8 retries;
};

struct scsi_failures {
/*
* If a scsi_failure does not have a retry limit setup this limit will
* be used.
*/
int total_allowed;
int total_retries;
struct scsi_failure *failure_definitions;
};

/* Optional arguments to scsi_execute_cmd */
struct scsi_exec_args {
unsigned char *sense; /* sense buffer */
Expand All @@ -497,12 +543,14 @@ struct scsi_exec_args {
blk_mq_req_flags_t req_flags; /* BLK_MQ_REQ flags */
int scmd_flags; /* SCMD flags */
int *resid; /* residual length */
struct scsi_failures *failures; /* failures to retry */
};

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,
const struct scsi_exec_args *args);
void scsi_failures_reset_retries(struct scsi_failures *failures);

extern void sdev_disable_disk_events(struct scsi_device *sdev);
extern void sdev_enable_disk_events(struct scsi_device *sdev);
Expand Down

0 comments on commit 994724e

Please sign in to comment.