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