From: sashiko-bot@kernel.org
To: "Petar Stepanovic" <pstepanovic@axiado.com>
Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v4 3/6] usb: gadget: udc: crg: Add ring and transfer management
Date: Fri, 30 Oct 2026 08:29:16 +0000 [thread overview]
Message-ID: <sashiko-outbox-190689@kernel.org> (raw)
In-Reply-To: <20261030-axiado-ax3000-usb-device-controller-v4-3-c5267b36a777@axiado.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] usb: gadget: crg: out-of-bounds TRB read in crg_udc_update_dequeue_pt()
- [High] usb: gadget: crg: premature wrap skips pause in crg_udc_actual_before()
- [High] usb: gadget: crg: crg_udc_process_event_ring() misses in-progress reset after lock drop
--
commit 050a1d6b53db454c24d8712a72246d62e4e494d9
Author: Petar Stepanovic <pstepanovic@axiado.com>
usb: gadget: udc: crg: Add ring and transfer management
This commit introduces memory management and ring buffer logic for TRBs and
event rings required by the Axiado AX3000/AX3005 UDC driver.
> diff --git a/drivers/usb/gadget/udc/crg/crg-udc-ring.c b/drivers/usb/gadget/udc/crg/crg-udc-ring.c
> new file mode 100644
> index 0000000000000..f1de8893b790f
> --- /dev/null
> +++ b/drivers/usb/gadget/udc/crg/crg-udc-ring.c
[ ... ]
> +int crg_udc_process_event_ring(struct crg_gadget_dev *crg_udc, int index,
> + unsigned long *flags)
> +{
> + struct crg_event_trb *event;
> + struct crg_udc_event *udc_event;
> + struct crg_uicr __iomem *uicr = crg_udc->uicr[index];
> + u32 val;
> + dma_addr_t erdp;
> + int ret;
> + u32 generation;
> + unsigned int processed = 0;
> +
> + if (!uicr)
> + return -ENODEV;
> +
> + val = readl(&uicr->iman);
> + val |= CRG_U3DC_IMAN_INT_PEND;
> + writel(val, &uicr->iman);
> +
> + udc_event = &crg_udc->udc_event[index];
> + generation = udc_event->generation;
> + while (udc_event->evt_dq_pt) {
> + event = (struct crg_event_trb *)udc_event->evt_dq_pt;
> +
> + if (FIELD_GET(EVE_TRB_CYCLE_BIT_MASK,
> + le32_to_cpu(event->dw3)) != udc_event->ccs)
> + break;
> + /* Read the payload only after observing device ownership. */
> + dma_rmb();
> +
> + ret = crg_udc_handle_event(crg_udc, event, flags);
> + /* The giveback callback may release the lock and reset the ring. */
> + if (unlikely(generation != udc_event->generation))
> + return 0;
> + if (ret == -ECONNRESET) {
> + /* The disconnect path rebuilt and reprogrammed the ring. */
> + return 0;
> + }
> + if (ret < 0)
> + dev_dbg(crg_udc->dev, "failed to process event: %d\n",
> + ret);
> +
> + /* Consume malformed events so they cannot pin the ring head. */
> + if (event == udc_event->evt_seg0_last_trb) {
> + udc_event->ccs = udc_event->ccs ? 0 : 1;
> + udc_event->evt_dq_pt = udc_event->event_ring.cpu_addr;
> + } else {
> + udc_event->evt_dq_pt++;
> + }
> +
> + /*
> + * udc_event state is only re-read from crg_udc, never cached
> + * across this drop, so releasing and reacquiring the lock
> + * here is safe and lets other udc_lock users, and this CPU's
> + * other interrupts, make progress during a busy event ring.
> + */
> + if (++processed % CRG_EVENT_RING_BATCH == 0) {
> + erdp = event_trb_virt_to_dma(udc_event,
> + udc_event->evt_dq_pt);
> + if (!dma_mapping_error(crg_udc->dev, erdp)) {
> + writel(upper_32_bits(erdp), &uicr->erdphi);
> + writel(lower_32_bits(erdp) |
> + CRG_U3DC_ERDPLO_EHB,
> + &uicr->erdplo);
> + }
> + spin_unlock_irqrestore(&crg_udc->udc_lock, *flags);
> + cond_resched();
> + spin_lock_irqsave(&crg_udc->udc_lock, *flags);
> + if (unlikely(generation != udc_event->generation))
> + return 0;
> + }
> + }
[Severity: High]
As reported by Sashiko on v3 [1], can crg_udc_process_event_ring() miss an
in-progress reset when crg_udc->udc_lock is dropped inside
crg_udc_handle_event() or across the cond_resched() batch boundary?
When crg_gadget_stop() -> crg_udc_reset() runs on another CPU while
crg_udc->udc_lock is released, it sets crg_udc->resetting = true, asserts
CRG_U3DC_CTRL_SWRST, and drops crg_udc->udc_lock while polling for SWRST to
clear and again during crg_udc_ep_nuke() -> crg_udc_req_done() givebacks.
Because udc_event->generation is only incremented later when
crg_udc_reset_data_struct() -> crg_udc_init_event_ring() runs (and is not
incremented at all if crg_udc_reset() times out),
crg_udc_process_event_ring() can re-acquire crg_udc->udc_lock during those
windows with generation == udc_event->generation still true while
crg_udc->resetting is set.
Would this allow crg_udc_process_event_ring() to continue advancing
udc_event->evt_dq_pt, processing stale pre-reset events, and writing
uicr->erdphi and uicr->erdplo while the controller is mid-reset? Should the
post-lock checks in crg_udc_process_event_ring() also test
crg_udc->resetting, similar to crg_udc_queue_pending_trbs() and
crg_udc_handle_setup_pkt()?
[ ... ]
> +struct crg_transfer_trb *crg_udc_tran_trb_dma_to_virt(struct crg_udc_ep *udc_ep,
> + dma_addr_t address)
> +{
> + dma_addr_t offset;
> + struct crg_transfer_trb *trb_virt;
> +
> + if (lower_32_bits(address) & 0xf)
> + return NULL;
> +
> + offset = address - udc_ep->tran_ring_info.dma_addr;
> + if (unlikely(offset >= udc_ep->tran_ring_info.len))
> + return NULL;
> + offset = offset / sizeof(struct crg_transfer_trb);
> + trb_virt = udc_ep->first_trb + offset;
> + return trb_virt;
> +}
> +
> +dma_addr_t crg_udc_tran_trb_virt_to_dma(struct crg_udc_ep *udc_ep,
> + struct crg_transfer_trb *trb)
> +{
> + unsigned long offset;
> + int trb_idx;
> + dma_addr_t dma_addr = 0;
> +
> + trb_idx = trb - udc_ep->first_trb;
> + if (unlikely(trb_idx < 0))
> + return DMA_MAPPING_ERROR;
> +
> + offset = trb_idx * sizeof(*trb);
> + if (unlikely(offset >= udc_ep->tran_ring_info.len))
> + return DMA_MAPPING_ERROR;
> + dma_addr = udc_ep->tran_ring_info.dma_addr + offset;
> + return dma_addr;
> +}
> +
> +void crg_udc_update_dequeue_pt(struct crg_event_trb *event,
> + struct crg_udc_ep *udc_ep)
> +{
> + u32 deq_pt_lo = le32_to_cpu(event->dw0);
> + u32 deq_pt_hi = le32_to_cpu(event->dw1);
> + u64 dq_pt_addr = (u64)deq_pt_lo | ((u64)deq_pt_hi << 32);
> + struct crg_transfer_trb *deq_pt;
> +
> + deq_pt = crg_udc_tran_trb_dma_to_virt(udc_ep, dq_pt_addr);
> + if (!deq_pt)
> + return;
> + deq_pt++;
> + if (deq_pt == udc_ep->last_trb)
> + deq_pt = udc_ep->first_trb;
> +
> + if (FIELD_GET(TRB_TYPE_MASK, le32_to_cpu(deq_pt->dw3)) == TRB_TYPE_LINK)
> + deq_pt = udc_ep->first_trb;
> +
> + udc_ep->deq_pt = deq_pt;
> +}
[Severity: High]
What happens in crg_udc_update_dequeue_pt() if the event TRB's DMA address
points to the Link TRB at udc_ep->last_trb (for example, on a
CMPL_CODE_TRB_ERR event)?
Because udc_ep->last_trb sits at &udc_ep->first_trb[ring_size - 1],
crg_udc_tran_trb_dma_to_virt() accepts its DMA address and returns
deq_pt == udc_ep->last_trb.
Advancing deq_pt++ before checking against udc_ep->last_trb moves deq_pt to
udc_ep->last_trb + 1, which is one element past the end of the
udc_ep->tran_ring_info coherent DMA buffer.
Doesn't that cause the subsequent read of deq_pt->dw3 to access memory out of
bounds and potentially store udc_ep->last_trb + 1 into udc_ep->deq_pt, which
in turn causes crg_udc_room_on_ring() to hit its loop guard and treat the
entire ring as free?
> +
> +u32 crg_udc_trb_chunk_len(dma_addr_t dma, u64 remaining)
> +{
> + u32 boundary = CRG_UDC_TRB_MAX_BUFFER_SIZE -
> + (lower_32_bits(dma) & (CRG_UDC_TRB_MAX_BUFFER_SIZE - 1));
> +
> + return min_t(u64, remaining, boundary);
> +}
> +
> +u32 crg_udc_actual_before(struct crg_udc_ep *udc_ep,
> + struct crg_udc_request *udc_req,
> + struct crg_transfer_trb *pause)
> +{
> + struct crg_transfer_trb *trb = udc_req->first_trb;
> + u32 actual = 0;
> + u32 guard = 0;
> + u32 max_iter = udc_ep->tran_ring_info.len / sizeof(*trb);
> +
> + if (!trb || !pause)
> + return 0;
> +
> + while (trb != pause && trb != udc_ep->enq_pt && guard++ < max_iter) {
> + u32 len;
> +
> + if (FIELD_GET(TRB_TYPE_MASK, le32_to_cpu(trb->dw3)) ==
> + TRB_TYPE_LINK) {
> + trb = udc_ep->first_trb;
> + continue;
> + }
> + len = FIELD_GET(TRB_TRANSFER_LEN_MASK, le32_to_cpu(trb->dw2));
> + actual += len;
> + trb++;
> + if (trb == udc_ep->last_trb)
> + trb = udc_ep->first_trb;
> + }
> +
> + return min(actual, udc_req->usb_req.length);
> +}
[Severity: High]
In crg_udc_actual_before(), if crg_udc_ep_dequeue() stops an active endpoint
while a multi-TRB request wraps around the transfer ring and the hardware
dequeue pointer (pause) is at the Link TRB (udc_ep->last_trb), will this loop
skip right past pause?
At the end of the iteration for udc_ep->last_trb - 1, trb++ reaches
udc_ep->last_trb and is immediately wrapped to udc_ep->first_trb before the
while (trb != pause && ...) condition is evaluated:
trb++;
if (trb == udc_ep->last_trb)
trb = udc_ep->first_trb;
Because trb jumps from udc_ep->last_trb - 1 directly to udc_ep->first_trb, it
never compares equal to pause == udc_ep->last_trb (and the TRB_TYPE_LINK
check at the top of the loop is also bypassed), causing
crg_udc_actual_before() to sum all remaining queued TRBs up to
udc_ep->enq_pt into udc_req->usb_req.actual.
[1] https://lore.kernel.org/r/20261020-axiado-ax3000-usb-device-controller-v3-3-3012ecfc0a93@axiado.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030-axiado-ax3000-usb-device-controller-v4-0-c5267b36a777@axiado.com?part=3
next prev parent reply other threads:[~2026-10-30 8:29 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 8:10 [PATCH v4 0/6] Axiado AX3000 and AX3005 USB Device Controller (UDC) Petar Stepanovic
2026-10-30 8:10 ` [PATCH v4 1/6] dt-bindings: usb: gadget: udc: add Axiado AX3000 and AX3005 UDC Petar Stepanovic
2026-10-30 8:14 ` sashiko-bot
2026-10-30 8:10 ` [PATCH v4 2/6] usb: gadget: udc: crg: Add hardware definitions and structs Petar Stepanovic
2026-10-30 8:21 ` sashiko-bot
2026-10-30 8:10 ` [PATCH v4 3/6] usb: gadget: udc: crg: Add ring and transfer management Petar Stepanovic
2026-10-30 8:29 ` sashiko-bot [this message]
2026-10-30 8:10 ` [PATCH v4 4/6] usb: gadget: udc: crg: Add endpoint 0 handling Petar Stepanovic
2026-10-30 8:40 ` sashiko-bot
2026-10-30 8:10 ` [PATCH v4 5/6] usb: gadget: udc: crg: Add gadget and endpoint operations Petar Stepanovic
2026-10-30 8:50 ` sashiko-bot
2026-10-30 8:10 ` [PATCH v4 6/6] usb: gadget: udc: crg: Add core driver and build integration Petar Stepanovic
2026-10-30 8:46 ` sashiko-bot
2026-10-30 9:34 ` Philipp Zabel
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-190689@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=pstepanovic@axiado.com \
--cc=robh@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.