mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-04-02 04:41:10 -04:00
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:
committed by
Greg Kroah-Hartman
parent
6dbc394fa5
commit
54aaa3b387
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user