[PATCH 5b/6] USB: make hub driver's release more robust

Alan Stern <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[email protected]>
This revised patch (as893b) improves the method used by the hub driver to
release its private data structure.  The current code is non-robust,
relying on a memory region not getting reused by another driver after
it has been freed.

Signed-off-by: Alan Stern <[email protected]>

---

It turned out that making khubd responsible doing the final usb_put_intf() 
wasn't such a good idea.  It caused a problem with dummy_hcd.  Doing 
things this way should be better; now all device references held by the 
hub driver will be dropped before hub_disconnect() returns.


Index: usb-2.6/drivers/usb/core/hub.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/hub.c
+++ usb-2.6/drivers/usb/core/hub.c
@@ -49,6 +49,9 @@ struct usb_hub {
 	int			error;		/* last reported error */
 	int			nerrors;	/* track consecutive errors */
 
+	struct completion	*finish;	/* signal that khubd is done
+							using this hub */
+
 	struct list_head	event_list;	/* hubs w/data or errs ready */
 	unsigned long		event_bits[1];	/* status change bitmask */
 	unsigned long		change_bits[1];	/* ports with logical connect
@@ -67,12 +70,15 @@ struct usb_hub {
 	unsigned		limited_power:1;
 	unsigned		quiescing:1;
 	unsigned		activating:1;
+	unsigned		disconnected:1;
 
 	unsigned		has_indicators:1;
 	u8			indicator[USB_MAXCHILDREN];
 	struct delayed_work	leds;
 };
 
+/* Sentinel value for hub->finish to indicate khubd is using the hub */
+#define	HUB_IN_USE(hub)		((struct completion *) (hub))
 
 /* Protect struct usb_device->state and ->children members
  * Note: Both are also protected by ->dev.sem, except that ->state can
@@ -324,7 +330,7 @@ static void kick_khubd(struct usb_hub *h
 	to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
 
 	spin_lock_irqsave(&hub_event_lock, flags);
-	if (list_empty(&hub->event_list)) {
+	if (!hub->disconnected & list_empty(&hub->event_list)) {
 		list_add_tail(&hub->event_list, &hub_event_list);
 		wake_up(&khubd_wait);
 	}
@@ -333,6 +339,7 @@ static void kick_khubd(struct usb_hub *h
 
 void usb_kick_khubd(struct usb_device *hdev)
 {
+	/* FIXME: What if hdev isn't bound to the hub driver? */
 	kick_khubd(hdev_to_hub(hdev));
 }
 
@@ -931,6 +938,21 @@ static void hub_disconnect(struct usb_in
 {
 	struct usb_hub *hub = usb_get_intfdata (intf);
 	struct usb_device *hdev;
+	struct completion wait_for_khubd;
+
+	/* Take the hub off the event list */
+	spin_lock_irq(&hub_event_lock);
+	hub->disconnected = 1;
+	list_del_init(&hub->event_list);
+
+	/* Wait for khubd to finish using the hub */
+	if (hub->finish == HUB_IN_USE(hub)) {
+		hub->finish = &wait_for_khubd;
+		init_completion(&wait_for_khubd);
+	}
+	spin_unlock_irq(&hub_event_lock);
+	if (hub->finish)
+		wait_for_completion(&wait_for_khubd);
 
 	/* Disconnect all children and quiesce the hub */
 	hub->error = 0;
@@ -945,10 +967,6 @@ static void hub_disconnect(struct usb_in
 	usb_free_urb(hub->urb);
 	hub->urb = NULL;
 
-	spin_lock_irq(&hub_event_lock);
-	list_del_init(&hub->event_list);
-	spin_unlock_irq(&hub_event_lock);
-
 	kfree(hub->descriptor);
 	hub->descriptor = NULL;
 
@@ -2600,19 +2618,19 @@ static void hub_events(void)
 	 * safe since we delete the hub from the event list.
 	 * Not the most efficient, but avoids deadlocks.
 	 */
+	spin_lock_irq(&hub_event_lock);
 	while (1) {
 
 		/* Grab the first entry at the beginning of the list */
-		spin_lock_irq(&hub_event_lock);
-		if (list_empty(&hub_event_list)) {
-			spin_unlock_irq(&hub_event_lock);
+		if (list_empty(&hub_event_list))
 			break;
-		}
-
 		tmp = hub_event_list.next;
 		list_del_init(tmp);
 
 		hub = list_entry(tmp, struct usb_hub, event_list);
+		hub->finish = HUB_IN_USE(hub);
+		spin_unlock_irq(&hub_event_lock);
+
 		hdev = hub->hdev;
 		intf = to_usb_interface(hub->intfdev);
 		hub_dev = &intf->dev;
@@ -2625,13 +2643,10 @@ static void hub_events(void)
 				(u16) hub->change_bits[0],
 				(u16) hub->event_bits[0]);
 
-		usb_get_intf(intf);
-		spin_unlock_irq(&hub_event_lock);
-
 		/* Lock the device, then check to see if we were
 		 * disconnected while waiting for the lock to succeed. */
 		usb_lock_device(hdev);
-		if (hub != usb_get_intfdata(intf))
+		if (unlikely(hub->disconnected))
 			goto loop;
 
 		/* If the hub has died, clean up after it */
@@ -2794,9 +2809,17 @@ loop_autopm:
 			usb_autopm_enable(intf);
 loop:
 		usb_unlock_device(hdev);
-		usb_put_intf(intf);
 
+		/* If hub_disconnect() is waiting for us to finish using
+		 * the hub, tell it we are now done.
+		 */
+		spin_lock_irq(&hub_event_lock);
+		if (hub->finish == HUB_IN_USE(hub))
+			hub->finish = NULL;
+		else
+			complete(hub->finish);
         } /* end while (1) */
+	spin_unlock_irq(&hub_event_lock);
 }
 
 static int hub_thread(void *__unused)


-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.