From ae8963adb4ad8c5f2a89ca1d99fb7bb721e7599f Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Wed, 3 Oct 2012 11:18:05 -0700 Subject: usb: Don't enable LPM if the exit latency is zero. Some USB 3.0 devices signal that they don't implement Link PM by having all zeroes in the U1/U2 exit latencies in their SuperSpeed BOS descriptor. Don found that a Western Digital device he has experiences transfer errors when LPM is enabled. The lsusb shows the U1/U2 exit latencies are set to zero: Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 22 bNumDeviceCaps 2 SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 Latency Tolerance Messages (LTM) Supported wSpeedsSupported 0x000e Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 0 micro seconds bU2DevExitLat 0 micro seconds The fix is to not enable LPM for a particular link state if we find its corresponding exit latency is zero. This patch should be backported to kernels as old as 3.5, that contain the commit 1ea7e0e8e3d0f50901d335ea4178ab2aa8c88201 "USB: Add support to enable/disable USB3 link states." Signed-off-by: Sarah Sharp Reported-by: Don Zickus Tested-by: Don Zickus Cc: stable@vger.kernel.org --- drivers/usb/core/hub.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 673ee4696262..8f0478709323 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3415,6 +3415,16 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { int timeout; + __u8 u1_mel = udev->bos->ss_cap->bU1devExitLat; + __le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat; + + /* If the device says it doesn't have *any* exit latency to come out of + * U1 or U2, it's probably lying. Assume it doesn't implement that link + * state. + */ + if ((state == USB3_LPM_U1 && u1_mel == 0) || + (state == USB3_LPM_U2 && u2_mel == 0)) + return; /* We allow the host controller to set the U1/U2 timeout internally * first, so that it can change its schedule to account for the -- cgit v1.2.3 From 65a95b75bc5afa7bbb844e222481044c1c4767eb Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 5 Oct 2012 10:32:07 -0700 Subject: usb: Send Set SEL before enabling parent U1/U2 timeout. The Set SEL control transfer tells a device the exit latencies associated with a device-initated U1 or U2 exit. Since a parent hub may initiate a transition to U1 soon after a downstream port's U1 timeout is set, we need to make sure the device receives the Set SEL transfer before the parent hub timeout is set. This patch should be backported to kernels as old as 3.5, that contain the commit 1ea7e0e8e3d0f50901d335ea4178ab2aa8c88201 "USB: Add support to enable/disable USB3 link states." Signed-off-by: Sarah Sharp Cc: stable@vger.kernel.org --- drivers/usb/core/hub.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 8f0478709323..b8e11fff17be 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3319,16 +3319,6 @@ static int usb_set_device_initiated_lpm(struct usb_device *udev, } if (enable) { - /* - * First, let the device know about the exit latencies - * associated with the link state we're about to enable. - */ - ret = usb_req_set_sel(udev, state); - if (ret < 0) { - dev_warn(&udev->dev, "Set SEL for device-initiated " - "%s failed.\n", usb3_lpm_names[state]); - return -EBUSY; - } /* * Now send the control transfer to enable device-initiated LPM * for either U1 or U2. @@ -3414,7 +3404,7 @@ static int usb_set_lpm_timeout(struct usb_device *udev, static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, enum usb3_link_state state) { - int timeout; + int timeout, ret; __u8 u1_mel = udev->bos->ss_cap->bU1devExitLat; __le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat; @@ -3426,6 +3416,17 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, (state == USB3_LPM_U2 && u2_mel == 0)) return; + /* + * First, let the device know about the exit latencies + * associated with the link state we're about to enable. + */ + ret = usb_req_set_sel(udev, state); + if (ret < 0) { + dev_warn(&udev->dev, "Set SEL for device-initiated %s failed.\n", + usb3_lpm_names[state]); + return; + } + /* We allow the host controller to set the U1/U2 timeout internally * first, so that it can change its schedule to account for the * additional latency to send data to a device in a lower power -- cgit v1.2.3 From 1510a1a2d0946b34422fe8816187f274a5086904 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Fri, 5 Oct 2012 10:30:35 -0700 Subject: usb: trival: Fix debugging units mistake. SEL and PEL are in microseconds, not milliseconds. Also, fix a split string that will trigger checkpatch warnings. Signed-off-by: Sarah Sharp --- drivers/usb/core/hub.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b8e11fff17be..64854d76f529 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3241,8 +3241,7 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state) (state == USB3_LPM_U2 && (u2_sel > USB3_LPM_MAX_U2_SEL_PEL || u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) { - dev_dbg(&udev->dev, "Device-initiated %s disabled due " - "to long SEL %llu ms or PEL %llu ms\n", + dev_dbg(&udev->dev, "Device-initiated %s disabled due to long SEL %llu us or PEL %llu us\n", usb3_lpm_names[state], u1_sel, u1_pel); return -EINVAL; } -- cgit v1.2.3