[PATCH] usb: xhci: Fix lockdep warning when entering test mode
From: Yang Zi
Date: Tue Aug 25 2026 - 05:09:56 EST
xhci_enter_test_mode() and xhci_set_port_power() are annotated
__must_hold(&xhci->lock), yet both drop the lock in the middle of the
function: xhci_enter_test_mode() around the slot-disable loop (because
xhci_disable_and_free_slot() takes the lock itself and can sleep) and
xhci_set_port_power() around the ACPI power-state calls.
xhci->lock is also taken from hardirq context in xhci_irq(), so it is a
hardirq-safe lock. Dropping it with spin_unlock_irqrestore() re-enables
interrupts while the lock is still held, and lockdep's
trace_hardirqs_on() -> mark_held_locks() then records the lock as
HARDIRQ-ON-W, which conflicts with the IN-HARDIRQ-W usage registered by
xhci_irq():
inconsistent {IN-HARDIRQ-W} -> {HARDIRQ-ON-W} usage.
Fix this by releasing the lock *before* re-enabling interrupts (and, on
the way back, disabling interrupts before re-acquiring the lock), so the
hardirq-safe lock is never held with IRQs enabled. Also drop the
incorrect __must_hold() annotations and pass the saved IRQ state by
value so these helpers cannot clobber the caller's flags.
This patch is tentative and needs maintainer review.
Signed-off-by: Yang Zi <2959243019@xxxxxx>
---
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index b0264bd8577a..c28d278f45b0 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -638,12 +638,19 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd)
/*
* xhci_set_port_power() must be called with xhci->lock held.
- * It will release and re-acquire the lock while calling ACPI
- * method.
+ * It drops the lock while calling the ACPI method, which may sleep, and
+ * re-acquires it before returning.
+ *
+ * The lock is released *before* interrupts are re-enabled because
+ * xhci->lock is also taken in hardirq context (xhci_irq()) and must never
+ * be held with IRQs enabled.
+ *
+ * @flags is passed by value: it is the IRQ state saved by the caller's
+ * spin_lock_irqsave() and must not be clobbered by the lock/irqsave dance
+ * below, so the caller can later restore it with spin_unlock_irqrestore().
*/
static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port,
- bool on, unsigned long *flags)
- __must_hold(&xhci->lock)
+ bool on, unsigned long flags)
{
struct usb_hcd *hcd;
u32 temp;
@@ -665,13 +672,15 @@ static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port,
xhci_portsc_writel(port, temp & ~PORT_POWER);
}
- spin_unlock_irqrestore(&xhci->lock, *flags);
+ spin_unlock(&xhci->lock);
+ local_irq_restore(flags);
temp = usb_acpi_power_manageable(hcd->self.root_hub,
port->hcd_portnum);
if (temp)
usb_acpi_set_power_state(hcd->self.root_hub,
port->hcd_portnum, on);
- spin_lock_irqsave(&xhci->lock, *flags);
+ local_irq_save(flags);
+ spin_lock(&xhci->lock);
}
static void xhci_port_set_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnum)
@@ -689,15 +698,24 @@ static void xhci_port_set_test_mode(struct xhci_hcd *xhci, u16 test_mode, int po
xhci_start(xhci);
}
+/*
+ * xhci_enter_test_mode() is called with xhci->lock held. It drops the lock
+ * around the slot-disable loop because xhci_disable_and_free_slot() takes
+ * xhci->lock itself and can sleep, then re-acquires it for the remainder of
+ * the function. The lock is released before interrupts are re-enabled since
+ * xhci->lock is also taken in hardirq context (xhci_irq()).
+ *
+ * @flags is passed by value so the caller's saved IRQ state is preserved.
+ */
static int xhci_enter_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnum,
- unsigned long *flags)
- __must_hold(&xhci->lock)
+ unsigned long flags)
{
int i, retval;
/* Disable all Device Slots */
xhci_dbg(xhci, "Disable all slots\n");
- spin_unlock_irqrestore(&xhci->lock, *flags);
+ spin_unlock(&xhci->lock);
+ local_irq_restore(flags);
for (i = 1; i <= xhci->max_slots; i++) {
if (!xhci->devs[i])
continue;
@@ -707,7 +725,8 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnu
xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n",
i, retval);
}
- spin_lock_irqsave(&xhci->lock, *flags);
+ local_irq_save(flags);
+ spin_lock(&xhci->lock);
/* Put all ports to the Disable state by clear PP */
xhci_dbg(xhci, "Disable all port (PP = 0)\n");
/* Power off USB3 ports*/
@@ -1463,7 +1482,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
* However, hub_wq will ignore the roothub events until
* the roothub is registered.
*/
- xhci_set_port_power(xhci, port, true, &flags);
+ xhci_set_port_power(xhci, port, true, flags);
break;
case USB_PORT_FEAT_RESET:
portsc |= PORT_RESET;
@@ -1514,7 +1533,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
if (test_mode > USB_TEST_FORCE_ENABLE ||
test_mode < USB_TEST_J)
goto error;
- retval = xhci_enter_test_mode(xhci, test_mode, portnum, &flags);
+ retval = xhci_enter_test_mode(xhci, test_mode, portnum, flags);
break;
default:
goto error;
@@ -1581,7 +1600,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
xhci_disable_port(xhci, port);
break;
case USB_PORT_FEAT_POWER:
- xhci_set_port_power(xhci, port, false, &flags);
+ xhci_set_port_power(xhci, port, false, flags);
break;
case USB_PORT_FEAT_TEST:
retval = xhci_exit_test_mode(xhci);