task_wdt.c 15 KB

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