1 /*
2  * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdint.h>
8 #include <string.h>
9 #include <stdbool.h>
10 #include <stdio.h>
11 #include <sys/queue.h>
12 #include "sdkconfig.h"
13 #include "freertos/FreeRTOS.h"
14 #include "freertos/task.h"
15 #include "freertos/task_snapshot.h"
16 #include "esp_err.h"
17 #include "esp_attr.h"
18 #include "esp_check.h"
19 #include "esp_log.h"
20 #include "esp_debug_helpers.h"
21 #include "esp_freertos_hooks.h"
22 #include "esp_task_wdt.h"
23 #include "esp_private/system_internal.h"
24 #include "esp_private/crosscore_int.h"
25 #include "esp_private/esp_task_wdt.h"
26 #include "esp_private/esp_task_wdt_impl.h"
27 
28 #if CONFIG_ESP_SYSTEM_USE_EH_FRAME
29 #include "esp_private/eh_frame_parser.h"
30 #endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME
31 
32 
33 #if CONFIG_IDF_TARGET_ARCH_RISCV && !CONFIG_ESP_SYSTEM_USE_EH_FRAME
34 /* Function used to print all the registers pointed by the given frame .*/
35 extern void panic_print_registers(const void *frame, int core);
36 #endif // CONFIG_IDF_TARGET_ARCH_RISCV && !CONFIG_ESP_SYSTEM_USE_EH_FRAME
37 
38 /* We will use this function in order to simulate an `abort()` occurring in
39  * a different context than the one it's called from. */
40 extern void xt_unhandled_exception(void *frame);
41 
42 /* Forward declaration of the idle hook callback */
43 static bool idle_hook_cb(void);
44 
45 /* Global flag set to make the `panic` mechanism think a real `abort()` was
46  * called. This is used in the ISR handler, in case we have to panic when
47  * a task doesn't feed its timer. */
48 extern bool g_panic_abort;
49 
50 /* Global flag marking whether the current ISR is a Task Watchdog ISR. */
51 bool g_twdt_isr = false;
52 
53 // --------------------------------------------------- Definitions -----------------------------------------------------
54 
55 // ---------------------- Typedefs -------------------------
56 
57 /**
58  * @brief Structure used for each subscribed task
59  */
60 typedef struct twdt_entry twdt_entry_t;
61 struct twdt_entry {
62     SLIST_ENTRY(twdt_entry) slist_entry;
63     TaskHandle_t task_handle;   // NULL if user entry
64     const char *user_name;      // NULL if task entry
65     bool has_reset;
66 };
67 
68 // Structure used to hold run time configuration of the TWDT
69 typedef struct twdt_obj twdt_obj_t;
70 struct twdt_obj {
71     twdt_ctx_t impl_ctx;
72     SLIST_HEAD(entry_list_head, twdt_entry) entries_slist;
73     uint32_t idle_core_mask;    // Current core's who's idle tasks are subscribed
74     bool panic; // Flag to trigger panic when TWDT times out
75     bool waiting_for_task; // Flag to start the timer as soon as a task is added
76 };
77 
78 // ----------------------- Objects -------------------------
79 
80 static const char *TAG = "task_wdt";
81 static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
82 static twdt_obj_t *p_twdt_obj = NULL;
83 
84 #if CONFIG_FREERTOS_SMP
85 #define CORE_USER_NAME_LEN      8   // Long enough for "CPU XXX"
86 static esp_task_wdt_user_handle_t core_user_handles[portNUM_PROCESSORS] = {NULL};
87 static char core_user_names[portNUM_PROCESSORS][CORE_USER_NAME_LEN];
88 #endif
89 
90 // ----------------------------------------------------- Private -------------------------------------------------------
91 
92 // ----------------------- Helpers -------------------------
93 
94 /**
95  * @brief Reset the timer and reset flags of each entry
96  * When entering this function, the spinlock has already been taken, no need to take it back.
97  */
task_wdt_timer_feed(void)98 static void task_wdt_timer_feed(void)
99 {
100     esp_task_wdt_impl_timer_feed(p_twdt_obj->impl_ctx);
101 
102     /* Clear the has_reset flag in each entry */
103     twdt_entry_t *entry;
104     SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
105         entry->has_reset = false;
106     }
107 }
108 
109 /**
110  * @brief Checks whether a user entry exists and if all other entries have been reset
111  *
112  * @param[in] user_entry User entry
113  * @param[out] all_reset Whether all entries have been reset
114  * @return Whether the user entry exists
115  */
find_entry_and_check_all_reset(twdt_entry_t * user_entry,bool * all_reset)116 static bool find_entry_and_check_all_reset(twdt_entry_t *user_entry, bool *all_reset)
117 {
118     bool found_user_entry = false;
119     bool found_non_reset = false;
120 
121     twdt_entry_t *entry;
122     SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
123         if (entry == user_entry) {
124             found_user_entry = true;
125         } else if (entry->has_reset == false) {
126             found_non_reset = true;
127         }
128     }
129 
130     *all_reset = !found_non_reset;
131     return found_user_entry;
132 }
133 
134 /**
135  * @brief Find whether a task entry exists, and checks if all other entries have been reset
136  *
137  * @param[in] handle Task handle
138  * @param[out] all_reset Whether all entries have been reset
139  * @return Task entry, or NULL if not found
140  */
find_entry_from_task_handle_and_check_all_reset(TaskHandle_t handle,bool * all_reset)141 static twdt_entry_t *find_entry_from_task_handle_and_check_all_reset(TaskHandle_t handle, bool *all_reset)
142 {
143     twdt_entry_t *target = NULL;
144     bool found_non_reset = false;
145 
146     twdt_entry_t *entry;
147     SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
148         if (entry->task_handle == handle) {
149             target = entry;
150         } else if (entry->has_reset == false) {
151             found_non_reset = true;
152         }
153     }
154 
155     *all_reset = !found_non_reset;
156     return target;
157 }
158 
159 /**
160  * @brief Create a task/user entry and add it to the task WDT
161  *
162  * @param[in] is_task Whether the entry is a task entry or user entry
163  * @param[in] entry_data Data associated with the entry (either a task handle or user entry name)
164  * @param[out] entry_ret Pointer to created entry
165  * @return ESP_OK if entry was added, failure otherwise
166  */
add_entry(bool is_task,void * entry_data,twdt_entry_t ** entry_ret)167 static esp_err_t add_entry(bool is_task, void *entry_data, twdt_entry_t **entry_ret)
168 {
169     esp_err_t ret;
170 
171     // Allocate entry object
172     twdt_entry_t *entry = calloc(1, sizeof(twdt_entry_t));
173     if (entry == NULL) {
174         return ESP_ERR_NO_MEM;
175     }
176     if (is_task) {
177         entry->task_handle = (TaskHandle_t)entry_data;
178     } else {
179         entry->user_name = (const char *)entry_data;
180     }
181 
182     portENTER_CRITICAL(&spinlock);
183     // Check TWDT state
184     ESP_GOTO_ON_FALSE_ISR((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, state_err, TAG, "task watchdog was never initialized");
185     // Check if the task is an entry, and if all entries have been reset
186     bool all_reset;
187     if (is_task) {
188         twdt_entry_t *entry_found = find_entry_from_task_handle_and_check_all_reset(entry->task_handle, &all_reset);
189         ESP_GOTO_ON_FALSE_ISR((entry_found == NULL), ESP_ERR_INVALID_ARG, state_err, TAG, "task is already subscribed");
190     } else {
191         bool entry_found = find_entry_and_check_all_reset(entry, &all_reset);
192         ESP_GOTO_ON_FALSE_ISR(!entry_found, ESP_ERR_INVALID_ARG, state_err, TAG, "user is already subscribed");
193     }
194     // Add entry to list
195     SLIST_INSERT_HEAD(&p_twdt_obj->entries_slist, entry, slist_entry);
196     // Start the timer if it has not been started yet and was waiting on a task to registered
197     if (p_twdt_obj->waiting_for_task) {
198         esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx);
199         p_twdt_obj->waiting_for_task = false;
200     }
201     if (all_reset) {   //Reset hardware timer if all other tasks in list have reset in
202         task_wdt_timer_feed();
203     }
204     portEXIT_CRITICAL(&spinlock);
205     *entry_ret = entry;
206     return ESP_OK;
207 
208 state_err:
209     portEXIT_CRITICAL(&spinlock);
210     free(entry);
211     return ret;
212 }
213 
214 /**
215  * @brief Delete a task/user entry
216  *
217  * @param[in] is_task Whether the entry is a task entry or user entry
218  * @param[in] entry_data Data associated with the entry (either a task handle or user entry name)
219  * @return ESP_OK if entry was deleted, failure otherwise
220  */
delete_entry(bool is_task,void * entry_data)221 static esp_err_t delete_entry(bool is_task, void *entry_data)
222 {
223     esp_err_t ret;
224 
225     portENTER_CRITICAL(&spinlock);
226     // Check TWDT state
227     ESP_GOTO_ON_FALSE_ISR((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized");
228     // Find entry for task
229     bool all_reset;
230     twdt_entry_t *entry;
231     if (is_task) {
232         entry = find_entry_from_task_handle_and_check_all_reset((TaskHandle_t)entry_data, &all_reset);
233         ESP_GOTO_ON_FALSE_ISR((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found");
234     } else {
235         entry = (twdt_entry_t *)entry_data;
236         bool entry_found = find_entry_and_check_all_reset(entry, &all_reset);
237         ESP_GOTO_ON_FALSE_ISR(entry_found, ESP_ERR_NOT_FOUND, err, TAG, "user not found");
238     }
239     // Remove entry
240     SLIST_REMOVE(&p_twdt_obj->entries_slist, entry, twdt_entry, slist_entry);
241     /* Stop the timer if we don't have any more tasks/objects to watch */
242     if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) {
243         p_twdt_obj->waiting_for_task = true;
244         esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx);
245     } else {
246         p_twdt_obj->waiting_for_task = false;
247     }
248     /* Reset hardware timer if all remaining tasks have reset and if the list of tasks is not empty */
249     if (!p_twdt_obj->waiting_for_task && all_reset) {
250         task_wdt_timer_feed();
251     }
252     portEXIT_CRITICAL(&spinlock);
253     free(entry);
254     return ESP_OK;
255 
256 err:
257     portEXIT_CRITICAL(&spinlock);
258     return ret;
259 }
260 
261 /**
262  * @brief Unsubscribe the idle tasks of one or more cores
263  *
264  * @param core_mask
265  */
unsubscribe_idle(uint32_t core_mask)266 static void unsubscribe_idle(uint32_t core_mask)
267 {
268     int core_num = 0;
269     while (core_mask != 0) {
270         if (core_mask & 0x1) {
271 #if CONFIG_FREERTOS_SMP
272             assert(core_user_handles[core_num]);
273             esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, core_num);
274             ESP_ERROR_CHECK(esp_task_wdt_delete_user(core_user_handles[core_num]));
275             core_user_handles[core_num] = NULL;
276 #else // CONFIG_FREERTOS_SMP
277             TaskHandle_t idle_task_handle = xTaskGetIdleTaskHandleForCPU(core_num);
278             assert(idle_task_handle);
279             esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, core_num);
280             ESP_ERROR_CHECK(esp_task_wdt_delete(idle_task_handle));
281 #endif // CONFIG_FREERTOS_SMP
282         }
283         core_mask >>= 1;
284         core_num++;
285     }
286 }
287 
288 
289 /**
290  * @brief Subscribes the idle tasks of one or more cores
291  *
292  * @param core_mask Bit mask of cores to subscribe
293  */
subscribe_idle(uint32_t core_mask)294 static void subscribe_idle(uint32_t core_mask)
295 {
296     int core_num = 0;
297     while (core_mask != 0) {
298         if (core_mask & 0x1) {
299 #if CONFIG_FREERTOS_SMP
300             snprintf(core_user_names[core_num], CORE_USER_NAME_LEN, "CPU %d", (uint8_t)core_num);
301             ESP_ERROR_CHECK(esp_task_wdt_add_user((const char *)core_user_names[core_num], &core_user_handles[core_num]));
302             ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, core_num));
303 #else // CONFIG_FREERTOS_SMP
304             TaskHandle_t idle_task_handle = xTaskGetIdleTaskHandleForCPU(core_num);
305             assert(idle_task_handle);
306             ESP_ERROR_CHECK(esp_task_wdt_add(idle_task_handle));
307             ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, core_num));
308 #endif // CONFIG_FREERTOS_SMP
309         }
310         core_mask >>= 1;
311         core_num++;
312     }
313 }
314 
315 
316 /**
317  * The behavior of the Task Watchdog depends on the configuration from the `menuconfig`.
318  * It can be summarized as follow, regardless of the target:
319  * +------------------------+--------------------------------+--------------------------+
320  * | \  Panic configuration |                                |                          |
321  * |  +------------------+  | Panic Enabled                  | Panic Disabled           |
322  * | TWDT triggered on    \ |                                |                          |
323  * +------------------------+--------------------------------+--------------------------+
324  * |                        | - Current core backtrace       | - Current core backtrace |
325  * | Both Cores             | - Crosscore TWDT abort         | - Crosscore backtrace    |
326  * |                        | - Wait for other core to abort |                          |
327  * +------------------------+--------------------------------+--------------------------+
328  * | Other Core             | - Crosscore TWDT abort         | - Crosscore backtrace    |
329  * +------------------------+--------------------------------+--------------------------+
330  * | Current Core           | - Abort from current CPU       | - Current core backtrace |
331  * +------------------------+--------------------------------+--------------------------+
332  *
333  */
334 
get_task_affinity(const TaskHandle_t xTask)335 static UBaseType_t get_task_affinity(const TaskHandle_t xTask)
336 {
337     if (xTask == NULL) {
338     /* User entry, we cannot predict on which core it is scheduled to run,
339      * so let's mark all cores as failing */
340 #if configNUM_CORES > 1
341         return BIT(1) | BIT(0);
342 #else
343         return BIT(0);
344 #endif
345     }
346 
347 #if CONFIG_FREERTOS_SMP
348     #if configNUM_CORES > 1
349         return vTaskCoreAffinityGet(xTask);
350     #else
351         return BIT(0);
352     #endif
353 #else
354     BaseType_t task_affinity = xTaskGetAffinity(xTask);
355     if (task_affinity == 0 || task_affinity == 1) {
356         return BIT(task_affinity);
357     }
358     return BIT(1) | BIT(0);
359 #endif
360 }
361 
362 #if CONFIG_IDF_TARGET_ARCH_RISCV
363 
task_wdt_timeout_handling(int cores_fail,bool panic)364 static void task_wdt_timeout_handling(int cores_fail, bool panic)
365 {
366     /* For RISC-V, make sure the cores that fail is only composed of core 0. */
367     assert(cores_fail == BIT(0));
368 
369     const int current_core = 0;
370     TaskSnapshot_t snapshot = { 0 };
371     BaseType_t ret = vTaskGetSnapshot(xTaskGetCurrentTaskHandle(), &snapshot);
372 
373     if (p_twdt_obj->panic) {
374         assert(ret == pdTRUE);
375         ESP_EARLY_LOGE(TAG, "Aborting.");
376         esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
377         /**
378          * We cannot simply use `abort` here because the `panic` handler would
379          * interpret it as if the task watchdog ISR aborted and so, print this
380          * current ISR backtrace/context. We want to trick the `panic` handler
381          * to think the task itself is aborting.
382          * To do so, we need to get the interruptee's top of the stack. It contains
383          * its own context, saved when the interrupt occurred.
384          * We must also set the global flag that states that an abort occurred
385          * (and not a panic)
386          **/
387         g_panic_abort = true;
388         g_twdt_isr = true;
389         void *frame = (void *) snapshot.pxTopOfStack;
390 #if CONFIG_ESP_SYSTEM_USE_EH_FRAME
391         ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
392 #endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME
393         xt_unhandled_exception(frame);
394     } else {
395         /* Targets based on a RISC-V CPU cannot perform backtracing that easily.
396          * We have two options here:
397          *     - Perform backtracing at runtime.
398          *     - Let IDF monitor do the backtracing for us. Used during panic already.
399          * This could be configurable, choosing one or the other depending on
400          * CONFIG_ESP_SYSTEM_USE_EH_FRAME configuration option.
401          *
402          * In both cases, this takes time, and we are in an ISR, we must
403          * exit this handler as fast as possible, then we will simply print
404          * the interruptee's registers.
405          */
406         if (ret == pdTRUE) {
407             void *frame = (void *) snapshot.pxTopOfStack;
408 #if CONFIG_ESP_SYSTEM_USE_EH_FRAME
409             ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
410             esp_eh_frame_print_backtrace(frame);
411 #else // CONFIG_ESP_SYSTEM_USE_EH_FRAME
412             ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) registers", current_core);
413             panic_print_registers(frame, current_core);
414             esp_rom_printf("\r\n");
415 #endif // CONFIG_ESP_SYSTEM_USE_EH_FRAME
416         }
417     }
418 }
419 
420 #else // CONFIG_IDF_TARGET_ARCH_RISCV
421 
422 /**
423  * Function simulating an abort coming from the interrupted task of the current
424  * core.
425  * It is called either by the function right below or by a crosscore interrupt,
426  * in the case where the other core (than the main one) has to abort because one
427  * of his tasks didn't reset the TWDT on time.
428  */
task_wdt_timeout_abort_xtensa(bool current_core)429 void task_wdt_timeout_abort_xtensa(bool current_core)
430 {
431     TaskSnapshot_t snapshot = { 0 };
432     BaseType_t ret = pdTRUE;
433 
434     ESP_EARLY_LOGE(TAG, "Aborting.");
435     esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
436     ret = vTaskGetSnapshot(xTaskGetCurrentTaskHandle(), &snapshot);
437     assert(ret == pdTRUE);
438     g_panic_abort = true;
439     /* For Xtensa, we should set this flag as late as possible, as this function may
440      * be called after a crosscore interrupt. Indeed, a higher interrupt may occur
441      * after calling the crosscore interrupt, if its handler fails, this flag
442      * shall not be set.
443      * This flag will tell the coredump component (if activated) that yes, we are in
444      * an ISR context, but it is intended, it is not because an ISR encountered an
445      * exception. If we don't set such flag, later tested by coredump, the later would
446      * switch the execution frame/context we are giving it to the interrupt stack.
447      * For details about this behavior in the TODO task: IDF-5694
448      */
449     g_twdt_isr = true;
450     void *frame = (void *) snapshot.pxTopOfStack;
451     if (current_core) {
452         ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", xPortGetCoreID());
453     } else {
454         ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", xPortGetCoreID());
455     }
456     xt_unhandled_exception(frame);
457 }
458 
task_wdt_timeout_handling(int cores_fail,bool panic)459 static void task_wdt_timeout_handling(int cores_fail, bool panic)
460 {
461     const int current_core = xPortGetCoreID();
462 
463     if (panic) {
464 #if !CONFIG_FREERTOS_UNICORE
465         const int other_core = !current_core;
466 
467         if ((cores_fail & BIT(0)) && (cores_fail & BIT(1))) {
468             /* In the case where both CPUs have failing tasks, print the current CPU backtrace and then let the
469              * other core fail. */
470             ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
471             esp_backtrace_print(100);
472             /* TODO: the interrupt we send should have the highest priority */
473             esp_crosscore_int_send_twdt_abort(other_core);
474             /* We are going to abort, on the other core, we have nothing to
475              * do anymore here, just wait until we crash */
476             while (1) {}
477         } else if (cores_fail & BIT(other_core)) {
478             /* If only the other core is failing, we can tell it to abort. */
479             esp_crosscore_int_send_twdt_abort(other_core);
480             while (1) {}
481         }
482 #endif // !CONFIG_FREERTOS_UNICORE
483         /* Current core is failing, abort right now */
484         task_wdt_timeout_abort_xtensa(true);
485     } else {
486         /* Print backtrace of the core that failed to reset the watchdog */
487         if (cores_fail & BIT(current_core)) {
488             ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
489             esp_backtrace_print(100);
490         }
491 #if !CONFIG_FREERTOS_UNICORE
492         const int other_core = !current_core;
493         if (cores_fail & BIT(other_core)) {
494             ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", other_core);
495             esp_crosscore_int_send_print_backtrace(other_core);
496         }
497 #endif // !CONFIG_FREERTOS_UNICORE
498     }
499 }
500 
501 #endif // CONFIG_IDF_TARGET_ARCH_RISCV
502 
503 
504 // ---------------------- Callbacks ------------------------
505 
506 /**
507  * @brief Idle hook callback
508  *
509  * Idle hook callback called by the idle tasks to feed the TWDT
510  *
511  * @return Whether the idle tasks should continue idling
512  */
idle_hook_cb(void)513 static bool idle_hook_cb(void)
514 {
515 #if CONFIG_FREERTOS_SMP
516     esp_task_wdt_reset_user(core_user_handles[xPortGetCoreID()]);
517 #else // CONFIG_FREERTOS_SMP
518     esp_task_wdt_reset();
519 #endif // CONFIG_FREERTOS_SMP
520     return true;
521 }
522 
523 /**
524  * @brief TWDT timeout ISR function
525  *
526  * The ISR checks which entries have not been reset, prints some debugging information, and triggers a panic if
527  * configured to do so.
528  *
529  * @param arg ISR argument
530  */
task_wdt_isr(void * arg)531 static void task_wdt_isr(void *arg)
532 {
533     portENTER_CRITICAL_ISR(&spinlock);
534     esp_task_wdt_impl_timeout_triggered(p_twdt_obj->impl_ctx);
535 
536     /* Keep a bitmap of CPU cores having tasks that have not reset TWDT.
537      * Bit 0 represents core 0, bit 1 represents core 1, and so on. */
538     int cpus_fail = 0;
539     bool panic = p_twdt_obj->panic;
540 
541 	if (esp_task_wdt_print_triggered_tasks(NULL, NULL, &cpus_fail) != ESP_OK) {
542         // If there are no entries, there's nothing to do.
543         portEXIT_CRITICAL_ISR(&spinlock);
544         return;
545 	}
546 
547     ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:"));
548     for (int x = 0; x < portNUM_PROCESSORS; x++) {
549         ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetName(xTaskGetCurrentTaskHandleForCPU(x)));
550     }
551     portEXIT_CRITICAL_ISR(&spinlock);
552 
553     /* Run user ISR handler.
554      * This function has been declared as weak, thus, it may be possible that it was not defines.
555      * to check this, we can directly test its address. In any case, the linker will get rid of
556      * this `if` when linking, this means that if the function was not defined, the whole `if`
557      * block will be discarded (zero runtime overhead), else only the function call will be kept.
558      */
559     if (esp_task_wdt_isr_user_handler != NULL) {
560         esp_task_wdt_isr_user_handler();
561     }
562 
563     // Trigger configured timeout behavior (e.g., panic or print backtrace)
564     assert(cpus_fail != 0);
565     task_wdt_timeout_handling(cpus_fail, panic);
566 }
567 
568 // ----------------------------------------------------- Public --------------------------------------------------------
569 
esp_task_wdt_init(const esp_task_wdt_config_t * config)570 esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config)
571 {
572     ESP_RETURN_ON_FALSE((config != NULL && config->idle_core_mask < (1 << portNUM_PROCESSORS)), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
573     ESP_RETURN_ON_FALSE(p_twdt_obj == NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT already initialized");
574     esp_err_t ret = ESP_OK;
575     twdt_obj_t *obj = NULL;
576 
577     /* Allocate and initialize the global object */
578     obj = calloc(1, sizeof(twdt_obj_t));
579     ESP_GOTO_ON_FALSE((obj != NULL), ESP_ERR_NO_MEM, err, TAG, "insufficient memory");
580     SLIST_INIT(&obj->entries_slist);
581     obj->panic = config->trigger_panic;
582 
583     /* Allocate the timer itself, NOT STARTED */
584     ret = esp_task_wdt_impl_timer_allocate(config, task_wdt_isr, &obj->impl_ctx);
585     if (ret != ESP_OK) {
586         goto err;
587     }
588 
589     /* No error so far, we can assign it to the driver object */
590     p_twdt_obj = obj;
591 
592     /* Update which core's idle tasks are subscribed */
593     p_twdt_obj->idle_core_mask = config->idle_core_mask;
594     if (config->idle_core_mask) {
595         /* Subscribe the new cores idle tasks */
596         subscribe_idle(config->idle_core_mask);
597     }
598 
599     /* Start the timer only if we are watching some tasks */
600     if (!SLIST_EMPTY(&p_twdt_obj->entries_slist)) {
601         p_twdt_obj->waiting_for_task = false;
602         esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx);
603     } else {
604         p_twdt_obj->waiting_for_task = true;
605     }
606 
607     return ESP_OK;
608 err:
609     free(obj);
610     return ret;
611 }
612 
esp_task_wdt_reconfigure(const esp_task_wdt_config_t * config)613 esp_err_t esp_task_wdt_reconfigure(const esp_task_wdt_config_t *config)
614 {
615     ESP_RETURN_ON_FALSE((config != NULL && config->idle_core_mask < (1 << portNUM_PROCESSORS)), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
616     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT not initialized yet");
617     uint32_t old_core_mask = 0;
618     esp_err_t ret = ESP_OK;
619 
620     /* Stop the timer to make sure we don't get into the ISR while reconfiguring the TWDT */
621     portENTER_CRITICAL(&spinlock);
622     ret = esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx);
623     if (ret != ESP_OK) {
624         goto err;
625     }
626 
627     /* We can start reconfiguring the tasks */
628     p_twdt_obj->panic = config->trigger_panic;
629 
630     /* Reconfigure the timer underneath (without restarting it) */
631     ret = esp_task_wdt_impl_timer_reconfigure(p_twdt_obj->impl_ctx, config);
632     if (ret != ESP_OK) {
633         goto err;
634     }
635 
636     old_core_mask = p_twdt_obj->idle_core_mask;
637     /* If the new mask is different than the old one, we have to subscribe the new idle tasks */
638     if (old_core_mask != config->idle_core_mask) {
639         p_twdt_obj->idle_core_mask = config->idle_core_mask;
640 
641         /* Unsubscribe all previously watched core idle tasks */
642         unsubscribe_idle(old_core_mask);
643 
644         if (config->idle_core_mask) {
645             /* Subscribe the new cores idle tasks */
646             subscribe_idle(config->idle_core_mask);
647         }
648     }
649 
650     /* Start the timer only if we are watching some tasks */
651     if (!SLIST_EMPTY(&p_twdt_obj->entries_slist)) {
652         esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx);
653     }
654 
655     portEXIT_CRITICAL(&spinlock);
656 err:
657     return ESP_OK;
658 }
659 
esp_task_wdt_stop(void)660 esp_err_t esp_task_wdt_stop(void)
661 {
662     esp_err_t ret = ESP_OK;
663 
664     /* If the timer has not been initialized, do not attempt to stop it */
665     if (p_twdt_obj == NULL) {
666         ret = ESP_ERR_INVALID_STATE;
667     }
668 
669     if (ret == ESP_OK) {
670         portENTER_CRITICAL(&spinlock);
671         ret = esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx);
672         portEXIT_CRITICAL(&spinlock);
673     }
674 
675     return ret;
676 }
677 
esp_task_wdt_restart(void)678 esp_err_t esp_task_wdt_restart(void)
679 {
680     esp_err_t ret = ESP_OK;
681 
682     /* If the timer has not been initialized, do not attempt to stop it */
683     if (p_twdt_obj == NULL) {
684         ret = ESP_ERR_INVALID_STATE;
685     }
686 
687     if (ret == ESP_OK) {
688         portENTER_CRITICAL(&spinlock);
689         ret = esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx);
690         portEXIT_CRITICAL(&spinlock);
691     }
692 
693     return ret;
694 }
695 
esp_task_wdt_deinit(void)696 esp_err_t esp_task_wdt_deinit(void)
697 {
698     esp_err_t ret;
699 
700     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
701 
702     // Unsubscribe all previously watched core idle tasks
703     unsubscribe_idle(p_twdt_obj->idle_core_mask);
704 
705     // Check TWDT state
706     ESP_GOTO_ON_FALSE_ISR(SLIST_EMPTY(&p_twdt_obj->entries_slist), ESP_ERR_INVALID_STATE, err, TAG, "Tasks/users still subscribed");
707 
708     // Disable the timer
709     esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx);
710 
711     // Free driver resources
712     esp_task_wdt_impl_timer_free(p_twdt_obj->impl_ctx);
713 
714     // Free the global object
715     free(p_twdt_obj);
716     p_twdt_obj = NULL;
717 
718     return ESP_OK;
719 
720 err:
721     subscribe_idle(p_twdt_obj->idle_core_mask); // Resubscribe idle tasks
722     return ret;
723 }
724 
esp_task_wdt_add(TaskHandle_t task_handle)725 esp_err_t esp_task_wdt_add(TaskHandle_t task_handle)
726 {
727     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
728     esp_err_t ret;
729     if (task_handle == NULL) {   // Get handle of current task if none is provided
730         task_handle = xTaskGetCurrentTaskHandle();
731     }
732 
733     twdt_entry_t *entry;
734     ret = add_entry(true, (void *)task_handle, &entry);
735     (void) entry; // Returned entry pointer not used
736     return ret;
737 }
738 
esp_task_wdt_add_user(const char * user_name,esp_task_wdt_user_handle_t * user_handle_ret)739 esp_err_t esp_task_wdt_add_user(const char *user_name, esp_task_wdt_user_handle_t *user_handle_ret)
740 {
741     ESP_RETURN_ON_FALSE((user_name != NULL && user_handle_ret != NULL), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
742     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
743     esp_err_t ret;
744     twdt_entry_t *entry;
745     ret = add_entry(false, (void *)user_name, &entry);
746     if (ret == ESP_OK) {
747         *user_handle_ret = (esp_task_wdt_user_handle_t)entry;
748     }
749     return ret;
750 }
751 
esp_task_wdt_reset(void)752 esp_err_t esp_task_wdt_reset(void)
753 {
754     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
755     esp_err_t ret;
756     TaskHandle_t handle = xTaskGetCurrentTaskHandle();
757 
758     portENTER_CRITICAL(&spinlock);
759     // Find entry from task handle
760     bool all_reset;
761     twdt_entry_t *entry;
762     entry = find_entry_from_task_handle_and_check_all_reset(handle, &all_reset);
763     ESP_GOTO_ON_FALSE_ISR((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found");
764     // Mark entry as reset and issue timer reset if all entries have been reset
765     entry->has_reset = true;    // Reset the task if it's on the task list
766     if (all_reset) {    // Reset if all other tasks in list have reset in
767         task_wdt_timer_feed();
768     }
769     ret = ESP_OK;
770 err:
771     portEXIT_CRITICAL(&spinlock);
772 
773     return ret;
774 }
775 
esp_task_wdt_reset_user(esp_task_wdt_user_handle_t user_handle)776 esp_err_t esp_task_wdt_reset_user(esp_task_wdt_user_handle_t user_handle)
777 {
778     ESP_RETURN_ON_FALSE(user_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
779     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
780     esp_err_t ret;
781 
782     portENTER_CRITICAL(&spinlock);
783     // Check if entry exists
784     bool all_reset;
785     twdt_entry_t *entry = (twdt_entry_t *)user_handle;
786     bool entry_found = find_entry_and_check_all_reset(entry, &all_reset);
787     ESP_GOTO_ON_FALSE_ISR(entry_found, ESP_ERR_NOT_FOUND, err, TAG, "user handle not found");
788     // Mark entry as reset and issue timer reset if all entries have been reset
789     entry->has_reset = true;    // Reset the task if it's on the task list
790     if (all_reset) {    // Reset if all other tasks in list have reset in
791         task_wdt_timer_feed();
792     }
793     ret = ESP_OK;
794 err:
795     portEXIT_CRITICAL(&spinlock);
796 
797     return ret;
798 }
799 
esp_task_wdt_delete(TaskHandle_t task_handle)800 esp_err_t esp_task_wdt_delete(TaskHandle_t task_handle)
801 {
802     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
803     esp_err_t ret;
804     if (task_handle == NULL) {
805         task_handle = xTaskGetCurrentTaskHandle();
806     }
807 
808     ret = delete_entry(true, (void *)task_handle);
809     return ret;
810 }
811 
esp_task_wdt_delete_user(esp_task_wdt_user_handle_t user_handle)812 esp_err_t esp_task_wdt_delete_user(esp_task_wdt_user_handle_t user_handle)
813 {
814     ESP_RETURN_ON_FALSE(user_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
815     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
816     return delete_entry(false, (void *)user_handle);
817 }
818 
esp_task_wdt_status(TaskHandle_t task_handle)819 esp_err_t esp_task_wdt_status(TaskHandle_t task_handle)
820 {
821     ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT was never initialized");
822     esp_err_t ret;
823     if (task_handle == NULL) {
824         task_handle = xTaskGetCurrentTaskHandle();
825     }
826 
827     portENTER_CRITICAL(&spinlock);
828     // Find entry for task
829     bool all_reset;
830     twdt_entry_t *entry;
831     entry = find_entry_from_task_handle_and_check_all_reset(task_handle, &all_reset);
832     (void) all_reset;   // Unused
833     ret = (entry != NULL) ? ESP_OK : ESP_ERR_NOT_FOUND;
834     portEXIT_CRITICAL(&spinlock);
835 
836     return ret;
837 }
838 
esp_task_wdt_print_triggered_tasks(task_wdt_msg_handler msg_handler,void * opaque,int * cpus_fail)839 esp_err_t esp_task_wdt_print_triggered_tasks(task_wdt_msg_handler msg_handler, void *opaque, int *cpus_fail)
840 {
841     if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) {
842         return ESP_FAIL;
843     }
844 
845     twdt_entry_t *entry;
846     const char *caption = "Task watchdog got triggered. "
847         "The following tasks/users did not reset the watchdog in time:";
848 
849     if (msg_handler == NULL) {
850         ESP_EARLY_LOGE(TAG, "%s", caption);
851     } else {
852         msg_handler(opaque, caption);
853     }
854 
855     // Find what entries triggered the TWDT timeout (i.e., which entries have not been reset)
856     SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
857         if (!entry->has_reset) {
858             const char *cpu;
859             const char *name = entry->task_handle ? pcTaskGetName(entry->task_handle) : entry->user_name;
860             const UBaseType_t affinity = get_task_affinity(entry->task_handle);
861             if (cpus_fail) {
862                 *cpus_fail |= affinity;
863             }
864             if (affinity == BIT(0)) {
865                 cpu = " (CPU 0)";
866             } else if (affinity == BIT(1)) {
867                 cpu = " (CPU 1)";
868             } else {
869                 cpu = " (CPU 0/1)";
870             }
871             if (msg_handler == NULL) {
872                 ESP_EARLY_LOGE(TAG, " - %s%s", name, cpu);
873             } else {
874                 msg_handler(opaque, "\n - ");
875                 msg_handler(opaque, name);
876                 msg_handler(opaque, cpu);
877             }
878         }
879     }
880     return ESP_OK;
881 }
882