diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 56c082f34927..b4265aa7d45e 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1238,7 +1238,6 @@ static int proc_ioctl (struct dev_state *ps, void __user *arg) int retval = 0; struct usb_interface *intf = NULL; struct usb_driver *driver = NULL; - int i; /* get input parameters and alloc buffer */ if (copy_from_user(&ctrl, arg, sizeof (ctrl))) @@ -1270,15 +1269,6 @@ static int proc_ioctl (struct dev_state *ps, void __user *arg) /* disconnect kernel driver from interface */ case USBDEVFS_DISCONNECT: - /* don't allow the user to unbind the hub driver from - * a hub with children to manage */ - for (i = 0; i < ps->dev->maxchild; ++i) { - if (ps->dev->children[i]) - retval = -EBUSY; - } - if (retval) - break; - down_write(&usb_bus_type.subsys.rwsem); if (intf->dev.driver) { driver = to_usb_driver(intf->dev.driver); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 4a4b41f2665a..9f54e8330f78 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -729,10 +729,29 @@ fail: static unsigned highspeed_hubs; +/* Called after the hub driver is unbound from a hub with children */ +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 void hub_disconnect(struct usb_interface *intf) { struct usb_hub *hub = usb_get_intfdata (intf); struct usb_device *hdev; + int n, port1; usb_set_intfdata (intf, NULL); hdev = hub->hdev; @@ -760,8 +779,27 @@ static void hub_disconnect(struct usb_interface *intf) hub->buffer = NULL; } - /* Free the memory */ - kfree(hub); + /* If there are any children then this is an 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 now because we may not hold the hub's device lock. + */ + n = 0; + for (port1 = 1; port1 <= hdev->maxchild; ++port1) { + if (hdev->children[port1 - 1]) { + ++n; + hub_port_disable(hub, port1, 1); + } + } + + if (n == 0) + kfree(hub); + else { + /* Reuse the hub->leds work_struct for our own purposes */ + 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)