Commit 9b5310c6 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] USB: Remove hub's children upon unbinding

This patch fixes a logical hole in the hub driver.  It's possible for the
driver to be unbound from a hub without physically unplugging the hub.
For example, writing 0 into the bConfigurationValue attribute file will
have this effect.  When this happens, we need to make sure that all the
child devices of the hub are logically disconnected and their ports
disabled.

That's what this patch does.  It's a little bit tricky because we can't
simply call usb_disconnect() from within the hub driver's disconnect()
routine.  While that routine is running it holds the usb bus writelock,
but usb_disconnect() would try to acquire it again.  Instead
schedule_work() is used, so after a brief delay the children will be
removed.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent bf02cab8
...@@ -53,6 +53,8 @@ static int blinkenlights = 0; ...@@ -53,6 +53,8 @@ static int blinkenlights = 0;
module_param (blinkenlights, bool, S_IRUGO); module_param (blinkenlights, bool, S_IRUGO);
MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs"); MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs");
static int hub_port_disable(struct usb_device *hdev, int port);
#ifdef DEBUG #ifdef DEBUG
static inline char *portspeed (int portstatus) static inline char *portspeed (int portstatus)
...@@ -621,12 +623,30 @@ static int hub_configure(struct usb_hub *hub, ...@@ -621,12 +623,30 @@ static int hub_configure(struct usb_hub *hub,
return ret; return ret;
} }
static void hub_remove_children_work(void *__hub)
{
struct usb_hub *hub = __hub;
struct usb_device *hdev = hub->hdev;
int i;
kfree(hub);
usb_lock_device(hdev);
for (i = 0; i < hdev->maxchild; ++i) {
if (hdev->children[i])
usb_disconnect(&hdev->children[i]);
}
usb_unlock_device(hdev);
usb_put_dev(hdev);
}
static unsigned highspeed_hubs; static unsigned highspeed_hubs;
static void hub_disconnect(struct usb_interface *intf) static void hub_disconnect(struct usb_interface *intf)
{ {
struct usb_hub *hub = usb_get_intfdata (intf); struct usb_hub *hub = usb_get_intfdata (intf);
struct usb_device *hdev; struct usb_device *hdev;
int i, n;
if (!hub) if (!hub)
return; return;
...@@ -669,8 +689,27 @@ static void hub_disconnect(struct usb_interface *intf) ...@@ -669,8 +689,27 @@ static void hub_disconnect(struct usb_interface *intf)
hub->buffer = NULL; hub->buffer = NULL;
} }
/* Free the memory */ /* If there are any children then this is unbind only, not a
* physical disconnection. The active ports must be disabled
* and later on we must call usb_disconnect(). We can't call
* it here because we already own the usb bus writelock.
*/
n = 0;
for (i = 0; i < hdev->maxchild; ++i) {
if (hdev->children[i]) {
++n;
hub_port_disable(hdev, i);
}
}
if (n == 0)
kfree(hub); kfree(hub);
else {
/* Reuse hub->leds to disconnect the children */
INIT_WORK(&hub->leds, hub_remove_children_work, hub);
schedule_work(&hub->leds);
usb_get_dev(hdev);
}
} }
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
......
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