USB: storage: add last-sector hacks

This patch (as1189b) adds some hacks to usb-storage for dealing with
the growing problems involving bad capacity values and last-sector
accesses:

	A new flag, US_FL_CAPACITY_OK, is created to indicate that
	the device is known to report its capacity correctly.  An
	unusual_devs entry for Linux's own File-backed Storage Gadget
	is added with this flag set, since g_file_storage always
	reports the correct capacity and since the capacity need
	not be even (it is determined by the size of the backing
	file).

	An entry in unusual_devs.h which has only the CAPACITY_OK
	flag set shouldn't prejudice libusual, since the device will
	work perfectly well with either usb-storage or ub.  So a
	new macro, COMPLIANT_DEV, is added to let libusual know
	about these entries.

	When a last-sector access succeeds and the total number of
	sectors is odd (the unexpected case, in which guessing that
	the number is even might cause trouble), a WARN is triggered.
	The kerneloops.org project will collect these warnings,
	allowing us to add CAPACITY_OK flags for the devices in
	question before implementing the default-to-even heuristic.
	If users want to prevent the stack dump produced by the WARN,
	they can disable the hack by adding an unusual_devs entry
	for their device with the CAPACITY_OK flag.

	When a last-sector access fails three times in a row and
	neither the FIX_CAPACITY nor the CAPACITY_OK flag is set,
	we assume the last-sector bug is present.  We replace the
	existing status and sense data with values that will cause
	the SCSI core to fail the access immediately rather than
	retry indefinitely.  This should fix the difficulties
	people have been having with Nokia phones.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: stable <stable@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Alan Stern 2008-12-15 12:43:41 -05:00 committed by Greg Kroah-Hartman
parent 9ebd961664
commit 25ff1c316f
7 changed files with 154 additions and 3 deletions

View File

