Blame view

drivers/watchdog/w83877f_wdt.c 10.1 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  /*
   *	W83877F Computer Watchdog Timer driver
   *
   *      Based on acquirewdt.c by Alan Cox,
   *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
   *
   *	This program is free software; you can redistribute it and/or
   *	modify it under the terms of the GNU General Public License
   *	as published by the Free Software Foundation; either version
   *	2 of the License, or (at your option) any later version.
   *
   *	The authors do NOT admit liability nor provide warranty for
   *	any of this software. This material is provided "AS-IS" in
   *      the hope that it may be useful for others.
   *
   *	(c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
   *
   *           4/19 - 2001      [Initial revision]
   *           9/27 - 2001      Added spinlocking
   *           4/12 - 2002      [rob@osinvestor.com] Eliminate extra comments
   *                            Eliminate fop_read
   *                            Eliminate extra spin_unlock
   *                            Added KERN_* tags to printks
   *                            add CONFIG_WATCHDOG_NOWAYOUT support
   *                            fix possible wdt_is_open race
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
26
27
28
29
   *                            changed watchdog_info to correctly reflect what
   *			      the driver offers
   *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
   *			      WDIOC_SETTIMEOUT,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
30
31
32
33
   *                            WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
   *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
   *                            added extra printk's for startup problems
   *                            use module_param
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
34
35
   *                            made timeout (the emulated heartbeat) a
   *			      module_param
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
   *                            made the keepalive ping an internal subroutine
   *
   *  This WDT driver is different from most other Linux WDT
   *  drivers in that the driver will ping the watchdog by itself,
   *  because this particular WDT has a very short timeout (1.6
   *  seconds) and it would be insane to count on any userspace
   *  daemon always getting scheduled within that time frame.
   */
  
  #include <linux/module.h>
  #include <linux/moduleparam.h>
  #include <linux/types.h>
  #include <linux/timer.h>
  #include <linux/jiffies.h>
  #include <linux/miscdevice.h>
  #include <linux/watchdog.h>
  #include <linux/fs.h>
  #include <linux/ioport.h>
  #include <linux/notifier.h>
  #include <linux/reboot.h>
  #include <linux/init.h>
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
57
58
  #include <linux/io.h>
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
  #include <asm/system.h>
  
  #define OUR_NAME "w83877f_wdt"
  #define PFX OUR_NAME ": "
  
  #define ENABLE_W83877F_PORT 0x3F0
  #define ENABLE_W83877F 0x87
  #define DISABLE_W83877F 0xAA
  #define WDT_PING 0x443
  #define WDT_REGISTER 0x14
  #define WDT_ENABLE 0x9C
  #define WDT_DISABLE 0x8C
  
  /*
   * The W83877F seems to be fixed at 1.6s timeout (at least on the
   * EMACS PC-104 board I'm using). If we reset the watchdog every
   * ~250ms we should be safe.  */
  
  #define WDT_INTERVAL (HZ/4+1)
  
  /*
   * We must not require too good response from the userspace daemon.
   * Here we require the userspace daemon to send us a heartbeat
   * char to /dev/watchdog every 30 seconds.
   */
  
  #define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
86
87
  /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
  static int timeout = WATCHDOG_TIMEOUT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
88
  module_param(timeout, int, 0);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
