usb: device_next: uac2: Double buffering on IN data endpoints

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ń <tomasz.mon@nordicsemi.no>
This commit is contained in:
Tomasz Moń 2024-11-27 11:48:01 +01:00 committed by Benjamin Cabé
commit c19d34c5d3

View file

@ -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.
@ -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 */
@ -80,6 +81,7 @@ struct uac2_ctx {
*/
atomic_t as_active;
atomic_t as_queued;
atomic_t as_double;
uint32_t fb_queued;
};
@ -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;
@ -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);
@ -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;
}
@ -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);
}
@ -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)) {