drivers: watchdog: Add software watchdog based on counter

Added watchdog implementation which is using counter device
to implement watchdog driver API. Watchdog timeout is called from
counter interrupt context. Some counter implementations support
using ZLI interrupt level which can be use here as well. Watchdog
like this can be used along hardware watchdog to cover for its
limitations, i.e. Nordic watchdog resets unconditionally after
62uS after triggering watchdog interrupt. It is not enough time
to dump logging data.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
Krzysztof Chruscinski 2021-10-22 13:12:43 +02:00 committed by Carles Cufí
parent 0ddc07d511
commit 9174cd8dbc
5 changed files with 221 additions and 0 deletions

View file

@ -387,6 +387,7 @@
/drivers/watchdog/*cc32xx* @pavlohamov
/drivers/watchdog/wdt_ite_it8xxx2.c @RuibinChang
/drivers/watchdog/Kconfig.it8xxx2 @RuibinChang
/drivers/watchdog/wdt_counter.c @nordic-krch
/drivers/wifi/ @rlubos @tbursztyka @pfalcon
/drivers/wifi/esp_at/ @mniestroj
/drivers/wifi/eswifi/ @loicpoulain @nandojve

View file

@ -21,5 +21,6 @@ zephyr_library_sources_ifdef(CONFIG_WDT_SAM wdt_sam.c)
zephyr_library_sources_ifdef(CONFIG_WDT_SAM0 wdt_sam0.c)
zephyr_library_sources_ifdef(CONFIG_WDT_SIFIVE wdt_sifive.c)
zephyr_library_sources_ifdef(CONFIG_WDT_XEC wdt_mchp_xec.c)
zephyr_library_sources_ifdef(CONFIG_WDT_COUNTER wdt_counter.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE wdt_handlers.c)

View file

@ -30,6 +30,32 @@ config WDT_MULTISTAGE
help
Enable multistage operation of watchdog timeouts.
config WDT_COUNTER
bool "Counter based watchdog"
def_bool $(dt_nodelabel_enabled,wdt_counter)
select COUNTER
help
Watchdog emulated with counter device. If counter device supports using
zero latency interrupts (ZLI) then expiration callback can be called from
that context. This watchdog can be used along hardware watchdog to
overcome hardware watchdog limitations, e.g. Nordic devices reset
unconditionally at fixed time after hitting watchdog interrupt, leaving
no time to print debug information. Watchdog has limitations since it
cannot interrupt same or higher priorities so it cannot fully replace
hardware based watchdog.
if WDT_COUNTER
config WDT_COUNTER_CH_COUNT
int "Maximum number of supported channel"
default 4
range 1 255
help
Note that actual channel count will be limited to number of channels
supported by the counter device which is used for watchdog.
endif # WDT_COUNTER
source "drivers/watchdog/Kconfig.stm32"
source "drivers/watchdog/Kconfig.cmsdk_apb"

View file

@ -0,0 +1,177 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/watchdog.h>
#include <drivers/counter.h>
#include <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)
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 alloc_cnt;
};
static struct wdt_counter_data wdt_data;
struct wdt_counter_config {
const struct device *counter;
};
static inline struct wdt_counter_data *get_dev_data(const struct device *dev)
{
return dev->data;
}
static inline const struct wdt_counter_config *get_dev_config(const struct device *dev)
{
return dev->config;
}
static int wdt_counter_setup(const struct device *dev, uint8_t options)
{
const struct device *counter = get_dev_config(dev)->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 device *counter = get_dev_config(dev)->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 = get_dev_data(wdt_dev);
counter_stop(dev);
if (data->callback[chan_id]) {
data->callback[chan_id](wdt_dev, chan_id);
}
LOG_PANIC();
sys_arch_reboot(0);
}
static int timeout_set(const struct device *dev, int chan_id, bool cancel)
{
struct wdt_counter_data *data = get_dev_data(dev);
const struct device *counter = get_dev_config(dev)->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 = get_dev_data(dev);
const struct wdt_counter_config *config = get_dev_config(dev);
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_FLAG_RESET_SOC) {
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;
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)
{
if (chan_id > counter_get_num_of_channels(get_dev_config(dev)->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)
{
struct wdt_counter_data *data = get_dev_data(dev);
uint8_t ch_cnt = counter_get_num_of_channels(get_dev_config(dev)->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);

View file

@ -0,0 +1,16 @@
# Copyright (c) 2021 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
description: |
Counter based watchdog
compatible: "zephyr,counter-watchdog"
include: base.yaml
properties:
counter:
type: phandle
required: true
description: |
Counter device used for watchdog.