#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>
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;
struct scsi_cmnd *cmnd;
spinlock_t lock;
+ struct work_struct work;
+ struct list_head work_list;
+ struct list_head dead_list;
};
enum {
struct urb *cmd_urb;
struct urb *data_in_urb;
struct urb *data_out_urb;
- struct list_head list;
+ struct list_head work;
+ struct list_head dead;
};
/* I hate forward declarations, but I actually have a loop */
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_configure_endpoints(struct uas_dev_info *devinfo);
+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_safe(cmdinfo, temp, &devinfo->work_list, work) {
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);
+ struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd,
+ SCp);
err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC);
- if (!err)
+ 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);
+ list_del(&cmdinfo->work);
+ } else {
+ schedule_work(&devinfo->work);
}
}
+ spin_unlock_irqrestore(&devinfo->lock, flags);
}
static void uas_abort_work(struct uas_dev_info *devinfo)
{
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, &list, list) {
+ list_for_each_entry_safe(cmdinfo, temp, &devinfo->work_list, work) {
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;
+ struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd,
+ SCp);
+ uas_log_cmd_state(cmnd, __func__);
+ WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED);
+ cmdinfo->state |= COMMAND_ABORTED;
+ cmdinfo->state &= ~IS_IN_WORK_LIST;
+ list_del(&cmdinfo->work);
+ list_add_tail(&cmdinfo->dead, &devinfo->dead_list);
+ }
+ spin_unlock_irqrestore(&devinfo->lock, flags);
+}
- 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);
- }
+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));
+ list_add_tail(&cmdinfo->work, &devinfo->work_list);
+ 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, &devinfo->dead_list, dead) {
+ struct scsi_pointer *scp = (void *)cmdinfo;
+ 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__);
}
spin_unlock_irqrestore(&devinfo->lock, flags);
}
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) {
scmd_printk(KERN_INFO, cmnd, "abort completed\n");
cmnd->result = DID_ABORT << 16;
+ list_del(&cmdinfo->dead);
}
cmnd->scsi_done(cmnd);
return 0;
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);
}
}
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;
}
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__);
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) {
/* error: no data transfered */
sdb->resid = sdb->length;
} else {
}
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;
usb_free_urb, NULL);
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);
goto err;
- usb_anchor_urb(urb, &devinfo->cmd_urbs);
+ }
return 0;
urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream);
if (!urb)
return SCSI_MLQUEUE_DEVICE_BUSY;
+ usb_anchor_urb(urb, &devinfo->sense_urbs);
if (usb_submit_urb(urb, gfp)) {
+ usb_unanchor_urb(urb);
shost_printk(KERN_INFO, shost,
"sense urb submission failure\n");
usb_free_urb(urb);
return SCSI_MLQUEUE_DEVICE_BUSY;
}
- usb_anchor_urb(urb, &devinfo->sense_urbs);
return 0;
}
struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp;
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 (cmdinfo->state & SUBMIT_DATA_IN_URB) {
+ usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs);
if (usb_submit_urb(cmdinfo->data_in_urb, gfp)) {
+ usb_unanchor_urb(cmdinfo->data_in_urb);
scmd_printk(KERN_INFO, cmnd,
"data in urb submission failure\n");
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) {
}
if (cmdinfo->state & SUBMIT_DATA_OUT_URB) {
+ usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs);
if (usb_submit_urb(cmdinfo->data_out_urb, gfp)) {
+ usb_unanchor_urb(cmdinfo->data_out_urb);
scmd_printk(KERN_INFO, cmnd,
"data out urb submission failure\n");
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);
+ usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs);
if (usb_submit_urb(cmdinfo->cmd_urb, gfp)) {
+ usb_unanchor_urb(cmdinfo->cmd_urb);
scmd_printk(KERN_INFO, cmnd,
"cmd urb submission failure\n");
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;
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);
}
spin_unlock_irqrestore(&devinfo->lock, flags);
{
struct Scsi_Host *shost = cmnd->device->host;
struct uas_dev_info *devinfo = (void *)shost->hostdata[0];
- u16 tag = devinfo->qdepth - 1;
+ u16 tag = devinfo->qdepth;
unsigned long flags;
spin_lock_irqsave(&devinfo->lock, flags);
uas_log_cmd_state(cmnd, __func__);
spin_lock_irqsave(&devinfo->lock, flags);
+ WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED);
cmdinfo->state |= COMMAND_ABORTED;
+ list_add_tail(&cmdinfo->dead, &devinfo->dead_list);
if (cmdinfo->state & IS_IN_WORK_LIST) {
- spin_lock(&uas_work_lock);
- list_del(&cmdinfo->list);
+ list_del(&cmdinfo->work);
cmdinfo->state &= ~IS_IN_WORK_LIST;
- spin_unlock(&uas_work_lock);
}
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;
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);
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;
{
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;
}
eps[3] = usb_pipe_endpoint(udev, devinfo->data_out_pipe);
} else {
devinfo->cmd_pipe = usb_sndbulkpipe(udev,
- eps[0]->desc.bEndpointAddress);
+ usb_endpoint_num(&eps[0]->desc));
devinfo->status_pipe = usb_rcvbulkpipe(udev,
- eps[1]->desc.bEndpointAddress);
+ usb_endpoint_num(&eps[1]->desc));
devinfo->data_in_pipe = usb_rcvbulkpipe(udev,
- eps[2]->desc.bEndpointAddress);
+ usb_endpoint_num(&eps[2]->desc));
devinfo->data_out_pipe = usb_sndbulkpipe(udev,
- eps[3]->desc.bEndpointAddress);
+ usb_endpoint_num(&eps[3]->desc));
}
devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256,
*/
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);
devinfo = kmalloc(sizeof(struct uas_dev_info), GFP_KERNEL);
if (!devinfo)
- return -ENOMEM;
+ goto set_alt0;
- result = -ENOMEM;
shost = scsi_host_alloc(&uas_host_template, sizeof(void *));
if (!shost)
- goto free;
+ goto set_alt0;
shost->max_cmd_len = 16 + 252;
shost->max_id = 1;
init_usb_anchor(&devinfo->sense_urbs);
init_usb_anchor(&devinfo->data_urbs);
spin_lock_init(&devinfo->lock);
+ INIT_WORK(&devinfo->work, uas_do_work);
+ INIT_LIST_HEAD(&devinfo->work_list);
+ INIT_LIST_HEAD(&devinfo->dead_list);
uas_configure_endpoints(devinfo);
- result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 3);
+ result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 2);
if (result)
- goto free;
+ goto free_streams;
result = scsi_add_host(shost, &intf->dev);
if (result)
- goto deconfig_eps;
+ goto free_streams;
shost->hostdata[0] = (unsigned long)devinfo;
usb_set_intfdata(intf, shost);
return result;
-deconfig_eps:
+free_streams:
uas_free_streams(devinfo);
- free:
+set_alt0:
+ usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0);
kfree(devinfo);
if (shost)
scsi_host_put(shost);
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 = (void *)shost->hostdata[0];
+ unsigned long flags;
+
+ /* 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 = (void *)shost->hostdata[0];
+ unsigned long flags;
+
+ uas_configure_endpoints(devinfo);
+
+ 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;
}
struct uas_dev_info *devinfo = (void *)shost->hostdata[0];
devinfo->resetting = 1;
+ cancel_work_sync(&devinfo->work);
uas_abort_work(devinfo);
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);