Linux USB
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Thinh Nguyen" <Thinh.Nguyen@synopsys.com>
Cc: "linux-usb@vger.kernel.org" <linux-usb@vger.kernel.org>
Subject: Re: [PATCH 7/7] usb: dwc3: gadget: Recover from End Transfer timeout
Date: Wed, 21 Oct 2026 01:05:48 +0000	[thread overview]
Message-ID: <sashiko-outbox-179659@kernel.org> (raw)
In-Reply-To: <7523e9a11303a33acd0b92a092fb0e8218d509d2.1792542945.git.Thinh.Nguyen@synopsys.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] usb: dwc3: gadget: __dwc3_stop_active_transfer() re-arms work on exit
- [High] usb: dwc3: dwc3_err_recovery_work() skips reset on soft_disconnect() timeout
- [High] usb: dwc3: gadget: race between dwc3_err_recovery_work() and suspend
- [High] usb: dwc3: gadget: race between dwc3_err_recovery_work() and threaded IRQ
- [Medium] usb: dwc3: gadget: __dwc3_gadget_start() clears DWC3_EP_ENABLED on active eps
--

commit d30e9ae79405a29ae6e289723c7c0106f74fcf1a
Author: Thinh Nguyen <Thinh.Nguyen@synopsys.com>

usb: dwc3: gadget: Recover from End Transfer timeout

This commit schedules controller error recovery via
dwc3_schedule_err_recovery() when an End Transfer command times out in
__dwc3_stop_active_transfer().

> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
> index a8f12e583a0f4..a76f669a796c2 100644
> --- a/drivers/usb/dwc3/gadget.c
> +++ b/drivers/usb/dwc3/gadget.c
[ ... ]
> @@ -1818,6 +1820,9 @@ static int __dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool int
>  		dev_err_ratelimited(dep->dwc->dev,
>  				"end transfer failed: %d\n", ret);
>  
> +	if (ret == -ETIMEDOUT && dep->dwc->err_state == DWC3_ERR_NONE)
> +		dwc3_schedule_err_recovery(dep->dwc);
> +
>  	dep->resource_index = 0;
>  
>  	if (!interrupt || (ret && ret != -ETIMEDOUT))

[Severity: Critical]
Can __dwc3_stop_active_transfer() re-schedule dwc->err_recovery_work during
teardown after cancel_work_sync() has already run in dwc3_gadget_exit()?

In drivers/usb/dwc3/gadget.c, dwc3_gadget_exit() cancels err_recovery_work
and destroys connect_mutex before tearing down the gadget and freeing
endpoints:

	cancel_work_sync(&dwc->err_recovery_work);
	mutex_destroy(&dwc->connect_mutex);
	dwc3_enable_susphy(dwc, true);
	usb_del_gadget(dwc->gadget);
	dwc3_gadget_free_endpoints(dwc);

Between cancel_work_sync() and the completion of usb_del_gadget() and
dwc3_gadget_free_endpoints(), __dwc3_stop_active_transfer() can still be
called with interrupt set to true and time out in dwc3_send_gadget_ep_cmd():

- usb_del_gadget() -> gadget_unbind_driver() invokes the gadget driver
  unbind callback, which calls usb_ep_disable() -> dwc3_gadget_ep_disable()
  -> __dwc3_gadget_ep_disable() -> dwc3_remove_requests() ->
  dwc3_stop_active_transfer() -> __dwc3_stop_active_transfer().

- Until usb_del_gadget() stops the gadget and frees dwc->irq_gadget,
  dwc3_thread_interrupt() can still invoke __dwc3_stop_active_transfer().

- Until dwc3_gadget_free_endpoints() cancels dep->nostream_work,
  dwc3_nostream_work() can still call dwc3_stop_active_transfer() ->
  __dwc3_stop_active_transfer().

If dwc3_send_gadget_ep_cmd() times out on any of those paths while in the
setup phase, wouldn't __dwc3_stop_active_transfer() re-queue
dwc->err_recovery_work after cancel_work_sync(), causing
dwc3_err_recovery_work() to lock the destroyed dwc->connect_mutex and access
freed dwc->eps[], dwc->gadget, and struct dwc3?

[Severity: High]
When an End Transfer command times out in __dwc3_stop_active_transfer() and
does not complete in hardware, will dwc3_err_recovery_work() actually reach
the soft reset in dwc3_gadget_soft_connect()?

