diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/driver.c | 54 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 27 | ||||
-rw-r--r-- | drivers/usb/core/message.c | 38 |
3 files changed, 118 insertions, 1 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f6f81c85c5cf..f536aebc958e 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -288,6 +288,7 @@ static int usb_probe_interface(struct device *dev) struct usb_device *udev = interface_to_usbdev(intf); const struct usb_device_id *id; int error = -ENODEV; + int lpm_disable_error; dev_dbg(dev, "%s\n", __func__); @@ -324,6 +325,25 @@ static int usb_probe_interface(struct device *dev) if (driver->supports_autosuspend) pm_runtime_enable(dev); + /* If the new driver doesn't allow hub-initiated LPM, and we can't + * disable hub-initiated LPM, then fail the probe. + * + * Otherwise, leaving LPM enabled should be harmless, because the + * endpoint intervals should remain the same, and the U1/U2 timeouts + * should remain the same. + * + * If we need to install alt setting 0 before probe, or another alt + * setting during probe, that should also be fine. usb_set_interface() + * will attempt to disable LPM, and fail if it can't disable it. + */ + lpm_disable_error = usb_unlocked_disable_lpm(udev); + if (lpm_disable_error && driver->disable_hub_initiated_lpm) { + dev_err(&intf->dev, "%s Failed to disable LPM for driver %s\n.", + __func__, driver->name); + error = lpm_disable_error; + goto err; + } + /* Carry out a deferred switch to altsetting 0 */ if (intf->needs_altsetting0) { error = usb_set_interface(udev, intf->altsetting[0]. @@ -338,6 +358,11 @@ static int usb_probe_interface(struct device *dev) goto err; intf->condition = USB_INTERFACE_BOUND; + + /* If the LPM disable succeeded, balance the ref counts. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + usb_autosuspend_device(udev); return error; @@ -361,7 +386,7 @@ static int usb_unbind_interface(struct device *dev) struct usb_driver *driver = to_usb_driver(dev->driver); struct usb_interface *intf = to_usb_interface(dev); struct usb_device *udev; - int error, r; + int error, r, lpm_disable_error; intf->condition = USB_INTERFACE_UNBINDING; @@ -369,6 +394,13 @@ static int usb_unbind_interface(struct device *dev) udev = interface_to_usbdev(intf); error = usb_autoresume_device(udev); + /* Hub-initiated LPM policy may change, so attempt to disable LPM until + * the driver is unbound. If LPM isn't disabled, that's fine because it + * wouldn't be enabled unless all the bound interfaces supported + * hub-initiated LPM. + */ + lpm_disable_error = usb_unlocked_disable_lpm(udev); + /* Terminate all URBs for this interface unless the driver * supports "soft" unbinding. */ @@ -402,6 +434,10 @@ static int usb_unbind_interface(struct device *dev) intf->condition = USB_INTERFACE_UNBOUND; intf->needs_remote_wakeup = 0; + /* Attempt to re-enable USB3 LPM, if the disable succeeded. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + /* Unbound interfaces are always runtime-PM-disabled and -suspended */ if (driver->supports_autosuspend) pm_runtime_disable(dev); @@ -442,17 +478,29 @@ int usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface *iface, void *priv) { struct device *dev = &iface->dev; + struct usb_device *udev; int retval = 0; + int lpm_disable_error; if (dev->driver) return -EBUSY; + udev = interface_to_usbdev(iface); + dev->driver = &driver->drvwrap.driver; usb_set_intfdata(iface, priv); iface->needs_binding = 0; iface->condition = USB_INTERFACE_BOUND; + /* Disable LPM until this driver is bound. */ + lpm_disable_error = usb_unlocked_disable_lpm(udev); + if (lpm_disable_error && driver->disable_hub_initiated_lpm) { + dev_err(&iface->dev, "%s Failed to disable LPM for driver %s\n.", + __func__, driver->name); + return -ENOMEM; + } + /* Claimed interfaces are initially inactive (suspended) and * runtime-PM-enabled, but only if the driver has autosuspend * support. Otherwise they are marked active, to prevent the @@ -471,6 +519,10 @@ int usb_driver_claim_interface(struct usb_driver *driver, if (device_is_registered(dev)) retval = device_bind_driver(dev); + /* Attempt to re-enable USB3 LPM, if the disable was successful. */ + if (!lpm_disable_error) + usb_unlocked_enable_lpm(udev); + return retval; } EXPORT_SYMBOL_GPL(usb_driver_claim_interface); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index fd1ec481aec1..fcc244e9056f 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2702,6 +2702,12 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (udev->usb2_hw_lpm_enabled == 1) usb_set_usb2_hardware_lpm(udev, 0); + if (usb_unlocked_disable_lpm(udev)) { + dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.", + __func__); + return -ENOMEM; + } + /* see 7.1.7.6 */ if (hub_is_superspeed(hub->hdev)) status = set_port_feature(hub->hdev, @@ -2725,6 +2731,9 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (udev->usb2_hw_lpm_capable == 1) usb_set_usb2_hardware_lpm(udev, 1); + /* Try to enable USB3 LPM again */ + usb_unlocked_enable_lpm(udev); + /* System sleep transitions should never fail */ if (!PMSG_IS_AUTO(msg)) status = 0; @@ -2922,6 +2931,9 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) /* Try to enable USB2 hardware LPM */ if (udev->usb2_hw_lpm_capable == 1) usb_set_usb2_hardware_lpm(udev, 1); + + /* Try to enable USB3 LPM */ + usb_unlocked_enable_lpm(udev); } return status; @@ -4681,11 +4693,22 @@ static int usb_reset_and_verify_device(struct usb_device *udev) goto done; mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM while we reset the device and reinstall the alt settings. + * Device-initiated LPM settings, and system exit latency settings are + * cleared when the device is reset, so we have to set them up again. + */ + ret = usb_disable_lpm(udev); + if (ret) { + dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + goto done; + } 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"); + usb_enable_lpm(udev); mutex_unlock(hcd->bandwidth_mutex); goto re_enumerate; } @@ -4697,6 +4720,7 @@ 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); + usb_enable_lpm(udev); mutex_unlock(hcd->bandwidth_mutex); goto re_enumerate; } @@ -4735,10 +4759,13 @@ static int usb_reset_and_verify_device(struct usb_device *udev) desc->bInterfaceNumber, desc->bAlternateSetting, ret); + usb_unlocked_enable_lpm(udev); goto re_enumerate; } } + /* Now that the alt settings are re-installed, enable LPM. */ + usb_unlocked_enable_lpm(udev); done: return 0; diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index ca717da3be95..b548cf1dbc62 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1308,10 +1308,19 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) * Remove the current alt setting and add the new alt setting. */ mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM, and re-enable it once the new alt setting is installed, + * so that the xHCI driver can recalculate the U1/U2 timeouts. + */ + if (usb_disable_lpm(dev)) { + dev_err(&iface->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + return -ENOMEM; + } 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); + usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); return ret; } @@ -1334,6 +1343,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) } else if (ret < 0) { /* Re-instate the old alt setting */ usb_hcd_alloc_bandwidth(dev, NULL, alt, iface->cur_altsetting); + usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); return ret; } @@ -1354,6 +1364,9 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) iface->cur_altsetting = alt; + /* Now that the interface is installed, re-enable LPM. */ + usb_unlocked_enable_lpm(dev); + /* If the interface only has one altsetting and the device didn't * accept the request, we attempt to carry out the equivalent action * by manually clearing the HALT feature for each endpoint in the @@ -1437,6 +1450,14 @@ int usb_reset_configuration(struct usb_device *dev) config = dev->actconfig; retval = 0; mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM, and re-enable it once the configuration is reset, so + * that the xHCI driver can recalculate the U1/U2 timeouts. + */ + if (usb_disable_lpm(dev)) { + dev_err(&dev->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + return -ENOMEM; + } /* 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]; @@ -1465,6 +1486,7 @@ reset_old_alts: usb_hcd_alloc_bandwidth(dev, NULL, alt, intf->cur_altsetting); } + usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); return retval; } @@ -1502,6 +1524,8 @@ reset_old_alts: create_intf_ep_devs(intf); } } + /* Now that the interfaces are installed, re-enable LPM. */ + usb_unlocked_enable_lpm(dev); return 0; } EXPORT_SYMBOL_GPL(usb_reset_configuration); @@ -1763,8 +1787,18 @@ free_interfaces: * this call fails, the device state is unchanged. */ mutex_lock(hcd->bandwidth_mutex); + /* Disable LPM, and re-enable it once the new configuration is + * installed, so that the xHCI driver can recalculate the U1/U2 + * timeouts. + */ + if (usb_disable_lpm(dev)) { + dev_err(&dev->dev, "%s Failed to disable LPM\n.", __func__); + mutex_unlock(hcd->bandwidth_mutex); + return -ENOMEM; + } ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL); if (ret < 0) { + usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); usb_autosuspend_device(dev); goto free_interfaces; @@ -1784,6 +1818,7 @@ free_interfaces: if (!cp) { usb_set_device_state(dev, USB_STATE_ADDRESS); usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); + usb_enable_lpm(dev); mutex_unlock(hcd->bandwidth_mutex); usb_autosuspend_device(dev); goto free_interfaces; @@ -1838,6 +1873,9 @@ free_interfaces: !(dev->quirks & USB_QUIRK_CONFIG_INTF_STRINGS)) cp->string = usb_cache_string(dev, cp->desc.iConfiguration); + /* Now that the interfaces are installed, re-enable LPM. */ + usb_unlocked_enable_lpm(dev); + /* Now that all the interfaces are set up, register them * to trigger binding of drivers to interfaces. probe() * routines may install different altsettings and may |