USB: Check bandwidth when switching alt settings.
authorSarah Sharp <sarah.a.sharp@linux.intel.com>
Thu, 3 Dec 2009 17:44:36 +0000 (09:44 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 11 Dec 2009 19:55:27 +0000 (11:55 -0800)
Make the USB core check the bandwidth when switching from one
interface alternate setting to another.  Also check the bandwidth
when resetting a configuration (so that alt setting 0 is used).  If
this check fails, the device's state is unchanged.  If the device
refuses the new alt setting, re-instate the old alt setting in the
host controller hardware.

If a USB device doesn't have an alternate interface setting 0, install
the first alt setting in its descriptors when a new configuration is
requested, or the device is reset.

Add a mutex per root hub to protect bandwidth operations:
adding/reseting/changing configurations, and changing alternate interface
settings.  We want to ensure that the xHCI host controller and the USB
device are set up for the same configurations and alternate settings.
There are two (possibly three) steps to do this:

 1. The host controller needs to check that bandwidth is available for a
    different setting, by issuing and waiting for a configure endpoint
    command.
 2. Once that returns successfully, a control message is sent to the
    device.
 3. If that fails, the host controller must be notified through another
    configure endpoint command.

The mutex is used to make these three operations seem atomic, to prevent
another driver from using more bandwidth for a different device while
we're in the middle of these operations.

While we're touching the bandwidth code, rename usb_hcd_check_bandwidth()
to usb_hcd_alloc_bandwidth().  This function does more than just check
that the bandwidth change won't exceed the bus bandwidth; it actually
changes the bandwidth configuration in the xHCI host controller.

Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/hcd.c
drivers/usb/core/hcd.h
drivers/usb/core/hub.c
drivers/usb/core/message.c

index fc235b0..6dac3b8 100644 (file)
@@ -38,6 +38,7 @@
 #include <asm/unaligned.h>
 #include <linux/platform_device.h>
 #include <linux/workqueue.h>
+#include <linux/mutex.h>
 
 #include <linux/usb.h>
 
@@ -1595,15 +1596,29 @@ rescan:
        }
 }
 
-/* Check whether a new configuration or alt setting for an interface
- * will exceed the bandwidth for the bus (or the host controller resources).
- * Only pass in a non-NULL config or interface, not both!
- * Passing NULL for both new_config and new_intf means the device will be
- * de-configured by issuing a set configuration 0 command.
+/**
+ * Check whether a new bandwidth setting exceeds the bus bandwidth.
+ * @new_config: new configuration to install
+ * @cur_alt: the current alternate interface setting
+ * @new_alt: alternate interface setting that is being installed
+ *
+ * To change configurations, pass in the new configuration in new_config,
+ * and pass NULL for cur_alt and new_alt.
+ *
+ * To reset a device's configuration (put the device in the ADDRESSED state),
+ * pass in NULL for new_config, cur_alt, and new_alt.
+ *
+ * To change alternate interface settings, pass in NULL for new_config,
+ * pass in the current alternate interface setting in cur_alt,
+ * and pass in the new alternate interface setting in new_alt.
+ *
+ * Returns an error if the requested bandwidth change exceeds the
+ * bus bandwidth or host controller internal resources.
  */
-int usb_hcd_check_bandwidth(struct usb_device *udev,
+int usb_hcd_alloc_bandwidth(struct usb_device *udev,
                struct usb_host_config *new_config,
-               struct usb_interface *new_intf)
+               struct usb_host_interface *cur_alt,
+               struct usb_host_interface *new_alt)
 {
        int num_intfs, i, j;
        struct usb_host_interface *alt = NULL;
@@ -1616,7 +1631,7 @@ int usb_hcd_check_bandwidth(struct usb_device *udev,
                return 0;
 
        /* Configuration is being removed - set configuration 0 */
-       if (!new_config && !new_intf) {
+       if (!new_config && !cur_alt) {
                for (i = 1; i < 16; ++i) {
                        ep = udev->ep_out[i];
                        if (ep)
@@ -1655,10 +1670,10 @@ int usb_hcd_check_bandwidth(struct usb_device *udev,
                for (i = 0; i < num_intfs; ++i) {
                        /* Set up endpoints for alternate interface setting 0 */
                        alt = usb_find_alt_setting(new_config, i, 0);
-                       if (!alt) {
-                               printk(KERN_DEBUG "Did not find alt setting 0 for intf %d\n", i);
-                               continue;
-                       }
+                       if (!alt)
+                               /* No alt setting 0? Pick the first setting. */
+                               alt = &new_config->intf_cache[i]->altsetting[0];
+
                        for (j = 0; j < alt->desc.bNumEndpoints; j++) {
                                ret = hcd->driver->add_endpoint(hcd, udev, &alt->endpoint[j]);
                                if (ret < 0)
@@ -1666,6 +1681,22 @@ int usb_hcd_check_bandwidth(struct usb_device *udev,
                        }
                }
        }
+       if (cur_alt && new_alt) {
+               /* Drop all the endpoints in the current alt setting */
+               for (i = 0; i < cur_alt->desc.bNumEndpoints; i++) {
+                       ret = hcd->driver->drop_endpoint(hcd, udev,
+                                       &cur_alt->endpoint[i]);
+                       if (ret < 0)
+                               goto reset;
+               }
+               /* Add all the endpoints in the new alt setting */
+               for (i = 0; i < new_alt->desc.bNumEndpoints; i++) {
+                       ret = hcd->driver->add_endpoint(hcd, udev,
+                                       &new_alt->endpoint[i]);
+                       if (ret < 0)
+                               goto reset;
+               }
+       }
        ret = hcd->driver->check_bandwidth(hcd, udev);
 reset:
        if (ret < 0)
@@ -1982,6 +2013,7 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
 #ifdef CONFIG_PM
        INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
 #endif
+       mutex_init(&hcd->bandwidth_mutex);
 
        hcd->driver = driver;
        hcd->product_desc = (driver->product_desc) ? driver->product_desc :
index 79782a1..d8b43ae 100644 (file)
@@ -111,6 +111,20 @@ struct usb_hcd {
        u64                     rsrc_len;       /* memory/io resource length */
        unsigned                power_budget;   /* in mA, 0 = no limit */
 
+       /* bandwidth_mutex should be taken before adding or removing
+        * any new bus bandwidth constraints:
+        *   1. Before adding a configuration for a new device.
+        *   2. Before removing the configuration to put the device into
+        *      the addressed state.
+        *   3. Before selecting a different configuration.
+        *   4. Before selecting an alternate interface setting.
+        *
+        * bandwidth_mutex should be dropped after a successful control message
+        * to the device, or resetting the bandwidth after a failed attempt.
+        */
+       struct mutex            bandwidth_mutex;
+
+
 #define HCD_BUFFER_POOLS       4
        struct dma_pool         *pool [HCD_BUFFER_POOLS];
 
@@ -290,9 +304,10 @@ extern void usb_hcd_disable_endpoint(struct usb_device *udev,
 extern void usb_hcd_reset_endpoint(struct usb_device *udev,
                struct usb_host_endpoint *ep);
 extern void usb_hcd_synchronize_unlinks(struct usb_device *udev);
-extern int usb_hcd_check_bandwidth(struct usb_device *udev,
+extern int usb_hcd_alloc_bandwidth(struct usb_device *udev,
                struct usb_host_config *new_config,
-               struct usb_interface *new_intf);
+               struct usb_host_interface *old_alt,
+               struct usb_host_interface *new_alt);
 extern int usb_hcd_get_frame_number(struct usb_device *udev);
 
 extern struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,
index b38fd67..e4b0e28 100644 (file)
@@ -3599,6 +3599,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 {
        struct usb_device               *parent_hdev = udev->parent;
        struct usb_hub                  *parent_hub;
+       struct usb_hcd                  *hcd = bus_to_hcd(udev->bus);
        struct usb_device_descriptor    descriptor = udev->descriptor;
        int                             i, ret = 0;
        int                             port1 = udev->portnum;
@@ -3642,6 +3643,16 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
        /* Restore the device's previous configuration */
        if (!udev->actconfig)
                goto done;
+
+       mutex_lock(&hcd->bandwidth_mutex);
+       ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL);
+       if (ret < 0) {
+               dev_warn(&udev->dev,
+                               "Busted HC?  Not enough HCD resources for "
+                               "old configuration.\n");
+               mutex_unlock(&hcd->bandwidth_mutex);
+               goto re_enumerate;
+       }
        ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
                        USB_REQ_SET_CONFIGURATION, 0,
                        udev->actconfig->desc.bConfigurationValue, 0,
@@ -3650,8 +3661,10 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
                dev_err(&udev->dev,
                        "can't restore configuration #%d (error=%d)\n",
                        udev->actconfig->desc.bConfigurationValue, ret);
+               mutex_unlock(&hcd->bandwidth_mutex);
                goto re_enumerate;
        }
+       mutex_unlock(&hcd->bandwidth_mutex);
        usb_set_device_state(udev, USB_STATE_CONFIGURED);
 
        /* Put interfaces back into the same altsettings as before.
@@ -3661,7 +3674,8 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
         * endpoint state.
         */
        for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
-               struct usb_interface *intf = udev->actconfig->interface[i];
+               struct usb_host_config *config = udev->actconfig;
+               struct usb_interface *intf = config->interface[i];
                struct usb_interface_descriptor *desc;
 
                desc = &intf->cur_altsetting->desc;
@@ -3670,6 +3684,17 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
                        usb_enable_interface(udev, intf, true);
                        ret = 0;
                } else {
+                       /* We've just reset the device, so it will think alt
+                        * setting 0 is installed.  For usb_set_interface() to
+                        * work properly, we need to set the current alternate
+                        * interface setting to 0 (or the first alt setting, if
+                        * the device doesn't have alt setting 0).
+                        */
+                       intf->cur_altsetting =
+                               usb_find_alt_setting(config, i, 0);
+                       if (!intf->cur_altsetting)
+                               intf->cur_altsetting =
+                                       &config->intf_cache[i]->altsetting[0];
                        ret = usb_set_interface(udev, desc->bInterfaceNumber,
                                        desc->bAlternateSetting);
                }
index adb9c8e..ed83f2b 100644 (file)
@@ -1298,6 +1298,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
 {
        struct usb_interface *iface;
        struct usb_host_interface *alt;
+       struct usb_hcd *hcd = bus_to_hcd(dev->bus);
        int ret;
        int manual = 0;
        unsigned int epaddr;
@@ -1320,6 +1321,18 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
                return -EINVAL;
        }
 
+       /* Make sure we have enough bandwidth for this alternate interface.
+        * Remove the current alt setting and add the new alt setting.
+        */
+       mutex_lock(&hcd->bandwidth_mutex);
+       ret = usb_hcd_alloc_bandwidth(dev, NULL, iface->cur_altsetting, alt);
+       if (ret < 0) {
+               dev_info(&dev->dev, "Not enough bandwidth for altsetting %d\n",
+                               alternate);
+               mutex_unlock(&hcd->bandwidth_mutex);
+               return ret;
+       }
+
        if (dev->quirks & USB_QUIRK_NO_SET_INTF)
                ret = -EPIPE;
        else
@@ -1335,8 +1348,13 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
                        "manual set_interface for iface %d, alt %d\n",
                        interface, alternate);
                manual = 1;
-       } else if (ret < 0)
+       } else if (ret < 0) {
+               /* Re-instate the old alt setting */
+               usb_hcd_alloc_bandwidth(dev, NULL, alt, iface->cur_altsetting);
+               mutex_unlock(&hcd->bandwidth_mutex);
                return ret;
+       }
+       mutex_unlock(&hcd->bandwidth_mutex);
 
        /* FIXME drivers shouldn't need to replicate/bugfix the logic here
         * when they implement async or easily-killable versions of this or
@@ -1418,6 +1436,7 @@ int usb_reset_configuration(struct usb_device *dev)
 {
        int                     i, retval;
        struct usb_host_config  *config;
+       struct usb_hcd *hcd = bus_to_hcd(dev->bus);
 
        if (dev->state == USB_STATE_SUSPENDED)
                return -EHOSTUNREACH;
@@ -1433,12 +1452,46 @@ int usb_reset_configuration(struct usb_device *dev)
        }
 
        config = dev->actconfig;
+       retval = 0;
+       mutex_lock(&hcd->bandwidth_mutex);
+       /* Make sure we have enough bandwidth for each alternate setting 0 */
+       for (i = 0; i < config->desc.bNumInterfaces; i++) {
+               struct usb_interface *intf = config->interface[i];
+               struct usb_host_interface *alt;
+
+               alt = usb_altnum_to_altsetting(intf, 0);
+               if (!alt)
+                       alt = &intf->altsetting[0];
+               if (alt != intf->cur_altsetting)
+                       retval = usb_hcd_alloc_bandwidth(dev, NULL,
+                                       intf->cur_altsetting, alt);
+               if (retval < 0)
+                       break;
+       }
+       /* If not, reinstate the old alternate settings */
+       if (retval < 0) {
+reset_old_alts:
+               for (; i >= 0; i--) {
+                       struct usb_interface *intf = config->interface[i];
+                       struct usb_host_interface *alt;
+
+                       alt = usb_altnum_to_altsetting(intf, 0);
+                       if (!alt)
+                               alt = &intf->altsetting[0];
+                       if (alt != intf->cur_altsetting)
+                               usb_hcd_alloc_bandwidth(dev, NULL,
+                                               alt, intf->cur_altsetting);
+               }
+               mutex_unlock(&hcd->bandwidth_mutex);
+               return retval;
+       }
        retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
                        USB_REQ_SET_CONFIGURATION, 0,
                        config->desc.bConfigurationValue, 0,
                        NULL, 0, USB_CTRL_SET_TIMEOUT);
        if (retval < 0)
-               return retval;
+               goto reset_old_alts;
+       mutex_unlock(&hcd->bandwidth_mutex);
 
        /* re-init hc/hcd interface/endpoint state */
        for (i = 0; i < config->desc.bNumInterfaces; i++) {
@@ -1647,6 +1700,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
        int i, ret;
        struct usb_host_config *cp = NULL;
        struct usb_interface **new_interfaces = NULL;
+       struct usb_hcd *hcd = bus_to_hcd(dev->bus);
        int n, nintf;
 
        if (dev->authorized == 0 || configuration == -1)
@@ -1716,12 +1770,11 @@ free_interfaces:
         * host controller will not allow submissions to dropped endpoints.  If
         * this call fails, the device state is unchanged.
         */
-       if (cp)
-               ret = usb_hcd_check_bandwidth(dev, cp, NULL);
-       else
-               ret = usb_hcd_check_bandwidth(dev, NULL, NULL);
+       mutex_lock(&hcd->bandwidth_mutex);
+       ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL);
        if (ret < 0) {
                usb_autosuspend_device(dev);
+               mutex_unlock(&hcd->bandwidth_mutex);
                goto free_interfaces;
        }
 
@@ -1747,10 +1800,12 @@ free_interfaces:
        dev->actconfig = cp;
        if (!cp) {
                usb_set_device_state(dev, USB_STATE_ADDRESS);
-               usb_hcd_check_bandwidth(dev, NULL, NULL);
+               usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
                usb_autosuspend_device(dev);
+               mutex_unlock(&hcd->bandwidth_mutex);
                goto free_interfaces;
        }
+       mutex_unlock(&hcd->bandwidth_mutex);
        usb_set_device_state(dev, USB_STATE_CONFIGURED);
 
        /* Initialize the new interface structures and the