[PATCH] USB: Disconnect children during hub unbind

This patch (as606b) is an updated version of my earlier patch to
disconnect children from a hub device when the hub driver is unbound.
Thanks to the changes in the driver core locking, we now know that the
entire hub device (and not just the interface) is locked whenever the
hub driver's disconnect method runs.  Hence it is safe to disconnect the
child device structures immediately instead of deferring the job.

The earlier version of the patch neglected to disable the hub's ports.
We don't want to forget that; otherwise we'd end up with live devices
using addresses that have been recycled.  This update adds the necessary
code.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Alan Stern 2005-11-18 12:06:34 -05:00 committed by Greg Kroah-Hartman
parent 9ad3d6ccf5
commit 7d069b7d80
1 changed files with 31 additions and 63 deletions

View File

@ -515,6 +515,31 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
return ret; return ret;
} }
/* caller has locked the hub device */
static void hub_pre_reset(struct usb_hub *hub, int disable_ports)
{
struct usb_device *hdev = hub->hdev;
int port1;
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
if (hdev->children[port1 - 1]) {
usb_disconnect(&hdev->children[port1 - 1]);
if (disable_ports)
hub_port_disable(hub, port1, 0);
}
}
hub_quiesce(hub);
}
/* caller has locked the hub device */
static void hub_post_reset(struct usb_hub *hub)
{
hub_activate(hub);
hub_power_on(hub);
}
static int hub_configure(struct usb_hub *hub, static int hub_configure(struct usb_hub *hub,
struct usb_endpoint_descriptor *endpoint) struct usb_endpoint_descriptor *endpoint)
{ {
@ -750,29 +775,10 @@ fail:
static unsigned highspeed_hubs; 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) 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 n, port1;
usb_set_intfdata (intf, NULL); usb_set_intfdata (intf, NULL);
hdev = hub->hdev; hdev = hub->hdev;
@ -780,7 +786,9 @@ static void hub_disconnect(struct usb_interface *intf)
if (hdev->speed == USB_SPEED_HIGH) if (hdev->speed == USB_SPEED_HIGH)
highspeed_hubs--; highspeed_hubs--;
hub_quiesce(hub); /* Disconnect all children and quiesce the hub */
hub_pre_reset(hub, 1);
usb_free_urb(hub->urb); usb_free_urb(hub->urb);
hub->urb = NULL; hub->urb = NULL;
@ -800,27 +808,7 @@ static void hub_disconnect(struct usb_interface *intf)
hub->buffer = NULL; hub->buffer = NULL;
} }
/* If there are any children then this is an unbind only, not a kfree(hub);
* 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) static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
@ -917,26 +905,6 @@ hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data)
} }
} }
/* caller has locked the hub device */
static void hub_pre_reset(struct usb_hub *hub)
{
struct usb_device *hdev = hub->hdev;
int i;
for (i = 0; i < hdev->maxchild; ++i) {
if (hdev->children[i])
usb_disconnect(&hdev->children[i]);
}
hub_quiesce(hub);
}
/* caller has locked the hub device */
static void hub_post_reset(struct usb_hub *hub)
{
hub_activate(hub);
hub_power_on(hub);
}
/* grab device/port lock, returning index of that port (zero based). /* grab device/port lock, returning index of that port (zero based).
* protects the upstream link used by this device from concurrent * protects the upstream link used by this device from concurrent
@ -2682,7 +2650,7 @@ static void hub_events(void)
/* If the hub has died, clean up after it */ /* If the hub has died, clean up after it */
if (hdev->state == USB_STATE_NOTATTACHED) { if (hdev->state == USB_STATE_NOTATTACHED) {
hub_pre_reset(hub); hub_pre_reset(hub, 0);
goto loop; goto loop;
} }
@ -2997,7 +2965,7 @@ int usb_reset_device(struct usb_device *udev)
udev->actconfig->interface[0]->dev.driver == udev->actconfig->interface[0]->dev.driver ==
&hub_driver.driver && &hub_driver.driver &&
(hub = hdev_to_hub(udev)) != NULL) { (hub = hdev_to_hub(udev)) != NULL) {
hub_pre_reset(hub); hub_pre_reset(hub, 0);
} }
set_bit(port1, parent_hub->busy_bits); set_bit(port1, parent_hub->busy_bits);