Skip to content

usb: device_next: remove udc_buf_get_all()#100876

Merged
fabiobaltieri merged 1 commit intozephyrproject-rtos:mainfrom
tmon-nordic:remove-udc_buf_get_all
Mar 21, 2026
Merged

usb: device_next: remove udc_buf_get_all()#100876
fabiobaltieri merged 1 commit intozephyrproject-rtos:mainfrom
tmon-nordic:remove-udc_buf_get_all

Conversation

@tmon-nordic
Copy link
Copy Markdown
Contributor

Function udc_buf_get_all() was intended to be a helper to remove all requests from endpoint FIFO. While for just freeing all queue the it may be argued that there may be some doubtful simplicity argument, merging multiple submitted transfers into one is just enforcing unnecessary complexity on class implementations.

At general level, every submitted (enqueued) request should get corresponding completion (request callback) call. UDC drivers were violating this sensible behavior when dequeuing (cancelling) requests by merging all submitted requests into one.

Remove udc_buf_get_all() and replace all uses with simple loops. For most classes (that submit just one request for an endpoint at a time) this has no functional difference. For classes that implement double buffering this simplifies completion handling.


Related discussion: #99439

Copy link
Copy Markdown
Contributor

@mathieuchopstm mathieuchopstm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fair. +1 for STM32 driver.

As there appears to be two main patterns repeating, it may be sensible to provide helper functions for them though, e.g.:

static void udc_buf_unref_all(struct udc_ep_config *cfg)
{
  struct net_buf *buf;

  while ((buf = udc_buf_get(cfg))) {
    net_buf_unref(buf);
  }
}

/* this name is pretty bad but i'm out of ideas */
static void udc_drain_queue_with_ep_event(const struct device *dev, struct udc_ep_config *cfg, int status) {
  struct net_buf *buf;

  while ((buf = udc_buf_get(cfg))) {
    udc_submit_ep_event(dev, buf, status);
  }
}

@tmon-nordic tmon-nordic force-pushed the remove-udc_buf_get_all branch from f1c1f4b to 6d70843 Compare December 11, 2025 12:07
@zephyrbot zephyrbot added the area: Tests Issues related to a particular existing or missing test label Dec 11, 2025
josuah
josuah previously approved these changes Feb 17, 2026
mathieuchopstm
mathieuchopstm previously approved these changes Feb 17, 2026
Copy link
Copy Markdown
Contributor

@mathieuchopstm mathieuchopstm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STM32 changes LGTM

Comment thread drivers/usb/udc/udc_ambiq.c Outdated
fabiobaltieri
fabiobaltieri previously approved these changes Feb 18, 2026
Comment on lines -114 to -134
struct net_buf *udc_buf_get_all(struct udc_ep_config *const ep_cfg)
{
struct net_buf *buf;

buf = k_fifo_get(&ep_cfg->fifo, K_NO_WAIT);
if (!buf) {
return NULL;
}

LOG_DBG("ep 0x%02x dequeue %p", ep_cfg->addr, buf);
for (struct net_buf *n = buf; !k_fifo_is_empty(&ep_cfg->fifo); n = n->frags) {
n->frags = k_fifo_get(&ep_cfg->fifo, K_NO_WAIT);
LOG_DBG("|-> %p ", n->frags);
if (n->frags == NULL) {
break;
}
}

return buf;
}

Copy link
Copy Markdown
Contributor

@jfischer-no jfischer-no Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it should be reworked after 3d306c1, but yes maybe it is better to remove it.

It looks like there should be helpers for the two common patterns to flush the queue and cancel all transfers.

