diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/usb/storage/scsiglue.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/usb/storage/scsiglue.c')
-rw-r--r-- | drivers/usb/storage/scsiglue.c | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c new file mode 100644 index 00000000000..22e48a2b0bd --- /dev/null +++ b/drivers/usb/storage/scsiglue.c @@ -0,0 +1,500 @@ +/* Driver for USB Mass Storage compliant devices + * SCSI layer glue code + * + * $Id: scsiglue.c,v 1.26 2002/04/22 03:39:43 mdharm Exp $ + * + * Current development and maintenance by: + * (c) 1999-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * + * Developed with the assistance of: + * (c) 2000 David L. Brown, Jr. (usb-storage@davidb.org) + * (c) 2000 Stephen J. Gowdy (SGowdy@lbl.gov) + * + * Initial work by: + * (c) 1999 Michael Gee (michael@linuxspecific.com) + * + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the protocol used to communicate with such + * devices. Clearly, the designers had SCSI and ATAPI commands in + * mind when they created this document. The commands are all very + * similar to commands in the SCSI-II and ATAPI specifications. + * + * It is important to note that in a number of cases this class + * exhibits class-specific exemptions from the USB specification. + * Notably the usage of NAK, STALL and ACK differs from the norm, in + * that they are used to communicate wait, failed and OK on commands. + * + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Please see http://www.one-eyed-alien.net/~mdharm/linux-usb for more + * information about this driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/slab.h> +#include <linux/module.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_devinfo.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_eh.h> + +#include "usb.h" +#include "scsiglue.h" +#include "debug.h" +#include "transport.h" +#include "protocol.h" + +/*********************************************************************** + * Host functions + ***********************************************************************/ + +static const char* host_info(struct Scsi_Host *host) +{ + return "SCSI emulation for USB Mass Storage devices"; +} + +static int slave_alloc (struct scsi_device *sdev) +{ + /* + * Set the INQUIRY transfer length to 36. We don't use any of + * the extra data and many devices choke if asked for more or + * less than 36 bytes. + */ + sdev->inquiry_len = 36; + return 0; +} + +static int slave_configure(struct scsi_device *sdev) +{ + struct us_data *us = host_to_us(sdev->host); + + /* Scatter-gather buffers (all but the last) must have a length + * divisible by the bulk maxpacket size. Otherwise a data packet + * would end up being short, causing a premature end to the data + * transfer. Since high-speed bulk pipes have a maxpacket size + * of 512, we'll use that as the scsi device queue's DMA alignment + * mask. Guaranteeing proper alignment of the first buffer will + * have the desired effect because, except at the beginning and + * the end, scatter-gather buffers follow page boundaries. */ + blk_queue_dma_alignment(sdev->request_queue, (512 - 1)); + + /* Set the SCSI level to at least 2. We'll leave it at 3 if that's + * what is originally reported. We need this to avoid confusing + * the SCSI layer with devices that report 0 or 1, but need 10-byte + * commands (ala ATAPI devices behind certain bridges, or devices + * which simply have broken INQUIRY data). + * + * NOTE: This means /dev/sg programs (ala cdrecord) will get the + * actual information. This seems to be the preference for + * programs like that. + * + * NOTE: This also means that /proc/scsi/scsi and sysfs may report + * the actual value or the modified one, depending on where the + * data comes from. + */ + if (sdev->scsi_level < SCSI_2) + sdev->scsi_level = SCSI_2; + + /* According to the technical support people at Genesys Logic, + * devices using their chips have problems transferring more than + * 32 KB at a time. In practice people have found that 64 KB + * works okay and that's what Windows does. But we'll be + * conservative; people can always use the sysfs interface to + * increase max_sectors. */ + if (le16_to_cpu(us->pusb_dev->descriptor.idVendor) == USB_VENDOR_ID_GENESYS && + sdev->request_queue->max_sectors > 64) + blk_queue_max_sectors(sdev->request_queue, 64); + + /* We can't put these settings in slave_alloc() because that gets + * called before the device type is known. Consequently these + * settings can't be overridden via the scsi devinfo mechanism. */ + if (sdev->type == TYPE_DISK) { + + /* Disk-type devices use MODE SENSE(6) if the protocol + * (SubClass) is Transparent SCSI, otherwise they use + * MODE SENSE(10). */ + if (us->subclass != US_SC_SCSI) + sdev->use_10_for_ms = 1; + + /* Many disks only accept MODE SENSE transfer lengths of + * 192 bytes (that's what Windows uses). */ + sdev->use_192_bytes_for_3f = 1; + + /* Some devices don't like MODE SENSE with page=0x3f, + * which is the command used for checking if a device + * is write-protected. Now that we tell the sd driver + * to do a 192-byte transfer with this command the + * majority of devices work fine, but a few still can't + * handle it. The sd driver will simply assume those + * devices are write-enabled. */ + if (us->flags & US_FL_NO_WP_DETECT) + sdev->skip_ms_page_3f = 1; + + /* A number of devices have problems with MODE SENSE for + * page x08, so we will skip it. */ + sdev->skip_ms_page_8 = 1; + + /* Some disks return the total number of blocks in response + * to READ CAPACITY rather than the highest block number. + * If this device makes that mistake, tell the sd driver. */ + if (us->flags & US_FL_FIX_CAPACITY) + sdev->fix_capacity = 1; + } else { + + /* Non-disk-type devices don't need to blacklist any pages + * or to force 192-byte transfer lengths for MODE SENSE. + * But they do need to use MODE SENSE(10). */ + sdev->use_10_for_ms = 1; + } + + /* Some devices choke when they receive a PREVENT-ALLOW MEDIUM + * REMOVAL command, so suppress those commands. */ + if (us->flags & US_FL_NOT_LOCKABLE) + sdev->lockable = 0; + + /* this is to satisfy the compiler, tho I don't think the + * return code is ever checked anywhere. */ + return 0; +} + +/* queue a command */ +/* This is always called with scsi_lock(host) held */ +static int queuecommand(struct scsi_cmnd *srb, + void (*done)(struct scsi_cmnd *)) +{ + struct us_data *us = host_to_us(srb->device->host); + + US_DEBUGP("%s called\n", __FUNCTION__); + + /* check for state-transition errors */ + if (us->srb != NULL) { + printk(KERN_ERR USB_STORAGE "Error in %s: us->srb = %p\n", + __FUNCTION__, us->srb); + return SCSI_MLQUEUE_HOST_BUSY; + } + + /* fail the command if we are disconnecting */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { + US_DEBUGP("Fail command during disconnect\n"); + srb->result = DID_NO_CONNECT << 16; + done(srb); + return 0; + } + + /* enqueue the command and wake up the control thread */ + srb->scsi_done = done; + us->srb = srb; + up(&(us->sema)); + + return 0; +} + +/*********************************************************************** + * Error handling functions + ***********************************************************************/ + +/* Command timeout and abort */ +/* This is always called with scsi_lock(host) held */ +static int command_abort(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + + US_DEBUGP("%s called\n", __FUNCTION__); + + /* Is this command still active? */ + if (us->srb != srb) { + US_DEBUGP ("-- nothing to abort\n"); + return FAILED; + } + + /* Set the TIMED_OUT bit. Also set the ABORTING bit, but only if + * a device reset isn't already in progress (to avoid interfering + * with the reset). To prevent races with auto-reset, we must + * stop any ongoing USB transfers while still holding the host + * lock. */ + set_bit(US_FLIDX_TIMED_OUT, &us->flags); + if (!test_bit(US_FLIDX_RESETTING, &us->flags)) { + set_bit(US_FLIDX_ABORTING, &us->flags); + usb_stor_stop_transport(us); + } + scsi_unlock(us_to_host(us)); + + /* Wait for the aborted command to finish */ + wait_for_completion(&us->notify); + + /* Reacquire the lock and allow USB transfers to resume */ + scsi_lock(us_to_host(us)); + clear_bit(US_FLIDX_ABORTING, &us->flags); + clear_bit(US_FLIDX_TIMED_OUT, &us->flags); + return SUCCESS; +} + +/* This invokes the transport reset mechanism to reset the state of the + * device */ +/* This is always called with scsi_lock(host) held */ +static int device_reset(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + int result; + + US_DEBUGP("%s called\n", __FUNCTION__); + + scsi_unlock(us_to_host(us)); + + /* lock the device pointers and do the reset */ + down(&(us->dev_semaphore)); + if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { + result = FAILED; + US_DEBUGP("No reset during disconnect\n"); + } else + result = us->transport_reset(us); + up(&(us->dev_semaphore)); + + /* lock the host for the return */ + scsi_lock(us_to_host(us)); + return result; +} + +/* This resets the device's USB port. */ +/* It refuses to work if there's more than one interface in + * the device, so that other users are not affected. */ +/* This is always called with scsi_lock(host) held */ +static int bus_reset(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + int result, rc; + + US_DEBUGP("%s called\n", __FUNCTION__); + + scsi_unlock(us_to_host(us)); + + /* The USB subsystem doesn't handle synchronisation between + * a device's several drivers. Therefore we reset only devices + * with just one interface, which we of course own. */ + + down(&(us->dev_semaphore)); + if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { + result = -EIO; + US_DEBUGP("No reset during disconnect\n"); + } else if (us->pusb_dev->actconfig->desc.bNumInterfaces != 1) { + result = -EBUSY; + US_DEBUGP("Refusing to reset a multi-interface device\n"); + } else { + rc = usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf); + if (rc < 0) { + US_DEBUGP("unable to lock device for reset: %d\n", rc); + result = rc; + } else { + result = usb_reset_device(us->pusb_dev); + if (rc) + usb_unlock_device(us->pusb_dev); + US_DEBUGP("usb_reset_device returns %d\n", result); + } + } + up(&(us->dev_semaphore)); + + /* lock the host for the return */ + scsi_lock(us_to_host(us)); + return result < 0 ? FAILED : SUCCESS; +} + +/* Report a driver-initiated device reset to the SCSI layer. + * Calling this for a SCSI-initiated reset is unnecessary but harmless. + * The caller must own the SCSI host lock. */ +void usb_stor_report_device_reset(struct us_data *us) +{ + int i; + struct Scsi_Host *host = us_to_host(us); + + scsi_report_device_reset(host, 0, 0); + if (us->flags & US_FL_SCM_MULT_TARG) { + for (i = 1; i < host->max_id; ++i) + scsi_report_device_reset(host, 0, i); + } +} + +/*********************************************************************** + * /proc/scsi/ functions + ***********************************************************************/ + +/* we use this macro to help us write into the buffer */ +#undef SPRINTF +#define SPRINTF(args...) \ + do { if (pos < buffer+length) pos += sprintf(pos, ## args); } while (0) + +static int proc_info (struct Scsi_Host *host, char *buffer, + char **start, off_t offset, int length, int inout) +{ + struct us_data *us = host_to_us(host); + char *pos = buffer; + const char *string; + + /* if someone is sending us data, just throw it away */ + if (inout) + return length; + + /* print the controller name */ + SPRINTF(" Host scsi%d: usb-storage\n", host->host_no); + + /* print product, vendor, and serial number strings */ + if (us->pusb_dev->manufacturer) + string = us->pusb_dev->manufacturer; + else if (us->unusual_dev->vendorName) + string = us->unusual_dev->vendorName; + else + string = "Unknown"; + SPRINTF(" Vendor: %s\n", string); + if (us->pusb_dev->product) + string = us->pusb_dev->product; + else if (us->unusual_dev->productName) + string = us->unusual_dev->productName; + else + string = "Unknown"; + SPRINTF(" Product: %s\n", string); + if (us->pusb_dev->serial) + string = us->pusb_dev->serial; + else + string = "None"; + SPRINTF("Serial Number: %s\n", string); + + /* show the protocol and transport */ + SPRINTF(" Protocol: %s\n", us->protocol_name); + SPRINTF(" Transport: %s\n", us->transport_name); + + /* show the device flags */ + if (pos < buffer + length) { + pos += sprintf(pos, " Quirks:"); + +#define US_FLAG(name, value) \ + if (us->flags & value) pos += sprintf(pos, " " #name); +US_DO_ALL_FLAGS +#undef US_FLAG + + *(pos++) = '\n'; + } + + /* + * Calculate start of next buffer, and return value. + */ + *start = buffer + offset; + + if ((pos - buffer) < offset) + return (0); + else if ((pos - buffer - offset) < length) + return (pos - buffer - offset); + else + return (length); +} + +/*********************************************************************** + * Sysfs interface + ***********************************************************************/ + +/* Output routine for the sysfs max_sectors file */ +static ssize_t show_max_sectors(struct device *dev, char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + return sprintf(buf, "%u\n", sdev->request_queue->max_sectors); +} + +/* Input routine for the sysfs max_sectors file */ +static ssize_t store_max_sectors(struct device *dev, const char *buf, + size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + unsigned short ms; + + if (sscanf(buf, "%hu", &ms) > 0 && ms <= SCSI_DEFAULT_MAX_SECTORS) { + blk_queue_max_sectors(sdev->request_queue, ms); + return strlen(buf); + } + return -EINVAL; +} + +static DEVICE_ATTR(max_sectors, S_IRUGO | S_IWUSR, show_max_sectors, + store_max_sectors); + +static struct device_attribute *sysfs_device_attr_list[] = { + &dev_attr_max_sectors, + NULL, + }; + +/* + * this defines our host template, with which we'll allocate hosts + */ + +struct scsi_host_template usb_stor_host_template = { + /* basic userland interface stuff */ + .name = "usb-storage", + .proc_name = "usb-storage", + .proc_info = proc_info, + .info = host_info, + + /* command interface -- queued only */ + .queuecommand = queuecommand, + + /* error and abort handlers */ + .eh_abort_handler = command_abort, + .eh_device_reset_handler = device_reset, + .eh_bus_reset_handler = bus_reset, + + /* queue commands only, only one command per LUN */ + .can_queue = 1, + .cmd_per_lun = 1, + + /* unknown initiator id */ + .this_id = -1, + + .slave_alloc = slave_alloc, + .slave_configure = slave_configure, + + /* lots of sg segments can be handled */ + .sg_tablesize = SG_ALL, + + /* limit the total size of a transfer to 120 KB */ + .max_sectors = 240, + + /* merge commands... this seems to help performance, but + * periodically someone should test to see which setting is more + * optimal. + */ + .use_clustering = 1, + + /* emulated HBA */ + .emulated = 1, + + /* we do our own delay after a device or bus reset */ + .skip_settle_delay = 1, + + /* sysfs device attributes */ + .sdev_attrs = sysfs_device_attr_list, + + /* module management */ + .module = THIS_MODULE +}; + +/* To Report "Illegal Request: Invalid Field in CDB */ +unsigned char usb_stor_sense_invalidCDB[18] = { + [0] = 0x70, /* current error */ + [2] = ILLEGAL_REQUEST, /* Illegal Request = 0x05 */ + [7] = 0x0a, /* additional length */ + [12] = 0x24 /* Invalid Field in CDB */ +}; + |