task_wdt: fix race condition for task_wdt_add function

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 <martin@libre.solar>
This commit is contained in:
Martin Jäger 2023-10-06 11:35:40 +02:00 committed by Johan Hedberg
parent 474aa963ff
commit 33bd2fed08

View File

@ -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;
}