From 947a07a3c38ddbd1f374d69d413ce4dd2a97f56d Mon Sep 17 00:00:00 2001 From: "peter.marcisovsky" Date: Thu, 17 Jul 2025 17:17:38 +0200 Subject: [PATCH 1/2] feat(uac_host): Add global suspend/resume --- host/class/uac/usb_host_uac/CHANGELOG.md | 4 + .../uac/usb_host_uac/include/usb/uac_host.h | 10 + .../test_app/main/test_host_uac.c | 418 +++++++++++++++++- host/class/uac/usb_host_uac/uac_host.c | 159 +++++-- 4 files changed, 537 insertions(+), 54 deletions(-) diff --git a/host/class/uac/usb_host_uac/CHANGELOG.md b/host/class/uac/usb_host_uac/CHANGELOG.md index a2ce6eba..32b2d894 100644 --- a/host/class/uac/usb_host_uac/CHANGELOG.md +++ b/host/class/uac/usb_host_uac/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for USB Host UAC +## [unreleased] + +1. Added global suspend/resume support + ## 1.3.0 1. Added Linux target build for the UAC component, host tests (https://github.com/espressif/esp-usb/issues/143) diff --git a/host/class/uac/usb_host_uac/include/usb/uac_host.h b/host/class/uac/usb_host_uac/include/usb/uac_host.h index 94c57b1a..f01a6c16 100644 --- a/host/class/uac/usb_host_uac/include/usb/uac_host.h +++ b/host/class/uac/usb_host_uac/include/usb/uac_host.h @@ -10,6 +10,7 @@ #include #include "freertos/FreeRTOS.h" #include "esp_err.h" +#include "usb/usb_host.h" #include "uac.h" #ifdef __cplusplus @@ -36,6 +37,11 @@ extern "C" { */ #define FLAG_STREAM_SUSPEND_AFTER_START (1 << 0) +// For backward compatibility with older idf versions without suspend/resume API +#ifdef USB_HOST_LIB_EVENT_FLAGS_AUTO_SUSPEND +#define UAC_HOST_SUSPEND_RESUME_API_SUPPORTED +#endif + typedef struct uac_interface *uac_host_device_handle_t; /*!< Logic Device Handle. Handle to a particular UAC interface */ // ------------------------ USB UAC Host events -------------------------------- @@ -55,6 +61,10 @@ typedef enum { UAC_HOST_DEVICE_EVENT_TX_DONE, /*!< TX Done: the transmit buffer data size falls below the threshold */ UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR, /*!< UAC Device transfer error */ UAC_HOST_DRIVER_EVENT_DISCONNECTED, /*!< UAC Device has been disconnected */ +#ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + UAC_HOST_DEVICE_EVENT_SUSPENDED, /*!< UAC Device has been suspended */ + UAC_HOST_DEVICE_EVENT_RESUMED, /*!< UAC Device has been resumed */ +#endif // UAC_HOST_SUSPEND_RESUME_API_SUPPORTED } uac_host_device_event_t; // ------------------------ USB UAC Host events callbacks ----------------------------- diff --git a/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c b/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c index a164839d..2fc1ba38 100644 --- a/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c +++ b/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c @@ -23,14 +23,15 @@ const static char *TAG = "UAC_TEST"; // ----------------------- Public ------------------------- static EventGroupHandle_t s_evt_handle; -static QueueHandle_t s_event_queue = NULL; +static QueueHandle_t s_transfer_event_queue = NULL; +static QueueHandle_t s_client_event_queue = NULL; static EventGroupHandle_t s_evt_handle = NULL; #define BIT0_USB_HOST_DRIVER_REMOVED (0x01 << 0) // Known microphone device parameters -#define UAC_DEV_PID 0x3307 -#define UAC_DEV_VID 0x349C +#define UAC_DEV_PID 0x25 +#define UAC_DEV_VID 0x1395 #define UAC_DEV_MIC_IFACE_NUM 3 #define UAC_DEV_MIC_IFACE_ALT_NUM 1 @@ -147,11 +148,6 @@ static void delete_phy(void) {} static void uac_device_callback(uac_host_device_handle_t uac_device_handle, const uac_host_device_event_t event, void *arg) { - if (event == UAC_HOST_DRIVER_EVENT_DISCONNECTED) { - ESP_LOGI(TAG, "UAC Device disconnected"); - TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); - return; - } // Send uac device event to the event queue event_queue_t evt_queue = { .event_group = UAC_DEVICE_EVENT, @@ -159,8 +155,44 @@ static void uac_device_callback(uac_host_device_handle_t uac_device_handle, cons .device_evt.event = event, .device_evt.arg = arg }; - // should not block here - xQueueSend(s_event_queue, &evt_queue, 0); + + bool transfer_evt_queue = false; + bool client_evt_queue = false; + + switch (event) { + case UAC_HOST_DRIVER_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "UAC Device disconnected"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); + client_evt_queue = true; + break; +#ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + case UAC_HOST_DEVICE_EVENT_SUSPENDED: + ESP_LOGI(TAG, "Device suspended"); + client_evt_queue = true; + break; + case UAC_HOST_DEVICE_EVENT_RESUMED: + ESP_LOGI(TAG, "Device resumed"); + client_evt_queue = true; + break; +#endif // UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + case UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR: + case UAC_HOST_DEVICE_EVENT_RX_DONE: + case UAC_HOST_DEVICE_EVENT_TX_DONE: + transfer_evt_queue = true; + break; + default: + TEST_FAIL_MESSAGE("Unrecognized device event"); + break; + } + + // Sanity check, logical XOR + assert(transfer_evt_queue != client_evt_queue); + + if (transfer_evt_queue) { + xQueueSend(s_transfer_event_queue, &evt_queue, 0); + } else { + xQueueSend(s_client_event_queue, &evt_queue, 0); + } } static void uac_host_lib_callback(uint8_t addr, uint8_t iface_num, const uac_host_driver_event_t event, void *arg) @@ -173,8 +205,50 @@ static void uac_host_lib_callback(uint8_t addr, uint8_t iface_num, const uac_hos .driver_evt.event = event, .driver_evt.arg = arg }; - xQueueSend(s_event_queue, &evt_queue, 0); + + switch (event) { + case UAC_HOST_DRIVER_EVENT_RX_CONNECTED: + ESP_LOGI(TAG, "RX Device connected"); + break; + case UAC_HOST_DRIVER_EVENT_TX_CONNECTED: + ESP_LOGI(TAG, "TX Device connected"); + break; + default: + TEST_FAIL_MESSAGE("Unrecognized driver event"); + } + + xQueueSend(s_client_event_queue, &evt_queue, 0); +} + + +#ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED +/** + * @brief Expect USB Host client event + * + * @param[in] expected_client_event Expected client event (NULL to not expect any event) + * @param[in] ticks Ticks to wait + */ +static void expect_client_event(event_queue_t *expected_client_event, TickType_t ticks) +{ + event_queue_t client_event; + + // Check, that no event is delivered + if (expected_client_event == NULL) { + if (pdFALSE == xQueueReceive(s_client_event_queue, &client_event, ticks)) { + return; + } else { + TEST_FAIL_MESSAGE("Expecting NO event, but an event delivered"); + } + } + + // Check that an event is delivered + if (pdTRUE == xQueueReceive(s_client_event_queue, &client_event, ticks)) { + TEST_ASSERT_EQUAL_MESSAGE(expected_client_event->device_evt.event, client_event.device_evt.event, "Unexpected app event"); + } else { + TEST_ASSERT_MESSAGE(false, "App event not generated on time"); + } } +#endif // UAC_HOST_SUSPEND_RESUME_API_SUPPORTED /** * @brief Start USB Host install and handle common USB host library events while app pin not low @@ -229,8 +303,11 @@ static void usb_lib_task(void *arg) void test_uac_setup(void) { // create a queue to handle events - s_event_queue = xQueueCreate(16, sizeof(event_queue_t)); - TEST_ASSERT_NOT_NULL(s_event_queue); + s_client_event_queue = xQueueCreate(8, sizeof(event_queue_t)); + TEST_ASSERT_NOT_NULL(s_client_event_queue); + s_transfer_event_queue = xQueueCreate(16, sizeof(event_queue_t)); + TEST_ASSERT_NOT_NULL(s_transfer_event_queue); + //TEST_ASSERT_NOT_NULL(s_event_queue); s_evt_handle = xEventGroupCreate(); TEST_ASSERT_NOT_NULL(s_evt_handle); static TaskHandle_t uac_task_handle = NULL; @@ -258,7 +335,8 @@ void test_uac_setup(void) void test_uac_queue_reset(void) { - xQueueReset(s_event_queue); + xQueueReset(s_client_event_queue); + xQueueReset(s_transfer_event_queue); } void test_uac_teardown(bool force) @@ -273,7 +351,8 @@ void test_uac_teardown(bool force) // Wait for USB lib task to finish xEventGroupWaitBits(s_evt_handle, BIT0_USB_HOST_DRIVER_REMOVED, pdTRUE, pdTRUE, portMAX_DELAY); // delete event queue and event group - vQueueDelete(s_event_queue); + vQueueDelete(s_client_event_queue); + vQueueDelete(s_transfer_event_queue); vEventGroupDelete(s_evt_handle); // delay to allow task to delete vTaskDelay(100); @@ -316,7 +395,7 @@ void test_handle_dev_connection(uint8_t *iface_num, uint8_t *if_rx) { event_queue_t evt_queue = {0}; // ignore the first connected event - TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)); + TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(s_client_event_queue, &evt_queue, portMAX_DELAY)); TEST_ASSERT_EQUAL(UAC_DRIVER_EVENT, evt_queue.event_group); TEST_ASSERT_EQUAL(1, evt_queue.driver_evt.addr); if (iface_num) { @@ -538,7 +617,7 @@ TEST_CASE("test uac rx reading", "[uac_host][rx]") event_queue_t evt_queue = {0}; ESP_LOGI(TAG, "Start reading data from MIC"); while (1) { - if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + if (xQueueReceive(s_transfer_event_queue, &evt_queue, portMAX_DELAY)) { TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); uac_host_device_handle_t uac_device_handle = evt_queue.device_evt.handle; uac_host_device_event_t event = evt_queue.device_evt.event; @@ -676,7 +755,7 @@ TEST_CASE("test uac tx writing", "[uac_host][tx]") const uint8_t test_counter_max = 15; event_queue_t evt_queue = {0}; while (1) { - if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + if (xQueueReceive(s_transfer_event_queue, &evt_queue, portMAX_DELAY)) { TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); uac_host_device_handle_t uac_device_handle = evt_queue.device_evt.handle; uac_host_device_event_t event = evt_queue.device_evt.event; @@ -834,7 +913,7 @@ TEST_CASE("test uac tx rx loopback", "[uac_host][tx][rx]") TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(mic_device_handle)); TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(spk_device_handle)); while (1) { - if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + if (xQueueReceive(s_transfer_event_queue, &evt_queue, portMAX_DELAY)) { TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); uac_host_device_event_t event = evt_queue.device_evt.event; esp_err_t ret = ESP_FAIL; @@ -904,10 +983,7 @@ TEST_CASE("test uac tx rx loopback", "[uac_host][tx][rx]") /** * @brief: Test disconnect the device when the stream is running - * @note: Currently, the P4 PHY can't be controlled to emulate the hot-plug event, - * so the test is disabled */ -#if !CONFIG_IDF_TARGET_ESP32P4 TEST_CASE("test uac tx rx loopback with disconnect", "[uac_host][tx][rx][hot-plug]") { // handle device connection @@ -985,7 +1061,7 @@ TEST_CASE("test uac tx rx loopback with disconnect", "[uac_host][tx][rx][hot-plu TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(mic_device_handle)); TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(spk_device_handle)); while (1) { - if (xQueueReceive(s_event_queue, &evt_queue, portMAX_DELAY)) { + if (xQueueReceive(s_transfer_event_queue, &evt_queue, portMAX_DELAY)) { TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); uac_host_device_event_t event = evt_queue.device_evt.event; esp_err_t ret = ESP_FAIL; @@ -1051,4 +1127,298 @@ TEST_CASE("test uac tx rx loopback with disconnect", "[uac_host][tx][rx][hot-plu free(rx_buffer_stereo); } } -#endif + +#ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED +/** + * @brief UAC reading with suspend/resume, TODO brief and procedure + */ +TEST_CASE("test uac rx reading, suspend/resume", "[uac_host][rx]") +{ + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + const uint32_t buffer_threshold = 4800; + const uint32_t buffer_size = 19200; + + // Issue suspend/resume, expect NO event, since the device is not opened by host + printf("Issue suspend\n"); + usb_host_lib_root_port_suspend(); + expect_client_event(NULL, 500); + + printf("Issue resume\n"); + usb_host_lib_root_port_resume(); + expect_client_event(NULL, 500); + + // Opend the device by client + uac_host_device_handle_t uac_device_handle = NULL; + test_open_mic_device(mic_iface_num, buffer_size, buffer_threshold, &uac_device_handle); + + // Issue suspend/resume, expect suspend/resume events, as the device is already opened + printf("Issue suspend\n"); + usb_host_lib_root_port_suspend(); + event_queue_t expect_event = {.device_evt.event = UAC_HOST_DEVICE_EVENT_SUSPENDED}; + expect_client_event(&expect_event, 500); + + printf("Issue resume\n"); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_RESUMED; + usb_host_lib_root_port_resume(); + expect_client_event(&expect_event, 500); + + // start the device with first alt interface params + uac_host_dev_alt_param_t iface_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(uac_device_handle, 1, &iface_alt_params)); + const uac_host_stream_config_t stream_config = { + .channels = iface_alt_params.channels, + .bit_resolution = iface_alt_params.bit_resolution, + .sample_freq = iface_alt_params.sample_freq[0], + }; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(uac_device_handle, &stream_config)); + // Most device support mute and volume control. if not, comment out the following two lines + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(uac_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(uac_device_handle, 80)); + + uint8_t *rx_buffer = (uint8_t *)calloc(1, buffer_threshold); + TEST_ASSERT_NOT_NULL(rx_buffer); + uint32_t rx_size = 0; + // got 5s data, then stop the stream + const uint32_t timeout = 5000; + uint32_t time_counter = 0; + event_queue_t evt_queue = {0}; + + // Check that data events are being sent + TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(s_transfer_event_queue, &evt_queue, pdMS_TO_TICKS(100))); + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + + // Issue suspend/resume after the device has been started + printf("Issue suspend\n"); + usb_host_lib_root_port_suspend(); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_SUSPENDED; + expect_client_event(&expect_event, 500); + + printf("Issue resume\n"); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_RESUMED; + usb_host_lib_root_port_resume(); + expect_client_event(&expect_event, 500); + + ESP_LOGI(TAG, "Start reading data from MIC"); + while (1) { + if (xQueueReceive(s_transfer_event_queue, &evt_queue, portMAX_DELAY)) { + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + uac_host_device_handle_t uac_device_handle = evt_queue.device_evt.handle; + uac_host_device_event_t event = evt_queue.device_evt.event; + switch (event) { + case UAC_HOST_DEVICE_EVENT_RX_DONE: + uac_host_device_read(uac_device_handle, rx_buffer, buffer_threshold, &rx_size, 0); + TEST_ASSERT_EQUAL(buffer_threshold, rx_size); + time_counter += rx_size / (iface_alt_params.channels * iface_alt_params.bit_resolution / 8 * iface_alt_params.sample_freq[0] / 1000); + if (time_counter >= timeout) { + goto exit_rx; + } + break; + default: + TEST_ASSERT(0); + break; + } + } + } +exit_rx: + ESP_LOGI(TAG, "Stop reading data from MIC"); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(uac_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(uac_device_handle)); + + // Issue suspend/resume after the device has been closed, no client events are expected + printf("Issue suspend\n"); + usb_host_lib_root_port_suspend(); + expect_client_event(NULL, 500); + + printf("Issue resume\n"); + usb_host_lib_root_port_resume(); + expect_client_event(NULL, 500); + + free(rx_buffer); +} + + +/** + * @brief UAC loopback with suspend/resume, TODO readme and procedure + */ +TEST_CASE("test uac tx rx loopback suspend/resume", "[uac_host][tx][rx]") +{ + // handle device connection + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + const uint32_t rx_buffer_size = 19200; + const uint32_t rx_buffer_threshold = 4800; + + uac_host_device_handle_t mic_device_handle = NULL; + test_open_mic_device(mic_iface_num, rx_buffer_size, rx_buffer_threshold, &mic_device_handle); + uac_host_device_handle_t spk_device_handle = NULL; + // set same params to spk device + test_open_spk_device(spk_iface_num, rx_buffer_size, rx_buffer_threshold, &spk_device_handle); + + // Issue suspend/resume, expect 2 set of suspend/resume events, one for each interface (mic+spk) + printf("Issue suspend\n"); + usb_host_lib_root_port_suspend(); + event_queue_t expect_event = {.device_evt.event = UAC_HOST_DEVICE_EVENT_SUSPENDED}; + expect_client_event(&expect_event, 500); + expect_client_event(&expect_event, 500); + + printf("Issue resume\n"); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_RESUMED; + usb_host_lib_root_port_resume(); + expect_client_event(&expect_event, 500); + expect_client_event(&expect_event, 500); + + // get mic alt interface 1 params, set same params to spk device + uac_host_dev_alt_param_t mic_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(mic_device_handle, 1, &mic_alt_params)); + + uac_host_stream_config_t stream_config = { + .channels = mic_alt_params.channels, + .bit_resolution = mic_alt_params.bit_resolution, + .sample_freq = mic_alt_params.sample_freq[0], + .flags = FLAG_STREAM_SUSPEND_AFTER_START, + }; + + uint8_t actual_volume = 0; + bool actual_mute = 0; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(mic_device_handle, &stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(mic_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_get_mute(mic_device_handle, &actual_mute)); + TEST_ASSERT_EQUAL(0, actual_mute); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(mic_device_handle, 80)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_get_volume(mic_device_handle, &actual_volume)); + TEST_ASSERT_EQUAL(80, actual_volume); + + uac_host_dev_alt_param_t spk_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(spk_device_handle, 1, &spk_alt_params)); + + // some usb headset may have one channel for mic and two channels for speaker + bool channel_mismatch = false; + if (spk_alt_params.channels != mic_alt_params.channels) { + if (mic_alt_params.channels == 1 && spk_alt_params.channels == 2) { + ESP_LOGW(TAG, "Speaker channels %u and microphone channels %u are not the same", spk_alt_params.channels, mic_alt_params.channels); + stream_config.channels = 2; + channel_mismatch = true; + } else { + ESP_LOGE(TAG, "Speaker channels %u and microphone channels %u are not supported", spk_alt_params.channels, mic_alt_params.channels); + TEST_ASSERT(0); + } + } + + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(spk_device_handle, &stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(spk_device_handle, 0)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_get_mute(spk_device_handle, &actual_mute)); + TEST_ASSERT_EQUAL(0, actual_mute); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_volume(spk_device_handle, 80)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_get_volume(spk_device_handle, &actual_volume)); + TEST_ASSERT_EQUAL(80, actual_volume); + + uint8_t *rx_buffer = (uint8_t *)calloc(1, rx_buffer_threshold); + uint8_t *rx_buffer_stereo = NULL; + if (channel_mismatch) { + rx_buffer_stereo = (uint8_t *)calloc(1, rx_buffer_threshold * 2); + TEST_ASSERT_NOT_NULL(rx_buffer_stereo); + } + TEST_ASSERT_NOT_NULL(rx_buffer); + uint32_t rx_size = 0; + // got 5s data, then stop the stream + const uint32_t timeout = 5000; + const uint32_t test_times = 3; + uint32_t time_counter = 0; + uint32_t test_counter = 0; + event_queue_t evt_queue = {0}; + while (1) { + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(mic_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_resume(spk_device_handle)); + while (1) { + if (xQueueReceive(s_transfer_event_queue, &evt_queue, portMAX_DELAY)) { + TEST_ASSERT_EQUAL(UAC_DEVICE_EVENT, evt_queue.event_group); + uac_host_device_event_t event = evt_queue.device_evt.event; + esp_err_t ret = ESP_FAIL; + switch (event) { + case UAC_HOST_DEVICE_EVENT_RX_DONE: + // read as much as possible + do { + ret = uac_host_device_read(mic_device_handle, rx_buffer, rx_buffer_threshold, &rx_size, 0); + if (ret == ESP_OK) { + if (channel_mismatch) { + // convert mono to stereo + if (mic_alt_params.bit_resolution == 16) { + for (size_t i = 0; i < rx_size; i += 2) { + rx_buffer_stereo[i * 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 1] = rx_buffer[i + 1]; + rx_buffer_stereo[i * 2 + 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 3] = rx_buffer[i + 1]; + } + ret = uac_host_device_write(spk_device_handle, rx_buffer_stereo, rx_size * 2, 0); + } else { + for (size_t i = 0; i < rx_size; i++) { + rx_buffer_stereo[i * 2] = rx_buffer[i]; + rx_buffer_stereo[i * 2 + 1] = rx_buffer[i]; + } + ret = uac_host_device_write(spk_device_handle, rx_buffer_stereo, rx_size * 2, 0); + } + } else { + ret = uac_host_device_write(spk_device_handle, rx_buffer, rx_size, 0); + } + time_counter += rx_size / (mic_alt_params.channels * mic_alt_params.bit_resolution / 8 * mic_alt_params.sample_freq[0] / 1000); + } + } while (ret == ESP_OK); + + if (time_counter >= timeout) { + goto restart_rx; + } + break; + case UAC_HOST_DEVICE_EVENT_TX_DONE: + // we do nothing here, just wait for the rx done event + break; + default: + TEST_ASSERT(0); + break; + } + } + } +restart_rx: + if (++test_counter >= test_times) { + goto exit_rx; + } + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_suspend(mic_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_suspend(spk_device_handle)); + time_counter = 0; + vTaskDelay(100); + } + +exit_rx: + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(spk_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(spk_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_set_mute(mic_device_handle, 1)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(mic_device_handle)); + free(rx_buffer); + if (rx_buffer_stereo) { + free(rx_buffer_stereo); + } +} + +#endif // UAC_HOST_SUSPEND_RESUME_API_SUPPORTED diff --git a/host/class/uac/usb_host_uac/uac_host.c b/host/class/uac/usb_host_uac/uac_host.c index a076115f..cb064d95 100644 --- a/host/class/uac/usb_host_uac/uac_host.c +++ b/host/class/uac/usb_host_uac/uac_host.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -202,6 +202,8 @@ static esp_err_t _uac_host_device_add(uint8_t addr, usb_device_handle_t dev_hdl, static esp_err_t _uac_host_device_delete(uac_device_t *uac_device); static esp_err_t uac_cs_request_set(uac_device_t *uac_device, const uac_cs_request_t *req); static esp_err_t uac_cs_request_set_ep_frequency(uac_iface_t *iface, uint8_t ep_addr, uint32_t freq); +static esp_err_t uac_host_interface_suspend(uac_iface_t *iface, bool root_port_suspended); +static esp_err_t uac_host_interface_resume(uac_iface_t *iface, bool root_port_suspended); // --------------------------- Utility Functions -------------------------------- /** @@ -1008,6 +1010,79 @@ static esp_err_t _uac_host_device_disconnected(usb_device_handle_t dev_hdl) return ESP_OK; } +#ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED +/** + * @brief Handler for global (root port) suspend event + * + * @param[in] dev_hdl USB device handle + * @return esp_err_t + */ +static esp_err_t _uac_host_device_suspended(usb_device_handle_t dev_hdl) +{ + uac_device_t *uac_device = get_uac_device_by_handle(dev_hdl); + assert(uac_device); + + UAC_ENTER_CRITICAL(); + uac_iface_t *uac_iface = STAILQ_FIRST(&s_uac_driver->uac_ifaces_tailq); + uac_iface_t *uac_tmp = NULL; + + while (uac_iface != NULL) { + uac_tmp = STAILQ_NEXT(uac_iface, tailq_entry); + UAC_EXIT_CRITICAL(); + + if (uac_iface->parent && (uac_iface->parent->addr == uac_device->addr)) { + ESP_LOGD(TAG, "Suspending interface %p in %d state", uac_iface, uac_iface->state); + + if (uac_iface->state == UAC_INTERFACE_STATE_ACTIVE) { + uac_host_interface_suspend(uac_iface, true); + } + uac_host_user_interface_callback(uac_iface, UAC_HOST_DEVICE_EVENT_SUSPENDED); + } + uac_iface = uac_tmp; + UAC_ENTER_CRITICAL(); + } + UAC_EXIT_CRITICAL(); + return ESP_OK; +} + +/** + * @brief Handler for global (root port) resume event + * + * @param[in] dev_hdl USB device handle + * @return esp_err_t + */ + +static esp_err_t _uac_host_device_resumed(usb_device_handle_t dev_hdl) +{ + uac_device_t *uac_device = get_uac_device_by_handle(dev_hdl); + assert(uac_device); + + UAC_ENTER_CRITICAL(); + uac_iface_t *uac_iface = STAILQ_FIRST(&s_uac_driver->uac_ifaces_tailq); + uac_iface_t *uac_tmp = NULL; + + while (uac_iface != NULL) { + uac_tmp = STAILQ_NEXT(uac_iface, tailq_entry); + UAC_EXIT_CRITICAL(); + + if (uac_iface->parent && (uac_iface->parent->addr == uac_device->addr)) { + ESP_LOGD(TAG, "Resuming interface %p in %d state", uac_iface, uac_iface->state); + + // The interface is in ready state, we must submit RX transfer(s) poll + if (uac_iface->state == UAC_INTERFACE_STATE_READY) { + uac_host_interface_resume(uac_iface, true); + } + uac_host_user_interface_callback(uac_iface, UAC_HOST_DEVICE_EVENT_RESUMED); + } + uac_iface = uac_tmp; + UAC_ENTER_CRITICAL(); + } + UAC_EXIT_CRITICAL(); + return ESP_OK; +} + +#endif // UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + /** * @brief USB Host Client's event callback * @@ -1016,10 +1091,28 @@ static esp_err_t _uac_host_device_disconnected(usb_device_handle_t dev_hdl) */ static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg) { - if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) { + switch (event->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + ESP_LOGD(TAG, "New device connected"); _uac_host_device_connected(event->new_dev.address); - } else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) { + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + ESP_LOGD(TAG, "Device suddenly disconnected"); _uac_host_device_disconnected(event->dev_gone.dev_hdl); + break; +#ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + case USB_HOST_CLIENT_EVENT_DEV_SUSPENDED: + ESP_LOGD(TAG, "Device suspended"); + _uac_host_device_suspended(event->dev_suspend_resume.dev_hdl); + break; + case USB_HOST_CLIENT_EVENT_DEV_RESUMED: + ESP_LOGD(TAG, "Device resumed"); + _uac_host_device_resumed(event->dev_suspend_resume.dev_hdl); + break; +#endif // UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + default: + ESP_LOGW(TAG, "Unrecognized USB Host client event"); + break; } } @@ -1253,7 +1346,7 @@ static void stream_tx_xfer_done(usb_transfer_t *out_xfer) * @param[in] iface Pointer to Interface structure * @return esp_err_t */ -static esp_err_t uac_host_interface_suspend(uac_iface_t *iface) +static esp_err_t uac_host_interface_suspend(uac_iface_t *iface, bool root_port_suspended) { UAC_RETURN_ON_INVALID_ARG(iface); UAC_RETURN_ON_INVALID_ARG(iface->parent); @@ -1263,19 +1356,23 @@ static esp_err_t uac_host_interface_suspend(uac_iface_t *iface) iface->state = UAC_INTERFACE_STATE_SUSPENDING; // Set Interface alternate setting to 0 - usb_setup_packet_t request; - USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, 0); - esp_err_t ret = uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request); - if (ret != ESP_OK) { - ESP_LOGW(TAG, "Set Interface %d-%d Failed", iface->dev_info.iface_num, 0); - } else { - ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, 0); + if (!root_port_suspended) { + usb_setup_packet_t request; + USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, 0); + esp_err_t ret = uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Set Interface %d-%d Failed", iface->dev_info.iface_num, 0); + } else { + ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, 0); + } } - uint8_t ep_addr = iface->iface_alt[iface->cur_alt].ep_addr; - UAC_RETURN_ON_ERROR(usb_host_endpoint_halt(iface->parent->dev_hdl, ep_addr), "Unable to HALT EP"); - UAC_RETURN_ON_ERROR(usb_host_endpoint_flush(iface->parent->dev_hdl, ep_addr), "Unable to FLUSH EP"); - usb_host_endpoint_clear(iface->parent->dev_hdl, ep_addr); + if (!root_port_suspended) { + uint8_t ep_addr = iface->iface_alt[iface->cur_alt].ep_addr; + UAC_RETURN_ON_ERROR(usb_host_endpoint_halt(iface->parent->dev_hdl, ep_addr), "Unable to HALT EP"); + UAC_RETURN_ON_ERROR(usb_host_endpoint_flush(iface->parent->dev_hdl, ep_addr), "Unable to FLUSH EP"); + usb_host_endpoint_clear(iface->parent->dev_hdl, ep_addr); + } _ring_buffer_flush(iface->ringbuf); // add all the transfer to free list @@ -1299,7 +1396,7 @@ static esp_err_t uac_host_interface_suspend(uac_iface_t *iface) * @param[in] iface Pointer to Interface structure * @return esp_err_t */ -static esp_err_t uac_host_interface_resume(uac_iface_t *iface) +static esp_err_t uac_host_interface_resume(uac_iface_t *iface, bool root_port_suspended) { UAC_RETURN_ON_INVALID_ARG(iface); UAC_RETURN_ON_INVALID_ARG(iface->parent); @@ -1308,15 +1405,17 @@ static esp_err_t uac_host_interface_resume(uac_iface_t *iface) UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); // Set Interface alternate setting - usb_setup_packet_t request; - USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, iface->cur_alt + 1); - UAC_RETURN_ON_ERROR(uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request), "Unable to set Interface alternate"); - ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, iface->cur_alt + 1); - // Set endpoint frequency control - if (iface->iface_alt[iface->cur_alt].freq_ctrl_supported) { - ESP_LOGI(TAG, "Set EP %02X frequency %"PRIu32, iface->iface_alt[iface->cur_alt].ep_addr, iface->iface_alt[iface->cur_alt].cur_sampling_freq); - UAC_RETURN_ON_ERROR(uac_cs_request_set_ep_frequency(iface, iface->iface_alt[iface->cur_alt].ep_addr, - iface->iface_alt[iface->cur_alt].cur_sampling_freq), "Unable to set endpoint frequency"); + if (!root_port_suspended) { + usb_setup_packet_t request; + USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, iface->cur_alt + 1); + UAC_RETURN_ON_ERROR(uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request), "Unable to set Interface alternate"); + ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, iface->cur_alt + 1); + // Set endpoint frequency control + if (iface->iface_alt[iface->cur_alt].freq_ctrl_supported) { + ESP_LOGI(TAG, "Set EP %02X frequency %"PRIu32, iface->iface_alt[iface->cur_alt].ep_addr, iface->iface_alt[iface->cur_alt].cur_sampling_freq); + UAC_RETURN_ON_ERROR(uac_cs_request_set_ep_frequency(iface, iface->iface_alt[iface->cur_alt].ep_addr, + iface->iface_alt[iface->cur_alt].cur_sampling_freq), "Unable to set endpoint frequency"); + } } // for RX, we just submit all the transfers if (iface->dev_info.type == UAC_STREAM_RX) { @@ -2109,7 +2208,7 @@ esp_err_t uac_host_device_close(uac_host_device_handle_t uac_dev_handle) UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(uac_iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "UAC Interface is busy by other task"); if (UAC_INTERFACE_STATE_ACTIVE == uac_iface->state) { - UAC_GOTO_ON_ERROR(uac_host_interface_suspend(uac_iface), "Unable to disable UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(uac_iface, false), "Unable to disable UAC Interface"); } if (UAC_INTERFACE_STATE_READY == uac_iface->state) { @@ -2281,7 +2380,7 @@ esp_err_t uac_host_device_start(uac_host_device_handle_t uac_dev_handle, const u iface_claimed = true; if (!(iface->flags & FLAG_STREAM_SUSPEND_AFTER_START)) { - UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface), "Unable to enable UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface, false), "Unable to resume UAC Interface"); } uac_host_interface_unlock(iface); return ESP_OK; @@ -2306,7 +2405,7 @@ esp_err_t uac_host_device_suspend(uac_host_device_handle_t uac_dev_handle) } esp_err_t ret = ESP_OK; UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state), ESP_ERR_INVALID_STATE, "device not active"); - UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface), "Unable to disable UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface, false), "Unable to suspend UAC Interface"); uac_host_interface_unlock(iface); return ESP_OK; @@ -2329,7 +2428,7 @@ esp_err_t uac_host_device_resume(uac_host_device_handle_t uac_dev_handle) esp_err_t ret = ESP_OK; UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "device not ready"); - UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface), "Unable to enable UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface, false), "Unable to resume UAC Interface"); uac_host_interface_unlock(iface); return ESP_OK; @@ -2347,7 +2446,7 @@ esp_err_t uac_host_device_stop(uac_host_device_handle_t uac_dev_handle) esp_err_t ret = ESP_OK; UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); if (UAC_INTERFACE_STATE_ACTIVE == iface->state) { - UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface), "Unable to disable UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface, false), "Unable to suspend UAC Interface"); } if (UAC_INTERFACE_STATE_READY == iface->state) { From 1c8eb776b323aa919aa562a55bccde0ee05b7267 Mon Sep 17 00:00:00 2001 From: "peter.marcisovsky" Date: Wed, 6 Aug 2025 12:08:08 +0200 Subject: [PATCH 2/2] feat(uac_host): Add suspend/resume tests --- .../test_app/main/test_host_uac.c | 112 +++++++++++- host/class/uac/usb_host_uac/uac_host.c | 163 ++++++++++++++---- 2 files changed, 237 insertions(+), 38 deletions(-) diff --git a/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c b/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c index 2fc1ba38..bb24bac4 100644 --- a/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c +++ b/host/class/uac/usb_host_uac/test_app/main/test_host_uac.c @@ -1129,8 +1129,118 @@ TEST_CASE("test uac tx rx loopback with disconnect", "[uac_host][tx][rx][hot-plu } #ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + +/** + * @brief: Test device open, close, start and close actions when the root port is suspended + * + * Procedure: + * - Connect both (mic, spk) devices and suspend the root port + * - Open both devices consecutively and expect suspended/resumed events + * - When opening a device a CTRL transfer is sent, which automatically resumes the root port + * - Start both devices when root port suspended/resumed + * - Close both devices when root port suspended + */ +TEST_CASE("test suspended uac device open/close", "[uac_host]") +{ + uint8_t mic_iface_num = 0; + uint8_t spk_iface_num = 0; + uint8_t if_rx = false; + test_handle_dev_connection(&mic_iface_num, &if_rx); + if (!if_rx) { + spk_iface_num = mic_iface_num; + test_handle_dev_connection(&mic_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, true); + } else { + test_handle_dev_connection(&spk_iface_num, &if_rx); + TEST_ASSERT_EQUAL(if_rx, false); + } + + // Issue suspend/resume, expect NO event, since the device is not opened by host + printf("Issue suspend\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_lib_root_port_suspend()); + expect_client_event(NULL, 100); + + // Open device with the root port suspended + const uint32_t buffer_threshold = 4800; + const uint32_t buffer_size = 19200; + uac_host_device_handle_t mic_device_handle = NULL, spk_device_handle = NULL; + test_open_mic_device(mic_iface_num, buffer_size, buffer_threshold, &mic_device_handle); + + // Expect one Resumed event -> opend device call submits a ctrl transfer, which automatically resumes the root port + event_queue_t expect_event = {.device_evt.event = UAC_HOST_DEVICE_EVENT_RESUMED}; + expect_client_event(&expect_event, 100); // Mic device is opened by client -> expect event + expect_client_event(NULL, 200); // Spk device is not opened by client yet -> expect NO event + + printf("Issue suspend\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_lib_root_port_suspend()); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_SUSPENDED; + expect_client_event(&expect_event, 100); // Mic device is opened by client -> expect event + expect_client_event(NULL, 200); // Spk device is not opened by client yet -> expect NO event + + test_open_spk_device(spk_iface_num, buffer_size, buffer_threshold, &spk_device_handle); + + // Expect 2 Resumed events -> opend device call submits a ctrl transfer, which automatically resumes the root port + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_RESUMED; + expect_client_event(&expect_event, 100); // Mic device is opened by client -> expect event + expect_client_event(&expect_event, 100); // Spk device is opened by client -> expect event + + // Both devices opened + + uac_host_dev_alt_param_t spk_iface_alt_params, mic_iface_alt_params; + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(spk_device_handle, 1, &spk_iface_alt_params)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_get_device_alt_param(mic_device_handle, 1, &mic_iface_alt_params)); + + // Suspend the root port back and expect client event + printf("Issue suspend\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_lib_root_port_suspend()); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_SUSPENDED; + expect_client_event(&expect_event, 100); + expect_client_event(&expect_event, 100); + + // Fail to start the speaker device, with the root port suspended + const uac_host_stream_config_t spk_stream_config = { + .channels = spk_iface_alt_params.channels, + .bit_resolution = spk_iface_alt_params.bit_resolution, + .sample_freq = spk_iface_alt_params.sample_freq[0], + }; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, uac_host_device_start(spk_device_handle, &spk_stream_config)); + + // Fail to start the microphone device, with the root port suspended + const uac_host_stream_config_t mic_stream_config = { + .channels = mic_iface_alt_params.channels, + .bit_resolution = mic_iface_alt_params.bit_resolution, + .sample_freq = mic_iface_alt_params.sample_freq[0], + }; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, uac_host_device_start(mic_device_handle, &mic_stream_config)); + + // Resume the root port + printf("Issue resume\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_lib_root_port_resume()); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_RESUMED; + expect_client_event(&expect_event, 100); + expect_client_event(&expect_event, 100); + + // Start both devices + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(spk_device_handle, &spk_stream_config)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_start(mic_device_handle, &mic_stream_config)); + + // Both devices started + + // Suspend the root port back and expect client event + printf("Issue suspend\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_lib_root_port_suspend()); + expect_event.device_evt.event = UAC_HOST_DEVICE_EVENT_SUSPENDED; + expect_client_event(&expect_event, 100); + expect_client_event(&expect_event, 100); + + // Close both devices + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(spk_device_handle)); + TEST_ASSERT_EQUAL(ESP_OK, uac_host_device_close(mic_device_handle)); +} + /** - * @brief UAC reading with suspend/resume, TODO brief and procedure + * @brief record the rx stream data from microphone with suspend/resume + * Issue suspend and resume signals */ TEST_CASE("test uac rx reading, suspend/resume", "[uac_host][rx]") { diff --git a/host/class/uac/usb_host_uac/uac_host.c b/host/class/uac/usb_host_uac/uac_host.c index cb064d95..9f7ba8bf 100644 --- a/host/class/uac/usb_host_uac/uac_host.c +++ b/host/class/uac/usb_host_uac/uac_host.c @@ -202,8 +202,8 @@ static esp_err_t _uac_host_device_add(uint8_t addr, usb_device_handle_t dev_hdl, static esp_err_t _uac_host_device_delete(uac_device_t *uac_device); static esp_err_t uac_cs_request_set(uac_device_t *uac_device, const uac_cs_request_t *req); static esp_err_t uac_cs_request_set_ep_frequency(uac_iface_t *iface, uint8_t ep_addr, uint32_t freq); -static esp_err_t uac_host_interface_suspend(uac_iface_t *iface, bool root_port_suspended); -static esp_err_t uac_host_interface_resume(uac_iface_t *iface, bool root_port_suspended); +static void stream_rx_xfer_done(usb_transfer_t *in_xfer); +static void stream_tx_xfer_done(usb_transfer_t *out_xfer); // --------------------------- Utility Functions -------------------------------- /** @@ -1011,6 +1011,101 @@ static esp_err_t _uac_host_device_disconnected(usb_device_handle_t dev_hdl) } #ifdef UAC_HOST_SUSPEND_RESUME_API_SUPPORTED + +/** + * @brief Global root port suspend was called, suspend an interface + * + * @param[in] iface Pointer to Interface structure, + * @return esp_err_t + */ +static esp_err_t _uac_host_interface_pm_suspend(uac_iface_t *iface) +{ + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(iface->parent); + UAC_RETURN_ON_INVALID_ARG(iface->free_xfer_list); + UAC_RETURN_ON_FALSE(is_interface_in_list(iface), ESP_ERR_NOT_FOUND, "Interface handle not found"); + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); + iface->state = UAC_INTERFACE_STATE_SUSPENDING; + + // EPs are already flushed and halted, managed by the usb_host_lib + + _ring_buffer_flush(iface->ringbuf); + + // add all the transfer to free list + UAC_ENTER_CRITICAL(); + for (int i = 0; i < iface->xfer_num; i++) { + if (iface->xfer_list[i]) { + iface->free_xfer_list[i] = iface->xfer_list[i]; + iface->xfer_list[i] = NULL; + } + } + UAC_EXIT_CRITICAL(); + // Change state + iface->state = UAC_INTERFACE_STATE_READY; + + return ESP_OK; +} + +/** + * @brief Global root port resume was called, resume an interface + * + * @param[in] iface Pointer to Interface structure, + * @return esp_err_t + */ +static esp_err_t _uac_host_interface_pm_resume(uac_iface_t *iface) +{ + UAC_RETURN_ON_INVALID_ARG(iface); + UAC_RETURN_ON_INVALID_ARG(iface->parent); + UAC_RETURN_ON_INVALID_ARG(iface->free_xfer_list); + UAC_RETURN_ON_FALSE(is_interface_in_list(iface), ESP_ERR_NOT_FOUND, "Interface handle not found"); + UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); + + // for RX, we just submit all the transfers + if (iface->dev_info.type == UAC_STREAM_RX) { + assert(iface->iface_alt[iface->cur_alt].ep_addr & 0x80); + for (int i = 0; i < iface->xfer_num; i++) { + assert(iface->free_xfer_list[i]); + iface->free_xfer_list[i]->device_handle = iface->parent->dev_hdl; + iface->free_xfer_list[i]->callback = stream_rx_xfer_done; + iface->free_xfer_list[i]->context = iface; + iface->free_xfer_list[i]->timeout_ms = DEFAULT_ISOC_XFER_TIMEOUT_MS; + iface->free_xfer_list[i]->bEndpointAddress = iface->iface_alt[iface->cur_alt].ep_addr; + // we request the size same as the MPS of the endpoint, but the actual size should be checked in the callback + iface->free_xfer_list[i]->num_bytes = iface->iface_alt[iface->cur_alt].ep_mps * iface->packet_num; + // set request nub_bytes of each packet + for (int j = 0; j < iface->packet_num; j++) { + iface->free_xfer_list[i]->isoc_packet_desc[j].num_bytes = iface->iface_alt[iface->cur_alt].ep_mps; + } + iface->xfer_list[i] = iface->free_xfer_list[i]; + iface->free_xfer_list[i] = NULL; + UAC_RETURN_ON_ERROR(usb_host_transfer_submit(iface->xfer_list[i]), "Unable to submit RX transfer"); + } + } else if (iface->dev_info.type == UAC_STREAM_TX) { + assert(!(iface->iface_alt[iface->cur_alt].ep_addr & 0x80)); + // for TX, we submit the first transfer with data 0 to make the speaker quiet + for (int i = 0; i < iface->xfer_num; i++) { + assert(iface->free_xfer_list[i]); + iface->free_xfer_list[i]->device_handle = iface->parent->dev_hdl; + iface->free_xfer_list[i]->callback = stream_tx_xfer_done; + iface->free_xfer_list[i]->context = iface; + iface->free_xfer_list[i]->timeout_ms = DEFAULT_ISOC_XFER_TIMEOUT_MS; + iface->free_xfer_list[i]->bEndpointAddress = iface->iface_alt[iface->cur_alt].ep_addr; + // set the data buffer to 0 + memset(iface->free_xfer_list[i]->data_buffer, 0, iface->free_xfer_list[i]->data_buffer_size); + // for synchronous transfer type, the packet size depends on the actual sample rate, channels and bit resolution. + for (int j = 0; j < iface->packet_num; j++) { + iface->free_xfer_list[i]->isoc_packet_desc[j].num_bytes = iface->packet_size; + } + iface->free_xfer_list[i]->num_bytes = iface->packet_num * iface->packet_size; + } + } + + // for TX, we check if data is available in the ringbuffer, if yes, we submit the transfer + iface->state = UAC_INTERFACE_STATE_ACTIVE; + + return ESP_OK; +} + /** * @brief Handler for global (root port) suspend event * @@ -1034,7 +1129,7 @@ static esp_err_t _uac_host_device_suspended(usb_device_handle_t dev_hdl) ESP_LOGD(TAG, "Suspending interface %p in %d state", uac_iface, uac_iface->state); if (uac_iface->state == UAC_INTERFACE_STATE_ACTIVE) { - uac_host_interface_suspend(uac_iface, true); + _uac_host_interface_pm_suspend(uac_iface); } uac_host_user_interface_callback(uac_iface, UAC_HOST_DEVICE_EVENT_SUSPENDED); } @@ -1070,7 +1165,7 @@ static esp_err_t _uac_host_device_resumed(usb_device_handle_t dev_hdl) // The interface is in ready state, we must submit RX transfer(s) poll if (uac_iface->state == UAC_INTERFACE_STATE_READY) { - uac_host_interface_resume(uac_iface, true); + _uac_host_interface_pm_resume(uac_iface); } uac_host_user_interface_callback(uac_iface, UAC_HOST_DEVICE_EVENT_RESUMED); } @@ -1346,7 +1441,7 @@ static void stream_tx_xfer_done(usb_transfer_t *out_xfer) * @param[in] iface Pointer to Interface structure * @return esp_err_t */ -static esp_err_t uac_host_interface_suspend(uac_iface_t *iface, bool root_port_suspended) +static esp_err_t uac_host_interface_suspend(uac_iface_t *iface) { UAC_RETURN_ON_INVALID_ARG(iface); UAC_RETURN_ON_INVALID_ARG(iface->parent); @@ -1356,23 +1451,19 @@ static esp_err_t uac_host_interface_suspend(uac_iface_t *iface, bool root_port_s iface->state = UAC_INTERFACE_STATE_SUSPENDING; // Set Interface alternate setting to 0 - if (!root_port_suspended) { - usb_setup_packet_t request; - USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, 0); - esp_err_t ret = uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request); - if (ret != ESP_OK) { - ESP_LOGW(TAG, "Set Interface %d-%d Failed", iface->dev_info.iface_num, 0); - } else { - ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, 0); - } + usb_setup_packet_t request; + USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, 0); + esp_err_t ret = uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Set Interface %d-%d Failed", iface->dev_info.iface_num, 0); + } else { + ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, 0); } - if (!root_port_suspended) { - uint8_t ep_addr = iface->iface_alt[iface->cur_alt].ep_addr; - UAC_RETURN_ON_ERROR(usb_host_endpoint_halt(iface->parent->dev_hdl, ep_addr), "Unable to HALT EP"); - UAC_RETURN_ON_ERROR(usb_host_endpoint_flush(iface->parent->dev_hdl, ep_addr), "Unable to FLUSH EP"); - usb_host_endpoint_clear(iface->parent->dev_hdl, ep_addr); - } + uint8_t ep_addr = iface->iface_alt[iface->cur_alt].ep_addr; + UAC_RETURN_ON_ERROR(usb_host_endpoint_halt(iface->parent->dev_hdl, ep_addr), "Unable to HALT EP"); + UAC_RETURN_ON_ERROR(usb_host_endpoint_flush(iface->parent->dev_hdl, ep_addr), "Unable to FLUSH EP"); + usb_host_endpoint_clear(iface->parent->dev_hdl, ep_addr); _ring_buffer_flush(iface->ringbuf); // add all the transfer to free list @@ -1396,7 +1487,7 @@ static esp_err_t uac_host_interface_suspend(uac_iface_t *iface, bool root_port_s * @param[in] iface Pointer to Interface structure * @return esp_err_t */ -static esp_err_t uac_host_interface_resume(uac_iface_t *iface, bool root_port_suspended) +static esp_err_t uac_host_interface_resume(uac_iface_t *iface) { UAC_RETURN_ON_INVALID_ARG(iface); UAC_RETURN_ON_INVALID_ARG(iface->parent); @@ -1405,17 +1496,15 @@ static esp_err_t uac_host_interface_resume(uac_iface_t *iface, bool root_port_su UAC_RETURN_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "Interface wrong state"); // Set Interface alternate setting - if (!root_port_suspended) { - usb_setup_packet_t request; - USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, iface->cur_alt + 1); - UAC_RETURN_ON_ERROR(uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request), "Unable to set Interface alternate"); - ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, iface->cur_alt + 1); - // Set endpoint frequency control - if (iface->iface_alt[iface->cur_alt].freq_ctrl_supported) { - ESP_LOGI(TAG, "Set EP %02X frequency %"PRIu32, iface->iface_alt[iface->cur_alt].ep_addr, iface->iface_alt[iface->cur_alt].cur_sampling_freq); - UAC_RETURN_ON_ERROR(uac_cs_request_set_ep_frequency(iface, iface->iface_alt[iface->cur_alt].ep_addr, - iface->iface_alt[iface->cur_alt].cur_sampling_freq), "Unable to set endpoint frequency"); - } + usb_setup_packet_t request; + USB_SETUP_PACKET_INIT_SET_INTERFACE(&request, iface->dev_info.iface_num, iface->cur_alt + 1); + UAC_RETURN_ON_ERROR(uac_cs_request_set(iface->parent, (uac_cs_request_t *)&request), "Unable to set Interface alternate"); + ESP_LOGI(TAG, "Set Interface %d-%d", iface->dev_info.iface_num, iface->cur_alt + 1); + // Set endpoint frequency control + if (iface->iface_alt[iface->cur_alt].freq_ctrl_supported) { + ESP_LOGI(TAG, "Set EP %02X frequency %"PRIu32, iface->iface_alt[iface->cur_alt].ep_addr, iface->iface_alt[iface->cur_alt].cur_sampling_freq); + UAC_RETURN_ON_ERROR(uac_cs_request_set_ep_frequency(iface, iface->iface_alt[iface->cur_alt].ep_addr, + iface->iface_alt[iface->cur_alt].cur_sampling_freq), "Unable to set endpoint frequency"); } // for RX, we just submit all the transfers if (iface->dev_info.type == UAC_STREAM_RX) { @@ -2208,7 +2297,7 @@ esp_err_t uac_host_device_close(uac_host_device_handle_t uac_dev_handle) UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(uac_iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "UAC Interface is busy by other task"); if (UAC_INTERFACE_STATE_ACTIVE == uac_iface->state) { - UAC_GOTO_ON_ERROR(uac_host_interface_suspend(uac_iface, false), "Unable to disable UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(uac_iface), "Unable to disable UAC Interface"); } if (UAC_INTERFACE_STATE_READY == uac_iface->state) { @@ -2380,7 +2469,7 @@ esp_err_t uac_host_device_start(uac_host_device_handle_t uac_dev_handle, const u iface_claimed = true; if (!(iface->flags & FLAG_STREAM_SUSPEND_AFTER_START)) { - UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface, false), "Unable to resume UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface), "Unable to resume UAC Interface"); } uac_host_interface_unlock(iface); return ESP_OK; @@ -2405,7 +2494,7 @@ esp_err_t uac_host_device_suspend(uac_host_device_handle_t uac_dev_handle) } esp_err_t ret = ESP_OK; UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_ACTIVE == iface->state), ESP_ERR_INVALID_STATE, "device not active"); - UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface, false), "Unable to suspend UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface), "Unable to suspend UAC Interface"); uac_host_interface_unlock(iface); return ESP_OK; @@ -2428,7 +2517,7 @@ esp_err_t uac_host_device_resume(uac_host_device_handle_t uac_dev_handle) esp_err_t ret = ESP_OK; UAC_GOTO_ON_FALSE((UAC_INTERFACE_STATE_READY == iface->state), ESP_ERR_INVALID_STATE, "device not ready"); - UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface, false), "Unable to resume UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_resume(iface), "Unable to resume UAC Interface"); uac_host_interface_unlock(iface); return ESP_OK; @@ -2446,7 +2535,7 @@ esp_err_t uac_host_device_stop(uac_host_device_handle_t uac_dev_handle) esp_err_t ret = ESP_OK; UAC_RETURN_ON_ERROR(uac_host_interface_try_lock(iface, DEFAULT_CTRL_XFER_TIMEOUT_MS), "Unable to lock UAC Interface"); if (UAC_INTERFACE_STATE_ACTIVE == iface->state) { - UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface, false), "Unable to suspend UAC Interface"); + UAC_GOTO_ON_ERROR(uac_host_interface_suspend(iface), "Unable to suspend UAC Interface"); } if (UAC_INTERFACE_STATE_READY == iface->state) {