ChangeSet 1.1504.2.10, 2003/12/08 17:08:03-08:00, stern@rowland.harvard.edu

[PATCH] USB: Fix khubd synchronization

It improves synchronization with hub_irq() and guarantees that the hub
disconnect() routine doesn't exit until the URB's completion routine has
finished.


 drivers/usb/core/hub.c |   22 ++++++++++++++++++----
 drivers/usb/core/hub.h |    2 ++
 2 files changed, 20 insertions(+), 4 deletions(-)


diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c	Mon Dec 29 14:25:54 2003
+++ b/drivers/usb/core/hub.c	Mon Dec 29 14:25:54 2003
@@ -128,11 +128,18 @@
 	struct usb_hub *hub = (struct usb_hub *)urb->context;
 	int status;
 
+	spin_lock(&hub_event_lock);
+	hub->urb_active = 0;
+	if (hub->urb_complete) {	/* disconnect or rmmod */
+		complete(hub->urb_complete);
+		goto done;
+	}
+
 	switch (urb->status) {
 	case -ENOENT:		/* synchronous unlink */
 	case -ECONNRESET:	/* async unlink */
 	case -ESHUTDOWN:	/* hardware going away */
-		return;
+		goto done;
 
 	default:		/* presumably an error */
 		/* Cause a hub reset after 10 consecutive errors */
@@ -150,18 +157,20 @@
 	hub->nerrors = 0;
 
 	/* Something happened, let khubd figure it out */
-	spin_lock(&hub_event_lock);
 	if (list_empty(&hub->event_list)) {
 		list_add(&hub->event_list, &hub_event_list);
 		wake_up(&khubd_wait);
 	}
-	spin_unlock(&hub_event_lock);
 
 resubmit:
 	if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
 			/* ENODEV means we raced disconnect() */
 			&& status != -ENODEV)
 		dev_err (&hub->intf->dev, "resubmit --> %d\n", urb->status);
+	if (status == 0)
+		hub->urb_active = 1;
+done:
+	spin_unlock(&hub_event_lock);
 }
 
 /* USB 2.0 spec Section 11.24.2.3 */
@@ -466,7 +475,8 @@
 		message = "couldn't submit status urb";
 		goto fail;
 	}
-		
+	hub->urb_active = 1;
+
 	/* Wake up khubd */
 	wake_up(&khubd_wait);
 
@@ -484,6 +494,7 @@
 static void hub_disconnect(struct usb_interface *intf)
 {
 	struct usb_hub *hub = usb_get_intfdata (intf);
+	DECLARE_COMPLETION(urb_complete);
 	unsigned long flags;
 
 	if (!hub)
@@ -491,6 +502,7 @@
 
 	usb_set_intfdata (intf, NULL);
 	spin_lock_irqsave(&hub_event_lock, flags);
+	hub->urb_complete = &urb_complete;
 
 	/* Delete it and then reset it */
 	list_del_init(&hub->event_list);
@@ -507,6 +519,8 @@
 
 	if (hub->urb) {
 		usb_unlink_urb(hub->urb);
+		if (hub->urb_active)
+			wait_for_completion(&urb_complete);
 		usb_free_urb(hub->urb);
 		hub->urb = NULL;
 	}
diff -Nru a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
--- a/drivers/usb/core/hub.h	Mon Dec 29 14:25:54 2003
+++ b/drivers/usb/core/hub.h	Mon Dec 29 14:25:54 2003
@@ -172,6 +172,8 @@
 struct usb_hub {
 	struct usb_interface	*intf;		/* the "real" device */
 	struct urb		*urb;		/* for interrupt polling pipe */
+	struct completion	*urb_complete;	/* wait for urb to end */
+	unsigned int		urb_active:1;
 
 	/* buffer for urb ... 1 bit each for hub and children, rounded up */
 	char			(*buffer)[(USB_MAXCHILDREN + 1 + 7) / 8];
