[RFC v2] usb: Fix xHCI host issues on remote wakeup.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When a device signals remote wakeup on a roothub, and the suspend change
bit is set, the host controller driver must not give control back to the
USB core until the port goes back into the active state.

EHCI accomplishes this by waiting in the get port status function until
the PORT_RESUME bit is cleared:

                        /* stop resume signaling */
                        temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME);
                        ehci_writel(ehci, temp, status_reg);
                        clear_bit(wIndex, &ehci->resuming_ports);
                        retval = ehci_handshake(ehci, status_reg,
                                        PORT_RESUME, 0, 2000 /* 2msec */);

Similarly, the xHCI host should wait until the port goes into U0, before
passing control up to the USB core.  When the port transitions from the
RExit state to U0, the xHCI driver will get a port status change event.
We need to wait for that event before passing control up to the USB
core.

After the port transitions to the active state, the USB core should time
a recovery interval before it talks to the device.  The length of that
recovery interval is TRSMRCY, 10 ms, mentioned in the USB 2.0 spec,
section 7.1.7.7.  The previous xHCI code (which did not wait for the
port to go into U0) would cause the USB core to violate that recovery
interval.

This bug caused numerous USB device disconnects on remote wakeup under
ChromeOS and a Lynx Point LP xHCI host that takes up to 20 ms to move
from RExit to U0.  ChromeOS is very aggressive about power savings, and
sets the autosuspend_delay to 100 ms, and disables USB persist.

I attempted to replicate this bug with Ubuntu 12.04, but could not.  I
used Ubuntu 12.04 on the same platform, with the same BIOS that the bug
was triggered on ChromeOS with.  I also changed the USB sysfs settings
as described above, but still could not reproduce the bug under Ubuntu.
It may be that ChromeOS userspace triggers this bug through additional
settings.

This patch should be backported to kernels as old as 2.6.37, that
contain the commit 561925318725a41189a69f36ebe99199b3fb84c4 "USB: xHCI:
port remote wakeup implementation".  It may be difficult to backport
this due to xhci->bus_state being unavailable in older kernels, and the
code to split xHCI roothubs into a USB 2.0 roothub and a USB 3.0 roothub
not going in until 2.6.39.

Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
 drivers/usb/host/xhci-hub.c  | 37 +++++++++++++++++++++++++++++--------
 drivers/usb/host/xhci-mem.c  |  2 ++
 drivers/usb/host/xhci-ring.c | 13 +++++++++++++
 drivers/usb/host/xhci.h      | 10 ++++++++++
 4 files changed, 54 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 187a3ec..b2fb67d 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -634,21 +634,42 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 				goto error;
 			if (time_after_eq(jiffies,
 					bus_state->resume_done[wIndex])) {
+				int time_left;
+
 				xhci_dbg(xhci, "Resume USB2 port %d\n",
 					wIndex + 1);
 				bus_state->resume_done[wIndex] = 0;
 				clear_bit(wIndex, &bus_state->resuming_ports);
+
+				set_bit(wIndex, &bus_state->rexit_ports);
 				xhci_set_link_state(xhci, port_array, wIndex,
 							XDEV_U0);
-				xhci_dbg(xhci, "set port %d resume\n",
-					wIndex + 1);
-				slot_id = xhci_find_slot_id_by_port(hcd, xhci,
-								 wIndex + 1);
-				if (!slot_id) {
-					xhci_dbg(xhci, "slot_id is zero\n");
-					goto error;
+
+				spin_unlock_irqrestore(&xhci->lock, flags);
+				time_left = wait_for_completion_timeout(
+						&bus_state->rexit_done[wIndex],
+						msecs_to_jiffies(
+							XHCI_MAX_REXIT_TIMEOUT));
+				spin_lock_irqsave(&xhci->lock, flags);
+
+				if (time_left) {
+					slot_id = xhci_find_slot_id_by_port(hcd,
+							xhci, wIndex + 1);
+					if (!slot_id) {
+						xhci_dbg(xhci, "slot_id is zero\n");
+						goto error;
+					}
+					xhci_ring_device(xhci, slot_id);
+				} else {
+					int port_status = xhci_readl(xhci,
+							port_array[wIndex]);
+					xhci_warn(xhci, "Port resume took longer than %i msec, port status = 0x%x\n",
+							XHCI_MAX_REXIT_TIMEOUT,
+							port_status);
+					status |= USB_PORT_STAT_SUSPEND;
+					clear_bit(wIndex, &bus_state->rexit_ports);
 				}
-				xhci_ring_device(xhci, slot_id);
+
 				bus_state->port_c_suspend |= 1 << wIndex;
 				bus_state->suspended_ports &= ~(1 << wIndex);
 			} else {
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index f2e57a1..19a0cc3 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -2448,6 +2448,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
 	for (i = 0; i < USB_MAXCHILDREN; ++i) {
 		xhci->bus_state[0].resume_done[i] = 0;
 		xhci->bus_state[1].resume_done[i] = 0;
+		/* Only the USB 2.0 completions will ever be used. */
+		init_completion(&xhci->bus_state[1].rexit_done[i]);
 	}
 
 	if (scratchpad_alloc(xhci, flags))
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index cc3bfc5..27ca8f5 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1712,6 +1712,19 @@ static void handle_port_status(struct xhci_hcd *xhci,
 		}
 	}
 
+	/*
+	 * Check to see if xhci-hub.c is waiting on RExit to U0 transition (or
+	 * RExit to a disconnect state).  If so, let the the driver know it's
+	 * out of the RExit state.
+	 */
+	if (!DEV_SUPERSPEED(temp) &&
+			test_and_clear_bit(faked_port_index,
+				&bus_state->rexit_ports)) {
+		complete(&bus_state->rexit_done[faked_port_index]);
+		bogus_port_status = true;
+		goto cleanup;
+	}
+
 	if (hcd->speed != HCD_USB3)
 		xhci_test_and_clear_bit(xhci, port_array, faked_port_index,
 					PORT_PLC);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 77600ce..f41df4b 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1386,8 +1386,18 @@ struct xhci_bus_state {
 	unsigned long		resume_done[USB_MAXCHILDREN];
 	/* which ports have started to resume */
 	unsigned long		resuming_ports;
+	/* Which ports are waiting on RExit to U0 transition. */
+	unsigned long		rexit_ports;
+	struct completion	rexit_done[USB_MAXCHILDREN];
 };
 
+
+/*
+ * It can take up to 20 ms to transition from RExit to U0 on the
+ * Intel Lynx Point LP xHCI host.
+ */
+#define	XHCI_MAX_REXIT_TIMEOUT	(20 * 1000)
+
 static inline unsigned int hcd_index(struct usb_hcd *hcd)
 {
 	if (hcd->speed == HCD_USB3)
-- 
1.8.3.3

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux