]> granicus.if.org Git - esp-idf/blob - components/esp32/task_wdt.c
Merge branch 'feature/bt_a2dp_i2s' 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
37 #include "esp_task_wdt.h"
38
39 //Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret'
40 #define ASSERT_EXIT_CRIT_RETURN(cond, ret)  ({                              \
41             if(!(cond)){                                                    \
42                 portEXIT_CRITICAL(&twdt_spinlock);                          \
43                 return ret;                                                 \
44             }                                                               \
45 })
46
47 //Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
48 #define VOID_RETURN
49
50 //Structure used for each subscribed task
51 typedef struct twdt_task_t twdt_task_t;
52 struct twdt_task_t {
53     TaskHandle_t task_handle;
54     bool has_reset;
55     twdt_task_t *next;
56 };
57
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;
65 };
66
67 static twdt_config_t *twdt_config = NULL;
68 static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
69
70 /*
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.
74  */
75 static bool idle_hook_cb(void)
76 {
77     esp_task_wdt_reset();
78     return true;
79 }
80
81 /*
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.
85  */
86 static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
87 {
88     twdt_task_t *target = NULL;
89     *all_reset = true;
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
93         }else{
94             if(task->has_reset == false){     //If a task has yet to reset
95                 *all_reset = false;
96             }
97         }
98     }
99     return target;
100 }
101
102 /*
103  * Resets the hardware timer and has_reset flags of each task on the list.
104  * Called within critical
105  */
106 static void reset_hw_timer()
107 {
108     //All tasks have reset; time to reset the hardware timer.
109     TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
110     TIMERG0.wdt_feed=1;
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;
115     }
116 }
117
118 /*
119  * ISR for when TWDT times out. Checks for which tasks have not reset. Also
120  * triggers panic if configured to do so
121  */
122 static void task_wdt_isr(void *arg)
123 {
124     portENTER_CRITICAL(&twdt_spinlock);
125     twdt_task_t *twdttask;
126     const char *cpu;
127     //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
128     TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
129     TIMERG0.wdt_feed=1;
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.
137
138     //Return immediately if no tasks have been added to task list
139     ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN);
140
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);
148         }
149     }
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)));
153     }
154
155     if (twdt_config->panic){     //Trigger Panic if configured to do so
156         ets_printf("Aborting.\n");
157         portEXIT_CRITICAL(&twdt_spinlock);
158         abort();
159     }
160
161     portEXIT_CRITICAL(&twdt_spinlock);
162 }
163
164 /*
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.
169  */
170 esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
171 {
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);
177
178         twdt_config->list = NULL;
179         twdt_config->timeout = timeout;
180         twdt_config->panic = panic;
181
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))
184
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;
196         TIMERG0.wdt_feed=1;
197         TIMERG0.wdt_wprotect=0;                         //Enable write protection
198
199     }else{      //twdt_config previously initialized
200         //Reconfigure task wdt
201         twdt_config->panic = panic;
202         twdt_config->timeout = timeout;
203
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
212     }
213     portEXIT_CRITICAL(&twdt_spinlock);
214     return ESP_OK;
215 }
216
217 esp_err_t esp_task_wdt_deinit()
218 {
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);
224
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
229
230     ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle))  //Unregister interrupt
231     free(twdt_config);                      //Free twdt_config
232     twdt_config = NULL;
233     portEXIT_CRITICAL(&twdt_spinlock);
234     return ESP_OK;
235 }
236
237 esp_err_t esp_task_wdt_add(TaskHandle_t handle)
238 {
239     portENTER_CRITICAL(&twdt_spinlock);
240     //TWDT must already be initialized
241     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
242
243     twdt_task_t *target_task;
244     bool all_reset;
245     if (handle == NULL){    //Get handle of current task if none is provided
246         handle = xTaskGetCurrentTaskHandle();
247     }
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);
252
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
262         twdt_task_t *task;
263         for (task = twdt_config->list; task->next != NULL; task = task->next){
264             ;   //point task to current tail of TWDT task list
265         }
266         task->next = target_task;
267     }
268
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))
273             break;
274         }
275     }
276
277     if(all_reset){     //Reset hardware timer if all other tasks in list have reset in
278         reset_hw_timer();
279     }
280
281     portEXIT_CRITICAL(&twdt_spinlock);       //Nested critical if Legacy
282     return ESP_OK;
283 }
284
285 esp_err_t esp_task_wdt_reset()
286 {
287     portENTER_CRITICAL(&twdt_spinlock);
288     //TWDT must already be initialized
289     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
290
291     TaskHandle_t handle = xTaskGetCurrentTaskHandle();
292     twdt_task_t *target_task;
293     bool all_reset;
294
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);
299
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
302         reset_hw_timer();
303     }
304
305     portEXIT_CRITICAL(&twdt_spinlock);
306     return ESP_OK;
307 }
308
309 esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
310 {
311     if(handle == NULL){
312         handle = xTaskGetCurrentTaskHandle();
313     }
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);
317
318     twdt_task_t *target_task;
319     bool all_reset;
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);
323
324     if(target_task == twdt_config->list){     //target_task is head of list. Delete
325         twdt_config->list = target_task->next;
326         free(target_task);
327     }else{                                      //target_task not head of list. Delete
328         twdt_task_t *prev;
329         for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
330             ;   //point prev to task preceding target_task
331         }
332         prev->next = target_task->next;
333         free(target_task);
334     }
335
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);
340             break;
341         }
342     }
343
344     if(all_reset){     //Reset hardware timer if all remaining tasks have reset
345         reset_hw_timer();
346     }
347
348     portEXIT_CRITICAL(&twdt_spinlock);
349     return ESP_OK;
350 }
351
352 esp_err_t esp_task_wdt_status(TaskHandle_t handle)
353 {
354     if(handle == NULL){
355         handle = xTaskGetCurrentTaskHandle();
356     }
357
358     portENTER_CRITICAL(&twdt_spinlock);
359     //Return if TWDT is not initialized
360     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
361
362     twdt_task_t *task;
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);
366     }
367
368     //Task could not be found
369     portEXIT_CRITICAL(&twdt_spinlock);
370     return ESP_ERR_NOT_FOUND;
371 }
372
373 void esp_task_wdt_feed()
374 {
375     portENTER_CRITICAL(&twdt_spinlock);
376     //Return immediately if TWDT has not been initialized
377     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), VOID_RETURN);
378
379     //Check if task is on list
380     TaskHandle_t handle = xTaskGetCurrentTaskHandle();
381     bool all_reset;
382     twdt_task_t *target_task = find_task_in_twdt_list(handle, &all_reset);
383
384     //reset the task if it's on the list, then return
385     if(target_task != NULL){
386         target_task->has_reset = true;
387         if(all_reset){
388             reset_hw_timer();       //Reset hardware timer if all other tasks have reset
389         }
390         portEXIT_CRITICAL(&twdt_spinlock);
391         return;
392     }
393
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;
400
401     if (twdt_config->list == NULL) {    //Adding to empty list
402         twdt_config->list = target_task;
403     } else {    //Adding to tail of list
404         twdt_task_t *task;
405         for (task = twdt_config->list; task->next != NULL; task = task->next){
406             ;   //point task to current tail of wdt task list
407         }
408         task->next = target_task;
409     }
410
411     portEXIT_CRITICAL(&twdt_spinlock);
412 }
413
414