From 33bd2fed0887703183171ddcc2af0549da2f313d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20J=C3=A4ger?= Date: Fri, 6 Oct 2023 11:35:40 +0200 Subject: [PATCH] task_wdt: fix race condition for task_wdt_add function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The task_wdt_add function changes the reload_period of the channel to a non-null value, which indicates that the channel is used. If the function is interrupted by a task_wdt_trigger running in ISR context before adding of the new channel has finished, the next timeout will be scheduled based on inconsistent channel data. Using a spinlock avoids such data races. Fixes #61004 Signed-off-by: Martin Jäger --- subsys/task_wdt/task_wdt.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/subsys/task_wdt/task_wdt.c b/subsys/task_wdt/task_wdt.c index 035a26017be..b7171a14e23 100644 --- a/subsys/task_wdt/task_wdt.c +++ b/subsys/task_wdt/task_wdt.c @@ -39,6 +39,7 @@ struct task_wdt_channel { /* 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; @@ -153,10 +154,18 @@ int task_wdt_init(const struct device *hw_wdt) 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) { @@ -176,21 +185,31 @@ int task_wdt_add(uint32_t reload_period, task_wdt_callback_t callback, /* 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; }