From de68bab4fa96014cfaa6fcbcdb9750e32969fb86 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Mon, 30 Sep 2013 17:26:28 +0300 Subject: [PATCH] usb: Don't enable USB 2.0 Link PM by default. How it's supposed to work: -------------------------- USB 2.0 Link PM is a lower power state that some newer USB 2.0 devices support. USB 3.0 devices certified by the USB-IF are required to support it if they are plugged into a USB 2.0 only port, or a USB 2.0 cable is used. USB 2.0 Link PM requires both a USB device and a host controller that supports USB 2.0 hardware-enabled LPM. USB 2.0 Link PM is designed to be enabled once by software, and the host hardware handles transitions to the L1 state automatically. The premise of USB 2.0 Link PM is to be able to put the device into a lower power link state when the bus is idle or the device NAKs USB IN transfers for a specified amount of time. ...but hardware is broken: -------------------------- It turns out many USB 3.0 devices claim to support USB 2.0 Link PM (by setting the LPM bit in their USB 2.0 BOS descriptor), but they don't actually implement it correctly. This manifests as the USB device refusing to respond to transfers when it is plugged into a USB 2.0 only port under the Haswell-ULT/Lynx Point LP xHCI host. These devices pass the xHCI driver's simple test to enable USB 2.0 Link PM, wait for the port to enter L1, and then bring it back into L0. They only start to break when L1 entry is interleaved with transfers. Some devices then fail to respond to the next control transfer (usually a Set Configuration). This results in devices never enumerating. Other mass storage devices (such as a later model Western Digital My Passport USB 3.0 hard drive) respond fine to going into L1 between control transfers. They ACK the entry, come out of L1 when the host needs to send a control transfer, and respond properly to those control transfers. However, when the first READ10 SCSI command is sent, the device NAKs the data phase while it's reading from the spinning disk. Eventually, the host requests to put the link into L1, and the device ACKs that request. Then it never responds to the data phase of the READ10 command. This results in not being able to read from the drive. Some mass storage devices (like the Corsair Survivor USB 3.0 flash drive) are well behaved. They ACK the entry into L1 during control transfers, and when SCSI commands start coming in, they NAK the requests to go into L1, because they need to be at full power. Not all USB 3.0 devices advertise USB 2.0 link PM support. My Point Grey USB 3.0 webcam advertises itself as a USB 2.1 device, but doesn't have a USB 2.0 BOS descriptor, so we don't enable USB 2.0 Link PM. I suspect that means the device isn't certified. What do we do about it? ----------------------- There's really no good way for the kernel to test these devices. Therefore, the kernel needs to disable USB 2.0 Link PM by default, and distros will have to enable it by writing 1 to the sysfs file /sys/bus/usb/devices/../power/usb2_hardware_lpm. Rip out the xHCI Link PM test, since it's not sufficient to detect these buggy devices, and don't automatically enable LPM after the device is addressed. This patch should be backported to kernels as old as 3.11, that contain the commit a558ccdcc71c7770c5e80c926a31cfe8a3892a09 "usb: xhci: add USB2 Link power management BESL support". Without this fix, some USB 3.0 devices will not enumerate or work properly under USB 2.0 ports on Haswell-ULT systems. Signed-off-by: Sarah Sharp Cc: stable@vger.kernel.org --- drivers/usb/core/driver.c | 3 + drivers/usb/core/hub.c | 1 + drivers/usb/core/sysfs.c | 6 +- drivers/usb/host/xhci-mem.c | 10 --- drivers/usb/host/xhci.c | 161 ++++-------------------------------- include/linux/usb.h | 4 +- 6 files changed, 29 insertions(+), 156 deletions(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f7841d44feda..689433cdef25 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1790,6 +1790,9 @@ int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) struct usb_hcd *hcd = bus_to_hcd(udev->bus); int ret = -EPERM; + if (enable && !udev->usb2_hw_lpm_allowed) + return 0; + if (hcd->driver->set_usb2_hw_lpm) { ret = hcd->driver->set_usb2_hw_lpm(hcd, udev, enable); if (!ret) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index d3a1d79d663d..9be1a2d5fba6 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5203,6 +5203,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) done: /* Now that the alt settings are re-installed, enable LTM and LPM. */ + usb_set_usb2_hardware_lpm(udev, 1); usb_unlocked_enable_lpm(udev); usb_enable_ltm(udev); usb_release_bos_descriptor(udev); diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 5cf431b0424c..52a97adf02a0 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -458,7 +458,7 @@ static ssize_t usb2_hardware_lpm_show(struct device *dev, struct usb_device *udev = to_usb_device(dev); const char *p; - if (udev->usb2_hw_lpm_enabled == 1) + if (udev->usb2_hw_lpm_allowed == 1) p = "enabled"; else p = "disabled"; @@ -478,8 +478,10 @@ static ssize_t usb2_hardware_lpm_store(struct device *dev, ret = strtobool(buf, &value); - if (!ret) + if (!ret) { + udev->usb2_hw_lpm_allowed = value; ret = usb_set_usb2_hardware_lpm(udev, value); + } usb_unlock_device(udev); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 83bcd13622c3..49b8bd063fab 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1693,9 +1693,7 @@ void xhci_free_command(struct xhci_hcd *xhci, void xhci_mem_cleanup(struct xhci_hcd *xhci) { struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); - struct dev_info *dev_info, *next; struct xhci_cd *cur_cd, *next_cd; - unsigned long flags; int size; int i, j, num_ports; @@ -1756,13 +1754,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) scratchpad_free(xhci); - spin_lock_irqsave(&xhci->lock, flags); - list_for_each_entry_safe(dev_info, next, &xhci->lpm_failed_devs, list) { - list_del(&dev_info->list); - kfree(dev_info); - } - spin_unlock_irqrestore(&xhci->lock, flags); - if (!xhci->rh_bw) goto no_bw; @@ -2231,7 +2222,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) u32 page_size, temp; int i; - INIT_LIST_HEAD(&xhci->lpm_failed_devs); INIT_LIST_HEAD(&xhci->cancel_cmd_list); page_size = xhci_readl(xhci, &xhci->op_regs->page_size); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2983e5dd98bf..0ea253ec813d 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -4013,133 +4013,6 @@ static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev) return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm); } -static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, - struct usb_device *udev) -{ - struct xhci_hcd *xhci = hcd_to_xhci(hcd); - struct dev_info *dev_info; - __le32 __iomem **port_array; - __le32 __iomem *addr, *pm_addr; - u32 temp, dev_id; - unsigned int port_num; - unsigned long flags; - int hird; - int ret; - - if (hcd->speed == HCD_USB3 || !xhci->sw_lpm_support || - !udev->lpm_capable) - return -EINVAL; - - /* we only support lpm for non-hub device connected to root hub yet */ - if (!udev->parent || udev->parent->parent || - udev->descriptor.bDeviceClass == USB_CLASS_HUB) - return -EINVAL; - - spin_lock_irqsave(&xhci->lock, flags); - - /* Look for devices in lpm_failed_devs list */ - dev_id = le16_to_cpu(udev->descriptor.idVendor) << 16 | - le16_to_cpu(udev->descriptor.idProduct); - list_for_each_entry(dev_info, &xhci->lpm_failed_devs, list) { - if (dev_info->dev_id == dev_id) { - ret = -EINVAL; - goto finish; - } - } - - port_array = xhci->usb2_ports; - port_num = udev->portnum - 1; - - if (port_num > HCS_MAX_PORTS(xhci->hcs_params1)) { - xhci_dbg(xhci, "invalid port number %d\n", udev->portnum); - ret = -EINVAL; - goto finish; - } - - /* - * Test USB 2.0 software LPM. - * FIXME: some xHCI 1.0 hosts may implement a new register to set up - * hardware-controlled USB 2.0 LPM. See section 5.4.11 and 4.23.5.1.1.1 - * in the June 2011 errata release. - */ - xhci_dbg(xhci, "test port %d software LPM\n", port_num); - /* - * Set L1 Device Slot and HIRD/BESL. - * Check device's USB 2.0 extension descriptor to determine whether - * HIRD or BESL shoule be used. See USB2.0 LPM errata. - */ - pm_addr = port_array[port_num] + PORTPMSC; - hird = xhci_calculate_hird_besl(xhci, udev); - temp = PORT_L1DS(udev->slot_id) | PORT_HIRD(hird); - xhci_writel(xhci, temp, pm_addr); - - /* Set port link state to U2(L1) */ - addr = port_array[port_num]; - xhci_set_link_state(xhci, port_array, port_num, XDEV_U2); - - /* wait for ACK */ - spin_unlock_irqrestore(&xhci->lock, flags); - msleep(10); - spin_lock_irqsave(&xhci->lock, flags); - - /* Check L1 Status */ - ret = xhci_handshake(xhci, pm_addr, - PORT_L1S_MASK, PORT_L1S_SUCCESS, 125); - if (ret != -ETIMEDOUT) { - /* enter L1 successfully */ - temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, "port %d entered L1 state, port status 0x%x\n", - port_num, temp); - ret = 0; - } else { - temp = xhci_readl(xhci, pm_addr); - xhci_dbg(xhci, "port %d software lpm failed, L1 status %d\n", - port_num, temp & PORT_L1S_MASK); - ret = -EINVAL; - } - - /* Resume the port */ - xhci_set_link_state(xhci, port_array, port_num, XDEV_U0); - - spin_unlock_irqrestore(&xhci->lock, flags); - msleep(10); - spin_lock_irqsave(&xhci->lock, flags); - - /* Clear PLC */ - xhci_test_and_clear_bit(xhci, port_array, port_num, PORT_PLC); - - /* Check PORTSC to make sure the device is in the right state */ - if (!ret) { - temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, "resumed port %d status 0x%x\n", port_num, temp); - if (!(temp & PORT_CONNECT) || !(temp & PORT_PE) || - (temp & PORT_PLS_MASK) != XDEV_U0) { - xhci_dbg(xhci, "port L1 resume fail\n"); - ret = -EINVAL; - } - } - - if (ret) { - /* Insert dev to lpm_failed_devs list */ - xhci_warn(xhci, "device LPM test failed, may disconnect and " - "re-enumerate\n"); - dev_info = kzalloc(sizeof(struct dev_info), GFP_ATOMIC); - if (!dev_info) { - ret = -ENOMEM; - goto finish; - } - dev_info->dev_id = dev_id; - INIT_LIST_HEAD(&dev_info->list); - list_add(&dev_info->list, &xhci->lpm_failed_devs); - } else { - xhci_ring_device(xhci, udev->slot_id); - } - -finish: - spin_unlock_irqrestore(&xhci->lock, flags); - return ret; -} - int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, struct usb_device *udev, int enable) { @@ -4267,24 +4140,26 @@ static int xhci_check_usb2_port_capability(struct xhci_hcd *xhci, int port, int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); - int ret; int portnum = udev->portnum - 1; - ret = xhci_usb2_software_lpm_test(hcd, udev); - if (!ret) { - xhci_dbg(xhci, "software LPM test succeed\n"); - if (xhci->hw_lpm_support == 1 && - xhci_check_usb2_port_capability(xhci, portnum, XHCI_HLC)) { - udev->usb2_hw_lpm_capable = 1; - udev->l1_params.timeout = XHCI_L1_TIMEOUT; - udev->l1_params.besl = XHCI_DEFAULT_BESL; - if (xhci_check_usb2_port_capability(xhci, portnum, - XHCI_BLC)) - udev->usb2_hw_lpm_besl_capable = 1; - ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1); - if (!ret) - udev->usb2_hw_lpm_enabled = 1; - } + if (hcd->speed == HCD_USB3 || !xhci->sw_lpm_support || + !udev->lpm_capable) + return 0; + + /* we only support lpm for non-hub device connected to root hub yet */ + if (!udev->parent || udev->parent->parent || + udev->descriptor.bDeviceClass == USB_CLASS_HUB) + return 0; + + if (xhci->hw_lpm_support == 1 && + xhci_check_usb2_port_capability( + xhci, portnum, XHCI_HLC)) { + udev->usb2_hw_lpm_capable = 1; + udev->l1_params.timeout = XHCI_L1_TIMEOUT; + udev->l1_params.besl = XHCI_DEFAULT_BESL; + if (xhci_check_usb2_port_capability(xhci, portnum, + XHCI_BLC)) + udev->usb2_hw_lpm_besl_capable = 1; } return 0; diff --git a/include/linux/usb.h b/include/linux/usb.h index 055ba74bee80..7454865ad148 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -475,7 +475,8 @@ struct usb3_lpm_parameters { * @lpm_capable: device supports LPM * @usb2_hw_lpm_capable: device can perform USB2 hardware LPM * @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM - * @usb2_hw_lpm_enabled: USB2 hardware LPM enabled + * @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled + * @usb2_hw_lpm_allowed: Userspace allows USB 2.0 LPM to be enabled * @usb3_lpm_enabled: USB3 hardware LPM enabled * @string_langid: language ID for strings * @product: iProduct string, if present (static) @@ -548,6 +549,7 @@ struct usb_device { unsigned usb2_hw_lpm_capable:1; unsigned usb2_hw_lpm_besl_capable:1; unsigned usb2_hw_lpm_enabled:1; + unsigned usb2_hw_lpm_allowed:1; unsigned usb3_lpm_enabled:1; int string_langid; -- GitLab