[RFC 07/13] USB: Disable USB 3.0 LPM in critical sections.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



There are several places where the USB core needs to disable USB 3.0
Link PM:
 - usb_bind_interface
 - usb_unbind_interface
 - usb_driver_claim_interface
 - usb_port_suspend/usb_port_resume
 - usb_reset_and_verify_device
 - usb_set_interface
 - usb_reset_configuration
 - usb_set_configuration

Use the new usb_disable_lpm() and usb_enable_lpm() functions
to disable LPM around these critical sections.  Make sure to always lock
the roothub bandwidth_mutex before calling into those functions.

We need to protect the critical section around binding and unbinding USB
interface drivers.  USB drivers may want to disable hub-initiated USB
3.0 LPM, which will change the value of the U1/U2 timeouts that the xHCI
driver will install.  We need to disable LPM completely until the driver
is bound to the interface, and the driver has a chance to enable
whatever alternate interface setting it needs in its probe routine.
Then re-enable USB3 LPM, and recalculate the U1/U2 timeout values.

We also need to disable LPM in usb_driver_claim_interface,
because drivers like usbfs can bind to an interface through that
function.  Note, there is no way currently for userspace drivers to
disable hub-initiated USB 3.0 LPM.  Revisit this later.

When a driver is unbound, the U1/U2 timeouts may change because we are
unbinding the last driver that needed hub-initiated USB 3.0 LPM to be
disabled.

USB LPM must be disabled when a USB device is going to be suspended.
The USB 3.0 spec does not define a state transition from U1 or U2 into
U3, so we need to bring the device into U0 by disabling LPM before we
can place it into U3.  Therefore, call usb_disable_lpm() in
usb_port_suspend(), and call usb_enable_lpm() in usb_port_resume().  If
the port suspend fails, make sure to re-enable LPM by calling
usb_enable_lpm(), since usb_port_resume() will not be called on a failed
port suspend.

USB 3.0 devices lose their USB 3.0 LPM settings (including whether USB
device-initiated LPM is enabled) across device suspend.  Therefore,
disable LPM before the device will be reset in
usb_reset_and_verify_device(), and re-enable LPM after the reset is
complete and the configuration/alt settings are re-installed.

The calculated U1/U2 timeout values are heavily dependent on what USB
device endpoints are currently enabled.  When any of the enabled
endpoints on the device might change, due to a new configuration, or new
alternate interface setting, we need to first disable USB 3.0 LPM, add
or delete endpoints from the xHCI schedule, install the new interfaces
and alt settings, and then re-enable LPM.  Do this in usb_set_interface,
usb_reset_configuration, and usb_set_configuration.

Basically, there is a call to usb_disable_lpm and usb_enable_lpm in all
functions that lock the bandwidth_mutex.  One exception is
usb_disable_device, because the device is disconnecting or otherwise
going away, and we should not care about whether USB 3.0 LPM is enabled.

Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx>
---
 drivers/usb/core/driver.c  |   71 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/hub.c     |   37 +++++++++++++++++++++++
 drivers/usb/core/message.c |   47 +++++++++++++++++++++++++++++
 include/linux/usb.h        |    2 +
 4 files changed, 157 insertions(+), 0 deletions(-)

diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 112a7ae..1975229 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -262,6 +262,7 @@ static int usb_probe_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 = interface_to_usbdev(intf);
+	struct usb_hcd *hcd = bus_to_hcd(udev->bus);
 	const struct usb_device_id *id;
 	int error = -ENODEV;
 
@@ -300,6 +301,28 @@ static int usb_probe_interface(struct device *dev)
 	if (driver->supports_autosuspend)
 		pm_runtime_enable(dev);
 
