Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

xhci: Limit time spent with xHC interrupts disabled during bus resume

Current xhci bus resume implementation prevents xHC host from generating
interrupts during high-speed USB 2 and super-speed USB 3 bus resume.

Only reason to disable interrupts during bus resume would be to prevent
the interrupt handler from interfering with the resume process of USB 2
ports.

Host initiated resume of USB 2 ports is done in two stages.

The xhci driver first transitions the port from 'U3' to 'Resume' state,
then wait in Resume for 20ms, and finally moves port to U0 state.
xhci driver can't prevent interrupts by keeping the xhci spinlock
due to this 20ms sleep.

Limit interrupt disabling to the USB 2 port resume case only.
resuming USB 2 ports in bus resume is only done in special cases where
USB 2 ports had to be forced to suspend during bus suspend.

The current way of preventing interrupts by clearing the 'Interrupt
Enable' (INTE) bit in USBCMD register won't prevent the Interrupter
registers 'Interrupt Pending' (IP), 'Event Handler Busy' (EHB) and
USBSTS register Event Interrupt (EINT) bits from being set.

New interrupts can't be issued before those bits are properly clered.

Disable interrupts by clearing the interrupter register 'Interrupt
Enable' (IE) bit instead. This way IP, EHB and INTE won't be set
before IE is enabled again and a new interrupt is triggered.

Reported-by: Devyn Liu <liudingyuan@huawei.com>
Closes: https://lore.kernel.org/linux-usb/b1a9e2d51b4d4ff7a304f77c5be8164e@huawei.com/
Cc: stable@vger.kernel.org
Tested-by: Devyn Liu <liudingyuan@huawei.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20250410151828.2868740-6-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Mathias Nyman and committed by
Greg Kroah-Hartman
bea5892d 1ea050da

+20 -16
+16 -14
drivers/usb/host/xhci-hub.c
··· 1878 1878 int max_ports, port_index; 1879 1879 int sret; 1880 1880 u32 next_state; 1881 - u32 temp, portsc; 1881 + u32 portsc; 1882 1882 struct xhci_hub *rhub; 1883 1883 struct xhci_port **ports; 1884 + bool disabled_irq = false; 1884 1885 1885 1886 rhub = xhci_get_rhub(hcd); 1886 1887 ports = rhub->ports; ··· 1897 1896 return -ESHUTDOWN; 1898 1897 } 1899 1898 1900 - /* delay the irqs */ 1901 - temp = readl(&xhci->op_regs->command); 1902 - temp &= ~CMD_EIE; 1903 - writel(temp, &xhci->op_regs->command); 1904 - 1905 1899 /* bus specific resume for ports we suspended at bus_suspend */ 1906 - if (hcd->speed >= HCD_USB3) 1900 + if (hcd->speed >= HCD_USB3) { 1907 1901 next_state = XDEV_U0; 1908 - else 1902 + } else { 1909 1903 next_state = XDEV_RESUME; 1910 - 1904 + if (bus_state->bus_suspended) { 1905 + /* 1906 + * prevent port event interrupts from interfering 1907 + * with usb2 port resume process 1908 + */ 1909 + xhci_disable_interrupter(xhci->interrupters[0]); 1910 + disabled_irq = true; 1911 + } 1912 + } 1911 1913 port_index = max_ports; 1912 1914 while (port_index--) { 1913 1915 portsc = readl(ports[port_index]->addr); ··· 1978 1974 (void) readl(&xhci->op_regs->command); 1979 1975 1980 1976 bus_state->next_statechange = jiffies + msecs_to_jiffies(5); 1981 - /* re-enable irqs */ 1982 - temp = readl(&xhci->op_regs->command); 1983 - temp |= CMD_EIE; 1984 - writel(temp, &xhci->op_regs->command); 1985 - temp = readl(&xhci->op_regs->command); 1977 + /* re-enable interrupter */ 1978 + if (disabled_irq) 1979 + xhci_enable_interrupter(xhci->interrupters[0]); 1986 1980 1987 1981 spin_unlock_irqrestore(&xhci->lock, flags); 1988 1982 return 0;
+2 -2
drivers/usb/host/xhci.c
··· 322 322 xhci_info(xhci, "Fault detected\n"); 323 323 } 324 324 325 - static int xhci_enable_interrupter(struct xhci_interrupter *ir) 325 + int xhci_enable_interrupter(struct xhci_interrupter *ir) 326 326 { 327 327 u32 iman; 328 328 ··· 335 335 return 0; 336 336 } 337 337 338 - static int xhci_disable_interrupter(struct xhci_interrupter *ir) 338 + int xhci_disable_interrupter(struct xhci_interrupter *ir) 339 339 { 340 340 u32 iman; 341 341
+2
drivers/usb/host/xhci.h
··· 1890 1890 struct usb_tt *tt, gfp_t mem_flags); 1891 1891 int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, 1892 1892 u32 imod_interval); 1893 + int xhci_enable_interrupter(struct xhci_interrupter *ir); 1894 + int xhci_disable_interrupter(struct xhci_interrupter *ir); 1893 1895 1894 1896 /* xHCI ring, segment, TRB, and TD functions */ 1895 1897 dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb);