xhci: Don’t let USB3 ports stuck in polling state prevent suspend [Linux 3.16.72]

This Linux kernel change "xhci: Don’t let USB3 ports stuck in polling state prevent suspend" is included in the Linux 3.16.72 release. This change is authored by Mathias Nyman <mathias.nyman [at] linux.intel.com> on Fri Mar 22 17:50:17 2019 +0200. The commit for this change in Linux stable tree is a3a3b6c (patch) which is from upstream commit d92f2c5. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream d92f2c5.

xhci: Don't let USB3 ports stuck in polling state prevent suspend

commit d92f2c59cc2cbca6bfb2cc54882b58ba76b15fd4 upstream.

Commit 2f31a67f01a8 ("usb: xhci: Prevent bus suspend if a port connect
change or polling state is detected") was intended to prevent ports that
were still link training from being forced to U3 suspend state mid
This solved enumeration issues for devices with slow link training.

Turns out some devices are stuck in the link training/polling state,
and thus that patch will prevent suspend completely for these devices.
This is seen with USB3 card readers in some MacBooks.

Instead of preventing suspend, give some time to complete the link
training. On successful training the port will end up as connected
and enabled.
If port instead is stuck in link training the bus suspend will continue
suspending after 360ms (10 * 36ms) timeout (tPollingLFPSTimeout).

Original patch was sent to stable, this one should go there as well

Fixes: 2f31a67f01a8 ("usb: xhci: Prevent bus suspend if a port connect change or polling state is detected")
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>

There are 27 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/usb/host/xhci-hub.c | 19 ++++++++++++-------
 drivers/usb/host/xhci.h     |  8 ++++++++
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index fd697bb..dc3270e 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1199,20 +1199,25 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
    port_index = max_ports;
    while (port_index--) {
        u32 t1, t2;
+       int retries = 10;
        t1 = readl(port_array[port_index]);
        t2 = xhci_port_state_to_neutral(t1);
        portsc_buf[port_index] = 0;

-       /* Bail out if a USB3 port has a new device in link training */
-       if ((hcd->speed >= HCD_USB3) &&
+       /*
+        * Give a USB3 port in link training time to finish, but don't
+        * prevent suspend as port might be stuck
+        */
+       if ((hcd->speed >= HCD_USB3) && retries-- &&
            (t1 & PORT_PLS_MASK) == XDEV_POLLING) {
-           bus_state->bus_suspended = 0;
            spin_unlock_irqrestore(&xhci->lock, flags);
-           xhci_dbg(xhci, "Bus suspend bailout, port in polling\n");
-           return -EBUSY;
+           msleep(XHCI_PORT_POLLING_LFPS_TIME);
+           spin_lock_irqsave(&xhci->lock, flags);
+           xhci_dbg(xhci, "port %d polling in bus suspend, waiting\n",
+                port_index);
+           goto retry;
        /* suspend ports in U0, or bail out for new connect changes */
        if ((t1 & PORT_PE) && (t1 & PORT_PLS_MASK) == XDEV_U0) {
            if ((t1 & PORT_CSC) && wake_enabled) {
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index feb7025..bd191fb 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -413,6 +413,14 @@ struct xhci_op_regs {

+ * USB3 specification define a 360ms tPollingLFPSTiemout for USB3 ports
+ * to complete link training. usually link trainig completes much faster
+ * so check status 10 times with 36ms sleep in places we need to wait for
+ * polling to complete.
+ */
  * struct xhci_intr_reg - Interrupt Register Set
  * @irq_pending:   IMAN - Interrupt Management Register.  Used to enable

Leave a Reply

Your email address will not be published. Required fields are marked *