Merge branch 'feature/task_watchdog_freeertos_smp' into 'master'

esp_system: Add task watchdog user feature and fix SMP FreeRTOS idle task watchdog

Closes IDF-3340

See merge request espressif/esp-idf!17768
This commit is contained in:
Darian 2022-05-17 15:13:43 +08:00
commit 4d751b9d6b
14 changed files with 898 additions and 613 deletions

View File

@ -32,9 +32,11 @@ entries:
freertos_hooks:esp_vApplicationIdleHook (noflash) freertos_hooks:esp_vApplicationIdleHook (noflash)
if PM_SLP_IRAM_OPT = y: if PM_SLP_IRAM_OPT = y:
task_wdt:idle_hook_cb (noflash) 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: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] [mapping:esp_timer_pm]
archive: libesp_timer.a archive: libesp_timer.a

View File

@ -1,19 +1,12 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD /*
// * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
// Licensed under the Apache License, Version 2.0 (the "License"); *
// you may not use this file except in compliance with the License. * SPDX-License-Identifier: Apache-2.0
// You may obtain a copy of the License at */
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once #pragma once
#include <stdbool.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_err.h" #include "esp_err.h"
@ -23,124 +16,139 @@ extern "C" {
#endif #endif
/** /**
* @brief Initialize the Task Watchdog Timer (TWDT) * @brief Task Watchdog Timer (TWDT) configuration structure
* */
* This function configures and initializes the TWDT. If the TWDT is already typedef struct {
* initialized when this function is called, this function will update the uint32_t timeout_ms; /**< TWDT timeout duration in milliseconds */
* TWDT's timeout period and panic configurations instead. After initializing uint32_t idle_core_mask; /**< Mask of the cores who's idle task should be subscribed on initialization */
* the TWDT, any task can elect to be watched by the TWDT by subscribing to it bool trigger_panic; /**< Trigger panic when timeout occurs */
* using esp_task_wdt_add(). } esp_task_wdt_config_t;
*
* @param[in] timeout Timeout period of TWDT in seconds /**
* @param[in] panic Flag that controls whether the panic handler will be * @brief Task Watchdog Timer (TWDT) user handle
* executed when the TWDT times out */
* typedef struct esp_task_wdt_user_handle_s * esp_task_wdt_user_handle_t;
* @return
* - ESP_OK: Initialization was successful /**
* - ESP_ERR_NO_MEM: Initialization failed due to lack of memory * @brief Initialize the Task Watchdog Timer (TWDT)
* *
* @note esp_task_wdt_init() must only be called after the scheduler * This function configures and initializes the TWDT. If the TWDT is already initialized when this function is called,
* started * 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().
esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic); *
* @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
* - Other: Failed to initialize TWDT
*/
esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config);
/** /**
* @brief Deinitialize the Task Watchdog Timer (TWDT) * @brief Deinitialize the Task Watchdog Timer (TWDT)
* *
* This function will deinitialize the TWDT. Calling this function whilst tasks * 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, * are still subscribed to the TWDT, or when the TWDT is already deinitialized, will result in an error code being
* will result in an error code being returned. * returned.
* *
* @return * @return
* - ESP_OK: TWDT successfully deinitialized * - ESP_OK: TWDT successfully deinitialized
* - ESP_ERR_INVALID_STATE: Error, tasks are still subscribed to the TWDT * - Other: Failed to deinitialize TWDT
* - ESP_ERR_NOT_FOUND: Error, TWDT has already been deinitialized
*/ */
esp_err_t esp_task_wdt_deinit(void); esp_err_t esp_task_wdt_deinit(void);
/** /**
* @brief Subscribe a task to the Task Watchdog Timer (TWDT) * @brief Subscribe a task to the Task Watchdog Timer (TWDT)
* *
* This function subscribes a task to the TWDT. Each subscribed task must * This function subscribes a task to the TWDT. Each subscribed task must periodically call esp_task_wdt_reset() to
* periodically call esp_task_wdt_reset() to prevent the TWDT from elapsing its * prevent the TWDT from elapsing its timeout period. Failure to do so will result in a TWDT timeout.
* 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 * @param task_handle Handle of the task. Input NULL to subscribe the current running task to the TWDT
* enable esp_task_wdt_reset() to called from the Idle Hook of the Idle Task. * @return
* Calling this function whilst the TWDT is uninitialized or attempting to * - ESP_OK: Successfully subscribed the task to the TWDT
* subscribe an already subscribed task will result in an error code being * - Other: Failed to subscribe task
* returned. */
* esp_err_t esp_task_wdt_add(TaskHandle_t task_handle);
* @param[in] 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: Error, the task is already subscribed
* - ESP_ERR_NO_MEM: Error, could not subscribe the task due to lack of
* memory
* - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet
*/
esp_err_t esp_task_wdt_add(TaskHandle_t handle);
/** /**
* @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently * @brief Subscribe a user to the Task Watchdog Timer (TWDT)
* running task *
* * This function subscribes a user to the TWDT. A user of the TWDT is usually a function that needs to run
* This function will reset the TWDT on behalf of the currently running task. * periodically. Each subscribed user must periodically call esp_task_wdt_reset_user() to prevent the TWDT from elapsing
* Each subscribed task must periodically call this function to prevent the * its timeout period. Failure to do so will result in a TWDT timeout.
* 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 * @param[in] user_name String to identify the user
* been subscribed to the TWDT, they will automatically call this function from * @param[out] user_handle_ret Handle of the user
* their idle hooks. Calling this function from a task that has not subscribed * @return
* to the TWDT, or when the TWDT is uninitialized will result in an error code * - ESP_OK: Successfully subscribed the user to the TWDT
* being returned. * - Other: Failed to subscribe user
* */
* @return esp_err_t esp_task_wdt_add_user(const char *user_name, esp_task_wdt_user_handle_t *user_handle_ret);
* - ESP_OK: Successfully reset the TWDT on behalf of the currently
* running task /**
* - ESP_ERR_NOT_FOUND: Error, the current running task has not subscribed * @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently running task
* to the TWDT *
* - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet * 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.
*
* @return
* - ESP_OK: Successfully reset the TWDT on behalf of the currently running task
* - Other: Failed to reset
*/
esp_err_t esp_task_wdt_reset(void); esp_err_t esp_task_wdt_reset(void);
/** /**
* @brief Unsubscribes a task from the Task Watchdog Timer (TWDT) * @brief Reset the Task Watchdog Timer (TWDT) on behalf of a user
* *
* This function will unsubscribe a task from the TWDT. After being * This function will reset the TWDT on behalf of a user. Each subscribed user must periodically call this function to
* unsubscribed, the task should no longer call esp_task_wdt_reset(). If the * prevent the TWDT from timing out. If one or more subscribed users fail to reset the TWDT on their own behalf, a TWDT
* task is an IDLE task, this function will automatically disable the calling * timeout will occur.
* of esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the *
* TWDT is uninitialized or attempting to unsubscribe an already unsubscribed * @param[in] user_handle User handle
* task from the TWDT will result in an error code being returned. * - ESP_OK: Successfully reset the TWDT on behalf of the user
* * - Other: Failed to reset
* @param[in] handle Handle of the task. Input NULL to unsubscribe the */
* current running task. esp_err_t esp_task_wdt_reset_user(esp_task_wdt_user_handle_t user_handle);
*
* @return
* - ESP_OK: Successfully unsubscribed the task from the TWDT
* - ESP_ERR_INVALID_ARG: Error, the task is already unsubscribed
* - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet
*/
esp_err_t esp_task_wdt_delete(TaskHandle_t handle);
/** /**
* @brief Query whether a task is subscribed to the Task Watchdog Timer (TWDT) * @brief Unsubscribes a task from the Task Watchdog Timer (TWDT)
* *
* This function will query whether a task is currently subscribed to the TWDT, * This function will unsubscribe a task from the TWDT. After being unsubscribed, the task should no longer call
* or whether the TWDT is initialized. * esp_task_wdt_reset().
* *
* @param[in] handle Handle of the task. Input NULL to query the current * @param[in] task_handle Handle of the task. Input NULL to unsubscribe the current running task.
* running task. * @return
* * - ESP_OK: Successfully unsubscribed the task from the TWDT
* @return: * - Other: Failed to unsubscribe task
* - ESP_OK: The task is currently subscribed to the TWDT */
* - ESP_ERR_NOT_FOUND: The task is currently not subscribed to the TWDT esp_err_t esp_task_wdt_delete(TaskHandle_t task_handle);
* - ESP_ERR_INVALID_STATE: The TWDT is not initialized, therefore no tasks
* can be subscribed
*/
esp_err_t esp_task_wdt_status(TaskHandle_t 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] 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 task_handle);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -5,391 +5,589 @@
*/ */
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h>
#include <sys/queue.h>
#include "sdkconfig.h" #include "sdkconfig.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/queue.h" #include "hal/wdt_hal.h"
#include "freertos/semphr.h"
#include <esp_types.h>
#include "esp_err.h" #include "esp_err.h"
#include "esp_intr_alloc.h"
#include "esp_attr.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_debug_helpers.h"
#include "esp_freertos_hooks.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_task_wdt.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/system_internal.h" #include "esp_private/system_internal.h"
#include "esp_private/crosscore_int.h" #include "esp_private/crosscore_int.h"
#include "hal/timer_types.h"
#include "hal/wdt_hal.h"
// --------------------------------------------------- Definitions -----------------------------------------------------
static const char *TAG = "task_wdt"; // ----------------------- Macros --------------------------
//Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret' // HAL related variables and constants
#define ASSERT_EXIT_CRIT_RETURN(cond, ret) ({ \
if(!(cond)){ \
portEXIT_CRITICAL(&twdt_spinlock); \
return ret; \
} \
})
//Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
#define VOID_RETURN
//HAL related variables and constants
#define TWDT_INSTANCE WDT_MWDT0 #define TWDT_INSTANCE WDT_MWDT0
#define TWDT_TICKS_PER_US MWDT0_TICKS_PER_US #define TWDT_TICKS_PER_US MWDT0_TICKS_PER_US
#define TWDT_PRESCALER MWDT0_TICK_PRESCALER //Tick period of 500us if WDT source clock is 80MHz #define TWDT_PRESCALER MWDT0_TICK_PRESCALER // Tick period of 500us if WDT source clock is 80MHz
static wdt_hal_context_t twdt_context;
//Structure used for each subscribed task // ---------------------- Typedefs -------------------------
typedef struct twdt_task_t twdt_task_t;
struct twdt_task_t { /**
TaskHandle_t task_handle; * @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; // NULL if user entry
const char *user_name; // NULL if task entry
bool has_reset; bool has_reset;
twdt_task_t *next;
}; };
//Structure used to hold run time configuration of the TWDT // Structure used to hold run time configuration of the TWDT
typedef struct twdt_config_t twdt_config_t; typedef struct twdt_obj twdt_obj_t;
struct twdt_config_t { struct twdt_obj {
twdt_task_t *list; //Linked list of subscribed tasks wdt_hal_context_t hal;
uint32_t timeout; //Timeout period of TWDT SLIST_HEAD(entry_list_head, twdt_entry) entries_slist;
bool panic; //Flag to trigger panic when TWDT times out 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; intr_handle_t intr_handle;
}; };
static twdt_config_t *twdt_config = NULL; // ----------------------- Objects -------------------------
static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
/* static const char *TAG = "task_wdt";
* Idle hook callback for Idle Tasks to reset the TWDT. This callback will only static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
* be registered to the Idle Hook of a particular core when the corresponding static twdt_obj_t *p_twdt_obj = NULL;
* Idle Task subscribes to the TWDT.
*/
static bool idle_hook_cb(void)
{
esp_task_wdt_reset();
return true;
}
/* #if CONFIG_FREERTOS_SMP
* Internal function that looks for the target task in the TWDT task list. #define CORE_USER_NAME_LEN 8 // Long enough for "CPU XXX"
* Returns the list item if found and returns null if not found. Also checks if static esp_task_wdt_user_handle_t core_user_handles[portNUM_PROCESSORS] = {NULL};
* all the other tasks have reset. Should be called within critical. static char core_user_names[portNUM_PROCESSORS][CORE_USER_NAME_LEN];
*/ #endif
static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
{
twdt_task_t *target = NULL;
*all_reset = true;
for(twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
if(task->task_handle == handle){
target = task; //Get pointer to target task list member
}else{
if(task->has_reset == false){ //If a task has yet to reset
*all_reset = false;
}
}
}
return target;
}
/* // ----------------------------------------------------- Private -------------------------------------------------------
* Resets the hardware timer and has_reset flags of each task on the list.
* Called within critical
*/
static void reset_hw_timer(void)
{
//All tasks have reset; time to reset the hardware timer.
wdt_hal_write_protect_disable(&twdt_context);
wdt_hal_feed(&twdt_context);
wdt_hal_write_protect_enable(&twdt_context);
//Clear all has_reset flags in list
for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
task->has_reset=false;
}
}
/* // ---------------------- Callbacks ------------------------
* 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. * @brief User ISR callback placeholder
* Do not use ESP_LOGI functions inside. *
* 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) void __attribute__((weak)) esp_task_wdt_isr_user_handler(void)
{ {
} }
/* /**
* ISR for when TWDT times out. Checks for which tasks have not reset. Also * @brief Idle hook callback
* triggers panic if configured to do so *
* 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 Task entry, or NULL if not found
*/
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;
twdt_entry_t *entry;
SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
if (entry->task_handle == handle) {
target = entry;
} else if (entry->has_reset == false) {
found_non_reset = true;
}
}
*all_reset = !found_non_reset;
return target;
}
/**
* @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 esp_err_t add_entry(bool is_task, void *entry_data, twdt_entry_t **entry_ret)
{
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;
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 Subscribes the idle tasks of one or more cores
*
* @param core_mask Bit mask of cores to subscribe
*/
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
*
* 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
*/ */
static void task_wdt_isr(void *arg) static void task_wdt_isr(void *arg)
{ {
portENTER_CRITICAL_ISR(&twdt_spinlock); portENTER_CRITICAL_ISR(&spinlock);
twdt_task_t *twdttask; // Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
const char *cpu; wdt_hal_write_protect_disable(&p_twdt_obj->hal);
//Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset) wdt_hal_handle_intr(&p_twdt_obj->hal); // Feeds WDT and clears acknowledges interrupt
wdt_hal_write_protect_disable(&twdt_context); wdt_hal_write_protect_enable(&p_twdt_obj->hal);
wdt_hal_handle_intr(&twdt_context); //Feeds WDT and clears acknowledges interrupt // If there are no entries, there's nothing to do.
wdt_hal_write_protect_enable(&twdt_context); if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) {
portEXIT_CRITICAL_ISR(&spinlock);
//We are taking a spinlock while doing I/O (ESP_EARLY_LOGE) here. Normally, that is a pretty return;
//bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case, }
//something bad already happened and reporting this is considered more important // Find what entries triggered the TWDT timeout (i.e., which entries have not been reset)
//than the badness caused by a spinlock here. /*
Note: We are currently in a critical section, thus under normal circumstances, logging should not be allowed.
//Return immediately if no tasks have been added to task list However, TWDT timeouts count as fatal errors, thus reporting the fatal error is considered more important than
ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN); minimizing interrupt latency. Thus we allow logging in critical sections in this narrow case.
*/
//Watchdog got triggered because at least one task did not reset in time. ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:");
ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks did not reset the watchdog in time:"); twdt_entry_t *entry;
for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) { SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
if (!twdttask->has_reset) { if (!entry->has_reset) {
cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1"); if (entry->task_handle) {
if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) { #if CONFIG_FREERTOS_SMP
cpu=DRAM_STR("CPU 0/1"); 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 {
ESP_EARLY_LOGE(TAG, " - %s", entry->user_name);
} }
ESP_EARLY_LOGE(TAG, " - %s (%s)", pcTaskGetName(twdttask->task_handle), cpu);
} }
} }
ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:")); ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:"));
for (int x=0; x<portNUM_PROCESSORS; x++) { for (int x = 0; x < portNUM_PROCESSORS; x++) {
ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetName(xTaskGetCurrentTaskHandleForCPU(x))); ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetName(xTaskGetCurrentTaskHandleForCPU(x)));
} }
portEXIT_CRITICAL_ISR(&spinlock);
// Run user ISR handler
esp_task_wdt_isr_user_handler(); esp_task_wdt_isr_user_handler();
// Trigger configured timeout behavior (e.g., panic or print backtrace)
if (twdt_config->panic){ //Trigger Panic if configured to do so if (p_twdt_obj->panic) {
ESP_EARLY_LOGE(TAG, "Aborting."); ESP_EARLY_LOGE(TAG, "Aborting.");
portEXIT_CRITICAL_ISR(&twdt_spinlock);
esp_reset_reason_set_hint(ESP_RST_TASK_WDT); esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
abort(); abort();
} else { } else { // Print
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 // TODO: ESP32-C3 IDF-2986 #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 // TODO: ESP32-C3 IDF-2986
int current_core = xPortGetCoreID(); int current_core = xPortGetCoreID();
//Print backtrace of current core // Print backtrace of current core
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core); ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
esp_backtrace_print(100); esp_backtrace_print(100);
#if !CONFIG_FREERTOS_UNICORE #if !CONFIG_FREERTOS_UNICORE
//Print backtrace of other core // Print backtrace of other core
ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core); ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core);
esp_crosscore_int_send_print_backtrace(!current_core); esp_crosscore_int_send_print_backtrace(!current_core);
#endif #endif
#endif #endif
} }
portEXIT_CRITICAL_ISR(&twdt_spinlock);
} }
/* // ----------------------------------------------------- Public --------------------------------------------------------
* Initializes the TWDT by allocating memory for the config data
* structure, obtaining the idle task handles/registering idle hooks, and esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config)
* setting the hardware timer registers. If reconfiguring, it will just modify
* wdt_config and reset the hardware timer.
*/
esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
{ {
portENTER_CRITICAL(&twdt_spinlock); ESP_RETURN_ON_FALSE((config != NULL && config->idle_core_mask < (1 << portNUM_PROCESSORS)), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
if(twdt_config == NULL){ //TWDT not initialized yet ESP_RETURN_ON_FALSE(p_twdt_obj == NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT already initialized");
//Allocate memory for wdt_config esp_err_t ret;
twdt_config = calloc(1, sizeof(twdt_config_t));
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM);
twdt_config->list = NULL; twdt_obj_t *obj = NULL;
twdt_config->timeout = timeout; if (p_twdt_obj == NULL) {
twdt_config->panic = panic; // Allocate and initialize TWDT driver object
obj = calloc(1, sizeof(twdt_obj_t));
//Register Interrupt and ISR ESP_GOTO_ON_FALSE((obj != NULL), ESP_ERR_NO_MEM, err, TAG, "insufficient memory");
ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle)); SLIST_INIT(&obj->entries_slist);
obj->panic = config->trigger_panic;
//Configure hardware timer 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
periph_module_enable(PERIPH_TIMG0_MODULE); periph_module_enable(PERIPH_TIMG0_MODULE);
wdt_hal_init(&twdt_context, TWDT_INSTANCE, TWDT_PRESCALER, true); wdt_hal_init(&obj->hal, TWDT_INSTANCE, TWDT_PRESCALER, true);
wdt_hal_write_protect_disable(&twdt_context); // Assign the driver object
//Configure 1st stage timeout and behavior p_twdt_obj = obj;
wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); portEXIT_CRITICAL(&spinlock);
//Configure 2nd stage timeout and behavior
wdt_hal_config_stage(&twdt_context, WDT_STAGE1, twdt_config->timeout * (2 * 1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
//Enable the WDT
wdt_hal_enable(&twdt_context);
wdt_hal_write_protect_enable(&twdt_context);
} else { //twdt_config previously initialized
//Reconfigure task wdt
twdt_config->panic = panic;
twdt_config->timeout = timeout;
//Reconfigure hardware timer
wdt_hal_write_protect_disable(&twdt_context);
wdt_hal_disable(&twdt_context);
wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT);
wdt_hal_config_stage(&twdt_context, WDT_STAGE1, twdt_config->timeout * (2 * 1000 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
wdt_hal_enable(&twdt_context);
wdt_hal_write_protect_enable(&twdt_context);
} }
portEXIT_CRITICAL(&twdt_spinlock);
return ESP_OK; 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, 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, 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;
} }
esp_err_t esp_task_wdt_deinit(void) esp_err_t esp_task_wdt_deinit(void)
{ {
portENTER_CRITICAL(&twdt_spinlock); ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
//TWDT must already be initialized
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
//Task list must be empty
ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
//Disable hardware timer esp_err_t ret;
wdt_hal_deinit(&twdt_context); // Unsubscribe all previously watched core idle tasks
unsubscribe_idle(p_twdt_obj->idle_core_mask);
ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)); //Unregister interrupt portENTER_CRITICAL(&spinlock);
free(twdt_config); //Free twdt_config // Check TWDT state
twdt_config = NULL; ESP_GOTO_ON_FALSE_ISR(SLIST_EMPTY(&p_twdt_obj->entries_slist), ESP_ERR_INVALID_STATE, err, TAG, "Tasks/users still subscribed");
portEXIT_CRITICAL(&twdt_spinlock); // Disable hardware timer and the interrupt
wdt_hal_write_protect_disable(&p_twdt_obj->hal);
wdt_hal_disable(&p_twdt_obj->hal);
wdt_hal_write_protect_enable(&p_twdt_obj->hal);
wdt_hal_deinit(&p_twdt_obj->hal);
esp_intr_disable(p_twdt_obj->intr_handle);
// Unassign driver object
twdt_obj_t *obj = p_twdt_obj;
p_twdt_obj = NULL;
portEXIT_CRITICAL(&spinlock);
// Free driver resources
ESP_ERROR_CHECK(esp_intr_free(obj->intr_handle)); // Deregister interrupt
free(obj); // Free p_twdt_obj
return ESP_OK; 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)
{ {
portENTER_CRITICAL(&twdt_spinlock); ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
//TWDT must already be initialized esp_err_t ret;
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); if (task_handle == NULL) { // Get handle of current task if none is provided
task_handle = xTaskGetCurrentTaskHandle();
twdt_task_t *target_task;
bool all_reset;
if (handle == NULL){ //Get handle of current task if none is provided
handle = xTaskGetCurrentTaskHandle();
}
//Check if tasks exists in task list, and if all other tasks have reset
target_task = find_task_in_twdt_list(handle, &all_reset);
//task cannot be already subscribed
ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
//Add target task to TWDT task list
target_task = calloc(1,sizeof(twdt_task_t));
ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM);
target_task->task_handle = handle;
target_task->has_reset = true;
target_task->next = NULL;
if (twdt_config->list == NULL) { //Adding to empty list
twdt_config->list = target_task;
} else { //Adding to tail of list
twdt_task_t *task;
for (task = twdt_config->list; task->next != NULL; task = task->next){
; //point task to current tail of TWDT task list
}
task->next = target_task;
} }
//If idle task, register the idle hook callback to appropriate core twdt_entry_t *entry;
for(int i = 0; i < portNUM_PROCESSORS; i++){ ret = add_entry(true, (void *)task_handle, &entry);
if(handle == xTaskGetIdleTaskHandleForCPU(i)){ (void) entry; // Returned entry pointer not used
ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i)); return ret;
break; }
}
}
if(all_reset){ //Reset hardware timer if all other tasks in list have reset in esp_err_t esp_task_wdt_add_user(const char *user_name, esp_task_wdt_user_handle_t *user_handle_ret)
reset_hw_timer(); {
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;
} }
return ret;
portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy
return ESP_OK;
} }
esp_err_t esp_task_wdt_reset(void) esp_err_t esp_task_wdt_reset(void)
{ {
portENTER_CRITICAL(&twdt_spinlock); ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
//TWDT must already be initialized esp_err_t ret;
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
TaskHandle_t handle = xTaskGetCurrentTaskHandle(); TaskHandle_t handle = xTaskGetCurrentTaskHandle();
twdt_task_t *target_task;
portENTER_CRITICAL(&spinlock);
// Find entry from task handle
bool all_reset; bool all_reset;
twdt_entry_t *entry;
//Check if task exists in task list, and if all other tasks have reset entry = find_entry_from_task_handle_and_check_all_reset(handle, &all_reset);
target_task = find_task_in_twdt_list(handle, &all_reset); ESP_GOTO_ON_FALSE_ISR((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found");
//Return error if trying to reset task that is not on the task list // Mark entry as reset and issue timer reset if all entries have been reset
ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND); 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
target_task->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(); reset_hw_timer();
} }
ret = ESP_OK;
err:
portEXIT_CRITICAL(&spinlock);
portEXIT_CRITICAL(&twdt_spinlock); return ret;
return ESP_OK;
} }
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)
{ {
if(handle == NULL){ ESP_RETURN_ON_FALSE(user_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
handle = xTaskGetCurrentTaskHandle(); ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
} esp_err_t ret;
portENTER_CRITICAL(&twdt_spinlock);
//Return error if twdt has not been initialized
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
twdt_task_t *target_task; portENTER_CRITICAL(&spinlock);
// Check if entry exists
bool all_reset; bool all_reset;
target_task = find_task_in_twdt_list(handle, &all_reset); twdt_entry_t *entry = (twdt_entry_t *)user_handle;
//Task doesn't exist on list. Return error bool entry_found = find_entry_and_check_all_reset(entry, &all_reset);
ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG); 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
if(target_task == twdt_config->list){ //target_task is head of list. Delete entry->has_reset = true; // Reset the task if it's on the task list
twdt_config->list = target_task->next; if (all_reset) { // Reset if all other tasks in list have reset in
free(target_task);
}else{ //target_task not head of list. Delete
twdt_task_t *prev;
for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
; //point prev to task preceding target_task
}
prev->next = target_task->next;
free(target_task);
}
//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;
}
}
if(all_reset){ //Reset hardware timer if all remaining tasks have reset
reset_hw_timer(); reset_hw_timer();
} }
ret = ESP_OK;
err:
portEXIT_CRITICAL(&spinlock);
portEXIT_CRITICAL(&twdt_spinlock); return ret;
return ESP_OK;
} }
esp_err_t esp_task_wdt_status(TaskHandle_t handle) esp_err_t esp_task_wdt_delete(TaskHandle_t task_handle)
{ {
if(handle == NULL){ ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
handle = xTaskGetCurrentTaskHandle(); esp_err_t ret;
if (task_handle == NULL) {
task_handle = xTaskGetCurrentTaskHandle();
} }
portENTER_CRITICAL(&twdt_spinlock); ret = delete_entry(true, (void *)task_handle);
//Return if TWDT is not initialized return ret;
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); }
twdt_task_t *task; esp_err_t esp_task_wdt_delete_user(esp_task_wdt_user_handle_t user_handle)
for(task = twdt_config->list; task!=NULL; task=task->next){ {
//Return ESP_OK if task is found ESP_RETURN_ON_FALSE(user_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK); ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
} return delete_entry(false, (void *)user_handle);
}
//Task could not be found
portEXIT_CRITICAL(&twdt_spinlock); esp_err_t esp_task_wdt_status(TaskHandle_t task_handle)
return ESP_ERR_NOT_FOUND; {
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);
// Find entry for task
bool all_reset;
twdt_entry_t *entry;
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;
portEXIT_CRITICAL(&spinlock);
return ret;
} }

