diff --git a/components/app_trace/gcov/gcov_rtio.c b/components/app_trace/gcov/gcov_rtio.c index 7556c9f7f..5e0b7e913 100644 --- a/components/app_trace/gcov/gcov_rtio.c +++ b/components/app_trace/gcov/gcov_rtio.c @@ -14,9 +14,9 @@ // This module implements runtime file I/O API for GCOV. +#include "esp_task_wdt.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "esp_task_wdt.h" #include "soc/cpu.h" #include "soc/timer_group_struct.h" #include "soc/timer_group_reg.h" diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 0870a731b..8f0c588bc 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -527,11 +527,10 @@ config INT_WDT_CHECK_CPU1 Also detect if interrupts on CPU 1 are disabled for too long. config TASK_WDT - bool "Initialize Task Watchdog on startup" + bool "Task watchdog" default y help This watchdog timer can be used to make sure individual tasks are still running. - The Task Watchdog timer can be initialized at run time as well config TASK_WDT_PANIC bool "Invoke panic handler when Task Watchdog is triggered" @@ -540,46 +539,33 @@ config TASK_WDT_PANIC help Normally, the Task Watchdog will only print out a warning if it detects it has not been fed. If this is enabled, it will invoke the panic handler instead, which - can then halt or reboot the chip. This can also be configured at run time - by reinitializing the task watchdog. + can then halt or reboot the chip. config TASK_WDT_TIMEOUT_S - int "Task watchdog timeout (ms)" + int "Task watchdog timeout (seconds)" depends on TASK_WDT - range 1 60000 - default 5000 + range 1 60 + default 5 help - Timeout for the task WDT, in ms. + Timeout for the task WDT, in seconds. -config TASK_WDT_CHECK_IDLE_TASK_CPU0 - bool "Add CPU0 idle task to task watchdog on startup" +config TASK_WDT_CHECK_IDLE_TASK + bool "Task watchdog watches CPU0 idle task" depends on TASK_WDT default y help - With this turned on, the CPU0 idle task will be added to the task watchdog - on startup. Adding the idle task to the task watchdog allows for the detection - of CPU starvation. The idle task not being called is usually a symptom of another + 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. + 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. config TASK_WDT_CHECK_IDLE_TASK_CPU1 - bool "Add CPU0 idle task to task watchdog on startup" - depends on TASK_WDT && !FREERTOS_UNICORE + bool "Task watchdog also watches CPU1 idle task" + depends on TASK_WDT_CHECK_IDLE_TASK && !FREERTOS_UNICORE default y help - With this turned on, the CPU1 idle task will also be added to the task watchdog - on startup. - -config TASK_WDT_LEGACY_BEHAVIOR - bool "Use legacy behavior for task watchdog" - depends on TASK_WDT - default n - help - Task wdt legacy behavior will add a task to the wdt list on its first - call to esp_task_wdt_feed(). Furthermore, tasks can only remove - themselves from the wdt task list when calling esp_task_wdt_delete(). - Therefore esp_task_wdt_delete() should be called with no parameters - when legacy behavior is enabled. + Also check the idle task that runs on CPU1. #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 983493839..f9c921b87 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -323,6 +323,9 @@ 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(); @@ -397,30 +400,6 @@ static void main_task(void* args) #endif //Enable allocation in region where the startup stacks were located. heap_caps_enable_nonos_stack_heaps(); - - //Initialize task wdt -#ifdef CONFIG_TASK_WDT -#ifdef CONFIG_TASK_WDT_PANIC - esp_task_wdt_init(CONFIG_TASK_WDT_TIMEOUT_S, true); -#else - esp_task_wdt_init(CONFIG_TASK_WDT_TIMEOUT_S, false); -#endif -#endif - //Add IDLE 0 to task wdt -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); - if(idle_0 != NULL){ - esp_task_wdt_add(idle_0); - } -#endif - //Add IDLE 1 to task wdt -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 - TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1); - if(idle_1 != NULL){ - esp_task_wdt_add(idle_1); - } -#endif - app_main(); vTaskDelete(NULL); } diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h index e2200faef..eb7737700 100644 --- a/components/esp32/include/esp_task_wdt.h +++ b/components/esp32/include/esp_task_wdt.h @@ -15,136 +15,60 @@ #ifndef __ESP_TASK_WDT_H #define __ESP_TASK_WDT_H -#include "freertos/task.h" -#include "esp_err.h" - #ifdef __cplusplus extern "C" { #endif -/* -This routine enables a more general-purpose task watchdog which uses the TIMERG0 -WDT: All tasks subscribed to the task wdt must feed the wdt at least once -before the wdt times out or else the wdt will bark. The Idle tasks can also be -subscribed to the wdt. This allows for the detection of CPU starvation. -To enable/disable legacy behavior, toggle the CONFIG_TASK_WDT_LEGACY_BEHAVIOR -option in menuconfig. Under legacy behavior, a task will subscribe to the -wdt on its first call of esp_task_wdt_feed(). esp_task_wdt_add() SHOULD NOT BE -CALLED in an application if legacy behavior is enabled. esp_task_wdt_delete() -will only allow tasks to unsubscribe themselves from the task wdt under legacy -behavior. Enabling legacy behavior will also force the task wdt to be enabled on -startup. +/** \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 timer + * @brief Initialize the task watchdog. This is called in the init code, if the + * task watchdog is enabled in menuconfig. * - * This function will initialize the task watchdog timer. If the wdt - * has already been initialized, calling this function again will change the - * timeout and panic configuration, then reset the timer count. Once the watchdog - * timer has been initialized, tasks can subscribe to the watchdog timer. - * - * @param[in] timeout Timeout(ms) period of watchdog timer - * @param[in] panic Flag that if set, will cause the wdt to trigger the - * panic handler when it times out - * @return - * - ESP_OK: Initialization was successful - * - ESP_ERR_NO_MEM: Initialization was unsuccessful due to lack of - * memory - * - * @note esp_task_wdt_init() must only be called after the scheduler - * started */ -esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic); - +void esp_task_wdt_init(); /** - * @brief Deinitialize the task watchdog timer - * - * This function will deinitialize the task watchdog timer. This function should - * be called when all tasks have unsubscribed form the watchdog timer. - * - * @return - * - ESP_OK: Watchdog timer successfully deinitialized - * - ESP_ERR_INVALID_STATE: Error due to wdt not being initialized, or - * if tasks are still subscribed to the wdt - */ -esp_err_t esp_task_wdt_deinit(); - - -/** - * @brief Subscribes a tasks to the task watchdog timer + * @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 subscribes a task to the task watchdog timer. Note that once - * subscribed, a task must periodically feed the watchdog timer. If the task - * is an IDLE task, this function will enable feeding from the idle hook - * automatically. - * - * @param[in] handle Handle of the task to be added. Input NULL - * to add the current running task - * - * @return - * - ESP_OK: Successfully subscribed the task to the wdt - * - ESP_ERR_INVALID_ARG: Failed as task is already subscribed - * - ESP_ERR_INVALID_STATE: Failed as wdt has not been initialized - * - ESP_ERR_NO_MEM: Failed due to lack of memory - * - * @note This function should not be called from application code if - * CONFIG_TASK_WDT_LEGACY_BEHAVIOR has been enabled. Legacy behavior - * uses esp_task_wdt_feed() to subscribe tasks to the wdt. */ -esp_err_t esp_task_wdt_add(TaskHandle_t handle); + +void esp_task_wdt_feed(); + /** - * @brief Feed the current running task + * @brief Delete the watchdog for the current task. * - * This function will feed the current running task if it has subscribed to the - * task watchdog timer. All non-idle tasks subscribed to the wdt must call this - * function at least once per watchdog timeout period. Idle tasks will feed - * the task watchdog automatically from their idle hooks. - * - * @return - * - ESP_OK: Successfully fed the current running task - * - ESP_ERR_INVALID_STATE: Failed to feed current running task as the - * wdt has not been initialized, or the - * current running task has not subscribed - * - * @note If CONFIG_TASK_WDT_LEGACY_BEHAVIOR is enabled, tasks will subscribe - * to the watchdog on their first call of esp_task_wdt_feed(). */ -esp_err_t esp_task_wdt_feed(); +void esp_task_wdt_delete(); /** - * @brief Unsubscribes a task from the task watchdog timer - * - * This function unsubscribes a task from the task watchdog. After unsubscribing, - * the task should no longer feed the wdt. If the unsubscribing task is an idle - * task, this function will disable feeding from the idle hook automatically. - * - * @param[in] handle Handle of the task to be deleted. Input NULL - * if deleting the current running task. - * - * @return - * - ESP_OK: Successfully unsubscribed task form the wdt - * - ESP_ERR_INVALID_ARG: Failed to unsubscribe task as it was not - * subscribed to begin with - * - ESP_ERR_INVALID_STATE: Failed to unsubscribe task as wdt has not - * been initialized yet - * - * @note Legacy behavior only allows tasks to unsubscribe from the wdt on their - * own behalf. Therefore, if CONFIG_TASK_WDT_LEGACY_BEHAVIOR is - * enabled, this esp_task_wdt_delete() will accept no parameters and - * unsubscribe the calling task from the wdt. + * @} */ -#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR -esp_err_t esp_task_wdt_delete(); -#else -esp_err_t esp_task_wdt_delete(TaskHandle_t handle); -#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR + #ifdef __cplusplus } #endif -#endif //__ESP_TASK_WDT_H + + +#endif \ No newline at end of file diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index bb45b6a4a..ce3f09b83 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -20,7 +20,6 @@ #include #include #include "sdkconfig.h" -#include "freertos/FreeRTOSConfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -29,7 +28,6 @@ #include "esp_err.h" #include "esp_intr.h" #include "esp_intr_alloc.h" -#include "esp_ipc.h" #include "esp_attr.h" #include "esp_freertos_hooks.h" #include "soc/timer_group_struct.h" @@ -39,80 +37,25 @@ #include "esp_task_wdt.h" -//Assertion macro that exits from critical section and returns 'ret' -#define ASSERT_EXIT_CRIT_RET_ERR(cond, ret) ({ \ - if(!(cond)){ \ - portEXIT_CRITICAL(&taskwdt_spinlock); \ - return ret; \ - } \ -}) +#if CONFIG_TASK_WDT + +static const char* TAG = "task_wdt"; typedef struct wdt_task_t wdt_task_t; struct wdt_task_t { TaskHandle_t task_handle; - bool fed; + bool fed_watchdog; wdt_task_t *next; }; -typedef struct task_wdt_config_t task_wdt_config_t; -struct task_wdt_config_t { - wdt_task_t *list; - uint32_t timeout; - bool panic; - bool idle_enable[portNUM_PROCESSORS]; - TaskHandle_t idle_handles[portNUM_PROCESSORS]; - intr_handle_t intr_handle; -}; - -static task_wdt_config_t *wdt_config = NULL; +static wdt_task_t *wdt_task_list=NULL; static portMUX_TYPE taskwdt_spinlock = portMUX_INITIALIZER_UNLOCKED; -/* - * Internal function that checks the list for target task. Returns the list item - * if found and returns null if not found. Also checks if the all the other - * tasks have checked in. Called within critical. - */ -static wdt_task_t *check_list(TaskHandle_t handle, bool *checked) -{ - wdt_task_t *target = NULL; - *checked = true; - for(wdt_task_t *task = wdt_config->list; task != NULL; task = task->next){ - if(task->task_handle == handle){ - target = task; //Get pointer to target task list member - }else{ - if(task->fed == false){ //If a task has yet to check in - *checked = false; - } - } - } - return target; -} -/* - * Resets the wdt and fed flags of each task on the list. Called within critical - */ -static void feed_wdt() -{ - //All tasks have checked in; time to feed the hw watchdog. - TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; - TIMERG0.wdt_feed=1; - TIMERG0.wdt_wprotect=0; - //Reset fed flags on all tasks in list - for (wdt_task_t *task = wdt_config->list; task != NULL; task = task->next){ - task->fed=false; - } -} - -/* - * ISR for when wdt expires. Checks for which tasks have not fed. Trigger panic - * if configured to do so in initialization - */ -static void task_wdt_isr(void *arg) -{ - portENTER_CRITICAL(&taskwdt_spinlock); +static void task_wdt_isr(void *arg) { wdt_task_t *wdttask; const char *cpu; - //Feed the watchdog so we don't reset + //Feed the watchdog so we do not reset TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; @@ -122,14 +65,16 @@ static void task_wdt_isr(void *arg) //bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case, //something bad already happened and reporting this is considered more important //than the badness caused by a spinlock here. - if (!wdt_config->list) { //No tasks on list + 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_config->list; wdttask!=NULL; wdttask=wdttask->next) { - if (!wdttask->fed) { + 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); @@ -140,243 +85,125 @@ static void task_wdt_isr(void *arg) ets_printf("CPU %d: %s\n", x, pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(x))); } - if (wdt_config->panic){ - ets_printf("Aborting.\n"); - portEXIT_CRITICAL(&taskwdt_spinlock); - abort(); - } - +#if CONFIG_TASK_WDT_PANIC + ets_printf("Aborting.\n"); + abort(); +#endif portEXIT_CRITICAL(&taskwdt_spinlock); } -/* - * Idle hook for Idle Task to feed the wdt. Will only call feed if the idle task - * of that core has been added to wdt task list. - */ -static bool idle_hook(void) -{ - uint32_t core_id = xPortGetCoreID(); - if (wdt_config->idle_enable[core_id]){ - esp_task_wdt_feed(); + +void esp_task_wdt_feed() { + wdt_task_t *wdttask=wdt_task_list; + bool found_task=false, do_feed_wdt=true; + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + portENTER_CRITICAL(&taskwdt_spinlock); + + //Walk the linked list of wdt tasks to find this one, as well as see if we need to feed + //the real watchdog timer. + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { + //See if we are at the current task. + if (wdttask->task_handle == handle) { + wdttask->fed_watchdog=true; + found_task=true; + } + //If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog. + if (!wdttask->fed_watchdog) do_feed_wdt=false; } + + if (!found_task) { + //This is the first time the task calls the task_wdt_feed function. Create a new entry for it in + //the linked list. + wdt_task_t *newtask=malloc(sizeof(wdt_task_t)); + memset(newtask, 0, sizeof(wdt_task_t)); + newtask->task_handle=handle; + newtask->fed_watchdog=true; + if (wdt_task_list == NULL) { + wdt_task_list=newtask; + } else { + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ; + wdttask->next=newtask; + } + } + if (do_feed_wdt) { + //All tasks have checked in; time to feed the hw watchdog. + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + //Reset fed_watchdog status + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false; + } + portEXIT_CRITICAL(&taskwdt_spinlock); +} + +void esp_task_wdt_delete() { + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + wdt_task_t *wdttask=wdt_task_list; + portENTER_CRITICAL(&taskwdt_spinlock); + + //Wdt task list can't be empty + if (!wdt_task_list) { + ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?"); + portEXIT_CRITICAL(&taskwdt_spinlock); + return; + } + if (handle==wdt_task_list) { + //Current task is first on list. + wdt_task_list=wdt_task_list->next; + free(wdttask); + } else { + //Find current task in list + if (wdt_task_list->task_handle==handle) { + //Task is the very first one. + wdt_task_t *freeme=wdt_task_list; + wdt_task_list=wdt_task_list->next; + free(freeme); + portEXIT_CRITICAL(&taskwdt_spinlock); + return; + } + while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next; + if (!wdttask->next) { + ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!"); + portEXIT_CRITICAL(&taskwdt_spinlock); + return; + } + wdt_task_t *freeme=wdttask->next; + wdttask->next=wdttask->next->next; + free(freeme); + } + portEXIT_CRITICAL(&taskwdt_spinlock); +} + + +#if CONFIG_TASK_WDT_CHECK_IDLE_TASK +static bool idle_hook(void) { +#if !CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 + if (xPortGetCoreID()!=0) return true; +#endif + esp_task_wdt_feed(); return true; } - -/* - * Initializes the task watchdog timer by allocating memory for the config data - * structure, obtaining the idle task handles/registering idle hooks, and - * setting the hardware timer registers. If reconfiguring, it will just modify - * wdt_config and reset the hardware timer. - */ -esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic) -{ - portENTER_CRITICAL(&taskwdt_spinlock); - if(wdt_config == NULL){ //wdt not initialized yet - //Allocate memory for wdt_config - wdt_config = calloc(1, sizeof(task_wdt_config_t)); - ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_NO_MEM); - - wdt_config->list = NULL; - wdt_config->timeout = timeout; - wdt_config->panic = panic; - - //Get idle task handles - for(int i = 0; i < portNUM_PROCESSORS; i++){ - wdt_config->idle_handles[i] = xTaskGetIdleTaskHandleForCPU( i ); - wdt_config->idle_enable[i] = false; - } - - //Register Idle Hook and Interrupts - esp_register_freertos_idle_hook(idle_hook); - ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &wdt_config->intr_handle)) - - //Configure hardware timer - TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection - TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS - TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS - TIMERG0.wdt_config0.level_int_en=1; - TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt - TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system - TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS - TIMERG0.wdt_config2=wdt_config->timeout*2; //Set timeout before interrupt - TIMERG0.wdt_config3=wdt_config->timeout*4; //Set timeout before reset - TIMERG0.wdt_config0.en=1; - TIMERG0.wdt_feed=1; - TIMERG0.wdt_wprotect=0; //Enable write protection - - }else{ //wdt_config previously initialized - //Reconfigure task wdt - wdt_config->panic = panic; - wdt_config->timeout = timeout; - - //Reconfigure hardware timer - TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection - TIMERG0.wdt_config0.en=0; //Disable timer - TIMERG0.wdt_config2=wdt_config->timeout*2; //Set timeout before interrupt - TIMERG0.wdt_config3=wdt_config->timeout*4; //Set timeout before reset - TIMERG0.wdt_config0.en=1; //Renable timer - TIMERG0.wdt_feed=1; //Reset timer - TIMERG0.wdt_wprotect=0; //Enable write protection - } - portEXIT_CRITICAL(&taskwdt_spinlock); - return ESP_OK; -} - -esp_err_t esp_task_wdt_deinit() -{ - portENTER_CRITICAL(&taskwdt_spinlock); - //wdt must already be initialized - ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE); - //Task list must be empty - ASSERT_EXIT_CRIT_RET_ERR((wdt_config->list == NULL), ESP_ERR_INVALID_STATE); - - //Disable hardware timer - TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection - TIMERG0.wdt_config0.en=0; //Disable timer - TIMERG0.wdt_wprotect=0; //Enable write protection - - esp_deregister_freertos_idle_hook(idle_hook); //deregister idle hook - ESP_ERROR_CHECK(esp_intr_free(wdt_config->intr_handle)) //Unregister interrupt - free(wdt_config); //Free wdt_config - wdt_config = NULL; - portEXIT_CRITICAL(&taskwdt_spinlock); - return ESP_OK; -} - -esp_err_t esp_task_wdt_add(TaskHandle_t handle) -{ - portENTER_CRITICAL(&taskwdt_spinlock); //Nested critical in Legacy (called from feed) -#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR - wdt_task_t *target_task; -#else - //Return error if wdt has not been initialized - ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE); - - wdt_task_t *target_task; - bool all_fed; - if (handle == NULL){ //Get handle of current task if none is provided - handle = xTaskGetCurrentTaskHandle(); - } - - //Check if tasks exists in task list, and if all other tasks have checked in - target_task = check_list(handle, &all_fed); - //Return error if task has already been added - ASSERT_EXIT_CRIT_RET_ERR((target_task == NULL), ESP_ERR_INVALID_ARG); -#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR - //Add task to wdt list - target_task = calloc(1,sizeof(wdt_task_t)); - //Return error if calloc failed - ASSERT_EXIT_CRIT_RET_ERR((target_task != NULL), ESP_ERR_NO_MEM); - - target_task->task_handle = handle; - target_task->fed = true; - target_task->next = NULL; - if (wdt_config->list == NULL) { //Adding to empty list - wdt_config->list = target_task; - } else { //Adding to tail of list - wdt_task_t *task; - for (task = wdt_config->list; task->next != NULL; task = task->next){ - ; //point task to current tail of wdt task list - } - task->next = target_task; - } - - //If idle task, set idle_enable flag - for(int i = 0; i < portNUM_PROCESSORS; i++){ - if(handle == wdt_config->idle_handles[i]){ - wdt_config->idle_enable[i] = true; - break; - } - } - -#ifndef CONFIG_TASK_WDT_LEGACY_BEHAVIOR - if(all_fed){ //Reset if all other tasks in list have checked in - feed_wdt(); - } #endif - portEXIT_CRITICAL(&taskwdt_spinlock); //Nested critical if Legacy - return ESP_OK; + + +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) ); } -esp_err_t esp_task_wdt_feed() -{ - portENTER_CRITICAL(&taskwdt_spinlock); - //Return error if wdt has not been initialized - ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE); - - TaskHandle_t handle = xTaskGetCurrentTaskHandle(); - wdt_task_t *target_task; - bool all_fed; - - //Check if tasks exists in task list, and if all other tasks have checked in - target_task = check_list(handle, &all_fed); - - if(target_task == NULL){ -#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR - //Add task to wdt task list if it doesn't exist, return error if failed to add - ASSERT_EXIT_CRIT_RET_ERR((esp_task_wdt_add(handle) == ESP_OK), ESP_ERR_NO_MEM); -#else - portEXIT_CRITICAL(&taskwdt_spinlock); - return ESP_ERR_INVALID_STATE; //Return error if task does not exist -#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR - }else{ - target_task->fed = true; //Feed the task - } - - if(all_fed){ //Reset if all other tasks in list have checked in - feed_wdt(); - } - - portEXIT_CRITICAL(&taskwdt_spinlock); - return ESP_OK; -} - -#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR -esp_err_t esp_task_wdt_delete() -{ - TaskHandle_t handle = xTaskGetCurrentTaskHandle(); -#else -esp_err_t esp_task_wdt_delete(TaskHandle_t handle) -{ - if(handle == NULL){ - handle = xTaskGetCurrentTaskHandle(); - } -#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR - portENTER_CRITICAL(&taskwdt_spinlock); - //Return error if wdt has not been initialized - ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE); - wdt_task_t *target_task; - bool all_fed; - - target_task = check_list(handle, &all_fed); - - //Task doesn't exist on list. Return error - ASSERT_EXIT_CRIT_RET_ERR((target_task != NULL), ESP_ERR_INVALID_ARG); - - if(target_task == wdt_config->list){ //target_task is head of list. Delete - wdt_config->list = target_task->next; - free(target_task); - }else{ //target_task not head of list. Delete - wdt_task_t *prev; - for (prev = wdt_config->list; prev->next != target_task; prev = prev->next){ - ; //point prev to task preceding target_task - } - prev->next = target_task->next; - free(target_task); - } - - //If idle task, disable idle_enable flag - for(int i = 0; i < portNUM_PROCESSORS; i++){ - if(handle == wdt_config->idle_handles[i]){ - wdt_config->idle_enable[i] = false; - } - } - - if(all_fed){ //Reset timer if all remaining tasks have checked in - feed_wdt(); - } - - portEXIT_CRITICAL(&taskwdt_spinlock); - return ESP_OK; -} +#endif diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index 073267445..2438d9600 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -1382,15 +1382,6 @@ 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 9fe36dbd9..15d2e135d 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -2371,18 +2371,6 @@ 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 98506cf14..e15ff53dd 100644 --- a/docs/api-reference/system/wdts.rst +++ b/docs/api-reference/system/wdts.rst @@ -28,40 +28,23 @@ Task watchdog ^^^^^^^^^^^^^ Any tasks can elect to be watched by the task watchdog. If such a task does not feed the watchdog within the time -specified by the task watchdog timeout, the watchdog will print out a warning with information about which processes -are running on the ESP32 CPUs and which processes failed to feed the watchdog. +specified by the task watchdog timeout (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. -Ideally, the task watchdog should watch the idle tasks. The usual cause of the idle tasks not feeding the watchdog +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. -Other task can elect to be watched by the task watchdog. If the watchdog watches multiple tasks, all the tasks -must feed before the watchdog times out. If one or more tasks fails to feed, the watchdog timeout will trigger. -The watchdog timer can be initialized using :cpp:func:`esp_task_wdt_init` which will configure the hardware timer and -establish the hardware timer interrupts. In order for a task to be watched by the task watchdog, a task must subscribe to -the task watchdog using :cpp:func:`esp_task_wdt_add`. Once added, each subscribed task must call :cpp:func:`esp_task_wdt_feed` -periodically to prevent the watchdog from timing out. If all tasks being watched have fed, the watchdog timer counter immediately resets -and starts recounting to the timeout value from zero. To unsubscribe a task from the task watchdog, call :cpp:func:`esp_task_wdt_delete()`. -Once all tasks have been unsubscribed form the task watchdog, :cpp:func:`esp_task_wdt_deinit()` can be called to deinitialize -the task watchdog. Deinitialization will stop the hardware timer, deregister the hardware timer interrupts, and remove the idle hooks -if idle tasks were being watched. +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()``. -By setting the task watchdog options in ``make menuconfig``, the task watchdog can be automatically initialized -during startup by enabling the :ref:`CONFIG_TASK_WDT`. Moreover the two Idle tasks can also be subscibed to the -task watchdog on startup as well by enabling :ref:`CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0` and -:ref:`CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1`. - -The task watchdog is built around the hardware watchdog in timer group 0. If the watchdog for some reason +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. -Note: ``make menuconfig`` provides a :ref:`CONFIG_TASK_WDT_LEGACY_BEHAVIOR` option which will change the behavior of the -task watchdog to make it compatible with the legacy code. Note that the legacy behavior causes tasks to subscribe to the -task watchdog on their first call to :cpp:func:`esp_task_wdt_feed`. Moreover, legacy behavior only allows -tasks to unsubscribe on their own behalf when calling :cpp:func:`esp_task_wdt_delete()`. It is strongly recommended that -non-legacy behavior is used as additions/deletions/feeds are explicit. - - JTAG and watchdogs ^^^^^^^^^^^^^^^^^^ @@ -70,27 +53,20 @@ CPU. This makes it very hard to debug code; that is why the OpenOCD config will This does mean that you will not get any warnings or panics from either the task or interrupt watchdog when the ESP32 is connected to OpenOCD via JTAG. -Interrupt Watchdog API Reference --------------------------------- +API Reference +------------- -Header File -^^^^^^^^^^^ +Header Files +^^^^^^^^^^^^ * :component_file:`esp32/include/esp_int_wdt.h` + * :component_file:`esp32/include/esp_task_wdt.h` Functions --------- - + .. doxygenfunction:: esp_int_wdt_init - -Task Watchdog API Reference ----------------------------- - -NOTE: Ensure that the :ref:`CONFIG_TASK_WDT_LEGACY_BEHAVIOR` configuraiton is disabled using ``make menuconfig`` -to prevent legacy behavior of the task watchdog. - -A full example using the Task Watchdog is available in esp-idf: :example:`system/task_watchdog` - - -.. include:: /_build/inc/esp_task_wdt.inc +.. doxygenfunction:: esp_task_wdt_init +.. doxygenfunction:: esp_task_wdt_feed +.. doxygenfunction:: esp_task_wdt_delete diff --git a/examples/system/task_watchdog/Makefile b/examples/system/task_watchdog/Makefile deleted file mode 100644 index fb4f26b38..000000000 --- a/examples/system/task_watchdog/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# -# 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 deleted file mode 100644 index 92a31328c..000000000 --- a/examples/system/task_watchdog/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# 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 deleted file mode 100644 index 44bd2b527..000000000 --- a/examples/system/task_watchdog/main/component.mk +++ /dev/null @@ -1,3 +0,0 @@ -# -# 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 deleted file mode 100644 index 072ac733d..000000000 --- a/examples/system/task_watchdog/main/task_watchdog_example_main.c +++ /dev/null @@ -1,98 +0,0 @@ -/* 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 "freertos/queue.h" -#include "esp_task_wdt.h" - -#ifndef CONFIG_TASK_WDT_LEGACY_BEHAVIOR - -#define TIMEOUT 4000 -#define TASK_PERIOD 2000 - -#define ASSERT_PRINT_ERROR(cond, str) ({ \ - if(!(cond)){ \ - printf("%s\n",str); \ - } \ -}) - -void feeding_task(void *arg){ - int core = xPortGetCoreID(); - //Simple task that periodically feeds watchdog - while(1){ - if(esp_task_wdt_feed() == ESP_OK){ - printf("Feed Task %d\n", core); - } - vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD)); - } -} - -void app_main(void) -{ - //Initialize task watchdog - if(esp_task_wdt_init(TIMEOUT, false) != ESP_OK){ - printf("Error\n"); - } - - //Create tasks - TaskHandle_t handle_0; - TaskHandle_t handle_1; - xTaskCreatePinnedToCore(feeding_task, "Feed Task 0", 2000, NULL, 15, &handle_0, 0); - xTaskCreatePinnedToCore(feeding_task, "Feed Task 1", 2000, NULL, 15, &handle_1, 1); - - //Add tasks to wdt - ASSERT_PRINT_ERROR(esp_task_wdt_add(handle_0) == ESP_OK, "Add failed"); - ASSERT_PRINT_ERROR(esp_task_wdt_add(handle_1) == ESP_OK, "Add failed"); - -#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - //Adding Idles. Idle hook will call feed automatically - TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); - ASSERT_PRINT_ERROR(idle_0 != NULL, "Get Idle handle failed"); - ASSERT_PRINT_ERROR(esp_task_wdt_add(idle_0) == ESP_OK, "Add failed"); -#endif - -#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 - TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1); - ASSERT_PRINT_ERROR(idle_1 != NULL, "Get Idle handle failed"); - ASSERT_PRINT_ERROR(esp_task_wdt_add(idle_1) == ESP_OK, "Add failed"); -#endif - - //Wait 10 seconds - vTaskDelay(pdMS_TO_TICKS(10000)); - - //Remove tasks form wdt - ASSERT_PRINT_ERROR(esp_task_wdt_delete(handle_0) == ESP_OK, "Failed to delete"); - ASSERT_PRINT_ERROR(esp_task_wdt_delete(handle_1) == ESP_OK, "Failed to delete"); - //Delete tasks - vTaskDelete(handle_0); - vTaskDelete(handle_1); - -#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - ASSERT_PRINT_ERROR(esp_task_wdt_delete(idle_0) == ESP_OK, "Failed to delete"); -#endif - -#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 - ASSERT_PRINT_ERROR(esp_task_wdt_delete(idle_1) == ESP_OK, "Failed to delete"); -#endif - - -#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 -#ifndef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 - //Deinit task watchdog. Can only do so once all tasks (including idle) are deleted from wdt - ASSERT_PRINT_ERROR(esp_task_wdt_deinit() == ESP_OK, "deinit failed"); -#endif -#endif - - printf("Complete\n"); - -} - -#endif