1 /*
2  *	Acquire Single Board Computer Watchdog Timer driver for Linux 2.1.x
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 #include <linux/config.h>
26 #include <linux/module.h>
27 #include <linux/version.h>
28 #include <linux/types.h>
29 #include <linux/errno.h>
30 #include <linux/kernel.h>
31 #include <linux/sched.h>
32 #include <linux/miscdevice.h>
33 #include <linux/watchdog.h>
34 #include <linux/slab.h>
35 #include <linux/ioport.h>
36 #include <linux/fcntl.h>
37 #include <asm/io.h>
38 #include <asm/uaccess.h>
39 #include <asm/system.h>
40 #include <linux/notifier.h>
41 #include <linux/reboot.h>
42 #include <linux/init.h>
43 #include <linux/spinlock.h>
44 #include <linux/smp_lock.h>
45 
46 static int acq_is_open;
47 static spinlock_t acq_lock;
48 static int expect_close = 0;
49 
50 /*
51  *	You must set these - there is no sane way to probe for this board.
52  */
53 
54 #define WDT_STOP 0x43
55 #define WDT_START 0x443
56 
57 #ifdef CONFIG_WATCHDOG_NOWAYOUT
58 static int nowayout = 1;
59 #else
60 static int nowayout = 0;
61 #endif
62 
63 MODULE_PARM(nowayout,"i");
64 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
65 
66 /*
67  *	Kernel methods.
68  */
69 
70 
acq_ping(void)71 static void acq_ping(void)
72 {
73 	/* Write a watchdog value */
74 	inb_p(WDT_START);
75 }
76 
acq_write(struct file * file,const char * buf,size_t count,loff_t * ppos)77 static ssize_t acq_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
78 {
79 	/*  Can't seek (pwrite) on this device  */
80 	if (ppos != &file->f_pos)
81 		return -ESPIPE;
82 
83 	if(count)
84 	{
85 		if (!nowayout)
86 		{
87 			size_t i;
88 
89 			expect_close = 0;
90 
91 			for (i = 0; i != count; i++) {
92 				char c;
93 				if (get_user(c, buf + i))
94 					return -EFAULT;
95 				if (c == 'V')
96 					expect_close = 1;
97 			}
98 		}
99 
100 		acq_ping();
101 		return 1;
102 	}
103 	return 0;
104 }
105 
acq_read(struct file * file,char * buf,size_t count,loff_t * ppos)106 static ssize_t acq_read(struct file *file, char *buf, size_t count, loff_t *ppos)
107 {
108 	return -EINVAL;
109 }
110 
111 
112 
acq_ioctl(struct inode * inode,struct file * file,unsigned int cmd,unsigned long arg)113 static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
114 	unsigned long arg)
115 {
116 	static struct watchdog_info ident=
117 	{
118 		WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 1, "Acquire WDT"
119 	};
120 
121 	switch(cmd)
122 	{
123 	case WDIOC_GETSUPPORT:
124 	  if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident)))
125 	    return -EFAULT;
126 	  break;
127 
128 	case WDIOC_GETSTATUS:
129 	  if (copy_to_user((int *)arg, &acq_is_open,  sizeof(int)))
130 	    return -EFAULT;
131 	  break;
132 
133 	case WDIOC_KEEPALIVE:
134 	  acq_ping();
135 	  break;
136 
137 	default:
138 	  return -ENOTTY;
139 	}
140 	return 0;
141 }
142 
acq_open(struct inode * inode,struct file * file)143 static int acq_open(struct inode *inode, struct file *file)
144 {
145 	switch(MINOR(inode->i_rdev))
146 	{
147 		case WATCHDOG_MINOR:
148 			spin_lock(&acq_lock);
149 			if(acq_is_open)
150 			{
151 				spin_unlock(&acq_lock);
152 				return -EBUSY;
153 			}
154 			if (nowayout) {
155 				MOD_INC_USE_COUNT;
156 			}
157 			/*
158 			 *	Activate
159 			 */
160 			acq_is_open=1;
161 			inb_p(WDT_START);
162 			spin_unlock(&acq_lock);
163 			return 0;
164 		default:
165 			return -ENODEV;
166 	}
167 }
168 
acq_close(struct inode * inode,struct file * file)169 static int acq_close(struct inode *inode, struct file *file)
170 {
171 	lock_kernel();
172 	if(MINOR(inode->i_rdev)==WATCHDOG_MINOR)
173 	{
174 		spin_lock(&acq_lock);
175 		if (expect_close)
176 		{
177 			inb_p(WDT_STOP);
178 		}
179 		else
180 		{
181 			printk(KERN_CRIT "WDT closed unexpectedly.  WDT will not stop!\n");
182 		}
183 		acq_is_open=0;
184 		spin_unlock(&acq_lock);
185 	}
186 	unlock_kernel();
187 	return 0;
188 }
189 
190 /*
191  *	Notifier for system down
192  */
193 
acq_notify_sys(struct notifier_block * this,unsigned long code,void * unused)194 static int acq_notify_sys(struct notifier_block *this, unsigned long code,
195 	void *unused)
196 {
197 	if(code==SYS_DOWN || code==SYS_HALT)
198 	{
199 		/* Turn the card off */
200 		inb_p(WDT_STOP);
201 	}
202 	return NOTIFY_DONE;
203 }
204 
205 /*
206  *	Kernel Interfaces
207  */
208 
209 
210 static struct file_operations acq_fops = {
211 	owner:		THIS_MODULE,
212 	read:		acq_read,
213 	write:		acq_write,
214 	ioctl:		acq_ioctl,
215 	open:		acq_open,
216 	release:	acq_close,
217 };
218 
219 static struct miscdevice acq_miscdev=
220 {
221 	WATCHDOG_MINOR,
222 	"watchdog",
223 	&acq_fops
224 };
225 
226 
227 /*
228  *	The WDT card needs to learn about soft shutdowns in order to
229  *	turn the timebomb registers off.
230  */
231 
232 static struct notifier_block acq_notifier=
233 {
234 	acq_notify_sys,
235 	NULL,
236 	0
237 };
238 
acq_init(void)239 static int __init acq_init(void)
240 {
241 	printk("WDT driver for Acquire single board computer initialising.\n");
242 
243 	spin_lock_init(&acq_lock);
244 	if (misc_register(&acq_miscdev))
245 		return -ENODEV;
246 	request_region(WDT_STOP, 1, "Acquire WDT");
247 	request_region(WDT_START, 1, "Acquire WDT");
248 	register_reboot_notifier(&acq_notifier);
249 	return 0;
250 }
251 
acq_exit(void)252 static void __exit acq_exit(void)
253 {
254 	misc_deregister(&acq_miscdev);
255 	unregister_reboot_notifier(&acq_notifier);
256 	release_region(WDT_STOP,1);
257 	release_region(WDT_START,1);
258 }
259 
260 module_init(acq_init);
261 module_exit(acq_exit);
262 
263 MODULE_LICENSE("GPL");
264 EXPORT_NO_SYMBOLS;
265