| From 2b7b0f1165481eba73ea835ea1a5ce1ae4ac2474 Mon Sep 17 00:00:00 2001 |
| From: Alan Stern <stern@rowland.harvard.edu> |
| Date: Tue, 16 Apr 2019 10:50:01 -0400 |
| Subject: USB: core: Don't unbind interfaces following device reset failure |
| |
| [ Upstream commit 381419fa720060ba48b7bbc483be787d5b1dca6f ] |
| |
| The SCSI core does not like to have devices or hosts unregistered |
| while error recovery is in progress. Trying to do so can lead to |
| self-deadlock: Part of the removal code tries to obtain a lock already |
| held by the error handler. |
| |
| This can cause problems for the usb-storage and uas drivers, because |
| their error handler routines perform a USB reset, and if the reset |
| fails then the USB core automatically goes on to unbind all drivers |
| from the device's interfaces -- all while still in the context of the |
| SCSI error handler. |
| |
| As it turns out, practically all the scenarios leading to a USB reset |
| failure end up causing a device disconnect (the main error pathway in |
| usb_reset_and_verify_device(), at the end of the routine, calls |
| hub_port_logical_disconnect() before returning). As a result, the |
| hub_wq thread will soon become aware of the problem and will unbind |
| all the device's drivers in its own context, not in the |
| error-handler's context. |
| |
| This means that usb_reset_device() does not need to call |
| usb_unbind_and_rebind_marked_interfaces() in cases where |
| usb_reset_and_verify_device() has returned an error, because hub_wq |
| will take care of everything anyway. |
| |
| This particular problem was observed in somewhat artificial |
| circumstances, by using usbfs to tell a hub to power-down a port |
| connected to a USB-3 mass storage device using the UAS protocol. With |
| the port turned off, the currently executing command timed out and the |
| error handler started running. The USB reset naturally failed, |
| because the hub port was off, and the error handler deadlocked as |
| described above. Not carrying out the call to |
| usb_unbind_and_rebind_marked_interfaces() fixes this issue. |
| |
| Signed-off-by: Alan Stern <stern@rowland.harvard.edu> |
| Reported-by: Kento Kobayashi <Kento.A.Kobayashi@sony.com> |
| Tested-by: Kento Kobayashi <Kento.A.Kobayashi@sony.com> |
| CC: Bart Van Assche <bvanassche@acm.org> |
| CC: Martin K. Petersen <martin.petersen@oracle.com> |
| CC: Jacky Cao <Jacky.Cao@sony.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/usb/core/hub.c | 5 ++++- |
| 1 file changed, 4 insertions(+), 1 deletion(-) |
| |
| diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c |
| index 8d4631c81b9f0..310eef451db82 100644 |
| --- a/drivers/usb/core/hub.c |
| +++ b/drivers/usb/core/hub.c |
| @@ -5902,7 +5902,10 @@ int usb_reset_device(struct usb_device *udev) |
| cintf->needs_binding = 1; |
| } |
| } |
| - usb_unbind_and_rebind_marked_interfaces(udev); |
| + |
| + /* If the reset failed, hub_wq will unbind drivers later */ |
| + if (ret == 0) |
| + usb_unbind_and_rebind_marked_interfaces(udev); |
| } |
| |
| usb_autosuspend_device(udev); |
| -- |
| 2.20.1 |
| |