From 9d63e1da4a233f8b3043157f58894fb9b2c2668d Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Mon, 9 Oct 2017 18:07:30 +0800 Subject: [PATCH] New Task Watchdog API (Revert of Revert) This commit reverts the revert on the new task watchdog API. It also fixes the following bug which caused the reversion. - sdkconfig TASK_WDT_TIMEOUT_S has been reverted from the unit of ms back to the unit of seconds. Fixes bug where projects using the new API without rebuilding sdkconfig would cause the old default value of 5 to be interpreted in ms. This commit also adds the following features to the task watchdog - Updated idle hook registration to be compatible with dual core hooks - Updated dual core hooks to support deregistration for cpu - Legacy mode has been removed and esp_task_wdt_feed() is now replaced by esp_task_wdt_reset(). esp_task_wdt_feed() is deprecated - Idle hooks to reset are now registered/deregistered when the idle tasks are added/deleted from the Task Watchdog instead of at Task Watchdog init/deinit - Updated example --- components/esp32/Kconfig | 41 +- components/esp32/cpu_start.c | 26 +- components/esp32/freertos_hooks.c | 32 +- components/esp32/include/esp_freertos_hooks.h | 14 + components/esp32/include/esp_task_wdt.h | 175 +++++-- components/esp32/task_wdt.c | 475 +++++++++++++----- components/freertos/include/freertos/task.h | 9 + components/freertos/tasks.c | 12 + docs/api-reference/system/wdts.rst | 93 ++-- examples/system/task_watchdog/Makefile | 9 + examples/system/task_watchdog/README.md | 12 + .../system/task_watchdog/main/component.mk | 3 + .../main/task_watchdog_example_main.c | 85 ++++ 13 files changed, 750 insertions(+), 236 deletions(-) create mode 100644 examples/system/task_watchdog/Makefile create mode 100644 examples/system/task_watchdog/README.md create mode 100644 examples/system/task_watchdog/main/component.mk create mode 100644 examples/system/task_watchdog/main/task_watchdog_example_main.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index dd5ac7041..5a5c9c471 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -534,45 +534,50 @@ 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 Timer 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 used to make sure individual tasks are still + running. Enabling this option will cause the Task Watchdog Timer to be + initialized automatically at startup. The Task Watchdog timer can be + initialized after startup as well (see Task Watchdog Timer API Reference) config TASK_WDT_PANIC - bool "Invoke panic handler when Task Watchdog is triggered" + bool "Invoke panic handler on Task Watchdog timeout" depends on TASK_WDT default n 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. + If this option is enabled, the Task Watchdog Timer will be configured to + trigger the panic handler when it times out. This can also be configured + at run time (see Task Watchdog Timer API Reference) config TASK_WDT_TIMEOUT_S - int "Task watchdog timeout (seconds)" + int "Task Watchdog timeout period (seconds)" depends on TASK_WDT range 1 60 default 5 help - Timeout for the task WDT, in seconds. + Timeout period configuration for the Task Watchdog Timer in seconds. + This is also configurable at run time (see Task Watchdog Timer API Reference) -config TASK_WDT_CHECK_IDLE_TASK - bool "Task watchdog watches CPU0 idle task" +config TASK_WDT_CHECK_IDLE_TASK_CPU0 + bool "Watch CPU0 Idle Task" 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 - 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. + If this option is enabled, the Task Watchdog Timer will watch the CPU0 + Idle Task. Having the Task Watchdog watch the Idle Task allows for detection + of CPU starvation as the Idle Task not being called is usually a symptom of + CPU starvation. Starvation of the Idle Task is detrimental as FreeRTOS household + tasks depend on the 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 "Watch CPU1 Idle Task" + depends on TASK_WDT && !FREERTOS_UNICORE default y help - Also check the idle task that runs on CPU1. + If this option is enabled, the Task Wtachdog Timer will wach the CPU1 + Idle Task. #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. diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 2132239fb..0feed97bd 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -335,9 +335,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(); @@ -426,6 +423,29 @@ 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 if configured to do so +#ifdef CONFIG_TASK_WDT_PANIC + ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_TASK_WDT_TIMEOUT_S, true)) +#elif CONFIG_TASK_WDT + ESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_TASK_WDT_TIMEOUT_S, false)) +#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_ERROR_CHECK(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_ERROR_CHECK(esp_task_wdt_add(idle_1)) + } +#endif + app_main(); vTaskDelete(NULL); } diff --git a/components/esp32/freertos_hooks.c b/components/esp32/freertos_hooks.c index abe88a97e..d2b24e995 100644 --- a/components/esp32/freertos_hooks.c +++ b/components/esp32/freertos_hooks.c @@ -106,13 +106,35 @@ esp_err_t esp_register_freertos_tick_hook(esp_freertos_tick_cb_t new_tick_cb) return esp_register_freertos_tick_hook_for_cpu(new_tick_cb, xPortGetCoreID()); } +void esp_deregister_freertos_idle_hook_for_cpu(esp_freertos_idle_cb_t old_idle_cb, UBaseType_t cpuid) +{ + portENTER_CRITICAL(&hooks_spinlock); + if(cpuid >= portNUM_PROCESSORS){ + return; + } + for(int n = 0; n < MAX_HOOKS; n++){ + if(idle_cb[cpuid][n] == old_idle_cb) idle_cb[cpuid][n] = NULL; + } + portEXIT_CRITICAL(&hooks_spinlock); +} + void esp_deregister_freertos_idle_hook(esp_freertos_idle_cb_t old_idle_cb) { portENTER_CRITICAL(&hooks_spinlock); for(int m = 0; m < portNUM_PROCESSORS; m++) { - for(int n = 0; n < MAX_HOOKS; n++){ - if(idle_cb[m][n] == old_idle_cb) idle_cb[m][n] = NULL; - } + esp_deregister_freertos_idle_hook_for_cpu(old_idle_cb, m); + } + portEXIT_CRITICAL(&hooks_spinlock); +} + +void esp_deregister_freertos_tick_hook_for_cpu(esp_freertos_tick_cb_t old_tick_cb, UBaseType_t cpuid) +{ + portENTER_CRITICAL(&hooks_spinlock); + if(cpuid >= portNUM_PROCESSORS){ + return; + } + for(int n = 0; n < MAX_HOOKS; n++){ + if(tick_cb[cpuid][n] == old_tick_cb) tick_cb[cpuid][n] = NULL; } portEXIT_CRITICAL(&hooks_spinlock); } @@ -121,9 +143,7 @@ void esp_deregister_freertos_tick_hook(esp_freertos_tick_cb_t old_tick_cb) { portENTER_CRITICAL(&hooks_spinlock); for(int m = 0; m < portNUM_PROCESSORS; m++){ - for(int n = 0; n < MAX_HOOKS; n++){ - if(tick_cb[m][n] == old_tick_cb) tick_cb[m][n] = NULL; - } + esp_deregister_freertos_tick_hook_for_cpu(old_tick_cb, m); } portEXIT_CRITICAL(&hooks_spinlock); } diff --git a/components/esp32/include/esp_freertos_hooks.h b/components/esp32/include/esp_freertos_hooks.h index 3d2111926..5f24bc35a 100644 --- a/components/esp32/include/esp_freertos_hooks.h +++ b/components/esp32/include/esp_freertos_hooks.h @@ -89,6 +89,13 @@ esp_err_t esp_register_freertos_tick_hook_for_cpu(esp_freertos_tick_cb_t new_tic */ esp_err_t esp_register_freertos_tick_hook(esp_freertos_tick_cb_t new_tick_cb); +/** + * @brief Unregister an idle callback from the idle hook of the specified core + * + * @param[in] old_idle_cb Callback to be unregistered + * @param[in] cpuid id of the core + */ +void esp_deregister_freertos_idle_hook_for_cpu(esp_freertos_idle_cb_t old_idle_cb, UBaseType_t cpuid); /** * @brief Unregister an idle callback. If the idle callback is registered to @@ -99,6 +106,13 @@ esp_err_t esp_register_freertos_tick_hook(esp_freertos_tick_cb_t new_tick_cb); */ void esp_deregister_freertos_idle_hook(esp_freertos_idle_cb_t old_idle_cb); +/** + * @brief Unregister a tick callback from the tick hook of the specified core + * + * @param[in] old_tick_cb Callback to be unregistered + * @param[in] cpuid id of the core + */ +void esp_deregister_freertos_tick_hook_for_cpu(esp_freertos_tick_cb_t old_tick_cb, UBaseType_t cpuid); /** * @brief Unregister a tick callback. If the tick callback is registered to the diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h index eb7737700..60b0e5e5c 100644 --- a/components/esp32/include/esp_task_wdt.h +++ b/components/esp32/include/esp_task_wdt.h @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // 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 @@ -12,63 +12,152 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef __ESP_TASK_WDT_H -#define __ESP_TASK_WDT_H +#pragma once + +#include "freertos/FreeRTOS.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 uses the TIMERG0 WDT. -*/ - - /** - * @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 (TWDT) * - */ -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 configures and initializes the TWDT. If the TWDT is already + * initialized when this function is called, this function will update the + * TWDT's timeout period and panic configurations instead. After initializing + * the TWDT, any task can elect to be watched by the TWDT by subscribing to it + * using esp_task_wdt_add(). * - */ - -void esp_task_wdt_feed(); - - -/** - * @brief Delete the watchdog for the current task. + * @param[in] timeout Timeout period of TWDT in seconds + * @param[in] panic Flag that controls whether the panic handler will be + * executed when the TWDT times out * + * @return + * - ESP_OK: Initialization was successful + * - ESP_ERR_NO_MEM: Initialization failed 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 (TWDT) + * + * This function will deinitialize the TWDT. Calling this function whilst tasks + * are still subscribed to the TWDT, or when the TWDT is already deinitialized, + * will result in an error code being returned. + * + * @return + * - ESP_OK: TWDT successfully deinitialized + * - ESP_ERR_INVALID_STATE: Error, tasks are still subscribed to the TWDT + * - ESP_ERR_NOT_FOUND: Error, TWDT has already been deinitialized + */ +esp_err_t esp_task_wdt_deinit(); + +/** + * @brief Subscribe a task to the Task Watchdog Timer (TWDT) + * + * This function subscribes a task to the TWDT. Each subscribed task must + * periodically call esp_task_wdt_reset() to prevent the TWDT from elapsing its + * timeout period. Failure to do so will result in a TWDT timeout. If the task + * being subscribed is one of the Idle Tasks, this function will automatically + * enable esp_task_wdt_reset() to called from the Idle Hook of the Idle Task. + * Calling this function whilst the TWDT is uninitialized or attempting to + * subscribe an already subscribed task will result in an error code being + * returned. + * + * @param[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 + * running task + * + * This function will reset the TWDT on behalf of the currently running task. + * Each subscribed task must periodically call this function to prevent the + * TWDT from timing out. If one or more subscribed tasks fail to reset the + * TWDT on their own behalf, a TWDT timeout will occur. If the IDLE tasks have + * been subscribed to the TWDT, they will automatically call this function from + * their idle hooks. Calling this function from a task that has not subscribed + * to the TWDT, or when the TWDT is uninitialized will result in an error code + * being returned. + * + * @return + * - 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 + * to the TWDT + * - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet + */ +esp_err_t esp_task_wdt_reset(); + +/** + * @brief Unsubscribes a task from the Task Watchdog Timer (TWDT) + * + * This function will unsubscribe a task from the TWDT. After being + * unsubscribed, the task should no longer call esp_task_wdt_reset(). If the + * task is an IDLE task, this function will automatically disable the calling + * of esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the + * TWDT is uninitialized or attempting to unsubscribe an already unsubscribed + * task from the TWDT will result in an error code being returned. + * + * @param[in] handle Handle of the task. Input NULL to unsubscribe the + * current running task. + * + * @return + * - ESP_OK: Successfully unsubscribed the task from the TWDT + * - ESP_ERR_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) + * + * This function will query whether a task is currently subscribed to the TWDT, + * or whether the TWDT is initialized. + * + * @param[in] handle Handle of the task. Input NULL to query the current + * running task. + * + * @return: + * - 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_INVALID_STATE: The TWDT is not initialized, therefore no tasks + * can be subscribed + */ +esp_err_t esp_task_wdt_status(TaskHandle_t handle); + +/** + * @brief Reset the TWDT on behalf of the current running task, or + * subscribe the TWDT to if it has not done so already + * + * @warning This function is deprecated, use esp_task_wdt_add() and + * esp_task_wdt_reset() instead + * + * This function is similar to esp_task_wdt_reset() and will reset the TWDT on + * behalf of the current running task. However if this task has not subscribed + * to the TWDT, this function will automatically subscribe the task. Therefore, + * an unsubscribed task will subscribe to the TWDT on its first call to this + * function, then proceed to reset the TWDT on subsequent calls of this + * function. + */ +void esp_task_wdt_feed() __attribute__ ((deprecated)); #ifdef __cplusplus } #endif - - - -#endif \ No newline at end of file diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index ce3f09b83..dc46e1c9b 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // 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 @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. - - #include #include #include #include #include #include "sdkconfig.h" +#include "freertos/FreeRTOSConfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -37,47 +36,115 @@ #include "esp_task_wdt.h" -#if CONFIG_TASK_WDT +//Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret' +#define ASSERT_EXIT_CRIT_RETURN(cond, ret) ({ \ + if(!(cond)){ \ + portEXIT_CRITICAL(&twdt_spinlock); \ + return ret; \ + } \ +}) -static const char* TAG = "task_wdt"; +//Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void +#define VOID_RETURN -typedef struct wdt_task_t wdt_task_t; -struct wdt_task_t { +//Structure used for each subscribed task +typedef struct twdt_task_t twdt_task_t; +struct twdt_task_t { TaskHandle_t task_handle; - bool fed_watchdog; - wdt_task_t *next; + bool has_reset; + twdt_task_t *next; }; -static wdt_task_t *wdt_task_list=NULL; -static portMUX_TYPE taskwdt_spinlock = portMUX_INITIALIZER_UNLOCKED; +//Structure used to hold run time configuration of the TWDT +typedef struct twdt_config_t twdt_config_t; +struct twdt_config_t { + twdt_task_t *list; //Linked list of subscribed tasks + uint32_t timeout; //Timeout period of TWDT + bool panic; //Flag to trigger panic when TWDT times out + intr_handle_t intr_handle; +}; +static twdt_config_t *twdt_config = NULL; +static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED; -static void task_wdt_isr(void *arg) { - wdt_task_t *wdttask; - const char *cpu; - //Feed the watchdog so we do not reset +/* + * Idle hook callback for Idle Tasks to reset the TWDT. This callback will only + * be registered to the Idle Hook of a particular core when the corresponding + * Idle Task subscribes to the TWDT. + */ +static bool idle_hook_cb(void) +{ + esp_task_wdt_reset(); + return true; +} + +/* + * Internal function that looks for the target task in the TWDT task list. + * Returns the list item if found and returns null if not found. Also checks if + * all the other tasks have reset. Should be called within critical. + */ +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; +} + +/* + * Resets the hardware timer and has_reset flags of each task on the list. + * Called within critical + */ +static void reset_hw_timer() +{ + //All tasks have reset; time to reset the hardware timer. TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; - //Ack interrupt + //Clear all has_reset flags in list + for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){ + task->has_reset=false; + } +} + +/* + * ISR for when TWDT times out. Checks for which tasks have not reset. Also + * triggers panic if configured to do so + */ +static void task_wdt_isr(void *arg) +{ + portENTER_CRITICAL(&twdt_spinlock); + twdt_task_t *twdttask; + const char *cpu; + //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset) + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + //Acknowledge interrupt TIMERG0.int_clr_timers.wdt=1; //We are taking a spinlock while doing I/O (ets_printf) here. Normally, that is a pretty //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. - 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) { - 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); + + //Return immediately if no tasks have been added to task list + ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN); + + //Watchdog got triggered because at least one task did not reset in time. + ets_printf("Task watchdog got triggered. The following tasks did not reset the watchdog in time:\n"); + for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) { + if (!twdttask->has_reset) { + cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1"); + if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) cpu=DRAM_STR("CPU 0/1"); + ets_printf(" - %s (%s)\n", pcTaskGetTaskName(twdttask->task_handle), cpu); } } ets_printf(DRAM_STR("Tasks currently running:\n")); @@ -85,125 +152,263 @@ 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); + if (twdt_config->panic){ //Trigger Panic if configured to do so + ets_printf("Aborting.\n"); + portEXIT_CRITICAL(&twdt_spinlock); + abort(); + } + + portEXIT_CRITICAL(&twdt_spinlock); } +/* + * Initializes the TWDT 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(&twdt_spinlock); + if(twdt_config == NULL){ //TWDT not initialized yet + //Allocate memory for wdt_config + twdt_config = calloc(1, sizeof(twdt_config_t)); + ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM); -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); + twdt_config->list = NULL; + twdt_config->timeout = timeout; + twdt_config->panic = panic; - //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; + //Register Interrupt and ISR + ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_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=twdt_config->timeout*2000; //Set timeout before interrupt + TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset + TIMERG0.wdt_config0.en=1; 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; + TIMERG0.wdt_wprotect=0; //Enable write protection + + }else{ //twdt_config previously initialized + //Reconfigure task wdt + twdt_config->panic = panic; + twdt_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=twdt_config->timeout*2000; //Set timeout before interrupt + TIMERG0.wdt_config3=twdt_config->timeout*4000; //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); + portEXIT_CRITICAL(&twdt_spinlock); + return ESP_OK; } -void esp_task_wdt_delete() { - TaskHandle_t handle=xTaskGetCurrentTaskHandle(); - wdt_task_t *wdttask=wdt_task_list; - portENTER_CRITICAL(&taskwdt_spinlock); +esp_err_t esp_task_wdt_deinit() +{ + portENTER_CRITICAL(&twdt_spinlock); + //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); - //Wdt task list can't be empty - if (!wdt_task_list) { - ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?"); - portEXIT_CRITICAL(&taskwdt_spinlock); + //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_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)) //Unregister interrupt + free(twdt_config); //Free twdt_config + twdt_config = NULL; + portEXIT_CRITICAL(&twdt_spinlock); + return ESP_OK; +} + +esp_err_t esp_task_wdt_add(TaskHandle_t handle) +{ + portENTER_CRITICAL(&twdt_spinlock); + //TWDT must already be initialized + ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); + + 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 + for(int i = 0; i < portNUM_PROCESSORS; i++){ + if(handle == xTaskGetIdleTaskHandleForCPU(i)){ + ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i)) + break; + } + } + + if(all_reset){ //Reset hardware timer if all other tasks in list have reset in + reset_hw_timer(); + } + + portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy + return ESP_OK; +} + +esp_err_t esp_task_wdt_reset() +{ + portENTER_CRITICAL(&twdt_spinlock); + //TWDT must already be initialized + ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); + + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + twdt_task_t *target_task; + bool all_reset; + + //Check if task exists in task list, and if all other tasks have reset + target_task = find_task_in_twdt_list(handle, &all_reset); + //Return error if trying to reset task that is not on the task list + ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND); + + 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(); + } + + portEXIT_CRITICAL(&twdt_spinlock); + return ESP_OK; +} + +esp_err_t esp_task_wdt_delete(TaskHandle_t handle) +{ + if(handle == NULL){ + handle = xTaskGetCurrentTaskHandle(); + } + 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; + bool all_reset; + target_task = find_task_in_twdt_list(handle, &all_reset); + //Task doesn't exist on list. Return error + ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG); + + if(target_task == twdt_config->list){ //target_task is head of list. Delete + twdt_config->list = target_task->next; + 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(); + } + + portEXIT_CRITICAL(&twdt_spinlock); + return ESP_OK; +} + +esp_err_t esp_task_wdt_status(TaskHandle_t handle) +{ + if(handle == NULL){ + handle = xTaskGetCurrentTaskHandle(); + } + + portENTER_CRITICAL(&twdt_spinlock); + //Return if TWDT is not initialized + ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); + + twdt_task_t *task; + for(task = twdt_config->list; task!=NULL; task=task->next){ + //Return ESP_OK if task is found + ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK); + } + + //Task could not be found + portEXIT_CRITICAL(&twdt_spinlock); + return ESP_ERR_NOT_FOUND; +} + +void esp_task_wdt_feed() +{ + portENTER_CRITICAL(&twdt_spinlock); + //Return immediately if TWDT has not been initialized + ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), VOID_RETURN); + + //Check if task is on list + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + bool all_reset; + twdt_task_t *target_task = find_task_in_twdt_list(handle, &all_reset); + + //reset the task if it's on the list, then return + if(target_task != NULL){ + target_task->has_reset = true; + if(all_reset){ + reset_hw_timer(); //Reset hardware timer if all other tasks have reset + } + portEXIT_CRITICAL(&twdt_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; + + //Add task if it's has not on list + target_task = calloc(1, sizeof(twdt_task_t)); + ASSERT_EXIT_CRIT_RETURN((target_task != NULL), VOID_RETURN); //If calloc failed + 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 wdt task list } - 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); + task->next = target_task; } - portEXIT_CRITICAL(&taskwdt_spinlock); + + portEXIT_CRITICAL(&twdt_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(); - return true; -} -#endif - - -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) ); -} - - -#endif diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index 2438d9600..073267445 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -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. diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index f6931c401..2263c939c 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -2369,6 +2369,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 */ /*----------------------------------------------------------*/ diff --git a/docs/api-reference/system/wdts.rst b/docs/api-reference/system/wdts.rst index e15ff53dd..adfa49693 100644 --- a/docs/api-reference/system/wdts.rst +++ b/docs/api-reference/system/wdts.rst @@ -4,8 +4,13 @@ Watchdogs Overview -------- -Esp-idf has support for two types of watchdogs: a task watchdog as well as an interrupt watchdog. Both can be -enabled using ``make menuconfig`` and selecting the appropriate options. +The ESP-IDF has support for two types of watchdogs: The Interrupt Watchdog Timer +and the Task Watchdog Timer (TWDT). The Interrupt Watchdog Timer and the TWDT +can both be enabled using ``make menuconfig``, 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 ^^^^^^^^^^^^^^^^^^ @@ -24,49 +29,75 @@ The interrupt watchdog is built around the hardware watchdog in timer group 1. I cannot execute the NMI handler that invokes the panic handler (e.g. because IRAM is overwritten by garbage), it will hard-reset the SOC. -Task watchdog -^^^^^^^^^^^^^ +Task Watchdog Timer +^^^^^^^^^^^^^^^^^^^ -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. +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. -By default, the task watchdog watches the idle tasks. The usual cause of 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. +By default the TWDT will watch the Idle Tasks of each CPU, however any task can +elect 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 on the ESP32 CPUs and. -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()``. +The TWDT is built around the Hardware Watchdog Timer in Timer Group 0. The TWDT +can be initialized by calling :cpp:func:`esp_task_wdt_init` which will configure +the hardware timer. A task can then subscribe to the TWDT using +:cpp:func:`esp_task_wdt_add` in order to be watched. Each subscribed task must +periodically call :cpp:func:`esp_task_wdt_reset` to reset the TWDT. Failure by +any subscribed tasks to periodically call :cpp:func:`esp_task_wdt_reset` +indicates that one or more tasks have been starved of CPU time or are stuck in a +loop somewhere. -The task watchdog is built around the hardware watchdog in timer group 0. If this 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. +A watched task can be unsubscribed from the TWDT using +:cpp:func:`esp_task_wdt_delete()`. A task that has been unsubscribed should no +longer call :cpp:func:`esp_task_wdt_reset`. Once all tasks have unsubscribed +form the TWDT, the TWDT can be deinitialized by calling +:cpp:func:`esp_task_wdt_deinit()`. + +By default :ref:`CONFIG_TASK_WDT` in ``make menuconfig`` will be enabled causing +the TWDT to be initialized automatically during startup. Likewise +:ref:`CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0` and +:ref:`CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1` are also enabled by default causing +the two Idle Tasks to be subscribed to the TWDT during startup. JTAG and watchdogs ^^^^^^^^^^^^^^^^^^ -While debugging using OpenOCD, if the CPUs are halted the watchdogs will keep running, eventually resetting the -CPU. This makes it very hard to debug code; that is why the OpenOCD config will disable both watchdogs on startup. -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. +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 ESP32 is connected to +OpenOCD via JTAG. -API Reference -------------- -Header Files -^^^^^^^^^^^^ +Interrupt Watchdog API Reference +-------------------------------- + +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 +---------------------------- + +A full example using the Task Watchdog is available in esp-idf: :example:`system/task_watchdog` + +.. include:: /_build/inc/esp_task_wdt.inc diff --git a/examples/system/task_watchdog/Makefile b/examples/system/task_watchdog/Makefile new file mode 100644 index 000000000..fb4f26b38 --- /dev/null +++ b/examples/system/task_watchdog/Makefile @@ -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 + diff --git a/examples/system/task_watchdog/README.md b/examples/system/task_watchdog/README.md new file mode 100644 index 000000000..92a31328c --- /dev/null +++ b/examples/system/task_watchdog/README.md @@ -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. + diff --git a/examples/system/task_watchdog/main/component.mk b/examples/system/task_watchdog/main/component.mk new file mode 100644 index 000000000..44bd2b527 --- /dev/null +++ b/examples/system/task_watchdog/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/system/task_watchdog/main/task_watchdog_example_main.c b/examples/system/task_watchdog/main/task_watchdog_example_main.c new file mode 100644 index 000000000..16a0a8402 --- /dev/null +++ b/examples/system/task_watchdog/main/task_watchdog_example_main.c @@ -0,0 +1,85 @@ +/* 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 +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_task_wdt.h" + +#define TWDT_TIMEOUT_S 3 +#define TASK_RESET_PERIOD_S 2 + +/* + * Macro to check the outputs of TWDT functions and trigger an abort if an + * incorrect code is returned. + */ +#define CHECK_ERROR_CODE(returned, expected) ({ \ + if(returned != expected){ \ + printf("TWDT ERROR\n"); \ + abort(); \ + } \ +}) + +static TaskHandle_t task_handles[portNUM_PROCESSORS]; + +//Callback for user tasks created in app_main() +void reset_task(void *arg) +{ + //Subscribe this task to TWDT, then check if it is subscribed + CHECK_ERROR_CODE(esp_task_wdt_add(NULL), ESP_OK); + CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_OK); + + while(1){ + //reset the watchdog every 2 seconds + CHECK_ERROR_CODE(esp_task_wdt_reset(), ESP_OK); //Comment this line to trigger a TWDT timeout + vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_S * 1000)); + } +} + +void app_main() +{ + printf("Initialize TWDT\n"); + //Initialize or reinitialize TWDT + CHECK_ERROR_CODE(esp_task_wdt_init(TWDT_TIMEOUT_S, false), ESP_OK); + + //Subscribe Idle Tasks to TWDT if they were not subscribed at startup +#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(0)); +#endif +#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 + esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(1)); +#endif + + //Create user tasks and add them to watchdog + for(int i = 0; i < portNUM_PROCESSORS; i++){ + xTaskCreatePinnedToCore(reset_task, "reset task", 1024, NULL, 10, &task_handles[i], i); + } + + printf("Delay for 10 seconds\n"); + vTaskDelay(pdMS_TO_TICKS(10000)); //Delay for 10 seconds + + printf("Unsubscribing and deleting tasks\n"); + //Delete and unsubscribe Users Tasks from Task Watchdog, then unsubscribe idle task + for(int i = 0; i < portNUM_PROCESSORS; i++){ + vTaskDelete(task_handles[i]); //Delete user task first (prevents the resetting of an unsubscribed task) + CHECK_ERROR_CODE(esp_task_wdt_delete(task_handles[i]), ESP_OK); //Unsubscribe task from TWDT + CHECK_ERROR_CODE(esp_task_wdt_status(task_handles[i]), ESP_ERR_NOT_FOUND); //Confirm task is unsubscribed + + //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"); +}