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