From a33b96cf0b8979025c971e2e7786fc208ad8c4fa Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 30 Sep 2025 12:36:44 +0200 Subject: [PATCH 1/3] refactor(tinyusb): Made the tinyusb task deletion notification driven - Fixed the _task_pending status change on error during tinyusb_task_start() - Kept the legace way of deleting task when blocking time is UINT32_MAX (task deleted in the user context) - Added blocking_timeout_ms parameter to the driver config, tinyusb task parameters --- device/esp_tinyusb/include/tinyusb.h | 13 ++ .../include/tinyusb_default_config.h | 28 ++++ device/esp_tinyusb/tinyusb_task.c | 139 ++++++++++++++---- 3 files changed, 153 insertions(+), 27 deletions(-) diff --git a/device/esp_tinyusb/include/tinyusb.h b/device/esp_tinyusb/include/tinyusb.h index cb302263..cd28587e 100644 --- a/device/esp_tinyusb/include/tinyusb.h +++ b/device/esp_tinyusb/include/tinyusb.h @@ -60,6 +60,19 @@ typedef struct { size_t size; /*!< USB Device Task size. */ uint8_t priority; /*!< USB Device Task priority. */ int xCoreID; /*!< USB Device Task core affinity. */ + uint32_t blocking_timeout_ms; /*!< USB Device Task blocking timeout in milliseconds. + This feature is used to control the behavior of the tud_task_ext() function called + inside the dedicated TinyUSB task. The timeout defines how long the tud_task_ext() function + will block waiting for USB events and how frequently the driver will poll for the uninstall notififcation. + If tinyusb_driver_uninstall() is not used, the blocking timeout can block indefinitely to reduce CPU usage. + + Values: + 0 - non-blocking mode. + UINT_MAX - blocks indefinetely (legacy mode). + + Default value: 1000 ms. + */ + } tinyusb_task_config_t; /** diff --git a/device/esp_tinyusb/include/tinyusb_default_config.h b/device/esp_tinyusb/include/tinyusb_default_config.h index a1cb9714..5c77f1c5 100644 --- a/device/esp_tinyusb/include/tinyusb_default_config.h +++ b/device/esp_tinyusb/include/tinyusb_default_config.h @@ -63,6 +63,12 @@ extern "C" { #define TINYUSB_DEFAULT_TASK_SIZE 4096 // Default priority for task used in TinyUSB task creation #define TINYUSB_DEFAULT_TASK_PRIO 5 +// Default blocking timeout_ms for tud_task_ext(timeout_ms, false), depending on target to acieve similar CPU load +#if CONFIG_IDF_TARGET_ESP32P4 +#define TINYUSB_DEFAULT_BLOCK_TIME_MS 1000 +#else +#define TINYUSB_DEFAULT_BLOCK_TIME_MS 2000 +#endif // CONFIG_IDF_TARGET_ESP32P4 #define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \ (tinyusb_config_t) { \ @@ -111,6 +117,7 @@ extern "C" { .size = TINYUSB_DEFAULT_TASK_SIZE, \ .priority = TINYUSB_DEFAULT_TASK_PRIO, \ .xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \ + .blocking_timeout_ms = TINYUSB_DEFAULT_BLOCK_TIME_MS,\ } /** @@ -118,6 +125,8 @@ extern "C" { * * This macro is used to create a custom TinyUSB Task configuration structure. * + * Note: blocking_timeout_ms is set to default value. + * * @param s Stack size of the task * @param p Task priority * @param a Task affinity (CPU core) @@ -127,6 +136,25 @@ extern "C" { .size = (s), \ .priority = (p), \ .xCoreID = (a), \ + .blocking_timeout_ms = TINYUSB_DEFAULT_BLOCK_TIME_MS, \ + } + +/** + * @brief TinyUSB Task configuration structure initializer + * + * This macro is used to create a custom TinyUSB Task configuration structure. + * + * @param s Stack size of the task + * @param p Task priority + * @param a Task affinity (CPU core) + * @param t Blocking timeout in milliseconds for tud_task_ext() function + */ +#define TINYUSB_TASK_CUSTOM_NON_BLOCKING(s, p, a, t) \ + (tinyusb_task_config_t) { \ + .size = (s), \ + .priority = (p), \ + .xCoreID = (a), \ + .blocking_timeout_ms = (t), \ } #ifdef __cplusplus diff --git a/device/esp_tinyusb/tinyusb_task.c b/device/esp_tinyusb/tinyusb_task.c index 79d4acaa..cacf25fb 100644 --- a/device/esp_tinyusb/tinyusb_task.c +++ b/device/esp_tinyusb/tinyusb_task.c @@ -24,6 +24,9 @@ static portMUX_TYPE tusb_task_lock = portMUX_INITIALIZER_UNLOCKED; #define TINYUSB_TASK_ENTER_CRITICAL() portENTER_CRITICAL(&tusb_task_lock) #define TINYUSB_TASK_EXIT_CRITICAL() portEXIT_CRITICAL(&tusb_task_lock) +// Timeout for taking the start/stop semaphore in milliseconds +#define TINYUSB_TASK_SEM_TAKE_TIMEOUT_MS (5000) + #define TINYUSB_TASK_CHECK(cond, ret_val) ({ \ if (!(cond)) { \ return (ret_val); \ @@ -44,11 +47,14 @@ typedef struct { tusb_rhport_init_t rhport_init; /*!< USB Device RH port initialization configuration pointer */ const tinyusb_desc_config_t *desc_cfg; /*!< USB Device descriptors configuration pointer */ // Task related - TaskHandle_t handle; /*!< Task handle */ - volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */ + TaskHandle_t handle; /*!< TinyUSB Device task handle */ + SemaphoreHandle_t start_stop_sem; /*!< Semaphore to start or stop the task */ + uint32_t blocking_timeout_ms; /*!< USB Device Task blocking timeout in milliseconds. */ } tinyusb_task_ctx_t; -static bool _task_is_running = false; // Locking flag for the task, access only from the critical section +#define TASK_NOTIF_STOP_BIT (1u << 0) + +static bool _task_is_pending = false; // Locking flag for the task, access only from the critical section static tinyusb_task_ctx_t *p_tusb_task_ctx = NULL; // TinyUSB task context /** @@ -57,12 +63,19 @@ static tinyusb_task_ctx_t *p_tusb_task_ctx = NULL; // TinyUSB task context static void tinyusb_device_task(void *arg) { tinyusb_task_ctx_t *task_ctx = (tinyusb_task_ctx_t *)arg; + bool stop_in_pending = false; // Sanity check assert(task_ctx != NULL); - assert(task_ctx->awaiting_handle != NULL); + assert(task_ctx->start_stop_sem != NULL); ESP_LOGD(TAG, "TinyUSB task started"); + ESP_LOGD(TAG, "\tPort %d", task_ctx->rhport); + if (task_ctx->blocking_timeout_ms == UINT32_MAX) { + ESP_LOGD(TAG, "\tBlocking timeout: MAX"); + } else { + ESP_LOGD(TAG, "\tBlocking timeout: %" PRIu32 " ms", task_ctx->blocking_timeout_ms); + } if (tud_inited()) { ESP_LOGE(TAG, "TinyUSB stack is already initialized"); @@ -78,22 +91,55 @@ static void tinyusb_device_task(void *arg) } TINYUSB_TASK_ENTER_CRITICAL(); - task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle - p_tusb_task_ctx = task_ctx; // Save global task context pointer + _task_is_pending = false; // Clear pending flag, task is running now + task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle + p_tusb_task_ctx = task_ctx; // Save global task context pointer TINYUSB_TASK_EXIT_CRITICAL(); - xTaskNotifyGive(task_ctx->awaiting_handle); // Notify parent task that TinyUSB stack was started successfully + // Notify parent task that TinyUSB stack was started successfully + if (xSemaphoreGive(task_ctx->start_stop_sem) != pdTRUE) { + // Fail only if semaphore queue is full, should never happen + ESP_LOGE(TAG, "Unable to notify that TinyUSB stack was started"); + goto desc_free; + } - while (1) { // RTOS forever loop - tud_task(); + uint32_t notify_blocking_ms = (task_ctx->blocking_timeout_ms == 0) ? 1 : 0; // Insignificant blocking time to check notifications, to not to waste CPU when tud_task_ext(0, false) + while (1) { + uint32_t notif = 0; + // Poll for notification bits + if (xTaskNotifyWait( + 0, // don't clear any bits on entry + TASK_NOTIF_STOP_BIT, // clear STOP bit on exit if it was set + ¬if, + pdMS_TO_TICKS(notify_blocking_ms) + ) == pdTRUE && (notif & TASK_NOTIF_STOP_BIT)) { + // Stop requested + stop_in_pending = true; + break; + } + // TinyUSB Device task + tud_task_ext(task_ctx->blocking_timeout_ms, false); } + // Stop TinyUSB stack + if (!tusb_deinit(task_ctx->rhport)) { + ESP_LOGE(TAG, "Unable to deinit TinyUSB stack"); + } + + ESP_LOGD(TAG, "TinyUSB task has stopped"); + desc_free: tinyusb_descriptors_free(); del: TINYUSB_TASK_ENTER_CRITICAL(); - _task_is_running = false; // Task is not running anymore + _task_is_pending = false; // Clear pending flag + p_tusb_task_ctx = NULL; // Clear global task context pointer TINYUSB_TASK_EXIT_CRITICAL(); + + if (stop_in_pending) { + // Notify that TinyUSB stack was stopped + xSemaphoreGive(task_ctx->start_stop_sem); + } vTaskDelete(NULL); // No return needed here: vTaskDelete(NULL) does not return } @@ -117,22 +163,25 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c TINYUSB_TASK_ENTER_CRITICAL(); TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE); // Task shouldn't started - TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_running, ESP_ERR_INVALID_STATE); // Task shouldn't be running - _task_is_running = true; // Task is running flag, will be cleared in task in case of the error + TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE); // Task shouldn't be pending + _task_is_pending = true; // Task is pending flag, will be cleared in task TINYUSB_TASK_EXIT_CRITICAL(); esp_err_t ret; tinyusb_task_ctx_t *task_ctx = heap_caps_calloc(1, sizeof(tinyusb_task_ctx_t), MALLOC_CAP_DEFAULT); - if (task_ctx == NULL) { - return ESP_ERR_NO_MEM; + SemaphoreHandle_t sem = xSemaphoreCreateBinary(); + + if (task_ctx == NULL || sem == NULL) { + ret = ESP_ERR_NO_MEM; + goto no_task; } - task_ctx->awaiting_handle = xTaskGetCurrentTaskHandle(); // Save parent task handle - task_ctx->handle = NULL; // TinyUSB task is not started + task_ctx->start_stop_sem = sem; // TunyUSB Device task start stop semaphore task_ctx->rhport = port; // Peripheral port number task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection task_ctx->desc_cfg = desc_cfg; + task_ctx->blocking_timeout_ms = config->blocking_timeout_ms; TaskHandle_t task_hdl = NULL; ESP_LOGD(TAG, "Creating TinyUSB main task on CPU%d", config->xCoreID); @@ -147,19 +196,32 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c if (task_hdl == NULL) { ESP_LOGE(TAG, "Create TinyUSB main task failed"); ret = ESP_ERR_NOT_FINISHED; - goto err; + goto no_task; } - // Wait until the Task notify that port is active, 5 sec is more than enough - if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) { + // Wait for "start" semophore with timeout + if (xSemaphoreTake(task_ctx->start_stop_sem, pdMS_TO_TICKS(TINYUSB_TASK_SEM_TAKE_TIMEOUT_MS)) != pdTRUE) { ESP_LOGE(TAG, "Task wasn't able to start TinyUSB stack"); ret = ESP_ERR_TIMEOUT; goto err; } + // Check the global task context pointer and pending state + TINYUSB_TASK_ENTER_CRITICAL(); + TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE); + TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == task_ctx, ESP_ERR_NOT_FOUND); + TINYUSB_TASK_EXIT_CRITICAL(); + return ESP_OK; +no_task: + TINYUSB_TASK_ENTER_CRITICAL(); + _task_is_pending = false; // Clear pending flag + TINYUSB_TASK_EXIT_CRITICAL(); err: + if (sem) { + vSemaphoreDelete(sem); + } heap_caps_free(task_ctx); return ret; } @@ -168,20 +230,43 @@ esp_err_t tinyusb_task_stop(void) { TINYUSB_TASK_ENTER_CRITICAL(); TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx != NULL, ESP_ERR_INVALID_STATE); + TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE); // Task shouldn't be pending tinyusb_task_ctx_t *task_ctx = p_tusb_task_ctx; - p_tusb_task_ctx = NULL; - _task_is_running = false; + _task_is_pending = true; // Task is pending to stop TINYUSB_TASK_EXIT_CRITICAL(); - if (task_ctx->handle != NULL) { + if (task_ctx->blocking_timeout_ms != UINT32_MAX) { + // Request TinyUSB task to stop + if (xTaskNotify(task_ctx->handle, TASK_NOTIF_STOP_BIT, eSetBits) != pdPASS) { + ESP_LOGE(TAG, "Unable to notify TinyUSB task to stop"); + return ESP_ERR_INVALID_STATE; + } + // Wait for "stop" semaphore with timeout + if (xSemaphoreTake(task_ctx->start_stop_sem, pdMS_TO_TICKS(TINYUSB_TASK_SEM_TAKE_TIMEOUT_MS)) != pdTRUE) { + ESP_LOGE(TAG, "Timeout while TinyUSB task stop"); + return ESP_ERR_TIMEOUT; + } + // Check the global task context pointer and pending state + TINYUSB_TASK_ENTER_CRITICAL(); + TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE); + TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE); + TINYUSB_TASK_EXIT_CRITICAL(); + } else { + // TinyUSB Task is in forever loop, delete the task the old way vTaskDelete(task_ctx->handle); task_ctx->handle = NULL; + // Free descriptors + tinyusb_descriptors_free(); + // Deinit TinyUSB Stask + ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_INVALID_STATE, TAG, "Unable to deinit TinyUSB stack"); + // Clean global context and pending state + TINYUSB_TASK_ENTER_CRITICAL(); + _task_is_pending = false; + p_tusb_task_ctx = NULL; + TINYUSB_TASK_EXIT_CRITICAL(); } - // Free descriptors - tinyusb_descriptors_free(); - // Stop TinyUSB stack - ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack"); - // Cleanup + + vSemaphoreDelete(task_ctx->start_stop_sem); heap_caps_free(task_ctx); return ESP_OK; } From db74ada082382ee1a3e2be30e387f0ca188569b0 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 18 Nov 2025 14:48:25 +0100 Subject: [PATCH 2/3] refactor(esp_tinyusb): Added test cases for different blocking_timeout_ms config --- .../runtime_config/main/test_cpu_load.c | 88 +++++++- .../main/test_multitask_access.c | 198 +++++++++++++++++- 2 files changed, 276 insertions(+), 10 deletions(-) diff --git a/device/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c b/device/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c index d0bb3518..50b309a7 100644 --- a/device/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c +++ b/device/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c @@ -149,7 +149,7 @@ static void test_cpu_load_measure(void) * - uninstall driver * - show results */ -TEST_CASE("[CPU load] Install & Uninstall, default configuration", "[cpu_load]") +TEST_CASE("[CPU load] Install & Uninstall, default blocking task", "[cpu_load]") { #if (!CONFIG_FREERTOS_UNICORE) // Allow other core to finish initialization @@ -178,4 +178,90 @@ TEST_CASE("[CPU load] Install & Uninstall, default configuration", "[cpu_load]") printf("TinyUSB CPU load: %" PRIu32 " %%\n", _tinyusb_cpu_load); } +/** + * @brief Test TinyUSB CPU load measurement + * + * Scenario: + * - Install TinyUSB driver with default configuration + * - wait for device connection + * - measure CPU load + * - uninstall driver + * - show results + */ +TEST_CASE("[CPU load] Install & Uninstall, non-blocking task", "[cpu_load]") +{ +#if (!CONFIG_FREERTOS_UNICORE) + // Allow other core to finish initialization + vTaskDelay(pdMS_TO_TICKS(100)); +#endif // (!CONFIG_FREERTOS_UNICORE) + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + // Set the task blocking timeout to 0: non-blocking + tusb_cfg.task.blocking_timeout_ms = 0; + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + // Initialize CPU load measurement + test_cpu_load_init(); + + // Wait for the device to be mounted and enumerated by the Host + test_device_wait(); + printf("\t -> Device connected\n"); + + // Measure CPU load + test_cpu_load_measure(); + + // Uninstall TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + + // Show results + printf("TinyUSB Run time: %" PRIu32 " ticks\n", _tinyusb_run_time); + printf("TinyUSB CPU load: %" PRIu32 " %%\n", _tinyusb_cpu_load); +} + +/** + * @brief Test TinyUSB CPU load measurement + * + * Scenario: + * - Install TinyUSB driver with default configuration + * - wait for device connection + * - measure CPU load + * - uninstall driver + * - show results + */ +TEST_CASE("[CPU load] Install & Uninstall, blocking task indefinitely (legacy mode)", "[cpu_load]") +{ +#if (!CONFIG_FREERTOS_UNICORE) + // Allow other core to finish initialization + vTaskDelay(pdMS_TO_TICKS(100)); +#endif // (!CONFIG_FREERTOS_UNICORE) + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + // Set the task blocking timeout to UINT32_t_MAX: blocking indefinitely + tusb_cfg.task.blocking_timeout_ms = UINT32_MAX; + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + // Initialize CPU load measurement + test_cpu_load_init(); + + // Wait for the device to be mounted and enumerated by the Host + test_device_wait(); + printf("\t -> Device connected\n"); + + // Measure CPU load + test_cpu_load_measure(); + + // Uninstall TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + + // Show results + printf("TinyUSB Run time: %" PRIu32 " ticks\n", _tinyusb_run_time); + printf("TinyUSB CPU load: %" PRIu32 " %%\n", _tinyusb_cpu_load); +} + + + #endif // SOC_USB_OTG_SUPPORTED diff --git a/device/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c b/device/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c index 9e34e1e7..74e79a43 100644 --- a/device/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c +++ b/device/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c @@ -38,15 +38,12 @@ static volatile int nb_of_success = 0; static void test_task_install(void *arg) { - (void) arg; + tinyusb_config_t *tusb_cfg = (tinyusb_config_t *) arg; // Install TinyUSB driver - tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); - tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks to install the driver - // Wait to be started by main thread ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - if (tinyusb_driver_install(&tusb_cfg) == ESP_OK) { + if (tinyusb_driver_install(tusb_cfg) == ESP_OK) { test_device_wait(); TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Unable to uninstall driver after install in worker"); taskENTER_CRITICAL(&_spinlock); @@ -101,12 +98,19 @@ static void test_deinit_phy(usb_phy_handle_t phy_hdl) // ============================= Tests ========================================= /** - * @brief TinyUSB Task specific testcase + * @brief TinyUSB Multitask access test cases + * + * Scenario: Trying to install and uninstall the driver from several tasks * * Scenario: Trying to install driver from several tasks * Note: when phy.skip_setup = false, the task access will be determined by the first task install the phy + * + * Parameter: tusb_cfg.task.blocking_timeout_ms + * - default blocking + * - non-blocking + * - blocking indefinitely (legacy mode) */ -TEST_CASE("Multitask: Install", "[runtime_config][default]") +TEST_CASE("[Multitask] Install, default blocking task", "[runtime_config][default]") { usb_phy_handle_t phy_hdl = test_init_phy(); @@ -117,12 +121,102 @@ TEST_CASE("Multitask: Install", "[runtime_config][default]") // No task are running yet nb_of_success = 0; + // Configure TinyUSB default config + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks to install the driver + // Create tasks that will start the driver for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_install, "InstallTask", 4096, - NULL, + (void *) &tusb_cfg, + 4 + i, + &test_task_handles[i])); + } + + // Start all tasks + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + xTaskNotifyGive(test_task_handles[i]); + } + + // Wait for all tasks to complete + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(sem_done, pdMS_TO_TICKS(5000)), "Not all tasks completed in time"); + } + + // There should be only one task that was able to install the driver + TEST_ASSERT_EQUAL_MESSAGE(1, nb_of_success, "Only one task should be able to install the driver"); + // Clean-up + test_deinit_phy(phy_hdl); + vSemaphoreDelete(sem_done); +} + +TEST_CASE("[Multitask] Install, non-blocking task", "[runtime_config][default]") +{ + usb_phy_handle_t phy_hdl = test_init_phy(); + + // Create counting semaphore to wait for all tasks to complete + sem_done = xSemaphoreCreateCounting(MULTIPLE_THREADS_TASKS_NUM, 0); + TEST_ASSERT_NOT_NULL(sem_done); + + // No task are running yet + nb_of_success = 0; + + // Configure TinyUSB default config + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks to install the driver + tusb_cfg.task.blocking_timeout_ms = 0; // Non-blocking mode + + // Create tasks that will start the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_install, + "InstallTask", + 4096, + (void *) &tusb_cfg, + 4 + i, + &test_task_handles[i])); + } + + // Start all tasks + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + xTaskNotifyGive(test_task_handles[i]); + } + + // Wait for all tasks to complete + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(sem_done, pdMS_TO_TICKS(5000)), "Not all tasks completed in time"); + } + + // There should be only one task that was able to install the driver + TEST_ASSERT_EQUAL_MESSAGE(1, nb_of_success, "Only one task should be able to install the driver"); + // Clean-up + test_deinit_phy(phy_hdl); + vSemaphoreDelete(sem_done); +} + +TEST_CASE("[Multitask] Install, blocking task indefinitely (legacy mode)", "[runtime_config][default]") +{ + usb_phy_handle_t phy_hdl = test_init_phy(); + + // Create counting semaphore to wait for all tasks to complete + sem_done = xSemaphoreCreateCounting(MULTIPLE_THREADS_TASKS_NUM, 0); + TEST_ASSERT_NOT_NULL(sem_done); + + // No task are running yet + nb_of_success = 0; + + // Configure TinyUSB default config + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks to install the driver + tusb_cfg.task.blocking_timeout_ms = UINT32_MAX; // Blocking indefinitely + + // Create tasks that will start the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_install, + "InstallTask", + 4096, + (void *) &tusb_cfg, 4 + i, &test_task_handles[i])); } @@ -144,7 +238,48 @@ TEST_CASE("Multitask: Install", "[runtime_config][default]") vSemaphoreDelete(sem_done); } -TEST_CASE("Multitask: Uninstall", "[runtime_config][default]") +TEST_CASE("[Multitask] Uninstall, default blocking task", "[runtime_config][default]") +{ + usb_phy_handle_t phy_hdl = test_init_phy(); + // Create counting semaphore to wait for all tasks to complete + sem_done = xSemaphoreCreateCounting(MULTIPLE_THREADS_TASKS_NUM, 0); + TEST_ASSERT_NOT_NULL(sem_done); + + // No task are running yet + nb_of_success = 0; + + // Install the driver once + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Unable to install TinyUSB driver "); + // Create tasks that will uninstall the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_uninstall, + "UninstallTask", + 4096, + NULL, + 4 + i, + &test_task_handles[i])); + } + + // Start all tasks + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + xTaskNotifyGive(test_task_handles[i]); + } + // Wait for all tasks to complete + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(sem_done, pdMS_TO_TICKS(5000)), "Not all tasks completed in time"); + } + + // There should be only one task that was able to uninstall the driver + TEST_ASSERT_EQUAL_MESSAGE(1, nb_of_success, "Only one task should be able to uninstall the driver"); + + // Clean-up + test_deinit_phy(phy_hdl); + vSemaphoreDelete(sem_done); +} + +TEST_CASE("[Multitask] Uninstall, non-blocking task", "[runtime_config][default]") { usb_phy_handle_t phy_hdl = test_init_phy(); // Create counting semaphore to wait for all tasks to complete @@ -157,7 +292,52 @@ TEST_CASE("Multitask: Uninstall", "[runtime_config][default]") // Install the driver once tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks + tusb_cfg.task.blocking_timeout_ms = 0; // Non-blocking mode TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Unable to install TinyUSB driver "); + + // Create tasks that will uninstall the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_uninstall, + "UninstallTask", + 4096, + NULL, + 4 + i, + &test_task_handles[i])); + } + + // Start all tasks + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + xTaskNotifyGive(test_task_handles[i]); + } + // Wait for all tasks to complete + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(sem_done, pdMS_TO_TICKS(5000)), "Not all tasks completed in time"); + } + + // There should be only one task that was able to uninstall the driver + TEST_ASSERT_EQUAL_MESSAGE(1, nb_of_success, "Only one task should be able to uninstall the driver"); + + // Clean-up + test_deinit_phy(phy_hdl); + vSemaphoreDelete(sem_done); +} + +TEST_CASE("[Multitask] Uninstall, blocking task indefinitely (legacy mode)", "[runtime_config][default]") +{ + usb_phy_handle_t phy_hdl = test_init_phy(); + // Create counting semaphore to wait for all tasks to complete + sem_done = xSemaphoreCreateCounting(MULTIPLE_THREADS_TASKS_NUM, 0); + TEST_ASSERT_NOT_NULL(sem_done); + + // No task are running yet + nb_of_success = 0; + + // Install the driver once + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks + tusb_cfg.task.blocking_timeout_ms = UINT32_MAX; // Blocking indefinitely + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Unable to install TinyUSB driver "); + // Create tasks that will uninstall the driver for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_uninstall, From 15a5ed564cbf0b44115a97c95ae29eaecb2fa8e8 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 25 Nov 2025 11:58:02 +0100 Subject: [PATCH 3/3] change(esp_tinyusb): Updated CHANGELOG.md and README.md --- device/esp_tinyusb/CHANGELOG.md | 4 ++++ device/esp_tinyusb/README.md | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/device/esp_tinyusb/CHANGELOG.md b/device/esp_tinyusb/CHANGELOG.md index 8750e1a5..44d778ec 100644 --- a/device/esp_tinyusb/CHANGELOG.md +++ b/device/esp_tinyusb/CHANGELOG.md @@ -1,3 +1,7 @@ +## [Unreleased] + +- esp_tinyusb: Added possibility to select blocking time for dedicated TinyUSB task + ## 2.0.1~1 - esp_tinyusb: Claim forward compatibility with TinyUSB 0.19 diff --git a/device/esp_tinyusb/README.md b/device/esp_tinyusb/README.md index 1ccfe67b..eb610482 100644 --- a/device/esp_tinyusb/README.md +++ b/device/esp_tinyusb/README.md @@ -177,6 +177,30 @@ When the default parameters of the internal task should be changed: } ``` +When your app calls `tinyusb_driver_uninstall()`, the driver cleanly stops the internal TinyUSB task. To make the start/stop points deterministic (so your code knows when the task is fully started or stopped), the driver exposes a blocking timeout for the TinyUSB task loop. + +This timeout is the maximum time (ms) the TinyUSB task may block while waiting for or servicing USB events. It is not a polling interval. + +> **Note:** Default timeout values may differ per CPU/target to match CPU load observed in legacy (block-forever) mode versus non-blocking mode. +To re-enable legacy behavior (block indefinitely), set `blocking_timeout_ms` to `UINT32_MAX`. + +You can configure the timeout during driver installation: + +```c + #include "tinyusb_default_config.h" + + void main(void) { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + tusb_cfg.task.blocking_timeout_ms = 3000; + tinyusb_driver_install(&tusb_cfg); + + // ... + + tinyusb_driver_uninstall(); // will stop deterministically within ~blocking_timeout_ms + } +``` +> **💡 Tip:** If you need very low CPU usage and don’t care about stop latency, use a larger value (hundreds or thousands of ms). If you need quick teardown (e.g., switching roles or remounting), keep this small. + ### USB Descriptors configuration Configure USB descriptors using the `tinyusb_config_t` structure: