aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathias Nyman <mathias.nyman@linux.intel.com>2015-12-11 07:38:06 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-12-11 18:40:51 -0500
commitf69115fdbc1ac0718e7d19ad3caa3da2ecfe1c96 (patch)
tree183e889b5a822a118d0ace1cee4603cf609f57da
parent73b39bb0a0feefc9b2f410e213f20a7d4f715682 (diff)
xhci: fix usb2 resume timing and races.
According to USB 2 specs ports need to signal resume for at least 20ms, in practice even longer, before moving to U0 state. Both host and devices can initiate resume. On device initiated resume, a port status interrupt with the port in resume state in issued. The interrupt handler tags a resume_done[port] timestamp with current time + USB_RESUME_TIMEOUT, and kick roothub timer. Root hub timer requests for port status, finds the port in resume state, checks if resume_done[port] timestamp passed, and set port to U0 state. On host initiated resume, current code sets the port to resume state, sleep 20ms, and finally sets the port to U0 state. This should also be changed to work in a similar way as the device initiated resume, with timestamp tagging, but that is not yet tested and will be a separate fix later. There are a few issues with this approach 1. A host initiated resume will also generate a resume event. The event handler will find the port in resume state, believe it's a device initiated resume, and act accordingly. 2. A port status request might cut the resume signalling short if a get_port_status request is handled during the host resume signalling. The port will be found in resume state. The timestamp is not set leading to time_after_eq(jiffies, timestamp) returning true, as timestamp = 0. get_port_status will proceed with moving the port to U0. 3. If an error, or anything else happens to the port during device initiated resume signalling it will leave all the device resume parameters hanging uncleared, preventing further suspend, returning -EBUSY, and cause the pm thread to busyloop trying to enter suspend. Fix this by using the existing resuming_ports bitfield to indicate that resume signalling timing is taken care of. Check if the resume_done[port] is set before using it for timestamp comparison, and also clear out any resume signalling related variables if port is not in U0 or Resume state This issue was discovered when a PM thread busylooped, trying to runtime suspend the xhci USB 2 roothub on a Dell XPS Cc: stable <stable@vger.kernel.org> Reported-by: Daniel J Blueman <daniel@quora.org> Tested-by: Daniel J Blueman <daniel@quora.org> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/host/xhci-hub.c47
-rw-r--r--drivers/usb/host/xhci-ring.c3
2 files changed, 44 insertions, 6 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 0230965fb78c..f980c239eded 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -733,8 +733,30 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
733 if ((raw_port_status & PORT_RESET) || 733 if ((raw_port_status & PORT_RESET) ||
734 !(raw_port_status & PORT_PE)) 734 !(raw_port_status & PORT_PE))
735 return 0xffffffff; 735 return 0xffffffff;
736 if (time_after_eq(jiffies, 736 /* did port event handler already start resume timing? */
737 bus_state->resume_done[wIndex])) { 737 if (!bus_state->resume_done[wIndex]) {
738 /* If not, maybe we are in a host initated resume? */
739 if (test_bit(wIndex, &bus_state->resuming_ports)) {
740 /* Host initated resume doesn't time the resume
741 * signalling using resume_done[].
742 * It manually sets RESUME state, sleeps 20ms
743 * and sets U0 state. This should probably be
744 * changed, but not right now.
745 */
746 } else {
747 /* port resume was discovered now and here,
748 * start resume timing
749 */
750 unsigned long timeout = jiffies +
751 msecs_to_jiffies(USB_RESUME_TIMEOUT);
752
753 set_bit(wIndex, &bus_state->resuming_ports);
754 bus_state->resume_done[wIndex] = timeout;
755 mod_timer(&hcd->rh_timer, timeout);
756 }
757 /* Has resume been signalled for USB_RESUME_TIME yet? */
758 } else if (time_after_eq(jiffies,
759 bus_state->resume_done[wIndex])) {
738 int time_left; 760 int time_left;
739 761
740 xhci_dbg(xhci, "Resume USB2 port %d\n", 762 xhci_dbg(xhci, "Resume USB2 port %d\n",
@@ -775,13 +797,26 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
775 } else { 797 } else {
776 /* 798 /*
777 * The resume has been signaling for less than 799 * The resume has been signaling for less than
778 * 20ms. Report the port status as SUSPEND, 800 * USB_RESUME_TIME. Report the port status as SUSPEND,
779 * let the usbcore check port status again 801 * let the usbcore check port status again and clear
780 * and clear resume signaling later. 802 * resume signaling later.
781 */ 803 */
782 status |= USB_PORT_STAT_SUSPEND; 804 status |= USB_PORT_STAT_SUSPEND;
783 } 805 }
784 } 806 }
807 /*
808 * Clear stale usb2 resume signalling variables in case port changed
809 * state during resume signalling. For example on error
810 */
811 if ((bus_state->resume_done[wIndex] ||
812 test_bit(wIndex, &bus_state->resuming_ports)) &&
813 (raw_port_status & PORT_PLS_MASK) != XDEV_U3 &&
814 (raw_port_status & PORT_PLS_MASK) != XDEV_RESUME) {
815 bus_state->resume_done[wIndex] = 0;
816 clear_bit(wIndex, &bus_state->resuming_ports);
817 }
818
819
785 if ((raw_port_status & PORT_PLS_MASK) == XDEV_U0 && 820 if ((raw_port_status & PORT_PLS_MASK) == XDEV_U0 &&
786 (raw_port_status & PORT_POWER)) { 821 (raw_port_status & PORT_POWER)) {
787 if (bus_state->suspended_ports & (1 << wIndex)) { 822 if (bus_state->suspended_ports & (1 << wIndex)) {
@@ -1115,6 +1150,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
1115 if ((temp & PORT_PE) == 0) 1150 if ((temp & PORT_PE) == 0)
1116 goto error; 1151 goto error;
1117 1152
1153 set_bit(wIndex, &bus_state->resuming_ports);
1118 xhci_set_link_state(xhci, port_array, wIndex, 1154 xhci_set_link_state(xhci, port_array, wIndex,
1119 XDEV_RESUME); 1155 XDEV_RESUME);
1120 spin_unlock_irqrestore(&xhci->lock, flags); 1156 spin_unlock_irqrestore(&xhci->lock, flags);
@@ -1122,6 +1158,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
1122 spin_lock_irqsave(&xhci->lock, flags); 1158 spin_lock_irqsave(&xhci->lock, flags);
1123 xhci_set_link_state(xhci, port_array, wIndex, 1159 xhci_set_link_state(xhci, port_array, wIndex,
1124 XDEV_U0); 1160 XDEV_U0);
1161 clear_bit(wIndex, &bus_state->resuming_ports);
1125 } 1162 }
1126 bus_state->port_c_suspend |= 1 << wIndex; 1163 bus_state->port_c_suspend |= 1 << wIndex;
1127 1164
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 6c5e8133cf87..eeaa6c6bd540 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1583,7 +1583,8 @@ static void handle_port_status(struct xhci_hcd *xhci,
1583 */ 1583 */
1584 bogus_port_status = true; 1584 bogus_port_status = true;
1585 goto cleanup; 1585 goto cleanup;
1586 } else { 1586 } else if (!test_bit(faked_port_index,
1587 &bus_state->resuming_ports)) {
1587 xhci_dbg(xhci, "resume HS port %d\n", port_id); 1588 xhci_dbg(xhci, "resume HS port %d\n", port_id);
1588 bus_state->resume_done[faked_port_index] = jiffies + 1589 bus_state->resume_done[faked_port_index] = jiffies +
1589 msecs_to_jiffies(USB_RESUME_TIMEOUT); 1590 msecs_to_jiffies(USB_RESUME_TIMEOUT);