task_wdt.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * Copyright (c) 2020 Libre Solar Technologies GmbH
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #include "task_wdt/task_wdt.h"
  7. #include <drivers/watchdog.h>
  8. #include <sys/reboot.h>
  9. #include <device.h>
  10. #include <errno.h>
  11. #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
  12. #include <logging/log.h>
  13. LOG_MODULE_REGISTER(task_wdt);
  14. /*
  15. * This dummy channel is used to continue feeding the hardware watchdog if the
  16. * task watchdog timeouts are too long for regular updates
  17. */
  18. #define TASK_WDT_BACKGROUND_CHANNEL UINTPTR_MAX
  19. /*
  20. * Task watchdog channel data
  21. */
  22. struct task_wdt_channel {
  23. /* period in milliseconds used to reset the timeout, set to 0 to
  24. * indicate that the channel is available
  25. */
  26. uint32_t reload_period;
  27. /* abs. ticks when this channel expires (updated by task_wdt_feed) */
  28. int64_t timeout_abs_ticks;
  29. /* user data passed to the callback function */
  30. void *user_data;
  31. /* function to be called when watchdog timer expired */
  32. task_wdt_callback_t callback;
  33. };
  34. /* array of all task watchdog channels */
  35. static struct task_wdt_channel channels[CONFIG_TASK_WDT_CHANNELS];
  36. static struct k_spinlock channels_lock;
  37. /* timer used for watchdog handling */
  38. static struct k_timer timer;
  39. #ifdef CONFIG_TASK_WDT_HW_FALLBACK
  40. /* pointer to the hardware watchdog used as a fallback */
  41. static const struct device *hw_wdt_dev;
  42. static int hw_wdt_channel;
  43. static bool hw_wdt_started;
  44. #endif
  45. static void schedule_next_timeout(int64_t current_ticks)
  46. {
  47. uintptr_t next_channel_id; /* channel which will time out next */
  48. int64_t next_timeout; /* timeout in absolute ticks of this channel */
  49. #ifdef CONFIG_TASK_WDT_HW_FALLBACK
  50. next_channel_id = TASK_WDT_BACKGROUND_CHANNEL;
  51. next_timeout = current_ticks +
  52. k_ms_to_ticks_ceil64(CONFIG_TASK_WDT_MIN_TIMEOUT);
  53. #else
  54. next_channel_id = 0;
  55. next_timeout = INT64_MAX;
  56. #endif
  57. /* find minimum timeout of all channels */
  58. for (int id = 0; id < ARRAY_SIZE(channels); id++) {
  59. if (channels[id].reload_period != 0 &&
  60. channels[id].timeout_abs_ticks < next_timeout) {
  61. next_channel_id = id;
  62. next_timeout = channels[id].timeout_abs_ticks;
  63. }
  64. }
  65. /* update task wdt kernel timer */
  66. k_timer_user_data_set(&timer, (void *)next_channel_id);
  67. k_timer_start(&timer, K_TIMEOUT_ABS_TICKS(next_timeout), K_FOREVER);
  68. #ifdef CONFIG_TASK_WDT_HW_FALLBACK
  69. if (hw_wdt_started) {
  70. wdt_feed(hw_wdt_dev, hw_wdt_channel);
  71. }
  72. #endif
  73. }
  74. /**
  75. * @brief Task watchdog timer callback.
  76. *
  77. * If the device operates as intended, this function will never be called,
  78. * as the timer is continuously restarted with the next due timeout in the
  79. * task_wdt_feed() function.
  80. *
  81. * If all task watchdogs have longer timeouts than the hardware watchdog,
  82. * this function is called regularly (via the background channel). This
  83. * should be avoided by setting CONFIG_TASK_WDT_MIN_TIMEOUT to the minimum
  84. * task watchdog timeout used in the application.
  85. *
  86. * @param timer_id Pointer to the timer which called the function
  87. */
  88. static void task_wdt_trigger(struct k_timer *timer_id)
  89. {
  90. uintptr_t channel_id = (uintptr_t)k_timer_user_data_get(timer_id);
  91. bool bg_channel = IS_ENABLED(CONFIG_TASK_WDT_HW_FALLBACK) &&
  92. (channel_id == TASK_WDT_BACKGROUND_CHANNEL);
  93. /* If the timeout expired for the background channel (so the hardware
  94. * watchdog needs to be fed) or for a channel that has been deleted,
  95. * only schedule a new timeout (the hardware watchdog, if used, will be
  96. * fed right after that new timeout is scheduled).
  97. */
  98. if (bg_channel || channels[channel_id].reload_period == 0) {
  99. schedule_next_timeout(sys_clock_tick_get());
  100. return;
  101. }
  102. if (channels[channel_id].callback) {
  103. channels[channel_id].callback(channel_id,
  104. channels[channel_id].user_data);
  105. } else {
  106. sys_reboot(SYS_REBOOT_COLD);
  107. }
  108. }
  109. int task_wdt_init(const struct device *hw_wdt)
  110. {
  111. if (hw_wdt) {
  112. #ifdef CONFIG_TASK_WDT_HW_FALLBACK
  113. struct wdt_timeout_cfg wdt_config;
  114. wdt_config.flags = WDT_FLAG_RESET_SOC;
  115. wdt_config.window.min = 0U;
  116. wdt_config.window.max = CONFIG_TASK_WDT_MIN_TIMEOUT +
  117. CONFIG_TASK_WDT_HW_FALLBACK_DELAY;
  118. wdt_config.callback = NULL;
  119. hw_wdt_dev = hw_wdt;
  120. hw_wdt_channel = wdt_install_timeout(hw_wdt_dev, &wdt_config);
  121. if (hw_wdt_channel < 0) {
  122. LOG_ERR("hw_wdt install timeout failed: %d", hw_wdt_channel);
  123. return hw_wdt_channel;
  124. }
  125. #endif
  126. }
  127. k_timer_init(&timer, task_wdt_trigger, NULL);
  128. schedule_next_timeout(sys_clock_tick_get());
  129. return 0;
  130. }
  131. int task_wdt_add(uint32_t reload_period, task_wdt_callback_t callback,
  132. void *user_data)
  133. {
  134. k_spinlock_key_t key;
  135. if (reload_period == 0) {
  136. return -EINVAL;
  137. }
  138. /*
  139. * k_spin_lock instead of k_sched_lock required here to avoid being interrupted by a
  140. * triggering other task watchdog channel (executed in ISR context).
  141. */
  142. key = k_spin_lock(&channels_lock);
  143. /* look for unused channel (reload_period set to 0) */
  144. for (int id = 0; id < ARRAY_SIZE(channels); id++) {
  145. if (channels[id].reload_period == 0) {
  146. channels[id].reload_period = reload_period;
  147. channels[id].user_data = user_data;
  148. channels[id].timeout_abs_ticks = K_TICKS_FOREVER;
  149. channels[id].callback = callback;
  150. #ifdef CONFIG_TASK_WDT_HW_FALLBACK
  151. if (!hw_wdt_started && hw_wdt_dev) {
  152. /* also start fallback hw wdt */
  153. wdt_setup(hw_wdt_dev,
  154. WDT_OPT_PAUSE_HALTED_BY_DBG);
  155. hw_wdt_started = true;
  156. }
  157. #endif
  158. /* must be called after hw wdt has been started */
  159. task_wdt_feed(id);
  160. k_spin_unlock(&channels_lock, key);
  161. return id;
  162. }
  163. }
  164. k_spin_unlock(&channels_lock, key);
  165. return -ENOMEM;
  166. }
  167. int task_wdt_delete(int channel_id)
  168. {
  169. k_spinlock_key_t key;
  170. if (channel_id < 0 || channel_id >= ARRAY_SIZE(channels)) {
  171. return -EINVAL;
  172. }
  173. key = k_spin_lock(&channels_lock);
  174. channels[channel_id].reload_period = 0;
  175. k_spin_unlock(&channels_lock, key);
  176. return 0;
  177. }
  178. int task_wdt_feed(int channel_id)
  179. {
  180. int64_t current_ticks;
  181. if (channel_id < 0 || channel_id >= ARRAY_SIZE(channels)) {
  182. return -EINVAL;
  183. }
  184. /*
  185. * We need a critical section instead of a mutex while updating the
  186. * channels array in order to prevent priority inversion. Otherwise,
  187. * a low priority thread could be preempted before releasing the mutex
  188. * and block a high priority thread that wants to feed its task wdt.
  189. */
  190. k_sched_lock();
  191. current_ticks = sys_clock_tick_get();
  192. /* feed the specified channel */
  193. channels[channel_id].timeout_abs_ticks = current_ticks +
  194. k_ms_to_ticks_ceil64(channels[channel_id].reload_period);
  195. schedule_next_timeout(current_ticks);
  196. k_sched_unlock();
  197. return 0;
  198. }