From: sashiko-bot@kernel.org
To: "Vadim Yuzifovich" <vadim@yuzi.co>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH 1/3] usb: dwc2: gadget: restart descriptor DMA isochronous OUT endpoints after BNA
Date: Sun, 15 Nov 2026 19:20:28 +0000 [thread overview]
Message-ID: <sashiko-outbox-215744@kernel.org> (raw)
In-Reply-To: <20261115185335.782678-2-vadim@yuzi.co>
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] usb: dwc2: gadget: ep_disabled restart before
dwc2_gadget_handle_isoc_bna()
- [Medium] usb: dwc2: gadget: empty queue in
dwc2_gadget_restart_isoc_out_ddma() stalls
- [Low] usb: dwc2: gadget: missing Fixes trailer on BNA recovery fix
--
commit b960395b5c5b08d46282314fb63ad59c3e17a74a
Author: Vadim Yuzifovich <vadim@yuzi.co>
> usb: dwc2: gadget: restart descriptor DMA isochronous OUT endpoints after BNA
>
> In descriptor DMA mode the core disables an isochronous endpoint when it
> reaches a descriptor that is not ready (BNA), for example after an
> interrupt latency longer than the queued requests cover.
> dwc2_gadget_handle_isoc_bna() resets the endpoint and leaves the restart
> to the next OUTTKNEPDIS. Two things keep that from happening.
>
> First, the endpoint-disabled interrupt that comes with the BNA runs
> dwc2_gadget_handle_ep_disabled(), whose isochronous part is written for
> buffer DMA: it completes requests frame by frame and advances
> target_frame up to the current frame. Run after the BNA reset, it moves
> target_frame off TARGET_FRAME_INITIAL, and the OUT token handler then
> takes the chain for running and never restarts it.
>
> Second, after the BNA the core raises BNA again for the disabled
> endpoint as fast as the handler clears it, and no OUTTKNEPDIS comes.
>
> On a Sophgo SG2002 (DWC_otg 4.20a) with a UAC1 capture endpoint and
> eight requests queued, one 8 ms interrupt latency (a line printed on the
> 115200 baud serial console) caused one BNA. The endpoint stayed
> disabled and the BNA interrupt kept the CPU busy until the host gave up
> and reset the port, which took the other functions of the composite
> gadget down as well.
>
> In descriptor DMA mode, do not run the buffer DMA completion for a
> disabled isochronous endpoint; put it back in its initial state. For an
> OUT endpoint, build the chain again from the queue and enable it at
> once, both after the BNA and after an endpoint-disabled interrupt that
> finds it disabled with requests queued. IN endpoints keep restarting on
> the next NAK.
>
> Signed-off-by: Vadim Yuzifovich <vadim@yuzi.co>
[Severity: Low]
This fixes BNA recovery in descriptor DMA mode. Should it include:
Fixes: 729cac693eec ("usb: dwc2: Change ISOC DDMA flow")
> diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
> index 6c8a9f05fff00..b0e129061abb6 100644
> --- a/drivers/usb/dwc2/gadget.c
> +++ b/drivers/usb/dwc2/gadget.c
> @@ -2259,6 +2259,28 @@ static void dwc2_gadget_complete_isoc_request_ddma(struct dwc2_hsotg_ep *hs_ep)
> }
> }
>
> +/*
> + * dwc2_gadget_restart_isoc_out_ddma - build the chain of a disabled ISOC OUT
> + * endpoint again from its queue and enable it.
> + * @hs_ep: The isochronous OUT endpoint, in its initial state.
> + *
> + * After a BNA the core keeps raising BNA for the endpoint, as fast as the
> + * handler clears it, and no OUTTKNEPDIS comes to restart the chain. So the
> + * chain is restarted here at once.
> + */
> +static void dwc2_gadget_restart_isoc_out_ddma(struct dwc2_hsotg_ep *hs_ep)
> +{
> + struct dwc2_hsotg *hsotg = hs_ep->parent;
> +
> + if (hs_ep->dir_in || list_empty(&hs_ep->queue) ||
> + hs_ep->target_frame != TARGET_FRAME_INITIAL ||
> + dwc2_readl(hsotg, DOEPCTL(hs_ep->index)) & DXEPCTL_EPENA)
> + return;
> +
> + hs_ep->target_frame = dwc2_hsotg_read_frameno(hsotg);
> + dwc2_gadget_start_isoc_ddma(hs_ep);
> +}
[Severity: Medium]
What happens if hs_ep->queue is empty when dwc2_gadget_restart_isoc_out_ddma()
runs (for example, if dwc2_gadget_handle_isoc_bna() just completed the
only remaining request and the gadget function queues requests
asynchronously)?
Returning early leaves the endpoint disabled, so the core continues
raising BNA interrupts. Even when dwc2_hsotg_ep_queue() is later called,
it sees hs_ep->target_frame == TARGET_FRAME_INITIAL and returns without
filling a descriptor or restarting the endpoint, because no OUTTKNEPDIS
will arrive after the BNA:
drivers/usb/dwc2/gadget.c:dwc2_hsotg_ep_queue() {
...
if (using_desc_dma(hs) && hs_ep->isochronous) {
if (hs_ep->target_frame != TARGET_FRAME_INITIAL) {
...
dwc2_gadget_fill_isoc_desc(hs_ep, dma_addr,
hs_req->req.length);
}
return 0;
}
...
}
> @@ -2902,6 +2926,27 @@ static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep)
> if (!hs_ep->isochronous)
> return;
>
> + /*
> + * In descriptor DMA the requests sit in the descriptor chain rather
> + * than one per frame, so the frame by frame completion below does not
> + * apply. The core disables an isochronous endpoint by itself when it
> + * meets a descriptor that was not filled in time (BNA). Put such an
> + * endpoint back in its initial state and build the chain again from
> + * the queue: at once for OUT, on the next NAK for IN. The loop below
> + * would move target_frame off TARGET_FRAME_INITIAL, which tells the
> + * token handlers that a chain runs: the endpoint then stays disabled
> + * for good, and every request queued again goes into a dead chain.
> + */
> + if (using_desc_dma(hsotg)) {
> + if (!(dwc2_readl(hsotg, epctl_reg) & DXEPCTL_EPENA)) {
> + hs_ep->target_frame = TARGET_FRAME_INITIAL;
> + hs_ep->next_desc = 0;
> + hs_ep->compl_desc = 0;
> + dwc2_gadget_restart_isoc_out_ddma(hs_ep);
> + }
> + return;
> + }
[Severity: High]
Can dwc2_gadget_handle_isoc_bna() corrupt the newly started descriptor
chain when DXEPINT_EPDISBLD and DXEPINT_BNAINTR are handled in the same
dwc2_hsotg_epint() call?
In dwc2_hsotg_epint(), DXEPINT_EPDISBLD and DXEPINT_NAKINTRPT are
dispatched before DXEPINT_BNAINTR:
drivers/usb/dwc2/gadget.c:dwc2_hsotg_epint() {
...
if (ints & DXEPINT_EPDISBLD)
dwc2_gadget_handle_ep_disabled(hs_ep);
if (ints & DXEPINT_OUTTKNEPDIS)
dwc2_gadget_handle_out_token_ep_disabled(hs_ep);
if (ints & DXEPINT_NAKINTRPT)
dwc2_gadget_handle_nak(hs_ep);
...
if (ints & DXEPINT_BNAINTR) {
dev_dbg(hsotg->dev, "%s: BNA interrupt\n", __func__);
if (hs_ep->isochronous)
dwc2_gadget_handle_isoc_bna(hs_ep);
}
...
}
When both bits are set after interrupt latency,
dwc2_gadget_handle_ep_disabled() (or dwc2_gadget_handle_nak() for IN)
rebuilds desc_list[] from hs_ep->queue and sets DXEPCTL_EPENA.
dwc2_gadget_handle_isoc_bna() then runs on the active endpoint: it
unmaps and completes get_ep_head(hs_ep) while hardware DMA is armed on
desc_list[0], shifts hs_ep->queue out of sync with desc_list[], and
resets hs_ep->target_frame to TARGET_FRAME_INITIAL while DXEPCTL_EPENA is
still set, so its call to dwc2_gadget_restart_isoc_out_ddma() returns
early and subsequent dwc2_hsotg_ep_queue() calls stop filling descriptors.
In addition, because dwc2_hsotg_epint() clears DOEPINT at the entry of the
handler before the endpoint is re-enabled and the core re-asserts
DXEPINT_BNAINTR while the endpoint is disabled, a newly latched BNA
interrupt can fire right after the restart and hit the running chain in
the same way.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115185335.782678-1-vadim@yuzi.co?part=1
next prev parent reply other threads:[~2026-11-15 19:20 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-15 18:53 [PATCH 0/3] usb: dwc2: gadget: descriptor DMA fixes for BNA, found on Sophgo SG2002 Vadim Yuzifovich
2026-11-15 18:53 ` [PATCH 1/3] usb: dwc2: gadget: restart descriptor DMA isochronous OUT endpoints after BNA Vadim Yuzifovich
2026-11-15 19:20 ` sashiko-bot [this message]
2026-11-15 18:53 ` [PATCH 2/3] usb: dwc2: gadget: restart descriptor DMA bulk and interrupt " Vadim Yuzifovich
2026-11-15 19:09 ` sashiko-bot
2026-11-15 18:53 ` [PATCH 3/3] usb: dwc2: gadget: make descriptors visible before the endpoint is enabled Vadim Yuzifovich
2026-11-15 19:07 ` sashiko-bot
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-215744@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vadim@yuzi.co \
/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