]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
usb: dwc3: run gadget disconnect from sleepable suspend context
authorRunyu Xiao <runyu.xiao@seu.edu.cn>
Fri, 12 Jun 2026 05:20:05 +0000 (13:20 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Jul 2026 11:35:22 +0000 (13:35 +0200)
dwc3_gadget_suspend() takes dwc->lock with IRQs disabled and then calls
dwc3_disconnect_gadget().  For async callbacks that helper only uses
plain spin_unlock()/spin_lock(), so the gadget ->disconnect() callback
still runs with IRQs disabled and any sleepable callback trips Lockdep.

This issue was found by our static analysis tool and then manually
reviewed against the current tree.

The grounded PoC kept the dwc3_gadget_suspend() ->
dwc3_disconnect_gadget() -> gadget_driver->disconnect() chain, and
Lockdep reported:

  BUG: sleeping function called from invalid context
  gadget_disconnect+0x21/0x39 [vuln_msv]
  dwc3_gadget_suspend.constprop.0+0x2b/0x42 [vuln_msv]

Keep the disconnect callback selection in one common helper, but add a
sleepable suspend-side wrapper which snapshots the callback under
dwc->lock and then runs it after spin_unlock_irqrestore().  The regular
event path still uses the existing spin_unlock()/spin_lock() window.

Fixes: c8540870af4c ("usb: dwc3: gadget: Improve dwc3_gadget_suspend() and dwc3_gadget_resume()")
Cc: stable <stable@kernel.org>
Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Link: https://patch.msgid.link/20260612052005.3849659-1-runyu.xiao@seu.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/dwc3/gadget.c

index 3d4ca68e584c7ba5069910aeed985d06a9cca16c..1082e9c9afaa8995fba047da4b53150b6838812b 100644 (file)
@@ -3934,15 +3934,48 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
        }
 }
 
+static bool dwc3_prepare_disconnect_gadget(struct dwc3 *dwc,
+                                          struct usb_gadget_driver **driver,
+                                          struct usb_gadget **gadget)
+{
+       if (!dwc->async_callbacks || !dwc->gadget_driver ||
+           !dwc->gadget_driver->disconnect)
+               return false;
+
+       *driver = dwc->gadget_driver;
+       *gadget = dwc->gadget;
+
+       return true;
+}
+
 static void dwc3_disconnect_gadget(struct dwc3 *dwc)
 {
-       if (dwc->async_callbacks && dwc->gadget_driver->disconnect) {
+       struct usb_gadget_driver *driver;
+       struct usb_gadget *gadget;
+
+       if (dwc3_prepare_disconnect_gadget(dwc, &driver, &gadget)) {
                spin_unlock(&dwc->lock);
-               dwc->gadget_driver->disconnect(dwc->gadget);
+               driver->disconnect(gadget);
                spin_lock(&dwc->lock);
        }
 }
 
+static void dwc3_disconnect_gadget_sleepable(struct dwc3 *dwc)
+{
+       struct usb_gadget_driver *driver;
+       struct usb_gadget *gadget;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dwc->lock, flags);
+       if (!dwc3_prepare_disconnect_gadget(dwc, &driver, &gadget)) {
+               spin_unlock_irqrestore(&dwc->lock, flags);
+               return;
+       }
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       driver->disconnect(gadget);
+}
+
 static void dwc3_suspend_gadget(struct dwc3 *dwc)
 {
        if (dwc->async_callbacks && dwc->gadget_driver->suspend) {
@@ -4838,7 +4871,6 @@ EXPORT_SYMBOL_GPL(dwc3_gadget_exit);
 
 int dwc3_gadget_suspend(struct dwc3 *dwc)
 {
-       unsigned long flags;
        int ret;
 
        ret = dwc3_gadget_soft_disconnect(dwc);
@@ -4852,10 +4884,7 @@ int dwc3_gadget_suspend(struct dwc3 *dwc)
                return -EAGAIN;
        }
 
-       spin_lock_irqsave(&dwc->lock, flags);
-       if (dwc->gadget_driver)
-               dwc3_disconnect_gadget(dwc);
-       spin_unlock_irqrestore(&dwc->lock, flags);
+       dwc3_disconnect_gadget_sleepable(dwc);
 
        return 0;
 }