diff --git a/CODEOWNERS b/CODEOWNERS index ca2a6d1c31a..d3350b4ae19 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 8fc5a03db6f..ea28a15b5dc 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -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) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 640f73ab42a..dd1a8225cd4 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -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" diff --git a/drivers/watchdog/wdt_counter.c b/drivers/watchdog/wdt_counter.c new file mode 100644 index 00000000000..52bfb12f1f0 --- /dev/null +++ b/drivers/watchdog/wdt_counter.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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); diff --git a/dts/bindings/watchdog/zephyr,counter-watchdog.yaml b/dts/bindings/watchdog/zephyr,counter-watchdog.yaml new file mode 100644 index 00000000000..1a847435b28 --- /dev/null +++ b/dts/bindings/watchdog/zephyr,counter-watchdog.yaml @@ -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.