USB: Enable Latency Tolerance Messaging (LTM).
USB 3.0 devices may optionally support a new feature called Latency Tolerance Messaging. If both the xHCI host controller and the device support LTM, it should be turned on in order to give the system hardware a better clue about the latency tolerance values of its PCI devices. Once a Set Feature request to enable LTM is received, the USB 3.0 device will begin to send LTM updates as its buffers fill or empty, and it can tolerate more or less latency. The USB 3.0 spec, section C.4.2 says that LTM should be disabled just before the device is placed into suspend. Then the device will send an updated LTM notification, so that the system doesn't think it should remain in an active state in order to satisfy the latency requirements of the suspended device. The Set and Clear Feature LTM enable command can only be sent to a configured device. The device will respond with an error if that command is sent while it is in the Default or Addressed state. Make sure to check udev->actconfig in usb_enable_ltm() and usb_disable_ltm(), and don't send those commands when the device is unconfigured. LTM should be enabled once a new configuration is installed in usb_set_configuration(). If we end up sending duplicate Set Feature LTM Enable commands on a switch from one installed configuration to another configuration, that should be harmless. Make sure that LTM is disabled before the device is unconfigured in usb_disable_device(). If no drivers are bound to the device, it doesn't make sense to allow the device to control the latency tolerance of the xHCI host controller. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
This commit is contained in:
parent
af3a23ef4e
commit
f74631e342
|
@ -2610,6 +2610,57 @@ static int check_port_resume_type(struct usb_device *udev,
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool usb_device_supports_ltm(struct usb_device *udev)
|
||||||
|
{
|
||||||
|
if (udev->speed != USB_SPEED_SUPER || !udev->bos || !udev->bos->ss_cap)
|
||||||
|
return false;
|
||||||
|
return udev->bos->ss_cap->bmAttributes & USB_LTM_SUPPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_disable_ltm(struct usb_device *udev)
|
||||||
|
{
|
||||||
|
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
|
||||||
|
|
||||||
|
/* Check if the roothub and device supports LTM. */
|
||||||
|
if (!usb_device_supports_ltm(hcd->self.root_hub) ||
|
||||||
|
!usb_device_supports_ltm(udev))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Clear Feature LTM Enable can only be sent if the device is
|
||||||
|
* configured.
|
||||||
|
*/
|
||||||
|
if (!udev->actconfig)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
|
||||||
|
USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,
|
||||||
|
USB_DEVICE_LTM_ENABLE, 0, NULL, 0,
|
||||||
|
USB_CTRL_SET_TIMEOUT);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(usb_disable_ltm);
|
||||||
|
|
||||||
|
void usb_enable_ltm(struct usb_device *udev)
|
||||||
|
{
|
||||||
|
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
|
||||||
|
|
||||||
|
/* Check if the roothub and device supports LTM. */
|
||||||
|
if (!usb_device_supports_ltm(hcd->self.root_hub) ||
|
||||||
|
!usb_device_supports_ltm(udev))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Set Feature LTM Enable can only be sent if the device is
|
||||||
|
* configured.
|
||||||
|
*/
|
||||||
|
if (!udev->actconfig)
|
||||||
|
return;
|
||||||
|
|
||||||
|
usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
|
||||||
|
USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
|
||||||
|
USB_DEVICE_LTM_ENABLE, 0, NULL, 0,
|
||||||
|
USB_CTRL_SET_TIMEOUT);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(usb_enable_ltm);
|
||||||
|
|
||||||
#ifdef CONFIG_USB_SUSPEND
|
#ifdef CONFIG_USB_SUSPEND
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2705,6 +2756,11 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
|
||||||
if (udev->usb2_hw_lpm_enabled == 1)
|
if (udev->usb2_hw_lpm_enabled == 1)
|
||||||
usb_set_usb2_hardware_lpm(udev, 0);
|
usb_set_usb2_hardware_lpm(udev, 0);
|
||||||
|
|
||||||
|
if (usb_disable_ltm(udev)) {
|
||||||
|
dev_err(&udev->dev, "%s Failed to disable LTM before suspend\n.",
|
||||||
|
__func__);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
if (usb_unlocked_disable_lpm(udev)) {
|
if (usb_unlocked_disable_lpm(udev)) {
|
||||||
dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",
|
dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",
|
||||||
__func__);
|
__func__);
|
||||||
|
@ -2734,7 +2790,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
|
||||||
if (udev->usb2_hw_lpm_capable == 1)
|
if (udev->usb2_hw_lpm_capable == 1)
|
||||||
usb_set_usb2_hardware_lpm(udev, 1);
|
usb_set_usb2_hardware_lpm(udev, 1);
|
||||||
|
|
||||||
/* Try to enable USB3 LPM again */
|
/* Try to enable USB3 LTM and LPM again */
|
||||||
|
usb_enable_ltm(udev);
|
||||||
usb_unlocked_enable_lpm(udev);
|
usb_unlocked_enable_lpm(udev);
|
||||||
|
|
||||||
/* System sleep transitions should never fail */
|
/* System sleep transitions should never fail */
|
||||||
|
@ -2935,7 +2992,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
|
||||||
if (udev->usb2_hw_lpm_capable == 1)
|
if (udev->usb2_hw_lpm_capable == 1)
|
||||||
usb_set_usb2_hardware_lpm(udev, 1);
|
usb_set_usb2_hardware_lpm(udev, 1);
|
||||||
|
|
||||||
/* Try to enable USB3 LPM */
|
/* Try to enable USB3 LTM and LPM */
|
||||||
|
usb_enable_ltm(udev);
|
||||||
usb_unlocked_enable_lpm(udev);
|
usb_unlocked_enable_lpm(udev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3488,6 +3546,15 @@ EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
|
||||||
|
|
||||||
void usb_unlocked_enable_lpm(struct usb_device *udev) { }
|
void usb_unlocked_enable_lpm(struct usb_device *udev) { }
|
||||||
EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
|
EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
|
||||||
|
|
||||||
|
int usb_disable_ltm(struct usb_device *udev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(usb_disable_ltm);
|
||||||
|
|
||||||
|
void usb_enable_ltm(struct usb_device *udev) { }
|
||||||
|
EXPORT_SYMBOL_GPL(usb_enable_ltm);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -4673,15 +4740,22 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
|
||||||
}
|
}
|
||||||
parent_hub = hdev_to_hub(parent_hdev);
|
parent_hub = hdev_to_hub(parent_hdev);
|
||||||
|
|
||||||
/* Disable LPM while we reset the device and reinstall the alt settings.
|
/* Disable LPM and LTM while we reset the device and reinstall the alt
|
||||||
* Device-initiated LPM settings, and system exit latency settings are
|
* settings. Device-initiated LPM settings, and system exit latency
|
||||||
* cleared when the device is reset, so we have to set them up again.
|
* settings are cleared when the device is reset, so we have to set
|
||||||
|
* them up again.
|
||||||
*/
|
*/
|
||||||
ret = usb_unlocked_disable_lpm(udev);
|
ret = usb_unlocked_disable_lpm(udev);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__);
|
dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__);
|
||||||
goto re_enumerate;
|
goto re_enumerate;
|
||||||
}
|
}
|
||||||
|
ret = usb_disable_ltm(udev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&udev->dev, "%s Failed to disable LTM\n.",
|
||||||
|
__func__);
|
||||||
|
goto re_enumerate;
|
||||||
|
}
|
||||||
|
|
||||||
set_bit(port1, parent_hub->busy_bits);
|
set_bit(port1, parent_hub->busy_bits);
|
||||||
for (i = 0; i < SET_CONFIG_TRIES; ++i) {
|
for (i = 0; i < SET_CONFIG_TRIES; ++i) {
|
||||||
|
@ -4769,8 +4843,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
/* Now that the alt settings are re-installed, enable LPM. */
|
/* Now that the alt settings are re-installed, enable LTM and LPM. */
|
||||||
usb_unlocked_enable_lpm(udev);
|
usb_unlocked_enable_lpm(udev);
|
||||||
|
usb_enable_ltm(udev);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
re_enumerate:
|
re_enumerate:
|
||||||
|
|
|
@ -1175,6 +1175,7 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
|
||||||
dev->actconfig->interface[i] = NULL;
|
dev->actconfig->interface[i] = NULL;
|
||||||
}
|
}
|
||||||
usb_unlocked_disable_lpm(dev);
|
usb_unlocked_disable_lpm(dev);
|
||||||
|
usb_disable_ltm(dev);
|
||||||
dev->actconfig = NULL;
|
dev->actconfig = NULL;
|
||||||
if (dev->state == USB_STATE_CONFIGURED)
|
if (dev->state == USB_STATE_CONFIGURED)
|
||||||
usb_set_device_state(dev, USB_STATE_ADDRESS);
|
usb_set_device_state(dev, USB_STATE_ADDRESS);
|
||||||
|
@ -1879,6 +1880,8 @@ free_interfaces:
|
||||||
|
|
||||||
/* Now that the interfaces are installed, re-enable LPM. */
|
/* Now that the interfaces are installed, re-enable LPM. */
|
||||||
usb_unlocked_enable_lpm(dev);
|
usb_unlocked_enable_lpm(dev);
|
||||||
|
/* Enable LTM if it was turned off by usb_disable_device. */
|
||||||
|
usb_enable_ltm(dev);
|
||||||
|
|
||||||
/* Now that all the interfaces are set up, register them
|
/* Now that all the interfaces are set up, register them
|
||||||
* to trigger binding of drivers to interfaces. probe()
|
* to trigger binding of drivers to interfaces. probe()
|
||||||
|
|
|
@ -633,6 +633,9 @@ extern void usb_enable_lpm(struct usb_device *udev);
|
||||||
extern int usb_unlocked_disable_lpm(struct usb_device *udev);
|
extern int usb_unlocked_disable_lpm(struct usb_device *udev);
|
||||||
extern void usb_unlocked_enable_lpm(struct usb_device *udev);
|
extern void usb_unlocked_enable_lpm(struct usb_device *udev);
|
||||||
|
|
||||||
|
extern int usb_disable_ltm(struct usb_device *udev);
|
||||||
|
extern void usb_enable_ltm(struct usb_device *udev);
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/* for drivers using iso endpoints */
|
/* for drivers using iso endpoints */
|
||||||
|
|
Loading…
Reference in a new issue