esp32: New Task Watchdog API

Legacy API of task watchdog used the same function esp_task_wdt_feed() to add
and feed a task. This caused issues of implicitly adding a task to the wdt list
if the function was used in shared code.

The new API introduces init, adding, feeding, deleting, deinit functions. Tasks
must now be explicitly added to the task watchdog using their handles. Deletion
must also be explicit using task handles. This resolves the issue of implicit
task additions to the task watchdog due to shared code calling
esp_task_wdt_feed().

Task watchdog is now fully configurable at runtime by calling the init and
deinit functions.

Also added functions to get the handles of idle tasks of the other core. This
helps when adding idle tasks to the watchdog at run time.

Configuring the task watchdog using menu config is still available, however
menu config will only result in calling the init and add functions for idle
tasks shortly after the scheduler starts.

Menu config also allows for using legacy behavior, however the legacy behavior
willcall the new API functions but with slight variations to make them legacy
compatible.

Documentation and example have also been updated

gcov_rtio.c headers updated to prevent error of freertos header files being
included in the wrong order.

Resolves issue TW#13265
This commit is contained in:
Darian Leung 2017-08-30 21:11:10 +08:00
parent ae9041ab71
commit 616baa239d
12 changed files with 645 additions and 194 deletions

View file

@ -14,9 +14,9 @@
// This module implements runtime file I/O API for GCOV.
#include "esp_task_wdt.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "soc/cpu.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"

View file

@ -527,10 +527,11 @@ config INT_WDT_CHECK_CPU1
Also detect if interrupts on CPU 1 are disabled for too long.
config TASK_WDT
bool "Task watchdog"
bool "Initialize Task Watchdog on startup"
default y
help
This watchdog timer can be used to make sure individual tasks are still running.
The Task Watchdog timer can be initialized at run time as well
config TASK_WDT_PANIC
bool "Invoke panic handler when Task Watchdog is triggered"
@ -539,33 +540,46 @@ config TASK_WDT_PANIC
help
Normally, the Task Watchdog will only print out a warning if it detects it has not
been fed. If this is enabled, it will invoke the panic handler instead, which
can then halt or reboot the chip.
can then halt or reboot the chip. This can also be configured at run time
by reinitializing the task watchdog.
config TASK_WDT_TIMEOUT_S
int "Task watchdog timeout (seconds)"
int "Task watchdog timeout (ms)"
depends on TASK_WDT
range 1 60
default 5
range 1 60000
default 5000
help
Timeout for the task WDT, in seconds.
Timeout for the task WDT, in ms.
config TASK_WDT_CHECK_IDLE_TASK
bool "Task watchdog watches CPU0 idle task"
config TASK_WDT_CHECK_IDLE_TASK_CPU0
bool "Add CPU0 idle task to task watchdog on startup"
depends on TASK_WDT
default y
help
With this turned on, the task WDT can detect if the idle task is not called within the task
watchdog timeout period. The idle task not being called usually is a symptom of another
With this turned on, the CPU0 idle task will be added to the task watchdog
on startup. Adding the idle task to the task watchdog allows for the detection
of CPU starvation. The idle task not being called is usually a symptom of another
task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the
idle task getting some runtime every now and then. Take Care: With this disabled, this
watchdog will trigger if no tasks register themselves within the timeout value.
idle task getting some runtime every now and then.
config TASK_WDT_CHECK_IDLE_TASK_CPU1
bool "Task watchdog also watches CPU1 idle task"
depends on TASK_WDT_CHECK_IDLE_TASK && !FREERTOS_UNICORE
bool "Add CPU0 idle task to task watchdog on startup"
depends on TASK_WDT && !FREERTOS_UNICORE
default y
help
Also check the idle task that runs on CPU1.
With this turned on, the CPU1 idle task will also be added to the task watchdog
on startup.
config TASK_WDT_LEGACY_BEHAVIOR
bool "Use legacy behavior for task watchdog"
depends on TASK_WDT
default n
help
Task wdt legacy behavior will add a task to the wdt list on its first
call to esp_task_wdt_feed(). Furthermore, tasks can only remove
themselves from the wdt task list when calling esp_task_wdt_delete().
Therefore esp_task_wdt_delete() should be called with no parameters
when legacy behavior is enabled.
#The brownout detector code is disabled (by making it depend on a nonexisting symbol) because the current revision of ESP32
#silicon has a bug in the brown-out detector, rendering it unusable for resetting the CPU.

