123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- /*
- * Copyright (c) 2020 Libre Solar Technologies GmbH
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include "task_wdt/task_wdt.h"
- #include <drivers/watchdog.h>
- #include <sys/reboot.h>
- #include <device.h>
- #include <errno.h>
- #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
- #include <logging/log.h>
- LOG_MODULE_REGISTER(task_wdt);
- /*
- * This dummy channel is used to continue feeding the hardware watchdog if the
- * task watchdog timeouts are too long for regular updates
- */
- #define TASK_WDT_BACKGROUND_CHANNEL UINTPTR_MAX
- /*
- * Task watchdog channel data
- */
- struct task_wdt_channel {
- /* period in milliseconds used to reset the timeout, set to 0 to
- * indicate that the channel is available
- */
- uint32_t reload_period;
- /* abs. ticks when this channel expires (updated by task_wdt_feed) */
- int64_t timeout_abs_ticks;
- /* user data passed to the callback function */
- void *user_data;
- /* function to be called when watchdog timer expired */
- task_wdt_callback_t callback;
- };
- /* array of all task watchdog channels */
- static struct task_wdt_channel channels[CONFIG_TASK_WDT_CHANNELS];
- static struct k_spinlock channels_lock;
- /* timer used for watchdog handling */
- static struct k_timer timer;
- #ifdef CONFIG_TASK_WDT_HW_FALLBACK
- /* pointer to the hardware watchdog used as a fallback */
- static const struct device *hw_wdt_dev;
- static int hw_wdt_channel;
- static bool hw_wdt_started;
- #endif
- static void schedule_next_timeout(int64_t current_ticks)
- {
- uintptr_t next_channel_id; /* channel which will time out next */
- int64_t next_timeout; /* timeout in absolute ticks of this channel */
- #ifdef CONFIG_TASK_WDT_HW_FALLBACK
- next_channel_id = TASK_WDT_BACKGROUND_CHANNEL;
- next_timeout = current_ticks +
- k_ms_to_ticks_ceil64(CONFIG_TASK_WDT_MIN_TIMEOUT);
- #else
- next_channel_id = 0;
- next_timeout = INT64_MAX;
- #endif
- /* find minimum timeout of all channels */
- for (int id = 0; id < ARRAY_SIZE(channels); id++) {
- if (channels[id].reload_period != 0 &&
- channels[id].timeout_abs_ticks < next_timeout) {
- next_channel_id = id;
- next_timeout = channels[id].timeout_abs_ticks;
- }
- }
- /* update task wdt kernel timer */
- k_timer_user_data_set(&timer, (void *)next_channel_id);
- k_timer_start(&timer, K_TIMEOUT_ABS_TICKS(next_timeout), K_FOREVER);
- #ifdef CONFIG_TASK_WDT_HW_FALLBACK
- if (hw_wdt_started) {
- wdt_feed(hw_wdt_dev, hw_wdt_channel);
- }
- #endif
- }
- /**
- * @brief Task watchdog timer callback.
- *
- * If the device operates as intended, this function will never be called,
- * as the timer is continuously restarted with the next due timeout in the
- * task_wdt_feed() function.
- *
- * If all task watchdogs have longer timeouts than the hardware watchdog,
- * this function is called regularly (via the background channel). This
- * should be avoided by setting CONFIG_TASK_WDT_MIN_TIMEOUT to the minimum
- * task watchdog timeout used in the application.
- *
- * @param timer_id Pointer to the timer which called the function
- */
- static void task_wdt_trigger(struct k_timer *timer_id)
- {
- uintptr_t channel_id = (uintptr_t)k_timer_user_data_get(timer_id);
- bool bg_channel = IS_ENABLED(CONFIG_TASK_WDT_HW_FALLBACK) &&
- (channel_id == TASK_WDT_BACKGROUND_CHANNEL);
- /* If the timeout expired for the background channel (so the hardware
- * watchdog needs to be fed) or for a channel that has been deleted,
- * only schedule a new timeout (the hardware watchdog, if used, will be
- * fed right after that new timeout is scheduled).
- */
- if (bg_channel || channels[channel_id].reload_period == 0) {
- schedule_next_timeout(sys_clock_tick_get());
- return;
- }
- if (channels[channel_id].callback) {
- channels[channel_id].callback(channel_id,
- channels[channel_id].user_data);
- } else {
- sys_reboot(SYS_REBOOT_COLD);
- }
- }
- int task_wdt_init(const struct device *hw_wdt)
- {
- if (hw_wdt) {
- #ifdef CONFIG_TASK_WDT_HW_FALLBACK
- struct wdt_timeout_cfg wdt_config;
- wdt_config.flags = WDT_FLAG_RESET_SOC;
- wdt_config.window.min = 0U;
- wdt_config.window.max = CONFIG_TASK_WDT_MIN_TIMEOUT +
- CONFIG_TASK_WDT_HW_FALLBACK_DELAY;
- wdt_config.callback = NULL;
- hw_wdt_dev = hw_wdt;
- hw_wdt_channel = wdt_install_timeout(hw_wdt_dev, &wdt_config);
- if (hw_wdt_channel < 0) {
- LOG_ERR("hw_wdt install timeout failed: %d", hw_wdt_channel);
- return hw_wdt_channel;
- }
- #endif
- }
- k_timer_init(&timer, task_wdt_trigger, NULL);
- schedule_next_timeout(sys_clock_tick_get());
- return 0;
- }
- int task_wdt_add(uint32_t reload_period, task_wdt_callback_t callback,
- void *user_data)
- {
- k_spinlock_key_t key;
- if (reload_period == 0) {
- return -EINVAL;
- }
- /*
- * k_spin_lock instead of k_sched_lock required here to avoid being interrupted by a
- * triggering other task watchdog channel (executed in ISR context).
- */
- key = k_spin_lock(&channels_lock);
- /* look for unused channel (reload_period set to 0) */
- for (int id = 0; id < ARRAY_SIZE(channels); id++) {
- if (channels[id].reload_period == 0) {
- channels[id].reload_period = reload_period;
- channels[id].user_data = user_data;
- channels[id].timeout_abs_ticks = K_TICKS_FOREVER;
- channels[id].callback = callback;
- #ifdef CONFIG_TASK_WDT_HW_FALLBACK
- if (!hw_wdt_started && hw_wdt_dev) {
- /* also start fallback hw wdt */
- wdt_setup(hw_wdt_dev,
- WDT_OPT_PAUSE_HALTED_BY_DBG);
- hw_wdt_started = true;
- }
- #endif
- /* must be called after hw wdt has been started */
- task_wdt_feed(id);
- k_spin_unlock(&channels_lock, key);
- return id;
- }
- }
- k_spin_unlock(&channels_lock, key);
- return -ENOMEM;
- }
- int task_wdt_delete(int channel_id)
- {
- k_spinlock_key_t key;
- if (channel_id < 0 || channel_id >= ARRAY_SIZE(channels)) {
- return -EINVAL;
- }
- key = k_spin_lock(&channels_lock);
- channels[channel_id].reload_period = 0;
- k_spin_unlock(&channels_lock, key);
- return 0;
- }
- int task_wdt_feed(int channel_id)
- {
- int64_t current_ticks;
- if (channel_id < 0 || channel_id >= ARRAY_SIZE(channels)) {
- return -EINVAL;
- }
- /*
- * We need a critical section instead of a mutex while updating the
- * channels array in order to prevent priority inversion. Otherwise,
- * a low priority thread could be preempted before releasing the mutex
- * and block a high priority thread that wants to feed its task wdt.
- */
- k_sched_lock();
- current_ticks = sys_clock_tick_get();
- /* feed the specified channel */
- channels[channel_id].timeout_abs_ticks = current_ticks +
- k_ms_to_ticks_ceil64(channels[channel_id].reload_period);
- schedule_next_timeout(current_ticks);
- k_sched_unlock();
- return 0;
- }
|