Skip to content

Commit

Permalink
[PATCH] USB: Disconnect children when unbinding the hub driver
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alan Stern authored and Greg Kroah-Hartman committed Sep 8, 2005
1 parent 8b28c75 commit bf193d3
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
10 changes: 0 additions & 10 deletions drivers/usb/core/devio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down Expand Up @@ -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);
Expand Down
42 changes: 40 additions & 2 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -729,10 +729,29 @@ static int hub_configure(struct usb_hub *hub,

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;
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit bf193d3

Please sign in to comment.