]> git.ipfire.org Git - thirdparty/kernel/stable.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)
committerSasha Levin <sashal@kernel.org>
Wed, 4 Mar 2026 12:21:23 +0000 (07:21 -0500)
[ Upstream commit 54aaa3b387c2f580a99dc86a9cc2eb6dfaf599a7 ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index eb10490a6d92ca83a1d19a07c75feeb88f7f68ed..c6b7df5682b4e6519b6f4c6e5625dff14c412bf1 100644 (file)
@@ -2149,6 +2149,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;
@@ -2163,6 +2177,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;
 }
 
@@ -2386,8 +2401,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 45757169b672fd04f3004f5b0b4588317b661732..9cfc36d4bc259d242120408c2a2e6b4745cba581 100644 (file)
@@ -1060,6 +1060,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
@@ -1246,6 +1248,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 17eebb60900bf2240fba9a306196c3afbc858806..db5e5b77b1eacaf5acb02a5c3ef6f59835a0c527 100644 (file)
@@ -3123,8 +3123,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);
@@ -3132,10 +3130,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;
 }
 
 /**