[WATCHDOG] acquirewdt.c - convert to platform_device
[deliverable/linux.git] / drivers / char / watchdog / acquirewdt.c
CommitLineData
1da177e4
LT
1/*
2 * Acquire Single Board Computer Watchdog Timer driver
3 *
4 * Based on wdt.c. Original copyright messages:
5 *
6 * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
7 * http://www.redhat.com
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version
12 * 2 of the License, or (at your option) any later version.
13 *
14 * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
15 * warranty for any of this software. This material is provided
16 * "AS-IS" and at no charge.
17 *
18 * (c) Copyright 1995 Alan Cox <alan@redhat.com>
19 *
20 * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
21 * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
22 * Can't add timeout - driver doesn't allow changing value
23 */
24
25/*
26 * Theory of Operation:
27 * The Watch-Dog Timer is provided to ensure that standalone
28 * Systems can always recover from catastrophic conditions that
29 * caused the CPU to crash. This condition may have occured by
30 * external EMI or a software bug. When the CPU stops working
31 * correctly, hardware on the board will either perform a hardware
32 * reset (cold boot) or a non-maskable interrupt (NMI) to bring the
33 * system back to a known state.
34 *
35 * The Watch-Dog Timer is controlled by two I/O Ports.
36 * 443 hex - Read - Enable or refresh the Watch-Dog Timer
37 * 043 hex - Read - Disable the Watch-Dog Timer
38 *
39 * To enable the Watch-Dog Timer, a read from I/O port 443h must
40 * be performed. This will enable and activate the countdown timer
41 * which will eventually time out and either reset the CPU or cause
42 * an NMI depending on the setting of a jumper. To ensure that this
43 * reset condition does not occur, the Watch-Dog Timer must be
44 * periodically refreshed by reading the same I/O port 443h.
45 * The Watch-Dog Timer is disabled by reading I/O port 043h.
46 *
47 * The Watch-Dog Timer Time-Out Period is set via jumpers.
48 * It can be 1, 2, 10, 20, 110 or 220 seconds.
49 */
50
76c11f04
WVS
51/*
52 * Includes, defines, variables, module parameters, ...
53 */
1da177e4 54
76c11f04
WVS
55/* Includes */
56#include <linux/module.h> /* For module specific items */
57#include <linux/moduleparam.h> /* For new moduleparam's */
58#include <linux/types.h> /* For standard types (like size_t) */
59#include <linux/errno.h> /* For the -ENODEV/... values */
60#include <linux/kernel.h> /* For printk/panic/... */
61#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */
62#include <linux/watchdog.h> /* For the watchdog specific items */
63#include <linux/fs.h> /* For file operations */
64#include <linux/ioport.h> /* For io-port access */
65#include <linux/notifier.h> /* For reboot notifier */
66#include <linux/reboot.h> /* For reboot notifier */
ad5fe323 67#include <linux/platform_device.h> /* For platform_driver framework */
76c11f04
WVS
68#include <linux/init.h> /* For __init/__exit/... */
69
70#include <asm/uaccess.h> /* For copy_to_user/put_user/... */
71#include <asm/io.h> /* For inb/outb/... */
72
73/* Module information */
74#define DRV_NAME "acquirewdt"
75#define PFX DRV_NAME ": "
1da177e4 76#define WATCHDOG_NAME "Acquire WDT"
1da177e4
LT
77#define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */
78
76c11f04 79/* internal variables */
ad5fe323 80static struct platform_device *acq_platform_device; /* the watchdog platform device */
1da177e4
LT
81static unsigned long acq_is_open;
82static char expect_close;
83
76c11f04
WVS
84/* module parameters */
85static int wdt_stop = 0x43; /* You must set this - there is no sane way to probe for this board. */
1da177e4
LT
86module_param(wdt_stop, int, 0);
87MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
88
76c11f04 89static int wdt_start = 0x443; /* You must set this - there is no sane way to probe for this board. */
1da177e4
LT
90module_param(wdt_start, int, 0);
91MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
92
4bfdf378 93static int nowayout = WATCHDOG_NOWAYOUT;
1da177e4 94module_param(nowayout, int, 0);
76c11f04 95MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1da177e4
LT
96
97/*
76c11f04 98 * Watchdog Operations
1da177e4
LT
99 */
100
101static void acq_keepalive(void)
102{
103 /* Write a watchdog value */
104 inb_p(wdt_start);
105}
106
107static void acq_stop(void)
108{
109 /* Turn the card off */
110 inb_p(wdt_stop);
111}
112
113/*
76c11f04 114 * /dev/watchdog handling
1da177e4
LT
115 */
116
117static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
118{
119 /* See if we got the magic character 'V' and reload the timer */
120 if(count) {
121 if (!nowayout) {
122 size_t i;
123
124 /* note: just in case someone wrote the magic character
125 * five months ago... */
126 expect_close = 0;
127
128 /* scan to see whether or not we got the magic character */
129 for (i = 0; i != count; i++) {
130 char c;
131 if (get_user(c, buf + i))
132 return -EFAULT;
133 if (c == 'V')
134 expect_close = 42;
135 }
136 }
137
138 /* Well, anyhow someone wrote to us, we should return that favour */
139 acq_keepalive();
140 }
141 return count;
142}
143
144static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
145 unsigned long arg)
146{
147 int options, retval = -EINVAL;
148 void __user *argp = (void __user *)arg;
149 int __user *p = argp;
150 static struct watchdog_info ident =
151 {
152 .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
153 .firmware_version = 1,
76c11f04 154 .identity = WATCHDOG_NAME,
1da177e4
LT
155 };
156
157 switch(cmd)
158 {
159 case WDIOC_GETSUPPORT:
160 return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
161
162 case WDIOC_GETSTATUS:
163 case WDIOC_GETBOOTSTATUS:
164 return put_user(0, p);
165
166 case WDIOC_KEEPALIVE:
167 acq_keepalive();
168 return 0;
169
170 case WDIOC_GETTIMEOUT:
171 return put_user(WATCHDOG_HEARTBEAT, p);
172
173 case WDIOC_SETOPTIONS:
174 {
175 if (get_user(options, p))
176 return -EFAULT;
177
178 if (options & WDIOS_DISABLECARD)
179 {
180 acq_stop();
181 retval = 0;
182 }
183
184 if (options & WDIOS_ENABLECARD)
185 {
186 acq_keepalive();
187 retval = 0;
188 }
189
190 return retval;
191 }
192
193 default:
795b89d2 194 return -ENOTTY;
1da177e4
LT
195 }
196}
197
198static int acq_open(struct inode *inode, struct file *file)
199{
200 if (test_and_set_bit(0, &acq_is_open))
201 return -EBUSY;
202
203 if (nowayout)
204 __module_get(THIS_MODULE);
205
206 /* Activate */
207 acq_keepalive();
208 return nonseekable_open(inode, file);
209}
210
211static int acq_close(struct inode *inode, struct file *file)
212{
213 if (expect_close == 42) {
214 acq_stop();
215 } else {
216 printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
217 acq_keepalive();
218 }
219 clear_bit(0, &acq_is_open);
220 expect_close = 0;
221 return 0;
222}
223
224/*
225 * Notifier for system down
226 */
227
228static int acq_notify_sys(struct notifier_block *this, unsigned long code,
229 void *unused)
230{
231 if(code==SYS_DOWN || code==SYS_HALT) {
232 /* Turn the WDT off */
233 acq_stop();
234 }
235 return NOTIFY_DONE;
236}
237
238/*
239 * Kernel Interfaces
240 */
241
62322d25 242static const struct file_operations acq_fops = {
1da177e4
LT
243 .owner = THIS_MODULE,
244 .llseek = no_llseek,
245 .write = acq_write,
246 .ioctl = acq_ioctl,
247 .open = acq_open,
248 .release = acq_close,
249};
250
76c11f04
WVS
251static struct miscdevice acq_miscdev = {
252 .minor = WATCHDOG_MINOR,
253 .name = "watchdog",
254 .fops = &acq_fops,
1da177e4
LT
255};
256
257/*
258 * The WDT card needs to learn about soft shutdowns in order to
259 * turn the timebomb registers off.
260 */
261
76c11f04 262static struct notifier_block acq_notifier = {
1da177e4
LT
263 .notifier_call = acq_notify_sys,
264};
265
76c11f04
WVS
266/*
267 * Init & exit routines
268 */
269
ad5fe323 270static int __devinit acq_probe(struct platform_device *dev)
1da177e4
LT
271{
272 int ret;
273
1da177e4
LT
274 if (wdt_stop != wdt_start) {
275 if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
276 printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
277 wdt_stop);
278 ret = -EIO;
279 goto out;
280 }
281 }
282
283 if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
284 printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
285 wdt_start);
286 ret = -EIO;
287 goto unreg_stop;
288 }
289
290 ret = register_reboot_notifier(&acq_notifier);
291 if (ret != 0) {
292 printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
293 ret);
294 goto unreg_regions;
295 }
296
297 ret = misc_register(&acq_miscdev);
298 if (ret != 0) {
299 printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
300 WATCHDOG_MINOR, ret);
301 goto unreg_reboot;
302 }
303
304 printk (KERN_INFO PFX "initialized. (nowayout=%d)\n",
305 nowayout);
306
307 return 0;
308
309unreg_reboot:
310 unregister_reboot_notifier(&acq_notifier);
311unreg_regions:
312 release_region(wdt_start, 1);
313unreg_stop:
314 if (wdt_stop != wdt_start)
315 release_region(wdt_stop, 1);
316out:
317 return ret;
318}
319
ad5fe323 320static int __devexit acq_remove(struct platform_device *dev)
1da177e4
LT
321{
322 misc_deregister(&acq_miscdev);
323 unregister_reboot_notifier(&acq_notifier);
76c11f04 324 release_region(wdt_start,1);
1da177e4
LT
325 if(wdt_stop != wdt_start)
326 release_region(wdt_stop,1);
ad5fe323
WVS
327
328 return 0;
329}
330
331static struct platform_driver acquirewdt_driver = {
332 .probe = acq_probe,
333 .remove = __devexit_p(acq_remove),
334 .driver = {
335 .owner = THIS_MODULE,
336 .name = DRV_NAME,
337 },
338};
339
340static int __init acq_init(void)
341{
342 int err;
343
344 printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n");
345
346 err = platform_driver_register(&acquirewdt_driver);
347 if (err)
348 return err;
349
350 acq_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
351 if (IS_ERR(acq_platform_device)) {
352 err = PTR_ERR(acq_platform_device);
353 goto unreg_platform_driver;
354 }
355
356 return 0;
357
358unreg_platform_driver:
359 platform_driver_unregister(&acquirewdt_driver);
360 return err;
361}
362
363static void __exit acq_exit(void)
364{
365 platform_device_unregister(acq_platform_device);
366 platform_driver_unregister(&acquirewdt_driver);
367 printk(KERN_INFO PFX "Watchdog Module Unloaded.\n");
1da177e4
LT
368}
369
370module_init(acq_init);
371module_exit(acq_exit);
372
373MODULE_AUTHOR("David Woodhouse");
374MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
375MODULE_LICENSE("GPL");
376MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
This page took 0.208496 seconds and 5 git commands to generate.