View File

@ -216,8 +216,12 @@ TEST_CASE_MULTIPLE_STAGES("reset reason ESP_RST_INT_WDT after interrupt watchdog
static void do_task_wdt(void) static void do_task_wdt(void)
{ {
setup_values(); setup_values();
esp_task_wdt_init(1, true); esp_task_wdt_config_t twdt_config = {
esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(0)); .timeout_ms = 1000,
.idle_core_mask = (1 << 0), // Watch core 0 idle
.trigger_panic = true,
};
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config));
while(1); while(1);
} }

View File

@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdbool.h>
#include "unity.h"
#include "esp_rom_sys.h"
#include "esp_task_wdt.h"
#define TASK_WDT_TIMEOUT_MS 1000
static bool timeout_flag;
void esp_task_wdt_isr_user_handler(void)
{
timeout_flag = true;
}
TEST_CASE("Task WDT task timeout", "[task_wdt]")
{
timeout_flag = false;
esp_task_wdt_config_t twdt_config = {
.timeout_ms = TASK_WDT_TIMEOUT_MS,
.idle_core_mask = 0,
.trigger_panic = false,
};
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_add(NULL));
// Short delay to allow timeout to occur
esp_rom_delay_us(TASK_WDT_TIMEOUT_MS * 1000);
TEST_ASSERT_EQUAL(true, timeout_flag);
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_delete(NULL));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit());
}
TEST_CASE("Task WDT task feed", "[task_wdt]")
{
timeout_flag = false;
esp_task_wdt_config_t twdt_config = {
.timeout_ms = TASK_WDT_TIMEOUT_MS,
.idle_core_mask = 0,
.trigger_panic = false,
};
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_add(NULL));
// Feed the watchdog after a short delay
esp_rom_delay_us((TASK_WDT_TIMEOUT_MS * 1000) / 2);
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_reset());
esp_rom_delay_us((TASK_WDT_TIMEOUT_MS * 1000) / 2);
TEST_ASSERT_EQUAL(false, timeout_flag);
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_delete(NULL));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit());
}
TEST_CASE("Task WDT user timeout", "[task_wdt]")
{
const char *user_name = "test_user";
esp_task_wdt_user_handle_t user_handle;
timeout_flag = false;
esp_task_wdt_config_t twdt_config = {
.timeout_ms = TASK_WDT_TIMEOUT_MS,
.idle_core_mask = 0,
.trigger_panic = false,
};
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_add_user(user_name, &user_handle));
// Short delay to allow timeout to occur
esp_rom_delay_us(TASK_WDT_TIMEOUT_MS * 1000);
TEST_ASSERT_EQUAL(true, timeout_flag);
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_delete_user(user_handle));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit());
}
TEST_CASE("Task WDT user feed", "[task_wdt]")
{
const char *user_name = "test_user";
esp_task_wdt_user_handle_t user_handle;
timeout_flag = false;
esp_task_wdt_config_t twdt_config = {
.timeout_ms = TASK_WDT_TIMEOUT_MS,
.idle_core_mask = 0,
.trigger_panic = false,
};
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_add_user(user_name, &user_handle));
// Feed the watchdog after a short delay
esp_rom_delay_us((TASK_WDT_TIMEOUT_MS * 1000) / 2);
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_reset_user(user_handle));
esp_rom_delay_us((TASK_WDT_TIMEOUT_MS * 1000) / 2);
TEST_ASSERT_EQUAL(false, timeout_flag);
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_delete_user(user_handle));
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit());
}

View File

@ -26,6 +26,7 @@
#include "esp_int_wdt.h" #include "esp_int_wdt.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "esp_heap_caps_init.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 "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() */ #include "esp32/spiram.h" /* Required by esp_spiram_reserve_dma_pool() */
#ifdef CONFIG_APPTRACE_ENABLE #ifdef CONFIG_APPTRACE_ENABLE
@ -188,27 +189,23 @@ static void main_task(void* args)
} }
#endif #endif
//Initialize task wdt if configured to do so //Initialize TWDT if configured to do so
#ifdef CONFIG_ESP_TASK_WDT_PANIC #if CONFIG_ESP_TASK_WDT
ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, true)); esp_task_wdt_config_t twdt_config = {
#elif CONFIG_ESP_TASK_WDT .timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false)); .idle_core_mask = 0,
#if CONFIG_ESP_TASK_WDT_PANIC
.trigger_panic = true,
#endif #endif
};
//Add IDLE 0 to task wdt #if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 twdt_config.idle_core_mask |= (1 << 0);
TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
if(idle_0 != NULL){
ESP_ERROR_CHECK(esp_task_wdt_add(idle_0));
}
#endif #endif
//Add IDLE 1 to task wdt #if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 twdt_config.idle_core_mask |= (1 << 1);
TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1);
if(idle_1 != NULL){
ESP_ERROR_CHECK(esp_task_wdt_add(idle_1));
}
#endif #endif
ESP_ERROR_CHECK(esp_task_wdt_init(&twdt_config));
#endif // CONFIG_ESP_TASK_WDT
app_main(); app_main();
vTaskDelete(NULL); vTaskDelete(NULL);

