task_wdt.c 29 KB

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