linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] usb: xhci: Fix lockdep warning when entering test mode
@ 2026-08-25  9:09 Yang Zi
  2026-08-25 11:29 ` Mathias Nyman
  0 siblings, 1 reply; 2+ messages in thread
From: Yang Zi @ 2026-08-25  9:09 UTC (permalink / raw)
  To: mathias.nyman, gregkh; +Cc: linux-usb, linux-kernel

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@qq.com>
---
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);



^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] usb: xhci: Fix lockdep warning when entering test mode
  2026-08-25  9:09 [PATCH] usb: xhci: Fix lockdep warning when entering test mode Yang Zi
@ 2026-08-25 11:29 ` Mathias Nyman
  0 siblings, 0 replies; 2+ messages in thread
From: Mathias Nyman @ 2026-08-25 11:29 UTC (permalink / raw)
  To: Yang Zi, mathias.nyman, gregkh; +Cc: linux-usb, linux-kernel

On 8/25/26 12:09, Yang Zi wrote:
> 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.
> 

This sounds odd, shouldn't spin_unlock_irqresore() first release the
spinlock and then enable interrupts?

Wouldn't this be an issue for every driver that shares a spinlock
in interrupt context and elsewhere?

spinlock_api_smp.h has:

static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
unsigned long flags)
	__releases(lock)
{
         spin_release(&lock->dep_map, _RET_IP_);
         do_raw_spin_unlock(lock);
	local_irq_restore(flags);
         preempt_enable();
}


To be fair it looks odd on a uniprocessor system:

spinlock_api_up.h:

#define __UNLOCK_IRQRESTORE(lock, flags, ...) \
   do { local_irq_restore(flags); __UNLOCK(lock, ##__VA_ARGS__); } while (0)

Are you running this on a single processor system?

> 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@qq.com>
> ---
> 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().

The caller should restore it to the flags value set during latest spin_lock_irqsave()
which is during the lock/irqsace dance.

Thanks
Mathias


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-25 11:29 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25  9:09 [PATCH] usb: xhci: Fix lockdep warning when entering test mode Yang Zi
2026-08-25 11:29 ` Mathias Nyman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).