ChangeSet 1.1186, 2003/05/20 11:48:24-07:00, david-b@pacbell.net

[PATCH] USB: Fix machine lockup when unloading HC driver (part 2)

Alan Stern wrote:
> I suggest you just forget about acquiring the lock in status_dequeue() and
> simply leave it as
>
> 	del_timer_sync (&hcd->rh_timer);
> 	hcd->rh_timer.data = 0;

Hmm, so if some other URB gets queued in that window,
it'll get trashed?  Unlikely .. the clean fix would be
making the status endpoint have a real URB queue.

I combined your suggested change with two others:
(a) protect the status-unlink and control completion
    handlers against IRQs [ the cases Duncan noted]
(b) use mod_timer to retrigger the timer, instead of the
    heavy weight path.


diff -Nru a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
--- a/drivers/usb/core/hcd.c	Tue May 20 17:25:02 2003
+++ b/drivers/usb/core/hcd.c	Tue May 20 17:25:02 2003
@@ -324,6 +324,7 @@
 	const u8	*bufp = 0;
 	u8		*ubuf = urb->transfer_buffer;
 	int		len = 0;
+	unsigned long	flags;
 
 	typeReq  = (cmd->bRequestType << 8) | cmd->bRequest;
 	wValue   = le16_to_cpu (cmd->wValue);
@@ -436,7 +437,9 @@
 	}
 
 	/* any errors get returned through the urb completion */
+	local_irq_save (flags);
 	usb_hcd_giveback_urb (hcd, urb, NULL);
+	local_irq_restore (flags);
 	return 0;
 }
 
@@ -500,13 +503,13 @@
 
 	/* complete the status urb, or retrigger the timer */
 	spin_lock (&hcd_data_lock);
-	hcd->rh_timer.data = 0;
 	if (length > 0) {
+		hcd->rh_timer.data = 0;
 		urb->actual_length = length;
 		urb->status = 0;
 		urb->hcpriv = 0;
 	} else
-		rh_status_urb (hcd, urb);
+		mod_timer (&hcd->rh_timer, jiffies + HZ/4);
 	spin_unlock (&hcd_data_lock);
 	spin_unlock (&urb->lock);
 
@@ -541,15 +544,14 @@
 {
 	unsigned long	flags;
 
-	spin_lock_irqsave (&hcd_data_lock, flags);
-	hcd->rh_timer.data = 0;
-	spin_unlock_irqrestore (&hcd_data_lock, flags);
-
 	/* note:  always a synchronous unlink */
 	del_timer_sync (&hcd->rh_timer);
+	hcd->rh_timer.data = 0;
 
+	local_irq_save (flags);
 	urb->hcpriv = 0;
 	usb_hcd_giveback_urb (hcd, urb, NULL);
+	local_irq_restore (flags);
 }
 
 /*-------------------------------------------------------------------------*/
