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"
36 #include "driver/periph_ctrl.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 periph_module_enable(PERIPH_TIMG0_MODULE);
187 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
188 TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS
189 TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS
190 TIMERG0.wdt_config0.level_int_en=1;
191 TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt
192 TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system
193 TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
194 TIMERG0.wdt_config2=twdt_config->timeout*2000; //Set timeout before interrupt
195 TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset
196 TIMERG0.wdt_config0.en=1;
198 TIMERG0.wdt_wprotect=0; //Enable write protection
200 }else{ //twdt_config previously initialized
201 //Reconfigure task wdt
202 twdt_config->panic = panic;
203 twdt_config->timeout = timeout;
205 //Reconfigure hardware timer
206 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
207 TIMERG0.wdt_config0.en=0; //Disable timer
208 TIMERG0.wdt_config2=twdt_config->timeout*2000; //Set timeout before interrupt
209 TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset
210 TIMERG0.wdt_config0.en=1; //Renable timer
211 TIMERG0.wdt_feed=1; //Reset timer
212 TIMERG0.wdt_wprotect=0; //Enable write protection
214 portEXIT_CRITICAL(&twdt_spinlock);
218 esp_err_t esp_task_wdt_deinit()
220 portENTER_CRITICAL(&twdt_spinlock);
221 //TWDT must already be initialized
222 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
223 //Task list must be empty
224 ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
226 //Disable hardware timer
227 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
228 TIMERG0.wdt_config0.en=0; //Disable timer
229 TIMERG0.wdt_wprotect=0; //Enable write protection
231 ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)) //Unregister interrupt
232 free(twdt_config); //Free twdt_config
234 portEXIT_CRITICAL(&twdt_spinlock);
238 esp_err_t esp_task_wdt_add(TaskHandle_t handle)
240 portENTER_CRITICAL(&twdt_spinlock);
241 //TWDT must already be initialized
242 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
244 twdt_task_t *target_task;
246 if (handle == NULL){ //Get handle of current task if none is provided
247 handle = xTaskGetCurrentTaskHandle();
249 //Check if tasks exists in task list, and if all other tasks have reset
250 target_task = find_task_in_twdt_list(handle, &all_reset);
251 //task cannot be already subscribed
252 ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
254 //Add target task to TWDT task list
255 target_task = calloc(1,sizeof(twdt_task_t));
256 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM);
257 target_task->task_handle = handle;
258 target_task->has_reset = true;
259 target_task->next = NULL;
260 if (twdt_config->list == NULL) { //Adding to empty list
261 twdt_config->list = target_task;
262 } else { //Adding to tail of list
264 for (task = twdt_config->list; task->next != NULL; task = task->next){
265 ; //point task to current tail of TWDT task list
267 task->next = target_task;
270 //If idle task, register the idle hook callback to appropriate core
271 for(int i = 0; i < portNUM_PROCESSORS; i++){
272 if(handle == xTaskGetIdleTaskHandleForCPU(i)){
273 ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i))
278 if(all_reset){ //Reset hardware timer if all other tasks in list have reset in
282 portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy
286 esp_err_t esp_task_wdt_reset()
288 portENTER_CRITICAL(&twdt_spinlock);
289 //TWDT must already be initialized
290 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
292 TaskHandle_t handle = xTaskGetCurrentTaskHandle();
293 twdt_task_t *target_task;
296 //Check if task exists in task list, and if all other tasks have reset
297 target_task = find_task_in_twdt_list(handle, &all_reset);
298 //Return error if trying to reset task that is not on the task list
299 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND);
301 target_task->has_reset = true; //Reset the task if it's on the task list
302 if(all_reset){ //Reset if all other tasks in list have reset in
306 portEXIT_CRITICAL(&twdt_spinlock);
310 esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
313 handle = xTaskGetCurrentTaskHandle();
315 portENTER_CRITICAL(&twdt_spinlock);
316 //Return error if twdt has not been initialized
317 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
319 twdt_task_t *target_task;
321 target_task = find_task_in_twdt_list(handle, &all_reset);
322 //Task doesn't exist on list. Return error
323 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG);
325 if(target_task == twdt_config->list){ //target_task is head of list. Delete
326 twdt_config->list = target_task->next;
328 }else{ //target_task not head of list. Delete
330 for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
331 ; //point prev to task preceding target_task
333 prev->next = target_task->next;
337 //If idle task, deregister idle hook callback form appropriate core
338 for(int i = 0; i < portNUM_PROCESSORS; i++){
339 if(handle == xTaskGetIdleTaskHandleForCPU(i)){
340 esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i);
345 if(all_reset){ //Reset hardware timer if all remaining tasks have reset
349 portEXIT_CRITICAL(&twdt_spinlock);
353 esp_err_t esp_task_wdt_status(TaskHandle_t handle)
356 handle = xTaskGetCurrentTaskHandle();
359 portENTER_CRITICAL(&twdt_spinlock);
360 //Return if TWDT is not initialized
361 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
364 for(task = twdt_config->list; task!=NULL; task=task->next){
365 //Return ESP_OK if task is found
366 ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK);
369 //Task could not be found
370 portEXIT_CRITICAL(&twdt_spinlock);
371 return ESP_ERR_NOT_FOUND;
374 void esp_task_wdt_feed()
376 portENTER_CRITICAL(&twdt_spinlock);
377 //Return immediately if TWDT has not been initialized
378 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), VOID_RETURN);
380 //Check if task is on list
381 TaskHandle_t handle = xTaskGetCurrentTaskHandle();
383 twdt_task_t *target_task = find_task_in_twdt_list(handle, &all_reset);
385 //reset the task if it's on the list, then return
386 if(target_task != NULL){
387 target_task->has_reset = true;
389 reset_hw_timer(); //Reset hardware timer if all other tasks have reset
391 portEXIT_CRITICAL(&twdt_spinlock);
395 //Add task if it's has not on list
396 target_task = calloc(1, sizeof(twdt_task_t));
397 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), VOID_RETURN); //If calloc failed
398 target_task->task_handle = handle;
399 target_task->has_reset = true;
400 target_task->next = NULL;
402 if (twdt_config->list == NULL) { //Adding to empty list
403 twdt_config->list = target_task;
404 } else { //Adding to tail of list
406 for (task = twdt_config->list; task->next != NULL; task = task->next){
407 ; //point task to current tail of wdt task list
409 task->next = target_task;
412 portEXIT_CRITICAL(&twdt_spinlock);