summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2019-11-07 14:26:42 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2019-11-07 14:26:42 +0100
commit047d617c6449da6b17a3b844802bb5e127fef7c3 (patch)
tree6d28caf8a4f7c155b3406d71a0b5f7c38f844eac
parentcd8a909bb3e7af6d905e4b554b10a3bf9718f5c5 (diff)
parentcbfdbf38c190bc9197f85c9633f103cdc15de571 (diff)
downloadbarebox-047d617c6449da6b17a3b844802bb5e127fef7c3.tar.gz
Merge branch 'for-next/watchdog'
-rw-r--r--Documentation/boards/efi.rst2
-rw-r--r--Documentation/user/user-manual.rst1
-rw-r--r--Documentation/user/watchdog.rst91
-rw-r--r--commands/wd.c34
-rw-r--r--common/boot.c3
-rw-r--r--common/efi/efi-image.c1
-rw-r--r--drivers/mfd/da9063.c14
-rw-r--r--drivers/watchdog/efi_wdt.c1
-rw-r--r--drivers/watchdog/stm32_iwdg.c1
-rw-r--r--drivers/watchdog/stpmic1_wdt.c1
-rw-r--r--drivers/watchdog/wd_core.c73
-rw-r--r--include/watchdog.h20
12 files changed, 202 insertions, 40 deletions
diff --git a/Documentation/boards/efi.rst b/Documentation/boards/efi.rst
index 2178c9a..f04b1d3 100644
--- a/Documentation/boards/efi.rst
+++ b/Documentation/boards/efi.rst
@@ -350,7 +350,7 @@ https://uefi.org/sites/default/files/resources/UEFI_Spec_2_1_D.pdf
Current linux kernel (v5.0) will execute ExitBootServices() during the early
boot stage and thus will automatically disable the (U)EFI watchdog. Since it is
-a proper behavior according to the (U)EFI specification, it is impossible to
+the proper behavior according to the (U)EFI specification, it is impossible to
protect full boot chain by using this watchdog only. It is recommended to use
an alternative hardware watchdog, preferably started before the bootloader. If (U)EFI
firmware lacks this feature, the bootloader should be able to start an alternative
diff --git a/Documentation/user/user-manual.rst b/Documentation/user/user-manual.rst
index f04981c..41fdb88 100644
--- a/Documentation/user/user-manual.rst
+++ b/Documentation/user/user-manual.rst
@@ -34,6 +34,7 @@ Contents:
state
random
debugging
+ watchdog
* :ref:`search`
* :ref:`genindex`
diff --git a/Documentation/user/watchdog.rst b/Documentation/user/watchdog.rst
new file mode 100644
index 0000000..02bd576
--- /dev/null
+++ b/Documentation/user/watchdog.rst
@@ -0,0 +1,91 @@
+Watchdog Support
+================
+
+Barebox Watchdog Functionality
+------------------------------
+
+In some cases we are not able to influence the hardware design anymore or while
+developing one needs to be able to feed the watchdog to disable it from within
+the bootloader. For these scenarios barebox provides the watchdog framework
+with the following functionality and at least ``CONFIG_WATCHDOG`` should be
+enabled.
+
+Polling
+~~~~~~~
+
+Watchdog polling/feeding allows to feed the watchdog and keep it running on one
+side and to not reset the system on the other side. It is needed on hardware
+with short-time watchdogs. For example the Atheros ar9331 watchdog has a
+maximal timeout of 7 seconds, so it may reset even on netboot.
+Or it can be used on systems where the watchdog is already running and can't be
+disabled, an example for that is the watchdog of the i.MX2 series.
+This functionally can be seen as a threat, since in error cases barebox will
+continue to feed the watchdog even if that is not desired. So, depending on
+your needs ``CONFIG_WATCHDOG_POLLER`` can be enabled or disabled at compile
+time. Even if barebox was built with watchdog polling support, it is not
+enabled by default. To start polling from command line run:
+
+.. code-block:: console
+
+ wdog0.autoping=1
+
+**NOTE** Using this feature might have the effect that the watchdog is
+effectively disabled. In case barebox is stuck in a loop that includes feeding
+the watchdog, then the watchdog will never trigger. Only use this feature
+during development or when a bad watchdog design (Short watchdog timeout
+enabled as boot default) doesn't give you another choice.
+
+The poller interval is not configurable, but fixed at 500ms and the watchdog
+timeout is configured by default to the maximum of the supported values by
+hardware. To change the timeout used by the poller, run:
+
+.. code-block:: console
+
+ wdog0.timeout_cur=7
+
+To read the current watchdog's configuration, run:
+
+.. code-block:: console
+
+ devinfo wdog0
+
+The output may look as follows where ``timeout_cur`` and ``timeout_max`` are
+measured in seconds:
+
+.. code-block:: console
+
+ barebox@DPTechnics DPT-Module:/ devinfo wdog0
+ Parameters:
+ autoping: 1 (type: bool)
+ timeout_cur: 7 (type: uint32)
+ timeout_max: 10 (type: uint32)
+
+Use barebox' environment to persist these changes between reboots:
+
+.. code-block:: console
+
+ nv dev.wdog0.autoping=1
+ nv dev.wdog0.timeout_cur=7
+
+Boot Watchdog Timeout
+~~~~~~~~~~~~~~~~~~~~~
+
+With this functionality barebox may start a watchdog or update the timeout of
+an already-running one, just before kicking the boot image. It can be
+configured temporarily via
+
+.. code-block:: console
+
+ global boot.watchdog_timeout=10
+
+or persistently by
+
+.. code-block:: console
+
+ nv boot.watchdog_timeout=10
+
+where the used value again is measured in seconds.
+
+On a system with multiple watchdogs, the watchdog with the highest positive
+priority is the one affected by the ``boot.watchdog_timeout`` parameter. If
+multiple watchdogs share the same priority, only one will be started.
diff --git a/commands/wd.c b/commands/wd.c
index 26823f8..8029bab 100644
--- a/commands/wd.c
+++ b/commands/wd.c
@@ -16,6 +16,8 @@
#include <command.h>
#include <errno.h>
#include <linux/ctype.h>
+#include <getopt.h>
+#include <complete.h>
#include <watchdog.h>
/* default timeout in [sec] */
@@ -23,18 +25,30 @@ static unsigned timeout = CONFIG_CMD_WD_DEFAULT_TIMOUT;
static int do_wd(int argc, char *argv[])
{
+ struct watchdog *wd = watchdog_get_default();
+ int opt;
int rc;
- if (argc > 1) {
- if (isdigit(*argv[1])) {
- timeout = simple_strtoul(argv[1], NULL, 0);
+ while ((opt = getopt(argc, argv, "d:")) > 0) {
+ switch (opt) {
+ case 'd':
+ wd = watchdog_get_by_name(optarg);
+ break;
+ default:
+ return COMMAND_ERROR_USAGE;
+ }
+ }
+
+ if (optind < argc) {
+ if (isdigit(*argv[optind])) {
+ timeout = simple_strtoul(argv[optind], NULL, 0);
} else {
printf("numerical parameter expected\n");
- return 1;
+ return COMMAND_ERROR_USAGE;
}
}
- rc = watchdog_set_timeout(timeout);
+ rc = watchdog_set_timeout(wd, timeout);
if (rc < 0) {
switch (rc) {
case -EINVAL:
@@ -43,12 +57,15 @@ static int do_wd(int argc, char *argv[])
case -ENOSYS:
printf("Watchdog cannot be disabled\n");
break;
+ case -ENODEV:
+ printf("Watchdog device doesn't exist.\n");
+ break;
default:
printf("Watchdog fails: '%s'\n", strerror(-rc));
break;
}
- return 1;
+ return COMMAND_ERROR;
}
return 0;
@@ -58,12 +75,15 @@ BAREBOX_CMD_HELP_START(wd)
BAREBOX_CMD_HELP_TEXT("Enable the watchdog to bark in TIME seconds.")
BAREBOX_CMD_HELP_TEXT("When TIME is 0, the watchdog gets disabled,")
BAREBOX_CMD_HELP_TEXT("Without a parameter the watchdog will be re-triggered.")
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT("-d DEVICE\t", "watchdog name (default is highest priority watchdog)")
BAREBOX_CMD_HELP_END
BAREBOX_CMD_START(wd)
.cmd = do_wd,
BAREBOX_CMD_DESC("enable/disable/trigger the watchdog")
- BAREBOX_CMD_OPTS("[TIME]")
+ BAREBOX_CMD_OPTS("[-d DEVICE] [TIME]")
BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
BAREBOX_CMD_HELP(cmd_wd_help)
+ BAREBOX_CMD_COMPLETE(device_complete)
BAREBOX_CMD_END
diff --git a/common/boot.c b/common/boot.c
index 14d4fe9..dcbe5cc 100644
--- a/common/boot.c
+++ b/common/boot.c
@@ -146,7 +146,8 @@ int boot_entry(struct bootentry *be, int verbose, int dryrun)
printf("Booting entry '%s'\n", be->title);
if (IS_ENABLED(CONFIG_WATCHDOG) && boot_watchdog_timeout) {
- ret = watchdog_set_timeout(boot_watchdog_timeout);
+ ret = watchdog_set_timeout(watchdog_get_default(),
+ boot_watchdog_timeout);
if (ret)
pr_warn("Failed to enable watchdog: %s\n", strerror(-ret));
}
diff --git a/common/efi/efi-image.c b/common/efi/efi-image.c
index 939663a..9c66c9f 100644
--- a/common/efi/efi-image.c
+++ b/common/efi/efi-image.c
@@ -174,7 +174,6 @@ static inline void linux_efi_handover(efi_handle_t handle,
{
handover_fn handover;
- asm volatile ("cli");
handover = (handover_fn)((long)header->code32_start + 512 +
header->handover_offset);
handover(handle, efi_sys_table, header);
diff --git a/drivers/mfd/da9063.c b/drivers/mfd/da9063.c
index b61e764..e1343ba 100644
--- a/drivers/mfd/da9063.c
+++ b/drivers/mfd/da9063.c
@@ -14,6 +14,7 @@
*/
#include <common.h>
+#include <clock.h>
#include <driver.h>
#include <gpio.h>
#include <restart.h>
@@ -33,6 +34,7 @@ struct da9063 {
struct i2c_client *client1;
struct device_d *dev;
unsigned int timeout;
+ uint64_t last_ping;
};
/* forbidden/impossible value; timeout will be set to this value initially to
@@ -237,6 +239,14 @@ static int da9063_watchdog_ping(struct da9063 *priv)
int ret;
u8 val;
+ /*
+ * The watchdog has a cool down phase of 200ms and if we ping to fast
+ * the da9062/3 resets the system. Reject those requests has a maximum
+ * failure of 10% if the watchdog timeout is set to 2.048s.
+ */
+ if (!is_timeout(priv->last_ping, 200 * MSECOND))
+ return 0;
+
dev_dbg(priv->dev, "ping\n");
/* reset watchdog timer; register is self clearing */
@@ -245,6 +255,8 @@ static int da9063_watchdog_ping(struct da9063 *priv)
if (ret < 0)
return ret;
+ priv->last_ping = get_time_ns();
+
return 0;
}
@@ -270,7 +282,7 @@ static int da9063_watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
while (timeout > (2048 << scale) && scale <= 6)
scale++;
dev_dbg(dev, "calculated TWDSCALE=%u (req=%ims calc=%ims)\n",
- scale, timeout, 2048 << scale);
+ scale + 1, timeout, 2048 << scale);
scale++; /* scale 0 disables the WD */
}
diff --git a/drivers/watchdog/efi_wdt.c b/drivers/watchdog/efi_wdt.c
index 8e3e51b..ea1ede1 100644
--- a/drivers/watchdog/efi_wdt.c
+++ b/drivers/watchdog/efi_wdt.c
@@ -41,6 +41,7 @@ static int efi_wdt_probe(struct device_d *dev)
priv->wd.set_timeout = efi_wdt_set_timeout;
priv->wd.hwdev = dev;
priv->dev = dev;
+ priv->wd.priority = WATCHDOG_DEFAULT_PRIORITY - 50;
dev->priv = priv;
diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c
index 20536cb..4d252e5 100644
--- a/drivers/watchdog/stm32_iwdg.c
+++ b/drivers/watchdog/stm32_iwdg.c
@@ -256,7 +256,6 @@ static int stm32_iwdg_probe(struct device_d *dev)
wdd->set_timeout = stm32_iwdg_set_timeout;
wdd->timeout_max = (RLR_MAX + 1) * data->max_prescaler * 1000;
wdd->timeout_max /= wd->rate * 1000;
- wdd->timeout_cur = wdd->timeout_max;
ret = watchdog_register(wdd);
if (ret) {
diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c
index 5d9720c..9b7a586 100644
--- a/drivers/watchdog/stpmic1_wdt.c
+++ b/drivers/watchdog/stpmic1_wdt.c
@@ -178,7 +178,6 @@ static int stpmic1_wdt_probe(struct device_d *dev)
wdd->hwdev = dev;
wdd->set_timeout = stpmic1_wdt_set_timeout;
wdd->timeout_max = PMIC_WDT_MAX_TIMEOUT;
- wdd->timeout_cur = PMIC_WDT_DEFAULT_TIMEOUT;
/* have the watchdog reset, not power-off the system */
regmap_write_bits(wdt->regmap, MAIN_CR, RREQ_EN, RREQ_EN);
diff --git a/drivers/watchdog/wd_core.c b/drivers/watchdog/wd_core.c
index e6e5dde..8b13950 100644
--- a/drivers/watchdog/wd_core.c
+++ b/drivers/watchdog/wd_core.c
@@ -31,8 +31,15 @@ static const char *watchdog_name(struct watchdog *wd)
return "unknown";
}
-static int _watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
+/*
+ * start, stop or retrigger the watchdog
+ * timeout in [seconds]. timeout of '0' will disable the watchdog (if possible)
+ */
+int watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
{
+ if (!wd)
+ return -ENODEV;
+
if (timeout > wd->timeout_max)
return -EINVAL;
@@ -40,12 +47,23 @@ static int _watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
return wd->set_timeout(wd, timeout);
}
+EXPORT_SYMBOL(watchdog_set_timeout);
+
+static int watchdog_set_priority(struct param_d *param, void *priv)
+{
+ struct watchdog *wd = priv;
+
+ if (wd->priority == 0)
+ return watchdog_set_timeout(wd, 0);
+
+ return 0;
+}
static int watchdog_set_cur(struct param_d *param, void *priv)
{
struct watchdog *wd = priv;
- if (wd->timeout_cur > wd->timeout_max)
+ if (wd->poller_timeout_cur > wd->timeout_max)
return -EINVAL;
return 0;
@@ -55,7 +73,7 @@ static void watchdog_poller_cb(void *priv);
static void watchdog_poller_start(struct watchdog *wd)
{
- _watchdog_set_timeout(wd, wd->timeout_cur);
+ watchdog_set_timeout(wd, wd->poller_timeout_cur);
poller_call_async(&wd->poller, 500 * MSECOND,
watchdog_poller_cb, wd);
@@ -130,24 +148,31 @@ int watchdog_register(struct watchdog *wd)
if (!wd->priority)
wd->priority = WATCHDOG_DEFAULT_PRIORITY;
+ p = dev_add_param_uint32(&wd->dev, "priority",
+ watchdog_set_priority, NULL,
+ &wd->priority, "%u", wd);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
/* set some default sane value */
if (!wd->timeout_max)
wd->timeout_max = 60 * 60 * 24;
- if (!wd->timeout_cur || wd->timeout_cur > wd->timeout_max)
- wd->timeout_cur = wd->timeout_max;
-
p = dev_add_param_uint32_ro(&wd->dev, "timeout_max",
&wd->timeout_max, "%u");
if (IS_ERR(p))
return PTR_ERR(p);
- p = dev_add_param_uint32(&wd->dev, "timeout_cur", watchdog_set_cur, NULL,
- &wd->timeout_cur, "%u", wd);
- if (IS_ERR(p))
- return PTR_ERR(p);
-
if (IS_ENABLED(CONFIG_WATCHDOG_POLLER)) {
+ if (!wd->poller_timeout_cur ||
+ wd->poller_timeout_cur > wd->timeout_max)
+ wd->poller_timeout_cur = wd->timeout_max;
+
+ p = dev_add_param_uint32(&wd->dev, "timeout_cur", watchdog_set_cur,
+ NULL, &wd->poller_timeout_cur, "%u", wd);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
ret = watchdog_register_poller(wd);
if (ret)
return ret;
@@ -176,7 +201,7 @@ int watchdog_deregister(struct watchdog *wd)
}
EXPORT_SYMBOL(watchdog_deregister);
-static struct watchdog *watchdog_get_default(void)
+struct watchdog *watchdog_get_default(void)
{
struct watchdog *tmp, *wd = NULL;
int priority = 0;
@@ -190,23 +215,23 @@ static struct watchdog *watchdog_get_default(void)
return wd;
}
+EXPORT_SYMBOL(watchdog_get_default);
-/*
- * start, stop or retrigger the watchdog
- * timeout in [seconds]. timeout of '0' will disable the watchdog (if possible)
- */
-int watchdog_set_timeout(unsigned timeout)
+struct watchdog *watchdog_get_by_name(const char *name)
{
- struct watchdog *wd;
+ struct watchdog *tmp;
+ struct device_d *dev = get_device_by_name(name);
+ if (!dev)
+ return NULL;
- wd = watchdog_get_default();
-
- if (!wd)
- return -ENODEV;
+ list_for_each_entry(tmp, &watchdog_list, list) {
+ if (dev == tmp->hwdev || dev == &tmp->dev)
+ return tmp;
+ }
- return _watchdog_set_timeout(wd, timeout);
+ return NULL;
}
-EXPORT_SYMBOL(watchdog_set_timeout);
+EXPORT_SYMBOL(watchdog_get_by_name);
/**
* of_get_watchdog_priority() - get the desired watchdog priority from device tree
diff --git a/include/watchdog.h b/include/watchdog.h
index 0db4263..184a218 100644
--- a/include/watchdog.h
+++ b/include/watchdog.h
@@ -14,6 +14,7 @@
# define INCLUDE_WATCHDOG_H
#include <poller.h>
+#include <driver.h>
struct watchdog {
int (*set_timeout)(struct watchdog *, unsigned);
@@ -22,7 +23,7 @@ struct watchdog {
struct device_d dev;
unsigned int priority;
unsigned int timeout_max;
- unsigned int timeout_cur;
+ unsigned int poller_timeout_cur;
unsigned int poller_enable;
struct poller_async poller;
struct list_head list;
@@ -31,7 +32,9 @@ struct watchdog {
#ifdef CONFIG_WATCHDOG
int watchdog_register(struct watchdog *);
int watchdog_deregister(struct watchdog *);
-int watchdog_set_timeout(unsigned);
+struct watchdog *watchdog_get_default(void);
+struct watchdog *watchdog_get_by_name(const char *name);
+int watchdog_set_timeout(struct watchdog*, unsigned);
unsigned int of_get_watchdog_priority(struct device_node *node);
#else
static inline int watchdog_register(struct watchdog *w)
@@ -44,11 +47,22 @@ static inline int watchdog_deregister(struct watchdog *w)
return 0;
}
-static inline int watchdog_set_timeout(unsigned t)
+static inline struct watchdog *watchdog_get_default(void)
+{
+ return NULL;
+}
+
+static inline struct watchdog *watchdog_get_by_name(const char *name)
+{
+ return NULL;
+}
+
+static inline int watchdog_set_timeout(struct watchdog*w, unsigned t)
{
return 0;
}
+
static inline unsigned int of_get_watchdog_priority(struct device_node *node)
{
return 0;