| From 1b7f56fbc7a1b66967b6114d1b5f5a257c3abae6 Mon Sep 17 00:00:00 2001 |
| From: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Date: Thu, 15 Jul 2021 18:01:22 +0300 |
| Subject: usb: hub: Disable USB 3 device initiated lpm if exit latency is too high |
| |
| From: Mathias Nyman <mathias.nyman@linux.intel.com> |
| |
| commit 1b7f56fbc7a1b66967b6114d1b5f5a257c3abae6 upstream. |
| |
| The device initiated link power management U1/U2 states should not be |
| enabled in case the system exit latency plus one bus interval (125us) is |
| greater than the shortest service interval of any periodic endpoint. |
| |
| This is the case for both U1 and U2 sytstem exit latencies and link states. |
| |
| See USB 3.2 section 9.4.9 "Set Feature" for more details |
| |
| Note, before this patch the host and device initiated U1/U2 lpm states |
| were both enabled with lpm. After this patch it's possible to end up with |
| only host inititated U1/U2 lpm in case the exit latencies won't allow |
| device initiated lpm. |
| |
| If this case we still want to set the udev->usb3_lpm_ux_enabled flag so |
| that sysfs users can see the link may go to U1/U2. |
| |
| Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Cc: stable <stable@vger.kernel.org> |
| Link: https://lore.kernel.org/r/20210715150122.1995966-2-mathias.nyman@linux.intel.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/usb/core/hub.c | 68 ++++++++++++++++++++++++++++++++++++++++--------- |
| 1 file changed, 56 insertions(+), 12 deletions(-) |
| |
| --- a/drivers/usb/core/hub.c |
| +++ b/drivers/usb/core/hub.c |
| @@ -4091,6 +4091,47 @@ static int usb_set_lpm_timeout(struct us |
| } |
| |
| /* |
| + * Don't allow device intiated U1/U2 if the system exit latency + one bus |
| + * interval is greater than the minimum service interval of any active |
| + * periodic endpoint. See USB 3.2 section 9.4.9 |
| + */ |
| +static bool usb_device_may_initiate_lpm(struct usb_device *udev, |
| + enum usb3_link_state state) |
| +{ |
| + unsigned int sel; /* us */ |
| + int i, j; |
| + |
| + if (state == USB3_LPM_U1) |
| + sel = DIV_ROUND_UP(udev->u1_params.sel, 1000); |
| + else if (state == USB3_LPM_U2) |
| + sel = DIV_ROUND_UP(udev->u2_params.sel, 1000); |
| + else |
| + return false; |
| + |
| + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { |
| + struct usb_interface *intf; |
| + struct usb_endpoint_descriptor *desc; |
| + unsigned int interval; |
| + |
| + intf = udev->actconfig->interface[i]; |
| + if (!intf) |
| + continue; |
| + |
| + for (j = 0; j < intf->cur_altsetting->desc.bNumEndpoints; j++) { |
| + desc = &intf->cur_altsetting->endpoint[j].desc; |
| + |
| + if (usb_endpoint_xfer_int(desc) || |
| + usb_endpoint_xfer_isoc(desc)) { |
| + interval = (1 << (desc->bInterval - 1)) * 125; |
| + if (sel + 125 > interval) |
| + return false; |
| + } |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +/* |
| * Enable the hub-initiated U1/U2 idle timeouts, and enable device-initiated |
| * U1/U2 entry. |
| * |
| @@ -4162,20 +4203,23 @@ static void usb_enable_link_state(struct |
| * U1/U2_ENABLE |
| */ |
| if (udev->actconfig && |
| - usb_set_device_initiated_lpm(udev, state, true) == 0) { |
| - if (state == USB3_LPM_U1) |
| - udev->usb3_lpm_u1_enabled = 1; |
| - else if (state == USB3_LPM_U2) |
| - udev->usb3_lpm_u2_enabled = 1; |
| - } else { |
| - /* Don't request U1/U2 entry if the device |
| - * cannot transition to U1/U2. |
| - */ |
| - usb_set_lpm_timeout(udev, state, 0); |
| - hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); |
| + usb_device_may_initiate_lpm(udev, state)) { |
| + if (usb_set_device_initiated_lpm(udev, state, true)) { |
| + /* |
| + * Request to enable device initiated U1/U2 failed, |
| + * better to turn off lpm in this case. |
| + */ |
| + usb_set_lpm_timeout(udev, state, 0); |
| + hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state); |
| + return; |
| + } |
| } |
| -} |
| |
| + if (state == USB3_LPM_U1) |
| + udev->usb3_lpm_u1_enabled = 1; |
| + else if (state == USB3_LPM_U2) |
| + udev->usb3_lpm_u2_enabled = 1; |
| +} |
| /* |
| * Disable the hub-initiated U1/U2 idle timeouts, and disable device-initiated |
| * U1/U2 entry. |