In drivers/usb/dwc3/gadget.c, __dwc3_stop_active_transfer() schedules
dwc3_err_recovery_work() and sets the end transfer pending flag while leaving
the transfer started flag set:

	if (!interrupt || (ret && ret != -ETIMEDOUT))
		dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
	else
		dep->flags |= DWC3_EP_END_TRANSFER_PENDING;

When dwc3_err_recovery_work() runs, it calls dwc3_gadget_soft_disconnect()
first and aborts recovery if that fails:

	ret = dwc3_gadget_soft_disconnect(dwc);

	dwc3_disconnect_gadget_sleepable(dwc);

	if (ret)
		goto err_unrecoverable;

Inside dwc3_gadget_soft_disconnect() -> dwc3_stop_active_transfers() ->
dwc3_remove_requests() -> dwc3_stop_active_transfer(), the endpoint is
skipped because the end transfer pending flag is set:

	if (!(dep->flags & DWC3_EP_TRANSFER_STARTED) ||
	    (dep->flags & DWC3_EP_END_TRANSFER_PENDING))
		return;

Then dwc3_gadget_soft_disconnect() calls dwc3_gadget_run_stop(dwc, false) to
clear run/stop and poll for the controller halt bit. Because the controller
cannot halt while an active transfer's End Transfer command remains
incomplete in hardware, dwc3_gadget_run_stop() times out as well.

Unlike dwc3_gadget_suspend(), which calls dwc3_gadget_soft_connect() ->
dwc3_core_soft_reset() when dwc3_gadget_soft_disconnect() fails:

	ret = dwc3_gadget_soft_disconnect(dwc);
	if (ret && dwc->softconnect) {
		dwc3_gadget_soft_connect(dwc);
		return -EAGAIN;
	}

doesn't dwc3_err_recovery_work() jump straight to err_unrecoverable without
ever resetting the controller?

[Severity: High]
Could scheduling dwc->err_recovery_work from __dwc3_stop_active_transfer()
race with system or runtime suspend and access registers after
dwc3_core_exit() has disabled clocks and asserted reset?

In drivers/usb/dwc3/core.c, dwc3_suspend_common() suspends the gadget and
calls dwc3_core_exit():

	case DWC3_GCTL_PRTCAP_DEVICE:
		if (pm_runtime_suspended(dwc->dev))
			break;
		ret = dwc3_gadget_suspend(dwc);
		if (ret)
			return ret;
		synchronize_irq(dwc->irq_gadget);
		dwc3_core_exit(dwc);
		break;

During dwc3_gadget_suspend() -> dwc3_gadget_soft_disconnect() ->
dwc3_stop_active_transfers() -> dwc3_remove_requests() ->
dwc3_stop_active_transfer() -> __dwc3_stop_active_transfer(), an End Transfer
timeout will now schedule dwc->err_recovery_work, while
dwc3_stop_active_transfers() ignores the error. Similarly, during
dwc3_gadget_disconnect_interrupt(), dwc->connected is set to false, endpoint
disable in dwc3_disconnect_gadget() can time out in
__dwc3_stop_active_transfer() and schedule dwc->err_recovery_work while
releasing per-request runtime power management references, and
pm_request_idle(dwc->dev) is called.

Since dwc3_suspend_common() does not cancel dwc->err_recovery_work before
dwc3_core_exit(), and dwc3_err_recovery_work() in drivers/usb/dwc3/gadget.c
does not acquire a runtime power management reference before calling
dwc3_gadget_soft_disconnect() and dwc3_gadget_soft_connect(), what prevents
dwc3_err_recovery_work() from accessing controller registers while clocks are
disabled and reset is asserted?

[Severity: High]
How is dwc3_err_recovery_work() synchronized against dwc3_thread_interrupt()
when __dwc3_stop_active_transfer() schedules recovery from inside the
threaded interrupt handler?

Previously, dwc3_schedule_err_recovery() was only called from the hardirq
handler dwc3_check_event_buf() when waking dwc3_thread_interrupt() was
skipped. With this change, __dwc3_stop_active_transfer() can call
dwc3_schedule_err_recovery() from inside dwc3_thread_interrupt() ->
dwc3_process_event_buf(), both directly from endpoint event handlers and
while dwc->lock is dropped for gadget callbacks such as dwc3_reset_gadget(),
dwc3_disconnect_gadget(), or dwc3_ep0_delegate_req() -> usb_ep_disable().

