| From e118573c298eb2437106b66a8e942dda7cee4be1 Mon Sep 17 00:00:00 2001 |
| From: EJ Hsu <ejh@nvidia.com> |
| Date: Thu, 30 Jan 2020 01:25:06 -0800 |
| Subject: [PATCH] usb: uas: fix a plug & unplug racing |
| |
| commit 3e99862c05a9caa5a27969f41566b428696f5a9a upstream. |
| |
| When a uas disk is plugged into an external hub, uas_probe() |
| will be called by the hub thread to do the probe. It will |
| first create a SCSI host and then do the scan for this host. |
| During the scan, it will probe the LUN using SCSI INQUERY command |
| which will be packed in the URB and submitted to uas disk. |
| |
| There might be a chance that this external hub with uas disk |
| attached is unplugged during the scan. In this case, uas driver |
| will fail to submit the URB (due to the NOTATTACHED state of uas |
| device) and try to put this SCSI command back to request queue |
| waiting for next chance to run. |
| |
| In normal case, this cycle will terminate when hub thread gets |
| disconnection event and calls into uas_disconnect() accordingly. |
| But in this case, uas_disconnect() will not be called because |
| hub thread of external hub gets stuck waiting for the completion |
| of this SCSI command. A deadlock happened. |
| |
| In this fix, uas will call scsi_scan_host() asynchronously to |
| avoid the blocking of hub thread. |
| |
| Signed-off-by: EJ Hsu <ejh@nvidia.com> |
| Acked-by: Oliver Neukum <oneukum@suse.com> |
| Cc: stable <stable@vger.kernel.org> |
| Link: https://lore.kernel.org/r/20200130092506.102760-1-ejh@nvidia.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c |
| index 80448ecf6b6a..07091c5125bc 100644 |
| --- a/drivers/usb/storage/uas.c |
| +++ b/drivers/usb/storage/uas.c |
| @@ -45,6 +45,7 @@ struct uas_dev_info { |
| struct scsi_cmnd *cmnd[MAX_CMNDS]; |
| spinlock_t lock; |
| struct work_struct work; |
| + struct work_struct scan_work; /* for async scanning */ |
| }; |
| |
| enum { |
| @@ -114,6 +115,17 @@ static void uas_do_work(struct work_struct *work) |
| spin_unlock_irqrestore(&devinfo->lock, flags); |
| } |
| |
| +static void uas_scan_work(struct work_struct *work) |
| +{ |
| + struct uas_dev_info *devinfo = |
| + container_of(work, struct uas_dev_info, scan_work); |
| + struct Scsi_Host *shost = usb_get_intfdata(devinfo->intf); |
| + |
| + dev_dbg(&devinfo->intf->dev, "starting scan\n"); |
| + scsi_scan_host(shost); |
| + dev_dbg(&devinfo->intf->dev, "scan complete\n"); |
| +} |
| + |
| static void uas_add_work(struct uas_cmd_info *cmdinfo) |
| { |
| struct scsi_pointer *scp = (void *)cmdinfo; |
| @@ -983,6 +995,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) |
| init_usb_anchor(&devinfo->data_urbs); |
| spin_lock_init(&devinfo->lock); |
| INIT_WORK(&devinfo->work, uas_do_work); |
| + INIT_WORK(&devinfo->scan_work, uas_scan_work); |
| |
| result = uas_configure_endpoints(devinfo); |
| if (result) |
| @@ -999,7 +1012,9 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) |
| if (result) |
| goto free_streams; |
| |
| - scsi_scan_host(shost); |
| + /* Submit the delayed_work for SCSI-device scanning */ |
| + schedule_work(&devinfo->scan_work); |
| + |
| return result; |
| |
| free_streams: |
| @@ -1167,6 +1182,12 @@ static void uas_disconnect(struct usb_interface *intf) |
| usb_kill_anchored_urbs(&devinfo->data_urbs); |
| uas_zap_pending(devinfo, DID_NO_CONNECT); |
| |
| + /* |
| + * Prevent SCSI scanning (if it hasn't started yet) |
| + * or wait for the SCSI-scanning routine to stop. |
| + */ |
| + cancel_work_sync(&devinfo->scan_work); |
| + |
| scsi_remove_host(shost); |
| uas_free_streams(devinfo); |
| scsi_host_put(shost); |
| -- |
| 2.7.4 |
| |