[PATCH] usb: pci-quirks: abort xHCI handoff if MMIO is inaccessible

From: Haowen Bai

Date: Sun Sep 06 2026 - 12:26:47 EST


The xHCI early handoff quirk polls the BIOS ownership, CNR, and HALT bits
with readl_poll_timeout_atomic(). Unlike xhci_handshake(), handshake()
does not treat an all-ones read as an inaccessible controller.

If the controller becomes inaccessible, readl() can return U32_MAX. The CNR
bit then never clears, and the atomic poll keeps retrying. The atomic poll
budget is decremented using the requested delay and loop iterations, but
not the time spent in readl(). Slow failed MMIO reads can therefore keep
the PCI hotplug thread spinning far beyond the nominal timeout and trigger
a soft lockup. The 26-second value in the first warning is the watchdog
threshold, not the handshake timeout; repeated warnings showed the thread
still stuck up to 260 seconds before a controlled reboot, leaving the
system unavailable to normal management. Comparing the watchdog timestamps
with the RBP loop counter in the dumps (about 1,372 iterations in 26 s and
15,638 in 260 s) implies roughly 16-19 ms per polling iteration, despite
configured 10 us delay; these values are inferred, not direct measurements
of an individual readl().

Return -ENODEV when the polled register reads U32_MAX and stop the handoff
before issuing further accesses. This prevents an inaccessible xHCI from
keeping the PCI hotplug thread busy and making the system unavailable. An
eGPU may still fail to enumerate, but that failure must remain controlled
rather than causing a kernel Soft Lockup and taking down SSH or desktop
management. The existing timeout behavior for non-all-ones reads is
preserved, matching xhci_handshake().

A Thunderbolt-attached AMD Radeon Pro W5700 in a Razer Core X enclosure
reproduced this on an x86_64 UGREEN DXP8800 Plus with an Intel Core
i5-1235U. The GPU's xHCI function 0000:06:00.2 (1002:7316) triggered
the soft lockup in irq/123-pciehp; the register dump contained
RAX=U32_MAX:

watchdog: BUG: soft lockup - CPU#6 stuck for 26s! [irq/123-pciehp:139]
RIP: 0010:quirk_usb_early_handoff+0x552/0x7e0
register state: RAX=00000000ffffffff

The call trace was:

pci_do_fixups
pci_bus_add_device
pci_bus_add_devices
pciehp_configure_device
pciehp_handle_presence_or_link_change
pciehp_ist
irq_thread_fn

The failure reproduced on two hot-plug attempts and did not occur when the
enclosure was connected before boot.

Fixes: 66d4eadd8d06 ("USB: xhci: BIOS handoff and HW initialization.")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Haowen Bai <calvin.bai@xxxxxxxxxx>
---
drivers/usb/host/pci-quirks.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
index 0404489c2f6a..e5ddd33c734d 100644
--- a/drivers/usb/host/pci-quirks.c
+++ b/drivers/usb/host/pci-quirks.c
@@ -1026,15 +1026,22 @@ static void quirk_usb_disable_ehci(struct pci_dev *pdev)
* Returns 0 when the mask bits have the value done.
* Returns -ETIMEDOUT if this condition is not true after
* wait_usec microseconds have passed.
+ * Returns -ENODEV if the register reads as all-ones (hardware removed).
*/
static int handshake(void __iomem *ptr, u32 mask, u32 done,
int wait_usec, int delay_usec)
{
u32 result;
+ int ret;

- return readl_poll_timeout_atomic(ptr, result,
- ((result & mask) == done),
- delay_usec, wait_usec);
+ ret = readl_poll_timeout_atomic(ptr, result,
+ (result & mask) == done ||
+ result == U32_MAX,
+ delay_usec, wait_usec);
+ if (result == U32_MAX)
+ return -ENODEV;
+
+ return ret;
}

/*
@@ -1203,6 +1210,9 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev)
timeout = handshake(base + ext_cap_offset, XHCI_HC_BIOS_OWNED,
0, 1000000, 10);

+ if (timeout == -ENODEV)
+ goto iounmap;
+
/* Assume a buggy BIOS and take HC ownership anyway */
if (timeout) {
dev_warn(&pdev->dev,
@@ -1231,6 +1241,9 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev)
*/
timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_CNR, 0,
5000000, 10);
+ if (timeout == -ENODEV)
+ goto iounmap;
+
/* Assume a buggy HC and start HC initialization anyway */
if (timeout) {
val = readl(op_reg_base + XHCI_STS_OFFSET);
@@ -1247,6 +1260,9 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev)
/* Wait for the HC to halt - poll every 125 usec (one microframe). */
timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_HALT, 1,
XHCI_MAX_HALT_USEC, 125);
+ if (timeout == -ENODEV)
+ goto iounmap;
+
if (timeout) {
val = readl(op_reg_base + XHCI_STS_OFFSET);
dev_warn(&pdev->dev,
--
2.47.3