| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * SoftDog: A Software Watchdog Device |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, |
|---|
| 5 | 6 | * All Rights Reserved. |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or |
|---|
| 8 | | - * modify it under the terms of the GNU General Public License |
|---|
| 9 | | - * as published by the Free Software Foundation; either version |
|---|
| 10 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 11 | 7 | * |
|---|
| 12 | 8 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide |
|---|
| 13 | 9 | * warranty for any of this software. This material is provided |
|---|
| .. | .. |
|---|
| 24 | 20 | #include <linux/hrtimer.h> |
|---|
| 25 | 21 | #include <linux/init.h> |
|---|
| 26 | 22 | #include <linux/kernel.h> |
|---|
| 23 | +#include <linux/kthread.h> |
|---|
| 27 | 24 | #include <linux/module.h> |
|---|
| 28 | 25 | #include <linux/moduleparam.h> |
|---|
| 29 | 26 | #include <linux/reboot.h> |
|---|
| 30 | 27 | #include <linux/types.h> |
|---|
| 31 | 28 | #include <linux/watchdog.h> |
|---|
| 29 | +#include <linux/workqueue.h> |
|---|
| 32 | 30 | |
|---|
| 33 | 31 | #define TIMER_MARGIN 60 /* Default is 60 seconds */ |
|---|
| 34 | 32 | static unsigned int soft_margin = TIMER_MARGIN; /* in seconds */ |
|---|
| .. | .. |
|---|
| 53 | 51 | MODULE_PARM_DESC(soft_panic, |
|---|
| 54 | 52 | "Softdog action, set to 1 to panic, 0 to reboot (default=0)"); |
|---|
| 55 | 53 | |
|---|
| 54 | +static char *soft_reboot_cmd; |
|---|
| 55 | +module_param(soft_reboot_cmd, charp, 0000); |
|---|
| 56 | +MODULE_PARM_DESC(soft_reboot_cmd, |
|---|
| 57 | + "Set reboot command. Emergency reboot takes place if unset"); |
|---|
| 58 | + |
|---|
| 59 | +static bool soft_active_on_boot; |
|---|
| 60 | +module_param(soft_active_on_boot, bool, 0000); |
|---|
| 61 | +MODULE_PARM_DESC(soft_active_on_boot, |
|---|
| 62 | + "Set to true to active Softdog on boot (default=false)"); |
|---|
| 63 | + |
|---|
| 56 | 64 | static struct hrtimer softdog_ticktock; |
|---|
| 57 | 65 | static struct hrtimer softdog_preticktock; |
|---|
| 58 | 66 | |
|---|
| 67 | +static int reboot_kthread_fn(void *data) |
|---|
| 68 | +{ |
|---|
| 69 | + kernel_restart(soft_reboot_cmd); |
|---|
| 70 | + return -EPERM; /* Should not reach here */ |
|---|
| 71 | +} |
|---|
| 72 | + |
|---|
| 73 | +static void reboot_work_fn(struct work_struct *unused) |
|---|
| 74 | +{ |
|---|
| 75 | + kthread_run(reboot_kthread_fn, NULL, "softdog_reboot"); |
|---|
| 76 | +} |
|---|
| 77 | + |
|---|
| 59 | 78 | static enum hrtimer_restart softdog_fire(struct hrtimer *timer) |
|---|
| 60 | 79 | { |
|---|
| 80 | + static bool soft_reboot_fired; |
|---|
| 81 | + |
|---|
| 61 | 82 | module_put(THIS_MODULE); |
|---|
| 62 | 83 | if (soft_noboot) { |
|---|
| 63 | 84 | pr_crit("Triggered - Reboot ignored\n"); |
|---|
| .. | .. |
|---|
| 66 | 87 | panic("Software Watchdog Timer expired"); |
|---|
| 67 | 88 | } else { |
|---|
| 68 | 89 | pr_crit("Initiating system reboot\n"); |
|---|
| 90 | + if (!soft_reboot_fired && soft_reboot_cmd != NULL) { |
|---|
| 91 | + static DECLARE_WORK(reboot_work, reboot_work_fn); |
|---|
| 92 | + /* |
|---|
| 93 | + * The 'kernel_restart' is a 'might-sleep' operation. |
|---|
| 94 | + * Also, executing it in system-wide workqueues blocks |
|---|
| 95 | + * any driver from using the same workqueue in its |
|---|
| 96 | + * shutdown callback function. Thus, we should execute |
|---|
| 97 | + * the 'kernel_restart' in a standalone kernel thread. |
|---|
| 98 | + * But since starting a kernel thread is also a |
|---|
| 99 | + * 'might-sleep' operation, so the 'reboot_work' is |
|---|
| 100 | + * required as a launcher of the kernel thread. |
|---|
| 101 | + * |
|---|
| 102 | + * After request the reboot, restart the timer to |
|---|
| 103 | + * schedule an 'emergency_restart' reboot after |
|---|
| 104 | + * 'TIMER_MARGIN' seconds. It's because if the softdog |
|---|
| 105 | + * hangs, it might be because of scheduling issues. And |
|---|
| 106 | + * if that is the case, both 'schedule_work' and |
|---|
| 107 | + * 'kernel_restart' may possibly be malfunctional at the |
|---|
| 108 | + * same time. |
|---|
| 109 | + */ |
|---|
| 110 | + soft_reboot_fired = true; |
|---|
| 111 | + schedule_work(&reboot_work); |
|---|
| 112 | + hrtimer_add_expires_ns(timer, |
|---|
| 113 | + (u64)TIMER_MARGIN * NSEC_PER_SEC); |
|---|
| 114 | + |
|---|
| 115 | + return HRTIMER_RESTART; |
|---|
| 116 | + } |
|---|
| 69 | 117 | emergency_restart(); |
|---|
| 70 | 118 | pr_crit("Reboot didn't ?????\n"); |
|---|
| 71 | 119 | } |
|---|
| .. | .. |
|---|
| 149 | 197 | softdog_preticktock.function = softdog_pretimeout; |
|---|
| 150 | 198 | } |
|---|
| 151 | 199 | |
|---|
| 200 | + if (soft_active_on_boot) |
|---|
| 201 | + softdog_ping(&softdog_dev); |
|---|
| 202 | + |
|---|
| 152 | 203 | ret = watchdog_register_device(&softdog_dev); |
|---|
| 153 | 204 | if (ret) |
|---|
| 154 | 205 | return ret; |
|---|
| 155 | 206 | |
|---|
| 156 | 207 | pr_info("initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n", |
|---|
| 157 | 208 | soft_noboot, softdog_dev.timeout, soft_panic, nowayout); |
|---|
| 209 | + pr_info(" soft_reboot_cmd=%s soft_active_on_boot=%d\n", |
|---|
| 210 | + soft_reboot_cmd ?: "<not set>", soft_active_on_boot); |
|---|
| 158 | 211 | |
|---|
| 159 | 212 | return 0; |
|---|
| 160 | 213 | } |
|---|