diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-03-07 12:53:41 -0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-03-07 12:53:41 -0800 |
commit | c9050b64940a1b47dfa623630431fec400edbf33 (patch) | |
tree | 1b0c061e2b0753066fe982c52e130767bb181551 /drivers/usb/storage | |
parent | 86e2864d7e9d429b94624a28ba3f05fc2db89051 (diff) | |
parent | 7969943789df1196faa9ba67518d83fd93e4f9f6 (diff) |
Merge tag 'for-usb-next-2014-03-06' of git://git.kernel.org/pub/scm/linux/kernel/git/sarah/xhci into usb-next
Sarah writes:
xhci: Streams and UAS cleanups, misc cleanups for 3.15
Hi Greg,
Here's 76 patches to queue to usb-next for 3.15.
The bulk of this rather large pull request is the UAS driver cleanup, the
xHCI streams fixes, and the new userspace API for usbfs to be able to use
and alloc/free bulk streams. I've hammered on these changes, and the UAS
driver seems solid. The performance numbers are pretty spiffy too:
root@xanatos:~# echo 3 > /proc/sys/vm/drop_caches; dd if=/dev/sdb of=/dev/null bs=4k count=1000M iflag=count_bytes
256000+0 records in
256000+0 records out
1048576000 bytes (1.0 GB) copied, 3.28557 s, 319 MB/s
That's about 100 MB/s faster than my fastest Bulk-only-Transport mass
storage drive.
There's a couple of miscellaneous cleanup patches and non-urgent bug fixes
in here as well:
7969943789df xhci: add the meaningful IRQ description if it is empty
bcffae7708eb xhci: Prevent runtime pm from autosuspending during initialization
e587b8b270d3 xhci: make warnings greppable
25cd2882e2fc usb/xhci: Change how we indicate a host supports Link PM.
Sarah Sharp
Diffstat (limited to 'drivers/usb/storage')
-rw-r--r-- | drivers/usb/storage/Kconfig | 2 | ||||
-rw-r--r-- | drivers/usb/storage/uas-detect.h | 96 | ||||
-rw-r--r-- | drivers/usb/storage/uas.c | 687 | ||||
-rw-r--r-- | drivers/usb/storage/unusual_devs.h | 5 | ||||
-rw-r--r-- | drivers/usb/storage/unusual_uas.h | 52 | ||||
-rw-r--r-- | drivers/usb/storage/usb.c | 26 | ||||
-rw-r--r-- | drivers/usb/storage/usb.h | 3 |
7 files changed, 614 insertions, 257 deletions
diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index 1dd0604d191..13b5bfbaf95 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -204,7 +204,7 @@ config USB_STORAGE_ENE_UB6250 config USB_UAS tristate "USB Attached SCSI" - depends on SCSI && BROKEN + depends on SCSI && USB_STORAGE help The USB Attached SCSI protocol is supported by some USB storage devices. It permits higher performance by supporting diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h new file mode 100644 index 00000000000..bb05b984d5f --- /dev/null +++ b/drivers/usb/storage/uas-detect.h @@ -0,0 +1,96 @@ +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include "usb.h" + +static int uas_is_interface(struct usb_host_interface *intf) +{ + return (intf->desc.bInterfaceClass == USB_CLASS_MASS_STORAGE && + intf->desc.bInterfaceSubClass == USB_SC_SCSI && + intf->desc.bInterfaceProtocol == USB_PR_UAS); +} + +static int uas_isnt_supported(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + dev_warn(&udev->dev, "The driver for the USB controller %s does not " + "support scatter-gather which is\n", + hcd->driver->description); + dev_warn(&udev->dev, "required by the UAS driver. Please try an" + "alternative USB controller if you wish to use UAS.\n"); + return -ENODEV; +} + +static int uas_find_uas_alt_setting(struct usb_interface *intf) +{ + int i; + struct usb_device *udev = interface_to_usbdev(intf); + int sg_supported = udev->bus->sg_tablesize != 0; + + for (i = 0; i < intf->num_altsetting; i++) { + struct usb_host_interface *alt = &intf->altsetting[i]; + + if (uas_is_interface(alt)) { + if (!sg_supported) + return uas_isnt_supported(udev); + return alt->desc.bAlternateSetting; + } + } + + return -ENODEV; +} + +static int uas_find_endpoints(struct usb_host_interface *alt, + struct usb_host_endpoint *eps[]) +{ + struct usb_host_endpoint *endpoint = alt->endpoint; + unsigned i, n_endpoints = alt->desc.bNumEndpoints; + + for (i = 0; i < n_endpoints; i++) { + unsigned char *extra = endpoint[i].extra; + int len = endpoint[i].extralen; + while (len >= 3) { + if (extra[1] == USB_DT_PIPE_USAGE) { + unsigned pipe_id = extra[2]; + if (pipe_id > 0 && pipe_id < 5) + eps[pipe_id - 1] = &endpoint[i]; + break; + } + len -= extra[0]; + extra += extra[0]; + } + } + + if (!eps[0] || !eps[1] || !eps[2] || !eps[3]) + return -ENODEV; + + return 0; +} + +static int uas_use_uas_driver(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_host_endpoint *eps[4] = { }; + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + unsigned long flags = id->driver_info; + int r, alt; + + usb_stor_adjust_quirks(udev, &flags); + + if (flags & US_FL_IGNORE_UAS) + return 0; + + if (udev->speed >= USB_SPEED_SUPER && !hcd->can_do_streams) + return 0; + + alt = uas_find_uas_alt_setting(intf); + if (alt < 0) + return 0; + + r = uas_find_endpoints(&intf->altsetting[alt], eps); + if (r < 0) + return 0; + + return 1; +} diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index d966b59f7d7..a7ac97cc594 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -2,6 +2,7 @@ * USB Attached SCSI * Note that this is not the same as the USB Mass Storage driver * + * Copyright Hans de Goede <hdegoede@redhat.com> for Red Hat, Inc. 2013 * Copyright Matthew Wilcox for Intel Corp, 2010 * Copyright Sarah Sharp for Intel Corp, 2010 * @@ -13,17 +14,21 @@ #include <linux/types.h> #include <linux/module.h> #include <linux/usb.h> +#include <linux/usb_usual.h> #include <linux/usb/hcd.h> #include <linux/usb/storage.h> #include <linux/usb/uas.h> #include <scsi/scsi.h> +#include <scsi/scsi_eh.h> #include <scsi/scsi_dbg.h> #include <scsi/scsi_cmnd.h> #include <scsi/scsi_device.h> #include <scsi/scsi_host.h> #include <scsi/scsi_tcq.h> +#include "uas-detect.h" + /* * The r00-r01c specs define this version of the SENSE IU data structure. * It's still in use by several different firmware releases. @@ -45,12 +50,17 @@ struct uas_dev_info { struct usb_anchor sense_urbs; struct usb_anchor data_urbs; int qdepth, resetting; - struct response_ui response; + struct response_iu response; unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; unsigned use_streams:1; unsigned uas_sense_old:1; + unsigned running_task:1; + unsigned shutdown:1; struct scsi_cmnd *cmnd; spinlock_t lock; + struct work_struct work; + struct list_head inflight_list; + struct list_head dead_list; }; enum { @@ -85,103 +95,117 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo, gfp_t gfp); static void uas_do_work(struct work_struct *work); static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); +static void uas_free_streams(struct uas_dev_info *devinfo); +static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller); -static DECLARE_WORK(uas_work, uas_do_work); -static DEFINE_SPINLOCK(uas_work_lock); -static LIST_HEAD(uas_work_list); - +/* Must be called with devinfo->lock held, will temporary unlock the lock */ static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, - struct uas_cmd_info *cmdinfo) + struct uas_cmd_info *cmdinfo, + unsigned long *lock_flags) { - unsigned long flags; - /* * The UNLINK_DATA_URBS flag makes sure uas_try_complete * (called by urb completion) doesn't release cmdinfo * underneath us. */ - spin_lock_irqsave(&devinfo->lock, flags); cmdinfo->state |= UNLINK_DATA_URBS; - spin_unlock_irqrestore(&devinfo->lock, flags); + spin_unlock_irqrestore(&devinfo->lock, *lock_flags); if (cmdinfo->data_in_urb) usb_unlink_urb(cmdinfo->data_in_urb); if (cmdinfo->data_out_urb) usb_unlink_urb(cmdinfo->data_out_urb); - spin_lock_irqsave(&devinfo->lock, flags); + spin_lock_irqsave(&devinfo->lock, *lock_flags); cmdinfo->state &= ~UNLINK_DATA_URBS; - spin_unlock_irqrestore(&devinfo->lock, flags); } static void uas_do_work(struct work_struct *work) { + struct uas_dev_info *devinfo = + container_of(work, struct uas_dev_info, work); struct uas_cmd_info *cmdinfo; - struct uas_cmd_info *temp; - struct list_head list; unsigned long flags; int err; - spin_lock_irq(&uas_work_lock); - list_replace_init(&uas_work_list, &list); - spin_unlock_irq(&uas_work_lock); - - list_for_each_entry_safe(cmdinfo, temp, &list, list) { + spin_lock_irqsave(&devinfo->lock, flags); + list_for_each_entry(cmdinfo, &devinfo->inflight_list, list) { struct scsi_pointer *scp = (void *)cmdinfo; - struct scsi_cmnd *cmnd = container_of(scp, - struct scsi_cmnd, SCp); - struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; - spin_lock_irqsave(&devinfo->lock, flags); - err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, + SCp); + + if (!(cmdinfo->state & IS_IN_WORK_LIST)) + continue; + + err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_NOIO); if (!err) cmdinfo->state &= ~IS_IN_WORK_LIST; - spin_unlock_irqrestore(&devinfo->lock, flags); - if (err) { - list_del(&cmdinfo->list); - spin_lock_irq(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - spin_unlock_irq(&uas_work_lock); - schedule_work(&uas_work); - } + else + schedule_work(&devinfo->work); } + spin_unlock_irqrestore(&devinfo->lock, flags); } -static void uas_abort_work(struct uas_dev_info *devinfo) +static void uas_mark_cmd_dead(struct uas_dev_info *devinfo, + struct uas_cmd_info *cmdinfo, + int result, const char *caller) +{ + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + + uas_log_cmd_state(cmnd, caller); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); + cmdinfo->state |= COMMAND_ABORTED; + cmdinfo->state &= ~IS_IN_WORK_LIST; + cmnd->result = result << 16; + list_move_tail(&cmdinfo->list, &devinfo->dead_list); +} + +static void uas_abort_inflight(struct uas_dev_info *devinfo, int result, + const char *caller) { struct uas_cmd_info *cmdinfo; struct uas_cmd_info *temp; - struct list_head list; unsigned long flags; - spin_lock_irq(&uas_work_lock); - list_replace_init(&uas_work_list, &list); - spin_unlock_irq(&uas_work_lock); + spin_lock_irqsave(&devinfo->lock, flags); + list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, list) + uas_mark_cmd_dead(devinfo, cmdinfo, result, caller); + spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_add_work(struct uas_cmd_info *cmdinfo) +{ + struct scsi_pointer *scp = (void *)cmdinfo; + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + struct uas_dev_info *devinfo = cmnd->device->hostdata; + + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); + cmdinfo->state |= IS_IN_WORK_LIST; + schedule_work(&devinfo->work); +} + +static void uas_zap_dead(struct uas_dev_info *devinfo) +{ + struct uas_cmd_info *cmdinfo; + struct uas_cmd_info *temp; + unsigned long flags; spin_lock_irqsave(&devinfo->lock, flags); - list_for_each_entry_safe(cmdinfo, temp, &list, list) { + list_for_each_entry_safe(cmdinfo, temp, &devinfo->dead_list, list) { struct scsi_pointer *scp = (void *)cmdinfo; - struct scsi_cmnd *cmnd = container_of(scp, - struct scsi_cmnd, SCp); - struct uas_dev_info *di = (void *)cmnd->device->hostdata; - - if (di == devinfo) { - cmdinfo->state |= COMMAND_ABORTED; - cmdinfo->state &= ~IS_IN_WORK_LIST; - if (devinfo->resetting) { - /* uas_stat_cmplt() will not do that - * when a device reset is in - * progress */ - cmdinfo->state &= ~COMMAND_INFLIGHT; - } - uas_try_complete(cmnd, __func__); - } else { - /* not our uas device, relink into list */ - list_del(&cmdinfo->list); - spin_lock_irq(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - spin_unlock_irq(&uas_work_lock); - } + struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, + SCp); + uas_log_cmd_state(cmnd, __func__); + WARN_ON_ONCE(!(cmdinfo->state & COMMAND_ABORTED)); + /* all urbs are killed, clear inflight bits */ + cmdinfo->state &= ~(COMMAND_INFLIGHT | + DATA_IN_URB_INFLIGHT | + DATA_OUT_URB_INFLIGHT); + uas_try_complete(cmnd, __func__); } + devinfo->running_task = 0; spin_unlock_irqrestore(&devinfo->lock, flags); } @@ -259,20 +283,19 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; - WARN_ON(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); if (cmdinfo->state & (COMMAND_INFLIGHT | DATA_IN_URB_INFLIGHT | DATA_OUT_URB_INFLIGHT | UNLINK_DATA_URBS)) return -EBUSY; - BUG_ON(cmdinfo->state & COMMAND_COMPLETED); + WARN_ON_ONCE(cmdinfo->state & COMMAND_COMPLETED); cmdinfo->state |= COMMAND_COMPLETED; usb_free_urb(cmdinfo->data_in_urb); usb_free_urb(cmdinfo->data_out_urb); - if (cmdinfo->state & COMMAND_ABORTED) { + if (cmdinfo->state & COMMAND_ABORTED) scmd_printk(KERN_INFO, cmnd, "abort completed\n"); - cmnd->result = DID_ABORT << 16; - } + list_del(&cmdinfo->list); cmnd->scsi_done(cmnd); return 0; } @@ -286,11 +309,7 @@ static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, cmdinfo->state |= direction | SUBMIT_STATUS_URB; err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); if (err) { - spin_lock(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - cmdinfo->state |= IS_IN_WORK_LIST; - spin_unlock(&uas_work_lock); - schedule_work(&uas_work); + uas_add_work(cmdinfo); } } @@ -298,14 +317,20 @@ static void uas_stat_cmplt(struct urb *urb) { struct iu *iu = urb->transfer_buffer; struct Scsi_Host *shost = urb->context; - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; struct scsi_cmnd *cmnd; struct uas_cmd_info *cmdinfo; unsigned long flags; u16 tag; if (urb->status) { - dev_err(&urb->dev->dev, "URB BAD STATUS %d\n", urb->status); + if (urb->status == -ENOENT) { + dev_err(&urb->dev->dev, "stat urb: killed, stream %d\n", + urb->stream_id); + } else { + dev_err(&urb->dev->dev, "stat urb: status %d\n", + urb->status); + } usb_free_urb(urb); return; } @@ -324,6 +349,9 @@ static void uas_stat_cmplt(struct urb *urb) if (!cmnd) { if (iu->iu_id == IU_ID_RESPONSE) { + if (!devinfo->running_task) + dev_warn(&urb->dev->dev, + "stat urb: recv unexpected response iu\n"); /* store results for uas_eh_task_mgmt() */ memcpy(&devinfo->response, iu, sizeof(devinfo->response)); } @@ -346,17 +374,25 @@ static void uas_stat_cmplt(struct urb *urb) uas_sense(urb, cmnd); if (cmnd->result != 0) { /* cancel data transfers on error */ - spin_unlock_irqrestore(&devinfo->lock, flags); - uas_unlink_data_urbs(devinfo, cmdinfo); - spin_lock_irqsave(&devinfo->lock, flags); + uas_unlink_data_urbs(devinfo, cmdinfo, &flags); } cmdinfo->state &= ~COMMAND_INFLIGHT; uas_try_complete(cmnd, __func__); break; case IU_ID_READ_READY: + if (!cmdinfo->data_in_urb || + (cmdinfo->state & DATA_IN_URB_INFLIGHT)) { + scmd_printk(KERN_ERR, cmnd, "unexpected read rdy\n"); + break; + } uas_xfer_data(urb, cmnd, SUBMIT_DATA_IN_URB); break; case IU_ID_WRITE_READY: + if (!cmdinfo->data_out_urb || + (cmdinfo->state & DATA_OUT_URB_INFLIGHT)) { + scmd_printk(KERN_ERR, cmnd, "unexpected write rdy\n"); + break; + } uas_xfer_data(urb, cmnd, SUBMIT_DATA_OUT_URB); break; default: @@ -383,8 +419,15 @@ static void uas_data_cmplt(struct urb *urb) sdb = scsi_out(cmnd); cmdinfo->state &= ~DATA_OUT_URB_INFLIGHT; } - BUG_ON(sdb == NULL); - if (urb->status) { + if (sdb == NULL) { + WARN_ON_ONCE(1); + } else if (urb->status) { + if (urb->status != -ECONNRESET) { + uas_log_cmd_state(cmnd, __func__); + scmd_printk(KERN_ERR, cmnd, + "data cmplt err %d stream %d\n", + urb->status, urb->stream_id); + } /* error: no data transfered */ sdb->resid = sdb->length; } else { @@ -394,6 +437,17 @@ static void uas_data_cmplt(struct urb *urb) spin_unlock_irqrestore(&devinfo->lock, flags); } +static void uas_cmd_cmplt(struct urb *urb) +{ + struct scsi_cmnd *cmnd = urb->context; + + if (urb->status) { + uas_log_cmd_state(cmnd, __func__); + scmd_printk(KERN_ERR, cmnd, "cmd cmplt err %d\n", urb->status); + } + usb_free_urb(urb); +} + static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, unsigned int pipe, u16 stream_id, struct scsi_cmnd *cmnd, @@ -408,8 +462,7 @@ static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, goto out; usb_fill_bulk_urb(urb, udev, pipe, NULL, sdb->length, uas_data_cmplt, cmnd); - if (devinfo->use_streams) - urb->stream_id = stream_id; + urb->stream_id = stream_id; urb->num_sgs = udev->bus->sg_tablesize ? sdb->table.nents : 0; urb->sg = sdb->table.sgl; out: @@ -442,7 +495,7 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, } static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, - struct scsi_cmnd *cmnd, u16 stream_id) + struct scsi_cmnd *cmnd) { struct usb_device *udev = devinfo->udev; struct scsi_device *sdev = cmnd->device; @@ -472,7 +525,7 @@ static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, memcpy(iu->cdb, cmnd->cmnd, cmnd->cmd_len); usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu) + len, - usb_free_urb, NULL); + uas_cmd_cmplt, cmnd); urb->transfer_flags |= URB_FREE_BUFFER; out: return urb; @@ -512,13 +565,17 @@ static int uas_submit_task_urb(struct scsi_cmnd *cmnd, gfp_t gfp, } usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu), - usb_free_urb, NULL); + uas_cmd_cmplt, cmnd); urb->transfer_flags |= URB_FREE_BUFFER; + usb_anchor_urb(urb, &devinfo->cmd_urbs); err = usb_submit_urb(urb, gfp); - if (err) + if (err) { + usb_unanchor_urb(urb); + uas_log_cmd_state(cmnd, __func__); + scmd_printk(KERN_ERR, cmnd, "task submission err %d\n", err); goto err; - usb_anchor_urb(urb, &devinfo->cmd_urbs); + } return 0; @@ -533,38 +590,43 @@ err: * daft to me. */ -static int uas_submit_sense_urb(struct Scsi_Host *shost, - gfp_t gfp, unsigned int stream) +static struct urb *uas_submit_sense_urb(struct scsi_cmnd *cmnd, + gfp_t gfp, unsigned int stream) { - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct Scsi_Host *shost = cmnd->device->host; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; struct urb *urb; + int err; urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); if (!urb) - return SCSI_MLQUEUE_DEVICE_BUSY; - if (usb_submit_urb(urb, gfp)) { + return NULL; + usb_anchor_urb(urb, &devinfo->sense_urbs); + err = usb_submit_urb(urb, gfp); + if (err) { + usb_unanchor_urb(urb); + uas_log_cmd_state(cmnd, __func__); shost_printk(KERN_INFO, shost, - "sense urb submission failure\n"); + "sense urb submission error %d stream %d\n", + err, stream); usb_free_urb(urb); - return SCSI_MLQUEUE_DEVICE_BUSY; + return NULL; } - usb_anchor_urb(urb, &devinfo->sense_urbs); - return 0; + return urb; } static int uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo, gfp_t gfp) { struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct urb *urb; int err; - WARN_ON(!spin_is_locked(&devinfo->lock)); + WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); if (cmdinfo->state & SUBMIT_STATUS_URB) { - err = uas_submit_sense_urb(cmnd->device->host, gfp, - cmdinfo->stream); - if (err) { - return err; - } + urb = uas_submit_sense_urb(cmnd, gfp, cmdinfo->stream); + if (!urb) + return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~SUBMIT_STATUS_URB; } @@ -578,14 +640,18 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & SUBMIT_DATA_IN_URB) { - if (usb_submit_urb(cmdinfo->data_in_urb, gfp)) { + usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); + err = usb_submit_urb(cmdinfo->data_in_urb, gfp); + if (err) { + usb_unanchor_urb(cmdinfo->data_in_urb); + uas_log_cmd_state(cmnd, __func__); scmd_printk(KERN_INFO, cmnd, - "data in urb submission failure\n"); + "data in urb submission error %d stream %d\n", + err, cmdinfo->data_in_urb->stream_id); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->state &= ~SUBMIT_DATA_IN_URB; cmdinfo->state |= DATA_IN_URB_INFLIGHT; - usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); } if (cmdinfo->state & ALLOC_DATA_OUT_URB) { @@ -598,33 +664,37 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, } if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { - if (usb_submit_urb(cmdinfo->data_out_urb, gfp)) { + usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); + err = usb_submit_urb(cmdinfo->data_out_urb, gfp); + if (err) { + usb_unanchor_urb(cmdinfo->data_out_urb); + uas_log_cmd_state(cmnd, __func__); scmd_printk(KERN_INFO, cmnd, - "data out urb submission failure\n"); + "data out urb submission error %d stream %d\n", + err, cmdinfo->data_out_urb->stream_id); return SCSI_MLQUEUE_DEVICE_BUSY; } cmdinfo->state &= ~SUBMIT_DATA_OUT_URB; cmdinfo->state |= DATA_OUT_URB_INFLIGHT; - usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); } if (cmdinfo->state & ALLOC_CMD_URB) { - cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd, - cmdinfo->stream); + cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd); if (!cmdinfo->cmd_urb) return SCSI_MLQUEUE_DEVICE_BUSY; cmdinfo->state &= ~ALLOC_CMD_URB; } if (cmdinfo->state & SUBMIT_CMD_URB) { - usb_get_urb(cmdinfo->cmd_urb); - if (usb_submit_urb(cmdinfo->cmd_urb, gfp)) { + usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); + err = usb_submit_urb(cmdinfo->cmd_urb, gfp); + if (err) { + usb_unanchor_urb(cmdinfo->cmd_urb); + uas_log_cmd_state(cmnd, __func__); scmd_printk(KERN_INFO, cmnd, - "cmd urb submission failure\n"); + "cmd urb submission error %d\n", err); return SCSI_MLQUEUE_DEVICE_BUSY; } - usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); - usb_put_urb(cmdinfo->cmd_urb); cmdinfo->cmd_urb = NULL; cmdinfo->state &= ~SUBMIT_CMD_URB; cmdinfo->state |= COMMAND_INFLIGHT; @@ -644,18 +714,22 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, BUILD_BUG_ON(sizeof(struct uas_cmd_info) > sizeof(struct scsi_pointer)); + spin_lock_irqsave(&devinfo->lock, flags); + if (devinfo->resetting) { cmnd->result = DID_ERROR << 16; cmnd->scsi_done(cmnd); + spin_unlock_irqrestore(&devinfo->lock, flags); return 0; } - spin_lock_irqsave(&devinfo->lock, flags); if (devinfo->cmnd) { spin_unlock_irqrestore(&devinfo->lock, flags); return SCSI_MLQUEUE_DEVICE_BUSY; } + memset(cmdinfo, 0, sizeof(*cmdinfo)); + if (blk_rq_tagged(cmnd->request)) { cmdinfo->stream = cmnd->request->tag + 2; } else { @@ -692,13 +766,10 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, spin_unlock_irqrestore(&devinfo->lock, flags); return SCSI_MLQUEUE_DEVICE_BUSY; } - spin_lock(&uas_work_lock); - list_add_tail(&cmdinfo->list, &uas_work_list); - cmdinfo->state |= IS_IN_WORK_LIST; - spin_unlock(&uas_work_lock); - schedule_work(&uas_work); + uas_add_work(cmdinfo); } + list_add_tail(&cmdinfo->list, &devinfo->inflight_list); spin_unlock_irqrestore(&devinfo->lock, flags); return 0; } @@ -709,46 +780,78 @@ static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, const char *fname, u8 function) { struct Scsi_Host *shost = cmnd->device->host; - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; - u16 tag = devinfo->qdepth - 1; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + u16 tag = devinfo->qdepth; unsigned long flags; + struct urb *sense_urb; + int result = SUCCESS; spin_lock_irqsave(&devinfo->lock, flags); + + if (devinfo->resetting) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + + if (devinfo->running_task) { + shost_printk(KERN_INFO, shost, + "%s: %s: error already running a task\n", + __func__, fname); + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; + } + + devinfo->running_task = 1; memset(&devinfo->response, 0, sizeof(devinfo->response)); - if (uas_submit_sense_urb(shost, GFP_ATOMIC, tag)) { + sense_urb = uas_submit_sense_urb(cmnd, GFP_NOIO, + devinfo->use_streams ? tag : 0); + if (!sense_urb) { shost_printk(KERN_INFO, shost, "%s: %s: submit sense urb failed\n", __func__, fname); + devinfo->running_task = 0; spin_unlock_irqrestore(&devinfo->lock, flags); return FAILED; } - if (uas_submit_task_urb(cmnd, GFP_ATOMIC, function, tag)) { + if (uas_submit_task_urb(cmnd, GFP_NOIO, function, tag)) { shost_printk(KERN_INFO, shost, "%s: %s: submit task mgmt urb failed\n", __func__, fname); + devinfo->running_task = 0; spin_unlock_irqrestore(&devinfo->lock, flags); + usb_kill_urb(sense_urb); return FAILED; } spin_unlock_irqrestore(&devinfo->lock, flags); if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 3000) == 0) { + /* + * Note we deliberately do not clear running_task here. If we + * allow new tasks to be submitted, there is no way to figure + * out if a received response_iu is for the failed task or for + * the new one. A bus-reset will eventually clear running_task. + */ shost_printk(KERN_INFO, shost, "%s: %s timed out\n", __func__, fname); return FAILED; } + + spin_lock_irqsave(&devinfo->lock, flags); + devinfo->running_task = 0; if (be16_to_cpu(devinfo->response.tag) != tag) { shost_printk(KERN_INFO, shost, "%s: %s failed (wrong tag %d/%d)\n", __func__, fname, be16_to_cpu(devinfo->response.tag), tag); - return FAILED; - } - if (devinfo->response.response_code != RC_TMF_COMPLETE) { + result = FAILED; + } else if (devinfo->response.response_code != RC_TMF_COMPLETE) { shost_printk(KERN_INFO, shost, "%s: %s failed (rc 0x%x)\n", __func__, fname, devinfo->response.response_code); - return FAILED; + result = FAILED; } - return SUCCESS; + spin_unlock_irqrestore(&devinfo->lock, flags); + + return result; } static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) @@ -758,22 +861,19 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) unsigned long flags; int ret; - uas_log_cmd_state(cmnd, __func__); spin_lock_irqsave(&devinfo->lock, flags); - cmdinfo->state |= COMMAND_ABORTED; - if (cmdinfo->state & IS_IN_WORK_LIST) { - spin_lock(&uas_work_lock); - list_del(&cmdinfo->list); - cmdinfo->state &= ~IS_IN_WORK_LIST; - spin_unlock(&uas_work_lock); + + if (devinfo->resetting) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return FAILED; } + + uas_mark_cmd_dead(devinfo, cmdinfo, DID_ABORT, __func__); if (cmdinfo->state & COMMAND_INFLIGHT) { spin_unlock_irqrestore(&devinfo->lock, flags); ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); } else { - spin_unlock_irqrestore(&devinfo->lock, flags); - uas_unlink_data_urbs(devinfo, cmdinfo); - spin_lock_irqsave(&devinfo->lock, flags); + uas_unlink_data_urbs(devinfo, cmdinfo, &flags); uas_try_complete(cmnd, __func__); spin_unlock_irqrestore(&devinfo->lock, flags); ret = SUCCESS; @@ -795,14 +895,25 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) struct usb_device *udev = devinfo->udev; int err; + err = usb_lock_device_for_reset(udev, devinfo->intf); + if (err) { + shost_printk(KERN_ERR, sdev->host, + "%s FAILED to get lock err %d\n", __func__, err); + return FAILED; + } + + shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); devinfo->resetting = 1; - uas_abort_work(devinfo); + uas_abort_inflight(devinfo, DID_RESET, __func__); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_zap_dead(devinfo); err = usb_reset_device(udev); devinfo->resetting = 0; + usb_unlock_device(udev); + if (err) { shost_printk(KERN_INFO, sdev->host, "%s FAILED\n", __func__); return FAILED; @@ -814,7 +925,25 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) static int uas_slave_alloc(struct scsi_device *sdev) { - sdev->hostdata = (void *)sdev->host->hostdata[0]; + sdev->hostdata = (void *)sdev->host->hostdata; + + /* USB has unusual DMA-alignment requirements: Although the + * starting address of each scatter-gather element doesn't matter, + * the length of each element except the last must be divisible + * by the Bulk maxpacket value. There's currently no way to + * express this by block-layer constraints, so we'll cop out + * and simply require addresses to be aligned at 512-byte + * boundaries. This is okay since most block I/O involves + * hardware sectors that are multiples of 512 bytes in length, + * and since host controllers up through USB 2.0 have maxpacket + * values no larger than 512. + * + * But it doesn't suffice for Wireless USB, where Bulk maxpacket + * values can be as large as 2048. To make that work properly + * will require changes to the block layer. + */ + blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); + return 0; } @@ -822,7 +951,7 @@ static int uas_slave_configure(struct scsi_device *sdev) { struct uas_dev_info *devinfo = sdev->hostdata; scsi_set_tag_type(sdev, MSG_ORDERED_TAG); - scsi_activate_tcq(sdev, devinfo->qdepth - 3); + scsi_activate_tcq(sdev, devinfo->qdepth - 2); return 0; } @@ -843,7 +972,14 @@ static struct scsi_host_template uas_host_template = { .ordered_tag = 1, }; +#define UNUSUAL_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) } + static struct usb_device_id uas_usb_ids[] = { +# include "unusual_uas.h" { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_BULK) }, { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_UAS) }, /* 0xaa is a prototype device I happen to have access to */ @@ -852,105 +988,55 @@ static struct usb_device_id uas_usb_ids[] = { }; MODULE_DEVICE_TABLE(usb, uas_usb_ids); -static int uas_is_interface(struct usb_host_interface *intf) -{ - return (intf->desc.bInterfaceClass == USB_CLASS_MASS_STORAGE && - intf->desc.bInterfaceSubClass == USB_SC_SCSI && - intf->desc.bInterfaceProtocol == USB_PR_UAS); -} - -static int uas_isnt_supported(struct usb_device *udev) -{ - struct usb_hcd *hcd = bus_to_hcd(udev->bus); - - dev_warn(&udev->dev, "The driver for the USB controller %s does not " - "support scatter-gather which is\n", - hcd->driver->description); - dev_warn(&udev->dev, "required by the UAS driver. Please try an" - "alternative USB controller if you wish to use UAS.\n"); - return -ENODEV; -} +#undef UNUSUAL_DEV static int uas_switch_interface(struct usb_device *udev, - struct usb_interface *intf) + struct usb_interface *intf) { - int i; - int sg_supported = udev->bus->sg_tablesize != 0; - - for (i = 0; i < intf->num_altsetting; i++) { - struct usb_host_interface *alt = &intf->altsetting[i]; - - if (uas_is_interface(alt)) { - if (!sg_supported) - return uas_isnt_supported(udev); - return usb_set_interface(udev, - alt->desc.bInterfaceNumber, - alt->desc.bAlternateSetting); - } - } + int alt; + + alt = uas_find_uas_alt_setting(intf); + if (alt < 0) + return alt; - return -ENODEV; + return usb_set_interface(udev, + intf->altsetting[0].desc.bInterfaceNumber, alt); } -static void uas_configure_endpoints(struct uas_dev_info *devinfo) +static int uas_configure_endpoints(struct uas_dev_info *devinfo) { struct usb_host_endpoint *eps[4] = { }; - struct usb_interface *intf = devinfo->intf; struct usb_device *udev = devinfo->udev; - struct usb_host_endpoint *endpoint = intf->cur_altsetting->endpoint; - unsigned i, n_endpoints = intf->cur_altsetting->desc.bNumEndpoints; + int r; devinfo->uas_sense_old = 0; devinfo->cmnd = NULL; - for (i = 0; i < n_endpoints; i++) { - unsigned char *extra = endpoint[i].extra; - int len = endpoint[i].extralen; - while (len > 1) { - if (extra[1] == USB_DT_PIPE_USAGE) { - unsigned pipe_id = extra[2]; - if (pipe_id > 0 && pipe_id < 5) - eps[pipe_id - 1] = &endpoint[i]; - break; - } - len -= extra[0]; - extra += extra[0]; - } - } + r = uas_find_endpoints(devinfo->intf->cur_altsetting, eps); + if (r) + return r; - /* - * Assume that if we didn't find a control pipe descriptor, we're - * using a device with old firmware that happens to be set up like - * this. - */ - if (!eps[0]) { - devinfo->cmd_pipe = usb_sndbulkpipe(udev, 1); - devinfo->status_pipe = usb_rcvbulkpipe(udev, 1); - devinfo->data_in_pipe = usb_rcvbulkpipe(udev, 2); - devinfo->data_out_pipe = usb_sndbulkpipe(udev, 2); - - eps[1] = usb_pipe_endpoint(udev, devinfo->status_pipe); - eps[2] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); - eps[3] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); - } else { - devinfo->cmd_pipe = usb_sndbulkpipe(udev, - eps[0]->desc.bEndpointAddress); - devinfo->status_pipe = usb_rcvbulkpipe(udev, - eps[1]->desc.bEndpointAddress); - devinfo->data_in_pipe = usb_rcvbulkpipe(udev, - eps[2]->desc.bEndpointAddress); - devinfo->data_out_pipe = usb_sndbulkpipe(udev, - eps[3]->desc.bEndpointAddress); - } + devinfo->cmd_pipe = usb_sndbulkpipe(udev, + usb_endpoint_num(&eps[0]->desc)); + devinfo->status_pipe = usb_rcvbulkpipe(udev, + usb_endpoint_num(&eps[1]->desc)); + devinfo->data_in_pipe = usb_rcvbulkpipe(udev, + usb_endpoint_num(&eps[2]->desc)); + devinfo->data_out_pipe = usb_sndbulkpipe(udev, + usb_endpoint_num(&eps[3]->desc)); - devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256, - GFP_KERNEL); - if (devinfo->qdepth < 0) { + if (udev->speed != USB_SPEED_SUPER) { devinfo->qdepth = 256; devinfo->use_streams = 0; } else { + devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, + 3, 256, GFP_KERNEL); + if (devinfo->qdepth < 0) + return devinfo->qdepth; devinfo->use_streams = 1; } + + return 0; } static void uas_free_streams(struct uas_dev_info *devinfo) @@ -964,30 +1050,23 @@ static void uas_free_streams(struct uas_dev_info *devinfo) usb_free_streams(devinfo->intf, eps, 3, GFP_KERNEL); } -/* - * XXX: What I'd like to do here is register a SCSI host for each USB host in - * the system. Follow usb-storage's design of registering a SCSI host for - * each USB device for the moment. Can implement this by walking up the - * USB hierarchy until we find a USB host. - */ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) { - int result; - struct Scsi_Host *shost; + int result = -ENOMEM; + struct Scsi_Host *shost = NULL; struct uas_dev_info *devinfo; struct usb_device *udev = interface_to_usbdev(intf); - if (uas_switch_interface(udev, intf)) + if (!uas_use_uas_driver(intf, id)) return -ENODEV; - devinfo = kmalloc(sizeof(struct uas_dev_info), GFP_KERNEL); - if (!devinfo) - return -ENOMEM; + if (uas_switch_interface(udev, intf)) + return -ENODEV; - result = -ENOMEM; - shost = scsi_host_alloc(&uas_host_template, sizeof(void *)); + shost = scsi_host_alloc(&uas_host_template, + sizeof(struct uas_dev_info)); if (!shost) - goto free; + goto set_alt0; shost->max_cmd_len = 16 + 252; shost->max_id = 1; @@ -995,33 +1074,40 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) shost->max_channel = 0; shost->sg_tablesize = udev->bus->sg_tablesize; + devinfo = (struct uas_dev_info *)shost->hostdata; devinfo->intf = intf; devinfo->udev = udev; devinfo->resetting = 0; + devinfo->running_task = 0; + devinfo->shutdown = 0; init_usb_anchor(&devinfo->cmd_urbs); init_usb_anchor(&devinfo->sense_urbs); init_usb_anchor(&devinfo->data_urbs); spin_lock_init(&devinfo->lock); - uas_configure_endpoints(devinfo); + INIT_WORK(&devinfo->work, uas_do_work); + INIT_LIST_HEAD(&devinfo->inflight_list); + INIT_LIST_HEAD(&devinfo->dead_list); - result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3); + result = uas_configure_endpoints(devinfo); if (result) - goto free; + goto set_alt0; - result = scsi_add_host(shost, &intf->dev); + result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 2); if (result) - goto deconfig_eps; + goto free_streams; - shost->hostdata[0] = (unsigned long)devinfo; + result = scsi_add_host(shost, &intf->dev); + if (result) + goto free_streams; scsi_scan_host(shost); usb_set_intfdata(intf, shost); return result; -deconfig_eps: +free_streams: uas_free_streams(devinfo); - free: - kfree(devinfo); +set_alt0: + usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); if (shost) scsi_host_put(shost); return result; @@ -1029,45 +1115,146 @@ deconfig_eps: static int uas_pre_reset(struct usb_interface *intf) { -/* XXX: Need to return 1 if it's not our device in error handling */ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + + if (devinfo->shutdown) + return 0; + + /* Block new requests */ + spin_lock_irqsave(shost->host_lock, flags); + scsi_block_requests(shost); + spin_unlock_irqrestore(shost->host_lock, flags); + + /* Wait for any pending requests to complete */ + flush_work(&devinfo->work); + if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000) == 0) { + shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); + return 1; + } + + uas_free_streams(devinfo); + return 0; } static int uas_post_reset(struct usb_interface *intf) { -/* XXX: Need to return 1 if it's not our device in error handling */ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + + if (devinfo->shutdown) + return 0; + + if (uas_configure_endpoints(devinfo) != 0) { + shost_printk(KERN_ERR, shost, + "%s: alloc streams error after reset", __func__); + return 1; + } + + spin_lock_irqsave(shost->host_lock, flags); + scsi_report_bus_reset(shost, 0); + spin_unlock_irqrestore(shost->host_lock, flags); + + scsi_unblock_requests(shost); + + return 0; +} + +static int uas_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + + /* Wait for any pending requests to complete */ + flush_work(&devinfo->work); + if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000) == 0) { + shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); + return -ETIME; + } + + return 0; +} + +static int uas_resume(struct usb_interface *intf) +{ + return 0; +} + +static int uas_reset_resume(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + + if (uas_configure_endpoints(devinfo) != 0) { + shost_printk(KERN_ERR, shost, + "%s: alloc streams error after reset", __func__); + return -EIO; + } + + spin_lock_irqsave(shost->host_lock, flags); + scsi_report_bus_reset(shost, 0); + spin_unlock_irqrestore(shost->host_lock, flags); + return 0; } static void uas_disconnect(struct usb_interface *intf) { struct Scsi_Host *shost = usb_get_intfdata(intf); - struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; devinfo->resetting = 1; - uas_abort_work(devinfo); + cancel_work_sync(&devinfo->work); + uas_abort_inflight(devinfo, DID_NO_CONNECT, __func__); usb_kill_anchored_urbs(&devinfo->cmd_urbs); usb_kill_anchored_urbs(&devinfo->sense_urbs); usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_zap_dead(devinfo); scsi_remove_host(shost); uas_free_streams(devinfo); - kfree(devinfo); + scsi_host_put(shost); } /* - * XXX: Should this plug into libusual so we can auto-upgrade devices from - * Bulk-Only to UAS? + * Put the device back in usb-storage mode on shutdown, as some BIOS-es + * hang on reboot when the device is still in uas mode. Note the reset is + * necessary as some devices won't revert to usb-storage mode without it. */ +static void uas_shutdown(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + + if (system_state != SYSTEM_RESTART) + return; + + devinfo->shutdown = 1; + uas_free_streams(devinfo); + usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); + usb_reset_device(udev); +} + static struct usb_driver uas_driver = { .name = "uas", .probe = uas_probe, .disconnect = uas_disconnect, .pre_reset = uas_pre_reset, .post_reset = uas_post_reset, + .suspend = uas_suspend, + .resume = uas_resume, + .reset_resume = uas_reset_resume, + .drvwrap.driver.shutdown = uas_shutdown, .id_table = uas_usb_ids, }; module_usb_driver(uas_driver); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Matthew Wilcox and Sarah Sharp"); +MODULE_AUTHOR( + "Hans de Goede <hdegoede@redhat.com>, Matthew Wilcox and Sarah Sharp"); diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index adbeb255616..f4a82291894 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -2086,6 +2086,11 @@ UNUSUAL_DEV( 0xed10, 0x7636, 0x0001, 0x0001, "Digital MP3 Audio Player", USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE ), +/* Unusual uas devices */ +#if IS_ENABLED(CONFIG_USB_UAS) +#include "unusual_uas.h" +#endif + /* Control/Bulk transport for all SubClass values */ USUAL_DEV(USB_SC_RBC, USB_PR_CB), USUAL_DEV(USB_SC_8020, USB_PR_CB), diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h new file mode 100644 index 00000000000..7244444df8e --- /dev/null +++ b/drivers/usb/storage/unusual_uas.h @@ -0,0 +1,52 @@ +/* Driver for USB Attached SCSI devices - Unusual Devices File + * + * (c) 2013 Hans de Goede <hdegoede@redhat.com> + * + * Based on the same file for the usb-storage driver, which is: + * (c) 2000-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * (c) 2000 Adam J. Richter (adam@yggdrasil.com), Yggdrasil Computing, Inc. + * + * 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. + */ + +/* + * IMPORTANT NOTE: This file must be included in another file which defines + * a UNUSUAL_DEV macro before this file is included. + */ + +/* + * If you edit this file, please try to keep it sorted first by VendorID, + * then by ProductID. + * + * If you want to add an entry for this file, be sure to include the + * following information: + * - a patch that adds the entry for your device, including your + * email address right above the entry (plus maybe a brief + * explanation of the reason for the entry), + * - lsusb -v output for the device + * Send your submission to Hans de Goede <hdegoede@redhat.com> + * and don't forget to CC: the USB development list <linux-usb@vger.kernel.org> + */ + +/* + * This is an example entry for the US_FL_IGNORE_UAS flag. Once we have an + * actual entry using US_FL_IGNORE_UAS this entry should be removed. + * + * UNUSUAL_DEV( 0xabcd, 0x1234, 0x0100, 0x0100, + * "Example", + * "Storage with broken UAS", + * USB_SC_DEVICE, USB_PR_DEVICE, NULL, + * US_FL_IGNORE_UAS), + */ diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 1c0b89f2a13..f1c96261a50 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -72,6 +72,10 @@ #include "sierra_ms.h" #include "option_ms.h" +#if IS_ENABLED(CONFIG_USB_UAS) +#include "uas-detect.h" +#endif + /* Some informational data */ MODULE_AUTHOR("Matthew Dharm <mdharm-usb@one-eyed-alien.net>"); MODULE_DESCRIPTION("USB Mass Storage driver for Linux"); @@ -459,14 +463,14 @@ static int associate_dev(struct us_data *us, struct usb_interface *intf) #define TOLOWER(x) ((x) | 0x20) /* Adjust device flags based on the "quirks=" module parameter */ -static void adjust_quirks(struct us_data *us) +void usb_stor_adjust_quirks(struct usb_device *udev, unsigned long *fflags) { char *p; - u16 vid = le16_to_cpu(us->pusb_dev->descriptor.idVendor); - u16 pid = le16_to_cpu(us->pusb_dev->descriptor.idProduct); + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); unsigned f = 0; unsigned int mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE | - US_FL_FIX_CAPACITY | + US_FL_FIX_CAPACITY | US_FL_IGNORE_UAS | US_FL_CAPACITY_HEURISTICS | US_FL_IGNORE_DEVICE | US_FL_NOT_LOCKABLE | US_FL_MAX_SECTORS_64 | US_FL_CAPACITY_OK | US_FL_IGNORE_RESIDUE | @@ -537,14 +541,18 @@ static void adjust_quirks(struct us_data *us) case 's': f |= US_FL_SINGLE_LUN; break; + case 'u': + f |= US_FL_IGNORE_UAS; + break; case 'w': f |= US_FL_NO_WP_DETECT; break; /* Ignore unrecognized flag characters */ } } - us->fflags = (us->fflags & ~mask) | f; + *fflags = (*fflags & ~mask) | f; } +EXPORT_SYMBOL_GPL(usb_stor_adjust_quirks); /* Get the unusual_devs entries and the string descriptors */ static int get_device_info(struct us_data *us, const struct usb_device_id *id, @@ -564,7 +572,7 @@ static int get_device_info(struct us_data *us, const struct usb_device_id *id, idesc->bInterfaceProtocol : unusual_dev->useTransport; us->fflags = id->driver_info; - adjust_quirks(us); + usb_stor_adjust_quirks(us->pusb_dev, &us->fflags); if (us->fflags & US_FL_IGNORE_DEVICE) { dev_info(pdev, "device ignored\n"); @@ -1035,6 +1043,12 @@ static int storage_probe(struct usb_interface *intf, int result; int size; + /* If uas is enabled and this device can do uas then ignore it. */ +#if IS_ENABLED(CONFIG_USB_UAS) + if (uas_use_uas_driver(intf, id)) + return -ENXIO; +#endif + /* * If the device isn't standard (is handled by a subdriver * module) then don't accept it. diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index 75f70f04f37..307e339a947 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -201,4 +201,7 @@ extern int usb_stor_probe1(struct us_data **pus, extern int usb_stor_probe2(struct us_data *us); extern void usb_stor_disconnect(struct usb_interface *intf); +extern void usb_stor_adjust_quirks(struct usb_device *dev, + unsigned long *fflags); + #endif |