aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2005-08-10 17:12:31 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2005-09-08 16:28:19 -0700
commitbf193d3cd2a3b73f2df74f57106114867946c09c (patch)
treecc47350df18dd7bf00a3adb136476e1896f20c19
parent8b28c7526a302bbfa618f7eab4ef961edd68c9a0 (diff)
[PATCH] USB: Disconnect children when unbinding the hub driver
This patch (as554) makes the hub driver disconnect any child USB devices when it is unbound from a hub. Normally this will never happen, but there are a few oddball ways to unbind the hub driver while leaving the children intact. For example, the new "unbind" sysfs attribute can be used for this purpose. Given that unbinding hubs with children is now safe, the patch also removes the code that prevented people from doing so using usbfs. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/core/devio.c10
-rw-r--r--drivers/usb/core/hub.c42
2 files changed, 40 insertions, 12 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index 56c082f3492..b4265aa7d45 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 4a4b41f2665..9f54e8330f7 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)