-	buf = udc_buf_get_all(cfg);
-	if (buf) {
+	while ((buf = udc_buf_get(cfg))) {
		udc_submit_ep_event(dev, buf, -ECONNABORTED);
	}
-	buf = udc_buf_get_all(cfg);
-	if (buf) {
+	while ((buf = udc_buf_get(cfg_out))) {
		net_buf_unref(buf);
	}

while ((buf = udc_buf_get(cfg_out))) is a bit problematic as it is foreseeable that people will get confused. Also, I do not want someone to get triggered, for IMO valid reasons here, by the rule "The result of an assignment operator should not be used". Please add helpers like:

/**
 * @brief Unref all queued UDC request
 *
 * Remove all the request from the endpoint FIFO and unref them.
 *
 * @param[in] cfg Pointer to endpoint configuration
 */
void udc_ep_unref_queued(struct udc_ep_config *const cfg);

void udc_ep_unref_queued(struct udc_ep_config *const cfg)
{
	struct net_buf *buf;

	buf = udc_buf_get(cfg)
	while (buf != NULL) {
		net_buf_unref(buf);
		buf = udc_buf_get(cfg)
	}
}
/**
 * @brief Cancel all queued UDC request
 *
 * Remove all the request from the endpoint FIFO and submit
 * them to higher level.
 *
 * @param[in] cfg Pointer to endpoint configuration
 */
void udc_ep_cancel_queued(struct udc_ep_config *const cfg);

void udc_ep_cancel_queued(struct udc_ep_config *const cfg)
{
	struct net_buf *buf;

	buf = udc_buf_get(cfg)
	while (buf != NULL) {
		udc_submit_ep_event(dev, buf, -ECONNABORTED);
		buf = udc_buf_get(cfg)
	}
}

Copy link
Copy Markdown
Contributor Author

@tmon-nordic tmon-nordic Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while ((buf = udc_buf_get(cfg_out))) is a bit problematic as it is foreseeable that people will get confused

The while (( is rather common in Zephyr codebase, for example:

while ((buf = k_fifo_get(&chan->iso->iso.txq, K_NO_WAIT))) {

while ((buf = k_fifo_get(&le_chan->tx_queue, K_NO_WAIT))) {
net_buf_unref(buf);
}
/* Remove buffers on the RX queue */
while ((buf = k_fifo_get(&le_chan->rx_queue, K_NO_WAIT))) {
net_buf_unref(buf);
}

while ((buf = k_fifo_get(&le_chan->tx_queue, K_NO_WAIT))) {

void udc_ep_unref_queued(struct udc_ep_config *const cfg)

This sounds like a bad advice to me. UDC should not be freeing queued buffers on its own, but rather it should be completing them. The only reason why the requests are currently freed by UDC driver is the broken design for how control transfers are handled. Hopefully this is resolved by #103493

void udc_ep_cancel_queued(struct udc_ep_config *const cfg)

I don't see the point. Maybe if the reason was provided as parameter (instead of hardcoding -ECONNABORTED) there would be some merit to it.

Would you revise your -1 given above argumentation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The while (( is rather common in Zephyr codebase, for example:

It is up to Bluetooth to fix "The result of an assignment operator should not be used".

Would you revise your -1 given above argumentation?

No, after #103493 is merged, there is still at common pattern in your changes

-	buf = udc_buf_get_all(cfg);
-	if (buf) {
+	while ((buf = udc_buf_get(cfg))) {
		udc_submit_ep_event(dev, buf, -ECONNABORTED);
	}

and that should be moved to something like

/**
 * @brief Cancel all queued UDC request
 *
 * Remove all the request from the endpoint FIFO and submit
 * them to higher level.
 *
 * @param[in] cfg Pointer to endpoint configuration
 */
void udc_ep_cancel_queued(struct udc_ep_config *const cfg);

void udc_ep_cancel_queued(struct udc_ep_config *const cfg)
{
	struct net_buf *buf;

	buf = udc_buf_get(cfg)
	while (buf != NULL) {
		udc_submit_ep_event(dev, buf, -ECONNABORTED);
		buf = udc_buf_get(cfg)
	}
}

This sounds like a bad advice to me. UDC should not be freeing queued buffers on its own, but rather it should be completing them. The only reason why the requests are currently freed by UDC driver is the broken design for how control transfers are handled. Hopefully this is resolved by #103493

One still can find this pattern here:
3a619d9#diff-9636542b03fbd14ba20898a4f5562fc753535a02a6547cb9ac3e346b9a2b6e2dR2182-R2184
and here

for (buf = udc_buf_get(cfg); buf; buf = udc_buf_get(cfg)) {
net_buf_unref(buf);
}

for (buf = udc_buf_get(cfg); buf; buf = udc_buf_get(cfg)) {
net_buf_unref(buf);
}

Looks like it still makes sense to have helper function void udc_ep_unref_queued().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is up to Bluetooth to fix "The result of an assignment operator should not be used".

Please open bug report so they are aware that this is an issue. That having said, I am fairly certain it is not issue and you are completely unreasonably forcing people to use different constructs.

I added void udc_ep_cancel_queued(const struct device *dev, struct udc_ep_config *const cfg);

Looks like it still makes sense to have helper function void udc_ep_unref_queued().

I prefer not to. These should eventually get removed, but it should be considered out-of-scope here.

@tmon-nordic tmon-nordic force-pushed the remove-udc_buf_get_all branch from 3841aa8 to 042d662 Compare March 20, 2026 07:55
@zephyrbot zephyrbot added the platform: ADI Analog Devices, Inc. label Mar 20, 2026
@tmon-nordic tmon-nordic force-pushed the remove-udc_buf_get_all branch from 042d662 to 3a619d9 Compare March 20, 2026 07:57
@tmon-nordic tmon-nordic force-pushed the remove-udc_buf_get_all branch from 3a619d9 to df3dbce Compare March 20, 2026 11:18
josuah
josuah previously approved these changes Mar 20, 2026
Copy link
Copy Markdown
Contributor

@josuah josuah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usb: device_next: Allocate control buffers in stack (244b015) already removed all other uses of udc_buf_get_all() as a side effect.

Function udc_buf_get_all() was intended to be a helper to remove all
requests from endpoint FIFO. While for just freeing all queue the
it may be argued that there may be some doubtful simplicity argument,
merging multiple submitted transfers into one is just enforcing
unnecessary complexity on class implementations.

At general level, every submitted (enqueued) request should get
corresponding completion (request callback) call. UDC drivers were
violating this sensible behavior when dequeuing (cancelling) requests by
merging all submitted requests into one.

Remove udc_buf_get_all() and replace all uses with simple loops. For
most classes (that submit just one request for an endpoint at a time)
this has no functional difference. For classes that implement double
buffering this simplifies completion handling.

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
@sonarqubecloud
Copy link
Copy Markdown

@jfischer-no jfischer-no added this to the v4.4.0 milestone Mar 20, 2026
@fabiobaltieri fabiobaltieri merged commit 1525950 into zephyrproject-rtos:main Mar 21, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Tests Issues related to a particular existing or missing test area: USB Universal Serial Bus platform: ADI Analog Devices, Inc. platform: Ambiq Ambiq platform: ITE ITE platform: NXP NXP platform: STM32 ST Micro STM32

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants