Merge git://git.kernel.org/pub/scm/linux/kernel/git/agk/linux-2.6-dm
[linux-2.6] / drivers / watchdog / softdog.c
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);