FROMLIST: scsi: core: Retry unaligned zoned writes

If zoned writes (REQ_OP_WRITE) for a sequential write required zone have
a starting LBA that differs from the write pointer, e.g. because zoned
writes have been reordered, then the storage device will respond with an
UNALIGNED WRITE COMMAND error. Send commands that failed with an
unaligned write error to the SCSI error handler if zone write locking is
disabled. Let the SCSI error handler sort SCSI commands per LBA before
resubmitting these.

If zone write locking is disabled, increase the number of retries for
write commands sent to a sequential zone to the maximum number of
outstanding commands because in the worst case the number of times
reordered zoned writes have to be retried is (number of outstanding
writes per sequential zone) - 1.

Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Cc: Martin K. Petersen <martin.petersen@oracle.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Ming Lei <ming.lei@redhat.com>
Change-Id: I519cb3aedea7e0635be8af554edee76e8d162498
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Bug: 234829282
Link: https://lore.kernel.org/linux-block/20230804154821.3232094-1-bvanassche@acm.org/T/#ma6573df4361baa921c258e3b39685a5c4c509faa
Signed-off-by: Bart Van Assche <bvanassche@google.com>
This commit is contained in:
Bart Van Assche
2023-01-09 15:27:37 -08:00
committed by Bart Van Assche
parent 4e1d1b839d
commit 3347f7daae
4 changed files with 42 additions and 0 deletions

View File

@@ -27,6 +27,7 @@
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/list_sort.h>
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
@@ -671,6 +672,16 @@ enum scsi_disposition scsi_check_sense(struct scsi_cmnd *scmd)
fallthrough;
case ILLEGAL_REQUEST:
/*
* Unaligned write command. This may indicate that zoned writes
* have been received by the device in the wrong order. If zone
* write locking is disabled, retry after all pending commands
* have completed.
*/
if (sshdr.asc == 0x21 && sshdr.ascq == 0x04 &&
blk_queue_no_zone_write_lock(scsi_cmd_to_rq(scmd)->q))
return NEEDS_DELAYED_RETRY;
if (sshdr.asc == 0x20 || /* Invalid command operation code */
sshdr.asc == 0x21 || /* Logical block address out of range */
sshdr.asc == 0x22 || /* Invalid function */
@@ -2129,6 +2140,25 @@ void scsi_eh_ready_devs(struct Scsi_Host *shost,
}
EXPORT_SYMBOL_GPL(scsi_eh_ready_devs);
/*
* Returns a negative value if @_a has a lower starting sector than @_b, zero if
* both have the same starting sector and a positive value otherwise.
*/
static int scsi_cmp_sector(void *priv, const struct list_head *_a,
const struct list_head *_b)
{
struct scsi_cmnd *a = list_entry(_a, typeof(*a), eh_entry);
struct scsi_cmnd *b = list_entry(_b, typeof(*b), eh_entry);
const sector_t pos_a = blk_rq_pos(scsi_cmd_to_rq(a));
const sector_t pos_b = blk_rq_pos(scsi_cmd_to_rq(b));
if (pos_a < pos_b)
return -1;
if (pos_a > pos_b)
return 1;
return 0;
}
/**
* scsi_eh_flush_done_q - finish processed commands or retry them.
* @done_q: list_head of processed commands.
@@ -2137,6 +2167,13 @@ void scsi_eh_flush_done_q(struct list_head *done_q)
{
struct scsi_cmnd *scmd, *next;
/*
* Sort pending SCSI commands in starting sector order. This is
* important if one of the SCSI devices associated with @shost is a
* zoned block device for which zone write locking is disabled.
*/
list_sort(NULL, done_q, scsi_cmp_sector);
list_for_each_entry_safe(scmd, next, done_q, eh_entry) {
list_del_init(&scmd->eh_entry);
if (scsi_device_online(scmd->device) &&

View File

@@ -1434,6 +1434,7 @@ static void scsi_complete(struct request *rq)
case ADD_TO_MLQUEUE:
scsi_queue_insert(cmd, SCSI_MLQUEUE_DEVICE_BUSY);
break;
case NEEDS_DELAYED_RETRY:
default:
scsi_eh_scmd_add(cmd);
break;

View File

@@ -1304,6 +1304,9 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)
cmd->transfersize = sdp->sector_size;
cmd->underflow = nr_blocks << 9;
cmd->allowed = sdkp->max_retries;
if (blk_queue_no_zone_write_lock(rq->q) &&
blk_rq_is_seq_zoned_write(rq))
cmd->allowed += rq->q->nr_requests;
cmd->sdb.length = nr_blocks * sdp->sector_size;
SCSI_LOG_HLQUEUE(1,

View File

@@ -93,6 +93,7 @@ static inline int scsi_status_is_check_condition(int status)
* Internal return values.
*/
enum scsi_disposition {
NEEDS_DELAYED_RETRY = 0x2000,
NEEDS_RETRY = 0x2001,
SUCCESS = 0x2002,
FAILED = 0x2003,