View file

@ -323,9 +323,6 @@ void start_cpu0_default(void)
do_global_ctors();
#if CONFIG_INT_WDT
esp_int_wdt_init();
#endif
#if CONFIG_TASK_WDT
esp_task_wdt_init();
#endif
esp_cache_err_int_init();
esp_crosscore_int_init();
@ -400,6 +397,30 @@ static void main_task(void* args)
#endif
//Enable allocation in region where the startup stacks were located.
heap_caps_enable_nonos_stack_heaps();
//Initialize task wdt
#ifdef CONFIG_TASK_WDT
#ifdef CONFIG_TASK_WDT_PANIC
esp_task_wdt_init(CONFIG_TASK_WDT_TIMEOUT_S, true);
#else
esp_task_wdt_init(CONFIG_TASK_WDT_TIMEOUT_S, false);
#endif
#endif
//Add IDLE 0 to task wdt
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
if(idle_0 != NULL){
esp_task_wdt_add(idle_0);
}
#endif
//Add IDLE 1 to task wdt
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1
TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1);
if(idle_1 != NULL){
esp_task_wdt_add(idle_1);
}
#endif
app_main();
vTaskDelete(NULL);
}

View file

@ -15,60 +15,136 @@
#ifndef __ESP_TASK_WDT_H
#define __ESP_TASK_WDT_H
#include "freertos/task.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \defgroup Watchdog_APIs Watchdog APIs
* @brief Watchdog APIs
*/
/** @addtogroup Watchdog_APIs
* @{
*/
/*
This routine enables a more general-purpose task watchdog: tasks can individually
feed the watchdog and the watchdog will bark if one or more tasks haven't fed the
watchdog within the specified time. Optionally, the idle tasks can also configured
to feed the watchdog in a similar fashion, to detect CPU starvation.
This routine enables a more general-purpose task watchdog which uses the TIMERG0
WDT: All tasks subscribed to the task wdt must feed the wdt at least once
before the wdt times out or else the wdt will bark. The Idle tasks can also be
subscribed to the wdt. This allows for the detection of CPU starvation.
This uses the TIMERG0 WDT.
To enable/disable legacy behavior, toggle the CONFIG_TASK_WDT_LEGACY_BEHAVIOR
option in menuconfig. Under legacy behavior, a task will subscribe to the
wdt on its first call of esp_task_wdt_feed(). esp_task_wdt_add() SHOULD NOT BE
CALLED in an application if legacy behavior is enabled. esp_task_wdt_delete()
will only allow tasks to unsubscribe themselves from the task wdt under legacy
behavior. Enabling legacy behavior will also force the task wdt to be enabled on
startup.
*/
/**
* @brief Initialize the task watchdog. This is called in the init code, if the
* task watchdog is enabled in menuconfig.
* @brief Initialize the task watchdog timer
*
*/
void esp_task_wdt_init();
/**
* @brief Feed the watchdog. After the first feeding session, the watchdog will expect the calling
* task to keep feeding the watchdog until task_wdt_delete() is called.
* This function will initialize the task watchdog timer. If the wdt
* has already been initialized, calling this function again will change the
* timeout and panic configuration, then reset the timer count. Once the watchdog
* timer has been initialized, tasks can subscribe to the watchdog timer.
*
*/
void esp_task_wdt_feed();
/**
* @brief Delete the watchdog for the current task.
* @param[in] timeout Timeout(ms) period of watchdog timer
* @param[in] panic Flag that if set, will cause the wdt to trigger the
* panic handler when it times out
* @return
* - ESP_OK: Initialization was successful
* - ESP_ERR_NO_MEM: Initialization was unsuccessful due to lack of
* memory
*
* @note esp_task_wdt_init() must only be called after the scheduler
* started
*/
void esp_task_wdt_delete();
esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic);
/**
* @}
*/
* @brief Deinitialize the task watchdog timer
*
* This function will deinitialize the task watchdog timer. This function should
* be called when all tasks have unsubscribed form the watchdog timer.
*
* @return
* - ESP_OK: Watchdog timer successfully deinitialized
* - ESP_ERR_INVALID_STATE: Error due to wdt not being initialized, or
* if tasks are still subscribed to the wdt
*/
esp_err_t esp_task_wdt_deinit();
/**
* @brief Subscribes a tasks to the task watchdog timer
*
* This function subscribes a task to the task watchdog timer. Note that once
* subscribed, a task must periodically feed the watchdog timer. If the task
* is an IDLE task, this function will enable feeding from the idle hook
* automatically.
*
* @param[in] handle Handle of the task to be added. Input NULL
* to add the current running task
*
* @return
* - ESP_OK: Successfully subscribed the task to the wdt
* - ESP_ERR_INVALID_ARG: Failed as task is already subscribed
* - ESP_ERR_INVALID_STATE: Failed as wdt has not been initialized
* - ESP_ERR_NO_MEM: Failed due to lack of memory
*
* @note This function should not be called from application code if
* CONFIG_TASK_WDT_LEGACY_BEHAVIOR has been enabled. Legacy behavior
* uses esp_task_wdt_feed() to subscribe tasks to the wdt.
*/
esp_err_t esp_task_wdt_add(TaskHandle_t handle);
/**
* @brief Feed the current running task
*
* This function will feed the current running task if it has subscribed to the
* task watchdog timer. All non-idle tasks subscribed to the wdt must call this
* function at least once per watchdog timeout period. Idle tasks will feed
* the task watchdog automatically from their idle hooks.
*
* @return
* - ESP_OK: Successfully fed the current running task
* - ESP_ERR_INVALID_STATE: Failed to feed current running task as the
* wdt has not been initialized, or the
* current running task has not subscribed
*
* @note If CONFIG_TASK_WDT_LEGACY_BEHAVIOR is enabled, tasks will subscribe
* to the watchdog on their first call of esp_task_wdt_feed().
*/
esp_err_t esp_task_wdt_feed();
/**
* @brief Unsubscribes a task from the task watchdog timer
*
* This function unsubscribes a task from the task watchdog. After unsubscribing,
* the task should no longer feed the wdt. If the unsubscribing task is an idle
* task, this function will disable feeding from the idle hook automatically.
*
* @param[in] handle Handle of the task to be deleted. Input NULL
* if deleting the current running task.
*
* @return
* - ESP_OK: Successfully unsubscribed task form the wdt
* - ESP_ERR_INVALID_ARG: Failed to unsubscribe task as it was not
* subscribed to begin with
* - ESP_ERR_INVALID_STATE: Failed to unsubscribe task as wdt has not
* been initialized yet
*
* @note Legacy behavior only allows tasks to unsubscribe from the wdt on their
* own behalf. Therefore, if CONFIG_TASK_WDT_LEGACY_BEHAVIOR is
* enabled, this esp_task_wdt_delete() will accept no parameters and
* unsubscribe the calling task from the wdt.
*/
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
esp_err_t esp_task_wdt_delete();
#else
esp_err_t esp_task_wdt_delete(TaskHandle_t handle);
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
#ifdef __cplusplus
}
#endif
#endif
#endif //__ESP_TASK_WDT_H

