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 4/6] usb: gadget: udc: crg: Add endpoint 0 handling
Date: Fri, 30 Oct 2026 08:40:30 +0000 [thread overview]
Message-ID: <sashiko-outbox-190707@kernel.org> (raw)
In-Reply-To: <20261030-axiado-ax3000-usb-device-controller-v4-4-c5267b36a777@axiado.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] usb: gadget: udc: crg: stale setup_status on EP0 halt in crg_udc_set_ep0_halt()
- [High] usb: gadget: udc: crg: missing state checks after relocking in crg_udc_handle_setup_pkt()
- [High] usb: gadget: crg: missing reset check after ep0 nuke in setup paths
- [Medium] usb: gadget: crg: crg_udc_prepare_for_setup() skips stopping halted EP0
--
commit 0dcda6f8ceb35327942fb48b7002136244fccc5c
Author: Petar Stepanovic <pstepanovic@axiado.com>
usb: gadget: udc: crg: Add endpoint 0 handling
This patch adds endpoint 0 setup packet, status stage, and control transfer
handling for the Axiado AX3000/AX3005 UDC driver.
> diff --git a/drivers/usb/gadget/udc/crg/crg-udc-ep0.c b/drivers/usb/gadget/udc/crg/crg-udc-ep0.c
> new file mode 100644
> index 0000000000000..073d9090dd07c
> --- /dev/null
> +++ b/drivers/usb/gadget/udc/crg/crg-udc-ep0.c
[ ... ]
> +int crg_udc_abort_ep0_for_setup(struct crg_gadget_dev *crg_udc)
> +{
> + struct crg_udc_ep *udc_ep0 = &crg_udc->udc_ep[0];
> + struct crg_uccr __iomem *uccr = crg_udc->uccr;
> + dma_addr_t dqptaddr;
> + u32 cmd_param0;
> + u32 cmd_param1;
> + u32 tmp;
> + int ret;
[ ... ]
> + udc_ep0->ep_state = EP_STATE_STOPPED;
> + /* Complete requests belonging to the aborted control transfer. */
> + crg_udc_ep_nuke(udc_ep0, -ECONNRESET);
> +
> + if (!udc_ep0->first_trb || !udc_ep0->tran_ring_info.cpu_addr) {
> + crg_udc->ep0_state = EP0_STATE_ERROR;
> + return -ENODEV;
> + }
[Severity: High]
When crg_udc_ep_nuke() runs here in crg_udc_abort_ep0_for_setup(), and
similarly in crg_udc_prepare_for_setup(), crg_udc_req_done() drops
crg_udc->udc_lock to invoke the request completion callback.
While udc_lock is dropped, another CPU running crg_gadget_stop() or
crg_udc_shutdown() can acquire udc_lock and call crg_udc_reset(), setting
crg_udc->resetting = true, incrementing udc_ep[0].generation, and dropping
udc_lock while polling for CRG_U3DC_CTRL_SWRST to clear, followed by
crg_udc_reset_data_struct() and crg_udc_init_ep0().
After crg_udc_ep_nuke() returns, neither crg_udc_abort_ep0_for_setup() nor
crg_udc_prepare_for_setup() checks crg_udc->resetting, udc_ep[0].generation,
or udc_event[0].generation before touching controller registers and issuing
CRG_CMD_INIT_EP0. If crg_udc_reset() is still polling SWRST, this issues
MMIO commands mid-reset; if crg_gadget_stop() has already staged
CRG_CMD_INIT_EP0 with CRG_U3DC_CMD_CTRL_ACTIVE set while RUN is clear, the
subsequent CRG_CMD_INIT_EP0 fails with -EBUSY, leaving ep0_state in
EP0_STATE_ERROR and causing crg_udc_handle_event() to call
crg_udc_set_ep0_halt() and queue status_req after the gadget has stopped.
Should both functions snapshot the generation counter before
crg_udc_ep_nuke() and abort if crg_udc->resetting is set or the generation
changed while udc_lock was dropped?
[ ... ]
> +int crg_udc_prepare_for_setup(struct crg_gadget_dev *crg_udc)
> +{
> + struct crg_udc_ep *udc_ep0_ptr;
> + dma_addr_t dqptaddr;
> + u32 cmd_param0;
> + u32 cmd_param1;
> + int ret;
> +
> + if (!crg_udc_is_event_rings_empty(crg_udc) ||
> + crg_udc->portsc_on_reconnecting == 1)
> + return -EBUSY;
> +
> + udc_ep0_ptr = &crg_udc->udc_ep[0];
> + /* If we reinit ep0 on bus reset, we just make ep0 dequeue pointer
> + * align with enqueue pointer, all remaining xfer trbs became dumb once
> + * which will not produce xfer event anymore.
> + *
> + * If we considering the opposite solution, we should wait all ep0 xfer
> + * trbs be completed(with some err complete code)
> + */
> + if (crg_udc->reinit_ep0_on_bus_reset) {
> + struct crg_uccr __iomem *uccr = crg_udc->uccr;
> + u32 running;
> +
> + /* Block requeue from giveback callbacks, then quiesce DMA. */
> + crg_udc->ep0_state = EP0_STATE_ABORTING;
> + if (crg_udc_get_ep_state(crg_udc, 0) == EP_STATE_RUNNING) {
> + ret = crg_udc_issue_command(crg_udc, CRG_CMD_STOP_EP,
> + BIT(0), 0);
> + if (ret)
> + goto reset_error;
> +
> + ret = readl_poll_timeout_atomic(&uccr->ep_running, running,
> + !(running & BIT(0)), 10, 10000);
> + if (ret)
> + goto reset_error;
> + }
> + udc_ep0_ptr->ep_state = EP_STATE_STOPPED;
> + crg_udc_ep_nuke(udc_ep0_ptr, -ESHUTDOWN);
[Severity: Medium]
As reported by Sashiko on v3 [1], what happens if a USB bus reset occurs
after a control transfer has stalled?
When crg_udc_set_ep0_halt() stalls EP0, it calls crg_udc_build_ep0_status()
to queue a status-stage stall TRB, rings doorbell 0, places status_req on
udc_ep[0].queue, and immediately sets udc_ep[0].ep_state = EP_STATE_HALTED
in software while the hardware is still processing the TRB.
If a bus reset then triggers crg_udc_prepare_for_setup() while ep_state is
EP_STATE_HALTED, checking only for EP_STATE_RUNNING skips issuing
CRG_CMD_STOP_EP and polling uccr->ep_running before crg_udc_ep_nuke() and
CRG_CMD_INIT_EP0. Should crg_udc_prepare_for_setup() stop EP0 whenever it
is not disabled or stopped, matching crg_udc_abort_ep0_for_setup()?
[ ... ]
> +int crg_udc_set_ep0_halt(struct crg_gadget_dev *crg_udc)
> +{
> + struct crg_udc_ep *udc_ep_ptr = &crg_udc->udc_ep[0];
> + enum crg_ep_state ep_state;
> +
> + ep_state = crg_udc_get_ep_state(crg_udc, udc_ep_ptr->dci);
> + if (ep_state == EP_STATE_HALTED || ep_state == EP_STATE_DISABLED)
> + return 0;
> +
> + crg_udc_build_ep0_status(udc_ep_ptr, true, -EINVAL, NULL, 0, 1);
> +
> + udc_ep_ptr->ep_state = EP_STATE_HALTED;
> +
> + return 0;
> +}
[Severity: High]
As reported by Sashiko on v3 [2], does crg_udc_set_ep0_halt() leave
crg_udc->setup_status in a stale data-stage state when stalling a control
transfer with wLength > 0?
In crg_udc_handle_setup_pkt() and getstatusrequest(), crg_udc->setup_status
is set to DATA_STAGE_XFER or DATA_STAGE_RECV before calling setselrequest(),
crg_udc_build_td(), or driver->setup():
crg_udc_handle_setup_pkt()
crg_udc->setup_status = DATA_STAGE_XFER / DATA_STAGE_RECV;
driver->setup() -> fails
crg_udc_set_ep0_halt()
crg_udc_build_ep0_status(..., stall = 1)
Because crg_udc_set_ep0_halt() does not update crg_udc->setup_status to
STATUS_STAGE_XFER (or clear crg_udc->setup_fn_call_back), two problems
follow:
1. crg_udc_setup_status_trb() computes dir as
(crg_udc->setup_status == STATUS_STAGE_XFER) ? 0 : 1, which evaluates
to 1 (OUT) instead of 0 (IN).
2. When the stall status TRB completes and invokes
crg_udc_ep0_req_complete(), switch (crg_udc->setup_status) matches
DATA_STAGE_XFER or DATA_STAGE_RECV instead of the default branch:
crg_udc_ep0_req_complete() {
switch (crg_udc->setup_status) {
case DATA_STAGE_XFER:
crg_udc->setup_status = STATUS_STAGE_RECV;
crg_udc_build_ep0_status(udc_ep_ptr, true, -EINPROGRESS, NULL,
0, 0);
break;
case DATA_STAGE_RECV:
crg_udc->setup_status = STATUS_STAGE_XFER;
crg_udc_build_ep0_status(udc_ep_ptr, true, -EINPROGRESS, NULL,
0, 0);
break;
This queues a second, unrequested, non-stalled status-stage TRB on EP0 and
rings doorbell 0 on the halted endpoint. Should crg_udc_set_ep0_halt() set
crg_udc->setup_status = STATUS_STAGE_XFER and clear
crg_udc->setup_fn_call_back before calling crg_udc_build_ep0_status()?
[ ... ]
> +void crg_udc_handle_setup_pkt(struct crg_gadget_dev *crg_udc,
> + struct usb_ctrlrequest *setup_pkt, u8 setup_tag,
> + unsigned long *flags)
> +{
> + u16 w_value = le16_to_cpu(setup_pkt->wValue);
> + u16 w_index = le16_to_cpu(setup_pkt->wIndex);
> + u16 w_length = le16_to_cpu(setup_pkt->wLength);
> + struct usb_gadget_driver *driver;
> + u32 tmp;
> +
> + /* No SETUP (including SET_ADDRESS) may overtake a pending reset. */
> + spin_unlock_irqrestore(&crg_udc->udc_lock, *flags);
> + flush_work(&crg_udc->udc_reset_work);
> + spin_lock_irqsave(&crg_udc->udc_lock, *flags);
> + if (crg_udc->resetting || !crg_udc->gadget_driver ||
> + crg_udc->ep0_state == EP0_STATE_ABORTING)
> + return;
[ ... ]
> + if (w_length) {
> + /* data phase from gadget like GET_CONFIGURATION
> + * call the setup routine of gadget driver.
> + * remember the request direction.
> + */
> + crg_udc->setup_status = (setup_pkt->bRequestType & USB_DIR_IN) ?
> + DATA_STAGE_XFER :
> + DATA_STAGE_RECV;
> + }
> + driver = crg_udc->async_callbacks ? crg_udc->gadget_driver : NULL;
> + spin_unlock_irqrestore(&crg_udc->udc_lock, *flags);
> +
> + /* Recheck callback enablement after the locked snapshot. */
> + if (!driver || !READ_ONCE(crg_udc->async_callbacks) ||
> + READ_ONCE(crg_udc->gadget_driver) != driver ||
> + driver->setup(&crg_udc->gadget, setup_pkt) < 0) {
> + spin_lock_irqsave(&crg_udc->udc_lock, *flags);
> + crg_udc_set_ep0_halt(crg_udc);
> + return;
> + }
> + spin_lock_irqsave(&crg_udc->udc_lock, *flags);
> + if (setup_pkt->bRequest == USB_REQ_SET_CONFIGURATION &&
> + (setup_pkt->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) ==
> + (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) {
> + if (w_value) {
> + crg_udc->device_state = USB_STATE_CONFIGURED;
> + usb_gadget_set_state(&crg_udc->gadget,
> + USB_STATE_CONFIGURED);
> + } else {
> + /* Endpoint disable may already have updated device_state. */
> + crg_udc->device_state = USB_STATE_ADDRESS;
> + usb_gadget_set_state(&crg_udc->gadget,
> + USB_STATE_ADDRESS);
> + }
> + }
> +}
[Severity: High]
Can dropping crg_udc->udc_lock in crg_udc_handle_setup_pkt() race with a
concurrent reset, unbind, or new setup packet?
First, around flush_work(&crg_udc->udc_reset_work), if a new setup packet
aborted an in-flight control transfer, crg_udc_abort_ep0_for_setup() has
already set ep0_state = EP0_STATE_IDLE before udc_lock is dropped for
flush_work(), allowing a concurrent usb_ep_queue() on EP0 to bypass the
EP0_STATE_ABORTING check in crg_udc_queue_ctrl(). In addition, if a
controller reset (crg_udc_reset() and crg_udc_reset_data_struct())
completes while udc_lock is dropped, crg_udc->resetting is false again and
udc_event[0].generation has been incremented, but crg_udc_handle_setup_pkt()
does not verify udc_event[0].generation and continues processing the stale
pre-reset setup packet.
Second, after udc_lock is dropped for driver->setup() and reacquired,
neither the error branch nor the USB_REQ_SET_CONFIGURATION branch rechecks
crg_udc->resetting, crg_udc->gadget_driver, crg_udc->async_callbacks,
crg_udc->ep0_state, crg_udc->setup_tag, or udc_event[0].generation:
1. If async_callbacks was cleared during gadget stop/unbind, or if
crg_udc_reset() is currently polling SWRST with udc_lock dropped (where
crg_udc->resetting is true while udc_ep[0].ep_state is still
EP_STATE_RUNNING), the error path unconditionally calls
crg_udc_set_ep0_halt(crg_udc), queueing a status TRB and ringing
doorbell 0 during reset or teardown.
2. If driver->setup() succeeded for USB_REQ_SET_CONFIGURATION while a
concurrent reset or disconnect occurred, the post-lock block overwrites
crg_udc->device_state and the gadget state with USB_STATE_CONFIGURED or
USB_STATE_ADDRESS.
Should crg_udc_handle_setup_pkt() keep ep0_state in EP0_STATE_ABORTING
until flush_work() finishes, and validate the event generation,
crg_udc->resetting, crg_udc->async_callbacks, and crg_udc->setup_tag after
reacquiring udc_lock?
[1] https://lore.kernel.org/r/20261020-axiado-ax3000-usb-device-controller-v3-4-3012ecfc0a93@axiado.com
[2] https://lore.kernel.org/r/20261020-axiado-ax3000-usb-device-controller-v3-4-3012ecfc0a93@axiado.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030-axiado-ax3000-usb-device-controller-v4-0-c5267b36a777@axiado.com?part=4
next prev parent reply other threads:[~2026-10-30 8:40 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
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 [this message]
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-190707@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox