usb: dwc3: gadget: Move vbus draw to workqueue context

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: 99288de360 ("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>
This commit is contained in:
Prashanth K
2026-02-04 11:11:55 +05:30
committed by Greg Kroah-Hartman
parent 6dbc394fa5
commit 54aaa3b387
3 changed files with 25 additions and 6 deletions

View 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);

View 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;

View 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;
}
/**