+	/* Disable LPM until this driver is bound. */
+	mutex_lock(hcd->bandwidth_mutex);
+	/* 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.
+	 */
+	error = usb_disable_lpm(udev);
+	if (error && driver->disable_hub_initiated_lpm) {
+		dev_err(&intf->dev, "%s Failed to disable LPM for driver %s\n.",
+				__func__, driver->name);
+		mutex_unlock(hcd->bandwidth_mutex);
+		goto err;
+	}
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* Carry out a deferred switch to altsetting 0 */
 	if (intf->needs_altsetting0) {
 		error = usb_set_interface(udev, intf->altsetting[0].
@@ -314,6 +337,11 @@ static int usb_probe_interface(struct device *dev)
 		goto err;
 
 	intf->condition = USB_INTERFACE_BOUND;
+
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(udev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	usb_autosuspend_device(udev);
 	return error;
 
@@ -322,6 +350,11 @@ static int usb_probe_interface(struct device *dev)
 	intf->condition = USB_INTERFACE_UNBOUND;
 	usb_cancel_queued_reset(intf);
 
+	/* Balance the LPM disable count. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(udev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* Unbound interfaces are always runtime-PM-disabled and -suspended */
 	if (driver->supports_autosuspend)
 		pm_runtime_disable(dev);
@@ -337,14 +370,25 @@ 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;
+	struct usb_hcd *hcd;
 	int error, r;
 
 	intf->condition = USB_INTERFACE_UNBINDING;
 
 	/* Autoresume for set_interface call below */
 	udev = interface_to_usbdev(intf);
+	hcd = bus_to_hcd(udev->bus);
 	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.
+	 */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_disable_lpm(udev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* Terminate all URBs for this interface unless the driver
 	 * supports "soft" unbinding.
 	 */
@@ -378,6 +422,11 @@ static int usb_unbind_interface(struct device *dev)
 	intf->condition = USB_INTERFACE_UNBOUND;
 	intf->needs_remote_wakeup = 0;
 
+	/* Attempt to re-enable USB3 LPM. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(udev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* Unbound interfaces are always runtime-PM-disabled and -suspended */
 	if (driver->supports_autosuspend)
 		pm_runtime_disable(dev);
@@ -418,17 +467,34 @@ int usb_driver_claim_interface(struct usb_driver *driver,
 				struct usb_interface *iface, void *priv)
 {
 	struct device *dev = &iface->dev;
+	struct usb_device *udev;
+	struct usb_hcd *hcd;
 	int retval = 0;
 
 	if (dev->driver)
 		return -EBUSY;
 
+	udev = interface_to_usbdev(iface);
+	hcd = bus_to_hcd(udev->bus);
+
 	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. */
+	mutex_lock(hcd->bandwidth_mutex);
+	retval = usb_disable_lpm(udev);
+	if (retval && driver->disable_hub_initiated_lpm) {
+		dev_err(&iface->dev, "%s Failed to disable LPM for driver %s\n.",
+				__func__, driver->name);
+		usb_enable_lpm(udev);
+		mutex_unlock(hcd->bandwidth_mutex);
+		return -ENOMEM;
+	}
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* 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
@@ -447,6 +513,11 @@ 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. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(udev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	return retval;
 }
 EXPORT_SYMBOL_GPL(usb_driver_claim_interface);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index b170505..dcfa00b 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2658,6 +2658,7 @@ static int check_port_resume_type(struct usb_device *udev,
 int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
 {
 	struct usb_hub	*hub = hdev_to_hub(udev->parent);
+	struct usb_hcd	*hcd = bus_to_hcd(udev->bus);
 	int		port1 = udev->portnum;
 	int		status;
 
@@ -2702,6 +2703,16 @@ 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);
 
+	mutex_lock(hcd->bandwidth_mutex);
+	if (usb_disable_lpm(udev)) {
+		dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",
+				__func__);
+		usb_enable_lpm(udev);
+		mutex_unlock(hcd->bandwidth_mutex);
+		return -ENOMEM;
+	}
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* see 7.1.7.6 */
 	if (hub_is_superspeed(hub->hdev))
 		status = set_port_feature(hub->hdev,
@@ -2856,6 +2867,7 @@ static int finish_port_resume(struct usb_device *udev)
 int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 {
 	struct usb_hub	*hub = hdev_to_hub(udev->parent);
+	struct usb_hcd	*hcd = bus_to_hcd(udev->bus);
 	int		port1 = udev->portnum;
 	int		status;
 	u16		portchange, portstatus;
@@ -2922,6 +2934,11 @@ 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 */
+		mutex_lock(hcd->bandwidth_mutex);
+		usb_enable_lpm(udev);
+		mutex_unlock(hcd->bandwidth_mutex);
 	}
 
 	return status;
@@ -4632,11 +4649,23 @@ 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__);
+		usb_enable_lpm(udev);
+		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;
 	}
@@ -4648,6 +4677,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;
   	}
@@ -4686,10 +4716,17 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
 				desc->bInterfaceNumber,
 				desc->bAlternateSetting,
 				ret);
+			mutex_lock(hcd->bandwidth_mutex);
+			usb_enable_lpm(udev);
+			mutex_unlock(hcd->bandwidth_mutex);
 			goto re_enumerate;
 		}
 	}
 
+	/* Now that the alt settings are re-installed, enable LPM. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(udev);
+	mutex_unlock(hcd->bandwidth_mutex);
 done:
 	return 0;
  
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index ca717da..74cdea6 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1308,10 +1308,20 @@ 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__);
+		usb_enable_lpm(dev);
+		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 +1344,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 +1365,11 @@ 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. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(dev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* 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 +1453,15 @@ 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__);
+		usb_enable_lpm(dev);
+		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 +1490,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 +1528,10 @@ reset_old_alts:
 			create_intf_ep_devs(intf);
 		}
 	}
+	/* Now that the interfaces are installed, re-enable LPM. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(dev);
+	mutex_unlock(hcd->bandwidth_mutex);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(usb_reset_configuration);
@@ -1763,8 +1793,19 @@ 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__);
+		usb_enable_lpm(dev);
+		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 +1825,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 +1880,11 @@ 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. */
+	mutex_lock(hcd->bandwidth_mutex);
+	usb_enable_lpm(dev);
+	mutex_unlock(hcd->bandwidth_mutex);
+
 	/* 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
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 6ef80e8..5b0133f 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -520,6 +520,7 @@ struct usb_device {
 	unsigned lpm_capable:1;
 	unsigned usb2_hw_lpm_capable:1;
 	unsigned usb2_hw_lpm_enabled:1;
+	unsigned usb3_lpm_enabled:1;
 	int string_langid;
 
 	/* static strings from the device */
@@ -549,6 +550,7 @@ struct usb_device {
 	struct usb3_lpm_parameters u1_params;
 	struct usb3_lpm_parameters u2_params;
 	unsigned lpm_disable_count;
+	unsigned hub_initiated_lpm_disable_count;
 };
 #define	to_usb_device(d) container_of(d, struct usb_device, dev)
 
-- 
1.7.9

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux