Skip to content

Commit a33b96c

Browse files
committed
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
1 parent 0ea7e08 commit a33b96c

File tree

3 files changed

+153
-27
lines changed

3 files changed

+153
-27
lines changed

device/esp_tinyusb/include/tinyusb.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,19 @@ typedef struct {
6060
size_t size; /*!< USB Device Task size. */
6161
uint8_t priority; /*!< USB Device Task priority. */
6262
int xCoreID; /*!< USB Device Task core affinity. */
63+
uint32_t blocking_timeout_ms; /*!< USB Device Task blocking timeout in milliseconds.
64+
This feature is used to control the behavior of the tud_task_ext() function called
65+
inside the dedicated TinyUSB task. The timeout defines how long the tud_task_ext() function
66+
will block waiting for USB events and how frequently the driver will poll for the uninstall notififcation.
67+
If tinyusb_driver_uninstall() is not used, the blocking timeout can block indefinitely to reduce CPU usage.
68+
69+
Values:
70+
0 - non-blocking mode.
71+
UINT_MAX - blocks indefinetely (legacy mode).
72+
73+
Default value: 1000 ms.
74+
*/
75+
6376
} tinyusb_task_config_t;
6477

6578
/**

device/esp_tinyusb/include/tinyusb_default_config.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ extern "C" {
6363
#define TINYUSB_DEFAULT_TASK_SIZE 4096
6464
// Default priority for task used in TinyUSB task creation
6565
#define TINYUSB_DEFAULT_TASK_PRIO 5
66+
// Default blocking timeout_ms for tud_task_ext(timeout_ms, false), depending on target to acieve similar CPU load
67+
#if CONFIG_IDF_TARGET_ESP32P4
68+
#define TINYUSB_DEFAULT_BLOCK_TIME_MS 1000
69+
#else
70+
#define TINYUSB_DEFAULT_BLOCK_TIME_MS 2000
71+
#endif // CONFIG_IDF_TARGET_ESP32P4
6672

6773
#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \
6874
(tinyusb_config_t) { \
@@ -111,13 +117,16 @@ extern "C" {
111117
.size = TINYUSB_DEFAULT_TASK_SIZE, \
112118
.priority = TINYUSB_DEFAULT_TASK_PRIO, \
113119
.xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \
120+
.blocking_timeout_ms = TINYUSB_DEFAULT_BLOCK_TIME_MS,\
114121
}
115122

116123
/**
117124
* @brief TinyUSB Task configuration structure initializer
118125
*
119126
* This macro is used to create a custom TinyUSB Task configuration structure.
120127
*
128+
* Note: blocking_timeout_ms is set to default value.
129+
*
121130
* @param s Stack size of the task
122131
* @param p Task priority
123132
* @param a Task affinity (CPU core)
@@ -127,6 +136,25 @@ extern "C" {
127136
.size = (s), \
128137
.priority = (p), \
129138
.xCoreID = (a), \
139+
.blocking_timeout_ms = TINYUSB_DEFAULT_BLOCK_TIME_MS, \
140+
}
141+
142+
/**
143+
* @brief TinyUSB Task configuration structure initializer
144+
*
145+
* This macro is used to create a custom TinyUSB Task configuration structure.
146+
*
147+
* @param s Stack size of the task
148+
* @param p Task priority
149+
* @param a Task affinity (CPU core)
150+
* @param t Blocking timeout in milliseconds for tud_task_ext() function
151+
*/
152+
#define TINYUSB_TASK_CUSTOM_NON_BLOCKING(s, p, a, t) \
153+
(tinyusb_task_config_t) { \
154+
.size = (s), \
155+
.priority = (p), \
156+
.xCoreID = (a), \
157+
.blocking_timeout_ms = (t), \
130158
}
131159

132160
#ifdef __cplusplus

