diff options
Diffstat (limited to 'drivers/watchdog/wd_core.c')
-rw-r--r-- | drivers/watchdog/wd_core.c | 125 |
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); |