diff options
author | Guenter Roeck <linux@roeck-us.net> | 2017-03-20 14:16:11 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-03-23 03:13:22 -0400 |
commit | 245b2eecee2aac6fdc77dcafaa73c33f9644c3c7 (patch) | |
tree | d86a86789f729ef97034a64f88d67a384a9e9ce8 | |
parent | 21a60f6e65181cad64fd66ccc8080d413721ba27 (diff) |
usb: hub: Fix error loop seen after hub communication errors
While stress testing a usb controller using a bind/unbind looop, the
following error loop was observed.
usb 7-1.2: new low-speed USB device number 3 using xhci-hcd
usb 7-1.2: hub failed to enable device, error -108
usb 7-1-port2: cannot disable (err = -22)
usb 7-1-port2: couldn't allocate usb_device
usb 7-1-port2: cannot disable (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: activate --> -22
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
hub 7-1:1.0: hub_ext_port_status failed (err = -22)
** 57 printk messages dropped ** hub 7-1:1.0: activate --> -22
** 82 printk messages dropped ** hub 7-1:1.0: hub_ext_port_status failed (err = -22)
This continues forever. After adding tracebacks into the code,
the call sequence leading to this is found to be as follows.
[<ffffffc0007fc8e0>] hub_activate+0x368/0x7b8
[<ffffffc0007fceb4>] hub_resume+0x2c/0x3c
[<ffffffc00080b3b8>] usb_resume_interface.isra.6+0x128/0x158
[<ffffffc00080b5d0>] usb_suspend_both+0x1e8/0x288
[<ffffffc00080c9c4>] usb_runtime_suspend+0x3c/0x98
[<ffffffc0007820a0>] __rpm_callback+0x48/0x7c
[<ffffffc00078217c>] rpm_callback+0xa8/0xd4
[<ffffffc000786234>] rpm_suspend+0x84/0x758
[<ffffffc000786ca4>] rpm_idle+0x2c8/0x498
[<ffffffc000786ed4>] __pm_runtime_idle+0x60/0xac
[<ffffffc00080eba8>] usb_autopm_put_interface+0x6c/0x7c
[<ffffffc000803798>] hub_event+0x10ac/0x12ac
[<ffffffc000249bb8>] process_one_work+0x390/0x6b8
[<ffffffc00024abcc>] worker_thread+0x480/0x610
[<ffffffc000251a80>] kthread+0x164/0x178
[<ffffffc0002045d0>] ret_from_fork+0x10/0x40
kick_hub_wq() is called from hub_activate() even after failures to
communicate with the hub. This results in an endless sequence of
hub event -> hub activate -> wq trigger -> hub event -> ...
Provide two solutions for the problem.
- Only trigger the hub event queue if communication with the hub
is successful.
- After a suspend failure, only resume already suspended interfaces
if the communication with the device is still possible.
Each of the changes fixes the observed problem. Use both to improve
robustness.
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/core/driver.c | 18 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 5 |
2 files changed, 22 insertions, 1 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index cdee5130638b..7ebdf2a4e8fe 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c | |||
@@ -1331,6 +1331,24 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) | |||
1331 | */ | 1331 | */ |
1332 | if (udev->parent && !PMSG_IS_AUTO(msg)) | 1332 | if (udev->parent && !PMSG_IS_AUTO(msg)) |
1333 | status = 0; | 1333 | status = 0; |
1334 | |||
1335 | /* | ||
1336 | * If the device is inaccessible, don't try to resume | ||
1337 | * suspended interfaces and just return the error. | ||
1338 | */ | ||
1339 | if (status && status != -EBUSY) { | ||
1340 | int err; | ||
1341 | u16 devstat; | ||
1342 | |||
1343 | err = usb_get_status(udev, USB_RECIP_DEVICE, 0, | ||
1344 | &devstat); | ||
1345 | if (err) { | ||
1346 | dev_err(&udev->dev, | ||
1347 | "Failed to suspend device, error %d\n", | ||
1348 | status); | ||
1349 | goto done; | ||
1350 | } | ||
1351 | } | ||
1334 | } | 1352 | } |
1335 | 1353 | ||
1336 | /* If the suspend failed, resume interfaces that did get suspended */ | 1354 | /* If the suspend failed, resume interfaces that did get suspended */ |
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index f0dd08198d74..735e8ab2cc1f 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -1066,6 +1066,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) | |||
1066 | 1066 | ||
1067 | portstatus = portchange = 0; | 1067 | portstatus = portchange = 0; |
1068 | status = hub_port_status(hub, port1, &portstatus, &portchange); | 1068 | status = hub_port_status(hub, port1, &portstatus, &portchange); |
1069 | if (status) | ||
1070 | goto abort; | ||
1071 | |||
1069 | if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) | 1072 | if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) |
1070 | dev_dbg(&port_dev->dev, "status %04x change %04x\n", | 1073 | dev_dbg(&port_dev->dev, "status %04x change %04x\n", |
1071 | portstatus, portchange); | 1074 | portstatus, portchange); |
@@ -1198,7 +1201,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) | |||
1198 | 1201 | ||
1199 | /* Scan all ports that need attention */ | 1202 | /* Scan all ports that need attention */ |
1200 | kick_hub_wq(hub); | 1203 | kick_hub_wq(hub); |
1201 | 1204 | abort: | |
1202 | if (type == HUB_INIT2 || type == HUB_INIT3) { | 1205 | if (type == HUB_INIT2 || type == HUB_INIT3) { |
1203 | /* Allow autosuspend if it was suppressed */ | 1206 | /* Allow autosuspend if it was suppressed */ |
1204 | disconnected: | 1207 | disconnected: |