Skip to content

Commit fccf4e8

Browse files
author
Sarah Sharp
committed
USB: Free bandwidth when usb_disable_device is called.
Tanya ran into an issue when trying to switch a UAS device from the BOT configuration to the UAS configuration via the bConfigurationValue sysfs file. Before installing the UAS configuration, set_bConfigurationValue() calls usb_disable_device(). That function is supposed to remove all host controller resources associated with that device, but it leaves some state in the xHCI host controller. Commit 0791971 usb: allow drivers to use allocated bandwidth until unbound added a call to usb_disable_device() in usb_set_configuration(), before the xHCI bandwidth functions were invoked. That commit fixed a bug, but also introduced a bug that is triggered when a configured device is switched to a new configuration. usb_disable_device() goes through all the motions of unbinding the drivers attached to active interfaces and removing the USB core structures associated with those interfaces, but it doesn't actually remove the endpoints from the internal xHCI host controller bandwidth structures. When usb_disable_device() calls usb_disable_endpoint() with reset_hardware set to true, the entries in udev->ep_out and udev->ep_in will be set to NULL. Usually, when the USB core installs a new configuration, usb_hcd_alloc_bandwidth() will drop all non-NULL endpoints in udev->ep_out and udev->ep_in before adding any new endpoints. However, when the new UAS configuration was added, all those entries were null, so none of the old endpoints in the BOT configuration were dropped. The xHCI driver blindly added the UAS configuration endpoints, and some of the endpoint addresses overlapped with the old BOT configuration endpoints. This caused the xHCI host to reject the Configure Endpoint command. Now that the xHCI driver code is cleaned up to reject a double-add of active endpoints, we need to fix the USB core to properly drop old endpoints in usb_disable_device(). If the host controller driver needs bandwidth checking support, make usb_disable_device() call usb_disable_endpoint() with reset_hardware set to false, drop the endpoints from the xHCI host controller, and then call usb_disable_endpoint() again with reset_hardware set to true. The first call to usb_disable_endpoint() will cancel any pending URBs and wait on them to be freed in usb_hcd_disable_endpoint(), but will keep the pointers in udev->ep_out and udev->ep in intact. Then usb_hcd_alloc_bandwidth() will use those pointers to know which endpoints to drop. The final call to usb_disable_endpoint() will do two things: 1. It will call usb_hcd_disable_endpoint() again, which should be harmless since the ep->urb_list should be empty after the first call to usb_disable_endpoint() returns. 2. It will set the entries in udev->ep_out and udev->ep in to NULL, and call usb_hcd_disable_endpoint(). That call will have no effect, since the xHCI driver doesn't set the endpoint_disable function pointer. Note that usb_disable_device() will now need to be called with hcd->bandwidth_mutex held. This should be backported to kernels as old as 2.6.32. Signed-off-by: Sarah Sharp <[email protected]> Reported-by: Tanya Brokhman <[email protected]> Cc: [email protected] Cc: Alan Stern <[email protected]> Cc: [email protected]
1 parent fa75ac3 commit fccf4e8

File tree

2 files changed

+17
-1
lines changed

2 files changed

+17
-1
lines changed

drivers/usb/core/hub.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,7 @@ void usb_disconnect(struct usb_device **pdev)
16341634
{
16351635
struct usb_device *udev = *pdev;
16361636
int i;
1637+
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
16371638

16381639
if (!udev) {
16391640
pr_debug ("%s nodev\n", __func__);
@@ -1661,7 +1662,9 @@ void usb_disconnect(struct usb_device **pdev)
16611662
* so that the hardware is now fully quiesced.
16621663
*/
16631664
dev_dbg (&udev->dev, "unregistering device\n");
1665+
mutex_lock(hcd->bandwidth_mutex);
16641666
usb_disable_device(udev, 0);
1667+
mutex_unlock(hcd->bandwidth_mutex);
16651668
usb_hcd_synchronize_unlinks(udev);
16661669

16671670
usb_remove_ep_devs(&udev->ep0);

drivers/usb/core/message.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,10 +1135,13 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf,
11351135
* Deallocates hcd/hardware state for the endpoints (nuking all or most
11361136
* pending urbs) and usbcore state for the interfaces, so that usbcore
11371137
* must usb_set_configuration() before any interfaces could be used.
1138+
*
1139+
* Must be called with hcd->bandwidth_mutex held.
11381140
*/
11391141
void usb_disable_device(struct usb_device *dev, int skip_ep0)
11401142
{
11411143
int i;
1144+
struct usb_hcd *hcd = bus_to_hcd(dev->bus);
11421145

11431146
/* getting rid of interfaces will disconnect
11441147
* any drivers bound to them (a key side effect)
@@ -1172,6 +1175,16 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
11721175

11731176
dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__,
11741177
skip_ep0 ? "non-ep0" : "all");
1178+
if (hcd->driver->check_bandwidth) {
1179+
/* First pass: Cancel URBs, leave endpoint pointers intact. */
1180+
for (i = skip_ep0; i < 16; ++i) {
1181+
usb_disable_endpoint(dev, i, false);
1182+
usb_disable_endpoint(dev, i + USB_DIR_IN, false);
1183+
}
1184+
/* Remove endpoints from the host controller internal state */
1185+
usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
1186+
/* Second pass: remove endpoint pointers */
1187+
}
11751188
for (i = skip_ep0; i < 16; ++i) {
11761189
usb_disable_endpoint(dev, i, true);
11771190
usb_disable_endpoint(dev, i + USB_DIR_IN, true);
@@ -1727,6 +1740,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
17271740
/* if it's already configured, clear out old state first.
17281741
* getting rid of old interfaces means unbinding their drivers.
17291742
*/
1743+
mutex_lock(hcd->bandwidth_mutex);
17301744
if (dev->state != USB_STATE_ADDRESS)
17311745
usb_disable_device(dev, 1); /* Skip ep0 */
17321746

@@ -1739,7 +1753,6 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
17391753
* host controller will not allow submissions to dropped endpoints. If
17401754
* this call fails, the device state is unchanged.
17411755
*/
1742-
mutex_lock(hcd->bandwidth_mutex);
17431756
ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL);
17441757
if (ret < 0) {
17451758
mutex_unlock(hcd->bandwidth_mutex);

0 commit comments

Comments
 (0)