If the UAS device has been informed of a pending SCSI command with an URB sent to its command pipe, we need to abort that command by sending a task management ABORT TASK IU, before canceling the other URBs associated with that SCSI command. The task management IU is sent to the command pipe (which does not have streams enabled), and the device will respond by sending a response IU on the status pipe (which does have streams enabled). The response IU URB needs to be submitted before the task management IU URB. Since task management IUs and command IUs share the same tag (stream) space, we can't have a task management IU with the same tag as a pending SCSI command. Otherwise the device will abort all queued transfers. When telling the SCSI layer how many tags we can handle, reserve one more tag for the ABORT TASK IU. Hopefully the SCSI layer doesn't try to cancel more than one command at a time, so someone please double check this. The response URB and task management URB will be freed in their completion handler (usb_free_urb), along with the task management URB buffer. However, we save the response URB buffer so we can see if the SCSI command was successfully aborted. If the ABORT TASK failed in any way (allocation failed, URB submission failed, one of the two URBs timed out, or the response IU indicates an error), then let the SCSI core know the command abort failed. Leave the URBs associated with the command queued, since they'll just get killed before the device reset. I'm not sure how to interpret the completion code in the ABORT TASK response IU. What's the difference between the response IU code 0x0 (TASK MANAGEMENT FUNCTION COMPLETE) and code 0x8 (TASK MANAGEMENT FUNCTION SUCCEEDED)? Perhaps that the task completed before it could aborted vs. the abort succeeded? I took both codes to mean success. Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> Cc: Matthew Wilcox <willy@xxxxxxxxxxxxxxx> --- drivers/usb/storage/uas.c | 140 ++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 137 insertions(+), 3 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index f61de1f..2a403fc 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -38,6 +38,28 @@ enum { IU_ID_WRITE_READY = 0x07, }; +enum { + IU_FUNC_ABORT_TASK = 0x01, + IU_FUNC_ABORT_TASK_SET = 0x02, + IU_FUNC_CLEAR_TASK_SET = 0x04, + IU_FUNC_LOG_UNIT_RESET = 0x08, + IU_FUNC_I_T_NEXUS_RESET = 0x10, + IU_FUNC_CLEAR_ACA = 0x40, + IU_FUNC_QUERY_TASK = 0x80, + IU_FUNC_QUERY_TASK_SET = 0x81, + IU_FUNC_QUERY_ASYNC_EVT = 0x82, +}; + +struct task_iu { + __u8 iu_id; + __u8 rsvd1; + __be16 tag; + __u8 function; + __u8 rsvd5; + __be16 managed_tag; + struct scsi_lun lun; +}; + struct command_iu { __u8 iu_id; __u8 rsvd1; @@ -79,6 +101,17 @@ struct sense_iu_old { __u8 sense[SCSI_SENSE_BUFFERSIZE]; }; +struct response_iu { + __u8 iu_id; + __u8 rsvd1; + __be16 tag; + __u8 info[3]; + __u8 code; +}; + +#define UAS_TASK_COMPLETE 0x00 +#define UAS_TASK_SUCCESS 0x08 + enum { CMD_PIPE_ID = 1, STATUS_PIPE_ID = 2, @@ -562,6 +595,97 @@ static void uas_kill_tagged_urbs(struct usb_anchor *anchor, } } +static int uas_issue_abort_task(struct scsi_cmnd *cmnd, + struct scsi_device *sdev, struct uas_dev_info *devinfo) +{ + struct task_iu *tiu = NULL; + struct response_iu *riu = NULL; + struct urb *task_urb = NULL; + struct urb *response_urb = NULL; + int timeleft, ret; + + ret = -ENOMEM; + task_urb = usb_alloc_urb(0, GFP_NOIO); + if (!task_urb) + goto out; + tiu = kzalloc(sizeof(*tiu), GFP_NOIO); + if (!tiu) + goto out; + response_urb = usb_alloc_urb(0, GFP_NOIO); + if (!response_urb) + goto out; + riu = kzalloc(sizeof(*riu), GFP_NOIO); + if (!riu) + goto out; + + ret = -ETIMEDOUT; + sdev_printk(KERN_INFO, sdev, + "%s tag %d issuing ABORT TASK with tag %d\n", + __func__, cmnd->request->tag, devinfo->qdepth); + + usb_fill_bulk_urb(response_urb, devinfo->udev, devinfo->status_pipe, + riu, sizeof(*riu), usb_free_urb, cmnd->device); + response_urb->stream_id = devinfo->qdepth; + /* Don't set URB_FREE_BUFFER so we can read the response */ + + if (usb_submit_urb(response_urb, GFP_NOIO)) + goto out; + usb_anchor_urb(response_urb, &devinfo->anchors[devinfo->qdepth - 1]); + response_urb = NULL; + + tiu->iu_id = IU_ID_TASK_MGMT; + /* Use tag we reserved when we set scsi queue length short */ + tiu->tag = cpu_to_be16(devinfo->qdepth); + tiu->function = IU_FUNC_ABORT_TASK; + if (blk_rq_tagged(cmnd->request)) + tiu->managed_tag = cpu_to_be16(cmnd->request->tag + 1); + else + tiu->managed_tag = cpu_to_be16(1); + int_to_scsilun(sdev->lun, &tiu->lun); + usb_fill_bulk_urb(task_urb, devinfo->udev, devinfo->cmd_pipe, tiu, + sizeof(*tiu), usb_free_urb, NULL); + task_urb->transfer_flags |= URB_FREE_BUFFER; + + if (usb_submit_urb(task_urb, GFP_NOIO)) { + usb_kill_anchored_urbs(&devinfo->anchors[devinfo->qdepth - 1]); + goto out; + } + usb_anchor_urb(task_urb, &devinfo->anchors[devinfo->qdepth - 1]); + task_urb = NULL; + tiu = NULL; + + /* Wait a whole second for the ABORT TASK to complete */ + timeleft = usb_wait_anchor_empty_timeout( + &devinfo->anchors[devinfo->qdepth - 1], + 1000); + if (!timeleft) { + sdev_printk(KERN_INFO, sdev, + "%s tag %d ABORT TASK timed out\n", + __func__, cmnd->request->tag); + usb_kill_anchored_urbs(&devinfo->anchors[devinfo->qdepth - 1]); + goto out; + } + + if (be16_to_cpu(riu->code) != UAS_TASK_SUCCESS && + be16_to_cpu(riu->code) != UAS_TASK_COMPLETE) { + sdev_printk(KERN_INFO, sdev, + "%s tag %d ABORT TASK failed, code 0x%x\n", + __func__, cmnd->request->tag, + be16_to_cpu(riu->code)); + } else { + sdev_printk(KERN_INFO, sdev, "%s tag %d ABORT TASK success\n", + __func__, cmnd->request->tag); + ret = 0; + } + +out: + kfree(tiu); + usb_free_urb(task_urb); + kfree(riu); + usb_free_urb(response_urb); + return ret; +} + static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) { struct scsi_device *sdev = cmnd->device; @@ -575,13 +699,22 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) list_del_init(&cmdinfo->list); spin_unlock_irq(&uas_work_lock); + /* Send ABORT TASK Task Management command if we sent the command IU. + * XXX: we don't know if the command IU didn't make it because of an + * error (like electrical noise), since the completion function is + * usb_free_urb. FIXME later. + */ + if (!(cmdinfo->state & SUBMIT_CMD_URB)) { + if (uas_issue_abort_task(cmnd, sdev, devinfo)) + return FAILED; + } + if (blk_rq_tagged(cmnd->request)) uas_kill_tagged_urbs(&devinfo->anchors[cmnd->request->tag], cmdinfo); else uas_kill_tagged_urbs(&devinfo->anchors[0], cmdinfo); -/* XXX: Send ABORT TASK Task Management command */ - return FAILED; + return SUCCESS; } static int uas_eh_device_reset_handler(struct scsi_cmnd *cmnd) @@ -631,7 +764,8 @@ 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 - 1); + /* Tag 0 is reserved; reserve another for command abort task IU */ + scsi_activate_tcq(sdev, devinfo->qdepth - 2); return 0; } -- 1.7.5.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html