summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/wd_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/wd_core.c')
-rw-r--r--drivers/watchdog/wd_core.c187
1 files changed, 155 insertions, 32 deletions
diff --git a/drivers/watchdog/wd_core.c b/drivers/watchdog/wd_core.c
index 8b13950238..f39a8f4522 100644
--- a/drivers/watchdog/wd_core.c
+++ b/drivers/watchdog/wd_core.c
@@ -1,15 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* (c) 2012 Juergen Beisert <kernel@pengutronix.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#define pr_fmt(fmt) "watchdog: " fmt
@@ -18,6 +9,7 @@
#include <errno.h>
#include <linux/ctype.h>
#include <watchdog.h>
+#include <restart.h>
static LIST_HEAD(watchdog_list);
@@ -37,15 +29,29 @@ static const char *watchdog_name(struct watchdog *wd)
*/
int watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
{
+ int ret;
+
if (!wd)
return -ENODEV;
if (timeout > wd->timeout_max)
return -EINVAL;
+ if (!timeout && !watchdog_hw_running(wd))
+ return 0;
+
pr_debug("setting timeout on %s to %ds\n", watchdog_name(wd), timeout);
- return wd->set_timeout(wd, timeout);
+ ret = wd->set_timeout(wd, timeout);
+ if (ret)
+ return ret;
+
+ wd->last_ping = get_time_ns();
+ wd->timeout_cur = timeout;
+
+ wd->running = timeout ? WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
+
+ return 0;
}
EXPORT_SYMBOL(watchdog_set_timeout);
@@ -108,7 +114,7 @@ static int watchdog_register_poller(struct watchdog *wd)
struct param_d *p;
int ret;
- ret = poller_async_register(&wd->poller);
+ ret = poller_async_register(&wd->poller, dev_name(&wd->dev));
if (ret)
return ret;
@@ -127,6 +133,62 @@ static int watchdog_register_dev(struct watchdog *wd, const char *name, int id)
return register_device(&wd->dev);
}
+/**
+ * dev_get_watchdog_priority() - get a device's desired watchdog priority
+ * @dev: The device, which device_node to read the property from
+ *
+ * return: The priority
+ */
+static unsigned int dev_get_watchdog_priority(struct device *dev)
+{
+ unsigned int priority = WATCHDOG_DEFAULT_PRIORITY;
+
+ if (dev)
+ of_property_read_u32(dev->of_node, "watchdog-priority",
+ &priority);
+
+ return priority;
+}
+
+static int seconds_to_expire_get(struct param_d *p, void *priv)
+{
+ struct watchdog *wd = priv;
+ uint64_t diff;
+
+ if (!wd->timeout_cur) {
+ wd->seconds_to_expire = -1;
+ return 0;
+ }
+
+ diff = get_time_ns() - wd->last_ping;
+
+ do_div(diff, 1000000000);
+
+ wd->seconds_to_expire = wd->timeout_cur - diff;
+
+ return 0;
+}
+
+static void __noreturn watchdog_restart_handle(struct restart_handler *this)
+{
+ struct watchdog *wd = watchdog_get_default();
+ int ret = -ENODEV;
+
+ if (wd)
+ ret = watchdog_set_timeout(wd, 1);
+
+ BUG_ON(ret);
+ mdelay(2000);
+
+ pr_emerg("Watchdog failed to reset the machine\n");
+ hang();
+}
+
+static struct restart_handler restart_handler = {
+ .restart = watchdog_restart_handle,
+ .name = "watchdog-restart",
+};
+
int watchdog_register(struct watchdog *wd)
{
struct param_d *p;
@@ -134,7 +196,7 @@ int watchdog_register(struct watchdog *wd)
int ret = 0;
if (wd->hwdev)
- alias = of_alias_get(wd->hwdev->device_node);
+ alias = of_alias_get(wd->hwdev->of_node);
if (alias)
ret = watchdog_register_dev(wd, alias, DEVICE_ID_SINGLE);
@@ -145,14 +207,22 @@ int watchdog_register(struct watchdog *wd)
if (ret)
return ret;
+ p = dev_add_param_tristate_ro(&wd->dev, "running", &wd->running);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto error_unregister;
+ }
+
if (!wd->priority)
- wd->priority = WATCHDOG_DEFAULT_PRIORITY;
+ wd->priority = dev_get_watchdog_priority(wd->hwdev);
p = dev_add_param_uint32(&wd->dev, "priority",
watchdog_set_priority, NULL,
&wd->priority, "%u", wd);
- if (IS_ERR(p))
- return PTR_ERR(p);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto error_unregister;
+ }
/* set some default sane value */
if (!wd->timeout_max)
@@ -160,8 +230,10 @@ int watchdog_register(struct watchdog *wd)
p = dev_add_param_uint32_ro(&wd->dev, "timeout_max",
&wd->timeout_max, "%u");
- if (IS_ERR(p))
- return PTR_ERR(p);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto error_unregister;
+ }
if (IS_ENABLED(CONFIG_WATCHDOG_POLLER)) {
if (!wd->poller_timeout_cur ||
@@ -170,12 +242,28 @@ int watchdog_register(struct watchdog *wd)
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);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto error_unregister;
+ }
ret = watchdog_register_poller(wd);
if (ret)
- return ret;
+ goto error_unregister;
+ }
+
+ p = dev_add_param_uint32(&wd->dev, "seconds_to_expire", param_set_readonly,
+ seconds_to_expire_get, &wd->seconds_to_expire, "%d", wd);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto error_unregister;
+ }
+
+ if (!restart_handler.priority) {
+ restart_handler.priority = 10; /* don't override others */
+ ret = restart_handler_register(&restart_handler);
+ if (ret)
+ dev_warn(&wd->dev, "failed to register restart handler\n");
}
list_add_tail(&wd->list, &watchdog_list);
@@ -184,6 +272,10 @@ int watchdog_register(struct watchdog *wd)
wd->priority);
return 0;
+
+error_unregister:
+ unregister_device(&wd->dev);
+ return ret;
}
EXPORT_SYMBOL(watchdog_register);
@@ -217,10 +309,29 @@ struct watchdog *watchdog_get_default(void)
}
EXPORT_SYMBOL(watchdog_get_default);
+int watchdog_get_alias_id_from(struct watchdog *wd, struct device_node *root)
+{
+ struct device_node *dstnp, *srcnp = wd->hwdev ? wd->hwdev->of_node : NULL;
+ char *name;
+
+ if (!srcnp)
+ return -ENODEV;
+
+ name = of_get_reproducible_name(srcnp);
+ dstnp = of_find_node_by_reproducible_name(root, name);
+ free(name);
+
+ if (!dstnp)
+ return -ENODEV;
+
+ return of_alias_get_id_from(root, wd->hwdev->of_node, "watchdog");
+}
+EXPORT_SYMBOL(watchdog_get_alias_id_from);
+
struct watchdog *watchdog_get_by_name(const char *name)
{
struct watchdog *tmp;
- struct device_d *dev = get_device_by_name(name);
+ struct device *dev = get_device_by_name(name);
if (!dev)
return NULL;
@@ -233,17 +344,29 @@ struct watchdog *watchdog_get_by_name(const char *name)
}
EXPORT_SYMBOL(watchdog_get_by_name);
-/**
- * of_get_watchdog_priority() - get the desired watchdog priority from device tree
- * @node: The device_node to read the property from
- *
- * return: The priority
- */
-unsigned int of_get_watchdog_priority(struct device_node *node)
+int watchdog_inhibit_all(void)
{
- unsigned int priority = WATCHDOG_DEFAULT_PRIORITY;
+ struct watchdog *wd;
+ int ret = 0;
- of_property_read_u32(node, "watchdog-priority", &priority);
+ list_for_each_entry(wd, &watchdog_list, list) {
+ int err;
+ if (!wd->priority || watchdog_hw_running(wd) == false)
+ continue;
- return priority;
+ err = watchdog_set_timeout(wd, 0);
+ if (!err)
+ continue;
+
+ if (err != -ENOSYS || !IS_ENABLED(CONFIG_WATCHDOG_POLLER)) {
+ ret = err;
+ continue;
+ }
+
+ wd->poller_enable = true;
+ watchdog_poller_start(wd);
+ }
+
+ return ret;
}
+EXPORT_SYMBOL(watchdog_inhibit_all);