View file

@ -20,6 +20,7 @@
#include <string.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
@ -28,6 +29,7 @@
#include "esp_err.h"
#include "esp_intr.h"
#include "esp_intr_alloc.h"
#include "esp_ipc.h"
#include "esp_attr.h"
#include "esp_freertos_hooks.h"
#include "soc/timer_group_struct.h"
@ -37,25 +39,80 @@
#include "esp_task_wdt.h"
#if CONFIG_TASK_WDT
static const char* TAG = "task_wdt";
//Assertion macro that exits from critical section and returns 'ret'
#define ASSERT_EXIT_CRIT_RET_ERR(cond, ret) ({ \
if(!(cond)){ \
portEXIT_CRITICAL(&taskwdt_spinlock); \
return ret; \
} \
})
typedef struct wdt_task_t wdt_task_t;
struct wdt_task_t {
TaskHandle_t task_handle;
bool fed_watchdog;
bool fed;
wdt_task_t *next;
};
static wdt_task_t *wdt_task_list=NULL;
typedef struct task_wdt_config_t task_wdt_config_t;
struct task_wdt_config_t {
wdt_task_t *list;
uint32_t timeout;
bool panic;
bool idle_enable[portNUM_PROCESSORS];
TaskHandle_t idle_handles[portNUM_PROCESSORS];
intr_handle_t intr_handle;
};
static task_wdt_config_t *wdt_config = NULL;
static portMUX_TYPE taskwdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
/*
* Internal function that checks the list for target task. Returns the list item
* if found and returns null if not found. Also checks if the all the other
* tasks have checked in. Called within critical.
*/
static wdt_task_t *check_list(TaskHandle_t handle, bool *checked)
{
wdt_task_t *target = NULL;
*checked = true;
for(wdt_task_t *task = wdt_config->list; task != NULL; task = task->next){
if(task->task_handle == handle){
target = task; //Get pointer to target task list member
}else{
if(task->fed == false){ //If a task has yet to check in
*checked = false;
}
}
}
return target;
}
static void task_wdt_isr(void *arg) {
/*
* Resets the wdt and fed flags of each task on the list. Called within critical
*/
static void feed_wdt()
{
//All tasks have checked in; time to feed the hw watchdog.
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
//Reset fed flags on all tasks in list
for (wdt_task_t *task = wdt_config->list; task != NULL; task = task->next){
task->fed=false;
}
}
/*
* ISR for when wdt expires. Checks for which tasks have not fed. Trigger panic
* if configured to do so in initialization
*/
static void task_wdt_isr(void *arg)
{
portENTER_CRITICAL(&taskwdt_spinlock);
wdt_task_t *wdttask;
const char *cpu;
//Feed the watchdog so we do not reset
//Feed the watchdog so we don't reset
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
@ -65,16 +122,14 @@ static void task_wdt_isr(void *arg) {
//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
//than the badness caused by a spinlock here.
portENTER_CRITICAL(&taskwdt_spinlock);
if (!wdt_task_list) {
//No task on list. Maybe none registered yet.
if (!wdt_config->list) { //No tasks on list
portEXIT_CRITICAL(&taskwdt_spinlock);
return;
}
//Watchdog got triggered because at least one task did not report in.
ets_printf("Task watchdog got triggered. The following tasks did not feed the watchdog in time:\n");
for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) {
if (!wdttask->fed_watchdog) {
for (wdttask=wdt_config->list; wdttask!=NULL; wdttask=wdttask->next) {
if (!wdttask->fed) {
cpu=xTaskGetAffinity(wdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1");
if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu=DRAM_STR("CPU 0/1");
ets_printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu);
@ -85,125 +140,243 @@ static void task_wdt_isr(void *arg) {
ets_printf("CPU %d: %s\n", x, pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(x)));
}
#if CONFIG_TASK_WDT_PANIC
ets_printf("Aborting.\n");
abort();
#endif
portEXIT_CRITICAL(&taskwdt_spinlock);
}
void esp_task_wdt_feed() {
wdt_task_t *wdttask=wdt_task_list;
bool found_task=false, do_feed_wdt=true;
TaskHandle_t handle=xTaskGetCurrentTaskHandle();
portENTER_CRITICAL(&taskwdt_spinlock);
//Walk the linked list of wdt tasks to find this one, as well as see if we need to feed
//the real watchdog timer.
for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) {
//See if we are at the current task.
if (wdttask->task_handle == handle) {
wdttask->fed_watchdog=true;
found_task=true;
}
//If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog.
if (!wdttask->fed_watchdog) do_feed_wdt=false;
}
if (!found_task) {
//This is the first time the task calls the task_wdt_feed function. Create a new entry for it in
//the linked list.
wdt_task_t *newtask=malloc(sizeof(wdt_task_t));
memset(newtask, 0, sizeof(wdt_task_t));
newtask->task_handle=handle;
newtask->fed_watchdog=true;
if (wdt_task_list == NULL) {
wdt_task_list=newtask;
} else {
for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ;
wdttask->next=newtask;
}
}
if (do_feed_wdt) {
//All tasks have checked in; time to feed the hw watchdog.
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
//Reset fed_watchdog status
for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false;
}
portEXIT_CRITICAL(&taskwdt_spinlock);
}
void esp_task_wdt_delete() {
TaskHandle_t handle=xTaskGetCurrentTaskHandle();
wdt_task_t *wdttask=wdt_task_list;
portENTER_CRITICAL(&taskwdt_spinlock);
//Wdt task list can't be empty
if (!wdt_task_list) {
ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?");
if (wdt_config->panic){
ets_printf("Aborting.\n");
portEXIT_CRITICAL(&taskwdt_spinlock);
return;
}
if (handle==wdt_task_list) {
//Current task is first on list.
wdt_task_list=wdt_task_list->next;
free(wdttask);
} else {
//Find current task in list
if (wdt_task_list->task_handle==handle) {
//Task is the very first one.
wdt_task_t *freeme=wdt_task_list;
wdt_task_list=wdt_task_list->next;
free(freeme);
portEXIT_CRITICAL(&taskwdt_spinlock);
return;
}
while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next;
if (!wdttask->next) {
ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!");
portEXIT_CRITICAL(&taskwdt_spinlock);
return;
}
wdt_task_t *freeme=wdttask->next;
wdttask->next=wdttask->next->next;
free(freeme);
abort();
}
portEXIT_CRITICAL(&taskwdt_spinlock);
}
#if CONFIG_TASK_WDT_CHECK_IDLE_TASK
static bool idle_hook(void) {
#if !CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1
if (xPortGetCoreID()!=0) return true;
#endif
esp_task_wdt_feed();
/*
* Idle hook for Idle Task to feed the wdt. Will only call feed if the idle task
* of that core has been added to wdt task list.
*/
static bool idle_hook(void)
{
uint32_t core_id = xPortGetCoreID();
if (wdt_config->idle_enable[core_id]){
esp_task_wdt_feed();
}
return true;
}
#endif
/*
* Initializes the task watchdog timer by allocating memory for the config data
* structure, obtaining the idle task handles/registering idle hooks, and
* 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(&taskwdt_spinlock);
if(wdt_config == NULL){ //wdt not initialized yet
//Allocate memory for wdt_config
wdt_config = calloc(1, sizeof(task_wdt_config_t));
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_NO_MEM);
void esp_task_wdt_init() {
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS
TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS
TIMERG0.wdt_config0.level_int_en=1;
TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt
TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system
TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000; //Set timeout before interrupt
TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000; //Set timeout before reset
TIMERG0.wdt_config0.en=1;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
#if CONFIG_TASK_WDT_CHECK_IDLE_TASK
esp_register_freertos_idle_hook(idle_hook);
#endif
ESP_ERROR_CHECK( esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, NULL) );
wdt_config->list = NULL;
wdt_config->timeout = timeout;
wdt_config->panic = panic;
//Get idle task handles
for(int i = 0; i < portNUM_PROCESSORS; i++){
wdt_config->idle_handles[i] = xTaskGetIdleTaskHandleForCPU( i );
wdt_config->idle_enable[i] = false;
}
//Register Idle Hook and Interrupts
esp_register_freertos_idle_hook(idle_hook);
ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &wdt_config->intr_handle))
//Configure hardware timer
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS
TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS
TIMERG0.wdt_config0.level_int_en=1;
TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt
TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system
TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
TIMERG0.wdt_config2=wdt_config->timeout*2; //Set timeout before interrupt
TIMERG0.wdt_config3=wdt_config->timeout*4; //Set timeout before reset
TIMERG0.wdt_config0.en=1;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0; //Enable write protection
}else{ //wdt_config previously initialized
//Reconfigure task wdt
wdt_config->panic = panic;
wdt_config->timeout = timeout;
//Reconfigure hardware timer
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
TIMERG0.wdt_config0.en=0; //Disable timer
TIMERG0.wdt_config2=wdt_config->timeout*2; //Set timeout before interrupt
TIMERG0.wdt_config3=wdt_config->timeout*4; //Set timeout before reset
TIMERG0.wdt_config0.en=1; //Renable timer
TIMERG0.wdt_feed=1; //Reset timer
TIMERG0.wdt_wprotect=0; //Enable write protection
}
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}
esp_err_t esp_task_wdt_deinit()
{
portENTER_CRITICAL(&taskwdt_spinlock);
//wdt must already be initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
//Task list must be empty
ASSERT_EXIT_CRIT_RET_ERR((wdt_config->list == NULL), ESP_ERR_INVALID_STATE);
//Disable hardware timer
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
TIMERG0.wdt_config0.en=0; //Disable timer
TIMERG0.wdt_wprotect=0; //Enable write protection
esp_deregister_freertos_idle_hook(idle_hook); //deregister idle hook
ESP_ERROR_CHECK(esp_intr_free(wdt_config->intr_handle)) //Unregister interrupt
free(wdt_config); //Free wdt_config
wdt_config = NULL;
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}
esp_err_t esp_task_wdt_add(TaskHandle_t handle)
{
portENTER_CRITICAL(&taskwdt_spinlock); //Nested critical in Legacy (called from feed)
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
wdt_task_t *target_task;
#else
//Return error if wdt has not been initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
wdt_task_t *target_task;
bool all_fed;
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 checked in
target_task = check_list(handle, &all_fed);
//Return error if task has already been added
ASSERT_EXIT_CRIT_RET_ERR((target_task == NULL), ESP_ERR_INVALID_ARG);
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
//Add task to wdt list
target_task = calloc(1,sizeof(wdt_task_t));
//Return error if calloc failed
ASSERT_EXIT_CRIT_RET_ERR((target_task != NULL), ESP_ERR_NO_MEM);
target_task->task_handle = handle;
target_task->fed = true;
target_task->next = NULL;
if (wdt_config->list == NULL) { //Adding to empty list
wdt_config->list = target_task;
} else { //Adding to tail of list
wdt_task_t *task;
for (task = wdt_config->list; task->next != NULL; task = task->next){
; //point task to current tail of wdt task list
}
task->next = target_task;
}
//If idle task, set idle_enable flag
for(int i = 0; i < portNUM_PROCESSORS; i++){
if(handle == wdt_config->idle_handles[i]){
wdt_config->idle_enable[i] = true;
break;
}
}
#ifndef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
if(all_fed){ //Reset if all other tasks in list have checked in
feed_wdt();
}
#endif
portEXIT_CRITICAL(&taskwdt_spinlock); //Nested critical if Legacy
return ESP_OK;
}
esp_err_t esp_task_wdt_feed()
{
portENTER_CRITICAL(&taskwdt_spinlock);
//Return error if wdt has not been initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
wdt_task_t *target_task;
bool all_fed;
//Check if tasks exists in task list, and if all other tasks have checked in
target_task = check_list(handle, &all_fed);
if(target_task == NULL){
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
//Add task to wdt task list if it doesn't exist, return error if failed to add
ASSERT_EXIT_CRIT_RET_ERR((esp_task_wdt_add(handle) == ESP_OK), ESP_ERR_NO_MEM);
#else
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_ERR_INVALID_STATE; //Return error if task does not exist
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
}else{
target_task->fed = true; //Feed the task
}
if(all_fed){ //Reset if all other tasks in list have checked in
feed_wdt();
}
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
esp_err_t esp_task_wdt_delete()
{
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
#else
esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
{
if(handle == NULL){
handle = xTaskGetCurrentTaskHandle();
}
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
portENTER_CRITICAL(&taskwdt_spinlock);
//Return error if wdt has not been initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
wdt_task_t *target_task;
bool all_fed;
target_task = check_list(handle, &all_fed);
//Task doesn't exist on list. Return error
ASSERT_EXIT_CRIT_RET_ERR((target_task != NULL), ESP_ERR_INVALID_ARG);
if(target_task == wdt_config->list){ //target_task is head of list. Delete
wdt_config->list = target_task->next;
free(target_task);
}else{ //target_task not head of list. Delete
wdt_task_t *prev;
for (prev = wdt_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, disable idle_enable flag
for(int i = 0; i < portNUM_PROCESSORS; i++){
if(handle == wdt_config->idle_handles[i]){
wdt_config->idle_enable[i] = false;
}
}
if(all_fed){ //Reset timer if all remaining tasks have checked in
feed_wdt();
}
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}

View file

@ -1382,6 +1382,15 @@ BaseType_t xTaskCallApplicationTaskHook( TaskHandle_t xTask, void *pvParameter )
*/
TaskHandle_t xTaskGetIdleTaskHandle( void );
/**
* xTaskGetIdleTaskHandleForCPU() is only available if
* INCLUDE_xTaskGetIdleTaskHandle is set to 1 in FreeRTOSConfig.h.
*
* Simply returns the idle task handle of a given cpu. It is not valid to call
* xTaskGetIdleTaskHandleForCPU() before the scheduler has been started.
*/
TaskHandle_t xTaskGetIdleTaskHandleForCPU( UBaseType_t cpuid );
/**
* configUSE_TRACE_FACILITY must be defined as 1 in FreeRTOSConfig.h for
* uxTaskGetSystemState() to be available.

View file

@ -2371,6 +2371,18 @@ UBaseType_t uxTaskGetNumberOfTasks( void )
return xIdleTaskHandle[ xPortGetCoreID() ];
}
TaskHandle_t xTaskGetIdleTaskHandleForCPU( UBaseType_t cpuid )
{
TaskHandle_t xReturn = NULL;
/* If xTaskGetIdleTaskHandleForCPU() is called before the scheduler has been
started, then xIdleTaskHandle will be NULL. */
if (cpuid < portNUM_PROCESSORS) {
configASSERT( ( xIdleTaskHandle[ cpuid ] != NULL ) );
xReturn = xIdleTaskHandle[ cpuid ];
}
return xReturn;
}
#endif /* INCLUDE_xTaskGetIdleTaskHandle */
/*----------------------------------------------------------*/

View file

@ -28,23 +28,40 @@ Task watchdog
^^^^^^^^^^^^^
Any tasks can elect to be watched by the task watchdog. If such a task does not feed the watchdog within the time
specified by the task watchdog timeout (which is configurable using ``make menuconfig``), the watchdog will
print out a warning with information about which processes are running on the ESP32 CPUs and which processes
failed to feed the watchdog.
specified by the task watchdog timeout, the watchdog will print out a warning with information about which processes
are running on the ESP32 CPUs and which processes failed to feed the watchdog.
By default, the task watchdog watches the idle tasks. The usual cause of idle tasks not feeding the watchdog
Ideally, the task watchdog should watch the idle tasks. The usual cause of the idle tasks not feeding the watchdog
is a higher-priority process looping without yielding to the lower-priority processes, and can be an indicator
of badly-written code that spinloops on a peripheral or a task that is stuck in an infinite loop.
Other task can elect to be watched by the task watchdog by calling ``esp_task_wdt_feed()``. Calling this routine
for the first time will register the task to the task watchdog; calling it subsequent times will feed
the watchdog. If a task does not want to be watched anymore (e.g. because it is finished and will call
``vTaskDelete()`` on itself), it needs to call ``esp_task_wdt_delete()``.
Other task can elect to be watched by the task watchdog. If the watchdog watches multiple tasks, all the tasks
must feed before the watchdog times out. If one or more tasks fails to feed, the watchdog timeout will trigger.
The watchdog timer can be initialized using :cpp:func:`esp_task_wdt_init` which will configure the hardware timer and
establish the hardware timer interrupts. In order for a task to be watched by the task watchdog, a task must subscribe to
the task watchdog using :cpp:func:`esp_task_wdt_add`. Once added, each subscribed task must call :cpp:func:`esp_task_wdt_feed`
periodically to prevent the watchdog from timing out. If all tasks being watched have fed, the watchdog timer counter immediately resets
and starts recounting to the timeout value from zero. To unsubscribe a task from the task watchdog, call :cpp:func:`esp_task_wdt_delete()`.
Once all tasks have been unsubscribed form the task watchdog, :cpp:func:`esp_task_wdt_deinit()` can be called to deinitialize
the task watchdog. Deinitialization will stop the hardware timer, deregister the hardware timer interrupts, and remove the idle hooks
if idle tasks were being watched.
The task watchdog is built around the hardware watchdog in timer group 0. If this watchdog for some reason
By setting the task watchdog options in ``make menuconfig``, the task watchdog can be automatically initialized
during startup by enabling the :ref:`CONFIG_TASK_WDT`. Moreover the two Idle tasks can also be subscibed to the
task watchdog on startup as well by enabling :ref:`CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0` and
:ref:`CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1`.
The task watchdog is built around the hardware watchdog in timer group 0. If the watchdog for some reason
cannot execute the interrupt handler that prints the task data (e.g. because IRAM is overwritten by garbage
or interrupts are disabled entirely) it will hard-reset the SOC.
Note: ``make menuconfig`` provides a :ref:`CONFIG_TASK_WDT_LEGACY_BEHAVIOR` option which will change the behavior of the
task watchdog to make it compatible with the legacy code. Note that the legacy behavior causes tasks to subscribe to the
task watchdog on their first call to :cpp:func:`esp_task_wdt_feed`. Moreover, legacy behavior only allows
tasks to unsubscribe on their own behalf when calling :cpp:func:`esp_task_wdt_delete()`. It is strongly recommended that
non-legacy behavior is used as additions/deletions/feeds are explicit.
JTAG and watchdogs
^^^^^^^^^^^^^^^^^^
@ -53,20 +70,27 @@ CPU. This makes it very hard to debug code; that is why the OpenOCD config will
This does mean that you will not get any warnings or panics from either the task or interrupt watchdog when the ESP32
is connected to OpenOCD via JTAG.
API Reference
-------------
Interrupt Watchdog API Reference
--------------------------------
Header Files
^^^^^^^^^^^^
Header File
^^^^^^^^^^^
* :component_file:`esp32/include/esp_int_wdt.h`
* :component_file:`esp32/include/esp_task_wdt.h`
Functions
---------
.. doxygenfunction:: esp_int_wdt_init
.. doxygenfunction:: esp_task_wdt_init
.. doxygenfunction:: esp_task_wdt_feed
.. doxygenfunction:: esp_task_wdt_delete
Task Watchdog API Reference
----------------------------
NOTE: Ensure that the :ref:`CONFIG_TASK_WDT_LEGACY_BEHAVIOR` configuraiton is disabled using ``make menuconfig``
to prevent legacy behavior of the task watchdog.
A full example using the Task Watchdog is available in esp-idf: :example:`system/task_watchdog`
.. include:: /_build/inc/esp_task_wdt.inc

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := task_watchdog
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,12 @@
# Example: task_watchdog
This test code shows how to initialize the task watchdog, add tasks to the
watchdog task list, feeding the tasks, deleting tasks from the watchdog task
list, and deinitializing the task watchdog.
### Test:
Program should run without error. Comment out "esp_task_wdt_feed()" to observe
a watchdog timeout.

View file

@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile.
#

View file

@ -0,0 +1,98 @@
/* Task_Watchdog Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_task_wdt.h"
#ifndef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
#define TIMEOUT 4000
#define TASK_PERIOD 2000
#define ASSERT_PRINT_ERROR(cond, str) ({ \
if(!(cond)){ \
printf("%s\n",str); \
} \
})
void feeding_task(void *arg){
int core = xPortGetCoreID();
//Simple task that periodically feeds watchdog
while(1){
if(esp_task_wdt_feed() == ESP_OK){
printf("Feed Task %d\n", core);
}
vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD));
}
}
void app_main(void)
{
//Initialize task watchdog
if(esp_task_wdt_init(TIMEOUT, false) != ESP_OK){
printf("Error\n");
}
//Create tasks
TaskHandle_t handle_0;
TaskHandle_t handle_1;
xTaskCreatePinnedToCore(feeding_task, "Feed Task 0", 2000, NULL, 15, &handle_0, 0);
xTaskCreatePinnedToCore(feeding_task, "Feed Task 1", 2000, NULL, 15, &handle_1, 1);
//Add tasks to wdt
ASSERT_PRINT_ERROR(esp_task_wdt_add(handle_0) == ESP_OK, "Add failed");
ASSERT_PRINT_ERROR(esp_task_wdt_add(handle_1) == ESP_OK, "Add failed");
#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
//Adding Idles. Idle hook will call feed automatically
TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
ASSERT_PRINT_ERROR(idle_0 != NULL, "Get Idle handle failed");
ASSERT_PRINT_ERROR(esp_task_wdt_add(idle_0) == ESP_OK, "Add failed");
#endif
#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1
TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1);
ASSERT_PRINT_ERROR(idle_1 != NULL, "Get Idle handle failed");
ASSERT_PRINT_ERROR(esp_task_wdt_add(idle_1) == ESP_OK, "Add failed");
#endif
//Wait 10 seconds
vTaskDelay(pdMS_TO_TICKS(10000));
//Remove tasks form wdt
ASSERT_PRINT_ERROR(esp_task_wdt_delete(handle_0) == ESP_OK, "Failed to delete");
ASSERT_PRINT_ERROR(esp_task_wdt_delete(handle_1) == ESP_OK, "Failed to delete");
//Delete tasks
vTaskDelete(handle_0);
vTaskDelete(handle_1);
#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
ASSERT_PRINT_ERROR(esp_task_wdt_delete(idle_0) == ESP_OK, "Failed to delete");
#endif
#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1
ASSERT_PRINT_ERROR(esp_task_wdt_delete(idle_1) == ESP_OK, "Failed to delete");
#endif
#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1
//Deinit task watchdog. Can only do so once all tasks (including idle) are deleted from wdt
ASSERT_PRINT_ERROR(esp_task_wdt_deinit() == ESP_OK, "deinit failed");
#endif
#endif
printf("Complete\n");
}
#endif