ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / drivers / char / watchdog / indydog.c
1 /*
2  *      IndyDog 0.3     A Hardware Watchdog Device for SGI IP22
3  *
4  *      (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, 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  *      based on softdog.c by Alan Cox <alan@redhat.com>
12  */
13
14 #include <linux/module.h>
15 #include <linux/config.h>
16 #include <linux/types.h>
17 #include <linux/kernel.h>
18 #include <linux/fs.h>
19 #include <linux/mm.h>
20 #include <linux/miscdevice.h>
21 #include <linux/watchdog.h>
22 #include <linux/notifier.h>
23 #include <linux/reboot.h>
24 #include <linux/init.h>
25 #include <linux/moduleparam.h>
26 #include <asm/uaccess.h>
27 #include <asm/sgi/sgimc.h>
28
29 #define PFX "indydog: "
30 #define WATCHDOG_HEARTBEAT 60
31
32 static unsigned long indydog_alive;
33 static struct sgimc_misc_ctrl *mcmisc_regs;
34 static char expect_close;
35
36 #ifdef CONFIG_WATCHDOG_NOWAYOUT
37 static int nowayout = 1;
38 #else
39 static int nowayout = 0;
40 #endif
41
42 module_param(nowayout, int, 0);
43 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
44
45 static void indydog_start(void)
46 {
47         u32 mc_ctrl0 = mcmisc_regs->cpuctrl0;
48
49         mc_ctrl0 |= SGIMC_CCTRL0_WDOG;
50         mcmisc_regs->cpuctrl0 = mc_ctrl0;
51
52         printk(KERN_INFO PFX "Started watchdog timer.\n");
53 }
54
55 static void indydog_stop(void)
56 {
57         u32 mc_ctrl0 = mcmisc_regs->cpuctrl0;
58
59         mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG;
60         mcmisc_regs->cpuctrl0 = mc_ctrl0;
61
62         printk(KERN_INFO PFX "Stopped watchdog timer.\n");
63 }
64
65 static void indydog_ping(void)
66 {
67         mcmisc_regs->watchdogt = 0;
68 }
69
70 /*
71  *      Allow only one person to hold it open
72  */
73
74 static int indydog_open(struct inode *inode, struct file *file)
75 {
76         if( test_and_set_bit(0,&indydog_alive) )
77                 return -EBUSY;
78
79         if (nowayout)
80                 __module_get(THIS_MODULE);
81
82         /*
83          *      Activate timer
84          */
85         indydog_start();
86         indydog_ping();
87
88         return 0;
89 }
90
91 static int indydog_release(struct inode *inode, struct file *file)
92 {
93         /*
94          *      Shut off the timer.
95          *      Lock it in if it's a module and we set nowayout
96          */
97
98         if (expect_close == 42) {
99                 indydog_stop();
100         } else {
101                 printk(KERN_CRIT PFX "WDT device closed unexpectedly.  WDT will not stop!\n");
102                 indydog_ping();
103         }
104         clear_bit(0,&indydog_alive);
105         expect_close = 0;
106         return 0;
107 }
108
109 static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
110 {
111         /*  Can't seek (pwrite) on this device  */
112         if (ppos != &file->f_pos)
113                 return -ESPIPE;
114
115         /* Refresh the timer. */
116         if (len) {
117                 if (!nowayout) {
118                         size_t i;
119
120                         /* In case it was set long ago */
121                         expect_close = 0;
122
123                         for (i = 0; i != len; i++) {
124                                 char c;
125
126                                 if (get_user(c, data + i))
127                                         return -EFAULT;
128                                 if (c == 'V')
129                                         expect_close = 42;
130                         }
131                 }
132                 indydog_ping();
133         }
134         return len;
135 }
136
137 static int indydog_ioctl(struct inode *inode, struct file *file,
138         unsigned int cmd, unsigned long arg)
139 {
140         int options, retval = -EINVAL;
141         static struct watchdog_info ident = {
142                 .options =              WDIOF_KEEPALIVEPING |
143                                         WDIOF_MAGICCLOSE,
144                 .firmware_version =     0,
145                 .identity =             "Hardware Watchdog for SGI IP22",
146         };
147
148         switch (cmd) {
149                 default:
150                         return -ENOIOCTLCMD;
151                 case WDIOC_GETSUPPORT:
152                         if(copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident)))
153                                 return -EFAULT;
154                         return 0;
155                 case WDIOC_GETSTATUS:
156                 case WDIOC_GETBOOTSTATUS:
157                         return put_user(0,(int *)arg);
158                 case WDIOC_KEEPALIVE:
159                         indydog_ping();
160                         return 0;
161                 case WDIOC_GETTIMEOUT:
162                         return put_user(WATCHDOG_TIMEOUT,(int *)arg);
163                 case WDIOC_SETOPTIONS:
164                 {
165                         if (get_user(options, (int *)arg))
166                                 return -EFAULT;
167
168                         if (options & WDIOS_DISABLECARD)
169                         {
170                                 indydog_stop();
171                                 retval = 0;
172                         }
173
174                         if (options & WDIOS_ENABLECARD)
175                         {
176                                 indydog_start();
177                                 retval = 0;
178                         }
179
180                         return retval;
181                 }
182         }
183 }
184
185 static int indydog_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
186 {
187         if (code==SYS_DOWN || code==SYS_HALT) {
188                 /* Turn the WDT off */
189                 indydog_stop();
190         }
191
192         return NOTIFY_DONE;
193 }
194
195 static struct file_operations indydog_fops = {
196         .owner  = THIS_MODULE,
197         .write  = indydog_write,
198         .ioctl  = indydog_ioctl,
199         .open   = indydog_open,
200         .release= indydog_release,
201 };
202
203 static struct miscdevice indydog_miscdev = {
204         .minor  = WATCHDOG_MINOR,
205         .name   = "watchdog",
206         .fops   = &indydog_fops,
207 };
208
209 static struct notifier_block indydog_notifier = {
210         .notifier_call = indydog_notify_sys,
211 };
212
213 static char banner[] __initdata = KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n";
214
215 static int __init watchdog_init(void)
216 {
217         int ret;
218
219         mcmisc_regs = (struct sgimc_misc_ctrl *)(KSEG1+0x1fa00000);
220
221         ret = register_reboot_notifier(&indydog_notifier);
222         if (ret) {
223                 printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
224                         ret);
225                 return ret;
226         }
227
228         ret = misc_register(&indydog_miscdev);
229         if (ret) {
230                 printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
231                         WATCHDOG_MINOR, ret);
232                 unregister_reboot_notifier(&indydog_notifier);
233                 return ret;
234         }
235
236         printk(banner);
237
238         return 0;
239 }
240
241 static void __exit watchdog_exit(void)
242 {
243         misc_deregister(&indydog_miscdev);
244         unregister_reboot_notifier(&indydog_notifier);
245 }
246
247 module_init(watchdog_init);
248 module_exit(watchdog_exit);
249
250 MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>");
251 MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22");
252 MODULE_LICENSE("GPL");
253 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);