In drivers/usb/dwc3/gadget.c, dwc3_err_recovery_work() executes
dwc3_gadget_soft_disconnect() and dwc3_disconnect_gadget_sleepable() before
calling synchronize_irq(dwc->irq_gadget):

	ret = dwc3_gadget_soft_disconnect(dwc);

	dwc3_disconnect_gadget_sleepable(dwc);

	if (ret)
		goto err_unrecoverable;

	if (dwc->softconnect) {
		u32 count;
		synchronize_irq(dwc->irq_gadget);

When dwc3_thread_interrupt() drops dwc->lock (for example in
dwc3_gadget_giveback() or during an in-flight gadget driver callback), can't
dwc3_err_recovery_work() concurrently halt the controller in
dwc3_gadget_soft_disconnect(), invoke the gadget disconnect callback in
dwc3_disconnect_gadget_sleepable(), and then have dwc3_thread_interrupt()
re-acquire dwc->lock and overwrite controller state (such as
dwc3_gadget_reset_interrupt() setting dwc->connected = true after
dwc3_reset_gadget())?

[Severity: Medium]
When dwc3_err_recovery_work() or dwc3_gadget_suspend() calls
dwc3_gadget_soft_connect() -> __dwc3_gadget_start() while non-control
endpoints are still enabled in the gadget layer, doesn't clearing the endpoint
enabled flag in __dwc3_gadget_start() desynchronize dep->flags from
dep->endpoint.enabled?

In drivers/usb/dwc3/gadget.c, __dwc3_gadget_start() masks dep->flags on all
non-control endpoints:

	for (epnum = 2; epnum < dwc->num_eps; epnum++) {
		dep = dwc->eps[epnum];
		if (!dep)
			continue;

		dep->flags &= (DWC3_EP_TXFIFO_RESIZED |
			       DWC3_EP_RESOURCE_ALLOCATED);
	}

In dwc3_gadget_suspend(), dwc3_gadget_soft_connect() is called on
soft-disconnect failure without calling dwc3_disconnect_gadget_sleepable(),
and in dwc3_err_recovery_work(), dwc3_gadget_soft_connect() runs immediately
after dwc3_disconnect_gadget_sleepable() even if a function driver defers
usb_ep_disable() to a worker thread or userspace. When usb_ep_disable() is
subsequently called while dep->endpoint.enabled is still true, won't
dwc3_gadget_ep_disable() hit dev_WARN_ONCE() and return early without running
__dwc3_gadget_ep_disable():

	if (dev_WARN_ONCE(dwc->dev, !(dep->flags & DWC3_EP_ENABLED),
					"%s is already disabled\n",
					dep->name))
		return 0;

leaving endpoint state such as dep->endpoint.desc uncleaned?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792542945.git.Thinh.Nguyen@synopsys.com?part=7

      reply	other threads:[~2026-10-21  1:05 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21  0:37 [PATCH 0/7] usb: dwc3: gadget: Fix No Resource after End Transfer Thinh Nguyen
2026-10-21  0:37 ` [PATCH 1/7] usb: dwc3: gadget: Reject requests to a disabled endpoint Thinh Nguyen
2026-10-21  0:44   ` sashiko-bot
2026-10-21  0:37 ` [PATCH 2/7] usb: dwc3: gadget: Track a timed out End Transfer Thinh Nguyen
2026-10-21  0:43   ` sashiko-bot
2026-10-21  0:38 ` [PATCH 3/7] usb: dwc3: gadget: Clear delayed stop on End Transfer completion Thinh Nguyen
2026-10-21  0:58   ` sashiko-bot
2026-10-21  0:38 ` [PATCH 4/7] usb: dwc3: gadget: Issue no-op Start Transfer only for stream endpoints Thinh Nguyen
2026-10-21  0:48   ` sashiko-bot
2026-10-21  0:38 ` [PATCH 5/7] usb: dwc3: gadget: Wait for End Transfer completion on ep disable Thinh Nguyen
2026-10-21  1:13   ` sashiko-bot
2026-10-21  0:38 ` [PATCH 6/7] usb: dwc3: gadget: Reset endpoint flags on controller start Thinh Nguyen
2026-10-21  1:03   ` sashiko-bot
2026-10-21  0:38 ` [PATCH 7/7] usb: dwc3: gadget: Recover from End Transfer timeout Thinh Nguyen
2026-10-21  1:05   ` 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-179659@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=Thinh.Nguyen@synopsys.com \
    --cc=linux-usb@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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