| From 72f68bf5c756f5ce1139b31daae2684501383ad5 Mon Sep 17 00:00:00 2001 |
| From: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Date: Thu, 15 Jul 2021 18:06:51 +0300 |
| Subject: xhci: Fix lost USB 2 remote wake |
| |
| From: Mathias Nyman <mathias.nyman@linux.intel.com> |
| |
| commit 72f68bf5c756f5ce1139b31daae2684501383ad5 upstream. |
| |
| There's a small window where a USB 2 remote wake may be left unhandled |
| due to a race between hub thread and xhci port event interrupt handler. |
| |
| When the resume event is detected in the xhci interrupt handler it kicks |
| the hub timer, which should move the port from resume to U0 once resume |
| has been signalled for long enough. |
| |
| To keep the hub "thread" running we set a bus_state->resuming_ports flag. |
| This flag makes sure hub timer function kicks itself. |
| |
| checking this flag was not properly protected by the spinlock. Flag was |
| copied to a local variable before lock was taken. The local variable was |
| then checked later with spinlock held. |
| |
| If interrupt is handled right after copying the flag to the local variable |
| we end up stopping the hub thread before it can handle the USB 2 resume. |
| |
| CPU0 CPU1 |
| (hub thread) (xhci event handler) |
| |
| xhci_hub_status_data() |
| status = bus_state->resuming_ports; |
| <Interrupt> |
| handle_port_status() |
| spin_lock() |
| bus_state->resuming_ports = 1 |
| set_flag(HCD_FLAG_POLL_RH) |
| spin_unlock() |
| spin_lock() |
| if (!status) |
| clear_flag(HCD_FLAG_POLL_RH) |
| spin_unlock() |
| |
| Fix this by taking the lock a bit earlier so that it covers |
| the resuming_ports flag copy in the hub thread |
| |
| Cc: <stable@vger.kernel.org> |
| Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Link: https://lore.kernel.org/r/20210715150651.1996099-2-mathias.nyman@linux.intel.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/usb/host/xhci-hub.c | 3 ++- |
| 1 file changed, 2 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/usb/host/xhci-hub.c |
| +++ b/drivers/usb/host/xhci-hub.c |
| @@ -1638,11 +1638,12 @@ int xhci_hub_status_data(struct usb_hcd |
| * Inform the usbcore about resume-in-progress by returning |
| * a non-zero value even if there are no status changes. |
| */ |
| + spin_lock_irqsave(&xhci->lock, flags); |
| + |
| status = bus_state->resuming_ports; |
| |
| mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC | PORT_CEC; |
| |
| - spin_lock_irqsave(&xhci->lock, flags); |
| /* For each port, did anything change? If so, set that bit in buf. */ |
| for (i = 0; i < max_ports; i++) { |
| temp = readl(ports[i]->addr); |