diff --git a/components/esp_pm/linker.lf b/components/esp_pm/linker.lf index 91237ef1b5..88489039eb 100644 --- a/components/esp_pm/linker.lf +++ b/components/esp_pm/linker.lf @@ -32,9 +32,11 @@ entries: freertos_hooks:esp_vApplicationIdleHook (noflash) if PM_SLP_IRAM_OPT = y: task_wdt:idle_hook_cb (noflash) - task_wdt:esp_task_wdt_reset (noflash) - task_wdt:find_task_in_twdt_list (noflash) task_wdt:reset_hw_timer (noflash) + task_wdt:find_entry_and_check_all_reset (noflash) + task_wdt:find_entry_from_task_handle_and_check_all_reset (noflash) + task_wdt:esp_task_wdt_reset (noflash) + task_wdt:esp_task_wdt_reset_user (noflash) [mapping:esp_timer_pm] archive: libesp_timer.a diff --git a/components/esp_system/include/esp_task_wdt.h b/components/esp_system/include/esp_task_wdt.h index e5a08eabe8..3ca1b86b00 100644 --- a/components/esp_system/include/esp_task_wdt.h +++ b/components/esp_system/include/esp_task_wdt.h @@ -6,6 +6,7 @@ #pragma once +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_err.h" @@ -14,31 +15,45 @@ extern "C" { #endif +/** + * @brief Task Watchdog Timer (TWDT) configuration structure + */ +typedef struct { + uint32_t timeout_ms; /**< TWDT timeout duration in milliseconds */ + uint32_t idle_core_mask; /**< Mask of the cores who's idle task should be subscribed on initialization */ + bool trigger_panic; /**< Trigger panic when timeout occurs */ +} esp_task_wdt_config_t; + +/** + * @brief Task Watchdog Timer (TWDT) user handle + */ +typedef struct esp_task_wdt_user_handle_s * esp_task_wdt_user_handle_t; + /** * @brief Initialize the Task Watchdog Timer (TWDT) * * This function configures and initializes the TWDT. If the TWDT is already initialized when this function is called, - * this function will update the TWDT's timeout period and panic configurations instead. After initializing the TWDT, - * any task can elect to be watched by the TWDT by subscribing to it using esp_task_wdt_add(). + * this function will update the TWDT's current configuration. This funciton will also subscribe the idle tasks if + * configured to do so. For other tasks, users can subscribe them using esp_task_wdt_add() or esp_task_wdt_add_user(). * - * @note esp_task_wdt_init() must only be called after the scheduler started - * @param[in] timeout Timeout period of TWDT in seconds - * @param[in] panic Flag that controls whether the panic handler will be executed when the TWDT times out + * @note esp_task_wdt_init() must only be called after the scheduler is started + * @param[in] config Configuration structure * @return * - ESP_OK: Initialization was successful - * - ESP_ERR_NO_MEM: Initialization failed due insufficient memory + * - Other: Failed to initialize TWDT */ -esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic); +esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config); /** * @brief Deinitialize the Task Watchdog Timer (TWDT) * - * This function will deinitialize the TWDT. Calling this function whilst tasks are still subscribed to the TWDT, or - * when the TWDT is already deinitialized, will result in an error code being returned. + * This function will deinitialize the TWDT, and unsubscribe any idle tasks. Calling this function whilst other tasks + * are still subscribed to the TWDT, or when the TWDT is already deinitialized, will result in an error code being + * returned. * * @return - * - ESP_OK: TWDT successfully deinitialized - * - ESP_ERR_INVALID_STATE: TWDT was never initialized, or tasks are still subscribed + * - ESP_OK: TWDT successfully deinitialized + * - Other: Failed to deinitialize TWDT */ esp_err_t esp_task_wdt_deinit(void); @@ -46,66 +61,94 @@ esp_err_t esp_task_wdt_deinit(void); * @brief Subscribe a task to the Task Watchdog Timer (TWDT) * * This function subscribes a task to the TWDT. Each subscribed task must periodically call esp_task_wdt_reset() to - * prevent the TWDT from elapsing its timeout period. Failure to do so will result in a TWDT timeout. If the task being - * subscribed is one of the Idle Tasks, this function will automatically enable esp_task_wdt_reset() to called from the - * Idle Hook of the Idle Task. + * prevent the TWDT from elapsing its timeout period. Failure to do so will result in a TWDT timeout. * - * Calling this function whilst the TWDT is uninitialized or attempting to subscribe an already subscribed task will - * result in an error code being returned. - * - * @param handle Handle of the task. Input NULL to subscribe the current running task to the TWDT + * @param task_handle Handle of the task. Input NULL to subscribe the current running task to the TWDT * @return * - ESP_OK: Successfully subscribed the task to the TWDT - * - ESP_ERR_INVALID_ARG: The task is already subscribed - * - ESP_ERR_NO_MEM: Could not subscribe the insufficient memory - * - ESP_ERR_INVALID_STATE: TWDT was never initialized + * - Other: Failed to subscribe task */ -esp_err_t esp_task_wdt_add(TaskHandle_t handle); +esp_err_t esp_task_wdt_add(TaskHandle_t task_handle); + +/** + * @brief Subscribe a user to the Task Watchdog Timer (TWDT) + * + * This function subscribes a user to the TWDT. A user of the TWDT is usually a function that needs to run + * periodically. Each subscribed user must periodically call esp_task_wdt_reset_user() to prevent the TWDT from elapsing + * its timeout period. Failure to do so will result in a TWDT timeout. + * + * @param[in] user_name String to identify the user + * @param[out] user_handle_ret Handle of the user + * @return + * - ESP_OK: Successfully subscribed the user to the TWDT + * - Other: Failed to subscribe user + */ +esp_err_t esp_task_wdt_add_user(const char *user_name, esp_task_wdt_user_handle_t *user_handle_ret); /** * @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently running task * * This function will reset the TWDT on behalf of the currently running task. Each subscribed task must periodically * call this function to prevent the TWDT from timing out. If one or more subscribed tasks fail to reset the TWDT on - * their own behalf, a TWDT timeout will occur. If the IDLE tasks have been subscribed to the TWDT, they will - * automatically call this function from their idle hooks. Calling this function from a task that has not subscribed to - * the TWDT, or when the TWDT is uninitialized will result in an error code being returned. + * their own behalf, a TWDT timeout will occur. * * @return * - ESP_OK: Successfully reset the TWDT on behalf of the currently running task - * - ESP_ERR_NOT_FOUND: The task is not subscribed - * - ESP_ERR_INVALID_STATE: TWDT was never initialized + * - Other: Failed to reset */ esp_err_t esp_task_wdt_reset(void); +/** + * @brief Reset the Task Watchdog Timer (TWDT) on behalf of a user + * + * This function will reset the TWDT on behalf of a user. Each subscribed user must periodically call this function to + * prevent the TWDT from timing out. If one or more subscribed users fail to reset the TWDT on their own behalf, a TWDT + * timeout will occur. + * + * @param[in] user_handle User handle + * - ESP_OK: Successfully reset the TWDT on behalf of the user + * - Other: Failed to reset + */ +esp_err_t esp_task_wdt_reset_user(esp_task_wdt_user_handle_t user_handle); + /** * @brief Unsubscribes a task from the Task Watchdog Timer (TWDT) * * This function will unsubscribe a task from the TWDT. After being unsubscribed, the task should no longer call - * esp_task_wdt_reset(). If the task is an IDLE task, this function will automatically disable the calling of - * esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the TWDT is uninitialized or attempting to - * unsubscribe an already unsubscribed task from the TWDT will result in an error code being returned. + * esp_task_wdt_reset(). * - * @param[in] handle Handle of the task. Input NULL to unsubscribe the current running task. + * @param[in] task_handle Handle of the task. Input NULL to unsubscribe the current running task. * @return * - ESP_OK: Successfully unsubscribed the task from the TWDT - * - ESP_ERR_NOT_FOUND: The task is not subscribed - * - ESP_ERR_INVALID_STATE: TWDT was never initialized + * - Other: Failed to unsubscribe task */ -esp_err_t esp_task_wdt_delete(TaskHandle_t handle); +esp_err_t esp_task_wdt_delete(TaskHandle_t task_handle); + +/** + * @brief Unsubscribes a user from the Task Watchdog Timer (TWDT) + * + * This function will unsubscribe a user from the TWDT. After being unsubscribed, the user should no longer call + * esp_task_wdt_reset_user(). + * + * @param[in] user_handle User handle + * @return + * - ESP_OK: Successfully unsubscribed the user from the TWDT + * - Other: Failed to unsubscribe user + */ +esp_err_t esp_task_wdt_delete_user(esp_task_wdt_user_handle_t user_handle); /** * @brief Query whether a task is subscribed to the Task Watchdog Timer (TWDT) * * This function will query whether a task is currently subscribed to the TWDT, or whether the TWDT is initialized. * - * @param[in] handle Handle of the task. Input NULL to query the current running task. + * @param[in] task_handle Handle of the task. Input NULL to query the current running task. * @return: * - ESP_OK: The task is currently subscribed to the TWDT * - ESP_ERR_NOT_FOUND: The task is not subscribed * - ESP_ERR_INVALID_STATE: TWDT was never initialized */ -esp_err_t esp_task_wdt_status(TaskHandle_t handle); +esp_err_t esp_task_wdt_status(TaskHandle_t task_handle); #ifdef __cplusplus } diff --git a/components/esp_system/task_wdt.c b/components/esp_system/task_wdt.c index 39f8fe7bdb..36fbeb525b 100644 --- a/components/esp_system/task_wdt.c +++ b/components/esp_system/task_wdt.c @@ -5,31 +5,24 @@ */ #include -#include -#include -#include #include +#include +#include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/queue.h" -#include "freertos/semphr.h" -#include -#include +#include "hal/wdt_hal.h" #include "esp_err.h" -#include "esp_check.h" -#include "esp_intr_alloc.h" #include "esp_attr.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_intr_alloc.h" #include "esp_debug_helpers.h" #include "esp_freertos_hooks.h" -#include "soc/timer_periph.h" -#include "esp_log.h" -#include "esp_private/periph_ctrl.h" #include "esp_task_wdt.h" +#include "esp_private/periph_ctrl.h" #include "esp_private/system_internal.h" #include "esp_private/crosscore_int.h" -#include "hal/timer_types.h" -#include "hal/wdt_hal.h" // --------------------------------------------------- Definitions ----------------------------------------------------- @@ -42,11 +35,14 @@ // ---------------------- Typedefs ------------------------- -// Structure used for each subscribed task +/** + * @brief Structure used for each subscribed task + */ typedef struct twdt_entry twdt_entry_t; struct twdt_entry { SLIST_ENTRY(twdt_entry) slist_entry; - TaskHandle_t task_handle; + TaskHandle_t task_handle; // NULL if user entry + const char *user_name; // NULL if task entry bool has_reset; }; @@ -55,6 +51,7 @@ typedef struct twdt_obj twdt_obj_t; struct twdt_obj { wdt_hal_context_t hal; SLIST_HEAD(entry_list_head, twdt_entry) entries_slist; + uint32_t idle_core_mask; // Current core's who's idle tasks are subscribed bool panic; // Flag to trigger panic when TWDT times out intr_handle_t intr_handle; }; @@ -65,16 +62,97 @@ static const char *TAG = "task_wdt"; static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED; static twdt_obj_t *p_twdt_obj = NULL; +#if CONFIG_FREERTOS_SMP +#define CORE_USER_NAME_LEN 8 // Long enough for "CPU XXX" +static esp_task_wdt_user_handle_t core_user_handles[portNUM_PROCESSORS] = {NULL}; +static char core_user_names[portNUM_PROCESSORS][CORE_USER_NAME_LEN]; +#endif + // ----------------------------------------------------- Private ------------------------------------------------------- +// ---------------------- Callbacks ------------------------ + /** - * @brief Find an entry from its task handle, and checks if all other entries have been reset + * @brief User ISR callback placeholder + * + * This function is called by task_wdt_isr function (ISR for when TWDT times out). It can be redefined in user code to + * handle TWDT events. + * + * @note It has the same limitations as the interrupt function. Do not use ESP_LOGI functions inside. + */ +void __attribute__((weak)) esp_task_wdt_isr_user_handler(void) +{ + +} + +/** + * @brief Idle hook callback + * + * Idle hook callback called by the idle tasks to feed the TWDT + * + * @return Whether the idle tasks should continue idling + */ +static bool idle_hook_cb(void) +{ +#if CONFIG_FREERTOS_SMP + esp_task_wdt_reset_user(core_user_handles[xPortGetCoreID()]); +#else + esp_task_wdt_reset(); +#endif + return true; +} + +// ----------------------- Helpers ------------------------- + +/** + * @brief Reset hardware timer and reset flags of each entry + */ +static void reset_hw_timer(void) +{ + // All tasks have reset; time to reset the hardware timer. + wdt_hal_write_protect_disable(&p_twdt_obj->hal); + wdt_hal_feed(&p_twdt_obj->hal); + wdt_hal_write_protect_enable(&p_twdt_obj->hal); + //Clear the has_reset flag in each entry + twdt_entry_t *entry; + SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) { + entry->has_reset = false; + } +} + +/** + * @brief Checks whether a user entry exists and if all other entries have been reset + * + * @param[in] user_entry User entry + * @param[out] all_reset Whether all entries have been reset + * @return Whether the user entry exists + */ +static bool find_entry_and_check_all_reset(twdt_entry_t *user_entry, bool *all_reset) +{ + bool found_user_entry = false; + bool found_non_reset = false; + + twdt_entry_t *entry; + SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) { + if (entry == user_entry) { + found_user_entry = true; + } else if (entry->has_reset == false) { + found_non_reset = true; + } + } + + *all_reset = !found_non_reset; + return found_user_entry; +} + +/** + * @brief Find whether a task entry exists, and checks if all other entries have been reset * * @param[in] handle Task handle * @param[out] all_reset Whether all entries have been reset - * @return Entry, or NULL if not found + * @return Task entry, or NULL if not found */ -static twdt_entry_t *find_entry_from_handle_and_check_all_reset(TaskHandle_t handle, bool *all_reset) +static twdt_entry_t *find_entry_from_task_handle_and_check_all_reset(TaskHandle_t handle, bool *all_reset) { twdt_entry_t *target = NULL; bool found_non_reset = false; @@ -93,52 +171,153 @@ static twdt_entry_t *find_entry_from_handle_and_check_all_reset(TaskHandle_t han } /** - * @brief Reset hardware timer and entry flags + * @brief Create a task/user entry and add it to the task WDT + * + * @param[in] is_task Whether the entry is a task entry or user entry + * @param[in] entry_data Data associated with the entry (either a task handle or user entry name) + * @param[out] entry_ret Pointer to created entry + * @return ESP_OK if entry was added, failure otherwise */ -static void reset_hw_timer(void) +static esp_err_t add_entry(bool is_task, void *entry_data, twdt_entry_t **entry_ret) { - // All tasks have reset; time to reset the hardware timer. - wdt_hal_write_protect_disable(&p_twdt_obj->hal); - wdt_hal_feed(&p_twdt_obj->hal); - wdt_hal_write_protect_enable(&p_twdt_obj->hal); - //C lear the has_reset flag in each entry + esp_err_t ret; + + // Allocate entry object + twdt_entry_t *entry = calloc(1, sizeof(twdt_entry_t)); + if (entry == NULL) { + return ESP_ERR_NO_MEM; + } + if (is_task) { + entry->task_handle = (TaskHandle_t)entry_data; + } else { + entry->user_name = (const char *)entry_data; + } + + portENTER_CRITICAL(&spinlock); + // Check TWDT state + ESP_GOTO_ON_FALSE_ISR((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, state_err, TAG, "task watchdog was never initialized"); + // Check if the task is an entry, and if all entries have been reset + bool all_reset; + if (is_task) { + twdt_entry_t *entry_found = find_entry_from_task_handle_and_check_all_reset(entry->task_handle, &all_reset); + ESP_GOTO_ON_FALSE_ISR((entry_found == NULL), ESP_ERR_INVALID_ARG, state_err, TAG, "task is already subscribed"); + } else { + bool entry_found = find_entry_and_check_all_reset(entry, &all_reset); + ESP_GOTO_ON_FALSE_ISR(!entry_found, ESP_ERR_INVALID_ARG, state_err, TAG, "user is already subscribed"); + } + // Add entry to list + SLIST_INSERT_HEAD(&p_twdt_obj->entries_slist, entry, slist_entry); + if (all_reset) { //Reset hardware timer if all other tasks in list have reset in + reset_hw_timer(); + } + portEXIT_CRITICAL(&spinlock); + *entry_ret = entry; + return ESP_OK; + +state_err: + portEXIT_CRITICAL(&spinlock); + free(entry); + return ret; +} + +/** + * @brief Delete a task/user entry + * + * @param[in] is_task Whether the entry is a task entry or user entry + * @param[in] entry_data Data associated with the entry (either a task handle or user entry name) + * @return ESP_OK if entry was deleted, failure otherwise + */ +static esp_err_t delete_entry(bool is_task, void *entry_data) +{ + esp_err_t ret; + + portENTER_CRITICAL(&spinlock); + // Check TWDT state + ESP_GOTO_ON_FALSE_ISR((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized"); + // Find entry for task + bool all_reset; twdt_entry_t *entry; - SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) { - entry->has_reset = false; + if (is_task) { + entry = find_entry_from_task_handle_and_check_all_reset((TaskHandle_t)entry_data, &all_reset); + ESP_GOTO_ON_FALSE_ISR((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found"); + } else { + entry = (twdt_entry_t *)entry_data; + bool entry_found = find_entry_and_check_all_reset(entry, &all_reset); + ESP_GOTO_ON_FALSE_ISR(entry_found, ESP_ERR_NOT_FOUND, err, TAG, "user not found"); + } + // Remove entry + SLIST_REMOVE(&p_twdt_obj->entries_slist, entry, twdt_entry, slist_entry); + // Reset hardware timer if all remaining tasks have reset + if (all_reset) { + reset_hw_timer(); + } + portEXIT_CRITICAL(&spinlock); + free(entry); + return ESP_OK; + +err: + portEXIT_CRITICAL(&spinlock); + return ret; +} + +/** + * @brief Unsubscribe the idle tasks of one or more cores + * + * @param core_mask + */ +static void unsubscribe_idle(uint32_t core_mask) +{ + int core_num = 0; + while (core_mask != 0) { + if (core_mask & 0x1) { +#if CONFIG_FREERTOS_SMP + assert(core_user_handles[core_num]); + ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, core_num)); + ESP_ERROR_CHECK(esp_task_wdt_delete_user(core_user_handles[core_num])); + core_user_handles[core_num] = NULL; +#else + TaskHandle_t idle_task_handle = xTaskGetIdleTaskHandleForCPU(core_num); + assert(idle_task_handle); + esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, core_num); + ESP_ERROR_CHECK(esp_task_wdt_delete(idle_task_handle)); +#endif + } + core_mask >>= 1; + core_num++; } } -/** - * @brief Idle hook callback - * - * Idle hook callback for Idle Tasks to reset the TWDT. This callback will only be registered to the Idle Hook of a - * particular core when the corresponding Idle Task subscribes to the TWDT. - * - * @return Always returns true - */ -static bool idle_hook_cb(void) -{ - esp_task_wdt_reset(); - return true; -} /** - * @brief User ISR callback placeholder + * @brief Subscribes the idle tasks of one or more cores * - * This function is called by task_wdt_isr function (ISR for when TWDT times out). It can be redefined in user code to - * handle twdt events. - * - * @note It has the same limitations as the interrupt function. Do not use ESP_LOGI functions inside. + * @param core_mask Bit mask of cores to subscribe */ -void __attribute__((weak)) esp_task_wdt_isr_user_handler(void) +static void subscribe_idle(uint32_t core_mask) { - + int core_num = 0; + while (core_mask != 0) { + if (core_mask & 0x1) { +#if CONFIG_FREERTOS_SMP + snprintf(core_user_names[core_num], CORE_USER_NAME_LEN, "CPU %d", (uint8_t)core_num); + ESP_ERROR_CHECK(esp_task_wdt_add_user((const char *)core_user_names[core_num], &core_user_handles[core_num])); + ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, core_num)); +#else + TaskHandle_t idle_task_handle = xTaskGetIdleTaskHandleForCPU(core_num); + assert(idle_task_handle); + ESP_ERROR_CHECK(esp_task_wdt_add(idle_task_handle)); + ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, core_num)); +#endif + } + core_mask >>= 1; + core_num++; + } } /** * @brief TWDT timeout ISR function * - * Tee ISR checks which entries have not been reset, prints some debugging information, and triggers a panic if + * The ISR checks which entries have not been reset, prints some debugging information, and triggers a panic if * configured to do so. * * @param arg ISR argument @@ -161,20 +340,29 @@ static void task_wdt_isr(void *arg) However, TWDT timeouts count as fatal errors, thus reporting the fatal error is considered more important than minimizing interrupt latency. Thus we allow logging in critical sections in this narrow case. */ - ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks did not reset the watchdog in time:"); + ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:"); twdt_entry_t *entry; SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) { if (!entry->has_reset) { - BaseType_t task_affinity = xTaskGetAffinity(entry->task_handle); - const char *cpu; - if (task_affinity == 0) { - cpu = DRAM_STR("CPU 0"); - } else if (task_affinity == 1) { - cpu = DRAM_STR("CPU 1"); + if (entry->task_handle) { +#if CONFIG_FREERTOS_SMP + UBaseType_t uxCoreAffinity = vTaskCoreAffinityGet(entry->task_handle); + ESP_EARLY_LOGE(TAG, " - %s (0x%x)", pcTaskGetName(entry->task_handle), uxCoreAffinity); +#else + BaseType_t task_affinity = xTaskGetAffinity(entry->task_handle); + const char *cpu; + if (task_affinity == 0) { + cpu = DRAM_STR("CPU 0"); + } else if (task_affinity == 1) { + cpu = DRAM_STR("CPU 1"); + } else { + cpu = DRAM_STR("CPU 0/1"); + } + ESP_EARLY_LOGE(TAG, " - %s (%s)", pcTaskGetName(entry->task_handle), cpu); +#endif } else { - cpu = DRAM_STR("CPU 0/1"); + ESP_EARLY_LOGE(TAG, " - %s", entry->user_name); } - ESP_EARLY_LOGE(TAG, " - %s (%s)", pcTaskGetName(entry->task_handle), cpu); } } ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:")); @@ -203,13 +391,14 @@ static void task_wdt_isr(void *arg) #endif #endif } - } // ----------------------------------------------------- Public -------------------------------------------------------- -esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic) +esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config) { + ESP_RETURN_ON_FALSE((config != NULL && config->idle_core_mask < (1 << portNUM_PROCESSORS)), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + ESP_RETURN_ON_FALSE(p_twdt_obj == NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT already initialized"); esp_err_t ret; twdt_obj_t *obj = NULL; @@ -218,7 +407,7 @@ esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic) obj = calloc(1, sizeof(twdt_obj_t)); ESP_GOTO_ON_FALSE((obj != NULL), ESP_ERR_NO_MEM, err, TAG, "insufficient memory"); SLIST_INIT(&obj->entries_slist); - obj->panic = panic; + obj->panic = config->trigger_panic; ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &obj->intr_handle)); portENTER_CRITICAL(&spinlock); // Configure hardware timer @@ -228,16 +417,29 @@ esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic) p_twdt_obj = obj; portEXIT_CRITICAL(&spinlock); } + portENTER_CRITICAL(&spinlock); wdt_hal_write_protect_disable(&p_twdt_obj->hal); // Configure 1st stage timeout and behavior - wdt_hal_config_stage(&p_twdt_obj->hal, WDT_STAGE0, timeout * (1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); + wdt_hal_config_stage(&p_twdt_obj->hal, WDT_STAGE0, config->timeout_ms * (1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); // Configure 2nd stage timeout and behavior - wdt_hal_config_stage(&p_twdt_obj->hal, WDT_STAGE1, timeout * (2 * 1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM); + wdt_hal_config_stage(&p_twdt_obj->hal, WDT_STAGE1, config->timeout_ms * (2 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM); // Enable the WDT wdt_hal_enable(&p_twdt_obj->hal); wdt_hal_write_protect_enable(&p_twdt_obj->hal); + // Update which core's idle tasks are subscribed + uint32_t old_core_mask = p_twdt_obj->idle_core_mask; + p_twdt_obj->idle_core_mask = config->idle_core_mask; portEXIT_CRITICAL(&spinlock); + if (old_core_mask) { + // Unsubscribe all previously watched core idle tasks + unsubscribe_idle(old_core_mask); + } + if (config->idle_core_mask) { + // Subscribe the new cores idle tasks + subscribe_idle(config->idle_core_mask); + } + ret = ESP_OK; err: return ret; @@ -245,12 +447,15 @@ err: esp_err_t esp_task_wdt_deinit(void) { + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); + esp_err_t ret; + // Unsubscribe all previously watched core idle tasks + unsubscribe_idle(p_twdt_obj->idle_core_mask); portENTER_CRITICAL(&spinlock); // Check TWDT state - ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized"); - ESP_GOTO_ON_FALSE(SLIST_EMPTY(&p_twdt_obj->entries_slist), ESP_ERR_INVALID_STATE, err, TAG, "all tasks must be deleted"); + ESP_GOTO_ON_FALSE_ISR(SLIST_EMPTY(&p_twdt_obj->entries_slist), ESP_ERR_INVALID_STATE, err, TAG, "Tasks/users still subscribed"); // Disable hardware timer and the interrupt wdt_hal_write_protect_disable(&p_twdt_obj->hal); wdt_hal_disable(&p_twdt_obj->hal); @@ -263,74 +468,58 @@ esp_err_t esp_task_wdt_deinit(void) portEXIT_CRITICAL(&spinlock); // Free driver resources - ESP_ERROR_CHECK(esp_intr_free(obj->intr_handle)); //Deregister interrupt - free(obj); //Free p_twdt_obj + ESP_ERROR_CHECK(esp_intr_free(obj->intr_handle)); // Deregister interrupt + free(obj); // Free p_twdt_obj return ESP_OK; err: portEXIT_CRITICAL(&spinlock); + subscribe_idle(p_twdt_obj->idle_core_mask); // Resubscribe idle tasks return ret; } -esp_err_t esp_task_wdt_add(TaskHandle_t handle) +esp_err_t esp_task_wdt_add(TaskHandle_t task_handle) { + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); esp_err_t ret; - if (handle == NULL) { //Get handle of current task if none is provided - handle = xTaskGetCurrentTaskHandle(); + if (task_handle == NULL) { // Get handle of current task if none is provided + task_handle = xTaskGetCurrentTaskHandle(); } - // Allocate entry for task - twdt_entry_t *entry = calloc(1, sizeof(twdt_entry_t)); - ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NO_MEM, alloc_err, TAG, "insufficient memory"); - entry->task_handle = handle; - entry->has_reset = false; + twdt_entry_t *entry; + ret = add_entry(true, (void *)task_handle, &entry); + (void) entry; // Returned entry pointer not used + return ret; +} - portENTER_CRITICAL(&spinlock); - // Check TWDT state - ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, state_err, TAG, "task watchdog was never initialized"); - // Check if the task is an entry, and if all entries have been reset - bool all_reset; - twdt_entry_t *found_entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset); - ESP_GOTO_ON_FALSE((found_entry == NULL), ESP_ERR_INVALID_ARG, state_err, TAG, "task is already subscribed"); - // Add task to entry list - SLIST_INSERT_HEAD(&p_twdt_obj->entries_slist, entry, slist_entry); - if (all_reset) { //Reset hardware timer if all other tasks in list have reset in - reset_hw_timer(); +esp_err_t esp_task_wdt_add_user(const char *user_name, esp_task_wdt_user_handle_t *user_handle_ret) +{ + ESP_RETURN_ON_FALSE((user_name != NULL && user_handle_ret != NULL), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); + esp_err_t ret; + twdt_entry_t *entry; + ret = add_entry(false, (void *)user_name, &entry); + if (ret == ESP_OK) { + *user_handle_ret = (esp_task_wdt_user_handle_t)entry; } - portEXIT_CRITICAL(&spinlock); //Nested critical if Legacy - - // If the task was the idle task, register the idle hook callback to appropriate core - for (int i = 0; i < portNUM_PROCESSORS; i++) { - if (handle == xTaskGetIdleTaskHandleForCPU(i)) { - ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i)); - break; - } - } - return ESP_OK; - -state_err: - portEXIT_CRITICAL(&spinlock); - free(entry); -alloc_err: return ret; } esp_err_t esp_task_wdt_reset(void) { + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); esp_err_t ret; TaskHandle_t handle = xTaskGetCurrentTaskHandle(); portENTER_CRITICAL(&spinlock); - // Check TWDT state - ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized"); - // Find entry for task + // Find entry from task handle bool all_reset; twdt_entry_t *entry; - entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset); - ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found"); + entry = find_entry_from_task_handle_and_check_all_reset(handle, &all_reset); + ESP_GOTO_ON_FALSE_ISR((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found"); // Mark entry as reset and issue timer reset if all entries have been reset - entry->has_reset = true; //Reset the task if it's on the task list - if (all_reset) { //Reset if all other tasks in list have reset in + entry->has_reset = true; // Reset the task if it's on the task list + if (all_reset) { // Reset if all other tasks in list have reset in reset_hw_timer(); } ret = ESP_OK; @@ -340,62 +529,64 @@ err: return ret; } -esp_err_t esp_task_wdt_delete(TaskHandle_t handle) +esp_err_t esp_task_wdt_reset_user(esp_task_wdt_user_handle_t user_handle) { + ESP_RETURN_ON_FALSE(user_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); esp_err_t ret; - if (handle == NULL) { - handle = xTaskGetCurrentTaskHandle(); - } portENTER_CRITICAL(&spinlock); - // Check TWDT state - ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized"); - // Find entry for task + // Check if entry exists bool all_reset; - twdt_entry_t *entry; - entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset); - ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found"); - // Remove entry - SLIST_REMOVE(&p_twdt_obj->entries_slist, entry, twdt_entry, slist_entry); - // Reset hardware timer if all remaining tasks have reset - if (all_reset) { + twdt_entry_t *entry = (twdt_entry_t *)user_handle; + bool entry_found = find_entry_and_check_all_reset(entry, &all_reset); + ESP_GOTO_ON_FALSE_ISR(entry_found, ESP_ERR_NOT_FOUND, err, TAG, "user handle not found"); + // Mark entry as reset and issue timer reset if all entries have been reset + entry->has_reset = true; // Reset the task if it's on the task list + if (all_reset) { // Reset if all other tasks in list have reset in reset_hw_timer(); } - portEXIT_CRITICAL(&spinlock); - - // Free the entry - free(entry); - // If idle task, deregister idle hook callback form appropriate core - for (int i = 0; i < portNUM_PROCESSORS; i++) { - if (handle == xTaskGetIdleTaskHandleForCPU(i)) { - esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i); - break; - } - } - return ESP_OK; - + ret = ESP_OK; err: portEXIT_CRITICAL(&spinlock); + return ret; } -esp_err_t esp_task_wdt_status(TaskHandle_t handle) +esp_err_t esp_task_wdt_delete(TaskHandle_t task_handle) { + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); esp_err_t ret; - if (handle == NULL) { - handle = xTaskGetCurrentTaskHandle(); + if (task_handle == NULL) { + task_handle = xTaskGetCurrentTaskHandle(); + } + + ret = delete_entry(true, (void *)task_handle); + return ret; +} + +esp_err_t esp_task_wdt_delete_user(esp_task_wdt_user_handle_t user_handle) +{ + ESP_RETURN_ON_FALSE(user_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); + return delete_entry(false, (void *)user_handle); +} + +esp_err_t esp_task_wdt_status(TaskHandle_t task_handle) +{ + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized"); + esp_err_t ret; + if (task_handle == NULL) { + task_handle = xTaskGetCurrentTaskHandle(); } portENTER_CRITICAL(&spinlock); - // Check TWDT state - ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized"); // Find entry for task bool all_reset; twdt_entry_t *entry; - entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset); + entry = find_entry_from_task_handle_and_check_all_reset(task_handle, &all_reset); (void) all_reset; // Unused ret = (entry != NULL) ? ESP_OK : ESP_ERR_NOT_FOUND; -err: portEXIT_CRITICAL(&spinlock); return ret; diff --git a/components/freertos/FreeRTOS-Kernel-SMP/portable/xtensa/port.c b/components/freertos/FreeRTOS-Kernel-SMP/portable/xtensa/port.c index a0d584a375..14cd46cdd7 100644 --- a/components/freertos/FreeRTOS-Kernel-SMP/portable/xtensa/port.c +++ b/components/freertos/FreeRTOS-Kernel-SMP/portable/xtensa/port.c @@ -26,6 +26,7 @@ #include "esp_int_wdt.h" #include "esp_task_wdt.h" #include "esp_heap_caps_init.h" +#include "esp_freertos_hooks.h" #include "esp_private/startup_internal.h" /* Required by g_spiram_ok. [refactor-todo] for g_spiram_ok */ #include "esp32/spiram.h" /* Required by esp_spiram_reserve_dma_pool() */ #ifdef CONFIG_APPTRACE_ENABLE @@ -188,27 +189,23 @@ static void main_task(void* args) } #endif - //Initialize task wdt if configured to do so -#ifdef CONFIG_ESP_TASK_WDT_PANIC - ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, true)); -#elif CONFIG_ESP_TASK_WDT - ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false)); + //Initialize TWDT if configured to do so +#if CONFIG_ESP_TASK_WDT + esp_task_wdt_config_t twdt_config = { + .timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000, + .idle_core_mask = 0, +#if CONFIG_ESP_TASK_WDT_PANIC + .trigger_panic = true, #endif - - //Add IDLE 0 to task wdt -#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 - TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); - if(idle_0 != NULL){ - ESP_ERROR_CHECK(esp_task_wdt_add(idle_0)); - } + }; +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 + twdt_config.idle_core_mask |= (1 << 0); #endif - //Add IDLE 1 to task wdt -#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 - TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1); - if(idle_1 != NULL){ - ESP_ERROR_CHECK(esp_task_wdt_add(idle_1)); - } +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 + twdt_config.idle_core_mask |= (1 << 1); #endif + ESP_ERROR_CHECK(esp_task_wdt_init(&twdt_config)); +#endif // CONFIG_ESP_TASK_WDT app_main(); vTaskDelete(NULL); diff --git a/components/freertos/FreeRTOS-Kernel/portable/port_common.c b/components/freertos/FreeRTOS-Kernel/portable/port_common.c index ac0e8f48ad..cb1fa368ed 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/port_common.c +++ b/components/freertos/FreeRTOS-Kernel/portable/port_common.c @@ -17,6 +17,7 @@ #include "esp_private/startup_internal.h" /* Required by g_spiram_ok. [refactor-todo] for g_spiram_ok */ #include "esp_log.h" #include "esp_memory_utils.h" +#include "esp_freertos_hooks.h" #include "soc/dport_access.h" #include "sdkconfig.h" @@ -102,27 +103,23 @@ static void main_task(void* args) } #endif - //Initialize task wdt if configured to do so -#ifdef CONFIG_ESP_TASK_WDT_PANIC - ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, true)); -#elif CONFIG_ESP_TASK_WDT - ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false)); + //Initialize TWDT if configured to do so +#if CONFIG_ESP_TASK_WDT + esp_task_wdt_config_t twdt_config = { + .timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000, + .idle_core_mask = 0, +#if CONFIG_ESP_TASK_WDT_PANIC + .trigger_panic = true, #endif - - //Add IDLE 0 to task wdt -#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 - TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); - if(idle_0 != NULL){ - ESP_ERROR_CHECK(esp_task_wdt_add(idle_0)); - } + }; +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 + twdt_config.idle_core_mask |= (1 << 0); #endif - //Add IDLE 1 to task wdt -#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 - TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1); - if(idle_1 != NULL){ - ESP_ERROR_CHECK(esp_task_wdt_add(idle_1)); - } +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 + twdt_config.idle_core_mask |= (1 << 1); #endif + ESP_ERROR_CHECK(esp_task_wdt_init(&twdt_config)); +#endif // CONFIG_ESP_TASK_WDT app_main(); vTaskDelete(NULL); diff --git a/docs/en/api-reference/system/wdts.rst b/docs/en/api-reference/system/wdts.rst index af93c365de..85b9493f85 100644 --- a/docs/en/api-reference/system/wdts.rst +++ b/docs/en/api-reference/system/wdts.rst @@ -4,104 +4,69 @@ Watchdogs Overview -------- -The ESP-IDF has support for multiple types of watchdogs, with the two main ones being: The Interrupt Watchdog Timer -and the Task Watchdog Timer (TWDT). The Interrupt Watchdog Timer and the TWDT -can both be enabled using :ref:`project-configuration-menu`, however the TWDT can also be -enabled during runtime. The Interrupt Watchdog is responsible for detecting -instances where FreeRTOS task switching is blocked for a prolonged period of -time. The TWDT is responsible for detecting instances of tasks running without -yielding for a prolonged period. +The ESP-IDF has support for multiple types of watchdogs, with the two main ones being: The Interrupt Watchdog Timer and the Task Watchdog Timer (TWDT). The Interrupt Watchdog Timer and the TWDT can both be enabled using :ref:`project-configuration-menu`, however the TWDT can also be enabled during runtime. The Interrupt Watchdog is responsible for detecting instances where FreeRTOS task switching is blocked for a prolonged period of time. The TWDT is responsible for detecting instances of tasks running without yielding for a prolonged period. Interrupt watchdog ^^^^^^^^^^^^^^^^^^ -The interrupt watchdog makes sure the FreeRTOS task switching interrupt isn't blocked for a long time. This -is bad because no other tasks, including potentially important ones like the WiFi task and the idle task, -can't get any CPU runtime. A blocked task switching interrupt can happen because a program runs into an -infinite loop with interrupts disabled or hangs in an interrupt. +The interrupt watchdog makes sure the FreeRTOS task switching interrupt isn't blocked for a long time. This is bad because no other tasks, including potentially important ones like the WiFi task and the idle task, can't get any CPU runtime. A blocked task switching interrupt can happen because a program runs into an infinite loop with interrupts disabled or hangs in an interrupt. -The default action of the interrupt watchdog is to invoke the panic handler. causing a register dump and an opportunity -for the programmer to find out, using either OpenOCD or gdbstub, what bit of code is stuck with interrupts -disabled. Depending on the configuration of the panic handler, it can also blindly reset the CPU, which may be -preferred in a production environment. +The default action of the interrupt watchdog is to invoke the panic handler causing a register dump and an opportunity for the programmer to find out, using either OpenOCD or gdbstub, what bit of code is stuck with interrupts disabled. Depending on the configuration of the panic handler, it can also blindly reset the CPU, which may be preferred in a production environment. -The interrupt watchdog is built around the hardware watchdog in timer group 1. If this watchdog for -some reason cannot execute the NMI handler that invokes the panic handler (e.g. because IRAM is -overwritten by garbage), it will hard-reset the SOC. If the panic handler executes, it will display -the panic reason as "Interrupt wdt timeout on CPU0" or "Interrupt wdt timeout on CPU1" (as -applicable). +The interrupt watchdog is built around the hardware watchdog in timer group 1. If this watchdog for some reason cannot execute the NMI handler that invokes the panic handler (e.g. because IRAM is overwritten by garbage), it will hard-reset the SOC. If the panic handler executes, it will display the panic reason as "Interrupt wdt timeout on CPU0" or "Interrupt wdt timeout on CPU1" (as applicable). Configuration -@@@@@@@@@@@@@ +""""""""""""" -The interrupt watchdog is enabled by default via the :ref:`CONFIG_ESP_INT_WDT` configuration -flag. The timeout is configured by setting :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS`. The default -timeout is higher if PSRAM support is enabled, as a critical section or interrupt routine that -accesses a large amount of PSRAM will take longer to complete in some circumstances. The INT WDT -timeout should always be longer than the period between FreeRTOS ticks (see -:ref:`CONFIG_FREERTOS_HZ`). +The interrupt watchdog is enabled by default via the :ref:`CONFIG_ESP_INT_WDT` configuration flag. The timeout is configured by setting :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS`. The default timeout is higher if PSRAM support is enabled, as a critical section or interrupt routine that accesses a large amount of PSRAM will take longer to complete in some circumstances. The INT WDT timeout should always be longer than the period between FreeRTOS ticks (see :ref:`CONFIG_FREERTOS_HZ`). Tuning -@@@@@@ +"""""" -If you find the Interrupt watchdog timeout is triggering because an interrupt or critical section is -running longer than the timeout period, consider rewriting the code: critical sections should be -made as short as possible, with non-critical computation happening outside the critical -section. Interrupt handlers should also perform the minimum possible amount of computation, consider -pushing data into a queue from the ISR and processing it in a task instead. Neither critical -sections or interrupt handlers should ever block waiting for another event to occur. +If you find the Interrupt watchdog timeout is triggered because an interrupt or critical section is running longer than the timeout period, consider rewriting the code: -If changing the code to reduce the processing time is not possible or desirable, it's possible to -increase the :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS` setting instead. +- Critical sections should be made as short as possible. Any non-critical code/computation should be placed outside the critical section. +- Interrupt handlers should also perform the minimum possible amount of computation. Users can consider deferring any computation to a task by having the ISR push data to a task using queues. + +Neither critical sections or interrupt handlers should ever block waiting for another event to occur. If changing the code to reduce the processing time is not possible or desirable, it's possible to increase the :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS` setting instead. .. _task-watchdog-timer: Task Watchdog Timer ^^^^^^^^^^^^^^^^^^^ -The Task Watchdog Timer (TWDT) is responsible for detecting instances of tasks -running for a prolonged period of time without yielding. This is a symptom of -CPU starvation and is usually caused by a higher priority task looping without -yielding to a lower-priority task thus starving the lower priority task from -CPU time. This can be an indicator of poorly written code that spinloops on a -peripheral, or a task that is stuck in an infinite loop. - {IDF_TARGET_IDLE_TASKS:default="Idle task", esp32="Idle Tasks of each CPU"} -By default the TWDT will watch the {IDF_TARGET_IDLE_TASKS}, however any task can -subscribe to be watched by the TWDT. Each watched task must 'reset' the TWDT -periodically to indicate that they have been allocated CPU time. If a task does -not reset within the TWDT timeout period, a warning will be printed with -information about which tasks failed to reset the TWDT in time and which -tasks are currently running. +The Task Watchdog Timer (TWDT) is used to monitor particular tasks, ensuring that they are able to execute within a given timeout period. The TWDT primarily watches the {IDF_TARGET_IDLE_TASKS}, however any task can subscribe to be watched by the TWDT. By watching the {IDF_TARGET_IDLE_TASKS}, the TWDT can detect instances of tasks running for a prolonged period of time wihtout yielding. This can be an indicator of poorly written code that spinloops on a peripheral, or a task that is stuck in an infinite loop. -It is also possible to redefine the function `esp_task_wdt_isr_user_handler` -in the user code, in order to receive the timeout event and handle it differently. +The TWDT is built around the Hardware Watchdog Timer in Timer Group 0. When a timeout occurs, an interrupt is triggered. Users can redefine the function `esp_task_wdt_isr_user_handler` in the user code, in order to receive the timeout event and handle it differently. -The TWDT is built around the Hardware Watchdog Timer in Timer Group 0. The TWDT -can be initialized by calling :cpp:func:`esp_task_wdt_init` which will configure -the hardware timer. A task can then subscribe to the TWDT using -:cpp:func:`esp_task_wdt_add` in order to be watched. Each subscribed task must -periodically call :cpp:func:`esp_task_wdt_reset` to reset the TWDT. Failure by -any subscribed tasks to periodically call :cpp:func:`esp_task_wdt_reset` -indicates that one or more tasks have been starved of CPU time or are stuck in a -loop somewhere. +Usage +""""" -A watched task can be unsubscribed from the TWDT using -:cpp:func:`esp_task_wdt_delete()`. A task that has been unsubscribed should no -longer call :cpp:func:`esp_task_wdt_reset`. Once all tasks have unsubscribed -form the TWDT, the TWDT can be deinitialized by calling -:cpp:func:`esp_task_wdt_deinit()`. +The following functions can be used to watch tasks using the TWDT: -The default timeout period for the TWDT is set using config item -:ref:`CONFIG_ESP_TASK_WDT_TIMEOUT_S`. This should be set to at least as long as you expect any -single task will need to monopolise the CPU (for example, if you expect the app will do a long -intensive calculation and should not yield to other tasks). It is also possible to change this -timeout at runtime by calling :cpp:func:`esp_task_wdt_init`. +- :cpp:func:`esp_task_wdt_init` to initialize the TWDT and subscribe the idle tasks. +- :cpp:func:`esp_task_wdt_add` subscribes other tasks to the TWDT. +- Once subscribed, :cpp:func:`esp_task_wdt_reset` should be called from the task to feed the TWDT. +- :cpp:func:`esp_task_wdt_delete()` unsubscribes a previously subscribed task +- :cpp:func:`esp_task_wdt_deinit()` unsubscribes the idle tasks and deinitializes the TWDT + + +In the case where applications need to watch at a more granular level (i.e., ensure that a particular functions/stub/code-path is called), the TWDT allows subscription of "users". + +- :cpp:func:`esp_task_wdt_add_user` to subscribe an arbitrary user of the TWDT. This function will return a user handle to the added user. +- :cpp:func:`esp_task_wdt_reset_user` must be called using the user handle in order to prevent a TWDT timeout. +- :cpp:func:`esp_task_wdt_delete_user` unsubscribes an arbitrary user of the TWDT. + +Configuration +""""""""""""" + +The default timeout period for the TWDT is set using config item :ref:`CONFIG_ESP_TASK_WDT_TIMEOUT_S`. This should be set to at least as long as you expect any single task will need to monopolize the CPU (for example, if you expect the app will do a long intensive calculation and should not yield to other tasks). It is also possible to change this timeout at runtime by calling :cpp:func:`esp_task_wdt_init`. .. note:: - It might cause severe watchdog timeout issue when erasing large flash areas. Here are two methods to avoid this issue: + Erasing large flash areas can be time consuming and can cause a task to run continuously, thus triggering a TWDT timeout. The following two methods can be used to avoid this: - Increase :ref:`CONFIG_ESP_TASK_WDT_TIMEOUT_S` in menuconfig for a larger watchdog timeout period. - You can also call :cpp:func:`esp_task_wdt_init` to increase the watchdog timeout period before erasing a large flash area. @@ -115,22 +80,14 @@ The following config options control TWDT configuration at startup. They are all .. list:: - :ref:`CONFIG_ESP_TASK_WDT` - the TWDT is initialized automatically during startup. If this option is disabled, it is still possible to initialize the Task WDT at runtime by calling :cpp:func:`esp_task_wdt_init`. - - :ref:`CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0` - {IDF_TARGET_IDLE_TASK} is subscribed to the TWDT during startup. If this option is disabled, it is still possible to subscribe the idle task by calling :cpp:func:`esp_task_wdt_add` at any time. + - :ref:`CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0` - {IDF_TARGET_IDLE_TASK} is subscribed to the TWDT during startup. If this option is disabled, it is still possible to subscribe the idle task by calling :cpp:func:`esp_task_wdt_init` again. :not CONFIG_FREERTOS_UNICORE: - :ref:`CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1` - CPU1 Idle task is subscribed to the TWDT during startup. JTAG and watchdogs ^^^^^^^^^^^^^^^^^^ -While debugging using OpenOCD, the CPUs will be halted every time a breakpoint -is reached. However if the watchdog timers continue to run when a breakpoint is -encountered, they will eventually trigger a reset making it very difficult to -debug code. Therefore OpenOCD will disable the hardware timers of both the -interrupt and task watchdogs at every breakpoint. Moreover, OpenOCD will not -reenable them upon leaving the breakpoint. This means that interrupt watchdog -and task watchdog functionality will essentially be disabled. No warnings or -panics from either watchdogs will be generated when the {IDF_TARGET_NAME} is connected to -OpenOCD via JTAG. +While debugging using OpenOCD, the CPUs will be halted every time a breakpoint is reached. However if the watchdog timers continue to run when a breakpoint is encountered, they will eventually trigger a reset making it very difficult to debug code. Therefore OpenOCD will disable the hardware timers of both the interrupt and task watchdogs at every breakpoint. Moreover, OpenOCD will not reenable them upon leaving the breakpoint. This means that interrupt watchdog and task watchdog functionality will essentially be disabled. No warnings or panics from either watchdogs will be generated when the {IDF_TARGET_NAME} is connected to OpenOCD via JTAG. .. only:: SOC_XT_WDT_SUPPORTED @@ -140,18 +97,16 @@ OpenOCD via JTAG. The XTAL32K watchdog makes sure the (optional) external 32 KHz crystal or oscillator is functioning correctly. - When `XTAL32K_CLK` works as the clock source of `RTC_SLOW_CLK` and stops oscillating, the XTAL32K watchdog timer will detect this and generate an interrupt. - It also provides functionality for automatically switching over to the internal, but less accurate oscillator as the `RTC_SLOW_CLK` source. + When `XTAL32K_CLK` works as the clock source of `RTC_SLOW_CLK` and stops oscillating, the XTAL32K watchdog timer will detect this and generate an interrupt. It also provides functionality for automatically switching over to the internal, but less accurate oscillator as the `RTC_SLOW_CLK` source. Since the switch to the backup clock is done in hardware it can also happen during deep sleep. This means that even if `XTAL32K_CLK` stops functioning while the chip in deep sleep, waiting for a timer to expire, it will still be able to wake-up as planned. If the `XTAL32K_CLK` starts functioning normally again, you can call `esp_xt_wdt_restore_clk` to switch back to this clock source and re-enable the watchdog timer. Configuration - @@@@@@@@@@@@@ + """"""""""""" - When the external 32KHz crystal or oscillator is selected (:ref:`CONFIG_RTC_CLK_SRC`) the XTAL32K watchdog can be enabled via the :ref:`CONFIG_ESP_XT_WDT` configuration - flag. The timeout is configured by setting :ref:`CONFIG_ESP_XT_WDT_TIMEOUT`. The automatic backup clock functionality is enabled via the ref:`CONFIG_ESP_XT_WDT_BACKUP_CLK_ENABLE` configuration. + When the external 32KHz crystal or oscillator is selected (:ref:`CONFIG_RTC_CLK_SRC`) the XTAL32K watchdog can be enabled via the :ref:`CONFIG_ESP_XT_WDT` configuration flag. The timeout is configured by setting :ref:`CONFIG_ESP_XT_WDT_TIMEOUT`. The automatic backup clock functionality is enabled via the ref:`CONFIG_ESP_XT_WDT_BACKUP_CLK_ENABLE` configuration. Interrupt Watchdog API Reference -------------------------------- diff --git a/docs/en/migration-guides/system.rst b/docs/en/migration-guides/system.rst index cf8ce31965..a2494bd9a2 100644 --- a/docs/en/migration-guides/system.rst +++ b/docs/en/migration-guides/system.rst @@ -86,3 +86,11 @@ Rename the image SPI speed enum definition. - Enum ``ESP_IMAGE_SPI_SPEED_40M`` has been renamed to ``ESP_IMAGE_SPI_SPEED_DIV_2``. - Enum ``ESP_IMAGE_SPI_SPEED_26M`` has been renamed to ``ESP_IMAGE_SPI_SPEED_DIV_3``. - Enum ``ESP_IMAGE_SPI_SPEED_20M`` has been renamed to ``ESP_IMAGE_SPI_SPEED_DIV_4``. + +Task Watchdog Timers +-------------------- + +- The API for ``esp_task_wdt_init()`` has changed as follows + + - Configuration is now passed as a configuration structure. + - The function will now handle subscribing of the idle tasks if configured to do so