]> granicus.if.org Git - esp-idf/blob - components/esp32/task_wdt.c
Merge branch 'feature/ci_fuzzer_tests_lwip' into 'master'
[esp-idf] / components / esp32 / task_wdt.c
1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2 //
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
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 #include <stdint.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <stdbool.h>
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>
27 #include "esp_err.h"
28 #include "esp_intr.h"
29 #include "esp_intr_alloc.h"
30 #include "esp_attr.h"
31 #include "esp_freertos_hooks.h"
32 #include "soc/timer_group_struct.h"
33 #include "soc/timer_group_reg.h"
34 #include "esp_log.h"
35 #include "driver/timer.h"
36 #include "driver/periph_ctrl.h"
37 #include "esp_task_wdt.h"
38 #include "esp_system_internal.h"
39
40 //Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret'
41 #define ASSERT_EXIT_CRIT_RETURN(cond, ret)  ({                              \
42             if(!(cond)){                                                    \
43                 portEXIT_CRITICAL(&twdt_spinlock);                          \
44                 return ret;                                                 \
45             }                                                               \
46 })
47
48 //Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
49 #define VOID_RETURN
50
51 //Structure used for each subscribed task
52 typedef struct twdt_task_t twdt_task_t;
53 struct twdt_task_t {
54     TaskHandle_t task_handle;
55     bool has_reset;
56     twdt_task_t *next;
57 };
58
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;
66 };
67
68 static twdt_config_t *twdt_config = NULL;
69 static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
70
71 /*
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.
75  */
76 static bool idle_hook_cb(void)
77 {
78     esp_task_wdt_reset();
79     return true;
80 }
81
82 /*
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.
86  */
87 static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
88 {
89     twdt_task_t *target = NULL;
90     *all_reset = true;
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
94         }else{
95             if(task->has_reset == false){     //If a task has yet to reset
96                 *all_reset = false;
97             }
98         }
99     }
100     return target;
101 }
102
103 /*
104  * Resets the hardware timer and has_reset flags of each task on the list.
105  * Called within critical
106  */
107 static void reset_hw_timer()
108 {
109     //All tasks have reset; time to reset the hardware timer.
110     TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
111     TIMERG0.wdt_feed=1;
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;
116     }
117 }
118
119 /*
120  * ISR for when TWDT times out. Checks for which tasks have not reset. Also
121  * triggers panic if configured to do so
122  */
123 static void task_wdt_isr(void *arg)
124 {
125     portENTER_CRITICAL(&twdt_spinlock);
126     twdt_task_t *twdttask;
127     const char *cpu;
128     //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
129     TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
130     TIMERG0.wdt_feed=1;
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.
138
139     //Return immediately if no tasks have been added to task list
140     ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN);
141
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);
149         }
150     }
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)));
154     }
155
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);
160         abort();
161     }
162
163     portEXIT_CRITICAL(&twdt_spinlock);
164 }
165
166 /*
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.
171  */
172 esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
173 {
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);
179
180         twdt_config->list = NULL;
181         twdt_config->timeout = timeout;
182         twdt_config->panic = panic;
183
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))
186
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;
199         TIMERG0.wdt_feed=1;
200         TIMERG0.wdt_wprotect=0;                         //Enable write protection
201
202     }else{      //twdt_config previously initialized
203         //Reconfigure task wdt
204         twdt_config->panic = panic;
205         twdt_config->timeout = timeout;
206
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
215     }
216     portEXIT_CRITICAL(&twdt_spinlock);
217     return ESP_OK;
218 }
219
220 esp_err_t esp_task_wdt_deinit()
221 {
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);
227
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
232
233     ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle))  //Unregister interrupt
234     free(twdt_config);                      //Free twdt_config
235     twdt_config = NULL;
236     portEXIT_CRITICAL(&twdt_spinlock);
237     return ESP_OK;
238 }
239
240 esp_err_t esp_task_wdt_add(TaskHandle_t handle)
241 {
242     portENTER_CRITICAL(&twdt_spinlock);
243     //TWDT must already be initialized
244     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
245
246     twdt_task_t *target_task;
247     bool all_reset;
248     if (handle == NULL){    //Get handle of current task if none is provided
249         handle = xTaskGetCurrentTaskHandle();
250     }
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);
255
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
265         twdt_task_t *task;
266         for (task = twdt_config->list; task->next != NULL; task = task->next){
267             ;   //point task to current tail of TWDT task list
268         }
269         task->next = target_task;
270     }
271
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))
276             break;
277         }
278     }
279
280     if(all_reset){     //Reset hardware timer if all other tasks in list have reset in
281         reset_hw_timer();
282     }
283
284     portEXIT_CRITICAL(&twdt_spinlock);       //Nested critical if Legacy
285     return ESP_OK;
286 }
287
288 esp_err_t esp_task_wdt_reset()
289 {
290     portENTER_CRITICAL(&twdt_spinlock);
291     //TWDT must already be initialized
292     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
293
294     TaskHandle_t handle = xTaskGetCurrentTaskHandle();
295     twdt_task_t *target_task;
296     bool all_reset;
297
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);
302
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
305         reset_hw_timer();
306     }
307
308     portEXIT_CRITICAL(&twdt_spinlock);
309     return ESP_OK;
310 }
311
312 esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
313 {
314     if(handle == NULL){
315         handle = xTaskGetCurrentTaskHandle();
316     }
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);
320
321     twdt_task_t *target_task;
322     bool all_reset;
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);
326
327     if(target_task == twdt_config->list){     //target_task is head of list. Delete
328         twdt_config->list = target_task->next;
329         free(target_task);
330     }else{                                      //target_task not head of list. Delete
331         twdt_task_t *prev;
332         for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
333             ;   //point prev to task preceding target_task
334         }
335         prev->next = target_task->next;
336         free(target_task);
337     }
338
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);
343             break;
344         }
345     }
346
347     if(all_reset){     //Reset hardware timer if all remaining tasks have reset
348         reset_hw_timer();
349     }
350
351     portEXIT_CRITICAL(&twdt_spinlock);
352     return ESP_OK;
353 }
354
355 esp_err_t esp_task_wdt_status(TaskHandle_t handle)
356 {
357     if(handle == NULL){
358         handle = xTaskGetCurrentTaskHandle();
359     }
360
361     portENTER_CRITICAL(&twdt_spinlock);
362     //Return if TWDT is not initialized
363     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
364
365     twdt_task_t *task;
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);
369     }
370
371     //Task could not be found
372     portEXIT_CRITICAL(&twdt_spinlock);
373     return ESP_ERR_NOT_FOUND;
374 }
375
376 void esp_task_wdt_feed()
377 {
378     portENTER_CRITICAL(&twdt_spinlock);
379     //Return immediately if TWDT has not been initialized
380     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), VOID_RETURN);
381
382     //Check if task is on list
383     TaskHandle_t handle = xTaskGetCurrentTaskHandle();
384     bool all_reset;
385     twdt_task_t *target_task = find_task_in_twdt_list(handle, &all_reset);
386
387     //reset the task if it's on the list, then return
388     if(target_task != NULL){
389         target_task->has_reset = true;
390         if(all_reset){
391             reset_hw_timer();       //Reset hardware timer if all other tasks have reset
392         }
393         portEXIT_CRITICAL(&twdt_spinlock);
394         return;
395     }
396
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;
403
404     if (twdt_config->list == NULL) {    //Adding to empty list
405         twdt_config->list = target_task;
406     } else {    //Adding to tail of list
407         twdt_task_t *task;
408         for (task = twdt_config->list; task->next != NULL; task = task->next){
409             ;   //point task to current tail of wdt task list
410         }
411         task->next = target_task;
412     }
413
414     portEXIT_CRITICAL(&twdt_spinlock);
415 }
416
417