aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2011-06-06 02:22:22 -0400
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2011-06-15 17:05:18 -0400
commitfccf4e86200b8f5edd9a65da26f150e32ba79808 (patch)
tree6de67060ac3825e46f67682a69298bddc8825535 /drivers/usb/core
parentfa75ac379e63c2864e9049b5e8615e40f65c1e70 (diff)
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 0791971ba8fbc44e4f476079f856335ed45e6324 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 <sarah.a.sharp@linux.intel.com> Reported-by: Tanya Brokhman <tlinder@codeaurora.org> Cc: ablay@codeaurora.org Cc: Alan Stern <stern@rowland.harvard.edu> Cc: stable@kernel.org
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/hub.c3
-rw-r--r--drivers/usb/core/message.c15
2 files changed, 17 insertions, 1 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 90ae1753dda1..ca339bc799e4 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1634,6 +1634,7 @@ void usb_disconnect(struct usb_device **pdev)
1634{ 1634{
1635 struct usb_device *udev = *pdev; 1635 struct usb_device *udev = *pdev;
1636 int i; 1636 int i;
1637 struct usb_hcd *hcd = bus_to_hcd(udev->bus);
1637 1638
1638 if (!udev) { 1639 if (!udev) {
1639 pr_debug ("%s nodev\n", __func__); 1640 pr_debug ("%s nodev\n", __func__);
@@ -1661,7 +1662,9 @@ void usb_disconnect(struct usb_device **pdev)
1661 * so that the hardware is now fully quiesced. 1662 * so that the hardware is now fully quiesced.
1662 */ 1663 */
1663 dev_dbg (&udev->dev, "unregistering device\n"); 1664 dev_dbg (&udev->dev, "unregistering device\n");
1665 mutex_lock(hcd->bandwidth_mutex);
1664 usb_disable_device(udev, 0); 1666 usb_disable_device(udev, 0);
1667 mutex_unlock(hcd->bandwidth_mutex);
1665 usb_hcd_synchronize_unlinks(udev); 1668 usb_hcd_synchronize_unlinks(udev);
1666 1669
1667 usb_remove_ep_devs(&udev->ep0); 1670 usb_remove_ep_devs(&udev->ep0);
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 5701e857392b..64c7ab4702df 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1135,10 +1135,13 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf,
1135 * Deallocates hcd/hardware state for the endpoints (nuking all or most 1135 * Deallocates hcd/hardware state for the endpoints (nuking all or most
1136 * pending urbs) and usbcore state for the interfaces, so that usbcore 1136 * pending urbs) and usbcore state for the interfaces, so that usbcore
1137 * must usb_set_configuration() before any interfaces could be used. 1137 * must usb_set_configuration() before any interfaces could be used.
1138 *
1139 * Must be called with hcd->bandwidth_mutex held.
1138 */ 1140 */
1139void usb_disable_device(struct usb_device *dev, int skip_ep0) 1141void usb_disable_device(struct usb_device *dev, int skip_ep0)
1140{ 1142{
1141 int i; 1143 int i;
1144 struct usb_hcd *hcd = bus_to_hcd(dev->bus);
1142 1145
1143 /* getting rid of interfaces will disconnect 1146 /* getting rid of interfaces will disconnect
1144 * any drivers bound to them (a key side effect) 1147 * any drivers bound to them (a key side effect)
@@ -1172,6 +1175,16 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
1172 1175
1173 dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__, 1176 dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__,
1174 skip_ep0 ? "non-ep0" : "all"); 1177 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 }
1175 for (i = skip_ep0; i < 16; ++i) { 1188 for (i = skip_ep0; i < 16; ++i) {
1176 usb_disable_endpoint(dev, i, true); 1189 usb_disable_endpoint(dev, i, true);
1177 usb_disable_endpoint(dev, i + USB_DIR_IN, true); 1190 usb_disable_endpoint(dev, i + USB_DIR_IN, true);
@@ -1727,6 +1740,7 @@ free_interfaces:
1727 /* if it's already configured, clear out old state first. 1740 /* if it's already configured, clear out old state first.
1728 * getting rid of old interfaces means unbinding their drivers. 1741 * getting rid of old interfaces means unbinding their drivers.
1729 */ 1742 */
1743 mutex_lock(hcd->bandwidth_mutex);
1730 if (dev->state != USB_STATE_ADDRESS) 1744 if (dev->state != USB_STATE_ADDRESS)
1731 usb_disable_device(dev, 1); /* Skip ep0 */ 1745 usb_disable_device(dev, 1); /* Skip ep0 */
1732 1746
@@ -1739,7 +1753,6 @@ free_interfaces:
1739 * host controller will not allow submissions to dropped endpoints. If 1753 * host controller will not allow submissions to dropped endpoints. If
1740 * this call fails, the device state is unchanged. 1754 * this call fails, the device state is unchanged.
1741 */ 1755 */
1742 mutex_lock(hcd->bandwidth_mutex);
1743 ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); 1756 ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL);
1744 if (ret < 0) { 1757 if (ret < 0) {
1745 mutex_unlock(hcd->bandwidth_mutex); 1758 mutex_unlock(hcd->bandwidth_mutex);