mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 10:40:47 +03:00
feat(esp_timer): Adds blocking stop function
This commit is contained in:
committed by
BOT
parent
db31a63351
commit
0382b59e21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -38,13 +38,13 @@
|
||||
typedef enum {
|
||||
FL_ISR_DISPATCH_METHOD = (1 << 0), //!< 0=Callback is called from timer task, 1=Callback is called from timer ISR
|
||||
FL_SKIP_UNHANDLED_EVENTS = (1 << 1), //!< 0=NOT skip unhandled events for periodic timers, 1=Skip unhandled events for periodic timers
|
||||
FL_CALLBACK_IS_RUNNING = (1 << 2), //!< 0=Callback is NOT running, 1=Callback is running
|
||||
FL_CALLBACK_IS_RUNNING = (1 << 2), //!< 0=Callback is NOT running, 1=Callback is running
|
||||
} flags_t;
|
||||
|
||||
struct esp_timer {
|
||||
uint64_t alarm;
|
||||
uint64_t period: 56;
|
||||
flags_t flags: 8;
|
||||
volatile flags_t flags: 8;
|
||||
union {
|
||||
esp_timer_cb_t callback;
|
||||
uint32_t event_id;
|
||||
@@ -62,10 +62,11 @@ struct esp_timer {
|
||||
|
||||
static inline bool is_initialized(void);
|
||||
static esp_err_t timer_insert(esp_timer_handle_t timer, bool without_update_alarm);
|
||||
static esp_err_t timer_remove(esp_timer_handle_t timer);
|
||||
static void timer_remove(esp_timer_handle_t timer);
|
||||
static bool timer_armed(esp_timer_handle_t timer);
|
||||
static void timer_list_lock(esp_timer_dispatch_t timer_type);
|
||||
static void timer_list_unlock(esp_timer_dispatch_t timer_type);
|
||||
static esp_err_t timer_restart(esp_timer_handle_t timer, uint64_t timeout_us, uint64_t alarm_us);
|
||||
|
||||
#if WITH_PROFILING
|
||||
static void timer_insert_inactive(esp_timer_handle_t timer);
|
||||
@@ -134,6 +135,11 @@ esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
|
||||
* in IRAM when PM_SLP_IRAM_OPT = y and ESP_TASK_WDT USE ESP_TIMER = y.
|
||||
*/
|
||||
esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us)
|
||||
{
|
||||
return timer_restart(timer, timeout_us, 0);
|
||||
}
|
||||
|
||||
static esp_err_t ESP_TIMER_IRAM_ATTR timer_restart(esp_timer_handle_t timer, uint64_t timeout_us, uint64_t first_alarm_us)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
@@ -154,24 +160,23 @@ esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_restart(esp_timer_handle_t timer, uint64
|
||||
/* We need to remove the timer to the list of timers and reinsert it at
|
||||
* the right position. In fact, the timers are sorted by their alarm value
|
||||
* (earliest first) */
|
||||
ret = timer_remove(timer);
|
||||
timer_remove(timer);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
/* Two cases here:
|
||||
* - if the alarm was a periodic one, i.e. `period` is not 0, the given timeout_us becomes the new period
|
||||
* - if the alarm was a one-shot one, i.e. `period` is 0, it remains non-periodic. */
|
||||
if (period != 0) {
|
||||
/* Remove function got rid of the alarm and period fields, restore them */
|
||||
const uint64_t new_period = MAX(timeout_us, esp_timer_impl_get_min_period_us());
|
||||
timer->alarm = now + new_period;
|
||||
timer->period = new_period;
|
||||
} else {
|
||||
/* The new one-shot alarm shall be triggered timeout_us after the current time */
|
||||
timer->alarm = now + timeout_us;
|
||||
timer->period = 0;
|
||||
}
|
||||
ret = timer_insert(timer, false);
|
||||
/* Two cases here:
|
||||
* - if the alarm was a periodic one, i.e. `period` is not 0, the given timeout_us becomes the new period
|
||||
* - if the alarm was a one-shot one, i.e. `period` is 0, it remains non-periodic. */
|
||||
if (period != 0) {
|
||||
/* Remove function got rid of the alarm and period fields, restore them */
|
||||
const uint64_t min_period = esp_timer_impl_get_min_period_us();
|
||||
const uint64_t new_period = MAX(timeout_us, min_period);
|
||||
timer->alarm = (first_alarm_us != 0) ? first_alarm_us : now + new_period;
|
||||
timer->period = new_period;
|
||||
} else {
|
||||
/* The new one-shot alarm shall be triggered timeout_us after the current time */
|
||||
timer->alarm = (first_alarm_us != 0) ? first_alarm_us : now + timeout_us;
|
||||
timer->period = 0;
|
||||
}
|
||||
ret = timer_insert(timer, false);
|
||||
|
||||
timer_list_unlock(dispatch_method);
|
||||
|
||||
@@ -250,7 +255,7 @@ esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_stop(esp_timer_handle_t timer)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
esp_err_t err;
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
timer_list_lock(dispatch_method);
|
||||
|
||||
@@ -258,12 +263,88 @@ esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_stop(esp_timer_handle_t timer)
|
||||
if (!timer_armed(timer)) {
|
||||
err = ESP_ERR_INVALID_STATE;
|
||||
} else {
|
||||
err = timer_remove(timer);
|
||||
timer_remove(timer);
|
||||
}
|
||||
timer_list_unlock(dispatch_method);
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline bool is_callback_running(esp_timer_handle_t timer, esp_timer_dispatch_t dispatch_method)
|
||||
{
|
||||
timer_list_lock(dispatch_method);
|
||||
bool callback_running = (timer != NULL) && (timer->flags & FL_CALLBACK_IS_RUNNING) != 0;
|
||||
timer_list_unlock(dispatch_method);
|
||||
return callback_running;
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_stop_blocking(esp_timer_handle_t timer, uint32_t timeout_ticks)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!is_initialized()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
|
||||
timer_list_lock(dispatch_method);
|
||||
|
||||
bool callback_running = (timer->flags & FL_CALLBACK_IS_RUNNING) != 0;
|
||||
/* Check if the timer is armed once the list is locked to avoid a data race */
|
||||
if (timer_armed(timer)) {
|
||||
timer_remove(timer);
|
||||
}
|
||||
|
||||
timer_list_unlock(dispatch_method);
|
||||
|
||||
if (callback_running) {
|
||||
// timer_process_alarm() releases the timer list lock while executing the callback.
|
||||
// So it is possible that timer is disarmed but its callback is still running.
|
||||
// To guarantee that the callback will not run after esp_timer_stop_blocking(),
|
||||
// we need to wait for the callback to complete here.
|
||||
|
||||
// In ISR context: do not wait to avoid blocking
|
||||
if (xPortInIsrContext()) {
|
||||
return ESP_ERR_NOT_FINISHED;
|
||||
}
|
||||
|
||||
if (xTaskGetCurrentTaskHandle() == s_timer_task) {
|
||||
// Called from the esp_timer task context (i.e., the callback owner is a TASK-dispatch timer).
|
||||
// Concurrency model:
|
||||
// - TASK-dispatch callbacks are executed by a single esp_timer task and are strictly serialized.
|
||||
// Therefore, only one TASK callback can be running at any time.
|
||||
// - If CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is enabled,
|
||||
// ISR-dispatch callbacks and TASK-dispatch callbacks may be running at the same time.
|
||||
if (dispatch_method == ESP_TIMER_TASK) {
|
||||
// Only one ESP_TIMER_TASK callback can be running at any time.
|
||||
// So we are stopping the timer from its own callback context:
|
||||
// the callback will complete naturally after this function returns.
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Stopping a running ISR-dispatch timer from a foreign callback:
|
||||
// we cannot wait for ISR context to complete, and blocking the esp_timer task would
|
||||
// stall TASK-dispatch callbacks. Report that the timer callback is still running.
|
||||
return ESP_ERR_NOT_FINISHED;
|
||||
}
|
||||
|
||||
TickType_t start_time = xTaskGetTickCount();
|
||||
while (is_callback_running(timer, dispatch_method)) {
|
||||
if (timeout_ticks != portMAX_DELAY) {
|
||||
TickType_t elapsed = xTaskGetTickCount() - start_time;
|
||||
if (elapsed >= timeout_ticks) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
@@ -320,7 +401,8 @@ static ESP_TIMER_IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer, bool
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static ESP_TIMER_IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
|
||||
// It should be always called with the timer list locked
|
||||
static ESP_TIMER_IRAM_ATTR void timer_remove(esp_timer_handle_t timer)
|
||||
{
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
esp_timer_handle_t first_timer = LIST_FIRST(&s_timers[dispatch_method]);
|
||||
@@ -338,7 +420,6 @@ static ESP_TIMER_IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
|
||||
#if WITH_PROFILING
|
||||
timer_insert_inactive(timer);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if WITH_PROFILING
|
||||
@@ -765,5 +846,14 @@ bool ESP_TIMER_IRAM_ATTR esp_timer_is_active(esp_timer_handle_t timer)
|
||||
if (timer == NULL) {
|
||||
return false;
|
||||
}
|
||||
return timer_armed(timer) || (timer->flags & FL_CALLBACK_IS_RUNNING);
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
|
||||
timer_list_lock(dispatch_method);
|
||||
// Timer is active if it is armed or its callback is currently running
|
||||
// After esp_timer_stop() timer is disarmed, but its callback may still be running
|
||||
bool active = (timer_armed(timer) && timer->event_id != EVENT_ID_DELETE_TIMER)
|
||||
|| ((timer->flags & FL_CALLBACK_IS_RUNNING) != 0);
|
||||
timer_list_unlock(dispatch_method);
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user