#include <asm/unaligned.h>
 #include <linux/platform_device.h>
 #include <linux/workqueue.h>
+#include <linux/mutex.h>
 
 #include <linux/usb.h>
 
        }
 }
 
-/* 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;
                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)
                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)
                        }
                }
        }
+       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)
 #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 :
 
        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];
 
 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,
 
 {
        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;
        /* 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,
                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.
         * 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;
                        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);
                }
 
 {
        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;
                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
                        "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
 {
        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;
        }
 
        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++) {
        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)
         * 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;
        }
 
        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