File: /usr/src/linux/drivers/char/pcwd.c
1 /*
2 * PC Watchdog Driver
3 * by Ken Hollis (khollis@bitgate.com)
4 *
5 * Permission granted from Simon Machell (73244.1270@compuserve.com)
6 * Written for the Linux Kernel, and GPLed by Ken Hollis
7 *
8 * 960107 Added request_region routines, modulized the whole thing.
9 * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added
10 * WD_TIMEOUT define.
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
26 * routine.
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 */
44
45 #include <linux/module.h>
46
47 #include <linux/types.h>
48 #include <linux/errno.h>
49 #include <linux/sched.h>
50 #include <linux/tty.h>
51 #include <linux/timer.h>
52 #include <linux/config.h>
53 #include <linux/kernel.h>
54 #include <linux/wait.h>
55 #include <linux/string.h>
56 #include <linux/slab.h>
57 #include <linux/ioport.h>
58 #include <linux/delay.h>
59 #include <linux/miscdevice.h>
60 #include <linux/fs.h>
61 #include <linux/mm.h>
62 #include <linux/watchdog.h>
63 #include <linux/init.h>
64 #include <linux/proc_fs.h>
65 #include <linux/spinlock.h>
66 #include <linux/smp_lock.h>
67
68 #include <asm/uaccess.h>
69 #include <asm/io.h>
70
71 /*
72 * These are the auto-probe addresses available.
73 *
74 * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350.
75 * Revision A has an address range of 2 addresses, while Revision C has 3.
76 */
77 static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 };
78
79 #define WD_VER "1.10 (06/05/99)"
80
81 /*
82 * It should be noted that PCWD_REVISION_B was removed because A and B
83 * are essentially the same types of card, with the exception that B
84 * has temperature reporting. Since I didn't receive a Rev.B card,
85 * the Rev.B card is not supported. (It's a good thing too, as they
86 * are no longer in production.)
87 */
88 #define PCWD_REVISION_A 1
89 #define PCWD_REVISION_C 2
90
91 #define WD_TIMEOUT 3 /* 1 1/2 seconds for a timeout */
92
93 /*
94 * These are the defines for the PC Watchdog card, revision A.
95 */
96 #define WD_WDRST 0x01 /* Previously reset state */
97 #define WD_T110 0x02 /* Temperature overheat sense */
98 #define WD_HRTBT 0x04 /* Heartbeat sense */
99 #define WD_RLY2 0x08 /* External relay triggered */
100 #define WD_SRLY2 0x80 /* Software external relay triggered */
101
102 static int current_readport, revision, temp_panic;
103 static int is_open, initial_status, supports_temp, mode_debug;
104 static spinlock_t io_lock;
105
106 /*
107 * PCWD_CHECKCARD
108 *
109 * This routine checks the "current_readport" to see if the card lies there.
110 * If it does, it returns accordingly.
111 */
112 static int __init pcwd_checkcard(void)
113 {
114 int card_dat, prev_card_dat, found = 0, count = 0, done = 0;
115
116 /* As suggested by Alan Cox - this is a safety measure. */
117 if (check_region(current_readport, 4)) {
118 printk("pcwd: Port 0x%x unavailable.\n", current_readport);
119 return 0;
120 }
121
122 card_dat = 0x00;
123 prev_card_dat = 0x00;
124
125 prev_card_dat = inb(current_readport);
126 if (prev_card_dat == 0xFF)
127 return 0;
128
129 while(count < WD_TIMEOUT) {
130
131 /* Read the raw card data from the port, and strip off the
132 first 4 bits */
133
134 card_dat = inb_p(current_readport);
135 card_dat &= 0x000F;
136
137 /* Sleep 1/2 second (or 500000 microseconds :) */
138
139 mdelay(500);
140 done = 0;
141
142 /* If there's a heart beat in both instances, then this means we
143 found our card. This also means that either the card was
144 previously reset, or the computer was power-cycled. */
145
146 if ((card_dat & WD_HRTBT) && (prev_card_dat & WD_HRTBT) &&
147 (!done)) {
148 found = 1;
149 done = 1;
150 break;
151 }
152
153 /* If the card data is exactly the same as the previous card data,
154 it's safe to assume that we should check again. The manual says
155 that the heart beat will change every second (or the bit will
156 toggle), and this can be used to see if the card is there. If
157 the card was powered up with a cold boot, then the card will
158 not start blinking until 2.5 minutes after a reboot, so this
159 bit will stay at 1. */
160
161 if ((card_dat == prev_card_dat) && (!done)) {
162 count++;
163 done = 1;
164 }
165
166 /* If the card data is toggling any bits, this means that the heart
167 beat was detected, or something else about the card is set. */
168
169 if ((card_dat != prev_card_dat) && (!done)) {
170 done = 1;
171 found = 1;
172 break;
173 }
174
175 /* Otherwise something else strange happened. */
176
177 if (!done)
178 count++;
179 }
180
181 return((found) ? 1 : 0);
182 }
183
184 void pcwd_showprevstate(void)
185 {
186 int card_status = 0x0000;
187
188 if (revision == PCWD_REVISION_A)
189 initial_status = card_status = inb(current_readport);
190 else {
191 initial_status = card_status = inb(current_readport + 1);
192 outb_p(0x00, current_readport + 1); /* clear reset status */
193 }
194
195 if (revision == PCWD_REVISION_A) {
196 if (card_status & WD_WDRST)
197 printk("pcwd: Previous reboot was caused by the card.\n");
198
199 if (card_status & WD_T110) {
200 printk("pcwd: Card senses a CPU Overheat. Panicking!\n");
201 panic("pcwd: CPU Overheat.\n");
202 }
203
204 if ((!(card_status & WD_WDRST)) &&
205 (!(card_status & WD_T110)))
206 printk("pcwd: Cold boot sense.\n");
207 } else {
208 if (card_status & 0x01)
209 printk("pcwd: Previous reboot was caused by the card.\n");
210
211 if (card_status & 0x04) {
212 printk("pcwd: Card senses a CPU Overheat. Panicking!\n");
213 panic("pcwd: CPU Overheat.\n");
214 }
215
216 if ((!(card_status & 0x01)) &&
217 (!(card_status & 0x04)))
218 printk("pcwd: Cold boot sense.\n");
219 }
220 }
221
222 static void pcwd_send_heartbeat(void)
223 {
224 int wdrst_stat;
225
226 wdrst_stat = inb_p(current_readport);
227 wdrst_stat &= 0x0F;
228
229 wdrst_stat |= WD_WDRST;
230
231 if (revision == PCWD_REVISION_A)
232 outb_p(wdrst_stat, current_readport + 1);
233 else
234 outb_p(wdrst_stat, current_readport);
235 }
236
237 static int pcwd_ioctl(struct inode *inode, struct file *file,
238 unsigned int cmd, unsigned long arg)
239 {
240 int i, cdat, rv;
241 static struct watchdog_info ident=
242 {
243 WDIOF_OVERHEAT|WDIOF_CARDRESET,
244 1,
245 "PCWD"
246 };
247
248 switch(cmd) {
249 default:
250 return -ENOTTY;
251
252 case WDIOC_GETSUPPORT:
253 i = copy_to_user((void*)arg, &ident, sizeof(ident));
254 return i ? -EFAULT : 0;
255
256 case WDIOC_GETSTATUS:
257 spin_lock(&io_lock);
258 if (revision == PCWD_REVISION_A)
259 cdat = inb(current_readport);
260 else
261 cdat = inb(current_readport + 1 );
262 spin_unlock(&io_lock);
263 rv = 0;
264
265 if (revision == PCWD_REVISION_A)
266 {
267 if (cdat & WD_WDRST)
268 rv |= WDIOF_CARDRESET;
269
270 if (cdat & WD_T110)
271 {
272 rv |= WDIOF_OVERHEAT;
273
274 if (temp_panic)
275 panic("pcwd: Temperature overheat trip!\n");
276 }
277 }
278 else
279 {
280 if (cdat & 0x01)
281 rv |= WDIOF_CARDRESET;
282
283 if (cdat & 0x04)
284 {
285 rv |= WDIOF_OVERHEAT;
286
287 if (temp_panic)
288 panic("pcwd: Temperature overheat trip!\n");
289 }
290 }
291
292 if(put_user(rv, (int *) arg))
293 return -EFAULT;
294 return 0;
295
296 case WDIOC_GETBOOTSTATUS:
297 rv = 0;
298
299 if (revision == PCWD_REVISION_A)
300 {
301 if (initial_status & WD_WDRST)
302 rv |= WDIOF_CARDRESET;
303
304 if (initial_status & WD_T110)
305 rv |= WDIOF_OVERHEAT;
306 }
307 else
308 {
309 if (initial_status & 0x01)
310 rv |= WDIOF_CARDRESET;
311
312 if (initial_status & 0x04)
313 rv |= WDIOF_OVERHEAT;
314 }
315
316 if(put_user(rv, (int *) arg))
317 return -EFAULT;
318 return 0;
319
320 case WDIOC_GETTEMP:
321
322 rv = 0;
323 if ((supports_temp) && (mode_debug == 0))
324 {
325 spin_lock(&io_lock);
326 rv = inb(current_readport);
327 spin_unlock(&io_lock);
328 if(put_user(rv, (int*) arg))
329 return -EFAULT;
330 } else if(put_user(rv, (int*) arg))
331 return -EFAULT;
332 return 0;
333
334 case WDIOC_SETOPTIONS:
335 if (revision == PCWD_REVISION_C)
336 {
337 if(copy_from_user(&rv, (int*) arg, sizeof(int)))
338 return -EFAULT;
339
340 if (rv & WDIOS_DISABLECARD)
341 {
342 spin_lock(&io_lock);
343 outb_p(0xA5, current_readport + 3);
344 outb_p(0xA5, current_readport + 3);
345 cdat = inb_p(current_readport + 2);
346 spin_unlock(&io_lock);
347 if ((cdat & 0x10) == 0)
348 {
349 printk("pcwd: Could not disable card.\n");
350 return -EIO;
351 }
352
353 return 0;
354 }
355
356 if (rv & WDIOS_ENABLECARD)
357 {
358 spin_lock(&io_lock);
359 outb_p(0x00, current_readport + 3);
360 cdat = inb_p(current_readport + 2);
361 spin_unlock(&io_lock);
362 if (cdat & 0x10)
363 {
364 printk("pcwd: Could not enable card.\n");
365 return -EIO;
366 }
367 return 0;
368 }
369
370 if (rv & WDIOS_TEMPPANIC)
371 {
372 temp_panic = 1;
373 }
374 }
375 return -EINVAL;
376
377 case WDIOC_KEEPALIVE:
378 pcwd_send_heartbeat();
379 return 0;
380 }
381
382 return 0;
383 }
384
385 static ssize_t pcwd_write(struct file *file, const char *buf, size_t len,
386 loff_t *ppos)
387 {
388 /* Can't seek (pwrite) on this device */
389 if (ppos != &file->f_pos)
390 return -ESPIPE;
391
392 if (len)
393 {
394 pcwd_send_heartbeat();
395 return 1;
396 }
397 return 0;
398 }
399
400 static int pcwd_open(struct inode *ino, struct file *filep)
401 {
402 switch (MINOR(ino->i_rdev))
403 {
404 case WATCHDOG_MINOR:
405 if (is_open)
406 return -EBUSY;
407 MOD_INC_USE_COUNT;
408 /* Enable the port */
409 if (revision == PCWD_REVISION_C)
410 {
411 spin_lock(&io_lock);
412 outb_p(0x00, current_readport + 3);
413 spin_unlock(&io_lock);
414 }
415 is_open = 1;
416 return(0);
417 case TEMP_MINOR:
418 return(0);
419 default:
420 return (-ENODEV);
421 }
422 }
423
424 static ssize_t pcwd_read(struct file *file, char *buf, size_t count,
425 loff_t *ppos)
426 {
427 unsigned short c;
428 unsigned char cp;
429
430 /* Can't seek (pread) on this device */
431 if (ppos != &file->f_pos)
432 return -ESPIPE;
433 switch(MINOR(file->f_dentry->d_inode->i_rdev))
434 {
435 case TEMP_MINOR:
436 /*
437 * Convert metric to Fahrenheit, since this was
438 * the decided 'standard' for this return value.
439 */
440
441 c = inb(current_readport);
442 cp = (c * 9 / 5) + 32;
443 if(copy_to_user(buf, &cp, 1))
444 return -EFAULT;
445 return 1;
446 default:
447 return -EINVAL;
448 }
449 }
450
451 static int pcwd_close(struct inode *ino, struct file *filep)
452 {
453 if (MINOR(ino->i_rdev)==WATCHDOG_MINOR)
454 {
455 lock_kernel();
456 is_open = 0;
457 #ifndef CONFIG_WATCHDOG_NOWAYOUT
458 /* Disable the board */
459 if (revision == PCWD_REVISION_C) {
460 spin_lock(&io_lock);
461 outb_p(0xA5, current_readport + 3);
462 outb_p(0xA5, current_readport + 3);
463 spin_unlock(&io_lock);
464 }
465 #endif
466 unlock_kernel();
467 }
468 return 0;
469 }
470
471 static inline void get_support(void)
472 {
473 if (inb(current_readport) != 0xF0)
474 supports_temp = 1;
475 }
476
477 static inline int get_revision(void)
478 {
479 int r = PCWD_REVISION_C;
480
481 spin_lock(&io_lock);
482 if ((inb(current_readport + 2) == 0xFF) ||
483 (inb(current_readport + 3) == 0xFF))
484 r=PCWD_REVISION_A;
485 spin_unlock(&io_lock);
486
487 return r;
488 }
489
490 static int __init send_command(int cmd)
491 {
492 int i;
493
494 outb_p(cmd, current_readport + 2);
495 mdelay(1);
496
497 i = inb(current_readport);
498 i = inb(current_readport);
499
500 return(i);
501 }
502
503 static inline char *get_firmware(void)
504 {
505 int i, found = 0, count = 0, one, ten, hund, minor;
506 char *ret;
507
508 ret = kmalloc(6, GFP_KERNEL);
509 if(ret == NULL)
510 return NULL;
511
512 while((count < 3) && (!found)) {
513 outb_p(0x80, current_readport + 2);
514 i = inb(current_readport);
515
516 if (i == 0x00)
517 found = 1;
518 else if (i == 0xF3)
519 outb_p(0x00, current_readport + 2);
520
521 udelay(400L);
522 count++;
523 }
524
525 if (found) {
526 mode_debug = 1;
527
528 one = send_command(0x81);
529 ten = send_command(0x82);
530 hund = send_command(0x83);
531 minor = send_command(0x84);
532 sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
533 }
534 else
535 sprintf(ret, "ERROR");
536
537 return(ret);
538 }
539
540 static void debug_off(void)
541 {
542 outb_p(0x00, current_readport + 2);
543 mode_debug = 0;
544 }
545
546 static struct file_operations pcwd_fops = {
547 owner: THIS_MODULE,
548 read: pcwd_read,
549 write: pcwd_write,
550 ioctl: pcwd_ioctl,
551 open: pcwd_open,
552 release: pcwd_close,
553 };
554
555 static struct miscdevice pcwd_miscdev = {
556 WATCHDOG_MINOR,
557 "watchdog",
558 &pcwd_fops
559 };
560
561 static struct miscdevice temp_miscdev = {
562 TEMP_MINOR,
563 "temperature",
564 &pcwd_fops
565 };
566
567 static int __init pcwatchdog_init(void)
568 {
569 int i, found = 0;
570 spin_lock_init(&io_lock);
571
572 revision = PCWD_REVISION_A;
573
574 printk("pcwd: v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER);
575
576 /* Initial variables */
577 is_open = 0;
578 supports_temp = 0;
579 mode_debug = 0;
580 temp_panic = 0;
581 initial_status = 0x0000;
582
583 #ifndef PCWD_BLIND
584 for (i = 0; pcwd_ioports[i] != 0; i++) {
585 current_readport = pcwd_ioports[i];
586
587 if (pcwd_checkcard()) {
588 found = 1;
589 break;
590 }
591 }
592
593 if (!found) {
594 printk("pcwd: No card detected, or port not available.\n");
595 return(-EIO);
596 }
597 #endif
598
599 #ifdef PCWD_BLIND
600 current_readport = PCWD_BLIND;
601 #endif
602
603 get_support();
604 revision = get_revision();
605
606 if (revision == PCWD_REVISION_A)
607 printk("pcwd: PC Watchdog (REV.A) detected at port 0x%03x\n", current_readport);
608 else if (revision == PCWD_REVISION_C)
609 printk("pcwd: PC Watchdog (REV.C) detected at port 0x%03x (Firmware version: %s)\n",
610 current_readport, get_firmware());
611 else {
612 /* Should NEVER happen, unless get_revision() fails. */
613 printk("pcwd: Unable to get revision.\n");
614 return -1;
615 }
616
617 if (supports_temp)
618 printk("pcwd: Temperature Option Detected.\n");
619
620 debug_off();
621
622 pcwd_showprevstate();
623
624 /* Disable the board */
625 if (revision == PCWD_REVISION_C) {
626 outb_p(0xA5, current_readport + 3);
627 outb_p(0xA5, current_readport + 3);
628 }
629
630 if (revision == PCWD_REVISION_A)
631 request_region(current_readport, 2, "PCWD Rev.A (Berkshire)");
632 else
633 request_region(current_readport, 4, "PCWD Rev.C (Berkshire)");
634
635 misc_register(&pcwd_miscdev);
636
637 if (supports_temp)
638 misc_register(&temp_miscdev);
639
640 return 0;
641 }
642
643 static void __exit pcwatchdog_exit(void)
644 {
645 misc_deregister(&pcwd_miscdev);
646 /* Disable the board */
647 if (revision == PCWD_REVISION_C) {
648 outb_p(0xA5, current_readport + 3);
649 outb_p(0xA5, current_readport + 3);
650 }
651 if (supports_temp)
652 misc_deregister(&temp_miscdev);
653
654 release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
655 }
656
657 module_init(pcwatchdog_init);
658 module_exit(pcwatchdog_exit);
659
660 MODULE_LICENSE("GPL");
661
662 EXPORT_NO_SYMBOLS;
663