Commit 54aaa3b3 authored by Prashanth K's avatar Prashanth K Committed by Greg Kroah-Hartman
Browse files

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: 99288de3 ("usb: dwc3: add an alternate path in vbus_draw callback")
Cc: stable <stable@kernel.org>
Tested-by: default avatarSamuel Wu <wusamuel@google.com>
Acked-by: default avatarThinh Nguyen <Thinh.Nguyen@synopsys.com>
Signed-off-by: default avatarPrashanth K <prashanth.k@oss.qualcomm.com>
Link: https://patch.msgid.link/20260204054155.3063825-1-prashanth.k@oss.qualcomm.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 6dbc394f
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
@@ -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,9 +2410,11 @@ 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);

static void dwc3_remove(struct platform_device *pdev)
+4 −0
Original line number Diff line number Diff line
@@ -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;
+3 −5
Original line number Diff line number Diff line
@@ -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;
}

/**