View File

@ -17,6 +17,7 @@
#include "esp_private/startup_internal.h" /* Required by g_spiram_ok. [refactor-todo] for g_spiram_ok */ #include "esp_private/startup_internal.h" /* Required by g_spiram_ok. [refactor-todo] for g_spiram_ok */
#include "esp_log.h" #include "esp_log.h"
#include "esp_memory_utils.h" #include "esp_memory_utils.h"
#include "esp_freertos_hooks.h"
#include "soc/dport_access.h" #include "soc/dport_access.h"
#include "sdkconfig.h" #include "sdkconfig.h"
@ -102,27 +103,23 @@ static void main_task(void* args)
} }
#endif #endif
//Initialize task wdt if configured to do so //Initialize TWDT if configured to do so
#ifdef CONFIG_ESP_TASK_WDT_PANIC #if CONFIG_ESP_TASK_WDT
ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, true)); esp_task_wdt_config_t twdt_config = {
#elif CONFIG_ESP_TASK_WDT .timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000,
ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false)); .idle_core_mask = 0,
#if CONFIG_ESP_TASK_WDT_PANIC
.trigger_panic = true,
#endif #endif
};
//Add IDLE 0 to task wdt #if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 twdt_config.idle_core_mask |= (1 << 0);
TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
if(idle_0 != NULL){
ESP_ERROR_CHECK(esp_task_wdt_add(idle_0));
}
#endif #endif
//Add IDLE 1 to task wdt #if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 twdt_config.idle_core_mask |= (1 << 1);
TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1);
if(idle_1 != NULL){
ESP_ERROR_CHECK(esp_task_wdt_add(idle_1));
}
#endif #endif
ESP_ERROR_CHECK(esp_task_wdt_init(&twdt_config));
#endif // CONFIG_ESP_TASK_WDT
app_main(); app_main();
vTaskDelete(NULL); vTaskDelete(NULL);

