diff options
Diffstat (limited to 'drivers/usb/storage/uas.c')
-rw-r--r-- | drivers/usb/storage/uas.c | 307 |
1 files changed, 303 insertions, 4 deletions
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 678903d1ce4d..d70bd5814796 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -80,6 +80,8 @@ 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 *prefix, int status); +static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, + struct scsi_cmnd *cmnd); /* * This driver needs its own workqueue, as we need to control memory allocation. @@ -296,18 +298,283 @@ static bool uas_evaluate_response_iu(struct response_iu *riu, struct scsi_cmnd * return response_code == RC_TMF_SUCCEEDED; } +static void dummy_scsi_done(struct scsi_cmnd *cmnd) +{ + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + + devinfo->cmnd[cmdinfo->uas_tag - 1] = NULL; + kfree(cmnd->request); + kfree(cmnd); +} + +static void uas_workaround_cmplt(struct urb *urb) +{ + struct scsi_cmnd *cmnd; + struct uas_cmd_info *cmdinfo; + + if ((urb->context != NULL) && (urb->status == 0)) { + cmnd = urb->context; + cmdinfo = (struct uas_cmd_info *)&cmnd->SCp; + + if (cmdinfo->data_in_urb != urb) + cmnd->scsi_done(cmnd); + } + + usb_free_urb(urb); +} + +static struct urb *uas_workaround_cmnd(struct uas_dev_info *devinfo, gfp_t gfp, + struct scsi_cmnd *cmnd) { + struct scsi_device *sdev = cmnd->device; + struct urb *urb; + int err; + + urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd); + if (!urb) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate cmnd URB\n", __func__); + return NULL; + } + + err = usb_submit_urb(urb, gfp); + if (err) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to submit cmd, err=%d\n", __func__, err); + goto free; + } + usb_anchor_urb(urb, &devinfo->cmd_urbs); + return urb; + +free: + usb_free_urb(urb); + return NULL; + +} + +static struct urb *uas_workaround_data(struct uas_dev_info *devinfo, gfp_t gfp, + struct scsi_cmnd *cmnd) { + struct scsi_device *sdev = cmnd->device; + struct usb_device *udev = devinfo->udev; + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct urb *urb = usb_alloc_urb(0, gfp); + struct scsi_data_buffer *sdb = NULL; + void *temp_buf; + unsigned int pipe; + int err; + + if (!urb) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate URB\n", __func__); + return NULL; + } + + cmdinfo->data_in_urb = urb; + sdb = &cmnd->sdb; + pipe = devinfo->data_in_pipe; + temp_buf = kzalloc(sdb->length, GFP_ATOMIC); + if (!temp_buf) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate memory\n", __func__); + goto free; + } + + usb_fill_bulk_urb(urb, udev, pipe, temp_buf, sdb->length, + uas_workaround_cmplt, cmnd); + if (devinfo->use_streams) + urb->stream_id = cmdinfo->uas_tag; + urb->transfer_flags |= URB_FREE_BUFFER; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to submit Data In urb, err = %d\n", + __func__, err); + goto free; + } + + usb_anchor_urb(urb, &devinfo->data_urbs); + return urb; + +free: + usb_free_urb(urb); + return NULL; +} + +static struct urb *uas_workaround_sense(struct uas_dev_info *devinfo, gfp_t gfp, + struct scsi_cmnd *cmnd) { + struct scsi_device *sdev = cmnd->device; + struct usb_device *udev = devinfo->udev; + struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; + struct urb *urb = usb_alloc_urb(0, gfp); + struct sense_iu *iu; + int err; + + if (!urb) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate URB\n", __func__); + return NULL; + } + + iu = kzalloc(sizeof(*iu), gfp); + if (!iu) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate memory for sense_iu\n", + __func__); + goto free; + } + + usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), + uas_workaround_cmplt, cmnd); + if (devinfo->use_streams) + urb->stream_id = cmdinfo->uas_tag; + urb->transfer_flags |= URB_FREE_BUFFER; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to submit Sense urb, err = %d\n", + __func__, err); + goto free; + } + usb_anchor_urb(urb, &devinfo->sense_urbs); + return urb; + +free: + usb_free_urb(urb); + return NULL; +} + +/* + * This function is called only if the DATA IN stream timer expired, which + * means xhci host controller has failed to process the TRB's present in the + * stream ring. As a part of recovery sequence, this function re-submits the + * previous stopped urb on which xhci failed to process data and along with + * that urb it prepares & submits sense, data and cmnd urb with scsi command + * set to standard inquiry request containing the next free stream id tag. + * Doing so will make the xhci start processing the previous stopped urb + * along with the urb that has standard inquiry scsi command. + */ +static int uas_workaround(struct urb *urb) +{ + struct scsi_cmnd *cmnd = urb->context; + struct scsi_device *sdev = cmnd->device; + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + struct scsi_cmnd *temp_cmnd; + struct uas_cmd_info *temp_cmdinfo; + struct urb *sense_urb, *data_urb, *cmnd_urb; + struct request *temp_request; + unsigned int idx; + int err; + char inquiry[16] = { 0x12, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + + /* Find a free uas-tag */ + for (idx = 0; idx < devinfo->qdepth; idx++) { + if (!devinfo->cmnd[idx]) + break; + } + + if (idx == devinfo->qdepth) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to find free tag\n", __func__); + err = -EINVAL; + goto free; + } + + /* Create a scsi_cmnd and send dummy inquiry data on the next + * available tag + */ + temp_cmnd = kzalloc(sizeof(struct scsi_cmnd), GFP_ATOMIC); + if (!temp_cmnd) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate memory for scsi_cmnd\n", + __func__); + err = -ENOMEM; + goto free; + } + + temp_request = kzalloc(sizeof(struct request), GFP_ATOMIC); + if (!temp_cmnd) { + shost_printk(KERN_INFO, sdev->host, + "%s: Failed to allocate memory for request\n", + __func__); + err = -ENOMEM; + goto free; + } + + temp_cmnd->device = cmnd->device; + temp_cmnd->cmnd = inquiry; + temp_cmnd->cmd_len = 16; + temp_cmnd->sdb.length = 0x10; + temp_cmnd->scsi_done = dummy_scsi_done; + temp_request->tag = idx; + temp_cmnd->request = temp_request; + + temp_cmdinfo = (struct uas_cmd_info *)&temp_cmnd->SCp; + memset(temp_cmdinfo, 0, sizeof(struct uas_cmd_info)); + + temp_cmdinfo->uas_tag = idx + 1; + devinfo->cmnd[idx] = temp_cmnd; + + /* Submit previously stopped URB first */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + shost_printk(KERN_INFO, sdev->host, + "%s: submit err %d\n", __func__, err); + kfree(temp_cmnd); + kfree(temp_request); + goto free; + } + usb_anchor_urb(urb, &devinfo->data_urbs); + + /* Allocate and submit SENSE urb for next available tag */ + sense_urb = uas_workaround_sense(devinfo, GFP_ATOMIC, temp_cmnd); + if (!sense_urb) { + kfree(temp_request); + kfree(temp_cmnd); + goto free; + } + + /* Allocate and submit DATA IN urb for next available tag */ + data_urb = uas_workaround_data(devinfo, GFP_ATOMIC, temp_cmnd); + if (!data_urb) { + /* Kill previously allocated sense urb */ + sense_urb->context = NULL; + usb_kill_urb(sense_urb); + usb_put_urb(sense_urb); + kfree(temp_request); + kfree(temp_cmnd); + goto free; + } + + /* Allocate and submit CMND urb with dummy inquiry data */ + cmnd_urb = uas_workaround_cmnd(devinfo, GFP_ATOMIC, temp_cmnd); + if (!cmnd_urb) { + /* Kill previously allocated data urb */ + data_urb->context = NULL; + usb_kill_urb(data_urb); + usb_put_urb(data_urb); + kfree(temp_request); + kfree(temp_cmnd); + } + +free: + return err; +} + 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 = (struct uas_dev_info *)shost->hostdata; + struct scsi_cmnd *cmnd = (struct scsi_cmnd *)urb->context; + struct uas_dev_info *devinfo = + (struct uas_dev_info *)cmnd->device->hostdata; struct urb *data_in_urb = NULL; struct urb *data_out_urb = NULL; - struct scsi_cmnd *cmnd; struct uas_cmd_info *cmdinfo; unsigned long flags; unsigned int idx; int status = urb->status; + int err; bool success; spin_lock_irqsave(&devinfo->lock, flags); @@ -316,6 +583,21 @@ static void uas_stat_cmplt(struct urb *urb) goto out; if (status) { + if (status == -EAGAIN) { + /* We get here only if the xhci stream timer expires, + * call uas_workaround() with this urb as argument. + */ + err = uas_workaround(urb); + if (err != 0) { + dev_err(&urb->dev->dev, + "%s: uas_workaround() failed, err=%d\n", + __func__, err); + goto out; + } + spin_unlock_irqrestore(&devinfo->lock, flags); + return; + } + if (status != -ENOENT && status != -ECONNRESET && status != -ESHUTDOWN) dev_err(&urb->dev->dev, "stat urb: status %d\n", status); goto out; @@ -398,10 +680,27 @@ static void uas_data_cmplt(struct urb *urb) struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; struct scsi_data_buffer *sdb = &cmnd->sdb; unsigned long flags; + int err; int status = urb->status; spin_lock_irqsave(&devinfo->lock, flags); + if ((status == -EAGAIN) && (!devinfo->resetting) && + (cmdinfo->data_in_urb == urb)) { + /* We get here only if the xhci stream timer expires, + * call uas_workaround() with this urb as argument. + */ + err = uas_workaround(urb); + if (err != 0) { + dev_err(&urb->dev->dev, + "%s: uas_workaround() failed, err=%d\n", + __func__, err); + goto out; + } + spin_unlock_irqrestore(&devinfo->lock, flags); + return; + } + if (cmdinfo->data_in_urb == urb) { cmdinfo->state &= ~DATA_IN_URB_INFLIGHT; cmdinfo->data_in_urb = NULL; @@ -480,7 +779,7 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, goto free; usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), - uas_stat_cmplt, cmnd->device->host); + uas_stat_cmplt, cmnd); if (devinfo->use_streams) urb->stream_id = cmdinfo->uas_tag; urb->transfer_flags |= URB_FREE_BUFFER; |