Skip to content

Commit

Permalink
usb: device_next: uac2: Double buffering on IN data endpoints
Browse files Browse the repository at this point in the history
Application is expected to call usbd_uac2_send() on each enabled USB
Streaming Output Terminal (isochronous IN data endpoint) exactly once
every SOF. The class is bookkeeping queued transfers to make it easier
to determine component at fault when things go wrong. However, this
approach only works fine if the underlying USB device controller buffers
the data to be sent on next SOF and reports the transfer completion
immediately after data is buffered (e.g. nRF52 USBD).

While DWC2 otg also requires the SW to arm endpoint with data for the
next SOF, unlike nRF52 USBD the transfer is only considered complete
after either the IN token for isochronous endpoint is received or after
the Periodic Frame Interval elapses without IN token. This design
inevitably requires the application to be able to have at least two
buffers for isochronous IN endpoints.

Support dual buffering on IN data endpoints to facilitate sending
isochronous IN data on every SOF regardless of the underlying USB device
controller design.

Signed-off-by: Tomasz Moń <[email protected]>
  • Loading branch information
tmon-nordic authored and kartben committed Nov 30, 2024
1 parent edbb053 commit c19d34c
Showing 1 changed file with 18 additions and 12 deletions.
30 changes: 18 additions & 12 deletions subsys/usb/device_next/class/usbd_uac2.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ LOG_MODULE_REGISTER(usbd_uac2, CONFIG_USBD_UAC2_LOG_LEVEL);

#define DT_DRV_COMPAT zephyr_uac2

#define COUNT_UAC2_AS_ENDPOINTS(node) \
#define COUNT_UAC2_AS_ENDPOINT_BUFFERS(node) \
IF_ENABLED(DT_NODE_HAS_COMPAT(node, zephyr_uac2_audio_streaming), ( \
+ AS_HAS_ISOCHRONOUS_DATA_ENDPOINT(node) + \
+ AS_IS_USB_ISO_IN(node) /* ISO IN double buffering */ + \
AS_HAS_EXPLICIT_FEEDBACK_ENDPOINT(node)))
#define COUNT_UAC2_ENDPOINTS(i) \
#define COUNT_UAC2_EP_BUFFERS(i) \
+ DT_PROP(DT_DRV_INST(i), interrupt_endpoint) \
DT_INST_FOREACH_CHILD(i, COUNT_UAC2_AS_ENDPOINTS)
#define UAC2_NUM_ENDPOINTS DT_INST_FOREACH_STATUS_OKAY(COUNT_UAC2_ENDPOINTS)
DT_INST_FOREACH_CHILD(i, COUNT_UAC2_AS_ENDPOINT_BUFFERS)
#define UAC2_NUM_EP_BUFFERS DT_INST_FOREACH_STATUS_OKAY(COUNT_UAC2_EP_BUFFERS)

/* Net buf is used mostly with external data. The main reason behind external
* data is avoiding unnecessary isochronous data copy operations.
Expand All @@ -40,7 +41,7 @@ LOG_MODULE_REGISTER(usbd_uac2, CONFIG_USBD_UAC2_LOG_LEVEL);
* "wasted memory" here is likely to be smaller than the memory overhead for
* more complex "only as much as needed" schemes (e.g. heap).
*/
UDC_BUF_POOL_DEFINE(uac2_pool, UAC2_NUM_ENDPOINTS, 6,
UDC_BUF_POOL_DEFINE(uac2_pool, UAC2_NUM_EP_BUFFERS, 6,
sizeof(struct udc_buf_info), NULL);

/* 5.2.2 Control Request Layout */
Expand Down Expand Up @@ -80,6 +81,7 @@ struct uac2_ctx {
*/
atomic_t as_active;
atomic_t as_queued;
atomic_t as_double;
uint32_t fb_queued;
};

Expand Down Expand Up @@ -247,6 +249,7 @@ int usbd_uac2_send(const struct device *dev, uint8_t terminal,
struct uac2_ctx *ctx = dev->data;
struct net_buf *buf;
const struct usb_ep_descriptor *desc;
atomic_t *queued_bits = &ctx->as_queued;
uint8_t ep = 0;
int as_idx = terminal_to_as_interface(dev, terminal);
int ret;
Expand All @@ -267,9 +270,12 @@ int usbd_uac2_send(const struct device *dev, uint8_t terminal,
return 0;
}

if (atomic_test_and_set_bit(&ctx->as_queued, as_idx)) {
LOG_ERR("Previous send not finished yet on 0x%02x", ep);
return -EAGAIN;
if (atomic_test_and_set_bit(queued_bits, as_idx)) {
queued_bits = &ctx->as_double;
if (atomic_test_and_set_bit(queued_bits, as_idx)) {
LOG_DBG("Already double queued on 0x%02x", ep);
return -EAGAIN;
}
}

buf = uac2_buf_alloc(ep, data, size);
Expand All @@ -278,7 +284,7 @@ int usbd_uac2_send(const struct device *dev, uint8_t terminal,
* enough, but if it does all we loose is just single packet.
*/
LOG_ERR("No netbuf for send");
atomic_clear_bit(&ctx->as_queued, as_idx);
atomic_clear_bit(queued_bits, as_idx);
ctx->ops->buf_release_cb(dev, terminal, data, ctx->user_data);
return -ENOMEM;
}
Expand All @@ -287,7 +293,7 @@ int usbd_uac2_send(const struct device *dev, uint8_t terminal,
if (ret) {
LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep);
net_buf_unref(buf);
atomic_clear_bit(&ctx->as_queued, as_idx);
atomic_clear_bit(queued_bits, as_idx);
ctx->ops->buf_release_cb(dev, terminal, data, ctx->user_data);
}

Expand Down Expand Up @@ -761,8 +767,8 @@ static int uac2_request(struct usbd_class_data *const c_data, struct net_buf *bu

if (is_feedback) {
ctx->fb_queued &= ~BIT(as_idx);
} else {
atomic_clear_bit(&ctx->as_queued, as_idx);
} else if (!atomic_test_and_clear_bit(&ctx->as_queued, as_idx) || buf->frags) {
atomic_clear_bit(&ctx->as_double, as_idx);
}

if (USB_EP_DIR_IS_OUT(ep)) {
Expand Down

0 comments on commit c19d34c

Please sign in to comment.