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:
parent
0ddc07d511
commit
9174cd8dbc
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
177
drivers/watchdog/wdt_counter.c
Normal file
177
drivers/watchdog/wdt_counter.c
Normal 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);
|
16
dts/bindings/watchdog/zephyr,counter-watchdog.yaml
Normal file
16
dts/bindings/watchdog/zephyr,counter-watchdog.yaml
Normal 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.
|
Loading…
Reference in a new issue