View File

@ -545,9 +545,12 @@ TEST_CASE("mbedtls RSA Generate Key", "[mbedtls][timeout=60]")
#if CONFIG_MBEDTLS_MPI_USE_INTERRUPT #if CONFIG_MBEDTLS_MPI_USE_INTERRUPT
/* Check that generating keys doesnt starve the watchdog if interrupt-based driver is used */ /* Check that generating keys doesnt starve the watchdog if interrupt-based driver is used */
const int timeout_s = 1; esp_task_wdt_config_t twdt_config = {
esp_task_wdt_init(timeout_s, true); .timeout_ms = 1000,
esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(0)); .idle_core_mask = (1 << 0), // Watch core 0 idle
.trigger_panic = true,
};
TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config));
#endif //CONFIG_MBEDTLS_MPI_USE_INTERRUPT #endif //CONFIG_MBEDTLS_MPI_USE_INTERRUPT
mbedtls_rsa_init(&ctx); mbedtls_rsa_init(&ctx);
@ -563,8 +566,7 @@ TEST_CASE("mbedtls RSA Generate Key", "[mbedtls][timeout=60]")
mbedtls_entropy_free(&entropy); mbedtls_entropy_free(&entropy);
#if CONFIG_MBEDTLS_MPI_USE_INTERRUPT #if CONFIG_MBEDTLS_MPI_USE_INTERRUPT
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)); TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit());
esp_task_wdt_deinit();
#endif //CONFIG_MBEDTLS_MPI_USE_INTERRUPT #endif //CONFIG_MBEDTLS_MPI_USE_INTERRUPT
} }

