Commit 729f806b authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] USB: Fix resource leakage in the hub driver

The hub driver is very careless about returning resources when an error
occurs while installing a new device.  This patch attempts to put some
order back into the situation.  Details:

	Since usb_new_device() allocates neither the device structure
	nor the device address, it shouldn't release either one.

	Because usb_new_device() no longer releases the device structure,
	usb_register_root_hub() doesn't need to take an extra reference
	to it.

	Since the device address selection and TT setup code is used
	only for new devices, not ones being reset, move that code from
	hub_port_init() to hub_port_connect_change().  By the same token,
	hub_port_init() doesn't have to release the device address or
	the device structure.

	Just to make things look better, move the failure code in
	hub_port_init() to the end of the routine.  And when disabling
	endpoint 0, disable both the IN and OUT parts of the endpoint.

	In hub_port_connect_change(), make all the failure paths
	execute the same code so that resources are always released.
	These resources comprise: the pointer from the parent to the
	new child device, the HCD state for ep0, the device's address,
	and the device structure itself -- in short, everything that's
	set up before calling usb_new_device().
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent 70244527
...@@ -787,14 +787,12 @@ int usb_register_root_hub (struct usb_device *usb_dev, struct device *parent_dev ...@@ -787,14 +787,12 @@ int usb_register_root_hub (struct usb_device *usb_dev, struct device *parent_dev
return (retval < 0) ? retval : -EMSGSIZE; return (retval < 0) ? retval : -EMSGSIZE;
} }
(void) usb_get_dev (usb_dev);
down (&usb_dev->serialize); down (&usb_dev->serialize);
retval = usb_new_device (usb_dev); retval = usb_new_device (usb_dev);
up (&usb_dev->serialize);
if (retval) if (retval)
dev_err (parent_dev, "can't register root hub for %s, %d\n", dev_err (parent_dev, "can't register root hub for %s, %d\n",
usb_dev->dev.bus_id, retval); usb_dev->dev.bus_id, retval);
up (&usb_dev->serialize);
usb_put_dev (usb_dev);
return retval; return retval;
} }
EXPORT_SYMBOL (usb_register_root_hub); EXPORT_SYMBOL (usb_register_root_hub);
......
...@@ -1055,8 +1055,6 @@ int usb_new_device(struct usb_device *udev) ...@@ -1055,8 +1055,6 @@ int usb_new_device(struct usb_device *udev)
fail: fail:
udev->state = USB_STATE_NOTATTACHED; udev->state = USB_STATE_NOTATTACHED;
release_address(udev);
usb_put_dev(udev);
return err; return err;
} }
...@@ -1334,33 +1332,9 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port) ...@@ -1334,33 +1332,9 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port)
udev->epmaxpacketin [0] = i; udev->epmaxpacketin [0] = i;
udev->epmaxpacketout[0] = i; udev->epmaxpacketout[0] = i;
/* set the address */
if (udev->devnum <= 0) {
choose_address(udev);
if (udev->devnum <= 0)
goto fail;
/* Set up TT records, if needed */
if (hdev->tt) {
udev->tt = hdev->tt;
udev->ttport = hdev->ttport;
} else if (udev->speed != USB_SPEED_HIGH
&& hdev->speed == USB_SPEED_HIGH) {
struct usb_hub *hub;
hub = usb_get_intfdata (hdev->actconfig
->interface[0]);
udev->tt = &hub->tt;
udev->ttport = port + 1;
}
/* force the right log message (below) at low speed */
oldspeed = USB_SPEED_UNKNOWN;
}
dev_info (&udev->dev, dev_info (&udev->dev,
"%s %s speed USB device using address %d\n", "%s %s speed USB device using address %d\n",
(oldspeed == USB_SPEED_UNKNOWN) ? "new" : "reset", (udev->config) ? "reset" : "new",
({ char *speed; switch (udev->speed) { ({ char *speed; switch (udev->speed) {
case USB_SPEED_LOW: speed = "low"; break; case USB_SPEED_LOW: speed = "low"; break;
case USB_SPEED_FULL: speed = "full"; break; case USB_SPEED_FULL: speed = "full"; break;
...@@ -1389,12 +1363,7 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port) ...@@ -1389,12 +1363,7 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port)
dev_err(&udev->dev, dev_err(&udev->dev,
"device not accepting address %d, error %d\n", "device not accepting address %d, error %d\n",
udev->devnum, retval); udev->devnum, retval);
fail: goto fail;
hub_port_disable(hdev, port);
release_address(udev);
usb_put_dev(udev);
up(&usb_address0_sem);
return retval;
} }
/* cope with hardware quirkiness: /* cope with hardware quirkiness:
...@@ -1417,7 +1386,8 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port) ...@@ -1417,7 +1386,8 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port)
if (udev->speed == USB_SPEED_FULL if (udev->speed == USB_SPEED_FULL
&& (udev->epmaxpacketin [0] && (udev->epmaxpacketin [0]
!= udev->descriptor.bMaxPacketSize0)) { != udev->descriptor.bMaxPacketSize0)) {
usb_disable_endpoint(udev, 0); usb_disable_endpoint(udev, 0 + USB_DIR_IN);
usb_disable_endpoint(udev, 0 + USB_DIR_OUT);
usb_endpoint_running(udev, 0, 1); usb_endpoint_running(udev, 0, 1);
usb_endpoint_running(udev, 0, 0); usb_endpoint_running(udev, 0, 0);
udev->epmaxpacketin [0] = udev->descriptor.bMaxPacketSize0; udev->epmaxpacketin [0] = udev->descriptor.bMaxPacketSize0;
...@@ -1435,9 +1405,11 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port) ...@@ -1435,9 +1405,11 @@ hub_port_init (struct usb_device *hdev, struct usb_device *udev, int port)
/* now dev is visible to other tasks */ /* now dev is visible to other tasks */
hdev->children[port] = udev; hdev->children[port] = udev;
retval = 0;
fail:
up(&usb_address0_sem); up(&usb_address0_sem);
return 0; return retval;
} }
static void static void
...@@ -1562,20 +1534,36 @@ static void hub_port_connect_change(struct usb_hub *hub, int port, ...@@ -1562,20 +1534,36 @@ static void hub_port_connect_change(struct usb_hub *hub, int port,
goto done; goto done;
} }
udev->state = USB_STATE_POWERED; udev->state = USB_STATE_POWERED;
/* hub can tell if it's lowspeed already: D- pullup (not D+) */ /* hub can tell if it's lowspeed already: D- pullup (not D+) */
if (portstatus & USB_PORT_STAT_LOW_SPEED) if (portstatus & USB_PORT_STAT_LOW_SPEED)
udev->speed = USB_SPEED_LOW; udev->speed = USB_SPEED_LOW;
else else
udev->speed = USB_SPEED_UNKNOWN; udev->speed = USB_SPEED_UNKNOWN;
/* reset, set address, get descriptor, add to hub's children */
down (&udev->serialize); down (&udev->serialize);
/* set the address */
choose_address(udev);
if (udev->devnum <= 0) {
status = -ENOTCONN; /* Don't retry */
goto loop;
}
/* reset, get descriptor, add to hub's children */
status = hub_port_init(hdev, udev, port); status = hub_port_init(hdev, udev, port);
if (status == -ENOTCONN)
break;
if (status < 0) if (status < 0)
continue; goto loop;
/* Set up TT records, if needed */
if (hdev->tt) {
udev->tt = hdev->tt;
udev->ttport = hdev->ttport;
} else if (udev->speed != USB_SPEED_HIGH
&& hdev->speed == USB_SPEED_HIGH) {
udev->tt = &hub->tt;
udev->ttport = port + 1;
}
/* consecutive bus-powered hubs aren't reliable; they can /* consecutive bus-powered hubs aren't reliable; they can
* violate the voltage drop budget. if the new child has * violate the voltage drop budget. if the new child has
...@@ -1591,7 +1579,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port, ...@@ -1591,7 +1579,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port,
&devstat); &devstat);
if (status < 0) { if (status < 0) {
dev_dbg(&udev->dev, "get status %d ?\n", status); dev_dbg(&udev->dev, "get status %d ?\n", status);
continue; goto loop;
} }
cpu_to_le16s(&devstat); cpu_to_le16s(&devstat);
if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
...@@ -1603,10 +1591,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port, ...@@ -1603,10 +1591,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port,
INDICATOR_AMBER_BLINK; INDICATOR_AMBER_BLINK;
schedule_work (&hub->leds); schedule_work (&hub->leds);
} }
hdev->children[port] = NULL; status = -ENOTCONN; /* Don't retry */
usb_put_dev(udev); goto loop;
hub_port_disable(hdev, port);
return;
} }
} }
...@@ -1618,11 +1604,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port, ...@@ -1618,11 +1604,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port,
/* Run it through the hoops (find a driver, etc) */ /* Run it through the hoops (find a driver, etc) */
status = usb_new_device(udev); status = usb_new_device(udev);
if (status != 0) { if (status)
hdev->children[port] = NULL; goto loop;
continue;
}
up (&udev->serialize);
status = hub_power_remaining(hub, hdev); status = hub_power_remaining(hub, hdev);
if (status) if (status)
...@@ -1630,7 +1613,19 @@ static void hub_port_connect_change(struct usb_hub *hub, int port, ...@@ -1630,7 +1613,19 @@ static void hub_port_connect_change(struct usb_hub *hub, int port,
"%dmA power budget left\n", "%dmA power budget left\n",
2 * status); 2 * status);
up (&udev->serialize);
return; return;
loop:
hdev->children[port] = NULL;
hub_port_disable(hdev, port);
usb_disable_endpoint(udev, 0 + USB_DIR_IN);
usb_disable_endpoint(udev, 0 + USB_DIR_OUT);
release_address(udev);
up (&udev->serialize);
usb_put_dev(udev);
if (status == -ENOTCONN)
break;
} }
done: done:
......
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