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.c125
1 files changed, 111 insertions, 14 deletions
diff --git a/drivers/watchdog/wd_core.c b/drivers/watchdog/wd_core.c
index a17234f4b6..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);
@@ -45,12 +37,18 @@ int watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
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);
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;
@@ -141,17 +139,56 @@ static int watchdog_register_dev(struct watchdog *wd, const char *name, int id)
*
* return: The priority
*/
-static unsigned int dev_get_watchdog_priority(struct device_d *dev)
+static unsigned int dev_get_watchdog_priority(struct device *dev)
{
unsigned int priority = WATCHDOG_DEFAULT_PRIORITY;
if (dev)
- of_property_read_u32(dev->device_node, "watchdog-priority",
+ 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;
@@ -159,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);
@@ -215,6 +252,20 @@ int watchdog_register(struct watchdog *wd)
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);
pr_debug("registering watchdog %s with priority %d\n", watchdog_name(wd),
@@ -258,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;
@@ -273,3 +343,30 @@ struct watchdog *watchdog_get_by_name(const char *name)
return NULL;
}
EXPORT_SYMBOL(watchdog_get_by_name);
+
+int watchdog_inhibit_all(void)
+{
+ struct watchdog *wd;
+ int ret = 0;
+
+ list_for_each_entry(wd, &watchdog_list, list) {
+ int err;
+ if (!wd->priority || watchdog_hw_running(wd) == false)
+ continue;
+
+ 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);