Skip to content

Commit

Permalink
scsi: sd: Optimal I/O size should be a multiple of reported granularity
Browse files Browse the repository at this point in the history
Commit a83da8a ("scsi: sd: Optimal I/O size should be a multiple of
physical block size") validated the reported optimal I/O size against the
physical block size to overcome problems with devices reporting nonsensical
transfer sizes.

However, some devices claim conformity to older SCSI versions that predate
the physical block size being reported. Other devices do not report a
physical block size at all. We need to be able to validate the optimal I/O
size on those devices as well.

Many devices report an OPTIMAL TRANSFER LENGTH GRANULARITY in the same VPD
page as the OPTIMAL TRANSFER LENGTH. Use this value to validate the optimal
I/O size. Also check that the reported granularity is a multiple of the
physical block size, if supported.

Link: https://lore.kernel.org/r/33fb522e-4f61-1b76-914f-c9e6a3553c9b@gmail.com
Link: https://lore.kernel.org/r/20220302053559.32147-9-martin.petersen@oracle.com
Reported-by: Bernhard Sulzer <micraft.b@gmail.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
  • Loading branch information
Martin K. Petersen committed May 2, 2022
1 parent 7fb019c commit 631669a
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 4 deletions.
45 changes: 41 additions & 4 deletions drivers/scsi/sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2843,7 +2843,6 @@ static void sd_read_app_tag_own(struct scsi_disk *sdkp, unsigned char *buffer)
*/
static void sd_read_block_limits(struct scsi_disk *sdkp)
{
unsigned int sector_sz = sdkp->device->sector_size;
struct scsi_vpd *vpd;

rcu_read_lock();
Expand All @@ -2852,9 +2851,7 @@ static void sd_read_block_limits(struct scsi_disk *sdkp)
if (!vpd || vpd->len < 16)
goto out;

blk_queue_io_min(sdkp->disk->queue,
get_unaligned_be16(&vpd->data[6]) * sector_sz);

sdkp->min_xfer_blocks = get_unaligned_be16(&vpd->data[6]);
sdkp->max_xfer_blocks = get_unaligned_be32(&vpd->data[8]);
sdkp->opt_xfer_blocks = get_unaligned_be32(&vpd->data[12]);

Expand Down Expand Up @@ -3112,6 +3109,29 @@ static void sd_read_cpr(struct scsi_disk *sdkp)
kfree(buffer);
}

static bool sd_validate_min_xfer_size(struct scsi_disk *sdkp)
{
struct scsi_device *sdp = sdkp->device;
unsigned int min_xfer_bytes =
logical_to_bytes(sdp, sdkp->min_xfer_blocks);

if (sdkp->min_xfer_blocks == 0)
return false;

if (min_xfer_bytes & (sdkp->physical_block_size - 1)) {
sd_first_printk(KERN_WARNING, sdkp,
"Preferred minimum I/O size %u bytes not a " \
"multiple of physical block size (%u bytes)\n",
min_xfer_bytes, sdkp->physical_block_size);
sdkp->min_xfer_blocks = 0;
return false;
}

sd_first_printk(KERN_INFO, sdkp, "Preferred minimum I/O size %u bytes\n",
min_xfer_bytes);
return true;
}

/*
* Determine the device's preferred I/O size for reads and writes
* unless the reported value is unreasonably small, large, not a
Expand All @@ -3123,6 +3143,8 @@ static bool sd_validate_opt_xfer_size(struct scsi_disk *sdkp,
struct scsi_device *sdp = sdkp->device;
unsigned int opt_xfer_bytes =
logical_to_bytes(sdp, sdkp->opt_xfer_blocks);
unsigned int min_xfer_bytes =
logical_to_bytes(sdp, sdkp->min_xfer_blocks);

if (sdkp->opt_xfer_blocks == 0)
return false;
Expand Down Expand Up @@ -3151,6 +3173,15 @@ static bool sd_validate_opt_xfer_size(struct scsi_disk *sdkp,
return false;
}

if (min_xfer_bytes && opt_xfer_bytes % min_xfer_bytes) {
sd_first_printk(KERN_WARNING, sdkp,
"Optimal transfer size %u bytes not a " \
"multiple of preferred minimum block " \
"size (%u bytes)\n",
opt_xfer_bytes, min_xfer_bytes);
return false;
}

if (opt_xfer_bytes & (sdkp->physical_block_size - 1)) {
sd_first_printk(KERN_WARNING, sdkp,
"Optimal transfer size %u bytes not a " \
Expand Down Expand Up @@ -3243,6 +3274,12 @@ static int sd_revalidate_disk(struct gendisk *disk)
dev_max = min_not_zero(dev_max, sdkp->max_xfer_blocks);
q->limits.max_dev_sectors = logical_to_sectors(sdp, dev_max);

if (sd_validate_min_xfer_size(sdkp))
blk_queue_io_min(sdkp->disk->queue,
logical_to_bytes(sdp, sdkp->min_xfer_blocks));
else
blk_queue_io_min(sdkp->disk->queue, 0);

if (sd_validate_opt_xfer_size(sdkp, dev_max)) {
q->limits.io_opt = logical_to_bytes(sdp, sdkp->opt_xfer_blocks);
rw_max = logical_to_sectors(sdp, sdkp->opt_xfer_blocks);
Expand Down
1 change: 1 addition & 0 deletions drivers/scsi/sd.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ struct scsi_disk {
atomic_t openers;
sector_t capacity; /* size in logical blocks */
int max_retries;
u32 min_xfer_blocks;
u32 max_xfer_blocks;
u32 opt_xfer_blocks;
u32 max_ws_blocks;
Expand Down

0 comments on commit 631669a

Please sign in to comment.