zephyr/drivers/watchdog/wdt_counter.c
Patryk Duda 09277d44e3 drivers: watchdog: Make SOC reset as an option in counter watchdog
When using watchdog which doesn't support callbacks (e.g. STM32 IWDG)
it's very useful to have counter based watchdog with shorter timeout.
This way we can gather useful information before IWDG resets SOC.

In above case it's usually not desired to reset SOC when counter
watchdog is triggerd, because we can lose information about reset cause.
For example, STM32 can determine if the reset was caused by watchdog.

Another use case for counter watchdog without SOC reset is to detect
slow code paths.

Signed-off-by: Patryk Duda <pdk@semihalf.com>
2023-01-09 10:15:33 +01:00

180 lines
4.3 KiB
C

/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/watchdog.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/logging/log_ctrl.h>
#define WDT_CHANNEL_COUNT DT_PROP(DT_WDT_COUNTER, num_channels)
#define DT_WDT_COUNTER DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_counter_watchdog)
#define WDT_SUPPORTED_CFG_FLAGS (WDT_FLAG_RESET_NONE | WDT_FLAG_RESET_SOC)
extern void sys_arch_reboot(int type);
struct wdt_counter_data {
wdt_callback_t callback[CONFIG_WDT_COUNTER_CH_COUNT];
uint32_t timeout[CONFIG_WDT_COUNTER_CH_COUNT];
uint8_t flags[CONFIG_WDT_COUNTER_CH_COUNT];
uint8_t alloc_cnt;
};
static struct wdt_counter_data wdt_data;
struct wdt_counter_config {
const struct device *counter;
};
static int wdt_counter_setup(const struct device *dev, uint8_t options)
{
const struct wdt_counter_config *config = dev->config;
const struct device *counter = config->counter;
if ((options & WDT_OPT_PAUSE_IN_SLEEP) || (options & WDT_OPT_PAUSE_IN_SLEEP)) {
return -ENOTSUP;
}
return counter_start(counter);
}
static int wdt_counter_disable(const struct device *dev)
{
const struct wdt_counter_config *config = dev->config;
const struct device *counter = config->counter;
return counter_stop(counter);
}
static void counter_alarm_callback(const struct device *dev,
uint8_t chan_id, uint32_t ticks,
void *user_data)
{
const struct device *wdt_dev = user_data;
struct wdt_counter_data *data = wdt_dev->data;
counter_stop(dev);
if (data->callback[chan_id]) {
data->callback[chan_id](wdt_dev, chan_id);
}
if (data->flags[chan_id] & WDT_FLAG_RESET_SOC) {
LOG_PANIC();
sys_arch_reboot(0);
}
}
static int timeout_set(const struct device *dev, int chan_id, bool cancel)
{
const struct wdt_counter_config *config = dev->config;
struct wdt_counter_data *data = dev->data;
const struct device *counter = config->counter;
struct counter_alarm_cfg alarm_cfg = {
.callback = counter_alarm_callback,
.ticks = data->timeout[chan_id],
.user_data = (void *)dev,
.flags = 0
};
if (cancel) {
int err = counter_cancel_channel_alarm(counter, chan_id);
if (err < 0) {
return err;
}
}
return counter_set_channel_alarm(counter, chan_id, &alarm_cfg);
}
static int wdt_counter_install_timeout(const struct device *dev,
const struct wdt_timeout_cfg *cfg)
{
struct wdt_counter_data *data = dev->data;
const struct wdt_counter_config *config = dev->config;
const struct device *counter = config->counter;
int chan_id;
if (!device_is_ready(counter)) {
return -EIO;
}
uint32_t max_timeout = counter_get_top_value(counter) -
counter_get_guard_period(counter,
COUNTER_GUARD_PERIOD_LATE_TO_SET);
uint32_t timeout_ticks = counter_us_to_ticks(counter, cfg->window.max * 1000);
if (cfg->flags & ~WDT_SUPPORTED_CFG_FLAGS) {
return -ENOTSUP;
}
if (cfg->window.min != 0U) {
return -EINVAL;
}
if (timeout_ticks > max_timeout || timeout_ticks == 0) {
return -EINVAL;
}
if (data->alloc_cnt == 0) {
return -ENOMEM;
}
data->alloc_cnt--;
chan_id = data->alloc_cnt;
data->timeout[chan_id] = timeout_ticks;
data->callback[chan_id] = cfg->callback;
data->flags[chan_id] = cfg->flags;
int err = timeout_set(dev, chan_id, false);
if (err < 0) {
return err;
}
return chan_id;
}
static int wdt_counter_feed(const struct device *dev, int chan_id)
{
const struct wdt_counter_config *config = dev->config;
if (chan_id > counter_get_num_of_channels(config->counter)) {
return -EINVAL;
}
/* Move alarm further in time. */
return timeout_set(dev, chan_id, true);
}
static const struct wdt_driver_api wdt_counter_driver_api = {
.setup = wdt_counter_setup,
.disable = wdt_counter_disable,
.install_timeout = wdt_counter_install_timeout,
.feed = wdt_counter_feed,
};
static const struct wdt_counter_config wdt_counter_config = {
.counter = DEVICE_DT_GET(DT_PHANDLE(DT_WDT_COUNTER, counter)),
};
static int wdt_counter_init(const struct device *dev)
{
const struct wdt_counter_config *config = dev->config;
struct wdt_counter_data *data = dev->data;
uint8_t ch_cnt = counter_get_num_of_channels(config->counter);
data->alloc_cnt = MIN(ch_cnt, CONFIG_WDT_COUNTER_CH_COUNT);
return 0;
}
DEVICE_DT_DEFINE(DT_WDT_COUNTER, wdt_counter_init, NULL,
&wdt_data, &wdt_counter_config,
POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&wdt_counter_driver_api);