aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorNicolas Saenz Julienne <nsaenzjulienne@suse.de>2019-01-08 10:45:22 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-01-18 03:58:04 -0500
commit8eb58994dd96da7055721c68e46b732febe671ae (patch)
treee55be1cee95361d7c56b765a2295b10e1530a2ca /drivers/usb/core
parentcb7edfd4cd47ed50ea618b660ee283a2d99edff2 (diff)
usb: hub: add retry routine after intr URB submit error
The hub sends hot-plug events to the host trough it's interrupt URB. The driver takes care of completing the URB and re-submitting it. Completion errors are handled in the hub_event() work, yet submission errors are ignored, rendering the device unresponsive. All further events are lost. It is fairly hard to find this issue in the wild, since you have to time the USB hot-plug event with the URB submission failure. For instance it could be the system running out of memory or some malfunction in the USB controller driver. Nevertheless, it's pretty reasonable to think it'll happen sometime. One can trigger this issue using eBPF's function override feature (see BCC's inject.py script). This patch adds a retry routine to the event of a submission error. The HUB driver will try to re-submit the URB once every second until it's successful or the HUB is disconnected. As some USB subsystems already take care of this issue, the implementation was inspired from usbhid/hid_core.c's. Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de> Reviewed-by: Oliver Neukum <oneukum@suse.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/hub.c43
-rw-r--r--drivers/usb/core/hub.h2
2 files changed, 39 insertions, 6 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 1d1e61e980f3..713ab85332f8 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -607,6 +607,36 @@ static int hub_port_status(struct usb_hub *hub, int port1,
607 status, change, NULL); 607 status, change, NULL);
608} 608}
609 609
610static void hub_resubmit_irq_urb(struct usb_hub *hub)
611{
612 unsigned long flags;
613 int status;
614
615 spin_lock_irqsave(&hub->irq_urb_lock, flags);
616
617 if (hub->quiescing) {
618 spin_unlock_irqrestore(&hub->irq_urb_lock, flags);
619 return;
620 }
621
622 status = usb_submit_urb(hub->urb, GFP_ATOMIC);
623 if (status && status != -ENODEV && status != -EPERM &&
624 status != -ESHUTDOWN) {
625 dev_err(hub->intfdev, "resubmit --> %d\n", status);
626 mod_timer(&hub->irq_urb_retry, jiffies + HZ);
627 }
628
629 spin_unlock_irqrestore(&hub->irq_urb_lock, flags);
630}
631
632static void hub_retry_irq_urb(struct timer_list *t)
633{
634 struct usb_hub *hub = from_timer(hub, t, irq_urb_retry);
635
636 hub_resubmit_irq_urb(hub);
637}
638
639
610static void kick_hub_wq(struct usb_hub *hub) 640static void kick_hub_wq(struct usb_hub *hub)
611{ 641{
612 struct usb_interface *intf; 642 struct usb_interface *intf;
@@ -709,12 +739,7 @@ static void hub_irq(struct urb *urb)
709 kick_hub_wq(hub); 739 kick_hub_wq(hub);
710 740
711resubmit: 741resubmit:
712 if (hub->quiescing) 742 hub_resubmit_irq_urb(hub);
713 return;
714
715 status = usb_submit_urb(hub->urb, GFP_ATOMIC);
716 if (status != 0 && status != -ENODEV && status != -EPERM)
717 dev_err(hub->intfdev, "resubmit --> %d\n", status);
718} 743}
719 744
720/* USB 2.0 spec Section 11.24.2.3 */ 745/* USB 2.0 spec Section 11.24.2.3 */
@@ -1264,10 +1289,13 @@ enum hub_quiescing_type {
1264static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type) 1289static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
1265{ 1290{
1266 struct usb_device *hdev = hub->hdev; 1291 struct usb_device *hdev = hub->hdev;
1292 unsigned long flags;
1267 int i; 1293 int i;
1268 1294
1269 /* hub_wq and related activity won't re-trigger */ 1295 /* hub_wq and related activity won't re-trigger */
1296 spin_lock_irqsave(&hub->irq_urb_lock, flags);
1270 hub->quiescing = 1; 1297 hub->quiescing = 1;
1298 spin_unlock_irqrestore(&hub->irq_urb_lock, flags);
1271 1299
1272 if (type != HUB_SUSPEND) { 1300 if (type != HUB_SUSPEND) {
1273 /* Disconnect all the children */ 1301 /* Disconnect all the children */
@@ -1278,6 +1306,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
1278 } 1306 }
1279 1307
1280 /* Stop hub_wq and related activity */ 1308 /* Stop hub_wq and related activity */
1309 del_timer_sync(&hub->irq_urb_retry);
1281 usb_kill_urb(hub->urb); 1310 usb_kill_urb(hub->urb);
1282 if (hub->has_indicators) 1311 if (hub->has_indicators)
1283 cancel_delayed_work_sync(&hub->leds); 1312 cancel_delayed_work_sync(&hub->leds);
@@ -1810,6 +1839,8 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
1810 INIT_DELAYED_WORK(&hub->leds, led_work); 1839 INIT_DELAYED_WORK(&hub->leds, led_work);
1811 INIT_DELAYED_WORK(&hub->init_work, NULL); 1840 INIT_DELAYED_WORK(&hub->init_work, NULL);
1812 INIT_WORK(&hub->events, hub_event); 1841 INIT_WORK(&hub->events, hub_event);
1842 spin_lock_init(&hub->irq_urb_lock);
1843 timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0);
1813 usb_get_intf(intf); 1844 usb_get_intf(intf);
1814 usb_get_dev(hdev); 1845 usb_get_dev(hdev);
1815 1846
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 4accfb63f7dc..a9e24e4b8df1 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -69,6 +69,8 @@ struct usb_hub {
69 struct delayed_work leds; 69 struct delayed_work leds;
70 struct delayed_work init_work; 70 struct delayed_work init_work;
71 struct work_struct events; 71 struct work_struct events;
72 spinlock_t irq_urb_lock;
73 struct timer_list irq_urb_retry;
72 struct usb_port **ports; 74 struct usb_port **ports;
73}; 75};
74 76