1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
20 #include "sdkconfig.h"
21 #include "freertos/FreeRTOSConfig.h"
22 #include "freertos/FreeRTOS.h"
23 #include "freertos/task.h"
24 #include "freertos/queue.h"
25 #include "freertos/semphr.h"
26 #include <esp_types.h>
29 #include "esp_intr_alloc.h"
31 #include "esp_freertos_hooks.h"
32 #include "soc/timer_group_struct.h"
33 #include "soc/timer_group_reg.h"
35 #include "driver/timer.h"
37 #include "esp_task_wdt.h"
39 //Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret'
40 #define ASSERT_EXIT_CRIT_RETURN(cond, ret) ({ \
42 portEXIT_CRITICAL(&twdt_spinlock); \
47 //Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
50 //Structure used for each subscribed task
51 typedef struct twdt_task_t twdt_task_t;
53 TaskHandle_t task_handle;
58 //Structure used to hold run time configuration of the TWDT
59 typedef struct twdt_config_t twdt_config_t;
60 struct twdt_config_t {
61 twdt_task_t *list; //Linked list of subscribed tasks
62 uint32_t timeout; //Timeout period of TWDT
63 bool panic; //Flag to trigger panic when TWDT times out
64 intr_handle_t intr_handle;
67 static twdt_config_t *twdt_config = NULL;
68 static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
71 * Idle hook callback for Idle Tasks to reset the TWDT. This callback will only
72 * be registered to the Idle Hook of a particular core when the corresponding
73 * Idle Task subscribes to the TWDT.
75 static bool idle_hook_cb(void)
82 * Internal function that looks for the target task in the TWDT task list.
83 * Returns the list item if found and returns null if not found. Also checks if
84 * all the other tasks have reset. Should be called within critical.
86 static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
88 twdt_task_t *target = NULL;
90 for(twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
91 if(task->task_handle == handle){
92 target = task; //Get pointer to target task list member
94 if(task->has_reset == false){ //If a task has yet to reset
103 * Resets the hardware timer and has_reset flags of each task on the list.
104 * Called within critical
106 static void reset_hw_timer()
108 //All tasks have reset; time to reset the hardware timer.
109 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
111 TIMERG0.wdt_wprotect=0;
112 //Clear all has_reset flags in list
113 for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
114 task->has_reset=false;
119 * ISR for when TWDT times out. Checks for which tasks have not reset. Also
120 * triggers panic if configured to do so
122 static void task_wdt_isr(void *arg)
124 portENTER_CRITICAL(&twdt_spinlock);
125 twdt_task_t *twdttask;
127 //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
128 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
130 TIMERG0.wdt_wprotect=0;
131 //Acknowledge interrupt
132 TIMERG0.int_clr_timers.wdt=1;
133 //We are taking a spinlock while doing I/O (ets_printf) here. Normally, that is a pretty
134 //bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case,
135 //something bad already happened and reporting this is considered more important
136 //than the badness caused by a spinlock here.
138 //Return immediately if no tasks have been added to task list
139 ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN);
141 //Watchdog got triggered because at least one task did not reset in time.
142 ets_printf("Task watchdog got triggered. The following tasks did not reset the watchdog in time:\n");
143 for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) {
144 if (!twdttask->has_reset) {
145 cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1");
146 if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) cpu=DRAM_STR("CPU 0/1");
147 ets_printf(" - %s (%s)\n", pcTaskGetTaskName(twdttask->task_handle), cpu);
150 ets_printf(DRAM_STR("Tasks currently running:\n"));
151 for (int x=0; x<portNUM_PROCESSORS; x++) {
152 ets_printf("CPU %d: %s\n", x, pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(x)));
155 if (twdt_config->panic){ //Trigger Panic if configured to do so
156 ets_printf("Aborting.\n");
157 portEXIT_CRITICAL(&twdt_spinlock);
161 portEXIT_CRITICAL(&twdt_spinlock);
165 * Initializes the TWDT by allocating memory for the config data
166 * structure, obtaining the idle task handles/registering idle hooks, and
167 * setting the hardware timer registers. If reconfiguring, it will just modify
168 * wdt_config and reset the hardware timer.
170 esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
172 portENTER_CRITICAL(&twdt_spinlock);
173 if(twdt_config == NULL){ //TWDT not initialized yet
174 //Allocate memory for wdt_config
175 twdt_config = calloc(1, sizeof(twdt_config_t));
176 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM);
178 twdt_config->list = NULL;
179 twdt_config->timeout = timeout;
180 twdt_config->panic = panic;
182 //Register Interrupt and ISR
183 ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle))
185 //Configure hardware timer
186 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
187 TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS
188 TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS
189 TIMERG0.wdt_config0.level_int_en=1;
190 TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt
191 TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system
192 TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
193 TIMERG0.wdt_config2=twdt_config->timeout*2000; //Set timeout before interrupt
194 TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset
195 TIMERG0.wdt_config0.en=1;
197 TIMERG0.wdt_wprotect=0; //Enable write protection
199 }else{ //twdt_config previously initialized
200 //Reconfigure task wdt
201 twdt_config->panic = panic;
202 twdt_config->timeout = timeout;
204 //Reconfigure hardware timer
205 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
206 TIMERG0.wdt_config0.en=0; //Disable timer
207 TIMERG0.wdt_config2=twdt_config->timeout*2000; //Set timeout before interrupt
208 TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset
209 TIMERG0.wdt_config0.en=1; //Renable timer
210 TIMERG0.wdt_feed=1; //Reset timer
211 TIMERG0.wdt_wprotect=0; //Enable write protection
213 portEXIT_CRITICAL(&twdt_spinlock);
217 esp_err_t esp_task_wdt_deinit()
219 portENTER_CRITICAL(&twdt_spinlock);
220 //TWDT must already be initialized
221 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
222 //Task list must be empty
223 ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
225 //Disable hardware timer
226 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
227 TIMERG0.wdt_config0.en=0; //Disable timer
228 TIMERG0.wdt_wprotect=0; //Enable write protection
230 ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)) //Unregister interrupt
231 free(twdt_config); //Free twdt_config
233 portEXIT_CRITICAL(&twdt_spinlock);
237 esp_err_t esp_task_wdt_add(TaskHandle_t handle)
239 portENTER_CRITICAL(&twdt_spinlock);
240 //TWDT must already be initialized
241 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
243 twdt_task_t *target_task;
245 if (handle == NULL){ //Get handle of current task if none is provided
246 handle = xTaskGetCurrentTaskHandle();
248 //Check if tasks exists in task list, and if all other tasks have reset
249 target_task = find_task_in_twdt_list(handle, &all_reset);
250 //task cannot be already subscribed
251 ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
253 //Add target task to TWDT task list
254 target_task = calloc(1,sizeof(twdt_task_t));
255 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM);
256 target_task->task_handle = handle;
257 target_task->has_reset = true;
258 target_task->next = NULL;
259 if (twdt_config->list == NULL) { //Adding to empty list
260 twdt_config->list = target_task;
261 } else { //Adding to tail of list
263 for (task = twdt_config->list; task->next != NULL; task = task->next){
264 ; //point task to current tail of TWDT task list
266 task->next = target_task;
269 //If idle task, register the idle hook callback to appropriate core
270 for(int i = 0; i < portNUM_PROCESSORS; i++){
271 if(handle == xTaskGetIdleTaskHandleForCPU(i)){
272 ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i))
277 if(all_reset){ //Reset hardware timer if all other tasks in list have reset in
281 portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy
285 esp_err_t esp_task_wdt_reset()
287 portENTER_CRITICAL(&twdt_spinlock);
288 //TWDT must already be initialized
289 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
291 TaskHandle_t handle = xTaskGetCurrentTaskHandle();
292 twdt_task_t *target_task;
295 //Check if task exists in task list, and if all other tasks have reset
296 target_task = find_task_in_twdt_list(handle, &all_reset);
297 //Return error if trying to reset task that is not on the task list
298 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND);
300 target_task->has_reset = true; //Reset the task if it's on the task list
301 if(all_reset){ //Reset if all other tasks in list have reset in
305 portEXIT_CRITICAL(&twdt_spinlock);
309 esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
312 handle = xTaskGetCurrentTaskHandle();
314 portENTER_CRITICAL(&twdt_spinlock);
315 //Return error if twdt has not been initialized
316 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
318 twdt_task_t *target_task;
320 target_task = find_task_in_twdt_list(handle, &all_reset);
321 //Task doesn't exist on list. Return error
322 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG);
324 if(target_task == twdt_config->list){ //target_task is head of list. Delete
325 twdt_config->list = target_task->next;
327 }else{ //target_task not head of list. Delete
329 for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
330 ; //point prev to task preceding target_task
332 prev->next = target_task->next;
336 //If idle task, deregister idle hook callback form appropriate core
337 for(int i = 0; i < portNUM_PROCESSORS; i++){
338 if(handle == xTaskGetIdleTaskHandleForCPU(i)){
339 esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i);
344 if(all_reset){ //Reset hardware timer if all remaining tasks have reset
348 portEXIT_CRITICAL(&twdt_spinlock);
352 esp_err_t esp_task_wdt_status(TaskHandle_t handle)
355 handle = xTaskGetCurrentTaskHandle();
358 portENTER_CRITICAL(&twdt_spinlock);
359 //Return if TWDT is not initialized
360 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
363 for(task = twdt_config->list; task!=NULL; task=task->next){
364 //Return ESP_OK if task is found
365 ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK);
368 //Task could not be found
369 portEXIT_CRITICAL(&twdt_spinlock);
370 return ESP_ERR_NOT_FOUND;
373 void esp_task_wdt_feed()
375 portENTER_CRITICAL(&twdt_spinlock);
376 //Return immediately if TWDT has not been initialized
377 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), VOID_RETURN);
379 //Check if task is on list
380 TaskHandle_t handle = xTaskGetCurrentTaskHandle();
382 twdt_task_t *target_task = find_task_in_twdt_list(handle, &all_reset);
384 //reset the task if it's on the list, then return
385 if(target_task != NULL){
386 target_task->has_reset = true;
388 reset_hw_timer(); //Reset hardware timer if all other tasks have reset
390 portEXIT_CRITICAL(&twdt_spinlock);
394 //Add task if it's has not on list
395 target_task = calloc(1, sizeof(twdt_task_t));
396 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), VOID_RETURN); //If calloc failed
397 target_task->task_handle = handle;
398 target_task->has_reset = true;
399 target_task->next = NULL;
401 if (twdt_config->list == NULL) { //Adding to empty list
402 twdt_config->list = target_task;
403 } else { //Adding to tail of list
405 for (task = twdt_config->list; task->next != NULL; task = task->next){
406 ; //point task to current tail of wdt task list
408 task->next = target_task;
411 portEXIT_CRITICAL(&twdt_spinlock);