@ -46,6 +46,12 @@ static int usu_probe_thread(void *arg);
{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin,bcdDeviceMax), \ { USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin,bcdDeviceMax), \
.driver_info = (flags)|(USB_US_TYPE_STOR<<24) } .driver_info = (flags)|(USB_US_TYPE_STOR<<24) }
#define COMPLIANT_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \
vendorName, productName, useProtocol, useTransport, \
initFunction, flags) \
{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \
.driver_info = (flags) }
#define USUAL_DEV(useProto, useTrans, useType) \ #define USUAL_DEV(useProto, useTrans, useType) \
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans), \ { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans), \
.driver_info = ((useType)<<24) } .driver_info = ((useType)<<24) }
@ -57,6 +63,7 @@ struct usb_device_id storage_usb_ids [] = {
#undef USUAL_DEV #undef USUAL_DEV
#undef UNUSUAL_DEV #undef UNUSUAL_DEV
#undef COMPLIANT_DEV
MODULE_DEVICE_TABLE(usb, storage_usb_ids); MODULE_DEVICE_TABLE(usb, storage_usb_ids);
EXPORT_SYMBOL_GPL(storage_usb_ids); EXPORT_SYMBOL_GPL(storage_usb_ids);

View File

@ -208,6 +208,14 @@ static int slave_configure(struct scsi_device *sdev)
* sector in a larger then 1 sector read, since the performance * sector in a larger then 1 sector read, since the performance
* impact is negible we set this flag for all USB disks */ * impact is negible we set this flag for all USB disks */
sdev->last_sector_bug = 1; sdev->last_sector_bug = 1;
/* Enable last-sector hacks for single-target devices using
* the Bulk-only transport, unless we already know the
* capacity will be decremented or is correct. */
if (!(us->fflags & (US_FL_FIX_CAPACITY | US_FL_CAPACITY_OK |
US_FL_SCM_MULT_TARG)) &&
us->protocol == US_PR_BULK)
us->use_last_sector_hacks = 1;
} else { } else {
/* Non-disk-type devices don't need to blacklist any pages /* Non-disk-type devices don't need to blacklist any pages

View File

@ -57,6 +57,9 @@
#include "scsiglue.h" #include "scsiglue.h"
#include "debug.h" #include "debug.h"
#include <linux/blkdev.h>
#include "../../scsi/sd.h"
/*********************************************************************** /***********************************************************************
* Data transfer routines * Data transfer routines
@ -511,6 +514,110 @@ int usb_stor_bulk_transfer_sg(struct us_data* us, unsigned int pipe,
* Transport routines * Transport routines
***********************************************************************/ ***********************************************************************/
/* There are so many devices that report the capacity incorrectly,
* this routine was written to counteract some of the resulting
* problems.
*/
static void last_sector_hacks(struct us_data *us, struct scsi_cmnd *srb)
{
struct gendisk *disk;
struct scsi_disk *sdkp;
u32 sector;
/* To Report "Medium Error: Record Not Found */
static unsigned char record_not_found[18] = {
[0] = 0x70, /* current error */
[2] = MEDIUM_ERROR, /* = 0x03 */
[7] = 0x0a, /* additional length */
[12] = 0x14 /* Record Not Found */
};
/* If last-sector problems can't occur, whether because the
* capacity was already decremented or because the device is
* known to report the correct capacity, then we don't need
* to do anything.
*/
if (!us->use_last_sector_hacks)
return;
/* Was this command a READ(10) or a WRITE(10)? */
if (srb->cmnd[0] != READ_10 && srb->cmnd[0] != WRITE_10)
goto done;
/* Did this command access the last sector? */
sector = (srb->cmnd[2] << 24) | (srb->cmnd[3] << 16) |
(srb->cmnd[4] << 8) | (srb->cmnd[5]);
disk = srb->request->rq_disk;
if (!disk)
goto done;
sdkp = scsi_disk(disk);
if (!sdkp)
goto done;
if (sector + 1 != sdkp->capacity)
goto done;
if (srb->result == SAM_STAT_GOOD && scsi_get_resid(srb) == 0) {
/* The command succeeded. If the capacity is odd
* (i.e., if the sector number is even) then the
* "always-even" heuristic would be wrong for this
* device. Issue a WARN() so that the kerneloops.org
* project will be notified and we will then know to
* mark the device with a CAPACITY_OK flag. Hopefully
* this will occur for only a few devices.
*
* Use the sign of us->last_sector_hacks to tell whether
* the warning has already been issued; we don't need
* more than one warning per device.
*/
if (!(sector & 1) && us->use_last_sector_hacks > 0) {
unsigned vid = le16_to_cpu(
us->pusb_dev->descriptor.idVendor);
unsigned pid = le16_to_cpu(
us->pusb_dev->descriptor.idProduct);
unsigned rev = le16_to_cpu(
us->pusb_dev->descriptor.bcdDevice);
WARN(1, "%s: Successful last sector success at %u, "
"device %04x:%04x:%04x\n",
sdkp->disk->disk_name, sector,
vid, pid, rev);
us->use_last_sector_hacks = -1;
}
} else {
/* The command failed. Allow up to 3 retries in case this
* is some normal sort of failure. After that, assume the
* capacity is wrong and we're trying to access the sector
* beyond the end. Replace the result code and sense data
* with values that will cause the SCSI core to fail the
* command immediately, instead of going into an infinite
* (or even just a very long) retry loop.
*/
if (++us->last_sector_retries < 3)
return;
srb->result = SAM_STAT_CHECK_CONDITION;
memcpy(srb->sense_buffer, record_not_found,
sizeof(record_not_found));
/* In theory we might want to issue a WARN() here if the
* capacity is even, since it could indicate the device
* has the READ CAPACITY bug _and_ the real capacity is
* odd. But it could also indicate that the device
* simply can't access its last sector, a failure mode
* which is surprisingly common. So no warning.
*/
}
done:
/* Don't reset the retry counter for TEST UNIT READY commands,
* because they get issued after device resets which might be
* caused by a failed last-sector access.
*/
if (srb->cmnd[0] != TEST_UNIT_READY)
us->last_sector_retries = 0;
}
/* Invoke the transport and basic error-handling/recovery methods /* Invoke the transport and basic error-handling/recovery methods
* *
* This is used by the protocol layers to actually send the message to * This is used by the protocol layers to actually send the message to
@ -544,6 +651,7 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us)
/* if the transport provided its own sense data, don't auto-sense */ /* if the transport provided its own sense data, don't auto-sense */
if (result == USB_STOR_TRANSPORT_NO_SENSE) { if (result == USB_STOR_TRANSPORT_NO_SENSE) {
srb->result = SAM_STAT_CHECK_CONDITION; srb->result = SAM_STAT_CHECK_CONDITION;
last_sector_hacks(us, srb);
return; return;
} }
@ -705,6 +813,7 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us)
scsi_bufflen(srb) - scsi_get_resid(srb) < srb->underflow) scsi_bufflen(srb) - scsi_get_resid(srb) < srb->underflow)
srb->result = (DID_ERROR << 16) | (SUGGEST_RETRY << 24); srb->result = (DID_ERROR << 16) | (SUGGEST_RETRY << 24);
last_sector_hacks(us, srb);
return; return;
/* Error and abort processing: try to resynchronize with the device /* Error and abort processing: try to resynchronize with the device
@ -732,6 +841,7 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us)
us->transport_reset(us); us->transport_reset(us);
} }
clear_bit(US_FLIDX_RESETTING, &us->dflags); clear_bit(US_FLIDX_RESETTING, &us->dflags);
last_sector_hacks(us, srb);
} }
/* Stop the current URB transfer */ /* Stop the current URB transfer */

