diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index dd5ac70418..5a5c9c4713 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 2132239fbf..0feed97bdf 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 abe88a97e1..d2b24e9952 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 3d21119262..5f24bc35a6 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 eb77377009..60b0e5e5c9 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 ce3f09b839..dc46e1c9b8 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 2438d96000..073267445f 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 48acd20580..4828dc710f 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -2367,6 +2367,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 e15ff53dd7..adfa49693a 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 0000000000..fb4f26b383 --- /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 0000000000..92a31328cd --- /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 0000000000..44bd2b5273 --- /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 0000000000..16a0a84026 --- /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"); +}