]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
usb: dwc3: gadget: Move vbus draw to workqueue context
authorPrashanth K <prashanth.k@oss.qualcomm.com>
Wed, 4 Feb 2026 05:41:55 +0000 (11:11 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 5 Feb 2026 16:13:48 +0000 (17:13 +0100)
Currently dwc3_gadget_vbus_draw() can be called from atomic
context, which in turn invokes power-supply-core APIs. And
some these PMIC APIs have operations that may sleep, leading
to kernel panic.

Fix this by moving the vbus_draw into a workqueue context.

Fixes: 99288de36020 ("usb: dwc3: add an alternate path in vbus_draw callback")
Cc: stable <stable@kernel.org>
Tested-by: Samuel Wu <wusamuel@google.com>
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Signed-off-by: Prashanth K <prashanth.k@oss.qualcomm.com>
Link: https://patch.msgid.link/20260204054155.3063825-1-prashanth.k@oss.qualcomm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index c07ffe82c85049364c38c7ba152aab0ff764d95e..161a4d58b2cecec802a06a7670827516cf0c88b4 100644 (file)
@@ -2155,6 +2155,20 @@ static int dwc3_get_num_ports(struct dwc3 *dwc)
        return 0;
 }
 
+static void dwc3_vbus_draw_work(struct work_struct *work)
+{
+       struct dwc3 *dwc = container_of(work, struct dwc3, vbus_draw_work);
+       union power_supply_propval val = {0};
+       int ret;
+
+       val.intval = 1000 * (dwc->current_limit);
+       ret = power_supply_set_property(dwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
+
+       if (ret < 0)
+               dev_dbg(dwc->dev, "Error (%d) setting vbus draw (%d mA)\n",
+                       ret, dwc->current_limit);
+}
+
 static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc)
 {
        struct power_supply *usb_psy;
@@ -2169,6 +2183,7 @@ static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc)
        if (!usb_psy)
                return ERR_PTR(-EPROBE_DEFER);
 
+       INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work);
        return usb_psy;
 }
 
@@ -2395,8 +2410,10 @@ void dwc3_core_remove(struct dwc3 *dwc)
 
        dwc3_free_event_buffers(dwc);
 
-       if (dwc->usb_psy)
+       if (dwc->usb_psy) {
+               cancel_work_sync(&dwc->vbus_draw_work);
                power_supply_put(dwc->usb_psy);
+       }
 }
 EXPORT_SYMBOL_GPL(dwc3_core_remove);
 
index 08cc6f2b5c23631a752c77fd7394e5876c929f0a..a35b3db1f9f3e71755bace4c1bce9d67bb8a0f60 100644 (file)
@@ -1058,6 +1058,8 @@ struct dwc3_glue_ops {
  * @role_switch_default_mode: default operation mode of controller while
  *                     usb role is USB_ROLE_NONE.
  * @usb_psy: pointer to power supply interface.
+ * @vbus_draw_work: Work to set the vbus drawing limit
+ * @current_limit: How much current to draw from vbus, in milliAmperes.
  * @usb2_phy: pointer to USB2 PHY
  * @usb3_phy: pointer to USB3 PHY
  * @usb2_generic_phy: pointer to array of USB2 PHYs
@@ -1244,6 +1246,8 @@ struct dwc3 {
        enum usb_dr_mode        role_switch_default_mode;
 
        struct power_supply     *usb_psy;
+       struct work_struct      vbus_draw_work;
+       unsigned int            current_limit;
 
        u32                     fladj;
        u32                     ref_clk_per;
index 384963151eced6368eb98cc0598ed90f031262cc..c65291e7b8d9044176ed656697c36e00d93683e1 100644 (file)
@@ -3124,8 +3124,6 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g,
 static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
 {
        struct dwc3             *dwc = gadget_to_dwc(g);
-       union power_supply_propval      val = {0};
-       int                             ret;
 
        if (dwc->usb2_phy)
                return usb_phy_set_power(dwc->usb2_phy, mA);
@@ -3133,10 +3131,10 @@ static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA)
        if (!dwc->usb_psy)
                return -EOPNOTSUPP;
 
-       val.intval = 1000 * mA;
-       ret = power_supply_set_property(dwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
+       dwc->current_limit = mA;
+       schedule_work(&dwc->vbus_draw_work);
 
-       return ret;
+       return 0;
 }
 
 /**