add chipidea ISO enable option Signed-off-by: HiFiPHile <admin@hifiphile.com>
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md index 5c2d6ec..4024c4e 100644 --- a/examples/host/audio_host/README.md +++ b/examples/host/audio_host/README.md
@@ -117,7 +117,7 @@ ## Notes - `tuh_audio_descriptor_cb()` exposes the validated Audio Control descriptor block during enumeration. Applications that need raw entity controls must copy the required entity IDs or descriptor fields before the callback returns, then use `tuh_audio_control_xfer()` after the device mounts. -- While a stream is running, the driver queues up to `CFG_TUH_XFER_QUEUE_DEPTH` isochronous transfers with separate buffers and refills on completion. The example selects depth 2 on supported EHCI configurations and depth 1 otherwise. Transfers follow the endpoint's `bInterval`. `tuh_audio_capture_cb()` / `tuh_audio_playback_cb()` only count completed transfers; `audio_app_task()` services the FIFOs independently from the main loop. `tuh_audio_event_cb()` reports asynchronous start/stop results and unrecoverable transfer failures. The example restarts a failed stream automatically 100 ms later. +- `audio_app_task()` services the FIFOs independently from the main loop. `tuh_audio_event_cb()` reports asynchronous start/stop results and unrecoverable transfer failures. The example restarts a failed stream automatically 100 ms later. - Capture and playback streams running concurrently in the same Audio Control instance must use the same sample rate. - `tuh_audio_read()` / `tuh_audio_write()` are non-blocking FIFO operations: they return the number of whole frames actually read/queued. `tuh_audio_read_available()` reports captured frames ready to read; `tuh_audio_write_available()` reports free playback capacity. `tuh_audio_write()` only queues data; the playback transfer-completion chain sends it, or sends silence when the FIFO does not contain a complete polling interval without consuming the partial data. - Isochronous transfers require the host to poll `tuh_task()` continuously; the capture FIFO absorbs short scheduling gaps and overwrites the oldest frames when full.
diff --git a/examples/host/audio_host/src/tusb_config.h b/examples/host/audio_host/src/tusb_config.h index 50b5f30..983db5d 100644 --- a/examples/host/audio_host/src/tusb_config.h +++ b/examples/host/audio_host/src/tusb_config.h
@@ -95,9 +95,9 @@ #define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) //------------- Audio Host Config -------------// -// Use the HCD's queue capacity, resolved by tusb_option.h after this config. -#ifndef CFG_TUH_XFER_QUEUE_DEPTH - #define CFG_TUH_XFER_QUEUE_DEPTH TUP_HCD_XFER_QUEUE_DEPTH +// Enable ISO and two transfer buffers on ChipIdea; other HCDs keep their defaults. +#ifndef CFG_TUH_CHIPIDEA_ISO_ENABLE + #define CFG_TUH_CHIPIDEA_ISO_ENABLE 1 #endif #define CFG_TUH_AUDIO_MAX 1 #define CFG_TUH_AUDIO_EPIN_BUFSIZE 256 // max capture transfer the application submits
diff --git a/src/portable/ehci/ehci.c b/src/portable/ehci/ehci.c index 45e6b1f..8bfcca6 100644 --- a/src/portable/ehci/ehci.c +++ b/src/portable/ehci/ehci.c
@@ -61,8 +61,9 @@ * Each submission covers one service interval: HS uses iTDs (up to 3 * transactions), FS uses siTDs. Requests exceeding interval capacity fail. * - * CFG_TUH_XFER_QUEUE_DEPTH defaults to 1; EHCI supports up to 2. Accepted - * buffers/descriptors remain owned until FIFO terminal completion. QUEUED + * CFG_TUH_CHIPIDEA_ISO_ENABLE enables ISO and defaults CFG_TUH_XFER_QUEUE_DEPTH + * to 2 (maximum supported); otherwise depth defaults to 1. Accepted buffers + * and descriptors remain owned until FIFO terminal completion. QUEUED * reports spare capacity with zero length; it does not release the buffer. * Audio uses a buffer per slot, primes on QUEUED and refills on completion. * Public tuh_edpt_xfer() retains single-outstanding callback behavior. @@ -108,7 +109,7 @@ * * References: EHCI 1.0 ch. 3/4; USB 2.0 ch. 11; RT1064 RM Rev. 2 ch. 42. */ -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE // An iTD must not cross a 4 KiB boundary (EHCI chapter 3). typedef union TU_ATTR_ALIGNED(64) { ehci_itd_t itd; @@ -139,12 +140,12 @@ typedef union { ehci_qhd_t qhd; -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE iso_ep_t iso; #endif } ehci_ep_t; -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE typedef union TU_ATTR_ALIGNED(64) { ehci_qtd_t qtd[2]; iso_td_t iso; @@ -166,7 +167,7 @@ }control[CFG_TUH_DEVICE_MAX+CFG_TUH_HUB+1]; ehci_ep_t qhd_pool[QHD_MAX]; -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE ehci_td_pair_t qtd_pool[(QTD_MAX + 1) / 2]; bool qhd_is_iso[QHD_MAX]; bool qtd_is_iso[(QTD_MAX + 1) / 2]; @@ -174,7 +175,7 @@ ehci_qtd_t qtd_pool[QTD_MAX] TU_ATTR_ALIGNED(32); #endif -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE uint32_t iso_uframe; uint16_t iso_last_frindex; uint8_t iso_saved_itc; @@ -259,7 +260,7 @@ TU_ATTR_ALWAYS_INLINE static inline void list_remove(ehci_link_t* head, ehci_link_t* prev, ehci_qhd_t* qhd); static void list_remove_qhd_by_addr(ehci_link_t *list_head, uint8_t dev_addr, uint8_t ep_addr); -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE static iso_ep_t* iso_ep_find(uint8_t daddr, uint8_t ep_addr); static bool iso_ep_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const* desc); static bool iso_ep_close(uint8_t rhport, iso_ep_t* ep); @@ -396,7 +397,7 @@ return; } -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE for (size_t i = 0; i < QHD_MAX; i++) { if (ehci_data.qhd_is_iso[i] && ehci_data.qhd_pool[i].iso.daddr == daddr) { TU_ASSERT(iso_ep_close(rhport, &ehci_data.qhd_pool[i].iso), ); @@ -543,7 +544,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) { if (ep_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE return iso_ep_open(rhport, dev_addr, ep_desc); #else return false; @@ -594,7 +595,7 @@ } bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE iso_ep_t* iso = iso_ep_find(daddr, ep_addr); if (iso != NULL) { return iso_ep_close(rhport, iso); @@ -640,7 +641,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { (void) rhport; -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE iso_ep_t* iso = iso_ep_find(dev_addr, ep_addr); if (iso != NULL) { return iso_xfer(rhport, iso, buffer, buflen); @@ -693,7 +694,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { (void) rhport; -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE iso_ep_t* iso = iso_ep_find(dev_addr, ep_addr); if (iso != NULL) { return iso_abort(rhport, iso); @@ -730,7 +731,7 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { (void) rhport; -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE TU_VERIFY(iso_ep_find(daddr, ep_addr) == NULL); // ISO endpoints do not halt #endif ehci_qhd_t *qhd = qhd_get_from_addr(daddr, ep_addr); @@ -742,7 +743,7 @@ return true; } -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE //--------------------------------------------------------------------+ // Isochronous transfers: one service interval per HCD submission //--------------------------------------------------------------------+ @@ -1197,7 +1198,7 @@ (void) rhport; for (uint32_t i = 0; i < QHD_MAX; i++) { -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE if (ehci_data.qhd_is_iso[i]) { continue; } @@ -1336,7 +1337,7 @@ return; } -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE if (int_status & regs->inten & EHCI_INT_MASK_NXP_SOF) { regs->status = EHCI_INT_MASK_NXP_SOF; } @@ -1367,7 +1368,7 @@ // visited must remain pending for the next interrupt. regs->status = usb_int; } -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE // SOF and completion commonly arrive together. Scan ISO only once, keeping // interrupt work short enough for task context to replenish the next slot. if (usb_int || (int_status & regs->inten & EHCI_INT_MASK_NXP_SOF)) { @@ -1472,7 +1473,7 @@ // Find a free queue head TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t *qhd_find_free(void) { for (uint32_t i = 0; i < QHD_MAX; i++) { -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE if (ehci_data.qhd_is_iso[i]) { continue; } @@ -1499,7 +1500,7 @@ ehci_qhd_t *result = NULL; usbh_spin_lock(false); for (uint32_t i = 0; i < QHD_MAX; i++) { -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE if (ehci_data.qhd_is_iso[i]) { continue; } @@ -1624,7 +1625,7 @@ TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t *qtd_find_free(void) { for (uint32_t i = 0; i < QTD_MAX; i++) { -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE if (!ehci_data.qtd_is_iso[i / 2] && !ehci_data.qtd_pool[i / 2].qtd[i % 2].used) { return &ehci_data.qtd_pool[i / 2].qtd[i % 2]; }
diff --git a/src/tusb_option.h b/src/tusb_option.h index b2bc439..6ab52e6 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h
@@ -782,9 +782,14 @@ #define CFG_TUH_TASK_EVENTS_PER_RUN 16 #endif -// HCD queue capacity. Applications can select this after MCU/IP resolution. +// ChipIdea ISO is opt-in and selects two outstanding transfers by default. +#ifndef CFG_TUH_CHIPIDEA_ISO_ENABLE + #define CFG_TUH_CHIPIDEA_ISO_ENABLE 0 +#endif + +// HCD queue capacity, resolved after the MCU/IP and controller selection. #ifndef TUP_HCD_XFER_QUEUE_DEPTH - #if defined(TUP_USBIP_EHCI) && !CFG_TUH_MAX3421 + #if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE && !CFG_TUH_MAX3421 #define TUP_HCD_XFER_QUEUE_DEPTH 2 #else #define TUP_HCD_XFER_QUEUE_DEPTH 1 @@ -794,7 +799,11 @@ // Maximum outstanding packets per endpoint. HCDs with spare capacity notify // class drivers with XFER_RESULT_QUEUED; other HCDs keep one packet in flight. #ifndef CFG_TUH_XFER_QUEUE_DEPTH - #define CFG_TUH_XFER_QUEUE_DEPTH 1 + #if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE && !CFG_TUH_MAX3421 + #define CFG_TUH_XFER_QUEUE_DEPTH TUP_HCD_XFER_QUEUE_DEPTH + #else + #define CFG_TUH_XFER_QUEUE_DEPTH 1 + #endif #endif #if CFG_TUH_XFER_QUEUE_DEPTH < 1 || CFG_TUH_XFER_QUEUE_DEPTH > TUP_HCD_XFER_QUEUE_DEPTH
diff --git a/test/unit-test/host/CMakeLists.txt b/test/unit-test/host/CMakeLists.txt index 10b511b..b2544e0 100644 --- a/test/unit-test/host/CMakeLists.txt +++ b/test/unit-test/host/CMakeLists.txt
@@ -23,16 +23,28 @@ set_tests_properties(${name} PROPERTIES TIMEOUT 10 LABELS host) endfunction() -foreach (variant IN ITEMS disabled depth1 depth2 depth4) +foreach (variant IN ITEMS generic disabled max3421 depth1 depth2 depth4) set(name test_ehci_iso_${variant}) add_host_test(${name} ${CMAKE_CURRENT_LIST_DIR}/ehci/test_iso.c) - if (variant STREQUAL "disabled") - target_compile_definitions(${name} PRIVATE TEST_EHCI_GENERIC) - set(depth 1) - else () - string(REPLACE "depth" "" depth ${variant}) + if (NOT variant STREQUAL "disabled") + target_compile_definitions(${name} PRIVATE CFG_TUH_CHIPIDEA_ISO_ENABLE=1) endif () - target_compile_definitions(${name} PRIVATE CFG_TUH_XFER_QUEUE_DEPTH=${depth} TUP_HCD_XFER_QUEUE_DEPTH=4) + if (variant STREQUAL "generic") + target_compile_definitions(${name} PRIVATE TEST_EHCI_GENERIC) + elseif (variant STREQUAL "max3421") + target_compile_definitions(${name} PRIVATE CFG_TUH_MAX3421=1) + elseif (variant STREQUAL "depth1") + target_compile_definitions(${name} PRIVATE CFG_TUH_XFER_QUEUE_DEPTH=1) + elseif (variant STREQUAL "depth4") + target_compile_definitions(${name} PRIVATE CFG_TUH_XFER_QUEUE_DEPTH=4 TUP_HCD_XFER_QUEUE_DEPTH=4) + endif () + # Exercise default depth selection without overrides in the other variants. + if (variant MATCHES "^depth") + string(REPLACE "depth" "" depth ${variant}) + else () + set(depth 1) + endif () + target_compile_definitions(${name} PRIVATE TEST_EXPECTED_QUEUE_DEPTH=${depth}) target_compile_options(${name} PRIVATE -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast) # EHCI hardware links are 32-bit; keep the static simulated DMA memory below 4 GiB. if (WIN32)
diff --git a/test/unit-test/host/README.md b/test/unit-test/host/README.md index 93fe07b..8583b90 100644 --- a/test/unit-test/host/README.md +++ b/test/unit-test/host/README.md
@@ -17,15 +17,15 @@ FRINDEX. Covers native FS, split transactions, HS, descriptor reuse, completion ordering/errors, cancellation, reset debounce and frame wrap. Shared QH/qTD pool tests cover mixed interrupt/ISO allocation, exhaustion, - rollback and reuse. Builds generic EHCI without ISO and ChipIdea at queue - depths 1, 2 and 4. Static DMA fixtures - are linked below 4 GiB; hardware descriptor layouts are asserted separately + rollback and reuse. Builds ChipIdea with ISO disabled and enabled at queue + depths 1, 2 and 4, and checks that the ISO flag keeps depth 1 on generic EHCI + and MAX3421. Static DMA fixtures are linked below 4 GiB; hardware descriptor layouts are asserted separately from the native-pointer software tail. - **USBH/audio:** actual stack and audio drivers with a stub HCD. Covers buffer ownership, queue capacity, callback dispatch, failed submissions, abort/close, capture/playback refill, underrun silence, feedback and reset timing. Builds generic, EHCI and MAX3421 configurations at depths 1, 2 and 4. -The 13 CTest cases retain assertions in release builds and have bounded +The 15 CTest cases retain assertions in release builds and have bounded execution times. They do not emulate DMA/cache coherency or USB wire timing; those still require hardware tests.
diff --git a/test/unit-test/host/ehci/test_iso.c b/test/unit-test/host/ehci/test_iso.c index 4685286..2b0b991 100644 --- a/test/unit-test/host/ehci/test_iso.c +++ b/test/unit-test/host/ehci/test_iso.c
@@ -14,6 +14,7 @@ #define TU_VERIFY_STATIC(condition, ...) _Static_assert(condition, __VA_ARGS__) #include "portable/ehci/ehci.c" +_Static_assert(CFG_TUH_XFER_QUEUE_DEPTH == TEST_EXPECTED_QUEUE_DEPTH, "queue depth selection"); _Static_assert(sizeof(ehci_link_t) == 4, "link ABI"); _Static_assert(sizeof(ehci_qtd_t) == 32, "qTD ABI"); _Static_assert(offsetof(ehci_qhd_t, qtd_overlay) == 16, "QH hardware prefix"); @@ -76,7 +77,7 @@ assert(regs.portsc == before); // Attach must not reset the port before USBH debounces it. } -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE && !CFG_TUH_MAX3421 #define TEST_ISO_STREAM_EP_COUNT 4 static bool open_ep(uint8_t addr, uint8_t speed, uint16_t size, uint8_t interval) { @@ -378,7 +379,7 @@ assert(events == 1); } -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE && !CFG_TUH_MAX3421 static void test_limits_and_late_completion(void) { reset(TUSB_SPEED_HIGH); assert(!open_ep(0x80, TUSB_SPEED_HIGH, 64, 1)); @@ -810,7 +811,7 @@ assert((uintptr_t)&ehci_data <= UINT32_MAX && (uintptr_t)buffer <= UINT32_MAX); test_attach_debounce(); test_qtd_retirement(); -#ifdef TUP_USBIP_CHIPIDEA_HS +#if defined(TUP_USBIP_CHIPIDEA_HS) && CFG_TUH_CHIPIDEA_ISO_ENABLE && !CFG_TUH_MAX3421 test_shared_pools(); test_native_fs(); test_split(); @@ -831,6 +832,15 @@ #if CFG_TUH_XFER_QUEUE_DEPTH > 1 test_queue(); #endif +#else + reset(TUSB_SPEED_HIGH); + tusb_desc_endpoint_t const iso_desc = { + .bLength = sizeof(iso_desc), .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 0x81, .bmAttributes = {.xfer = TUSB_XFER_ISOCHRONOUS}, + .wMaxPacketSize = 64, .bInterval = 1 + }; + assert(!hcd_edpt_open(0, 1, &iso_desc)); + assert(regs.command_bm.int_threshold == 8); #endif puts("EHCI ISO regression tests passed"); return 0;