1 /* 2 * SoftDog 0.07: A Software Watchdog Device 3 * 4 * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, All Rights Reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 * 11 * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide 12 * warranty for any of this software. This material is provided 13 * "AS-IS" and at no charge. 14 * 15 * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> 16 * 17 * Software only watchdog driver. Unlike its big brother the WDT501P 18 * driver this won't always recover a failed machine. 19 * 20 * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> : 21 * Modularised. 22 * Added soft_margin; use upon insmod to change the timer delay. 23 * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate 24 * minors. 25 * 26 * 19980911 Alan Cox 27 * Made SMP safe for 2.3.x 28 * 29 * 20011127 Joel Becker (jlbec@evilplan.org> 30 * Added soft_noboot; Allows testing the softdog trigger without 31 * requiring a recompile. 32 * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. 33 * 34 * 20020530 Joel Becker <joel.becker@oracle.com> 35 * Added Matt Domsch's nowayout module option. 36 */ 37 38 #include <linux/module.h> 39 #include <linux/moduleparam.h> 40 #include <linux/types.h> 41 #include <linux/timer.h> 42 #include <linux/miscdevice.h> 43 #include <linux/watchdog.h> 44 #include <linux/fs.h> 45 #include <linux/notifier.h> 46 #include <linux/reboot.h> 47 #include <linux/init.h> 48 #include <linux/jiffies.h> 49 #include <linux/uaccess.h> 50 51 #define PFX "SoftDog: " 52 53 #define TIMER_MARGIN 60 /* Default is 60 seconds */ 54 static int soft_margin = TIMER_MARGIN; /* in seconds */ 55 module_param(soft_margin, int, 0); 56 MODULE_PARM_DESC(soft_margin, 57 "Watchdog soft_margin in seconds. (0 < soft_margin < 65536, default=" 58 __MODULE_STRING(TIMER_MARGIN) ")"); 59 60 static int nowayout = WATCHDOG_NOWAYOUT; 61 module_param(nowayout, int, 0); 62 MODULE_PARM_DESC(nowayout, 63 "Watchdog cannot be stopped once started (default=" 64 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 65 66 #ifdef ONLY_TESTING 67 static int soft_noboot = 1; 68 #else 69 static int soft_noboot = 0; 70 #endif /* ONLY_TESTING */ 71 72 module_param(soft_noboot, int, 0); 73 MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); 74 75 /* 76 * Our timer 77 */ 78 79 static void watchdog_fire(unsigned long); 80 81 static struct timer_list watchdog_ticktock = 82 TIMER_INITIALIZER(watchdog_fire, 0, 0); 83 static unsigned long driver_open, orphan_timer; 84 static char expect_close; 85 86 87 /* 88 * If the timer expires.. 89 */ 90 91 static void watchdog_fire(unsigned long data) 92 { 93 if (test_and_clear_bit(0, &orphan_timer)) 94 module_put(THIS_MODULE); 95 96 if (soft_noboot) 97 printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n"); 98 else { 99 printk(KERN_CRIT PFX "Initiating system reboot.\n"); 100 emergency_restart(); 101 printk(KERN_CRIT PFX "Reboot didn't ?????\n"); 102 } 103 } 104 105 /* 106 * Softdog operations 107 */ 108 109 static int softdog_keepalive(void) 110 { 111 mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ)); 112 return 0; 113 } 114 115 static int softdog_stop(void) 116 { 117 del_timer(&watchdog_ticktock); 118 return 0; 119 } 120 121 static int softdog_set_heartbeat(int t) 122 { 123 if ((t < 0x0001) || (t > 0xFFFF)) 124 return -EINVAL; 125 126 soft_margin = t; 127 return 0; 128 } 129 130 /* 131 * /dev/watchdog handling 132 */ 133 134 static int softdog_open(struct inode *inode, struct file *file) 135 { 136 if (test_and_set_bit(0, &driver_open)) 137 return -EBUSY; 138 if (!test_and_clear_bit(0, &orphan_timer)) 139 __module_get(THIS_MODULE); 140 /* 141 * Activate timer 142 */ 143 softdog_keepalive(); 144 return nonseekable_open(inode, file); 145 } 146 147 static int softdog_release(struct inode *inode, struct file *file) 148 { 149 /* 150 * Shut off the timer. 151 * Lock it in if it's a module and we set nowayout 152 */ 153 if (expect_close == 42) { 154 softdog_stop(); 155 module_put(THIS_MODULE); 156 } else { 157 printk(KERN_CRIT PFX 158 "Unexpected close, not stopping watchdog!\n"); 159 set_bit(0, &orphan_timer); 160 softdog_keepalive(); 161 } 162 clear_bit(0, &driver_open); 163 expect_close = 0; 164 return 0; 165 } 166 167 static ssize_t softdog_write(struct file *file, const char __user *data, 168 size_t len, loff_t *ppos) 169 { 170 /* 171 * Refresh the timer. 172 */ 173 if (len) { 174 if (!nowayout) { 175 size_t i; 176 177 /* In case it was set long ago */ 178 expect_close = 0; 179 180 for (i = 0; i != len; i++) { 181 char c; 182 183 if (get_user(c, data + i)) 184 return -EFAULT; 185 if (c == 'V') 186 expect_close = 42; 187 } 188 } 189 softdog_keepalive(); 190 } 191 return len; 192 } 193 194 static long softdog_ioctl(struct file *file, unsigned int cmd, 195 unsigned long arg) 196 { 197 void __user *argp = (void __user *)arg; 198 int __user *p = argp; 199 int new_margin; 200 static const struct watchdog_info ident = { 201 .options = WDIOF_SETTIMEOUT | 202 WDIOF_KEEPALIVEPING | 203 WDIOF_MAGICCLOSE, 204 .firmware_version = 0, 205 .identity = "Software Watchdog", 206 }; 207 switch (cmd) { 208 case WDIOC_GETSUPPORT: 209 return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; 210 case WDIOC_GETSTATUS: 211 case WDIOC_GETBOOTSTATUS: 212 return put_user(0, p); 213 case WDIOC_KEEPALIVE: 214 softdog_keepalive(); 215 return 0; 216 case WDIOC_SETTIMEOUT: 217 if (get_user(new_margin, p)) 218 return -EFAULT; 219 if (softdog_set_heartbeat(new_margin)) 220 return -EINVAL; 221 softdog_keepalive(); 222 /* Fall */ 223 case WDIOC_GETTIMEOUT: 224 return put_user(soft_margin, p); 225 default: 226 return -ENOTTY; 227 } 228 } 229 230 /* 231 * Notifier for system down 232 */ 233 234 static int softdog_notify_sys(struct notifier_block *this, unsigned long code, 235 void *unused) 236 { 237 if (code == SYS_DOWN || code == SYS_HALT) 238 /* Turn the WDT off */ 239 softdog_stop(); 240 return NOTIFY_DONE; 241 } 242 243 /* 244 * Kernel Interfaces 245 */ 246 247 static const struct file_operations softdog_fops = { 248 .owner = THIS_MODULE, 249 .llseek = no_llseek, 250 .write = softdog_write, 251 .unlocked_ioctl = softdog_ioctl, 252 .open = softdog_open, 253 .release = softdog_release, 254 }; 255 256 static struct miscdevice softdog_miscdev = { 257 .minor = WATCHDOG_MINOR, 258 .name = "watchdog", 259 .fops = &softdog_fops, 260 }; 261 262 static struct notifier_block softdog_notifier = { 263 .notifier_call = softdog_notify_sys, 264 }; 265 266 static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n"; 267 268 static int __init watchdog_init(void) 269 { 270 int ret; 271 272 /* Check that the soft_margin value is within it's range; 273 if not reset to the default */ 274 if (softdog_set_heartbeat(soft_margin)) { 275 softdog_set_heartbeat(TIMER_MARGIN); 276 printk(KERN_INFO PFX 277 "soft_margin must be 0 < soft_margin < 65536, using %d\n", 278 TIMER_MARGIN); 279 } 280 281 ret = register_reboot_notifier(&softdog_notifier); 282 if (ret) { 283 printk(KERN_ERR PFX 284 "cannot register reboot notifier (err=%d)\n", ret); 285 return ret; 286 } 287 288 ret = misc_register(&softdog_miscdev); 289 if (ret) { 290 printk(KERN_ERR PFX 291 "cannot register miscdev on minor=%d (err=%d)\n", 292 WATCHDOG_MINOR, ret); 293 unregister_reboot_notifier(&softdog_notifier); 294 return ret; 295 } 296 297 printk(banner, soft_noboot, soft_margin, nowayout); 298 299 return 0; 300 } 301 302 static void __exit watchdog_exit(void) 303 { 304 misc_deregister(&softdog_miscdev); 305 unregister_reboot_notifier(&softdog_notifier); 306 } 307 308 module_init(watchdog_init); 309 module_exit(watchdog_exit); 310 311 MODULE_AUTHOR("Alan Cox"); 312 MODULE_DESCRIPTION("Software Watchdog Device Driver"); 313 MODULE_LICENSE("GPL"); 314 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 315