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