View File

@ -4,104 +4,69 @@ Watchdogs
Overview Overview
-------- --------
The ESP-IDF has support for multiple types of watchdogs, with the two main ones being: The Interrupt Watchdog Timer 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.
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 Interrupt watchdog
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
The interrupt watchdog makes sure the FreeRTOS task switching interrupt isn't blocked for a long time. This 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.
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 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.
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 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).
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 Configuration
@@@@@@@@@@@@@ """""""""""""
The interrupt watchdog is enabled by default via the :ref:`CONFIG_ESP_INT_WDT` 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`).
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 Tuning
@@@@@@ """"""
If you find the Interrupt watchdog timeout is triggering because an interrupt or critical section is 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:
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 changing the code to reduce the processing time is not possible or desirable, it's possible to - Critical sections should be made as short as possible. Any non-critical code/computation should be placed outside the critical section.
increase the :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS` setting instead. - 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:
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"} {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 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.
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.
It is also possible to redefine the function `esp_task_wdt_isr_user_handler` 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.
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 Usage
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.
A watched task can be unsubscribed from the TWDT using The following functions can be used to watch tasks using the TWDT:
: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 default timeout period for the TWDT is set using config item - :cpp:func:`esp_task_wdt_init` to initialize the TWDT and subscribe the idle tasks.
:ref:`CONFIG_ESP_TASK_WDT_TIMEOUT_S`. This should be set to at least as long as you expect any - :cpp:func:`esp_task_wdt_add` subscribes other tasks to the TWDT.
single task will need to monopolise the CPU (for example, if you expect the app will do a long - Once subscribed, :cpp:func:`esp_task_wdt_reset` should be called from the task to feed the TWDT.
intensive calculation and should not yield to other tasks). It is also possible to change this - :cpp:func:`esp_task_wdt_delete()` unsubscribes a previously subscribed task
timeout at runtime by calling :cpp:func:`esp_task_wdt_init`. - :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:: .. 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. - 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. - 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:: .. 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` - 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. :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 JTAG and watchdogs
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
While debugging using OpenOCD, the CPUs will be halted every time a breakpoint 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.
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 .. 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. 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. 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.
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. 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. 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 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 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.
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 Interrupt Watchdog API Reference
-------------------------------- --------------------------------

View File

@ -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_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_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``. - 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

View File

@ -1,8 +1,11 @@
# Task Watchdog Example # Task Watchdog Example
This test code shows how to initialize the task watchdog, add tasks to the The following example demonstrates how to use the following features of the task watchdog timer (TWDT):
watchdog task list, feeding the tasks, deleting tasks from the watchdog task
list, and deinitializing the task watchdog. - How to initialize and deinitialize the TWDT
- How to subscribe and unsubscribe tasks to the TWDT
- How to subscribe and unsubscribe users to the TWDT
- How to tasks and users can reset (i.e., feed) the TWDT
## How to use example ## How to use example
@ -15,7 +18,7 @@ Before project configuration and build, be sure to set the correct chip target u
### Configure the project ### Configure the project
Program should run without error. Comment out `esp_task_wdt_reset()` to observe a watchdog timeout. Program should run correctly without needing any special configuration. However, users can disable `CONFIG_ESP_TASK_WDT` which will prevent the TWDT from being automatically initialized on startup. If disabled, the example will manually initialize the TWDT.
### Build and Flash ### Build and Flash
@ -29,31 +32,33 @@ See the [ESP-IDF Getting Started Guide](https://idf.espressif.com/) for all the
## Example Output ## Example Output
As you run the example, you will see the following log: When the example runs normally, the following output will be observed:
With `esp_task_wdt_reset()`:
``` ```
I (316) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU. I (0) cpu_start: Starting scheduler on APP CPU.
Initialize TWDT TWDT initialized
Subscribed to TWDT
Delay for 10 seconds Delay for 10 seconds
Unsubscribing and deleting tasks Unsubscribed from TWDT
Complete TWDT deinitialized
Example complete
``` ```
Without `esp_task_wdt_reset()`: Users can comment out any of the `esp_task_wdt_reset()` or `esp_task_wdt_reset_user()` calls to trigger the TWDT, which in turn will result in the following output:
``` ```
I (316) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU. I (0) cpu_start: Starting scheduler on APP CPU.
Initialize TWDT TWDT initialized
Subscribed to TWDT
Delay for 10 seconds Delay for 10 seconds
E (6316) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time: E (6326) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (6316) task_wdt: - reset task (CPU 0) E (6326) task_wdt: - task (CPU 0)
E (6316) task_wdt: - reset task (CPU 1) E (6326) task_wdt: Tasks currently running:
E (6316) task_wdt: Tasks currently running: E (6326) task_wdt: CPU 0: IDLE
E (6316) task_wdt: CPU 0: IDLE E (6326) task_wdt: CPU 1: IDLE
E (6316) task_wdt: CPU 1: IDLE E (6326) task_wdt: Print CPU 0 (current core) backtrace
E (6316) task_wdt: Print CPU 0 (current core) backtrace
``` ```
## Troubleshooting ## Troubleshooting

View File

@ -6,80 +6,98 @@
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. CONDITIONS OF ANY KIND, either express or implied.
*/ */
#include "sdkconfig.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_err.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#define TWDT_TIMEOUT_S 3 #define TWDT_TIMEOUT_MS 3000
#define TASK_RESET_PERIOD_S 2 #define TASK_RESET_PERIOD_MS 2000
#define MAIN_DELAY_MS 10000
/* static volatile bool run_loop;
* Macro to check the outputs of TWDT functions and trigger an abort if an static esp_task_wdt_user_handle_t func_a_twdt_user_hdl;
* incorrect code is returned. static esp_task_wdt_user_handle_t func_b_twdt_user_hdl;
*/
#define CHECK_ERROR_CODE(returned, expected) ({ \
if(returned != expected){ \
printf("TWDT ERROR\n"); \
abort(); \
} \
})
static TaskHandle_t task_handles[portNUM_PROCESSORS]; static void func_a(void)
//Callback for user tasks created in app_main()
void reset_task(void *arg)
{ {
//Subscribe this task to TWDT, then check if it is subscribed esp_task_wdt_reset_user(func_a_twdt_user_hdl);
CHECK_ERROR_CODE(esp_task_wdt_add(NULL), ESP_OK); }
CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_OK);
while(1){ static void func_b(void)
//reset the watchdog every 2 seconds {
CHECK_ERROR_CODE(esp_task_wdt_reset(), ESP_OK); //Comment this line to trigger a TWDT timeout esp_task_wdt_reset_user(func_b_twdt_user_hdl);
vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_S * 1000)); }
void task_func(void *arg)
{
// Subscribe this task to TWDT, then check if it is subscribed
ESP_ERROR_CHECK(esp_task_wdt_add(NULL));
ESP_ERROR_CHECK(esp_task_wdt_status(NULL));
// Subscribe func_a and func_b as users of the the TWDT
ESP_ERROR_CHECK(esp_task_wdt_add_user("func_a", &func_a_twdt_user_hdl));
ESP_ERROR_CHECK(esp_task_wdt_add_user("func_b", &func_b_twdt_user_hdl));
printf("Subscribed to TWDT\n");
while (run_loop) {
// Reset the task and each user periodically
/*
Note: Comment out any one of the calls below to trigger the TWDT
*/
esp_task_wdt_reset();
func_a();
func_b();
vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_MS));
} }
// Unsubscribe this task, func_a, and func_b
ESP_ERROR_CHECK(esp_task_wdt_delete_user(func_a_twdt_user_hdl));
ESP_ERROR_CHECK(esp_task_wdt_delete_user(func_b_twdt_user_hdl));
ESP_ERROR_CHECK(esp_task_wdt_delete(NULL));
printf("Unsubscribed from TWDT\n");
// Notify main task of deletion
xTaskNotifyGive((TaskHandle_t)arg);
vTaskDelete(NULL);
} }
void app_main(void) void app_main(void)
{ {
printf("Initialize TWDT\n"); #if !CONFIG_ESP_TASK_WDT
//Initialize or reinitialize TWDT // If the TWDT was not initialized automatically on startup, manually intialize it now
CHECK_ERROR_CODE(esp_task_wdt_init(TWDT_TIMEOUT_S, false), ESP_OK); esp_task_wdt_config_t twdt_config = {
.timeout_ms = TWDT_TIMEOUT_MS,
.idle_core_mask = (1 << portNUM_PROCESSORS) - 1, // Bitmask of all cores
.trigger_panic = false,
};
ESP_ERROR_CHECK(esp_task_wdt_init(&twdt_config));
printf("TWDT initialized\n");
#endif // CONFIG_ESP_TASK_WDT
//Subscribe Idle Tasks to TWDT if they were not subscribed at startup // Create a task
#ifndef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 run_loop = true;
esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(0)); xTaskCreatePinnedToCore(task_func, "task", 2048, xTaskGetCurrentTaskHandle(), 10, NULL, 0);
#endif
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 && !CONFIG_FREERTOS_UNICORE
esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(1));
#endif
//Create user tasks and add them to watchdog // Let the created task run for a while
for(int i = 0; i < portNUM_PROCESSORS; i++){ printf("Delay for %d seconds\n", MAIN_DELAY_MS/1000);
xTaskCreatePinnedToCore(reset_task, "reset task", 1024, NULL, 10, &task_handles[i], i); vTaskDelay(pdMS_TO_TICKS(MAIN_DELAY_MS));
}
printf("Delay for 10 seconds\n"); // Stop the created task
vTaskDelay(pdMS_TO_TICKS(10000)); //Delay for 10 seconds run_loop = false;
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
printf("Unsubscribing and deleting tasks\n"); #if !CONFIG_ESP_TASK_WDT
//Delete and unsubscribe Users Tasks from Task Watchdog, then unsubscribe idle task // If we manually initialized the TWDT, deintialize it now
for(int i = 0; i < portNUM_PROCESSORS; i++){ ESP_ERROR_CHECK(esp_task_wdt_deinit());
vTaskDelete(task_handles[i]); //Delete user task first (prevents the resetting of an unsubscribed task) printf("TWDT deinitialized\n");
CHECK_ERROR_CODE(esp_task_wdt_delete(task_handles[i]), ESP_OK); //Unsubscribe task from TWDT #endif // CONFIG_ESP_TASK_WDT
CHECK_ERROR_CODE(esp_task_wdt_status(task_handles[i]), ESP_ERR_NOT_FOUND); //Confirm task is unsubscribed printf("Example complete\n");
//unsubscribe idle task
CHECK_ERROR_CODE(esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(i)), ESP_OK); //Unsubscribe Idle Task from TWDT
CHECK_ERROR_CODE(esp_task_wdt_status(xTaskGetIdleTaskHandleForCPU(i)), ESP_ERR_NOT_FOUND); //Confirm Idle task has unsubscribed
}
//Deinit TWDT after all tasks have unsubscribed
CHECK_ERROR_CODE(esp_task_wdt_deinit(), ESP_OK);
CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_ERR_INVALID_STATE); //Confirm TWDT has been deinitialized
printf("Complete\n");
} }

View File

@ -9,7 +9,4 @@ from pytest_embedded import Dut
@pytest.mark.generic @pytest.mark.generic
def test_task_watchdog(dut: Dut) -> None: def test_task_watchdog(dut: Dut) -> None:
dut.expect_exact('Initialize TWDT') dut.expect_exact('Example complete')
dut.expect_exact('Delay for 10 seconds')
dut.expect_exact('Unsubscribing and deleting tasks')
dut.expect_exact('Complete')

View File

@ -676,7 +676,6 @@ components/esp_system/include/esp_private/startup_internal.h
components/esp_system/include/esp_private/system_internal.h components/esp_system/include/esp_private/system_internal.h
components/esp_system/include/esp_private/usb_console.h components/esp_system/include/esp_private/usb_console.h
components/esp_system/include/esp_task.h components/esp_system/include/esp_task.h
components/esp_system/include/esp_task_wdt.h
components/esp_system/port/arch/riscv/expression_with_stack.c components/esp_system/port/arch/riscv/expression_with_stack.c
components/esp_system/port/arch/xtensa/expression_with_stack.c components/esp_system/port/arch/xtensa/expression_with_stack.c
components/esp_system/port/public_compat/brownout.h components/esp_system/port/public_compat/brownout.h