3 * by Ken Hollis (khollis@bitgate.com)
5 * Permission granted from Simon Machell (73244.1270@compuserve.com)
6 * Written for the Linux Kernel, and GPLed by Ken Hollis
8 * 960107 Added request_region routines, modulized the whole thing.
9 * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added
11 * 960216 Added eof marker on the file, and changed verbose messages.
12 * 960716 Made functional and cosmetic changes to the source for
13 * inclusion in Linux 2.0.x kernels, thanks to Alan Cox.
14 * 960717 Removed read/seek routines, replaced with ioctl. Also, added
15 * check_region command due to Alan's suggestion.
16 * 960821 Made changes to compile in newer 2.0.x kernels. Added
17 * "cold reboot sense" entry.
18 * 960825 Made a few changes to code, deleted some defines and made
19 * typedefs to replace them. Made heartbeat reset only available
20 * via ioctl, and removed the write routine.
21 * 960828 Added new items for PC Watchdog Rev.C card.
22 * 960829 Changed around all of the IOCTLs, added new features,
23 * added watchdog disable/re-enable routines. Added firmware
24 * version reporting. Added read routine for temperature.
25 * Removed some extra defines, added an autodetect Revision
27 * 961006 Revised some documentation, fixed some cosmetic bugs. Made
28 * drivers to panic the system if it's overheating at bootup.
29 * 961118 Changed some verbiage on some of the output, tidied up
30 * code bits, and added compatibility to 2.1.x.
31 * 970912 Enabled board on open and disable on close.
32 * 971107 Took account of recent VFS changes (broke read).
33 * 971210 Disable board on initialisation in case board already ticking.
34 * 971222 Changed open/close for temperature handling
35 * Michael Meskes <meskes@debian.org>.
36 * 980112 Used minor numbers from include/linux/miscdevice.h
37 * 990403 Clear reset status after reading control status register in
38 * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>]
39 * 990605 Made changes to code to support Firmware 1.22a, added
40 * fairly useless proc entry.
41 * 990610 removed said useless proc code for the merge <alan>
42 * 000403 Removed last traces of proc code. <davej>
43 * 011214 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com>
44 * Added timeout module option to override default
47 #include <linux/module.h>
48 #include <linux/moduleparam.h>
49 #include <linux/types.h>
50 #include <linux/timer.h>
51 #include <linux/config.h>
52 #include <linux/wait.h>
53 #include <linux/slab.h>
54 #include <linux/ioport.h>
55 #include <linux/delay.h>
57 #include <linux/miscdevice.h>
58 #include <linux/watchdog.h>
59 #include <linux/init.h>
60 #include <linux/spinlock.h>
61 #include <linux/reboot.h>
63 #include <asm/uaccess.h>
67 * These are the auto-probe addresses available.
69 * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350.
70 * Revision A has an address range of 2 addresses, while Revision C has 3.
72 static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 };
74 #define WD_VER "1.12 (12/14/2001)"
77 * It should be noted that PCWD_REVISION_B was removed because A and B
78 * are essentially the same types of card, with the exception that B
79 * has temperature reporting. Since I didn't receive a Rev.B card,
80 * the Rev.B card is not supported. (It's a good thing too, as they
81 * are no longer in production.)
83 #define PCWD_REVISION_A 1
84 #define PCWD_REVISION_C 2
86 #define WD_TIMEOUT 4 /* 2 seconds for a timeout */
87 static int timeout_val = WD_TIMEOUT;
88 static int timeout = 2;
89 static int expect_close = 0;
91 module_param(timeout, int, 0);
92 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=2)");
94 #ifdef CONFIG_WATCHDOG_NOWAYOUT
95 static int nowayout = 1;
97 static int nowayout = 0;
100 module_param(nowayout, int, 0);
101 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
105 * These are the defines for the PC Watchdog card, revision A.
107 #define WD_WDRST 0x01 /* Previously reset state */
108 #define WD_T110 0x02 /* Temperature overheat sense */
109 #define WD_HRTBT 0x04 /* Heartbeat sense */
110 #define WD_RLY2 0x08 /* External relay triggered */
111 #define WD_SRLY2 0x80 /* Software external relay triggered */
113 static int current_readport, revision, temp_panic;
114 static atomic_t open_allowed = ATOMIC_INIT(1);
115 static int initial_status, supports_temp, mode_debug;
116 static spinlock_t io_lock;
121 * This routine checks the "current_readport" to see if the card lies there.
122 * If it does, it returns accordingly.
124 static int __init pcwd_checkcard(void)
126 int card_dat, prev_card_dat, found = 0, count = 0, done = 0;
129 prev_card_dat = 0x00;
131 prev_card_dat = inb(current_readport);
132 if (prev_card_dat == 0xFF)
135 while(count < timeout_val) {
137 /* Read the raw card data from the port, and strip off the
140 card_dat = inb_p(current_readport);
143 /* Sleep 1/2 second (or 500000 microseconds :) */
148 /* If there's a heart beat in both instances, then this means we
149 found our card. This also means that either the card was
150 previously reset, or the computer was power-cycled. */
152 if ((card_dat & WD_HRTBT) && (prev_card_dat & WD_HRTBT) &&
159 /* If the card data is exactly the same as the previous card data,
160 it's safe to assume that we should check again. The manual says
161 that the heart beat will change every second (or the bit will
162 toggle), and this can be used to see if the card is there. If
163 the card was powered up with a cold boot, then the card will
164 not start blinking until 2.5 minutes after a reboot, so this
165 bit will stay at 1. */
167 if ((card_dat == prev_card_dat) && (!done)) {
172 /* If the card data is toggling any bits, this means that the heart
173 beat was detected, or something else about the card is set. */
175 if ((card_dat != prev_card_dat) && (!done)) {
181 /* Otherwise something else strange happened. */
187 return((found) ? 1 : 0);
190 void pcwd_showprevstate(void)
192 int card_status = 0x0000;
194 if (revision == PCWD_REVISION_A)
195 initial_status = card_status = inb(current_readport);
197 initial_status = card_status = inb(current_readport + 1);
198 outb_p(0x00, current_readport + 1); /* clear reset status */
201 if (revision == PCWD_REVISION_A) {
202 if (card_status & WD_WDRST)
203 printk(KERN_INFO "pcwd: Previous reboot was caused by the card.\n");
205 if (card_status & WD_T110) {
206 printk(KERN_EMERG "pcwd: Card senses a CPU Overheat. Panicking!\n");
207 printk(KERN_EMERG "pcwd: CPU Overheat.\n");
211 if ((!(card_status & WD_WDRST)) &&
212 (!(card_status & WD_T110)))
213 printk(KERN_INFO "pcwd: Cold boot sense.\n");
215 if (card_status & 0x01)
216 printk(KERN_INFO "pcwd: Previous reboot was caused by the card.\n");
218 if (card_status & 0x04) {
219 printk(KERN_EMERG "pcwd: Card senses a CPU Overheat. Panicking!\n");
220 printk(KERN_EMERG "pcwd: CPU Overheat.\n");
224 if ((!(card_status & 0x01)) &&
225 (!(card_status & 0x04)))
226 printk(KERN_INFO "pcwd: Cold boot sense.\n");
230 static void pcwd_send_heartbeat(void)
234 wdrst_stat = inb_p(current_readport);
237 wdrst_stat |= WD_WDRST;
239 if (revision == PCWD_REVISION_A)
240 outb_p(wdrst_stat, current_readport + 1);
242 outb_p(wdrst_stat, current_readport);
245 static int pcwd_ioctl(struct inode *inode, struct file *file,
246 unsigned int cmd, unsigned long arg)
249 static struct watchdog_info ident=
251 WDIOF_OVERHEAT|WDIOF_CARDRESET,
260 case WDIOC_GETSUPPORT:
261 if(copy_to_user((void*)arg, &ident, sizeof(ident)))
265 case WDIOC_GETSTATUS:
267 if (revision == PCWD_REVISION_A)
268 cdat = inb(current_readport);
270 cdat = inb(current_readport + 1 );
271 spin_unlock(&io_lock);
272 rv = WDIOF_MAGICCLOSE;
274 if (revision == PCWD_REVISION_A)
277 rv |= WDIOF_CARDRESET;
281 rv |= WDIOF_OVERHEAT;
284 printk (KERN_INFO "pcwd: Temperature overheat trip!\n");
292 rv |= WDIOF_CARDRESET;
296 rv |= WDIOF_OVERHEAT;
299 printk (KERN_INFO "pcwd: Temperature overheat trip!\n");
305 if(put_user(rv, (int *) arg))
309 case WDIOC_GETBOOTSTATUS:
312 if (revision == PCWD_REVISION_A)
314 if (initial_status & WD_WDRST)
315 rv |= WDIOF_CARDRESET;
317 if (initial_status & WD_T110)
318 rv |= WDIOF_OVERHEAT;
322 if (initial_status & 0x01)
323 rv |= WDIOF_CARDRESET;
325 if (initial_status & 0x04)
326 rv |= WDIOF_OVERHEAT;
329 if(put_user(rv, (int *) arg))
336 if ((supports_temp) && (mode_debug == 0))
339 rv = inb(current_readport);
340 spin_unlock(&io_lock);
341 if(put_user(rv, (int*) arg))
343 } else if(put_user(rv, (int*) arg))
347 case WDIOC_SETOPTIONS:
348 if (revision == PCWD_REVISION_C)
350 if(copy_from_user(&rv, (int*) arg, sizeof(int)))
353 if (rv & WDIOS_DISABLECARD)
356 outb_p(0xA5, current_readport + 3);
357 outb_p(0xA5, current_readport + 3);
358 cdat = inb_p(current_readport + 2);
359 spin_unlock(&io_lock);
360 if ((cdat & 0x10) == 0)
362 printk(KERN_INFO "pcwd: Could not disable card.\n");
369 if (rv & WDIOS_ENABLECARD)
372 outb_p(0x00, current_readport + 3);
373 cdat = inb_p(current_readport + 2);
374 spin_unlock(&io_lock);
377 printk(KERN_INFO "pcwd: Could not enable card.\n");
383 if (rv & WDIOS_TEMPPANIC)
390 case WDIOC_KEEPALIVE:
391 pcwd_send_heartbeat();
398 static ssize_t pcwd_write(struct file *file, const char *buf, size_t len,
401 /* Can't seek (pwrite) on this device */
402 if (ppos != &file->f_pos)
409 /* In case it was set long ago */
412 for (i = 0; i != len; i++) {
415 if (get_user(c, buf + i))
421 pcwd_send_heartbeat();
427 static int pcwd_open(struct inode *ino, struct file *filep)
429 switch (minor(ino->i_rdev)) {
431 if (!atomic_dec_and_test(&open_allowed) ) {
432 atomic_inc( &open_allowed );
435 __module_get(THIS_MODULE);
436 /* Enable the port */
437 if (revision == PCWD_REVISION_C) {
439 outb_p(0x00, current_readport + 3);
440 spin_unlock(&io_lock);
451 static ssize_t pcwd_read(struct file *file, char *buf, size_t count,
457 /* Can't seek (pread) on this device */
458 if (ppos != &file->f_pos)
460 switch(minor(file->f_dentry->d_inode->i_rdev))
464 * Convert metric to Fahrenheit, since this was
465 * the decided 'standard' for this return value.
468 c = inb(current_readport);
469 cp = (c * 9 / 5) + 32;
470 if(copy_to_user(buf, &cp, 1))
478 static int pcwd_close(struct inode *ino, struct file *filep)
480 if (minor(ino->i_rdev)==WATCHDOG_MINOR) {
482 /* Disable the board */
483 if (revision == PCWD_REVISION_C) {
485 outb_p(0xA5, current_readport + 3);
486 outb_p(0xA5, current_readport + 3);
487 spin_unlock(&io_lock);
489 atomic_inc( &open_allowed );
495 static inline void get_support(void)
497 if (inb(current_readport) != 0xF0)
501 static inline int get_revision(void)
503 int r = PCWD_REVISION_C;
506 if ((inb(current_readport + 2) == 0xFF) ||
507 (inb(current_readport + 3) == 0xFF))
509 spin_unlock(&io_lock);
514 static int __init send_command(int cmd)
518 outb_p(cmd, current_readport + 2);
521 i = inb(current_readport);
522 i = inb(current_readport);
527 static inline char *get_firmware(void)
529 int i, found = 0, count = 0, one, ten, hund, minor;
532 ret = kmalloc(6, GFP_KERNEL);
536 while((count < 3) && (!found)) {
537 outb_p(0x80, current_readport + 2);
538 i = inb(current_readport);
543 outb_p(0x00, current_readport + 2);
552 one = send_command(0x81);
553 ten = send_command(0x82);
554 hund = send_command(0x83);
555 minor = send_command(0x84);
556 sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
559 sprintf(ret, "ERROR");
564 static void debug_off(void)
566 outb_p(0x00, current_readport + 2);
570 static struct file_operations pcwd_fops = {
571 .owner = THIS_MODULE,
576 .release = pcwd_close,
579 static struct miscdevice pcwd_miscdev = {
585 static struct miscdevice temp_miscdev = {
591 static void __init pcwd_validate_timeout(void)
593 timeout_val = timeout * 2;
596 static int __init pcwatchdog_init(void)
600 pcwd_validate_timeout();
601 spin_lock_init(&io_lock);
603 revision = PCWD_REVISION_A;
605 printk(KERN_INFO "pcwd: v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER);
607 /* Initial variables */
611 initial_status = 0x0000;
614 for (i = 0; pcwd_ioports[i] != 0; i++) {
615 current_readport = pcwd_ioports[i];
617 if (pcwd_checkcard()) {
624 printk(KERN_INFO "pcwd: No card detected, or port not available.\n");
630 current_readport = PCWD_BLIND;
634 revision = get_revision();
636 if (revision == PCWD_REVISION_A)
637 printk(KERN_INFO "pcwd: PC Watchdog (REV.A) detected at port 0x%03x\n", current_readport);
638 else if (revision == PCWD_REVISION_C) {
639 firmware = get_firmware();
640 printk(KERN_INFO "pcwd: PC Watchdog (REV.C) detected at port 0x%03x (Firmware version: %s)\n",
641 current_readport, firmware);
644 /* Should NEVER happen, unless get_revision() fails. */
645 printk("pcwd: Unable to get revision.\n");
650 printk(KERN_INFO "pcwd: Temperature Option Detected.\n");
654 pcwd_showprevstate();
656 /* Disable the board */
657 if (revision == PCWD_REVISION_C) {
658 outb_p(0xA5, current_readport + 3);
659 outb_p(0xA5, current_readport + 3);
662 if (misc_register(&pcwd_miscdev))
666 if (misc_register(&temp_miscdev)) {
667 misc_deregister(&pcwd_miscdev);
672 if (revision == PCWD_REVISION_A) {
673 if (!request_region(current_readport, 2, "PCWD Rev.A (Berkshire)")) {
674 misc_deregister(&pcwd_miscdev);
676 misc_deregister(&pcwd_miscdev);
681 if (!request_region(current_readport, 4, "PCWD Rev.C (Berkshire)")) {
682 misc_deregister(&pcwd_miscdev);
684 misc_deregister(&pcwd_miscdev);
691 static void __exit pcwatchdog_exit(void)
693 misc_deregister(&pcwd_miscdev);
694 /* Disable the board */
695 if (revision == PCWD_REVISION_C) {
696 outb_p(0xA5, current_readport + 3);
697 outb_p(0xA5, current_readport + 3);
700 misc_deregister(&temp_miscdev);
702 release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
705 module_init(pcwatchdog_init);
706 module_exit(pcwatchdog_exit);
708 MODULE_LICENSE("GPL");