aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
authorSarah Sharp <sarah.a.sharp@linux.intel.com>2012-05-02 14:25:52 -0700
committerSarah Sharp <sarah.a.sharp@linux.intel.com>2012-05-18 15:41:59 -0700
commit8306095fd2c1100e8244c09bf560f97aca5a311d (patch)
tree1096f11806046e60f32496f1bac843a6f17b4c26 /drivers/usb/core/hub.c
parent1ea7e0e8e3d0f50901d335ea4178ab2aa8c88201 (diff)
USB: Disable USB 3.0 LPM in critical sections.
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 LPM disable/enable functions to temporarily disable LPM around these critical sections. 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_unlocked_disable_lpm() in usb_port_suspend(), and call usb_unlocked_enable_lpm() in usb_port_resume(). If the port suspend fails, make sure to re-enable LPM by calling usb_unlocked_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 disable and then 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@linux.intel.com>
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c27
1 files changed, 27 insertions, 0 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index fd1ec481aec..fcc244e9056 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;