task_wdt.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #include <stdbool.h>
  11. #include "sdkconfig.h"
  12. #include "freertos/FreeRTOS.h"
  13. #include "freertos/task.h"
  14. #include "freertos/queue.h"
  15. #include "freertos/semphr.h"
  16. #include <esp_types.h>
  17. #include "esp_err.h"
  18. #include "esp_intr_alloc.h"
  19. #include "esp_attr.h"
  20. #include "esp_debug_helpers.h"
  21. #include "esp_freertos_hooks.h"
  22. #include "soc/timer_periph.h"
  23. #include "esp_log.h"
  24. #include "esp_private/periph_ctrl.h"
  25. #include "esp_task_wdt.h"
  26. #include "esp_private/system_internal.h"
  27. #include "esp_private/crosscore_int.h"
  28. #include "hal/timer_types.h"
  29. #include "hal/wdt_hal.h"
  30. static const char *TAG = "task_wdt";
  31. //Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret'
  32. #define ASSERT_EXIT_CRIT_RETURN(cond, ret) ({ \
  33. if(!(cond)){ \
  34. portEXIT_CRITICAL(&twdt_spinlock); \
  35. return ret; \
  36. } \
  37. })
  38. //Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
  39. #define VOID_RETURN
  40. //HAL related variables and constants
  41. #define TWDT_INSTANCE WDT_MWDT0
  42. #define TWDT_TICKS_PER_US MWDT0_TICKS_PER_US
  43. #define TWDT_PRESCALER MWDT0_TICK_PRESCALER //Tick period of 500us if WDT source clock is 80MHz
  44. static wdt_hal_context_t twdt_context;
  45. //Structure used for each subscribed task
  46. typedef struct twdt_task_t twdt_task_t;
  47. struct twdt_task_t {
  48. TaskHandle_t task_handle;
  49. bool has_reset;
  50. twdt_task_t *next;
  51. };
  52. //Structure used to hold run time configuration of the TWDT
  53. typedef struct twdt_config_t twdt_config_t;
  54. struct twdt_config_t {
  55. twdt_task_t *list; //Linked list of subscribed tasks
  56. uint32_t timeout; //Timeout period of TWDT
  57. bool panic; //Flag to trigger panic when TWDT times out
  58. intr_handle_t intr_handle;
  59. };
  60. static twdt_config_t *twdt_config = NULL;
  61. static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
  62. /*
  63. * Idle hook callback for Idle Tasks to reset the TWDT. This callback will only
  64. * be registered to the Idle Hook of a particular core when the corresponding
  65. * Idle Task subscribes to the TWDT.
  66. */
  67. static bool idle_hook_cb(void)
  68. {
  69. esp_task_wdt_reset();
  70. return true;
  71. }
  72. /*
  73. * Internal function that looks for the target task in the TWDT task list.
  74. * Returns the list item if found and returns null if not found. Also checks if
  75. * all the other tasks have reset. Should be called within critical.
  76. */
  77. static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset)
  78. {
  79. twdt_task_t *target = NULL;
  80. *all_reset = true;
  81. for(twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
  82. if(task->task_handle == handle){
  83. target = task; //Get pointer to target task list member
  84. }else{
  85. if(task->has_reset == false){ //If a task has yet to reset
  86. *all_reset = false;
  87. }
  88. }
  89. }
  90. return target;
  91. }
  92. /*
  93. * Resets the hardware timer and has_reset flags of each task on the list.
  94. * Called within critical
  95. */
  96. static void reset_hw_timer(void)
  97. {
  98. //All tasks have reset; time to reset the hardware timer.
  99. wdt_hal_write_protect_disable(&twdt_context);
  100. wdt_hal_feed(&twdt_context);
  101. wdt_hal_write_protect_enable(&twdt_context);
  102. //Clear all has_reset flags in list
  103. for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
  104. task->has_reset=false;
  105. }
  106. }
  107. /*
  108. * This function is called by task_wdt_isr function (ISR for when TWDT times out).
  109. * It can be redefined in user code to handle twdt events.
  110. * Note: It has the same limitations as the interrupt function.
  111. * Do not use ESP_LOGI functions inside.
  112. */
  113. void __attribute__((weak)) esp_task_wdt_isr_user_handler(void)
  114. {
  115. }
  116. /*
  117. * ISR for when TWDT times out. Checks for which tasks have not reset. Also
  118. * triggers panic if configured to do so
  119. */
  120. static void task_wdt_isr(void *arg)
  121. {
  122. portENTER_CRITICAL_ISR(&twdt_spinlock);
  123. twdt_task_t *twdttask;
  124. const char *cpu;
  125. //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
  126. wdt_hal_write_protect_disable(&twdt_context);
  127. wdt_hal_handle_intr(&twdt_context); //Feeds WDT and clears acknowledges interrupt
  128. wdt_hal_write_protect_enable(&twdt_context);
  129. //We are taking a spinlock while doing I/O (ESP_EARLY_LOGE) here. Normally, that is a pretty
  130. //bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case,
  131. //something bad already happened and reporting this is considered more important
  132. //than the badness caused by a spinlock here.
  133. //Return immediately if no tasks have been added to task list
  134. ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN);
  135. //Watchdog got triggered because at least one task did not reset in time.
  136. ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks did not reset the watchdog in time:");
  137. for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) {
  138. if (!twdttask->has_reset) {
  139. cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1");
  140. if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) {
  141. cpu=DRAM_STR("CPU 0/1");
  142. }
  143. ESP_EARLY_LOGE(TAG, " - %s (%s)", pcTaskGetName(twdttask->task_handle), cpu);
  144. }
  145. }
  146. ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:"));
  147. for (int x=0; x<portNUM_PROCESSORS; x++) {
  148. ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetName(xTaskGetCurrentTaskHandleForCPU(x)));
  149. }
  150. esp_task_wdt_isr_user_handler();
  151. if (twdt_config->panic){ //Trigger Panic if configured to do so
  152. ESP_EARLY_LOGE(TAG, "Aborting.");
  153. portEXIT_CRITICAL_ISR(&twdt_spinlock);
  154. esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
  155. abort();
  156. } else {
  157. #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 // TODO: ESP32-C3 IDF-2986
  158. int current_core = xPortGetCoreID();
  159. //Print backtrace of current core
  160. ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
  161. esp_backtrace_print(100);
  162. #if !CONFIG_FREERTOS_UNICORE
  163. //Print backtrace of other core
  164. ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core);
  165. esp_crosscore_int_send_print_backtrace(!current_core);
  166. #endif
  167. #endif
  168. }
  169. portEXIT_CRITICAL_ISR(&twdt_spinlock);
  170. }
  171. /*
  172. * Initializes the TWDT by allocating memory for the config data
  173. * structure, obtaining the idle task handles/registering idle hooks, and
  174. * setting the hardware timer registers. If reconfiguring, it will just modify
  175. * wdt_config and reset the hardware timer.
  176. */
  177. esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
  178. {
  179. portENTER_CRITICAL(&twdt_spinlock);
  180. if(twdt_config == NULL){ //TWDT not initialized yet
  181. //Allocate memory for wdt_config
  182. twdt_config = calloc(1, sizeof(twdt_config_t));
  183. ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM);
  184. twdt_config->list = NULL;
  185. twdt_config->timeout = timeout;
  186. twdt_config->panic = panic;
  187. //Register Interrupt and ISR
  188. ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle));
  189. //Configure hardware timer
  190. periph_module_enable(PERIPH_TIMG0_MODULE);
  191. wdt_hal_init(&twdt_context, TWDT_INSTANCE, TWDT_PRESCALER, true);
  192. wdt_hal_write_protect_disable(&twdt_context);
  193. //Configure 1st stage timeout and behavior
  194. wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT);
  195. //Configure 2nd stage timeout and behavior
  196. wdt_hal_config_stage(&twdt_context, WDT_STAGE1, twdt_config->timeout * (2 * 1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
  197. //Enable the WDT
  198. wdt_hal_enable(&twdt_context);
  199. wdt_hal_write_protect_enable(&twdt_context);
  200. } else { //twdt_config previously initialized
  201. //Reconfigure task wdt
  202. twdt_config->panic = panic;
  203. twdt_config->timeout = timeout;
  204. //Reconfigure hardware timer
  205. wdt_hal_write_protect_disable(&twdt_context);
  206. wdt_hal_disable(&twdt_context);
  207. wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT);
  208. wdt_hal_config_stage(&twdt_context, WDT_STAGE1, twdt_config->timeout * (2 * 1000 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
  209. wdt_hal_enable(&twdt_context);
  210. wdt_hal_write_protect_enable(&twdt_context);
  211. }
  212. portEXIT_CRITICAL(&twdt_spinlock);
  213. return ESP_OK;
  214. }
  215. esp_err_t esp_task_wdt_deinit(void)
  216. {
  217. portENTER_CRITICAL(&twdt_spinlock);
  218. //TWDT must already be initialized
  219. ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
  220. //Task list must be empty
  221. ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
  222. //Disable hardware timer
  223. wdt_hal_deinit(&twdt_context);
  224. ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)); //Unregister interrupt
  225. free(twdt_config); //Free twdt_config
  226. twdt_config = NULL;
  227. portEXIT_CRITICAL(&twdt_spinlock);
  228. return ESP_OK;
  229. }
  230. esp_err_t esp_task_wdt_add(TaskHandle_t handle)
  231. {
  232. portENTER_CRITICAL(&twdt_spinlock);
  233. //TWDT must already be initialized
  234. ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
  235. twdt_task_t *target_task;
  236. bool all_reset;
  237. if (handle == NULL){ //Get handle of current task if none is provided
  238. handle = xTaskGetCurrentTaskHandle();
  239. }
  240. //Check if tasks exists in task list, and if all other tasks have reset
  241. target_task = find_task_in_twdt_list(handle, &all_reset);
  242. //task cannot be already subscribed
  243. ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
  244. //Add target task to TWDT task list
  245. target_task = calloc(1,sizeof(twdt_task_t));
  246. ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM);
  247. target_task->task_handle = handle;
  248. target_task->has_reset = true;
  249. target_task->next = NULL;
  250. if (twdt_config->list == NULL) { //Adding to empty list
  251. twdt_config->list = target_task;
  252. } else { //Adding to tail of list
  253. twdt_task_t *task;
  254. for (task = twdt_config->list; task->next != NULL; task = task->next){
  255. ; //point task to current tail of TWDT task list
  256. }
  257. task->next = target_task;
  258. }
  259. //If idle task, register the idle hook callback to appropriate core
  260. for(int i = 0; i < portNUM_PROCESSORS; i++){
  261. if(handle == xTaskGetIdleTaskHandleForCPU(i)){
  262. ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i));
  263. break;
  264. }
  265. }
  266. if(all_reset){ //Reset hardware timer if all other tasks in list have reset in
  267. reset_hw_timer();
  268. }
  269. portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy
  270. return ESP_OK;
  271. }
  272. esp_err_t esp_task_wdt_reset(void)
  273. {
  274. portENTER_CRITICAL(&twdt_spinlock);
  275. //TWDT must already be initialized
  276. ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
  277. TaskHandle_t handle = xTaskGetCurrentTaskHandle();
  278. twdt_task_t *target_task;
  279. bool all_reset;
  280. //Check if task exists in task list, and if all other tasks have reset
  281. target_task = find_task_in_twdt_list(handle, &all_reset);
  282. //Return error if trying to reset task that is not on the task list
  283. ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND);
  284. target_task->has_reset = true; //Reset the task if it's on the task list
  285. if(all_reset){ //Reset if all other tasks in list have reset in
  286. reset_hw_timer();
  287. }
  288. portEXIT_CRITICAL(&twdt_spinlock);
  289. return ESP_OK;
  290. }
  291. esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
  292. {
  293. if(handle == NULL){
  294. handle = xTaskGetCurrentTaskHandle();
  295. }
  296. portENTER_CRITICAL(&twdt_spinlock);
  297. //Return error if twdt has not been initialized
  298. ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
  299. twdt_task_t *target_task;
  300. bool all_reset;
  301. target_task = find_task_in_twdt_list(handle, &all_reset);
  302. //Task doesn't exist on list. Return error
  303. ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG);
  304. if(target_task == twdt_config->list){ //target_task is head of list. Delete
  305. twdt_config->list = target_task->next;
  306. free(target_task);
  307. }else{ //target_task not head of list. Delete
  308. twdt_task_t *prev;
  309. for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
  310. ; //point prev to task preceding target_task
  311. }
  312. prev->next = target_task->next;
  313. free(target_task);
  314. }
  315. //If idle task, deregister idle hook callback form appropriate core
  316. for(int i = 0; i < portNUM_PROCESSORS; i++){
  317. if(handle == xTaskGetIdleTaskHandleForCPU(i)){
  318. esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i);
  319. break;
  320. }
  321. }
  322. if(all_reset){ //Reset hardware timer if all remaining tasks have reset
  323. reset_hw_timer();
  324. }
  325. portEXIT_CRITICAL(&twdt_spinlock);
  326. return ESP_OK;
  327. }
  328. esp_err_t esp_task_wdt_status(TaskHandle_t handle)
  329. {
  330. if(handle == NULL){
  331. handle = xTaskGetCurrentTaskHandle();
  332. }
  333. portENTER_CRITICAL(&twdt_spinlock);
  334. //Return if TWDT is not initialized
  335. ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
  336. twdt_task_t *task;
  337. for(task = twdt_config->list; task!=NULL; task=task->next){
  338. //Return ESP_OK if task is found
  339. ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK);
  340. }
  341. //Task could not be found
  342. portEXIT_CRITICAL(&twdt_spinlock);
  343. return ESP_ERR_NOT_FOUND;
  344. }