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"
38 #include "esp_system_internal.h"
40 //Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret'
41 #define ASSERT_EXIT_CRIT_RETURN(cond, ret) ({ \
43 portEXIT_CRITICAL(&twdt_spinlock); \
48 //Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
51 //Structure used for each subscribed task
52 typedef struct twdt_task_t twdt_task_t;
54 TaskHandle_t task_handle;
59 //Structure used to hold run time configuration of the TWDT
60 typedef struct twdt_config_t twdt_config_t;
61 struct twdt_config_t {
62 twdt_task_t *list; //Linked list of subscribed tasks
63 uint32_t timeout; //Timeout period of TWDT
64 bool panic; //Flag to trigger panic when TWDT times out
65 intr_handle_t intr_handle;
68 static twdt_config_t *twdt_config = NULL;
69 static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
72 * Idle hook callback for Idle Tasks to reset the TWDT. This callback will only
73 * be registered to the Idle Hook of a particular core when the corresponding
74 * Idle Task subscribes to the TWDT.
76 static bool idle_hook_cb(void)
83 * Internal function that looks for the target task in the TWDT task list.
84 * Returns the list item if found and returns null if not found. Also checks if
85 * all the other tasks have reset. Should be called within critical.
87 static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
89 twdt_task_t *target = NULL;
91 for(twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
92 if(task->task_handle == handle){
93 target = task; //Get pointer to target task list member
95 if(task->has_reset == false){ //If a task has yet to reset
104 * Resets the hardware timer and has_reset flags of each task on the list.
105 * Called within critical
107 static void reset_hw_timer()
109 //All tasks have reset; time to reset the hardware timer.
110 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
112 TIMERG0.wdt_wprotect=0;
113 //Clear all has_reset flags in list
114 for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
115 task->has_reset=false;
120 * ISR for when TWDT times out. Checks for which tasks have not reset. Also
121 * triggers panic if configured to do so
123 static void task_wdt_isr(void *arg)
125 portENTER_CRITICAL(&twdt_spinlock);
126 twdt_task_t *twdttask;
128 //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
129 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
131 TIMERG0.wdt_wprotect=0;
132 //Acknowledge interrupt
133 TIMERG0.int_clr_timers.wdt=1;
134 //We are taking a spinlock while doing I/O (ets_printf) here. Normally, that is a pretty
135 //bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case,
136 //something bad already happened and reporting this is considered more important
137 //than the badness caused by a spinlock here.
139 //Return immediately if no tasks have been added to task list
140 ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN);
142 //Watchdog got triggered because at least one task did not reset in time.
143 ets_printf("Task watchdog got triggered. The following tasks did not reset the watchdog in time:\n");
144 for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) {
145 if (!twdttask->has_reset) {
146 cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1");
147 if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) cpu=DRAM_STR("CPU 0/1");
148 ets_printf(" - %s (%s)\n", pcTaskGetTaskName(twdttask->task_handle), cpu);
151 ets_printf(DRAM_STR("Tasks currently running:\n"));
152 for (int x=0; x<portNUM_PROCESSORS; x++) {
153 ets_printf("CPU %d: %s\n", x, pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(x)));
156 if (twdt_config->panic){ //Trigger Panic if configured to do so
157 ets_printf("Aborting.\n");
158 portEXIT_CRITICAL(&twdt_spinlock);
159 esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
163 portEXIT_CRITICAL(&twdt_spinlock);
167 * Initializes the TWDT by allocating memory for the config data
168 * structure, obtaining the idle task handles/registering idle hooks, and
169 * setting the hardware timer registers. If reconfiguring, it will just modify
170 * wdt_config and reset the hardware timer.
172 esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
174 portENTER_CRITICAL(&twdt_spinlock);
175 if(twdt_config == NULL){ //TWDT not initialized yet
176 //Allocate memory for wdt_config
177 twdt_config = calloc(1, sizeof(twdt_config_t));
178 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM);
180 twdt_config->list = NULL;
181 twdt_config->timeout = timeout;
182 twdt_config->panic = panic;
184 //Register Interrupt and ISR
185 ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle))
187 //Configure hardware timer
188 periph_module_enable(PERIPH_TIMG0_MODULE);
189 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
190 TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS
191 TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS
192 TIMERG0.wdt_config0.level_int_en=1;
193 TIMERG0.wdt_config0.stg0=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt
194 TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system
195 TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
196 TIMERG0.wdt_config2=twdt_config->timeout*2000; //Set timeout before interrupt
197 TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset
198 TIMERG0.wdt_config0.en=1;
200 TIMERG0.wdt_wprotect=0; //Enable write protection
202 }else{ //twdt_config previously initialized
203 //Reconfigure task wdt
204 twdt_config->panic = panic;
205 twdt_config->timeout = timeout;
207 //Reconfigure hardware timer
208 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
209 TIMERG0.wdt_config0.en=0; //Disable timer
210 TIMERG0.wdt_config2=twdt_config->timeout*2000; //Set timeout before interrupt
211 TIMERG0.wdt_config3=twdt_config->timeout*4000; //Set timeout before reset
212 TIMERG0.wdt_config0.en=1; //Renable timer
213 TIMERG0.wdt_feed=1; //Reset timer
214 TIMERG0.wdt_wprotect=0; //Enable write protection
216 portEXIT_CRITICAL(&twdt_spinlock);
220 esp_err_t esp_task_wdt_deinit()
222 portENTER_CRITICAL(&twdt_spinlock);
223 //TWDT must already be initialized
224 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
225 //Task list must be empty
226 ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
228 //Disable hardware timer
229 TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
230 TIMERG0.wdt_config0.en=0; //Disable timer
231 TIMERG0.wdt_wprotect=0; //Enable write protection
233 ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)) //Unregister interrupt
234 free(twdt_config); //Free twdt_config
236 portEXIT_CRITICAL(&twdt_spinlock);
240 esp_err_t esp_task_wdt_add(TaskHandle_t handle)
242 portENTER_CRITICAL(&twdt_spinlock);
243 //TWDT must already be initialized
244 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
246 twdt_task_t *target_task;
248 if (handle == NULL){ //Get handle of current task if none is provided
249 handle = xTaskGetCurrentTaskHandle();
251 //Check if tasks exists in task list, and if all other tasks have reset
252 target_task = find_task_in_twdt_list(handle, &all_reset);
253 //task cannot be already subscribed
254 ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
256 //Add target task to TWDT task list
257 target_task = calloc(1,sizeof(twdt_task_t));
258 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM);
259 target_task->task_handle = handle;
260 target_task->has_reset = true;
261 target_task->next = NULL;
262 if (twdt_config->list == NULL) { //Adding to empty list
263 twdt_config->list = target_task;
264 } else { //Adding to tail of list
266 for (task = twdt_config->list; task->next != NULL; task = task->next){
267 ; //point task to current tail of TWDT task list
269 task->next = target_task;
272 //If idle task, register the idle hook callback to appropriate core
273 for(int i = 0; i < portNUM_PROCESSORS; i++){
274 if(handle == xTaskGetIdleTaskHandleForCPU(i)){
275 ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i))
280 if(all_reset){ //Reset hardware timer if all other tasks in list have reset in
284 portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy
288 esp_err_t esp_task_wdt_reset()
290 portENTER_CRITICAL(&twdt_spinlock);
291 //TWDT must already be initialized
292 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
294 TaskHandle_t handle = xTaskGetCurrentTaskHandle();
295 twdt_task_t *target_task;
298 //Check if task exists in task list, and if all other tasks have reset
299 target_task = find_task_in_twdt_list(handle, &all_reset);
300 //Return error if trying to reset task that is not on the task list
301 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND);
303 target_task->has_reset = true; //Reset the task if it's on the task list
304 if(all_reset){ //Reset if all other tasks in list have reset in
308 portEXIT_CRITICAL(&twdt_spinlock);
312 esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
315 handle = xTaskGetCurrentTaskHandle();
317 portENTER_CRITICAL(&twdt_spinlock);
318 //Return error if twdt has not been initialized
319 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
321 twdt_task_t *target_task;
323 target_task = find_task_in_twdt_list(handle, &all_reset);
324 //Task doesn't exist on list. Return error
325 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG);
327 if(target_task == twdt_config->list){ //target_task is head of list. Delete
328 twdt_config->list = target_task->next;
330 }else{ //target_task not head of list. Delete
332 for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
333 ; //point prev to task preceding target_task
335 prev->next = target_task->next;
339 //If idle task, deregister idle hook callback form appropriate core
340 for(int i = 0; i < portNUM_PROCESSORS; i++){
341 if(handle == xTaskGetIdleTaskHandleForCPU(i)){
342 esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i);
347 if(all_reset){ //Reset hardware timer if all remaining tasks have reset
351 portEXIT_CRITICAL(&twdt_spinlock);
355 esp_err_t esp_task_wdt_status(TaskHandle_t handle)
358 handle = xTaskGetCurrentTaskHandle();
361 portENTER_CRITICAL(&twdt_spinlock);
362 //Return if TWDT is not initialized
363 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
366 for(task = twdt_config->list; task!=NULL; task=task->next){
367 //Return ESP_OK if task is found
368 ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK);
371 //Task could not be found
372 portEXIT_CRITICAL(&twdt_spinlock);
373 return ESP_ERR_NOT_FOUND;
376 void esp_task_wdt_feed()
378 portENTER_CRITICAL(&twdt_spinlock);
379 //Return immediately if TWDT has not been initialized
380 ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), VOID_RETURN);
382 //Check if task is on list
383 TaskHandle_t handle = xTaskGetCurrentTaskHandle();
385 twdt_task_t *target_task = find_task_in_twdt_list(handle, &all_reset);
387 //reset the task if it's on the list, then return
388 if(target_task != NULL){
389 target_task->has_reset = true;
391 reset_hw_timer(); //Reset hardware timer if all other tasks have reset
393 portEXIT_CRITICAL(&twdt_spinlock);
397 //Add task if it's has not on list
398 target_task = calloc(1, sizeof(twdt_task_t));
399 ASSERT_EXIT_CRIT_RETURN((target_task != NULL), VOID_RETURN); //If calloc failed
400 target_task->task_handle = handle;
401 target_task->has_reset = true;
402 target_task->next = NULL;
404 if (twdt_config->list == NULL) { //Adding to empty list
405 twdt_config->list = target_task;
406 } else { //Adding to tail of list
408 for (task = twdt_config->list; task->next != NULL; task = task->next){
409 ; //point task to current tail of wdt task list
411 task->next = target_task;
414 portEXIT_CRITICAL(&twdt_spinlock);