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