device/esp_tinyusb/tinyusb_task.c

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ static portMUX_TYPE tusb_task_lock = portMUX_INITIALIZER_UNLOCKED;
2424
#define TINYUSB_TASK_ENTER_CRITICAL() portENTER_CRITICAL(&tusb_task_lock)
2525
#define TINYUSB_TASK_EXIT_CRITICAL() portEXIT_CRITICAL(&tusb_task_lock)
2626

27+
// Timeout for taking the start/stop semaphore in milliseconds
28+
#define TINYUSB_TASK_SEM_TAKE_TIMEOUT_MS (5000)
29+
2730
#define TINYUSB_TASK_CHECK(cond, ret_val) ({ \
2831
if (!(cond)) { \
2932
return (ret_val); \
@@ -44,11 +47,14 @@ typedef struct {
4447
tusb_rhport_init_t rhport_init; /*!< USB Device RH port initialization configuration pointer */
4548
const tinyusb_desc_config_t *desc_cfg; /*!< USB Device descriptors configuration pointer */
4649
// Task related
47-
TaskHandle_t handle; /*!< Task handle */
48-
volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */
50+
TaskHandle_t handle; /*!< TinyUSB Device task handle */
51+
SemaphoreHandle_t start_stop_sem; /*!< Semaphore to start or stop the task */
52+
uint32_t blocking_timeout_ms; /*!< USB Device Task blocking timeout in milliseconds. */
4953
} tinyusb_task_ctx_t;
5054

51-
static bool _task_is_running = false; // Locking flag for the task, access only from the critical section
55+
#define TASK_NOTIF_STOP_BIT (1u << 0)
56+
57+
static bool _task_is_pending = false; // Locking flag for the task, access only from the critical section
5258
static tinyusb_task_ctx_t *p_tusb_task_ctx = NULL; // TinyUSB task context
5359

5460
/**
@@ -57,12 +63,19 @@ static tinyusb_task_ctx_t *p_tusb_task_ctx = NULL; // TinyUSB task context
5763
static void tinyusb_device_task(void *arg)
5864
{
5965
tinyusb_task_ctx_t *task_ctx = (tinyusb_task_ctx_t *)arg;
66+
bool stop_in_pending = false;
6067

6168
// Sanity check
6269
assert(task_ctx != NULL);
63-
assert(task_ctx->awaiting_handle != NULL);
70+
assert(task_ctx->start_stop_sem != NULL);
6471

6572
ESP_LOGD(TAG, "TinyUSB task started");
73+
ESP_LOGD(TAG, "\tPort %d", task_ctx->rhport);
74+
if (task_ctx->blocking_timeout_ms == UINT32_MAX) {
75+
ESP_LOGD(TAG, "\tBlocking timeout: MAX");
76+
} else {
77+
ESP_LOGD(TAG, "\tBlocking timeout: %" PRIu32 " ms", task_ctx->blocking_timeout_ms);
78+
}
6679

6780
if (tud_inited()) {
6881
ESP_LOGE(TAG, "TinyUSB stack is already initialized");
@@ -78,22 +91,55 @@ static void tinyusb_device_task(void *arg)
7891
}
7992

8093
TINYUSB_TASK_ENTER_CRITICAL();
81-
task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle
82-
p_tusb_task_ctx = task_ctx; // Save global task context pointer
94+
_task_is_pending = false; // Clear pending flag, task is running now
95+
task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle
96+
p_tusb_task_ctx = task_ctx; // Save global task context pointer
8397
TINYUSB_TASK_EXIT_CRITICAL();
8498

85-
xTaskNotifyGive(task_ctx->awaiting_handle); // Notify parent task that TinyUSB stack was started successfully
99+
// Notify parent task that TinyUSB stack was started successfully
100+
if (xSemaphoreGive(task_ctx->start_stop_sem) != pdTRUE) {
101+
// Fail only if semaphore queue is full, should never happen
102+
ESP_LOGE(TAG, "Unable to notify that TinyUSB stack was started");
103+
goto desc_free;
104+
}
86105

87-
while (1) { // RTOS forever loop
88-
tud_task();
106+
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)
107+
while (1) {
108+
uint32_t notif = 0;
109+
// Poll for notification bits
110+
if (xTaskNotifyWait(
111+
0, // don't clear any bits on entry
112+
TASK_NOTIF_STOP_BIT, // clear STOP bit on exit if it was set
113+
&notif,
114+
pdMS_TO_TICKS(notify_blocking_ms)
115+
) == pdTRUE && (notif & TASK_NOTIF_STOP_BIT)) {
116+
// Stop requested
117+
stop_in_pending = true;
118+
break;
119+
}
120+
// TinyUSB Device task
121+
tud_task_ext(task_ctx->blocking_timeout_ms, false);
89122
}
90123

124+
// Stop TinyUSB stack
125+
if (!tusb_deinit(task_ctx->rhport)) {
126+
ESP_LOGE(TAG, "Unable to deinit TinyUSB stack");
127+
}
128+
129+
ESP_LOGD(TAG, "TinyUSB task has stopped");
130+
91131
desc_free:
92132
tinyusb_descriptors_free();
93133
del:
94134
TINYUSB_TASK_ENTER_CRITICAL();
95-
_task_is_running = false; // Task is not running anymore
135+
_task_is_pending = false; // Clear pending flag
136+
p_tusb_task_ctx = NULL; // Clear global task context pointer
96137
TINYUSB_TASK_EXIT_CRITICAL();
138+
139+
if (stop_in_pending) {
140+
// Notify that TinyUSB stack was stopped
141+
xSemaphoreGive(task_ctx->start_stop_sem);
142+
}
97143
vTaskDelete(NULL);
98144
// No return needed here: vTaskDelete(NULL) does not return
99145
}
@@ -117,22 +163,25 @@ esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *c
117163

118164
TINYUSB_TASK_ENTER_CRITICAL();
119165
TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE); // Task shouldn't started
120-
TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_running, ESP_ERR_INVALID_STATE); // Task shouldn't be running
121-
_task_is_running = true; // Task is running flag, will be cleared in task in case of the error
166+
TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE); // Task shouldn't be pending
167+
_task_is_pending = true; // Task is pending flag, will be cleared in task
122168
TINYUSB_TASK_EXIT_CRITICAL();
123169

124170
esp_err_t ret;
125171
tinyusb_task_ctx_t *task_ctx = heap_caps_calloc(1, sizeof(tinyusb_task_ctx_t), MALLOC_CAP_DEFAULT);
126-
if (task_ctx == NULL) {
127-
return ESP_ERR_NO_MEM;
172+
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
173+
174+
if (task_ctx == NULL || sem == NULL) {
175+
ret = ESP_ERR_NO_MEM;
176+
goto no_task;
128177
}
129178

130-
task_ctx->awaiting_handle = xTaskGetCurrentTaskHandle(); // Save parent task handle
131-
task_ctx->handle = NULL; // TinyUSB task is not started
179+
task_ctx->start_stop_sem = sem; // TunyUSB Device task start stop semaphore
132180
task_ctx->rhport = port; // Peripheral port number
133181
task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device
134182
task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection
135183
task_ctx->desc_cfg = desc_cfg;
184+
task_ctx->blocking_timeout_ms = config->blocking_timeout_ms;
136185

137186
TaskHandle_t task_hdl = NULL;
138187
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
147196
if (task_hdl == NULL) {
148197
ESP_LOGE(TAG, "Create TinyUSB main task failed");
149198
ret = ESP_ERR_NOT_FINISHED;
150-
goto err;
199+
goto no_task;
151200
}
152201

153-
// Wait until the Task notify that port is active, 5 sec is more than enough
154-
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) {
202+
// Wait for "start" semophore with timeout
203+
if (xSemaphoreTake(task_ctx->start_stop_sem, pdMS_TO_TICKS(TINYUSB_TASK_SEM_TAKE_TIMEOUT_MS)) != pdTRUE) {
155204
ESP_LOGE(TAG, "Task wasn't able to start TinyUSB stack");
156205
ret = ESP_ERR_TIMEOUT;
157206
goto err;
158207
}
159208

209+
// Check the global task context pointer and pending state
210+
TINYUSB_TASK_ENTER_CRITICAL();
211+
TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE);
212+
TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == task_ctx, ESP_ERR_NOT_FOUND);
213+
TINYUSB_TASK_EXIT_CRITICAL();
214+
160215
return ESP_OK;
161216

217+
no_task:
218+
TINYUSB_TASK_ENTER_CRITICAL();
219+
_task_is_pending = false; // Clear pending flag
220+
TINYUSB_TASK_EXIT_CRITICAL();
162221
err:
222+
if (sem) {
223+
vSemaphoreDelete(sem);
224+
}
163225
heap_caps_free(task_ctx);
164226
return ret;
165227
}
@@ -168,20 +230,43 @@ esp_err_t tinyusb_task_stop(void)
168230
{
169231
TINYUSB_TASK_ENTER_CRITICAL();
170232
TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx != NULL, ESP_ERR_INVALID_STATE);
233+
TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE); // Task shouldn't be pending
171234
tinyusb_task_ctx_t *task_ctx = p_tusb_task_ctx;
172-
p_tusb_task_ctx = NULL;
173-
_task_is_running = false;
235+
_task_is_pending = true; // Task is pending to stop
174236
TINYUSB_TASK_EXIT_CRITICAL();
175237

176-
if (task_ctx->handle != NULL) {
238+
if (task_ctx->blocking_timeout_ms != UINT32_MAX) {
239+
// Request TinyUSB task to stop
240+
if (xTaskNotify(task_ctx->handle, TASK_NOTIF_STOP_BIT, eSetBits) != pdPASS) {
241+
ESP_LOGE(TAG, "Unable to notify TinyUSB task to stop");
242+
return ESP_ERR_INVALID_STATE;
243+
}
244+
// Wait for "stop" semaphore with timeout
245+
if (xSemaphoreTake(task_ctx->start_stop_sem, pdMS_TO_TICKS(TINYUSB_TASK_SEM_TAKE_TIMEOUT_MS)) != pdTRUE) {
246+
ESP_LOGE(TAG, "Timeout while TinyUSB task stop");
247+
return ESP_ERR_TIMEOUT;
248+
}
249+
// Check the global task context pointer and pending state
250+
TINYUSB_TASK_ENTER_CRITICAL();
251+
TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_pending, ESP_ERR_INVALID_STATE);
252+
TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE);
253+
TINYUSB_TASK_EXIT_CRITICAL();
254+
} else {
255+
// TinyUSB Task is in forever loop, delete the task the old way
177256
vTaskDelete(task_ctx->handle);
178257
task_ctx->handle = NULL;
258+
// Free descriptors
259+
tinyusb_descriptors_free();
260+
// Deinit TinyUSB Stask
261+
ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_INVALID_STATE, TAG, "Unable to deinit TinyUSB stack");
262+
// Clean global context and pending state
263+
TINYUSB_TASK_ENTER_CRITICAL();
264+
_task_is_pending = false;
265+
p_tusb_task_ctx = NULL;
266+
TINYUSB_TASK_EXIT_CRITICAL();
179267
}
180-
// Free descriptors
181-
tinyusb_descriptors_free();
182-
// Stop TinyUSB stack
183-
ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack");
184-
// Cleanup
268+
269+
vSemaphoreDelete(task_ctx->start_stop_sem);
185270
heap_caps_free(task_ctx);
186271
return ESP_OK;
187272
}

0 commit comments

Comments
 (0)