diff options
Diffstat (limited to 'drivers/watchdog/softdog.c')
| -rw-r--r-- | drivers/watchdog/softdog.c | 212 |
1 files changed, 112 insertions, 100 deletions
diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c index b68b1e519d53..0820e35ad2e3 100644 --- a/drivers/watchdog/softdog.c +++ b/drivers/watchdog/softdog.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * SoftDog: A Software Watchdog Device * * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, * All Rights Reserved. * - * 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. - * * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide * warranty for any of this software. This material is provided * "AS-IS" and at no charge. @@ -17,38 +13,20 @@ * * Software only watchdog driver. Unlike its big brother the WDT501P * driver this won't always recover a failed machine. - * - * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> : - * Modularised. - * Added soft_margin; use upon insmod to change the timer delay. - * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate - * minors. - * - * 19980911 Alan Cox - * Made SMP safe for 2.3.x - * - * 20011127 Joel Becker (jlbec@evilplan.org> - * Added soft_noboot; Allows testing the softdog trigger without - * requiring a recompile. - * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. - * - * 20020530 Joel Becker <joel.becker@oracle.com> - * Added Matt Domsch's nowayout module option. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/hrtimer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/kthread.h> #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/reboot.h> #include <linux/types.h> -#include <linux/timer.h> -#include <linux/miscdevice.h> #include <linux/watchdog.h> -#include <linux/notifier.h> -#include <linux/reboot.h> -#include <linux/init.h> -#include <linux/jiffies.h> -#include <linux/kernel.h> +#include <linux/workqueue.h> #define TIMER_MARGIN 60 /* Default is 60 seconds */ static unsigned int soft_margin = TIMER_MARGIN; /* in seconds */ @@ -63,7 +41,7 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -static int soft_noboot = 0; +static int soft_noboot; module_param(soft_noboot, int, 0); MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default=0)"); @@ -73,138 +51,172 @@ module_param(soft_panic, int, 0); MODULE_PARM_DESC(soft_panic, "Softdog action, set to 1 to panic, 0 to reboot (default=0)"); -/* - * Our timer - */ +static char *soft_reboot_cmd; +module_param(soft_reboot_cmd, charp, 0000); +MODULE_PARM_DESC(soft_reboot_cmd, + "Set reboot command. Emergency reboot takes place if unset"); -static void watchdog_fire(unsigned long); +static bool soft_active_on_boot; +module_param(soft_active_on_boot, bool, 0000); +MODULE_PARM_DESC(soft_active_on_boot, + "Set to true to active Softdog on boot (default=false)"); -static struct timer_list watchdog_ticktock = - TIMER_INITIALIZER(watchdog_fire, 0, 0); +static struct hrtimer softdog_ticktock; +static struct hrtimer softdog_preticktock; -/* - * If the timer expires.. - */ +static int reboot_kthread_fn(void *data) +{ + kernel_restart(soft_reboot_cmd); + return -EPERM; /* Should not reach here */ +} + +static void reboot_work_fn(struct work_struct *unused) +{ + kthread_run(reboot_kthread_fn, NULL, "softdog_reboot"); +} -static void watchdog_fire(unsigned long data) +static enum hrtimer_restart softdog_fire(struct hrtimer *timer) { - if (soft_noboot) + static bool soft_reboot_fired; + + module_put(THIS_MODULE); + if (soft_noboot) { pr_crit("Triggered - Reboot ignored\n"); - else if (soft_panic) { + } else if (soft_panic) { pr_crit("Initiating panic\n"); panic("Software Watchdog Timer expired"); } else { pr_crit("Initiating system reboot\n"); + if (!soft_reboot_fired && soft_reboot_cmd != NULL) { + static DECLARE_WORK(reboot_work, reboot_work_fn); + /* + * The 'kernel_restart' is a 'might-sleep' operation. + * Also, executing it in system-wide workqueues blocks + * any driver from using the same workqueue in its + * shutdown callback function. Thus, we should execute + * the 'kernel_restart' in a standalone kernel thread. + * But since starting a kernel thread is also a + * 'might-sleep' operation, so the 'reboot_work' is + * required as a launcher of the kernel thread. + * + * After request the reboot, restart the timer to + * schedule an 'emergency_restart' reboot after + * 'TIMER_MARGIN' seconds. It's because if the softdog + * hangs, it might be because of scheduling issues. And + * if that is the case, both 'schedule_work' and + * 'kernel_restart' may possibly be malfunctional at the + * same time. + */ + soft_reboot_fired = true; + schedule_work(&reboot_work); + hrtimer_add_expires_ns(timer, + (u64)TIMER_MARGIN * NSEC_PER_SEC); + + return HRTIMER_RESTART; + } emergency_restart(); pr_crit("Reboot didn't ?????\n"); } + + return HRTIMER_NORESTART; } -/* - * Softdog operations - */ +static struct watchdog_device softdog_dev; -static int softdog_ping(struct watchdog_device *w) +static enum hrtimer_restart softdog_pretimeout(struct hrtimer *timer) { - mod_timer(&watchdog_ticktock, jiffies+(w->timeout*HZ)); - return 0; -} + watchdog_notify_pretimeout(&softdog_dev); -static int softdog_stop(struct watchdog_device *w) -{ - del_timer(&watchdog_ticktock); - return 0; + return HRTIMER_NORESTART; } -static int softdog_set_timeout(struct watchdog_device *w, unsigned int t) +static int softdog_ping(struct watchdog_device *w) { - w->timeout = t; + if (!hrtimer_active(&softdog_ticktock)) + __module_get(THIS_MODULE); + hrtimer_start(&softdog_ticktock, ktime_set(w->timeout, 0), + HRTIMER_MODE_REL); + + if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) { + if (w->pretimeout) + hrtimer_start(&softdog_preticktock, + ktime_set(w->timeout - w->pretimeout, 0), + HRTIMER_MODE_REL); + else + hrtimer_cancel(&softdog_preticktock); + } + return 0; } -/* - * Notifier for system down - */ - -static int softdog_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) +static int softdog_stop(struct watchdog_device *w) { - if (code == SYS_DOWN || code == SYS_HALT) - /* Turn the WDT off */ - softdog_stop(NULL); - return NOTIFY_DONE; -} + if (hrtimer_cancel(&softdog_ticktock)) + module_put(THIS_MODULE); -/* - * Kernel Interfaces - */ + if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) + hrtimer_cancel(&softdog_preticktock); -static struct notifier_block softdog_notifier = { - .notifier_call = softdog_notify_sys, -}; + return 0; +} static struct watchdog_info softdog_info = { .identity = "Software Watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, }; -static struct watchdog_ops softdog_ops = { +static const struct watchdog_ops softdog_ops = { .owner = THIS_MODULE, .start = softdog_ping, .stop = softdog_stop, - .set_timeout = softdog_set_timeout, }; static struct watchdog_device softdog_dev = { .info = &softdog_info, .ops = &softdog_ops, .min_timeout = 1, - .max_timeout = 0xFFFF + .max_timeout = 65535, + .timeout = TIMER_MARGIN, }; -static int __init watchdog_init(void) +static int __init softdog_init(void) { int ret; - /* Check that the soft_margin value is within it's range; - if not reset to the default */ - if (soft_margin < 1 || soft_margin > 65535) { - pr_info("soft_margin must be 0 < soft_margin < 65536, using %d\n", - TIMER_MARGIN); - return -EINVAL; - } - softdog_dev.timeout = soft_margin; - + watchdog_init_timeout(&softdog_dev, soft_margin, NULL); watchdog_set_nowayout(&softdog_dev, nowayout); + watchdog_stop_on_reboot(&softdog_dev); - ret = register_reboot_notifier(&softdog_notifier); - if (ret) { - pr_err("cannot register reboot notifier (err=%d)\n", ret); - return ret; + hrtimer_setup(&softdog_ticktock, softdog_fire, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) { + softdog_info.options |= WDIOF_PRETIMEOUT; + hrtimer_setup(&softdog_preticktock, softdog_pretimeout, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); } + if (soft_active_on_boot) + softdog_ping(&softdog_dev); + ret = watchdog_register_device(&softdog_dev); - if (ret) { - unregister_reboot_notifier(&softdog_notifier); + if (ret) return ret; - } - pr_info("Software Watchdog Timer: 0.08 initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n", - soft_noboot, soft_margin, soft_panic, nowayout); + pr_info("initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n", + soft_noboot, softdog_dev.timeout, soft_panic, nowayout); + pr_info(" soft_reboot_cmd=%s soft_active_on_boot=%d\n", + soft_reboot_cmd ?: "<not set>", soft_active_on_boot); return 0; } +module_init(softdog_init); -static void __exit watchdog_exit(void) +static void __exit softdog_exit(void) { watchdog_unregister_device(&softdog_dev); - unregister_reboot_notifier(&softdog_notifier); } - -module_init(watchdog_init); -module_exit(watchdog_exit); +module_exit(softdog_exit); MODULE_AUTHOR("Alan Cox"); MODULE_DESCRIPTION("Software Watchdog Device Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
