]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
xhci: fix USB3 device initiated resume race with roothub autosuspend
authorMathias Nyman <mathias.nyman@linux.intel.com>
Wed, 11 Dec 2019 14:20:03 +0000 (16:20 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 21 Dec 2019 09:42:34 +0000 (10:42 +0100)
commit 057d476fff778f1d3b9f861fdb5437ea1a3cfc99 upstream.

A race in xhci USB3 remote wake handling may force device back to suspend
after it initiated resume siganaling, causing a missed resume event or warm
reset of device.

When a USB3 link completes resume signaling and goes to enabled (UO)
state a interrupt is issued and the interrupt handler will clear the
bus_state->port_remote_wakeup resume flag, allowing bus suspend.

If the USB3 roothub thread just finished reading port status before
the interrupt, finding ports still in suspended (U3) state, but hasn't
yet started suspending the hub, then the xhci interrupt handler will clear
the flag that prevented roothub suspend and allow bus to suspend, forcing
all port links back to suspended (U3) state.

Example case:
usb_runtime_suspend() # because all ports still show suspended U3
  usb_suspend_both()
    hub_suspend();   # successful as hub->wakeup_bits not set yet
==> INTERRUPT
xhci_irq()
  handle_port_status()
    clear bus_state->port_remote_wakeup
    usb_wakeup_notification()
      sets hub->wakeup_bits;
        kick_hub_wq()
<== END INTERRUPT
      hcd_bus_suspend()
        xhci_bus_suspend() # success as port_remote_wakeup bits cleared

Fix this by increasing roothub usage count during port resume to prevent
roothub autosuspend, and by making sure bus_state->port_remote_wakeup
flag is only cleared after resume completion is visible, i.e.
after xhci roothub returned U0 or other non-U3 link state link on a
get port status request.

Issue rootcaused by Chiasheng Lee

Cc: <stable@vger.kernel.org>
Cc: Lee, Hou-hsun <hou-hsun.lee@intel.com>
Reported-by: Lee, Chiasheng <chiasheng.lee@intel.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20191211142007.8847-3-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-hub.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h

index 6777a81fb372b2139c334a1479ca1f59e3c568f6..5cf5f3d9c1e59a77d0bdc4b19f9d44c1d2b62d68 100644 (file)
@@ -760,6 +760,14 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
                        status |= USB_PORT_STAT_C_BH_RESET << 16;
                if ((raw_port_status & PORT_CEC))
                        status |= USB_PORT_STAT_C_CONFIG_ERROR << 16;
+
+               /* USB3 remote wake resume signaling completed */
+               if (bus_state->port_remote_wakeup & (1 << wIndex) &&
+                   (raw_port_status & PORT_PLS_MASK) != XDEV_RESUME &&
+                   (raw_port_status & PORT_PLS_MASK) != XDEV_RECOVERY) {
+                       bus_state->port_remote_wakeup &= ~(1 << wIndex);
+                       usb_hcd_end_port_resume(&hcd->self, wIndex);
+               }
        }
 
        if (hcd->speed < HCD_USB3) {
index 69ad9817076ad590179ec362c23bb7212f4e1d4c..b426c83ecb9b948324e253dab28a656960680fa0 100644 (file)
@@ -1609,9 +1609,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
                usb_hcd_resume_root_hub(hcd);
        }
 
-       if (hcd->speed >= HCD_USB3 && (temp & PORT_PLS_MASK) == XDEV_INACTIVE)
-               bus_state->port_remote_wakeup &= ~(1 << faked_port_index);
-
        if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_RESUME) {
                xhci_dbg(xhci, "port resume event for port %d\n", port_id);
 
@@ -1630,6 +1627,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
                        bus_state->port_remote_wakeup |= 1 << faked_port_index;
                        xhci_test_and_clear_bit(xhci, port_array,
                                        faked_port_index, PORT_PLC);
+                       usb_hcd_start_port_resume(&hcd->self, faked_port_index);
                        xhci_set_link_state(xhci, port_array, faked_port_index,
                                                XDEV_U0);
                        /* Need to wait until the next link state change
@@ -1667,8 +1665,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
                if (slot_id && xhci->devs[slot_id])
                        xhci_ring_device(xhci, slot_id);
                if (bus_state->port_remote_wakeup & (1 << faked_port_index)) {
-                       bus_state->port_remote_wakeup &=
-                               ~(1 << faked_port_index);
                        xhci_test_and_clear_bit(xhci, port_array,
                                        faked_port_index, PORT_PLC);
                        usb_wakeup_notification(hcd->self.root_hub,
index 7472de2f704e356c62be86ba089a360268024be0..2b603ef5c10cd4464b6f17d2ecf75f78319c5ec6 100644 (file)
@@ -316,6 +316,7 @@ struct xhci_op_regs {
 #define XDEV_U3                (0x3 << 5)
 #define XDEV_INACTIVE  (0x6 << 5)
 #define XDEV_POLLING   (0x7 << 5)
+#define XDEV_RECOVERY  (0x8 << 5)
 #define XDEV_COMP_MODE  (0xa << 5)
 #define XDEV_RESUME    (0xf << 5)
 /* true: port has power (see HCC_PPC) */