Linux USB
 help / color / mirror / Atom feed
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

  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