• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "esp_osal/esp_osal.h"
22 #include "esp_osal/task.h"
23 #include "esp_osal/queue.h"
24 #include "esp_osal/semphr.h"
25 #include <esp_types.h>
26 #include "esp_err.h"
27 #include "esp_intr_alloc.h"
28 #include "esp_attr.h"
29 #include "esp_debug_helpers.h"
30 #include "esp_freertos_hooks.h"
31 #include "soc/timer_periph.h"
32 #include "esp_log.h"
33 #include "driver/timer.h"
34 #include "driver/periph_ctrl.h"
35 #include "esp_task_wdt.h"
36 #include "esp_private/system_internal.h"
37 #include "esp_private/crosscore_int.h"
38 #include "hal/timer_types.h"
39 #include "hal/wdt_hal.h"
40 
41 
42 static const char *TAG = "task_wdt";
43 
44 //Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret'
45 #define ASSERT_EXIT_CRIT_RETURN(cond, ret)  ({                              \
46             if(!(cond)){                                                    \
47                 portEXIT_CRITICAL(&twdt_spinlock);                          \
48                 return ret;                                                 \
49             }                                                               \
50 })
51 
52 //Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
53 #define VOID_RETURN
54 
55 //HAL related variables and constants
56 #define TWDT_INSTANCE           WDT_MWDT0
57 #define TWDT_TICKS_PER_US       MWDT0_TICKS_PER_US
58 #define TWDT_PRESCALER          MWDT0_TICK_PRESCALER   //Tick period of 500us if WDT source clock is 80MHz
59 static wdt_hal_context_t twdt_context;
60 
61 //Structure used for each subscribed task
62 typedef struct twdt_task_t twdt_task_t;
63 struct twdt_task_t {
64     TaskHandle_t task_handle;
65     bool has_reset;
66     twdt_task_t *next;
67 };
68 
69 //Structure used to hold run time configuration of the TWDT
70 typedef struct twdt_config_t twdt_config_t;
71 struct twdt_config_t {
72     twdt_task_t *list;      //Linked list of subscribed tasks
73     uint32_t timeout;       //Timeout period of TWDT
74     bool panic;             //Flag to trigger panic when TWDT times out
75     intr_handle_t intr_handle;
76 };
77 
78 static twdt_config_t *twdt_config = NULL;
79 static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
80 
81 /*
82  * Idle hook callback for Idle Tasks to reset the TWDT. This callback will only
83  * be registered to the Idle Hook of a particular core when the corresponding
84  * Idle Task subscribes to the TWDT.
85  */
idle_hook_cb(void)86 static bool idle_hook_cb(void)
87 {
88     esp_task_wdt_reset();
89     return true;
90 }
91 
92 /*
93  * Internal function that looks for the target task in the TWDT task list.
94  * Returns the list item if found and returns null if not found. Also checks if
95  * all the other tasks have reset. Should be called within critical.
96  */
find_task_in_twdt_list(TaskHandle_t handle,bool * all_reset)97 static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
98 {
99     twdt_task_t *target = NULL;
100     *all_reset = true;
101     for(twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
102         if(task->task_handle == handle){
103             target = task;   //Get pointer to target task list member
104         }else{
105             if(task->has_reset == false){     //If a task has yet to reset
106                 *all_reset = false;
107             }
108         }
109     }
110     return target;
111 }
112 
113 /*
114  * Resets the hardware timer and has_reset flags of each task on the list.
115  * Called within critical
116  */
reset_hw_timer(void)117 static void reset_hw_timer(void)
118 {
119     //All tasks have reset; time to reset the hardware timer.
120     wdt_hal_write_protect_disable(&twdt_context);
121     wdt_hal_feed(&twdt_context);
122     wdt_hal_write_protect_enable(&twdt_context);
123     //Clear all has_reset flags in list
124     for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
125         task->has_reset=false;
126     }
127 }
128 
129 /*
130  * This function is called by task_wdt_isr function (ISR for when TWDT times out).
131  * It can be redefined in user code to handle twdt events.
132  * Note: It has the same limitations as the interrupt function.
133  *       Do not use ESP_LOGI functions inside.
134  */
esp_task_wdt_isr_user_handler(void)135 void __attribute__((weak)) esp_task_wdt_isr_user_handler(void)
136 {
137 
138 }
139 
140 /*
141  * ISR for when TWDT times out. Checks for which tasks have not reset. Also
142  * triggers panic if configured to do so
143  */
task_wdt_isr(void * arg)144 static void task_wdt_isr(void *arg)
145 {
146     portENTER_CRITICAL_ISR(&twdt_spinlock);
147     twdt_task_t *twdttask;
148     const char *cpu;
149     //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
150     wdt_hal_write_protect_disable(&twdt_context);
151     wdt_hal_handle_intr(&twdt_context);     //Feeds WDT and clears acknowledges interrupt
152     wdt_hal_write_protect_enable(&twdt_context);
153 
154     //We are taking a spinlock while doing I/O (ESP_EARLY_LOGE) here. Normally, that is a pretty
155     //bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case,
156     //something bad already happened and reporting this is considered more important
157     //than the badness caused by a spinlock here.
158 
159     //Return immediately if no tasks have been added to task list
160     ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN);
161 
162     //Watchdog got triggered because at least one task did not reset in time.
163     ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks did not reset the watchdog in time:");
164     for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) {
165         if (!twdttask->has_reset) {
166             cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1");
167             if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) cpu=DRAM_STR("CPU 0/1");
168             ESP_EARLY_LOGE(TAG, " - %s (%s) handle:%p", pcTaskGetTaskName(twdttask->task_handle), cpu, twdttask->task_handle);
169         }
170     }
171     ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:"));
172     for (int x=0; x<portNUM_PROCESSORS; x++) {
173         ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(x)));
174     }
175 
176     esp_task_wdt_isr_user_handler();
177 
178     if (twdt_config->panic){     //Trigger Panic if configured to do so
179         ESP_EARLY_LOGE(TAG, "Aborting.");
180         portEXIT_CRITICAL_ISR(&twdt_spinlock);
181         esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
182         abort();
183     } else {
184 
185 #if !CONFIG_IDF_TARGET_ESP32C3 // TODO ESP32-C3 add backtrace printing support IDF-2285
186         int current_core = xPortGetCoreID();
187         //Print backtrace of current core
188         ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
189         esp_backtrace_print(100);
190 #if !CONFIG_FREERTOS_UNICORE
191         //Print backtrace of other core
192         ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core);
193         esp_crosscore_int_send_print_backtrace(!current_core);
194 #endif
195 #endif
196     }
197 	esp_task_wdt_deinit();
198     portEXIT_CRITICAL_ISR(&twdt_spinlock);
199 }
200 
201 /*
202  * Initializes the TWDT by allocating memory for the config data
203  * structure, obtaining the idle task handles/registering idle hooks, and
204  * setting the hardware timer registers. If reconfiguring, it will just modify
205  * wdt_config and reset the hardware timer.
206  */
esp_task_wdt_init(uint32_t timeout,bool panic)207 esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
208 {
209     portENTER_CRITICAL(&twdt_spinlock);
210     if(twdt_config == NULL){        //TWDT not initialized yet
211         //Allocate memory for wdt_config
212         twdt_config = calloc(1, sizeof(twdt_config_t));
213         ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM);
214 
215         twdt_config->list = NULL;
216         twdt_config->timeout = timeout;
217         twdt_config->panic = panic;
218 
219         //Register Interrupt and ISR
220         ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle));
221 
222         //Configure hardware timer
223         periph_module_enable(PERIPH_TIMG0_MODULE);
224         wdt_hal_init(&twdt_context, TWDT_INSTANCE, TWDT_PRESCALER, true);
225         wdt_hal_write_protect_disable(&twdt_context);
226         //Configure 1st stage timeout and behavior
227         wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * 1000000 / TWDT_TICKS_PER_US, WDT_STAGE_ACTION_INT);
228         //Configure 2nd stage timeout and behavior
229         wdt_hal_config_stage(&twdt_context, WDT_STAGE1, 2*twdt_config->timeout * 1000000 / TWDT_TICKS_PER_US, WDT_STAGE_ACTION_RESET_SYSTEM);
230         //Enable the WDT
231         wdt_hal_enable(&twdt_context);
232         wdt_hal_write_protect_enable(&twdt_context);
233     } else {      //twdt_config previously initialized
234         //Reconfigure task wdt
235         twdt_config->panic = panic;
236         twdt_config->timeout = timeout;
237 
238         //Reconfigure hardware timer
239         wdt_hal_write_protect_disable(&twdt_context);
240         wdt_hal_disable(&twdt_context);
241         wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout*1000*1000/TWDT_TICKS_PER_US, WDT_STAGE_ACTION_INT);
242         wdt_hal_config_stage(&twdt_context, WDT_STAGE1, 2*twdt_config->timeout*1000*1000/TWDT_TICKS_PER_US, WDT_STAGE_ACTION_RESET_SYSTEM);
243         wdt_hal_enable(&twdt_context);
244         wdt_hal_write_protect_enable(&twdt_context);
245     }
246     portEXIT_CRITICAL(&twdt_spinlock);
247     return ESP_OK;
248 }
249 
esp_task_wdt_deinit(void)250 esp_err_t esp_task_wdt_deinit(void)
251 {
252     portENTER_CRITICAL(&twdt_spinlock);
253     //TWDT must already be initialized
254     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
255     //Task list must be empty
256     ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
257 
258     //Disable hardware timer
259     wdt_hal_deinit(&twdt_context);
260 
261     ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle));  //Unregister interrupt
262     free(twdt_config);                      //Free twdt_config
263     twdt_config = NULL;
264     portEXIT_CRITICAL(&twdt_spinlock);
265     return ESP_OK;
266 }
267 
esp_task_wdt_add(TaskHandle_t handle)268 esp_err_t esp_task_wdt_add(TaskHandle_t handle)
269 {
270     portENTER_CRITICAL(&twdt_spinlock);
271     //TWDT must already be initialized
272     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
273 
274     twdt_task_t *target_task;
275     bool all_reset;
276     if (handle == NULL){    //Get handle of current task if none is provided
277         handle = xTaskGetCurrentTaskHandle();
278     }
279     //Check if tasks exists in task list, and if all other tasks have reset
280     target_task = find_task_in_twdt_list(handle, &all_reset);
281     //task cannot be already subscribed
282     ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
283 
284     //Add target task to TWDT task list
285     target_task = calloc(1,sizeof(twdt_task_t));
286     ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM);
287     target_task->task_handle = handle;
288     target_task->has_reset = true;
289     target_task->next = NULL;
290     if (twdt_config->list == NULL) {    //Adding to empty list
291         twdt_config->list = target_task;
292     } else {    //Adding to tail of list
293         twdt_task_t *task;
294         for (task = twdt_config->list; task->next != NULL; task = task->next){
295             ;   //point task to current tail of TWDT task list
296         }
297         task->next = target_task;
298     }
299 
300 #if 0
301     //If idle task, register the idle hook callback to appropriate core
302     for(int i = 0; i < portNUM_PROCESSORS; i++){
303         if(handle == xTaskGetIdleTaskHandleForCPU(i)){
304             ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i));
305             break;
306         }
307     }
308 #endif
309 
310     if(all_reset){     //Reset hardware timer if all other tasks in list have reset in
311         reset_hw_timer();
312     }
313 
314     portEXIT_CRITICAL(&twdt_spinlock);       //Nested critical if Legacy
315     return ESP_OK;
316 }
317 
esp_task_wdt_reset(void)318 esp_err_t esp_task_wdt_reset(void)
319 {
320     portENTER_CRITICAL(&twdt_spinlock);
321     //TWDT must already be initialized
322     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
323 
324     TaskHandle_t handle = xTaskGetCurrentTaskHandle();
325     twdt_task_t *target_task;
326     bool all_reset;
327 
328     //Check if task exists in task list, and if all other tasks have reset
329     target_task = find_task_in_twdt_list(handle, &all_reset);
330     //Return error if trying to reset task that is not on the task list
331     ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND);
332 
333     target_task->has_reset = true;    //Reset the task if it's on the task list
334     if(all_reset){     //Reset if all other tasks in list have reset in
335         reset_hw_timer();
336     }
337 
338     portEXIT_CRITICAL(&twdt_spinlock);
339     return ESP_OK;
340 }
341 
esp_task_wdt_delete(TaskHandle_t handle)342 esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
343 {
344     if(handle == NULL){
345         handle = xTaskGetCurrentTaskHandle();
346     }
347     portENTER_CRITICAL(&twdt_spinlock);
348     //Return error if twdt has not been initialized
349     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
350 
351     twdt_task_t *target_task;
352     bool all_reset;
353     target_task = find_task_in_twdt_list(handle, &all_reset);
354     //Task doesn't exist on list. Return error
355     ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG);
356 
357     if(target_task == twdt_config->list){     //target_task is head of list. Delete
358         twdt_config->list = target_task->next;
359         free(target_task);
360     }else{                                      //target_task not head of list. Delete
361         twdt_task_t *prev;
362         for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
363             ;   //point prev to task preceding target_task
364         }
365         prev->next = target_task->next;
366         free(target_task);
367     }
368 
369 #if 0
370     //If idle task, deregister idle hook callback form appropriate core
371     for(int i = 0; i < portNUM_PROCESSORS; i++){
372         if(handle == xTaskGetIdleTaskHandleForCPU(i)){
373             esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i);
374             break;
375         }
376     }
377 #endif
378 
379     if(all_reset){     //Reset hardware timer if all remaining tasks have reset
380         reset_hw_timer();
381     }
382 
383     portEXIT_CRITICAL(&twdt_spinlock);
384     return ESP_OK;
385 }
386 
esp_task_wdt_status(TaskHandle_t handle)387 esp_err_t esp_task_wdt_status(TaskHandle_t handle)
388 {
389     if(handle == NULL){
390         handle = xTaskGetCurrentTaskHandle();
391     }
392 
393     portENTER_CRITICAL(&twdt_spinlock);
394     //Return if TWDT is not initialized
395     ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
396 
397     twdt_task_t *task;
398     for(task = twdt_config->list; task!=NULL; task=task->next){
399         //Return ESP_OK if task is found
400         ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK);
401     }
402 
403     //Task could not be found
404     portEXIT_CRITICAL(&twdt_spinlock);
405     return ESP_ERR_NOT_FOUND;
406 }
407