View File

@ -27,7 +27,8 @@
/* IMPORTANT NOTE: This file must be included in another file which does /* IMPORTANT NOTE: This file must be included in another file which does
* the following thing for it to work: * the following thing for it to work:
* The macro UNUSUAL_DEV() must be defined before this file is included * The UNUSUAL_DEV, COMPLIANT_DEV, and USUAL_DEV macros must be defined
* before this file is included.
*/ */
/* If you edit this file, please try to keep it sorted first by VendorID, /* If you edit this file, please try to keep it sorted first by VendorID,
@ -46,6 +47,12 @@
* <usb-storage@lists.one-eyed-alien.net> * <usb-storage@lists.one-eyed-alien.net>
*/ */
/* Note: If you add an entry only in order to set the CAPACITY_OK flag,
* use the COMPLIANT_DEV macro instead of UNUSUAL_DEV. This is
* because such entries mark devices which actually work correctly,
* as opposed to devices that do something strangely or wrongly.
*/
/* patch submitted by Vivian Bregier <Vivian.Bregier@imag.fr> /* patch submitted by Vivian Bregier <Vivian.Bregier@imag.fr>
*/ */
UNUSUAL_DEV( 0x03eb, 0x2002, 0x0100, 0x0100, UNUSUAL_DEV( 0x03eb, 0x2002, 0x0100, 0x0100,
@ -704,6 +711,13 @@ UNUSUAL_DEV( 0x0525, 0xa140, 0x0100, 0x0100,
US_SC_8070, US_PR_DEVICE, NULL, US_SC_8070, US_PR_DEVICE, NULL,
US_FL_FIX_INQUIRY ), US_FL_FIX_INQUIRY ),
/* Added by Alan Stern <stern@rowland.harvard.edu> */
COMPLIANT_DEV(0x0525, 0xa4a5, 0x0000, 0x9999,
"Linux",
"File-backed Storage Gadget",
US_SC_DEVICE, US_PR_DEVICE, NULL,
US_FL_CAPACITY_OK ),
/* Yakumo Mega Image 37 /* Yakumo Mega Image 37
* Submitted by Stephan Fuhrmann <atomenergie@t-online.de> */ * Submitted by Stephan Fuhrmann <atomenergie@t-online.de> */
UNUSUAL_DEV( 0x052b, 0x1801, 0x0100, 0x0100, UNUSUAL_DEV( 0x052b, 0x1801, 0x0100, 0x0100,

View File

@ -134,6 +134,8 @@ static struct quirks_entry *quirks_list, *quirks_end;
{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin,bcdDeviceMax), \ { USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin,bcdDeviceMax), \
.driver_info = (flags)|(USB_US_TYPE_STOR<<24) } .driver_info = (flags)|(USB_US_TYPE_STOR<<24) }
#define COMPLIANT_DEV UNUSUAL_DEV
#define USUAL_DEV(useProto, useTrans, useType) \ #define USUAL_DEV(useProto, useTrans, useType) \
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans), \ { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans), \
.driver_info = (USB_US_TYPE_STOR<<24) } .driver_info = (USB_US_TYPE_STOR<<24) }
@ -142,6 +144,7 @@ static struct usb_device_id storage_usb_ids [] = {
# include "unusual_devs.h" # include "unusual_devs.h"
#undef UNUSUAL_DEV #undef UNUSUAL_DEV
#undef COMPLIANT_DEV
#undef USUAL_DEV #undef USUAL_DEV
/* Terminating entry */ /* Terminating entry */
{ } { }
@ -172,6 +175,8 @@ MODULE_DEVICE_TABLE (usb, storage_usb_ids);
.initFunction = init_function, \ .initFunction = init_function, \
} }
#define COMPLIANT_DEV UNUSUAL_DEV
#define USUAL_DEV(use_protocol, use_transport, use_type) \ #define USUAL_DEV(use_protocol, use_transport, use_type) \
{ \ { \
.useProtocol = use_protocol, \ .useProtocol = use_protocol, \
@ -181,6 +186,7 @@ MODULE_DEVICE_TABLE (usb, storage_usb_ids);
static struct us_unusual_dev us_unusual_dev_list[] = { static struct us_unusual_dev us_unusual_dev_list[] = {
# include "unusual_devs.h" # include "unusual_devs.h"
# undef UNUSUAL_DEV # undef UNUSUAL_DEV
# undef COMPLIANT_DEV
# undef USUAL_DEV # undef USUAL_DEV
/* Terminating entry */ /* Terminating entry */

View File

@ -154,6 +154,10 @@ struct us_data {
#ifdef CONFIG_PM #ifdef CONFIG_PM
pm_hook suspend_resume_hook; pm_hook suspend_resume_hook;
#endif #endif
/* hacks for READ CAPACITY bug handling */
int use_last_sector_hacks;
int last_sector_retries;
}; };
/* Convert between us_data and the corresponding Scsi_Host */ /* Convert between us_data and the corresponding Scsi_Host */

View File

@ -53,8 +53,10 @@
/* Sets max_sectors to arch min */ \ /* Sets max_sectors to arch min */ \
US_FLAG(BULK_IGNORE_TAG,0x00004000) \ US_FLAG(BULK_IGNORE_TAG,0x00004000) \
/* Ignore tag mismatch in bulk operations */ \ /* Ignore tag mismatch in bulk operations */ \
US_FLAG(SANE_SENSE, 0x00008000) US_FLAG(SANE_SENSE, 0x00008000) \
/* Sane Sense (> 18 bytes) */ /* Sane Sense (> 18 bytes) */ \
US_FLAG(CAPACITY_OK, 0x00010000) \
/* READ CAPACITY response is correct */
#define US_FLAG(name, value) US_FL_##name = value , #define US_FLAG(name, value) US_FL_##name = value ,
enum { US_DO_ALL_FLAGS }; enum { US_DO_ALL_FLAGS };