提交 fccf4e86 编写于 作者: S Sarah Sharp

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 0791971b
	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: NSarah Sharp <sarah.a.sharp@linux.intel.com>
Reported-by: NTanya Brokhman <tlinder@codeaurora.org>
Cc: ablay@codeaurora.org
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: stable@kernel.org
上级 fa75ac37
...@@ -1634,6 +1634,7 @@ void usb_disconnect(struct usb_device **pdev) ...@@ -1634,6 +1634,7 @@ void usb_disconnect(struct usb_device **pdev)
{ {
struct usb_device *udev = *pdev; struct usb_device *udev = *pdev;
int i; int i;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
if (!udev) { if (!udev) {
pr_debug ("%s nodev\n", __func__); pr_debug ("%s nodev\n", __func__);
...@@ -1661,7 +1662,9 @@ void usb_disconnect(struct usb_device **pdev) ...@@ -1661,7 +1662,9 @@ void usb_disconnect(struct usb_device **pdev)
* so that the hardware is now fully quiesced. * so that the hardware is now fully quiesced.
*/ */
dev_dbg (&udev->dev, "unregistering device\n"); dev_dbg (&udev->dev, "unregistering device\n");
mutex_lock(hcd->bandwidth_mutex);
usb_disable_device(udev, 0); usb_disable_device(udev, 0);
mutex_unlock(hcd->bandwidth_mutex);
usb_hcd_synchronize_unlinks(udev); usb_hcd_synchronize_unlinks(udev);
usb_remove_ep_devs(&udev->ep0); usb_remove_ep_devs(&udev->ep0);
......
...@@ -1135,10 +1135,13 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf, ...@@ -1135,10 +1135,13 @@ void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf,
* Deallocates hcd/hardware state for the endpoints (nuking all or most * Deallocates hcd/hardware state for the endpoints (nuking all or most
* pending urbs) and usbcore state for the interfaces, so that usbcore * pending urbs) and usbcore state for the interfaces, so that usbcore
* must usb_set_configuration() before any interfaces could be used. * must usb_set_configuration() before any interfaces could be used.
*
* Must be called with hcd->bandwidth_mutex held.
*/ */
void usb_disable_device(struct usb_device *dev, int skip_ep0) void usb_disable_device(struct usb_device *dev, int skip_ep0)
{ {
int i; int i;
struct usb_hcd *hcd = bus_to_hcd(dev->bus);
/* getting rid of interfaces will disconnect /* getting rid of interfaces will disconnect
* any drivers bound to them (a key side effect) * 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,6 +1175,16 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__, dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__,
skip_ep0 ? "non-ep0" : "all"); skip_ep0 ? "non-ep0" : "all");
if (hcd->driver->check_bandwidth) {
/* First pass: Cancel URBs, leave endpoint pointers intact. */
for (i = skip_ep0; i < 16; ++i) {
usb_disable_endpoint(dev, i, false);
usb_disable_endpoint(dev, i + USB_DIR_IN, false);
}
/* Remove endpoints from the host controller internal state */
usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
/* Second pass: remove endpoint pointers */
}
for (i = skip_ep0; i < 16; ++i) { for (i = skip_ep0; i < 16; ++i) {
usb_disable_endpoint(dev, i, true); usb_disable_endpoint(dev, i, true);
usb_disable_endpoint(dev, i + USB_DIR_IN, true); usb_disable_endpoint(dev, i + USB_DIR_IN, true);
...@@ -1727,6 +1740,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration) ...@@ -1727,6 +1740,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
/* if it's already configured, clear out old state first. /* if it's already configured, clear out old state first.
* getting rid of old interfaces means unbinding their drivers. * getting rid of old interfaces means unbinding their drivers.
*/ */
mutex_lock(hcd->bandwidth_mutex);
if (dev->state != USB_STATE_ADDRESS) if (dev->state != USB_STATE_ADDRESS)
usb_disable_device(dev, 1); /* Skip ep0 */ usb_disable_device(dev, 1); /* Skip ep0 */
...@@ -1739,7 +1753,6 @@ int usb_set_configuration(struct usb_device *dev, int configuration) ...@@ -1739,7 +1753,6 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
* host controller will not allow submissions to dropped endpoints. If * host controller will not allow submissions to dropped endpoints. If
* this call fails, the device state is unchanged. * this call fails, the device state is unchanged.
*/ */
mutex_lock(hcd->bandwidth_mutex);
ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL);
if (ret < 0) { if (ret < 0) {
mutex_unlock(hcd->bandwidth_mutex); mutex_unlock(hcd->bandwidth_mutex);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册