diff --git a/include/zephyr/task_wdt/task_wdt.h b/include/zephyr/task_wdt/task_wdt.h index 91cc9798505..dfeede4963a 100644 --- a/include/zephyr/task_wdt/task_wdt.h +++ b/include/zephyr/task_wdt/task_wdt.h @@ -102,6 +102,28 @@ int task_wdt_delete(int channel_id); */ int task_wdt_feed(int channel_id); +/** + * @brief Pause all channels before changing system power mode. + * + * Stop the internal timer and feed the hardware watchdog a last time + * (if enabled). It is expected that the system enters a low-power + * mode quite fast after this call. + * + * @note To pause the hardware watchdog (if this is supported), + * enable @kconfig{CONFIG_TASK_WDT_HW_FALLBACK_PAUSE_IN_SLEEP} in your + * configuration. + */ +void task_wdt_suspend(void); + +/** + * @brief Resume all channels execution. + * + * Resume the internal timer and feed all channels. Also feed the hardware + * watchdog (if enabled) to let enough time to the application to resume + * feeding the channels by itself. + */ +void task_wdt_resume(void); + #ifdef __cplusplus } #endif diff --git a/subsys/task_wdt/task_wdt.c b/subsys/task_wdt/task_wdt.c index 83e80ea91c2..243012198fe 100644 --- a/subsys/task_wdt/task_wdt.c +++ b/subsys/task_wdt/task_wdt.c @@ -44,6 +44,9 @@ static struct k_spinlock channels_lock; /* timer used for watchdog handling */ static struct k_timer timer; +/* Tell whether the Task Watchdog has been fully initialized. */ +static bool task_wdt_initialized; + #ifdef CONFIG_TASK_WDT_HW_FALLBACK /* pointer to the hardware watchdog used as a fallback */ static const struct device *hw_wdt_dev; @@ -149,6 +152,8 @@ int task_wdt_init(const struct device *hw_wdt) k_timer_init(&timer, task_wdt_trigger, NULL); schedule_next_timeout(sys_clock_tick_get()); + task_wdt_initialized = true; + return 0; } @@ -246,3 +251,68 @@ int task_wdt_feed(int channel_id) return 0; } + +void task_wdt_suspend(void) +{ + k_spinlock_key_t key; + + /* + * Allow the function to be called from a custom PM policy callback, even when + * the Task Watchdog was not initialized yet. + */ + if (!task_wdt_initialized) { + return; + } + + /* + * Prevent all task watchdog channels from triggering. + * Protect the timer access with the spinlock to avoid the timer being started + * concurrently by a call to schedule_next_timeout(). + */ + key = k_spin_lock(&channels_lock); + k_timer_stop(&timer); + k_spin_unlock(&channels_lock, key); + +#ifdef CONFIG_TASK_WDT_HW_FALLBACK + /* + * Give a whole hardware watchdog timer period of time to the application to put + * the system in a suspend mode that will pause the hardware watchdog. + */ + if (hw_wdt_started) { + wdt_feed(hw_wdt_dev, hw_wdt_channel); + } +#endif +} + +void task_wdt_resume(void) +{ + k_spinlock_key_t key; + int64_t current_ticks; + + /* + * Allow the function to be called from a custom PM policy callback, even when + * the Task Watchdog was not initialized yet. + */ + if (!task_wdt_initialized) { + return; + } + + key = k_spin_lock(&channels_lock); + + /* + * Feed all enabled channels, so the application threads have time to resume + * feeding the channels by themselves. + */ + current_ticks = sys_clock_tick_get(); + for (size_t id = 0; id < ARRAY_SIZE(channels); id++) { + if (channels[id].reload_period != 0) { + channels[id].timeout_abs_ticks = current_ticks + + k_ms_to_ticks_ceil64(channels[id].reload_period); + } + } + + /* Restart the Task Watchdog timer */ + schedule_next_timeout(current_ticks); + + k_spin_unlock(&channels_lock, key); +}