From: sashiko-bot@kernel.org
To: "Vladyslav Tymoshchuk" <tvlad744@gmail.com>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH] usb: dwc3: qcom: Defer VBUS override update during system suspend
Date: Sun, 18 Oct 2026 14:54:47 +0000 [thread overview]
Message-ID: <sashiko-outbox-176222@kernel.org> (raw)
In-Reply-To: <20261018143126.3978-1-tvlad744@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] usb: dwc3: qcom: premature pm_suspended clear in dwc3_qcom_pm_resume()
- [High] usb: dwc3: qcom: race between dwc3_qcom_set_role_notifier() and core PM callbacks
- [Medium] usb: dwc3: qcom: role switch dropped or overwritten during system sleep
--
commit a81658c1fd87525e81eccc5d37bb7630d869a4d0
Author: Vladyslav Tymoshchuk <tvlad744@gmail.com>
usb: dwc3: qcom: Defer VBUS override update during system suspend
This patch serializes dwc3_qcom_set_role_notifier() against system suspend
and resume using a mutex and defers QSCRATCH VBUS override updates while
the glue is suspended for system sleep until dwc3_qcom_pm_resume() runs.
> diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
> index ac68b4218b56b..2997479988c74 100644
> --- a/drivers/usb/dwc3/dwc3-qcom.c
> +++ b/drivers/usb/dwc3/dwc3-qcom.c
[ ... ]
> @@ -561,27 +567,40 @@ static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *p
> static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role)
> {
> struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
> + bool enable;
> +
> + guard(mutex)(&qcom->role_lock);
>
> if (qcom->current_role == next_role)
> return;
>
> + enable = qcom->current_role != USB_ROLE_DEVICE;
> +
> + /*
> + * Not every caller of usb_role_switch_set_role() is frozen during
> + * system sleep, e.g. the UCSI connector work can run in the noirq
> + * resume phase. The glue clocks are off then and runtime PM can't
> + * turn them on, so leave QSCRATCH alone and let
> + * dwc3_qcom_pm_resume() apply the update.
> + */
> + if (qcom->pm_suspended) {
> + qcom->vbus_override_pending = true;
> + qcom->vbus_override = enable;
> + qcom->current_role = next_role;
> + return;
> + }
> +
> if (pm_runtime_resume_and_get(qcom->dev)) {
> dev_dbg(qcom->dev, "Failed to resume device\n");
> return;
> }
[Severity: Medium]
Can a role switch be dropped during system sleep when the PM core takes the
direct_complete path?
dwc3_qcom_dev_pm_ops wires up dwc3_qcom_prepare(), which calls
dwc3_pm_prepare() and returns 1 when the controller is runtime-suspended in
DWC3_GCTL_PRTCAP_DEVICE mode and has no pinctrl state. When no genpd
overrides .prepare and device_may_wakeup(dev) is false (for example, when
wakeup-source is not present or no gadget driver is bound), device_suspend()
takes the direct_complete path: it calls pm_runtime_disable(dev) while the
device is RPM_SUSPENDED and skips both dwc3_qcom_pm_suspend() and
dwc3_qcom_pm_resume().
In that state, qcom->pm_suspended remains false while runtime PM is disabled
and the glue clocks are off. If dwc3_qcom_set_role_notifier() runs during
system sleep, it sees qcom->pm_suspended == false and calls
pm_runtime_resume_and_get(qcom->dev), which fails with -EACCES and returns
without updating qcom->current_role or recording the VBUS override update.
>
> - if (qcom->current_role == USB_ROLE_DEVICE)
> - dwc3_qcom_vbus_override_enable(qcom, false);
> - else if (qcom->current_role != USB_ROLE_DEVICE)
> - dwc3_qcom_vbus_override_enable(qcom, true);
> + qcom->vbus_override_pending = false;
> + dwc3_qcom_vbus_override_enable(qcom, enable);
>
> pm_runtime_mark_last_busy(qcom->dev);
> pm_runtime_put_sync(qcom->dev);
>
> - /*
> - * Current role changes via usb_role_switch_set_role callback protected
> - * internally by mutex lock.
> - */
> qcom->current_role = next_role;
> }
[ ... ]
> @@ -756,6 +779,8 @@ static int dwc3_qcom_pm_suspend(struct device *dev)
> if (ret)
> return ret;
>
> + guard(mutex)(&qcom->role_lock);
> +
> ret = dwc3_qcom_suspend(qcom, wakeup);
> if (ret)
> return ret;
[Severity: High]
What happens if dwc3_qcom_set_role_notifier() runs while dwc3_pm_suspend()
is executing, before dwc3_qcom_pm_suspend() acquires qcom->role_lock and
sets qcom->pm_suspended = true?
If the device enters system suspend while already runtime-suspended
(RPM_SUSPENDED, qcom->is_suspended == true) with direct_complete not used
(for example, when wakeup is enabled), dwc3_pm_suspend() ->
dwc3_suspend_common() sees pm_runtime_suspended(dwc->dev) in device mode
and returns 0 without suspending the gadget or exiting the core.
Because the PM core holds a runtime PM usage reference from device_prepare()
and only disables runtime PM later in device_suspend_late(), a concurrent
dwc3_qcom_set_role_notifier() running before guard(mutex)(&qcom->role_lock)
sees qcom->pm_suspended == false and calls pm_runtime_resume_and_get(). That
invokes dwc3_qcom_runtime_resume(), turning on the glue clocks (setting
qcom->is_suspended = false) and resuming the DWC3 core, PHYs, and gadget via
dwc3_runtime_resume(), while the subsequent pm_runtime_put_sync() does not
suspend the device because the usage count is elevated.
dwc3_qcom_pm_suspend() then acquires qcom->role_lock and calls
dwc3_qcom_suspend(qcom, wakeup), which sees qcom->is_suspended == false and
disables the glue clocks and interconnects while the DWC3 core and PHYs
remain active.
Additionally, if the device was active in USB_ROLE_DEVICE and a role switch
to USB_ROLE_NONE runs during dwc3_pm_suspend() after dwc3_gadget_suspend(),
dwc3_qcom_set_role_notifier() sees qcom->pm_suspended == false, clears
qcom->vbus_override_pending, and disables the VBUS override in QSCRATCH
immediately. On the subsequent dwc3_pm_resume(), dwc3_gadget_resume() ->
dwc3_qcom_run_stop_notifier(dwc, true) re-enables the VBUS override, and
dwc3_qcom_pm_resume() will not clear it because qcom->vbus_override_pending
is false.
> @@ -772,17 +797,29 @@ static int dwc3_qcom_pm_resume(struct device *dev)
> bool wakeup = device_may_wakeup(dev);
> int ret;
>
> - ret = dwc3_qcom_resume(qcom, wakeup);
> - if (ret)
> - return ret;
> + scoped_guard(mutex, &qcom->role_lock) {
> + ret = dwc3_qcom_resume(qcom, wakeup);
> + if (ret)
> + return ret;
>
> - qcom->pm_suspended = false;
> + qcom->pm_suspended = false;
> + }
>
> ret = dwc3_pm_resume(&qcom->dwc);
> - if (ret)
> - return ret;
>
> - return 0;
> + /*
> + * Apply a VBUS override update from a role switch that came in while
> + * suspended. Do it after the core has resumed, as resuming the gadget
> + * enables the override and the role switch is the more recent event.
> + */
> + scoped_guard(mutex, &qcom->role_lock) {
> + if (qcom->vbus_override_pending) {
> + dwc3_qcom_vbus_override_enable(qcom, qcom->vbus_override);
> + qcom->vbus_override_pending = false;
> + }
> + }
> +
> + return ret;
> }
[Severity: High]
Should qcom->pm_suspended = false be moved into the second scoped_guard()
block after dwc3_pm_resume(&qcom->dwc) completes?
By clearing qcom->pm_suspended and releasing qcom->role_lock before calling
dwc3_pm_resume(&qcom->dwc), a concurrent dwc3_qcom_set_role_notifier() call
that arrives while dwc3_pm_resume() is executing will see
qcom->pm_suspended == false and take the active path instead of deferring
via qcom->vbus_override_pending:
1. If the device was in USB_ROLE_DEVICE and RPM_ACTIVE before suspend,
pm_runtime_resume_and_get(qcom->dev) in dwc3_qcom_set_role_notifier()
succeeds (rpm_resume() returns 1 while runtime PM is disabled in
dwc3_pm_resume() because runtime_status and last_status are both
RPM_ACTIVE). For a switch to USB_ROLE_NONE, dwc3_qcom_set_role_notifier()
sets qcom->vbus_override_pending = false and disables the VBUS override
in QSCRATCH. Moments later, dwc3_pm_resume() -> dwc3_resume_common() ->
dwc3_gadget_resume() -> dwc3_gadget_soft_connect() ->
dwc3_gadget_run_stop(dwc, true) -> dwc3_qcom_run_stop_notifier(dwc, true)
calls dwc3_qcom_vbus_override_enable(qcom, true) without holding
qcom->role_lock, racing on non-atomic QSCRATCH register accesses and
re-enabling the VBUS override. When dwc3_qcom_pm_resume() reaches the
second scoped_guard(), qcom->vbus_override_pending is false, leaving the
VBUS override stuck enabled in QSCRATCH while qcom->current_role is
USB_ROLE_NONE.
2. If the device was RPM_SUSPENDED before system suspend, inside
dwc3_pm_resume():
drivers/usb/dwc3/core.c:dwc3_pm_resume() {
...
pm_runtime_disable(dev);
ret = pm_runtime_set_active(dev);
if (ret)
goto out;
ret = dwc3_resume_common(dwc, PMSG_RESUME);
...
}
pm_runtime_disable(dev) records dev->power.last_status = RPM_SUSPENDED,
and pm_runtime_set_active(dev) updates runtime_status without changing
last_status. When dwc3_qcom_set_role_notifier() calls
pm_runtime_resume_and_get(qcom->dev) during this window, rpm_resume()
returns -EACCES because disable_depth > 0 and last_status != RPM_ACTIVE,
dropping the role switch and VBUS override update.
3. If the device was RPM_SUSPENDED before system suspend and
dwc3_qcom_set_role_notifier() runs after qcom->pm_suspended = false is
set but before dwc3_pm_resume() calls pm_runtime_disable(dev),
pm_runtime_resume_and_get(qcom->dev) will synchronously invoke
dwc3_qcom_runtime_resume() -> dwc3_runtime_resume(&qcom->dwc)
concurrently with dwc3_pm_resume(&qcom->dwc).
Keeping qcom->pm_suspended = true until the second scoped_guard() block
after dwc3_pm_resume(&qcom->dwc) finishes would ensure role switches during
dwc3_pm_resume() are also deferred and applied once the core has resumed.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261018143126.3978-1-tvlad744@gmail.com?part=1
prev parent reply other threads:[~2026-10-18 14:54 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 14:31 [PATCH] usb: dwc3: qcom: Defer VBUS override update during system suspend Vladyslav Tymoshchuk
2026-10-18 14:54 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-176222@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tvlad744@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox