diff options
-rw-r--r-- | drivers/usb/core/hcd.c | 56 | ||||
-rw-r--r-- | drivers/usb/core/hcd.h | 19 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 27 | ||||
-rw-r--r-- | drivers/usb/core/message.c | 69 |
4 files changed, 149 insertions, 22 deletions
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index fc235b02ff2..6dac3b802d4 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -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 : diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 79782a1c43f..d8b43aee581 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -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, diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b38fd6730e2..e4b0e28f745 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -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); } diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index adb9c8ee0c1..ed83f2b1d55 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -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 |