[PATCH 1/3] musb_host: fix oops in endpoint_disable() method (take 3)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The end of musb_h_disable() is totally broken: list_for_each_entry_safe_from()
will cause a kernel oops given a NULL pointer, and otherwise it's illegal to
call musb_giveback() on the first URB in the list as musb_cleanup_urb() will
have already done that (which will unlink it from the URB list and thus e. g.
cause the iterator to loop on this URB forever).  The list iterator isn't safe
to use either as URBs may be explicitly dequeued by the upper layers while it's
running.

As if that was not enough, musb_giveback() isn't safe to call from here either:

- when we're disabling an endpoint with an URB being currently active, once
  the URB list empties, we must immediately switch to serving next endpoint
  represented by 'struct musb_qh', or risk the schedule being staleld (thus,
  musb_advance_schedule() should've been called intead);

- when we're disabling an endpoint not being currently active on the hardware,
  musb_giveback() will clobber the 'in_qh'/'out_qh' pointers in the 'struct
  musb_hw_ep' pointing to the active endpoint's 'struct musb_qh' which will
  result in a kernel oops (thus, __musb_giveback() should've been called).

Finally, it doesn't seem to be safe to read 'hep->hcpriv' before taking hold
of 'musb->lock'... phew. :-)

Signed-off-by: Sergei Shtylyov <sshtylyov@xxxxxxxxxxxxx>

---
This patch, with the description rewritten and comments added, is intended to
replace usb/usb-musb_host-fix-endpoint_disable-method.patch in Greg's series...

 drivers/usb/musb/musb_host.c |   38 ++++++++++++++++++++++++++++----------
 1 files changed, 28 insertions(+), 10 deletions(-)

Index: linux-2.6/drivers/usb/musb/musb_host.c
===================================================================
--- linux-2.6.orig/drivers/usb/musb/musb_host.c
+++ linux-2.6/drivers/usb/musb/musb_host.c
@@ -2094,15 +2094,16 @@ musb_h_disable(struct usb_hcd *hcd, stru
 	unsigned long		flags;
 	struct musb		*musb = hcd_to_musb(hcd);
 	u8			is_in = epnum & USB_DIR_IN;
-	struct musb_qh		*qh = hep->hcpriv;
-	struct urb		*urb, *tmp;
+	struct musb_qh		*qh;
+	struct urb		*urb;
 	struct list_head	*sched;
 
-	if (!qh)
-		return;
-
 	spin_lock_irqsave(&musb->lock, flags);
 
+	qh = hep->hcpriv;
+	if (qh == NULL)
+		goto exit;
+
 	switch (qh->type) {
 	case USB_ENDPOINT_XFER_CONTROL:
 		sched = &musb->control;
@@ -2136,13 +2137,30 @@ musb_h_disable(struct usb_hcd *hcd, stru
 
 		/* cleanup */
 		musb_cleanup_urb(urb, qh, urb->pipe & USB_DIR_IN);
-	} else
-		urb = NULL;
 
-	/* then just nuke all the others */
-	list_for_each_entry_safe_from(urb, tmp, &hep->urb_list, urb_list)
-		musb_giveback(qh, urb, -ESHUTDOWN);
+		/*
+		 * Then just nuke all the other URBs and advance
+		 * the schedule on this hw_ep when we're done.
+		 */
+		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 URB list; the hardware is busy with
+		 * other transfers, and since !qh->is_ready nothing
+		 * will activate any of these URBs 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);
 }
 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux