From f27bac2761cab5a2e212dea602d22457a9aa6943 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Jul 2008 14:59:30 +0900 Subject: [SCSI] sd: update index allocation and use ida instead of idr Update index allocation as follows. * sd_index_idr is used only for ID allocation and mapping functionality is not used. Use more memory efficient ida instead. * idr and ida have their own locks inside them and don't need them for operation. Drop it. * index wasn't freed if probing failed after index allocation. fix it. * ida allocation should be repeated if it fails with -EAGAIN. Signed-off-by: Tejun Heo Signed-off-by: James Bottomley --- drivers/scsi/sd.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'drivers/scsi/sd.c') diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 0c63947d8a9d..99dddcae7851 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -99,8 +99,7 @@ static void scsi_disk_release(struct device *cdev); static void sd_print_sense_hdr(struct scsi_disk *, struct scsi_sense_hdr *); static void sd_print_result(struct scsi_disk *, int); -static DEFINE_IDR(sd_index_idr); -static DEFINE_SPINLOCK(sd_index_lock); +static DEFINE_IDA(sd_index_ida); /* This semaphore is used to mediate the 0->1 reference get in the * face of object destruction (i.e. we can't allow a get on an @@ -1643,18 +1642,20 @@ static int sd_probe(struct device *dev) if (!gd) goto out_free; - if (!idr_pre_get(&sd_index_idr, GFP_KERNEL)) - goto out_put; + do { + if (!ida_pre_get(&sd_index_ida, GFP_KERNEL)) + goto out_put; - spin_lock(&sd_index_lock); - error = idr_get_new(&sd_index_idr, NULL, &index); - spin_unlock(&sd_index_lock); + error = ida_get_new(&sd_index_ida, &index); + } while (error == -EAGAIN); - if (index >= SD_MAX_DISKS) - error = -EBUSY; if (error) goto out_put; + error = -EBUSY; + if (index >= SD_MAX_DISKS) + goto out_free_index; + sdkp->device = sdp; sdkp->driver = &sd_template; sdkp->disk = gd; @@ -1675,7 +1676,7 @@ static int sd_probe(struct device *dev) strncpy(sdkp->dev.bus_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE); if (device_add(&sdkp->dev)) - goto out_put; + goto out_free_index; get_device(&sdp->sdev_gendev); @@ -1717,6 +1718,8 @@ static int sd_probe(struct device *dev) return 0; + out_free_index: + ida_remove(&sd_index_ida, index); out_put: put_disk(gd); out_free: @@ -1766,9 +1769,7 @@ static void scsi_disk_release(struct device *dev) struct scsi_disk *sdkp = to_scsi_disk(dev); struct gendisk *disk = sdkp->disk; - spin_lock(&sd_index_lock); - idr_remove(&sd_index_idr, sdkp->index); - spin_unlock(&sd_index_lock); + ida_remove(&sd_index_ida, sdkp->index); disk->private_data = NULL; put_disk(disk); -- cgit v1.2.3 From e0597d70012c82e16ee152270a55d89d8bf66693 Mon Sep 17 00:00:00 2001 From: "Martin K. Petersen" Date: Thu, 17 Jul 2008 04:28:34 -0400 Subject: [SCSI] sd: Identify DIF protection type and application tag ownership If a disk is formatted with protection information (Inquiry bit PROTECT=1) it is required to support Read Capacity(16). Force use of the 16-bit command in this case and extract the P_TYPE field which indicates whether the disk is formatted using DIF Type 1, 2 or 3. The ATO (App Tag Own) bit in the Control Mode Page indicates whether the storage device or the initiator own the contents of the DIF application tag. Signed-off-by: Martin K. Petersen Signed-off-by: James Bottomley --- drivers/scsi/sd.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- drivers/scsi/sd.h | 23 ++++++++++ 2 files changed, 141 insertions(+), 4 deletions(-) (limited to 'drivers/scsi/sd.c') diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 99dddcae7851..56b9501d12f3 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -233,6 +233,24 @@ sd_show_allow_restart(struct device *dev, struct device_attribute *attr, return snprintf(buf, 40, "%d\n", sdkp->device->allow_restart); } +static ssize_t +sd_show_protection_type(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct scsi_disk *sdkp = to_scsi_disk(dev); + + return snprintf(buf, 20, "%u\n", sdkp->protection_type); +} + +static ssize_t +sd_show_app_tag_own(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct scsi_disk *sdkp = to_scsi_disk(dev); + + return snprintf(buf, 20, "%u\n", sdkp->ATO); +} + static struct device_attribute sd_disk_attrs[] = { __ATTR(cache_type, S_IRUGO|S_IWUSR, sd_show_cache_type, sd_store_cache_type), @@ -241,6 +259,8 @@ static struct device_attribute sd_disk_attrs[] = { sd_store_allow_restart), __ATTR(manage_start_stop, S_IRUGO|S_IWUSR, sd_show_manage_start_stop, sd_store_manage_start_stop), + __ATTR(protection_type, S_IRUGO, sd_show_protection_type, NULL), + __ATTR(app_tag_own, S_IRUGO, sd_show_app_tag_own, NULL), __ATTR_NULL, }; @@ -1164,6 +1184,49 @@ sd_spinup_disk(struct scsi_disk *sdkp) } } + +/* + * Determine whether disk supports Data Integrity Field. + */ +void sd_read_protection_type(struct scsi_disk *sdkp, unsigned char *buffer) +{ + struct scsi_device *sdp = sdkp->device; + u8 type; + + if (scsi_device_protection(sdp) == 0 || (buffer[12] & 1) == 0) + type = 0; + else + type = ((buffer[12] >> 1) & 7) + 1; /* P_TYPE 0 = Type 1 */ + + switch (type) { + case SD_DIF_TYPE0_PROTECTION: + sdkp->protection_type = 0; + break; + + case SD_DIF_TYPE1_PROTECTION: + case SD_DIF_TYPE3_PROTECTION: + sdkp->protection_type = type; + break; + + case SD_DIF_TYPE2_PROTECTION: + sd_printk(KERN_ERR, sdkp, "formatted with DIF Type 2 " \ + "protection which is currently unsupported. " \ + "Disabling disk!\n"); + goto disable; + + default: + sd_printk(KERN_ERR, sdkp, "formatted with unknown " \ + "protection type %d. Disabling disk!\n", type); + goto disable; + } + + return; + +disable: + sdkp->protection_type = 0; + sdkp->capacity = 0; +} + /* * read disk capacity */ @@ -1173,7 +1236,8 @@ sd_read_capacity(struct scsi_disk *sdkp, unsigned char *buffer) unsigned char cmd[16]; int the_result, retries; int sector_size = 0; - int longrc = 0; + /* Force READ CAPACITY(16) when PROTECT=1 */ + int longrc = scsi_device_protection(sdkp->device) ? 1 : 0; struct scsi_sense_hdr sshdr; int sense_valid = 0; struct scsi_device *sdp = sdkp->device; @@ -1185,8 +1249,8 @@ repeat: memset((void *) cmd, 0, 16); cmd[0] = SERVICE_ACTION_IN; cmd[1] = SAI_READ_CAPACITY_16; - cmd[13] = 12; - memset((void *) buffer, 0, 12); + cmd[13] = 13; + memset((void *) buffer, 0, 13); } else { cmd[0] = READ_CAPACITY; memset((void *) &cmd[1], 0, 9); @@ -1194,7 +1258,7 @@ repeat: } the_result = scsi_execute_req(sdp, cmd, DMA_FROM_DEVICE, - buffer, longrc ? 12 : 8, &sshdr, + buffer, longrc ? 13 : 8, &sshdr, SD_TIMEOUT, SD_MAX_RETRIES); if (media_not_present(sdkp, &sshdr)) @@ -1269,6 +1333,8 @@ repeat: sector_size = (buffer[8] << 24) | (buffer[9] << 16) | (buffer[10] << 8) | buffer[11]; + + sd_read_protection_type(sdkp, buffer); } /* Some devices return the total number of sectors, not the @@ -1530,6 +1596,52 @@ defaults: sdkp->DPOFUA = 0; } +/* + * The ATO bit indicates whether the DIF application tag is available + * for use by the operating system. + */ +void sd_read_app_tag_own(struct scsi_disk *sdkp, unsigned char *buffer) +{ + int res, offset; + struct scsi_device *sdp = sdkp->device; + struct scsi_mode_data data; + struct scsi_sense_hdr sshdr; + + if (sdp->type != TYPE_DISK) + return; + + if (sdkp->protection_type == 0) + return; + + res = scsi_mode_sense(sdp, 1, 0x0a, buffer, 36, SD_TIMEOUT, + SD_MAX_RETRIES, &data, &sshdr); + + if (!scsi_status_is_good(res) || !data.header_length || + data.length < 6) { + sd_printk(KERN_WARNING, sdkp, + "getting Control mode page failed, assume no ATO\n"); + + if (scsi_sense_valid(&sshdr)) + sd_print_sense_hdr(sdkp, &sshdr); + + return; + } + + offset = data.header_length + data.block_descriptor_length; + + if ((buffer[offset] & 0x3f) != 0x0a) { + sd_printk(KERN_ERR, sdkp, "ATO Got wrong page\n"); + return; + } + + if ((buffer[offset + 5] & 0x80) == 0) + return; + + sdkp->ATO = 1; + + return; +} + /** * sd_revalidate_disk - called the first time a new disk is seen, * performs disk spin up, read_capacity, etc. @@ -1566,6 +1678,7 @@ static int sd_revalidate_disk(struct gendisk *disk) sdkp->write_prot = 0; sdkp->WCE = 0; sdkp->RCD = 0; + sdkp->ATO = 0; sd_spinup_disk(sdkp); @@ -1577,6 +1690,7 @@ static int sd_revalidate_disk(struct gendisk *disk) sd_read_capacity(sdkp, buffer); sd_read_write_protect_flag(sdkp, buffer); sd_read_cache_type(sdkp, buffer); + sd_read_app_tag_own(sdkp, buffer); } /* diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 03a3d45cfa42..86b18d4170fe 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -41,7 +41,9 @@ struct scsi_disk { u32 index; u8 media_present; u8 write_prot; + u8 protection_type;/* Data Integrity Field */ unsigned previous_state : 1; + unsigned ATO : 1; /* state of disk ATO bit */ unsigned WCE : 1; /* state of disk WCE bit */ unsigned RCD : 1; /* state of disk RCD bit, unused */ unsigned DPOFUA : 1; /* state of disk DPOFUA bit */ @@ -59,4 +61,25 @@ static inline struct scsi_disk *scsi_disk(struct gendisk *disk) (sdsk)->disk->disk_name, ##a) : \ sdev_printk(prefix, (sdsk)->device, fmt, ##a) +/* + * A DIF-capable target device can be formatted with different + * protection schemes. Currently 0 through 3 are defined: + * + * Type 0 is regular (unprotected) I/O + * + * Type 1 defines the contents of the guard and reference tags + * + * Type 2 defines the contents of the guard and reference tags and + * uses 32-byte commands to seed the latter + * + * Type 3 defines the contents of the guard tag only + */ + +enum sd_dif_target_protection_types { + SD_DIF_TYPE0_PROTECTION = 0x0, + SD_DIF_TYPE1_PROTECTION = 0x1, + SD_DIF_TYPE2_PROTECTION = 0x2, + SD_DIF_TYPE3_PROTECTION = 0x3, +}; + #endif /* _SCSI_DISK_H */ -- cgit v1.2.3 From af55ff675a8461da6a632320710b050af4366e0c Mon Sep 17 00:00:00 2001 From: "Martin K. Petersen" Date: Thu, 17 Jul 2008 04:28:35 -0400 Subject: [SCSI] sd: Support for SCSI disk (SBC) Data Integrity Field Support for controllers and disks that implement DIF protection information: - During command preparation the RDPROTECT/WRPROTECT must be set correctly if the target has DIF enabled. - READ(6) and WRITE(6) are not supported when DIF is on. - The controller must be told how to handle the I/O via the protection operation field in scsi_cmnd. - Refactor the I/O completion code that extracts failed LBA from the returned sense data and handle DIF failures correctly. - sd_dif.c implements the functions required to prepare and complete requests with protection information attached. Signed-off-by: Martin K. Petersen Signed-off-by: James Bottomley --- drivers/scsi/Kconfig | 1 + drivers/scsi/Makefile | 2 + drivers/scsi/sd.c | 121 ++++++++---- drivers/scsi/sd.h | 25 +++ drivers/scsi/sd_dif.c | 538 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 647 insertions(+), 40 deletions(-) create mode 100644 drivers/scsi/sd_dif.c (limited to 'drivers/scsi/sd.c') diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 26be540d1dd3..c7f06298bd3c 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -63,6 +63,7 @@ comment "SCSI support type (disk, tape, CD-ROM)" config BLK_DEV_SD tristate "SCSI disk support" depends on SCSI + select CRC_T10DIF ---help--- If you want to use SCSI hard disks, Fibre Channel disks, Serial ATA (SATA) or Parallel ATA (PATA) hard disks, diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index a8149677de23..72fd5043cfa1 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -151,6 +151,8 @@ scsi_mod-$(CONFIG_SCSI_PROC_FS) += scsi_proc.o scsi_tgt-y += scsi_tgt_lib.o scsi_tgt_if.o sd_mod-objs := sd.o +sd_mod-$(CONFIG_BLK_DEV_INTEGRITY) += sd_dif.o + sr_mod-objs := sr.o sr_ioctl.o sr_vendor.o ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \ := -DCONFIG_NCR53C8XX_PREFETCH -DSCSI_NCR_BIG_ENDIAN \ diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 56b9501d12f3..8e08d51a0f05 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -373,6 +373,7 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) struct scsi_cmnd *SCpnt; struct scsi_device *sdp = q->queuedata; struct gendisk *disk = rq->rq_disk; + struct scsi_disk *sdkp; sector_t block = rq->sector; unsigned int this_count = rq->nr_sectors; unsigned int timeout = sdp->timeout; @@ -389,6 +390,7 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) if (ret != BLKPREP_OK) goto out; SCpnt = rq->special; + sdkp = scsi_disk(disk); /* from here on until we're complete, any goto out * is used for a killable error condition */ @@ -478,6 +480,11 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) } SCpnt->cmnd[0] = WRITE_6; SCpnt->sc_data_direction = DMA_TO_DEVICE; + + if (blk_integrity_rq(rq) && + sd_dif_prepare(rq, block, sdp->sector_size) == -EIO) + goto out; + } else if (rq_data_dir(rq) == READ) { SCpnt->cmnd[0] = READ_6; SCpnt->sc_data_direction = DMA_FROM_DEVICE; @@ -492,8 +499,12 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) "writing" : "reading", this_count, rq->nr_sectors)); - SCpnt->cmnd[1] = 0; - + /* Set RDPROTECT/WRPROTECT if disk is formatted with DIF */ + if (scsi_host_dif_capable(sdp->host, sdkp->protection_type)) + SCpnt->cmnd[1] = 1 << 5; + else + SCpnt->cmnd[1] = 0; + if (block > 0xffffffff) { SCpnt->cmnd[0] += READ_16 - READ_6; SCpnt->cmnd[1] |= blk_fua_rq(rq) ? 0x8 : 0; @@ -511,6 +522,7 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) SCpnt->cmnd[13] = (unsigned char) this_count & 0xff; SCpnt->cmnd[14] = SCpnt->cmnd[15] = 0; } else if ((this_count > 0xff) || (block > 0x1fffff) || + scsi_device_protection(SCpnt->device) || SCpnt->device->use_10_for_rw) { if (this_count > 0xffff) this_count = 0xffff; @@ -545,6 +557,10 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) } SCpnt->sdb.length = this_count * sdp->sector_size; + /* If DIF or DIX is enabled, tell HBA how to handle request */ + if (sdkp->protection_type || scsi_prot_sg_count(SCpnt)) + sd_dif_op(SCpnt, sdkp->protection_type, scsi_prot_sg_count(SCpnt)); + /* * We shouldn't disconnect in the middle of a sector, so with a dumb * host adapter, it's safe to assume that we can at least transfer @@ -939,6 +955,48 @@ static struct block_device_operations sd_fops = { .revalidate_disk = sd_revalidate_disk, }; +static unsigned int sd_completed_bytes(struct scsi_cmnd *scmd) +{ + u64 start_lba = scmd->request->sector; + u64 end_lba = scmd->request->sector + (scsi_bufflen(scmd) / 512); + u64 bad_lba; + int info_valid; + + if (!blk_fs_request(scmd->request)) + return 0; + + info_valid = scsi_get_sense_info_fld(scmd->sense_buffer, + SCSI_SENSE_BUFFERSIZE, + &bad_lba); + if (!info_valid) + return 0; + + if (scsi_bufflen(scmd) <= scmd->device->sector_size) + return 0; + + if (scmd->device->sector_size < 512) { + /* only legitimate sector_size here is 256 */ + start_lba <<= 1; + end_lba <<= 1; + } else { + /* be careful ... don't want any overflows */ + u64 factor = scmd->device->sector_size / 512; + do_div(start_lba, factor); + do_div(end_lba, factor); + } + + /* The bad lba was reported incorrectly, we have no idea where + * the error is. + */ + if (bad_lba < start_lba || bad_lba >= end_lba) + return 0; + + /* This computation should always be done in terms of + * the resolution of the device's medium. + */ + return (bad_lba - start_lba) * scmd->device->sector_size; +} + /** * sd_done - bottom half handler: called when the lower level * driver has completed (successfully or otherwise) a scsi command. @@ -949,15 +1007,10 @@ static struct block_device_operations sd_fops = { static int sd_done(struct scsi_cmnd *SCpnt) { int result = SCpnt->result; - unsigned int xfer_size = scsi_bufflen(SCpnt); - unsigned int good_bytes = result ? 0 : xfer_size; - u64 start_lba = SCpnt->request->sector; - u64 end_lba = SCpnt->request->sector + (xfer_size / 512); - u64 bad_lba; + unsigned int good_bytes = result ? 0 : scsi_bufflen(SCpnt); struct scsi_sense_hdr sshdr; int sense_valid = 0; int sense_deferred = 0; - int info_valid; if (result) { sense_valid = scsi_command_normalize_sense(SCpnt, &sshdr); @@ -982,36 +1035,7 @@ static int sd_done(struct scsi_cmnd *SCpnt) switch (sshdr.sense_key) { case HARDWARE_ERROR: case MEDIUM_ERROR: - if (!blk_fs_request(SCpnt->request)) - goto out; - info_valid = scsi_get_sense_info_fld(SCpnt->sense_buffer, - SCSI_SENSE_BUFFERSIZE, - &bad_lba); - if (!info_valid) - goto out; - if (xfer_size <= SCpnt->device->sector_size) - goto out; - if (SCpnt->device->sector_size < 512) { - /* only legitimate sector_size here is 256 */ - start_lba <<= 1; - end_lba <<= 1; - } else { - /* be careful ... don't want any overflows */ - u64 factor = SCpnt->device->sector_size / 512; - do_div(start_lba, factor); - do_div(end_lba, factor); - } - - if (bad_lba < start_lba || bad_lba >= end_lba) - /* the bad lba was reported incorrectly, we have - * no idea where the error is - */ - goto out; - - /* This computation should always be done in terms of - * the resolution of the device's medium. - */ - good_bytes = (bad_lba - start_lba)*SCpnt->device->sector_size; + good_bytes = sd_completed_bytes(SCpnt); break; case RECOVERED_ERROR: case NO_SENSE: @@ -1021,10 +1045,23 @@ static int sd_done(struct scsi_cmnd *SCpnt) scsi_print_sense("sd", SCpnt); SCpnt->result = 0; memset(SCpnt->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE); - good_bytes = xfer_size; + good_bytes = scsi_bufflen(SCpnt); + break; + case ABORTED_COMMAND: + if (sshdr.asc == 0x10) { /* DIF: Disk detected corruption */ + scsi_print_result(SCpnt); + scsi_print_sense("sd", SCpnt); + good_bytes = sd_completed_bytes(SCpnt); + } break; case ILLEGAL_REQUEST: - if (SCpnt->device->use_10_for_rw && + if (sshdr.asc == 0x10) { /* DIX: HBA detected corruption */ + scsi_print_result(SCpnt); + scsi_print_sense("sd", SCpnt); + good_bytes = sd_completed_bytes(SCpnt); + } + if (!scsi_device_protection(SCpnt->device) && + SCpnt->device->use_10_for_rw && (SCpnt->cmnd[0] == READ_10 || SCpnt->cmnd[0] == WRITE_10)) SCpnt->device->use_10_for_rw = 0; @@ -1037,6 +1074,9 @@ static int sd_done(struct scsi_cmnd *SCpnt) break; } out: + if (rq_data_dir(SCpnt->request) == READ && scsi_prot_sg_count(SCpnt)) + sd_dif_complete(SCpnt, good_bytes); + return good_bytes; } @@ -1826,6 +1866,7 @@ static int sd_probe(struct device *dev) dev_set_drvdata(dev, sdkp); add_disk(gd); + sd_dif_config_host(sdkp); sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n", sdp->removable ? "removable " : ""); diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 86b18d4170fe..550b2f70a1f8 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -82,4 +82,29 @@ enum sd_dif_target_protection_types { SD_DIF_TYPE3_PROTECTION = 0x3, }; +/* + * Data Integrity Field tuple. + */ +struct sd_dif_tuple { + __be16 guard_tag; /* Checksum */ + __be16 app_tag; /* Opaque storage */ + __be32 ref_tag; /* Target LBA or indirect LBA */ +}; + +#if defined(CONFIG_BLK_DEV_INTEGRITY) + +extern void sd_dif_op(struct scsi_cmnd *, unsigned int, unsigned int); +extern void sd_dif_config_host(struct scsi_disk *); +extern int sd_dif_prepare(struct request *rq, sector_t, unsigned int); +extern void sd_dif_complete(struct scsi_cmnd *, unsigned int); + +#else /* CONFIG_BLK_DEV_INTEGRITY */ + +#define sd_dif_op(a, b, c) do { } while (0) +#define sd_dif_config_host(a) do { } while (0) +#define sd_dif_prepare(a, b, c) (0) +#define sd_dif_complete(a, b) (0) + +#endif /* CONFIG_BLK_DEV_INTEGRITY */ + #endif /* _SCSI_DISK_H */ diff --git a/drivers/scsi/sd_dif.c b/drivers/scsi/sd_dif.c new file mode 100644 index 000000000000..4d17f3d35aac --- /dev/null +++ b/drivers/scsi/sd_dif.c @@ -0,0 +1,538 @@ +/* + * sd_dif.c - SCSI Data Integrity Field + * + * Copyright (C) 2007, 2008 Oracle Corporation + * Written by: Martin K. Petersen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, + * USA. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sd.h" + +typedef __u16 (csum_fn) (void *, unsigned int); + +static __u16 sd_dif_crc_fn(void *data, unsigned int len) +{ + return cpu_to_be16(crc_t10dif(data, len)); +} + +static __u16 sd_dif_ip_fn(void *data, unsigned int len) +{ + return ip_compute_csum(data, len); +} + +/* + * Type 1 and Type 2 protection use the same format: 16 bit guard tag, + * 16 bit app tag, 32 bit reference tag. + */ +static void sd_dif_type1_generate(struct blk_integrity_exchg *bix, csum_fn *fn) +{ + void *buf = bix->data_buf; + struct sd_dif_tuple *sdt = bix->prot_buf; + sector_t sector = bix->sector; + unsigned int i; + + for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) { + sdt->guard_tag = fn(buf, bix->sector_size); + sdt->ref_tag = cpu_to_be32(sector & 0xffffffff); + sdt->app_tag = 0; + + buf += bix->sector_size; + sector++; + } +} + +static void sd_dif_type1_generate_crc(struct blk_integrity_exchg *bix) +{ + sd_dif_type1_generate(bix, sd_dif_crc_fn); +} + +static void sd_dif_type1_generate_ip(struct blk_integrity_exchg *bix) +{ + sd_dif_type1_generate(bix, sd_dif_ip_fn); +} + +static int sd_dif_type1_verify(struct blk_integrity_exchg *bix, csum_fn *fn) +{ + void *buf = bix->data_buf; + struct sd_dif_tuple *sdt = bix->prot_buf; + sector_t sector = bix->sector; + unsigned int i; + __u16 csum; + + for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) { + /* Unwritten sectors */ + if (sdt->app_tag == 0xffff) + return 0; + + /* Bad ref tag received from disk */ + if (sdt->ref_tag == 0xffffffff) { + printk(KERN_ERR + "%s: bad phys ref tag on sector %lu\n", + bix->disk_name, (unsigned long)sector); + return -EIO; + } + + if (be32_to_cpu(sdt->ref_tag) != (sector & 0xffffffff)) { + printk(KERN_ERR + "%s: ref tag error on sector %lu (rcvd %u)\n", + bix->disk_name, (unsigned long)sector, + be32_to_cpu(sdt->ref_tag)); + return -EIO; + } + + csum = fn(buf, bix->sector_size); + + if (sdt->guard_tag != csum) { + printk(KERN_ERR "%s: guard tag error on sector %lu " \ + "(rcvd %04x, data %04x)\n", bix->disk_name, + (unsigned long)sector, + be16_to_cpu(sdt->guard_tag), be16_to_cpu(csum)); + return -EIO; + } + + buf += bix->sector_size; + sector++; + } + + return 0; +} + +static int sd_dif_type1_verify_crc(struct blk_integrity_exchg *bix) +{ + return sd_dif_type1_verify(bix, sd_dif_crc_fn); +} + +static int sd_dif_type1_verify_ip(struct blk_integrity_exchg *bix) +{ + return sd_dif_type1_verify(bix, sd_dif_ip_fn); +} + +/* + * Functions for interleaving and deinterleaving application tags + */ +static void sd_dif_type1_set_tag(void *prot, void *tag_buf, unsigned int sectors) +{ + struct sd_dif_tuple *sdt = prot; + char *tag = tag_buf; + unsigned int i, j; + + for (i = 0, j = 0 ; i < sectors ; i++, j += 2, sdt++) { + sdt->app_tag = tag[j] << 8 | tag[j+1]; + BUG_ON(sdt->app_tag == 0xffff); + } +} + +static void sd_dif_type1_get_tag(void *prot, void *tag_buf, unsigned int sectors) +{ + struct sd_dif_tuple *sdt = prot; + char *tag = tag_buf; + unsigned int i, j; + + for (i = 0, j = 0 ; i < sectors ; i++, j += 2, sdt++) { + tag[j] = (sdt->app_tag & 0xff00) >> 8; + tag[j+1] = sdt->app_tag & 0xff; + } +} + +static struct blk_integrity dif_type1_integrity_crc = { + .name = "T10-DIF-TYPE1-CRC", + .generate_fn = sd_dif_type1_generate_crc, + .verify_fn = sd_dif_type1_verify_crc, + .get_tag_fn = sd_dif_type1_get_tag, + .set_tag_fn = sd_dif_type1_set_tag, + .tuple_size = sizeof(struct sd_dif_tuple), + .tag_size = 0, +}; + +static struct blk_integrity dif_type1_integrity_ip = { + .name = "T10-DIF-TYPE1-IP", + .generate_fn = sd_dif_type1_generate_ip, + .verify_fn = sd_dif_type1_verify_ip, + .get_tag_fn = sd_dif_type1_get_tag, + .set_tag_fn = sd_dif_type1_set_tag, + .tuple_size = sizeof(struct sd_dif_tuple), + .tag_size = 0, +}; + + +/* + * Type 3 protection has a 16-bit guard tag and 16 + 32 bits of opaque + * tag space. + */ +static void sd_dif_type3_generate(struct blk_integrity_exchg *bix, csum_fn *fn) +{ + void *buf = bix->data_buf; + struct sd_dif_tuple *sdt = bix->prot_buf; + unsigned int i; + + for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) { + sdt->guard_tag = fn(buf, bix->sector_size); + sdt->ref_tag = 0; + sdt->app_tag = 0; + + buf += bix->sector_size; + } +} + +static void sd_dif_type3_generate_crc(struct blk_integrity_exchg *bix) +{ + sd_dif_type3_generate(bix, sd_dif_crc_fn); +} + +static void sd_dif_type3_generate_ip(struct blk_integrity_exchg *bix) +{ + sd_dif_type3_generate(bix, sd_dif_ip_fn); +} + +static int sd_dif_type3_verify(struct blk_integrity_exchg *bix, csum_fn *fn) +{ + void *buf = bix->data_buf; + struct sd_dif_tuple *sdt = bix->prot_buf; + sector_t sector = bix->sector; + unsigned int i; + __u16 csum; + + for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) { + /* Unwritten sectors */ + if (sdt->app_tag == 0xffff && sdt->ref_tag == 0xffffffff) + return 0; + + csum = fn(buf, bix->sector_size); + + if (sdt->guard_tag != csum) { + printk(KERN_ERR "%s: guard tag error on sector %lu " \ + "(rcvd %04x, data %04x)\n", bix->disk_name, + (unsigned long)sector, + be16_to_cpu(sdt->guard_tag), be16_to_cpu(csum)); + return -EIO; + } + + buf += bix->sector_size; + sector++; + } + + return 0; +} + +static int sd_dif_type3_verify_crc(struct blk_integrity_exchg *bix) +{ + return sd_dif_type3_verify(bix, sd_dif_crc_fn); +} + +static int sd_dif_type3_verify_ip(struct blk_integrity_exchg *bix) +{ + return sd_dif_type3_verify(bix, sd_dif_ip_fn); +} + +static void sd_dif_type3_set_tag(void *prot, void *tag_buf, unsigned int sectors) +{ + struct sd_dif_tuple *sdt = prot; + char *tag = tag_buf; + unsigned int i, j; + + for (i = 0, j = 0 ; i < sectors ; i++, j += 6, sdt++) { + sdt->app_tag = tag[j] << 8 | tag[j+1]; + sdt->ref_tag = tag[j+2] << 24 | tag[j+3] << 16 | + tag[j+4] << 8 | tag[j+5]; + } +} + +static void sd_dif_type3_get_tag(void *prot, void *tag_buf, unsigned int sectors) +{ + struct sd_dif_tuple *sdt = prot; + char *tag = tag_buf; + unsigned int i, j; + + for (i = 0, j = 0 ; i < sectors ; i++, j += 2, sdt++) { + tag[j] = (sdt->app_tag & 0xff00) >> 8; + tag[j+1] = sdt->app_tag & 0xff; + tag[j+2] = (sdt->ref_tag & 0xff000000) >> 24; + tag[j+3] = (sdt->ref_tag & 0xff0000) >> 16; + tag[j+4] = (sdt->ref_tag & 0xff00) >> 8; + tag[j+5] = sdt->ref_tag & 0xff; + BUG_ON(sdt->app_tag == 0xffff || sdt->ref_tag == 0xffffffff); + } +} + +static struct blk_integrity dif_type3_integrity_crc = { + .name = "T10-DIF-TYPE3-CRC", + .generate_fn = sd_dif_type3_generate_crc, + .verify_fn = sd_dif_type3_verify_crc, + .get_tag_fn = sd_dif_type3_get_tag, + .set_tag_fn = sd_dif_type3_set_tag, + .tuple_size = sizeof(struct sd_dif_tuple), + .tag_size = 0, +}; + +static struct blk_integrity dif_type3_integrity_ip = { + .name = "T10-DIF-TYPE3-IP", + .generate_fn = sd_dif_type3_generate_ip, + .verify_fn = sd_dif_type3_verify_ip, + .get_tag_fn = sd_dif_type3_get_tag, + .set_tag_fn = sd_dif_type3_set_tag, + .tuple_size = sizeof(struct sd_dif_tuple), + .tag_size = 0, +}; + +/* + * Configure exchange of protection information between OS and HBA. + */ +void sd_dif_config_host(struct scsi_disk *sdkp) +{ + struct scsi_device *sdp = sdkp->device; + struct gendisk *disk = sdkp->disk; + u8 type = sdkp->protection_type; + + /* If this HBA doesn't support DIX, resort to normal I/O or DIF */ + if (scsi_host_dix_capable(sdp->host, type) == 0) { + + if (type == SD_DIF_TYPE0_PROTECTION) + return; + + if (scsi_host_dif_capable(sdp->host, type) == 0) { + sd_printk(KERN_INFO, sdkp, "Type %d protection " \ + "unsupported by HBA. Disabling DIF.\n", type); + sdkp->protection_type = 0; + return; + } + + sd_printk(KERN_INFO, sdkp, "Enabling DIF Type %d protection\n", + type); + + return; + } + + /* Enable DMA of protection information */ + if (scsi_host_get_guard(sdkp->device->host) & SHOST_DIX_GUARD_IP) + if (type == SD_DIF_TYPE3_PROTECTION) + blk_integrity_register(disk, &dif_type3_integrity_ip); + else + blk_integrity_register(disk, &dif_type1_integrity_ip); + else + if (type == SD_DIF_TYPE3_PROTECTION) + blk_integrity_register(disk, &dif_type3_integrity_crc); + else + blk_integrity_register(disk, &dif_type1_integrity_crc); + + sd_printk(KERN_INFO, sdkp, + "Enabling %s integrity protection\n", disk->integrity->name); + + /* Signal to block layer that we support sector tagging */ + if (type && sdkp->ATO) { + if (type == SD_DIF_TYPE3_PROTECTION) + disk->integrity->tag_size = sizeof(u16) + sizeof(u32); + else + disk->integrity->tag_size = sizeof(u16); + + sd_printk(KERN_INFO, sdkp, "DIF application tag size %u\n", + disk->integrity->tag_size); + } +} + +/* + * DIF DMA operation magic decoder ring. + */ +void sd_dif_op(struct scsi_cmnd *scmd, unsigned int dif, unsigned int dix) +{ + int csum_convert, prot_op; + + prot_op = 0; + + /* Convert checksum? */ + if (scsi_host_get_guard(scmd->device->host) != SHOST_DIX_GUARD_CRC) + csum_convert = 1; + else + csum_convert = 0; + + switch (scmd->cmnd[0]) { + case READ_10: + case READ_12: + case READ_16: + if (dif && dix) + if (csum_convert) + prot_op = SCSI_PROT_READ_CONVERT; + else + prot_op = SCSI_PROT_READ_PASS; + else if (dif && !dix) + prot_op = SCSI_PROT_READ_STRIP; + else if (!dif && dix) + prot_op = SCSI_PROT_READ_INSERT; + + break; + + case WRITE_10: + case WRITE_12: + case WRITE_16: + if (dif && dix) + if (csum_convert) + prot_op = SCSI_PROT_WRITE_CONVERT; + else + prot_op = SCSI_PROT_WRITE_PASS; + else if (dif && !dix) + prot_op = SCSI_PROT_WRITE_INSERT; + else if (!dif && dix) + prot_op = SCSI_PROT_WRITE_STRIP; + + break; + } + + scsi_set_prot_op(scmd, prot_op); + scsi_set_prot_type(scmd, dif); +} + +/* + * The virtual start sector is the one that was originally submitted + * by the block layer. Due to partitioning, MD/DM cloning, etc. the + * actual physical start sector is likely to be different. Remap + * protection information to match the physical LBA. + * + * From a protocol perspective there's a slight difference between + * Type 1 and 2. The latter uses 32-byte CDBs exclusively, and the + * reference tag is seeded in the CDB. This gives us the potential to + * avoid virt->phys remapping during write. However, at read time we + * don't know whether the virt sector is the same as when we wrote it + * (we could be reading from real disk as opposed to MD/DM device. So + * we always remap Type 2 making it identical to Type 1. + * + * Type 3 does not have a reference tag so no remapping is required. + */ +int sd_dif_prepare(struct request *rq, sector_t hw_sector, unsigned int sector_sz) +{ + const int tuple_sz = sizeof(struct sd_dif_tuple); + struct bio *bio; + struct scsi_disk *sdkp; + struct sd_dif_tuple *sdt; + unsigned int i, j; + u32 phys, virt; + + /* Already remapped? */ + if (rq->cmd_flags & REQ_INTEGRITY) + return 0; + + sdkp = rq->bio->bi_bdev->bd_disk->private_data; + + if (sdkp->protection_type == SD_DIF_TYPE3_PROTECTION) + return 0; + + rq->cmd_flags |= REQ_INTEGRITY; + phys = hw_sector & 0xffffffff; + + __rq_for_each_bio(bio, rq) { + struct bio_vec *iv; + + virt = bio->bi_integrity->bip_sector & 0xffffffff; + + bip_for_each_vec(iv, bio->bi_integrity, i) { + sdt = kmap_atomic(iv->bv_page, KM_USER0) + + iv->bv_offset; + + for (j = 0 ; j < iv->bv_len ; j += tuple_sz, sdt++) { + + if (be32_to_cpu(sdt->ref_tag) != virt) + goto error; + + sdt->ref_tag = cpu_to_be32(phys); + virt++; + phys++; + } + + kunmap_atomic(sdt, KM_USER0); + } + } + + return 0; + +error: + kunmap_atomic(sdt, KM_USER0); + sd_printk(KERN_ERR, sdkp, "%s: virt %u, phys %u, ref %u\n", + __func__, virt, phys, be32_to_cpu(sdt->ref_tag)); + + return -EIO; +} + +/* + * Remap physical sector values in the reference tag to the virtual + * values expected by the block layer. + */ +void sd_dif_complete(struct scsi_cmnd *scmd, unsigned int good_bytes) +{ + const int tuple_sz = sizeof(struct sd_dif_tuple); + struct scsi_disk *sdkp; + struct bio *bio; + struct sd_dif_tuple *sdt; + unsigned int i, j, sectors, sector_sz; + u32 phys, virt; + + sdkp = scsi_disk(scmd->request->rq_disk); + + if (sdkp->protection_type == SD_DIF_TYPE3_PROTECTION || good_bytes == 0) + return; + + sector_sz = scmd->device->sector_size; + sectors = good_bytes / sector_sz; + + phys = scmd->request->sector & 0xffffffff; + if (sector_sz == 4096) + phys >>= 3; + + __rq_for_each_bio(bio, scmd->request) { + struct bio_vec *iv; + + virt = bio->bi_integrity->bip_sector & 0xffffffff; + + bip_for_each_vec(iv, bio->bi_integrity, i) { + sdt = kmap_atomic(iv->bv_page, KM_USER0) + + iv->bv_offset; + + for (j = 0 ; j < iv->bv_len ; j += tuple_sz, sdt++) { + + if (sectors == 0) { + kunmap_atomic(sdt, KM_USER0); + return; + } + + if (be32_to_cpu(sdt->ref_tag) != phys && + sdt->app_tag != 0xffff) + sdt->ref_tag = 0xffffffff; /* Bad ref */ + else + sdt->ref_tag = cpu_to_be32(virt); + + virt++; + phys++; + sectors--; + } + + kunmap_atomic(sdt, KM_USER0); + } + } +} + -- cgit v1.2.3 From 2b142900784c6e38c8d39fa57d5f95ef08e735d8 Mon Sep 17 00:00:00 2001 From: Alan Jenkins Date: Sun, 27 Jul 2008 09:38:42 +0100 Subject: [SCSI] extend the last_sector_bug flag to cover more sectors The last_sector_bug flag was added to work around a bug in certain usb cardreaders, where they would crash if a multiple sector read included the last sector. The original implementation avoids this by e.g. splitting an 8 sector read which includes the last sector into a 7 sector read, and a single sector read for the last sector. The flag is enabled for all USB devices. This revealed a second bug in other usb cardreaders, which crash when they get a multiple sector read which stops 1 sector short of the last sector. Affected hardware includes the Kingston "MobileLite" external USB cardreader and the internal USB cardreader on the Asus EeePC. Extend the last_sector_bug workaround to ensure that any access which touches the last 8 hardware sectors of the device is a single sector long. Requests are shrunk as necessary to meet this constraint. This gives us a safety margin against potential unknown or future bugs affecting multi-sector access to the end of the device. The two known bugs only affect the last 2 sectors. However, they suggest that these devices are prone to fencepost errors and that multi-sector access to the end of the device is not well tested. Popular OS's use multi-sector accesses, but they rarely read the last few sectors. Linux (with udev & vol_id) automatically reads sectors from the end of the device on insertion. It is assumed that single sector accesses are more thoroughly tested during development. Signed-off-by: Alan Jenkins Tested-by: Alan Jenkins Signed-off-by: James Bottomley --- drivers/scsi/sd.c | 21 +++++++++++++++------ drivers/scsi/sd.h | 6 ++++++ include/scsi/scsi_device.h | 3 ++- 3 files changed, 23 insertions(+), 7 deletions(-) (limited to 'drivers/scsi/sd.c') diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 8e08d51a0f05..e5e7d7856454 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -375,6 +375,7 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) struct gendisk *disk = rq->rq_disk; struct scsi_disk *sdkp; sector_t block = rq->sector; + sector_t threshold; unsigned int this_count = rq->nr_sectors; unsigned int timeout = sdp->timeout; int ret; @@ -422,13 +423,21 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) } /* - * Some devices (some sdcards for one) don't like it if the - * last sector gets read in a larger then 1 sector read. + * Some SD card readers can't handle multi-sector accesses which touch + * the last one or two hardware sectors. Split accesses as needed. */ - if (unlikely(sdp->last_sector_bug && - rq->nr_sectors > sdp->sector_size / 512 && - block + this_count == get_capacity(disk))) - this_count -= sdp->sector_size / 512; + threshold = get_capacity(disk) - SD_LAST_BUGGY_SECTORS * + (sdp->sector_size / 512); + + if (unlikely(sdp->last_sector_bug && block + this_count > threshold)) { + if (block < threshold) { + /* Access up to the threshold but not beyond */ + this_count = threshold - block; + } else { + /* Access only a single hardware sector */ + this_count = sdp->sector_size / 512; + } + } SCSI_LOG_HLQUEUE(2, scmd_printk(KERN_INFO, SCpnt, "block=%llu\n", (unsigned long long)block)); diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 550b2f70a1f8..95b9f06534d5 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -31,6 +31,12 @@ */ #define SD_BUF_SIZE 512 +/* + * Number of sectors at the end of the device to avoid multi-sector + * accesses to in the case of last_sector_bug + */ +#define SD_LAST_BUGGY_SECTORS 8 + struct scsi_disk { struct scsi_driver *driver; /* always &sd_template */ struct scsi_device *device; diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 9cecc409f0f8..291d56a19167 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -140,7 +140,8 @@ struct scsi_device { unsigned fix_capacity:1; /* READ_CAPACITY is too high by 1 */ unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */ unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */ - unsigned last_sector_bug:1; /* Always read last sector in a 1 sector read */ + unsigned last_sector_bug:1; /* do not use multisector accesses on + SD_LAST_BUGGY_SECTORS */ DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ struct list_head event_list; /* asserted events */ -- cgit v1.2.3 From fca082c9f1e11ec07efa8d2f9f13688521253f36 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 4 Aug 2008 16:36:20 -0700 Subject: Revert "[SCSI] extend the last_sector_bug flag to cover more sectors" This reverts commit 2b142900784c6e38c8d39fa57d5f95ef08e735d8, since it seems to break some other USB storage devices (at least a JMicron USB to ATA bridge). As such, while it apparently fixes some cardreaders, it would need to be made conditional on the exact reader it fixes in order to avoid causing regressions. Cc: Alan Jenkins Cc: James Bottomley Signed-off-by: Linus Torvalds --- drivers/scsi/sd.c | 21 ++++++--------------- drivers/scsi/sd.h | 6 ------ include/scsi/scsi_device.h | 3 +-- 3 files changed, 7 insertions(+), 23 deletions(-) (limited to 'drivers/scsi/sd.c') diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index e5e7d7856454..8e08d51a0f05 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -375,7 +375,6 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) struct gendisk *disk = rq->rq_disk; struct scsi_disk *sdkp; sector_t block = rq->sector; - sector_t threshold; unsigned int this_count = rq->nr_sectors; unsigned int timeout = sdp->timeout; int ret; @@ -423,21 +422,13 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) } /* - * Some SD card readers can't handle multi-sector accesses which touch - * the last one or two hardware sectors. Split accesses as needed. + * Some devices (some sdcards for one) don't like it if the + * last sector gets read in a larger then 1 sector read. */ - threshold = get_capacity(disk) - SD_LAST_BUGGY_SECTORS * - (sdp->sector_size / 512); - - if (unlikely(sdp->last_sector_bug && block + this_count > threshold)) { - if (block < threshold) { - /* Access up to the threshold but not beyond */ - this_count = threshold - block; - } else { - /* Access only a single hardware sector */ - this_count = sdp->sector_size / 512; - } - } + if (unlikely(sdp->last_sector_bug && + rq->nr_sectors > sdp->sector_size / 512 && + block + this_count == get_capacity(disk))) + this_count -= sdp->sector_size / 512; SCSI_LOG_HLQUEUE(2, scmd_printk(KERN_INFO, SCpnt, "block=%llu\n", (unsigned long long)block)); diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 95b9f06534d5..550b2f70a1f8 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -31,12 +31,6 @@ */ #define SD_BUF_SIZE 512 -/* - * Number of sectors at the end of the device to avoid multi-sector - * accesses to in the case of last_sector_bug - */ -#define SD_LAST_BUGGY_SECTORS 8 - struct scsi_disk { struct scsi_driver *driver; /* always &sd_template */ struct scsi_device *device; diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 291d56a19167..9cecc409f0f8 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -140,8 +140,7 @@ struct scsi_device { unsigned fix_capacity:1; /* READ_CAPACITY is too high by 1 */ unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */ unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */ - unsigned last_sector_bug:1; /* do not use multisector accesses on - SD_LAST_BUGGY_SECTORS */ + unsigned last_sector_bug:1; /* Always read last sector in a 1 sector read */ DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ struct list_head event_list; /* asserted events */ -- cgit v1.2.3 From 18351070b86d155713cf790b26af4f21b1fd0b29 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 5 Aug 2008 21:42:21 -0700 Subject: Re-introduce "[SCSI] extend the last_sector_bug flag to cover more sectors" This re-introduces commit 2b142900784c6e38c8d39fa57d5f95ef08e735d8, which was reverted due to the regression it caused by commit fca082c9f1e11ec07efa8d2f9f13688521253f36. That regression was not root-caused by the original commit, it was just uncovered by it, and the real fix was done by Alan Stern in commit 580da34847488b404218d1d7f53b156f245f5555 ("Fix USB storage hang on command abort"). We can thus re-introduce the change that was confirmed by Alan Jenkins to be still required by his odd card reader. Cc: Alan Jenkins Cc: Alan Stern Cc: James Bottomley Signed-off-by: Linus Torvalds --- drivers/scsi/sd.c | 21 +++++++++++++++------ drivers/scsi/sd.h | 6 ++++++ include/scsi/scsi_device.h | 3 ++- 3 files changed, 23 insertions(+), 7 deletions(-) (limited to 'drivers/scsi/sd.c') diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 8e08d51a0f05..e5e7d7856454 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -375,6 +375,7 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) struct gendisk *disk = rq->rq_disk; struct scsi_disk *sdkp; sector_t block = rq->sector; + sector_t threshold; unsigned int this_count = rq->nr_sectors; unsigned int timeout = sdp->timeout; int ret; @@ -422,13 +423,21 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) } /* - * Some devices (some sdcards for one) don't like it if the - * last sector gets read in a larger then 1 sector read. + * Some SD card readers can't handle multi-sector accesses which touch + * the last one or two hardware sectors. Split accesses as needed. */ - if (unlikely(sdp->last_sector_bug && - rq->nr_sectors > sdp->sector_size / 512 && - block + this_count == get_capacity(disk))) - this_count -= sdp->sector_size / 512; + threshold = get_capacity(disk) - SD_LAST_BUGGY_SECTORS * + (sdp->sector_size / 512); + + if (unlikely(sdp->last_sector_bug && block + this_count > threshold)) { + if (block < threshold) { + /* Access up to the threshold but not beyond */ + this_count = threshold - block; + } else { + /* Access only a single hardware sector */ + this_count = sdp->sector_size / 512; + } + } SCSI_LOG_HLQUEUE(2, scmd_printk(KERN_INFO, SCpnt, "block=%llu\n", (unsigned long long)block)); diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 550b2f70a1f8..95b9f06534d5 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -31,6 +31,12 @@ */ #define SD_BUF_SIZE 512 +/* + * Number of sectors at the end of the device to avoid multi-sector + * accesses to in the case of last_sector_bug + */ +#define SD_LAST_BUGGY_SECTORS 8 + struct scsi_disk { struct scsi_driver *driver; /* always &sd_template */ struct scsi_device *device; diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 9cecc409f0f8..291d56a19167 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -140,7 +140,8 @@ struct scsi_device { unsigned fix_capacity:1; /* READ_CAPACITY is too high by 1 */ unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */ unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */ - unsigned last_sector_bug:1; /* Always read last sector in a 1 sector read */ + unsigned last_sector_bug:1; /* do not use multisector accesses on + SD_LAST_BUGGY_SECTORS */ DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ struct list_head event_list; /* asserted events */ -- cgit v1.2.3