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