^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0+
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * SoftDog: A Software Watchdog Device
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) * All Rights Reserved.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) * warranty for any of this software. This material is provided
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) * "AS-IS" and at no charge.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) * Software only watchdog driver. Unlike its big brother the WDT501P
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) * driver this won't always recover a failed machine.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) #include <linux/hrtimer.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) #include <linux/init.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) #include <linux/kernel.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) #include <linux/kthread.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) #include <linux/moduleparam.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) #include <linux/reboot.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) #include <linux/types.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) #include <linux/watchdog.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) #include <linux/workqueue.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) #define TIMER_MARGIN 60 /* Default is 60 seconds */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) static unsigned int soft_margin = TIMER_MARGIN; /* in seconds */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) module_param(soft_margin, uint, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) MODULE_PARM_DESC(soft_margin,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) "Watchdog soft_margin in seconds. (0 < soft_margin < 65536, default="
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) __MODULE_STRING(TIMER_MARGIN) ")");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) static bool nowayout = WATCHDOG_NOWAYOUT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) module_param(nowayout, bool, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) MODULE_PARM_DESC(nowayout,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) "Watchdog cannot be stopped once started (default="
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) static int soft_noboot;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) module_param(soft_noboot, int, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) MODULE_PARM_DESC(soft_noboot,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) "Softdog action, set to 1 to ignore reboots, 0 to reboot (default=0)");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) static int soft_panic;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) module_param(soft_panic, int, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) MODULE_PARM_DESC(soft_panic,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) "Softdog action, set to 1 to panic, 0 to reboot (default=0)");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) static char *soft_reboot_cmd;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) module_param(soft_reboot_cmd, charp, 0000);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) MODULE_PARM_DESC(soft_reboot_cmd,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) "Set reboot command. Emergency reboot takes place if unset");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) static bool soft_active_on_boot;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) module_param(soft_active_on_boot, bool, 0000);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) MODULE_PARM_DESC(soft_active_on_boot,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) "Set to true to active Softdog on boot (default=false)");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) static struct hrtimer softdog_ticktock;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) static struct hrtimer softdog_preticktock;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) static int reboot_kthread_fn(void *data)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) kernel_restart(soft_reboot_cmd);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) return -EPERM; /* Should not reach here */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) static void reboot_work_fn(struct work_struct *unused)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) kthread_run(reboot_kthread_fn, NULL, "softdog_reboot");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) static enum hrtimer_restart softdog_fire(struct hrtimer *timer)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) static bool soft_reboot_fired;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) module_put(THIS_MODULE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) if (soft_noboot) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) pr_crit("Triggered - Reboot ignored\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) } else if (soft_panic) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) pr_crit("Initiating panic\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) panic("Software Watchdog Timer expired");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) pr_crit("Initiating system reboot\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) if (!soft_reboot_fired && soft_reboot_cmd != NULL) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) static DECLARE_WORK(reboot_work, reboot_work_fn);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) * The 'kernel_restart' is a 'might-sleep' operation.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) * Also, executing it in system-wide workqueues blocks
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) * any driver from using the same workqueue in its
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) * shutdown callback function. Thus, we should execute
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) * the 'kernel_restart' in a standalone kernel thread.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) * But since starting a kernel thread is also a
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) * 'might-sleep' operation, so the 'reboot_work' is
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) * required as a launcher of the kernel thread.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) * After request the reboot, restart the timer to
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) * schedule an 'emergency_restart' reboot after
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) * 'TIMER_MARGIN' seconds. It's because if the softdog
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) * hangs, it might be because of scheduling issues. And
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) * if that is the case, both 'schedule_work' and
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) * 'kernel_restart' may possibly be malfunctional at the
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) * same time.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) soft_reboot_fired = true;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) schedule_work(&reboot_work);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) hrtimer_add_expires_ns(timer,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) (u64)TIMER_MARGIN * NSEC_PER_SEC);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) return HRTIMER_RESTART;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) emergency_restart();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) pr_crit("Reboot didn't ?????\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) return HRTIMER_NORESTART;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) static struct watchdog_device softdog_dev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) static enum hrtimer_restart softdog_pretimeout(struct hrtimer *timer)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) watchdog_notify_pretimeout(&softdog_dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) return HRTIMER_NORESTART;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) static int softdog_ping(struct watchdog_device *w)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) if (!hrtimer_active(&softdog_ticktock))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) __module_get(THIS_MODULE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) hrtimer_start(&softdog_ticktock, ktime_set(w->timeout, 0),
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) HRTIMER_MODE_REL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) if (w->pretimeout)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) hrtimer_start(&softdog_preticktock,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) ktime_set(w->timeout - w->pretimeout, 0),
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) HRTIMER_MODE_REL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) else
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) hrtimer_cancel(&softdog_preticktock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) static int softdog_stop(struct watchdog_device *w)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) if (hrtimer_cancel(&softdog_ticktock))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) module_put(THIS_MODULE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) hrtimer_cancel(&softdog_preticktock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) static struct watchdog_info softdog_info = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) .identity = "Software Watchdog",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) static const struct watchdog_ops softdog_ops = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) .owner = THIS_MODULE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) .start = softdog_ping,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) .stop = softdog_stop,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) static struct watchdog_device softdog_dev = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) .info = &softdog_info,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176) .ops = &softdog_ops,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) .min_timeout = 1,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) .max_timeout = 65535,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) .timeout = TIMER_MARGIN,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) static int __init softdog_init(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) int ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) watchdog_init_timeout(&softdog_dev, soft_margin, NULL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) watchdog_set_nowayout(&softdog_dev, nowayout);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) watchdog_stop_on_reboot(&softdog_dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) hrtimer_init(&softdog_ticktock, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) softdog_ticktock.function = softdog_fire;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) softdog_info.options |= WDIOF_PRETIMEOUT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) hrtimer_init(&softdog_preticktock, CLOCK_MONOTONIC,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) HRTIMER_MODE_REL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) softdog_preticktock.function = softdog_pretimeout;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) if (soft_active_on_boot)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) softdog_ping(&softdog_dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) ret = watchdog_register_device(&softdog_dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) if (ret)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) return ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) pr_info("initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) soft_noboot, softdog_dev.timeout, soft_panic, nowayout);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209) pr_info(" soft_reboot_cmd=%s soft_active_on_boot=%d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210) soft_reboot_cmd ?: "<not set>", soft_active_on_boot);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214) module_init(softdog_init);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216) static void __exit softdog_exit(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218) watchdog_unregister_device(&softdog_dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 220) module_exit(softdog_exit);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 221)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 222) MODULE_AUTHOR("Alan Cox");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 223) MODULE_DESCRIPTION("Software Watchdog Device Driver");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 224) MODULE_LICENSE("GPL");