89
90
91
  MODULE_PARM_DESC(timeout,
  	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
  				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
92

4bfdf3783   Andrey Panin   [PATCH] consolida...
93
  static int nowayout = WATCHDOG_NOWAYOUT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
94
  module_param(nowayout, int, 0);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
95
96
97
  MODULE_PARM_DESC(nowayout,
  		"Watchdog cannot be stopped once started (default="
  				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
98
99
  
  static void wdt_timer_ping(unsigned long);
82eb7c505   Jiri Slaby   [WATCHDOG] timers...
100
  static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
101
102
103
  static unsigned long next_heartbeat;
  static unsigned long wdt_is_open;
  static char wdt_expect_close;
c7dfd0cca   Alexey Dobriyan   [WATCHDOG] spin_l...
104
  static DEFINE_SPINLOCK(wdt_spinlock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
105
106
107
108
109
110
111
112
113
114
  
  /*
   *	Whack the dog
   */
  
  static void wdt_timer_ping(unsigned long data)
  {
  	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
  	 * we agree to ping the WDT
  	 */
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
115
  	if (time_before(jiffies, next_heartbeat)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
116
117
118
119
120
121
122
  		/* Ping the WDT */
  		spin_lock(&wdt_spinlock);
  
  		/* Ping the WDT by reading from WDT_PING */
  		inb_p(WDT_PING);
  
  		/* Re-set the timer interval */
82eb7c505   Jiri Slaby   [WATCHDOG] timers...
123
  		mod_timer(&timer, jiffies + WDT_INTERVAL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
124
125
  
  		spin_unlock(&wdt_spinlock);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
126
127
128
129
  	} else
  		printk(KERN_WARNING PFX
  			"Heartbeat lost! Will not ping the watchdog
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
  }
  
  /*
   * Utility routines
   */
  
  static void wdt_change(int writeval)
  {
  	unsigned long flags;
  	spin_lock_irqsave(&wdt_spinlock, flags);
  
  	/* buy some time */
  	inb_p(WDT_PING);
  
  	/* make W83877F available */
  	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
  	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
  
  	/* enable watchdog */
  	outb_p(WDT_REGISTER,    ENABLE_W83877F_PORT);
  	outb_p(writeval,        ENABLE_W83877F_PORT+1);
  
  	/* lock the W8387FF away */
  	outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT);
  
  	spin_unlock_irqrestore(&wdt_spinlock, flags);
  }
  
  static void wdt_startup(void)
  {
  	next_heartbeat = jiffies + (timeout * HZ);
  
  	/* Start the timer */
82eb7c505   Jiri Slaby   [WATCHDOG] timers...
163
  	mod_timer(&timer, jiffies + WDT_INTERVAL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  
  	wdt_change(WDT_ENABLE);
  
  	printk(KERN_INFO PFX "Watchdog timer is now enabled.
  ");
  }
  
  static void wdt_turnoff(void)
  {
  	/* Stop the timer */
  	del_timer(&timer);
  
  	wdt_change(WDT_DISABLE);
  
  	printk(KERN_INFO PFX "Watchdog timer is now disabled...
  ");
  }
  
  static void wdt_keepalive(void)
  {
  	/* user land ping */
  	next_heartbeat = jiffies + (timeout * HZ);
  }
  
  /*
   * /dev/watchdog handling
   */
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
191
192
  static ssize_t fop_write(struct file *file, const char __user *buf,
  						size_t count, loff_t *ppos)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
193
194
  {
  	/* See if we got the magic character 'V' and reload the timer */
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
195
196
  	if (count) {
  		if (!nowayout) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
197
  			size_t ofs;
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
198
199
  			/* note: just in case someone wrote the magic
  			   character five months ago... */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
200
  			wdt_expect_close = 0;
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
201
202
203
  			/* scan to see whether or not we got the
  			   magic character */
  			for (ofs = 0; ofs != count; ofs++) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
204
205
206
207
208
209
210
211
212
213
214
215
216
  				char c;
  				if (get_user(c, buf + ofs))
  					return -EFAULT;
  				if (c == 'V')
  					wdt_expect_close = 42;
  			}
  		}
  
  		/* someone wrote to us, we should restart timer */
  		wdt_keepalive();
  	}
  	return count;
  }
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
217
  static int fop_open(struct inode *inode, struct file *file)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
218
219
  {
  	/* Just in case we're already talking to someone... */
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
220
  	if (test_and_set_bit(0, &wdt_is_open))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
221
222
223
224
225
226
  		return -EBUSY;
  
  	/* Good, fire up the show */
  	wdt_startup();
  	return nonseekable_open(inode, file);
  }
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
227
  static int fop_close(struct inode *inode, struct file *file)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
228
  {
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
229
  	if (wdt_expect_close == 42)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
230
231
232
  		wdt_turnoff();
  	else {
  		del_timer(&timer);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
233
234
235
  		printk(KERN_CRIT PFX
  		  "device file closed unexpectedly. Will not stop the WDT!
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
237
238
239
240
  	}
  	clear_bit(0, &wdt_is_open);
  	wdt_expect_close = 0;
  	return 0;
  }
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
241
  static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
242
243
244
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
245
246
247
  	static const struct watchdog_info ident = {
  		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
  							| WDIOF_MAGICCLOSE,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
248
249
250
  		.firmware_version = 1,
  		.identity = "W83877F",
  	};
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
251
  	switch (cmd) {
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
252
253
254
255
256
  	case WDIOC_GETSUPPORT:
  		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
  	case WDIOC_GETSTATUS:
  	case WDIOC_GETBOOTSTATUS:
  		return put_user(0, p);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
257
  	case WDIOC_SETOPTIONS:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
258
  	{
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
259
  		int new_options, retval = -EINVAL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
260

c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
261
262
  		if (get_user(new_options, p))
  			return -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
263

c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
264
265
266
  		if (new_options & WDIOS_DISABLECARD) {
  			wdt_turnoff();
  			retval = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
267
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
268

c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
269
270
271
272
  		if (new_options & WDIOS_ENABLECARD) {
  			wdt_startup();
  			retval = 0;
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
273

c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
274
275
  		return retval;
  	}
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
276
277
278
  	case WDIOC_KEEPALIVE:
  		wdt_keepalive();
  		return 0;
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
279
280
281
  	case WDIOC_SETTIMEOUT:
  	{
  		int new_timeout;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
282

c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
283
284
285
286
287
288
289
290
291
292
293
294
295
  		if (get_user(new_timeout, p))
  			return -EFAULT;
  
  		/* arbitrary upper limit */
  		if (new_timeout < 1 || new_timeout > 3600)
  			return -EINVAL;
  
  		timeout = new_timeout;
  		wdt_keepalive();
  		/* Fall through */
  	}
  	case WDIOC_GETTIMEOUT:
  		return put_user(timeout, p);
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
296
297
  	default:
  		return -ENOTTY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
298
299
  	}
  }
62322d255   Arjan van de Ven   [PATCH] make more...
300
  static const struct file_operations wdt_fops = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
301
302
303
304
305
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.write		= fop_write,
  	.open		= fop_open,
  	.release	= fop_close,
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
306
  	.unlocked_ioctl	= fop_ioctl,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
  };
  
  static struct miscdevice wdt_miscdev = {
  	.minor	= WATCHDOG_MINOR,
  	.name	= "watchdog",
  	.fops	= &wdt_fops,
  };
  
  /*
   *	Notifier for system down
   */
  
  static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
  	void *unused)
  {
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
322
  	if (code == SYS_DOWN || code == SYS_HALT)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
323
324
325
326
327
328
329
330
  		wdt_turnoff();
  	return NOTIFY_DONE;
  }
  
  /*
   *	The WDT needs to learn about soft shutdowns in order to
   *	turn the timebomb registers off.
   */
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
331
  static struct notifier_block wdt_notifier = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
332
333
334
335
336
337
338
339
340
341
342
  	.notifier_call = wdt_notify_sys,
  };
  
  static void __exit w83877f_wdt_unload(void)
  {
  	wdt_turnoff();
  
  	/* Deregister */
  	misc_deregister(&wdt_miscdev);
  
  	unregister_reboot_notifier(&wdt_notifier);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
343
344
  	release_region(WDT_PING, 1);
  	release_region(ENABLE_W83877F_PORT, 2);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
345
346
347
348
349
  }
  
  static int __init w83877f_wdt_init(void)
  {
  	int rc = -EBUSY;
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
350
  	if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
351
  		timeout = WATCHDOG_TIMEOUT;
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
352
353
354
355
  		printk(KERN_INFO PFX
  			"timeout value must be 1 <= x <= 3600, using %d
  ",
  							timeout);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
356
  	}
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
357
  	if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
358
359
360
361
362
363
  		printk(KERN_ERR PFX "I/O address 0x%04x already in use
  ",
  			ENABLE_W83877F_PORT);
  		rc = -EIO;
  		goto err_out;
  	}
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
364
  	if (!request_region(WDT_PING, 1, "W8387FF WDT")) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
365
366
367
368
369
370
  		printk(KERN_ERR PFX "I/O address 0x%04x already in use
  ",
  			WDT_PING);
  		rc = -EIO;
  		goto err_out_region1;
  	}
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
371
  	rc = register_reboot_notifier(&wdt_notifier);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
372
373
374
375
  	if (rc) {
  		printk(KERN_ERR PFX
  			"cannot register reboot notifier (err=%d)
  ", rc);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
376
377
  		goto err_out_region2;
  	}
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
378
  	rc = misc_register(&wdt_miscdev);
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
379
380
381
382
383
  	if (rc) {
  		printk(KERN_ERR PFX
  			"cannot register miscdev on minor=%d (err=%d)
  ",
  							wdt_miscdev.minor, rc);
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
384
  		goto err_out_reboot;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
385
  	}
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
386
387
388
  	printk(KERN_INFO PFX
  	  "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)
  ",
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
389
390
391
  		timeout, nowayout);
  
  	return 0;
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
392
393
  err_out_reboot:
  	unregister_reboot_notifier(&wdt_notifier);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
394
  err_out_region2:
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
395
  	release_region(WDT_PING, 1);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
396
  err_out_region1:
c1cfd1a2f   Alan Cox   [WATCHDOG 51/57] ...
397
  	release_region(ENABLE_W83877F_PORT, 2);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
398
399
400
401
402
403
404
405
406
407
408
  err_out:
  	return rc;
  }
  
  module_init(w83877f_wdt_init);
  module_exit(w83877f_wdt_unload);
  
  MODULE_AUTHOR("Scott and Bill Jennings");
  MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);