Commit dc61d238 authored by Sergei Shtylyov's avatar Sergei Shtylyov Committed by Greg Kroah-Hartman

USB: musb: host endpoint_disable() oops fixes

The musb_h_disable() routine can oops in some cases:

 - It's not safe to read hep->hcpriv outside musb->lock,
   since it gets changed on completion IRQ paths.

 - The list iterators aren't safe to use in that way;
   just remove the first element while !list_empty(),
   so deletions on other code paths can't make trouble.

We need two "scrub the list" loops because only one branch
should touch hardware and advance the schedule.

[ dbrownell@users.sourceforge.net: massively simplify
  patch description; add key points as code comments ]
Signed-off-by: default avatarSergei Shtylyov <sshtylyov@ru.mvista.com>
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent a2fd814e
...@@ -2103,15 +2103,16 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) ...@@ -2103,15 +2103,16 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep)
unsigned long flags; unsigned long flags;
struct musb *musb = hcd_to_musb(hcd); struct musb *musb = hcd_to_musb(hcd);
u8 is_in = epnum & USB_DIR_IN; u8 is_in = epnum & USB_DIR_IN;
struct musb_qh *qh = hep->hcpriv; struct musb_qh *qh;
struct urb *urb, *tmp; struct urb *urb;
struct list_head *sched; struct list_head *sched;
if (!qh)
return;
spin_lock_irqsave(&musb->lock, flags); spin_lock_irqsave(&musb->lock, flags);
qh = hep->hcpriv;
if (qh == NULL)
goto exit;
switch (qh->type) { switch (qh->type) {
case USB_ENDPOINT_XFER_CONTROL: case USB_ENDPOINT_XFER_CONTROL:
sched = &musb->control; sched = &musb->control;
...@@ -2145,13 +2146,28 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) ...@@ -2145,13 +2146,28 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep)
/* cleanup */ /* cleanup */
musb_cleanup_urb(urb, qh, urb->pipe & USB_DIR_IN); musb_cleanup_urb(urb, qh, urb->pipe & USB_DIR_IN);
} else
urb = NULL;
/* then just nuke all the others */ /* Then nuke all the others ... and advance the
list_for_each_entry_safe_from(urb, tmp, &hep->urb_list, urb_list) * queue on hw_ep (e.g. bulk ring) when we're done.
musb_giveback(qh, urb, -ESHUTDOWN); */
while (!list_empty(&hep->urb_list)) {
urb = next_urb(qh);
urb->status = -ESHUTDOWN;
musb_advance_schedule(musb, urb, qh->hw_ep, is_in);
}
} else {
/* Just empty the queue; the hardware is busy with
* other transfers, and since !qh->is_ready nothing
* will activate any of these as it advances.
*/
while (!list_empty(&hep->urb_list))
__musb_giveback(musb, next_urb(qh), -ESHUTDOWN);
hep->hcpriv = NULL;
list_del(&qh->ring);
kfree(qh);
}
exit:
spin_unlock_irqrestore(&musb->lock, flags); spin_unlock_irqrestore(&musb->lock, flags);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment