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>
This commit is contained in:
Nicolas Saenz Julienne 2019-01-08 16:45:22 +01:00 committed by Greg Kroah-Hartman
parent cb7edfd4cd
commit 8eb58994dd
2 changed files with 39 additions and 6 deletions

View File

@ -607,6 +607,36 @@ static int hub_port_status(struct usb_hub *hub, int port1,
status, change, NULL); status, change, NULL);
} }
static void hub_resubmit_irq_urb(struct usb_hub *hub)
{
unsigned long flags;
int status;
spin_lock_irqsave(&hub->irq_urb_lock, flags);
if (hub->quiescing) {
spin_unlock_irqrestore(&hub->irq_urb_lock, flags);
return;
}
status = usb_submit_urb(hub->urb, GFP_ATOMIC);
if (status && status != -ENODEV && status != -EPERM &&
status != -ESHUTDOWN) {
dev_err(hub->intfdev, "resubmit --> %d\n", status);
mod_timer(&hub->irq_urb_retry, jiffies + HZ);
}
spin_unlock_irqrestore(&hub->irq_urb_lock, flags);
}
static void hub_retry_irq_urb(struct timer_list *t)
{
struct usb_hub *hub = from_timer(hub, t, irq_urb_retry);
hub_resubmit_irq_urb(hub);
}
static void kick_hub_wq(struct usb_hub *hub) static void kick_hub_wq(struct usb_hub *hub)
{ {
struct usb_interface *intf; struct usb_interface *intf;
@ -709,12 +739,7 @@ static void hub_irq(struct urb *urb)
kick_hub_wq(hub); kick_hub_wq(hub);
resubmit: resubmit:
if (hub->quiescing) hub_resubmit_irq_urb(hub);
return;
status = usb_submit_urb(hub->urb, GFP_ATOMIC);
if (status != 0 && status != -ENODEV && status != -EPERM)
dev_err(hub->intfdev, "resubmit --> %d\n", status);
} }
/* USB 2.0 spec Section 11.24.2.3 */ /* USB 2.0 spec Section 11.24.2.3 */
@ -1264,10 +1289,13 @@ enum hub_quiescing_type {
static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type) static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
{ {
struct usb_device *hdev = hub->hdev; struct usb_device *hdev = hub->hdev;
unsigned long flags;
int i; int i;
/* hub_wq and related activity won't re-trigger */ /* hub_wq and related activity won't re-trigger */
spin_lock_irqsave(&hub->irq_urb_lock, flags);
hub->quiescing = 1; hub->quiescing = 1;
spin_unlock_irqrestore(&hub->irq_urb_lock, flags);
if (type != HUB_SUSPEND) { if (type != HUB_SUSPEND) {
/* Disconnect all the children */ /* Disconnect all the children */
@ -1278,6 +1306,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
} }
/* Stop hub_wq and related activity */ /* Stop hub_wq and related activity */
del_timer_sync(&hub->irq_urb_retry);
usb_kill_urb(hub->urb); usb_kill_urb(hub->urb);
if (hub->has_indicators) if (hub->has_indicators)
cancel_delayed_work_sync(&hub->leds); cancel_delayed_work_sync(&hub->leds);
@ -1810,6 +1839,8 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
INIT_DELAYED_WORK(&hub->leds, led_work); INIT_DELAYED_WORK(&hub->leds, led_work);
INIT_DELAYED_WORK(&hub->init_work, NULL); INIT_DELAYED_WORK(&hub->init_work, NULL);
INIT_WORK(&hub->events, hub_event); INIT_WORK(&hub->events, hub_event);
spin_lock_init(&hub->irq_urb_lock);
timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0);
usb_get_intf(intf); usb_get_intf(intf);
usb_get_dev(hdev); usb_get_dev(hdev);

View File

@ -69,6 +69,8 @@ struct usb_hub {
struct delayed_work leds; struct delayed_work leds;
struct delayed_work init_work; struct delayed_work init_work;
struct work_struct events; struct work_struct events;
spinlock_t irq_urb_lock;
struct timer_list irq_urb_retry;
struct usb_port **ports; struct usb_port **ports;
}; };