Blame view

drivers/watchdog/wdt.c 15.7 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
  /*
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
2
   *	Industrial Computer Source WDT501 driver
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
3
   *
29fa0586d   Alan Cox   [PATCH] Switch al...
4
5
   *	(c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
   *						All Rights Reserved.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   *
   *	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.
   *
   *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
   *	warranty for any of this software. This material is provided
   *	"AS-IS" and at no charge.
   *
   *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
   *
   *	Release 0.10.
   *
   *	Fixes
   *		Dave Gregorich	:	Modularisation and minor bugs
   *		Alan Cox	:	Added the watchdog ioctl() stuff
   *		Alan Cox	:	Fixed the reboot problem (as noted by
   *					Matt Crocker).
   *		Alan Cox	:	Added wdt= boot option
   *		Alan Cox	:	Cleaned up copy/user stuff
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
27
28
29
30
   *		Tim Hockin	:	Added insmod parameters, comment
   *					cleanup, parameterized timeout
   *		Tigran Aivazian	:	Restructured wdt_init() to handle
   *					failures
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
31
32
33
   *		Joel Becker	:	Added WDIOC_GET/SETTIMEOUT
   *		Matt Domsch	:	Added nowayout module option
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
34
35
36
37
38
39
40
41
42
43
44
  #include <linux/interrupt.h>
  #include <linux/module.h>
  #include <linux/moduleparam.h>
  #include <linux/types.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>
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
45
46
  #include <linux/io.h>
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
47

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
  #include <asm/system.h>
  #include "wd501p.h"
  
  static unsigned long wdt_is_open;
  static char expect_close;
  
  /*
   *	Module parameters
   */
  
  #define WD_TIMO 60			/* Default heartbeat = 60 seconds */
  
  static int heartbeat = WD_TIMO;
  static int wd_heartbeat;
  module_param(heartbeat, int, 0);
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
63
64
65
  MODULE_PARM_DESC(heartbeat,
  	"Watchdog heartbeat in seconds. (0 < heartbeat < 65536, default="
  				__MODULE_STRING(WD_TIMO) ")");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
66

4bfdf3783   Andrey Panin   [PATCH] consolida...
67
  static int nowayout = WATCHDOG_NOWAYOUT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
68
  module_param(nowayout, int, 0);
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
69
70
71
  MODULE_PARM_DESC(nowayout,
  	"Watchdog cannot be stopped once started (default="
  				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
72
73
  
  /* You must set these - there is no sane way to probe for this board. */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
74
75
  static int io = 0x240;
  static int irq = 11;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
76

01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
77
  static DEFINE_SPINLOCK(wdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
78
79
80
81
  module_param(io, int, 0);
  MODULE_PARM_DESC(io, "WDT io port (default=0x240)");
  module_param(irq, int, 0);
  MODULE_PARM_DESC(irq, "WDT irq (default=11)");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
82
83
  /* Support for the Fan Tachometer on the WDT501-P */
  static int tachometer;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
84
  module_param(tachometer, int, 0);
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
85
86
  MODULE_PARM_DESC(tachometer,
  		"WDT501-P Fan Tachometer support (0=disable, default=0)");
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
87
88
89
90
  
  static int type = 500;
  module_param(type, int, 0);
  MODULE_PARM_DESC(type,
4724ba575   Randy Dunlap   watchdog: update/...
91
  		"WDT501-P Card type (500 or 501, default=500)");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
92
93
94
95
96
97
98
  
  /*
   *	Programming support
   */
  
  static void wdt_ctr_mode(int ctr, int mode)
  {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
99
100
101
  	ctr <<= 6;
  	ctr |= 0x30;
  	ctr |= (mode << 1);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
  	outb_p(ctr, WDT_CR);
  }
  
  static void wdt_ctr_load(int ctr, int val)
  {
  	outb_p(val&0xFF, WDT_COUNT0+ctr);
  	outb_p(val>>8, WDT_COUNT0+ctr);
  }
  
  /**
   *	wdt_start:
   *
   *	Start the watchdog driver.
   */
  
  static int wdt_start(void)
  {
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
119
120
  	unsigned long flags;
  	spin_lock_irqsave(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
121
  	inb_p(WDT_DC);			/* Disable watchdog */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
122
123
124
125
126
127
  	wdt_ctr_mode(0, 3);		/* Program CTR0 for Mode 3:
  						Square Wave Generator */
  	wdt_ctr_mode(1, 2);		/* Program CTR1 for Mode 2:
  						Rate Generator */
  	wdt_ctr_mode(2, 0);		/* Program CTR2 for Mode 0:
  						Pulse on Terminal Count */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
128
  	wdt_ctr_load(0, 8948);		/* Count at 100Hz */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
129
130
  	wdt_ctr_load(1, wd_heartbeat);	/* Heartbeat */
  	wdt_ctr_load(2, 65535);		/* Length of reset pulse */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
131
  	outb_p(0, WDT_DC);		/* Enable watchdog */
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
132
  	spin_unlock_irqrestore(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
133
134
135
136
137
138
139
140
  	return 0;
  }
  
  /**
   *	wdt_stop:
   *
   *	Stop the watchdog driver.
   */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
141
  static int wdt_stop(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
142
  {
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
143
144
  	unsigned long flags;
  	spin_lock_irqsave(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
145
146
  	/* Turn the card off */
  	inb_p(WDT_DC);			/* Disable watchdog */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
147
  	wdt_ctr_load(2, 0);		/* 0 length reset pulses now */
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
148
  	spin_unlock_irqrestore(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
149
150
151
152
153
154
  	return 0;
  }
  
  /**
   *	wdt_ping:
   *
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
155
156
   *	Reload counter one with the watchdog heartbeat. We don't bother
   *	reloading the cascade counter.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
157
   */
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
158
  static void wdt_ping(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
159
  {
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
160
161
  	unsigned long flags;
  	spin_lock_irqsave(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
162
163
  	/* Write a watchdog value */
  	inb_p(WDT_DC);			/* Disable watchdog */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
164
165
166
  	wdt_ctr_mode(1, 2);		/* Re-Program CTR1 for Mode 2:
  							Rate Generator */
  	wdt_ctr_load(1, wd_heartbeat);	/* Heartbeat */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
167
  	outb_p(0, WDT_DC);		/* Enable watchdog */
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
168
  	spin_unlock_irqrestore(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
169
170
171
172
173
174
  }
  
  /**
   *	wdt_set_heartbeat:
   *	@t:		the new heartbeat value that needs to be set.
   *
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
175
176
177
   *	Set a new heartbeat value for the watchdog device. If the heartbeat
   *	value is incorrect we keep the old value and return -EINVAL. If
   *	successful we return 0.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
178
   */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
179

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
181
  static int wdt_set_heartbeat(int t)
  {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
182
  	if (t < 1 || t > 65535)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
183
184
185
186
187
188
189
190
191
  		return -EINVAL;
  
  	heartbeat = t;
  	wd_heartbeat = t * 100;
  	return 0;
  }
  
  /**
   *	wdt_get_status:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
192
193
194
195
196
197
198
   *
   *	Extract the status information from a WDT watchdog device. There are
   *	several board variants so we have to know which bits are valid. Some
   *	bits default to one and some to zero in order to be maximally painful.
   *
   *	we then map the bits onto the status ioctl flags.
   */
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
199
  static int wdt_get_status(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
200
  {
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
201
  	unsigned char new_status;
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
202
  	int status = 0;
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
203
204
205
206
207
  	unsigned long flags;
  
  	spin_lock_irqsave(&wdt_lock, flags);
  	new_status = inb_p(WDT_SR);
  	spin_unlock_irqrestore(&wdt_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
208

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209
  	if (new_status & WDC_SR_ISOI0)
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
210
  		status |= WDIOF_EXTERN1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
211
  	if (new_status & WDC_SR_ISII1)
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
212
213
214
215
216
217
218
219
220
221
222
223
  		status |= WDIOF_EXTERN2;
  	if (type == 501) {
  		if (!(new_status & WDC_SR_TGOOD))
  			status |= WDIOF_OVERHEAT;
  		if (!(new_status & WDC_SR_PSUOVER))
  			status |= WDIOF_POWEROVER;
  		if (!(new_status & WDC_SR_PSUUNDR))
  			status |= WDIOF_POWERUNDER;
  		if (tachometer) {
  			if (!(new_status & WDC_SR_FANGOOD))
  				status |= WDIOF_FANFAULT;
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
224
  	}
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
225
  	return status;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
226
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
227
228
229
230
231
232
  /**
   *	wdt_get_temperature:
   *
   *	Reports the temperature in degrees Fahrenheit. The API is in
   *	farenheit. It was designed by an imperial measurement luddite.
   */
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
233
  static int wdt_get_temperature(void)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
234
  {
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
235
236
  	unsigned short c;
  	unsigned long flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
237

01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
238
239
240
  	spin_lock_irqsave(&wdt_lock, flags);
  	c = inb_p(WDT_RT);
  	spin_unlock_irqrestore(&wdt_lock, flags);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
  	return (c * 11 / 15) + 7;
  }
  
  static void wdt_decode_501(int status)
  {
  	if (!(status & WDC_SR_TGOOD))
  		printk(KERN_CRIT "Overheat alarm.(%d)
  ", inb_p(WDT_RT));
  	if (!(status & WDC_SR_PSUOVER))
  		printk(KERN_CRIT "PSU over voltage.
  ");
  	if (!(status & WDC_SR_PSUUNDR))
  		printk(KERN_CRIT "PSU under voltage.
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
255
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
256
257
258
259
260
  
  /**
   *	wdt_interrupt:
   *	@irq:		Interrupt number
   *	@dev_id:	Unused as we don't allow multiple devices.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
261
262
263
264
265
   *
   *	Handle an interrupt from the board. These are raised when the status
   *	map changes in what the board considers an interesting way. That means
   *	a failure condition occurring.
   */
7d12e780e   David Howells   IRQ: Maintain reg...
266
  static irqreturn_t wdt_interrupt(int irq, void *dev_id)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
267
268
269
270
271
  {
  	/*
  	 *	Read the status register see what is up and
  	 *	then printk it.
  	 */
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
272
273
274
275
  	unsigned char status;
  
  	spin_lock(&wdt_lock);
  	status = inb_p(WDT_SR);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
276
277
278
  
  	printk(KERN_CRIT "WDT status %d
  ", status);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
279
280
281
282
283
284
285
  	if (type == 501) {
  		wdt_decode_501(status);
  		if (tachometer) {
  			if (!(status & WDC_SR_FANGOOD))
  				printk(KERN_CRIT "Possible fan fault.
  ");
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
286
  	}
59338d4cb   Ilpo Jarvinen   [WATCHDOG] Add ne...
287
  	if (!(status & WDC_SR_WCCR)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
288
289
290
291
292
293
294
  #ifdef SOFTWARE_REBOOT
  #ifdef ONLY_TESTING
  		printk(KERN_CRIT "Would Reboot.
  ");
  #else
  		printk(KERN_CRIT "Initiating system reboot.
  ");
f82567e55   Eric W. Biederman   [PATCH] Fix watch...
295
  		emergency_restart();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
296
297
298
299
300
  #endif
  #else
  		printk(KERN_CRIT "Reset in 5ms.
  ");
  #endif
59338d4cb   Ilpo Jarvinen   [WATCHDOG] Add ne...
301
  	}
01c785dcb   Alan Cox   [WATCHDOG] wdt: f...
302
  	spin_unlock(&wdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
303
304
305
306
307
308
309
310
311
312
313
314
315
316
  	return IRQ_HANDLED;
  }
  
  
  /**
   *	wdt_write:
   *	@file: file handle to the watchdog
   *	@buf: buffer to write (unused as data does not matter here
   *	@count: count of bytes
   *	@ppos: pointer to the position to write. No seeks allowed
   *
   *	A write to a watchdog device is defined as a keepalive signal. Any
   *	write of data will do, as we we don't define content meaning.
   */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
317
318
  static ssize_t wdt_write(struct file *file, const char __user *buf,
  						size_t count, loff_t *ppos)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
319
  {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
320
  	if (count) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
  		if (!nowayout) {
  			size_t i;
  
  			/* In case it was set long ago */
  			expect_close = 0;
  
  			for (i = 0; i != count; i++) {
  				char c;
  				if (get_user(c, buf + i))
  					return -EFAULT;
  				if (c == 'V')
  					expect_close = 42;
  			}
  		}
  		wdt_ping();
  	}
  	return count;
  }
  
  /**
   *	wdt_ioctl:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
342
343
344
345
346
347
348
349
   *	@file: file handle to the device
   *	@cmd: watchdog command
   *	@arg: argument pointer
   *
   *	The watchdog API defines a common set of functions for all watchdogs
   *	according to their available features. We only actually usefully support
   *	querying capabilities and current status.
   */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
350
  static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
351
352
353
354
355
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
  	int new_heartbeat;
  	int status;
c1bf3acff   Andrew Morton   [WATCHDOG] driver...
356
  	struct watchdog_info ident = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
357
358
359
360
361
362
363
364
365
  		.options =		WDIOF_SETTIMEOUT|
  					WDIOF_MAGICCLOSE|
  					WDIOF_KEEPALIVEPING,
  		.firmware_version =	1,
  		.identity =		"WDT500/501",
  	};
  
  	/* Add options according to the card we have */
  	ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
366
367
368
369
370
371
  	if (type == 501) {
  		ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|
  							WDIOF_POWEROVER);
  		if (tachometer)
  			ident.options |= WDIOF_FANFAULT;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
372

9f2d1f0da   Alan Cox   wdt: Cleanup and ...
373
  	switch (cmd) {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
374
375
376
  	case WDIOC_GETSUPPORT:
  		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
  	case WDIOC_GETSTATUS:
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
377
  		status = wdt_get_status();
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
  		return put_user(status, p);
  	case WDIOC_GETBOOTSTATUS:
  		return put_user(0, p);
  	case WDIOC_KEEPALIVE:
  		wdt_ping();
  		return 0;
  	case WDIOC_SETTIMEOUT:
  		if (get_user(new_heartbeat, p))
  			return -EFAULT;
  		if (wdt_set_heartbeat(new_heartbeat))
  			return -EINVAL;
  		wdt_ping();
  		/* Fall */
  	case WDIOC_GETTIMEOUT:
  		return put_user(heartbeat, p);
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
393
394
  	default:
  		return -ENOTTY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
  	}
  }
  
  /**
   *	wdt_open:
   *	@inode: inode of device
   *	@file: file handle to device
   *
   *	The watchdog device has been opened. The watchdog device is single
   *	open and on opening we load the counters. Counter zero is a 100Hz
   *	cascade, into counter 1 which downcounts to reboot. When the counter
   *	triggers counter 2 downcounts the length of the reset pulse which
   *	set set to be as long as possible.
   */
  
  static int wdt_open(struct inode *inode, struct file *file)
  {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
412
  	if (test_and_set_bit(0, &wdt_is_open))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
  		return -EBUSY;
  	/*
  	 *	Activate
  	 */
  	wdt_start();
  	return nonseekable_open(inode, file);
  }
  
  /**
   *	wdt_release:
   *	@inode: inode to board
   *	@file: file handle to board
   *
   *	The watchdog has a configurable API. There is a religious dispute
   *	between people who want their watchdog to be able to shut down and
   *	those who want to be sure if the watchdog manager dies the machine
   *	reboots. In the former case we disable the counters, in the latter
   *	case you have to open it again very soon.
   */
  
  static int wdt_release(struct inode *inode, struct file *file)
  {
  	if (expect_close == 42) {
  		wdt_stop();
  		clear_bit(0, &wdt_is_open);
  	} else {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
439
440
441
  		printk(KERN_CRIT
  		 "wdt: WDT device closed unexpectedly.  WDT will not stop!
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
442
443
444
445
446
  		wdt_ping();
  	}
  	expect_close = 0;
  	return 0;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
447
448
449
450
451
452
453
454
455
456
  /**
   *	wdt_temp_read:
   *	@file: file handle to the watchdog board
   *	@buf: buffer to write 1 byte into
   *	@count: length of buffer
   *	@ptr: offset (no seek allowed)
   *
   *	Temp_read reports the temperature in degrees Fahrenheit. The API is in
   *	farenheit. It was designed by an imperial measurement luddite.
   */
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
457
458
  static ssize_t wdt_temp_read(struct file *file, char __user *buf,
  						size_t count, loff_t *ptr)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
459
  {
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
460
  	int temperature = wdt_get_temperature();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
461

9f2d1f0da   Alan Cox   wdt: Cleanup and ...
462
  	if (copy_to_user(buf, &temperature, 1))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
  		return -EFAULT;
  
  	return 1;
  }
  
  /**
   *	wdt_temp_open:
   *	@inode: inode of device
   *	@file: file handle to device
   *
   *	The temperature device has been opened.
   */
  
  static int wdt_temp_open(struct inode *inode, struct file *file)
  {
  	return nonseekable_open(inode, file);
  }
  
  /**
   *	wdt_temp_release:
   *	@inode: inode to board
   *	@file: file handle to board
   *
   *	The temperature device has been closed.
   */
  
  static int wdt_temp_release(struct inode *inode, struct file *file)
  {
  	return 0;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
  
  /**
   *	notify_sys:
   *	@this: our notifier block
   *	@code: the event being reported
   *	@unused: unused
   *
   *	Our notifier is called on system shutdowns. We want to turn the card
   *	off at reboot otherwise the machine will reboot again during memory
   *	test or worse yet during the following fsck. This would suck, in fact
   *	trust me - if it happens it does suck.
   */
  
  static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
  	void *unused)
  {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
509
  	if (code == SYS_DOWN || code == SYS_HALT)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
510
  		wdt_stop();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
511
512
513
514
515
516
  	return NOTIFY_DONE;
  }
  
  /*
   *	Kernel Interfaces
   */
62322d255   Arjan van de Ven   [PATCH] make more...
517
  static const struct file_operations wdt_fops = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
518
519
520
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.write		= wdt_write,
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
521
  	.unlocked_ioctl	= wdt_ioctl,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
522
523
524
525
526
527
528
529
530
  	.open		= wdt_open,
  	.release	= wdt_release,
  };
  
  static struct miscdevice wdt_miscdev = {
  	.minor	= WATCHDOG_MINOR,
  	.name	= "watchdog",
  	.fops	= &wdt_fops,
  };
62322d255   Arjan van de Ven   [PATCH] make more...
531
  static const struct file_operations wdt_temp_fops = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
532
533
534
535
536
537
538
539
540
541
542
543
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.read		= wdt_temp_read,
  	.open		= wdt_temp_open,
  	.release	= wdt_temp_release,
  };
  
  static struct miscdevice temp_miscdev = {
  	.minor	= TEMP_MINOR,
  	.name	= "temperature",
  	.fops	= &wdt_temp_fops,
  };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
  
  /*
   *	The WDT card needs to learn about soft shutdowns in order to
   *	turn the timebomb registers off.
   */
  
  static struct notifier_block wdt_notifier = {
  	.notifier_call = wdt_notify_sys,
  };
  
  /**
   *	cleanup_module:
   *
   *	Unload the watchdog. You cannot do this with any file handles open.
   *	If your watchdog is set to continue ticking on close and you unload
   *	it, well it keeps ticking. We won't get the interrupt but the board
   *	will not touch PC memory so all is fine. You just have to load a new
   *	module in 60 seconds or reboot.
   */
  
  static void __exit wdt_exit(void)
  {
  	misc_deregister(&wdt_miscdev);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
567
568
  	if (type == 501)
  		misc_deregister(&temp_miscdev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
569
570
  	unregister_reboot_notifier(&wdt_notifier);
  	free_irq(irq, NULL);
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
571
  	release_region(io, 8);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
572
573
574
  }
  
  /**
5f3b27569   Wim Van Sebroeck   watchdog: cleanup...
575
   *	wdt_init:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
576
577
578
579
580
581
582
583
584
   *
   *	Set up the WDT watchdog board. All we have to do is grab the
   *	resources we require and bitch if anyone beat us to them.
   *	The open() function will actually kick the board off.
   */
  
  static int __init wdt_init(void)
  {
  	int ret;
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
585
586
587
588
589
  	if (type != 500 && type != 501) {
  		printk(KERN_ERR "wdt: unknown card type '%d'.
  ", type);
  		return -ENODEV;
  	}
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
590
591
  	/* Check that the heartbeat value is within it's range;
  	   if not reset to the default */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
592
593
  	if (wdt_set_heartbeat(heartbeat)) {
  		wdt_set_heartbeat(WD_TIMO);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
594
595
596
  		printk(KERN_INFO "wdt: heartbeat value must be "
  			"0 < heartbeat < 65536, using %d
  ", WD_TIMO);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
597
598
599
  	}
  
  	if (!request_region(io, 8, "wdt501p")) {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
600
601
602
  		printk(KERN_ERR
  			"wdt: I/O address 0x%04x already in use
  ", io);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
603
604
605
  		ret = -EBUSY;
  		goto out;
  	}
86b591288   Yong Zhang   watchdog: irq: Re...
606
  	ret = request_irq(irq, wdt_interrupt, 0, "wdt501p", NULL);
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
607
  	if (ret) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
608
609
610
611
612
613
  		printk(KERN_ERR "wdt: IRQ %d is not free.
  ", irq);
  		goto outreg;
  	}
  
  	ret = register_reboot_notifier(&wdt_notifier);
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
614
615
616
617
  	if (ret) {
  		printk(KERN_ERR
  		      "wdt: cannot register reboot notifier (err=%d)
  ", ret);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
618
619
  		goto outirq;
  	}
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
620
621
622
623
624
625
626
627
  	if (type == 501) {
  		ret = misc_register(&temp_miscdev);
  		if (ret) {
  			printk(KERN_ERR "wdt: cannot register miscdev "
  				"on minor=%d (err=%d)
  ", TEMP_MINOR, ret);
  			goto outrbt;
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
628
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
629
630
631
  
  	ret = misc_register(&wdt_miscdev);
  	if (ret) {
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
632
633
634
635
  		printk(KERN_ERR
  			"wdt: cannot register miscdev on minor=%d (err=%d)
  ",
  							WATCHDOG_MINOR, ret);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
636
637
  		goto outmisc;
  	}
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
638
639
640
  	printk(KERN_INFO "WDT500/501-P driver 0.10 "
  		"at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)
  ",
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
641
  		io, irq, heartbeat, nowayout);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
642
643
644
  	if (type == 501)
  		printk(KERN_INFO "wdt: Fan Tachometer is %s
  ",
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
645
  				(tachometer ? "Enabled" : "Disabled"));
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
646
  	return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
647
648
  
  outmisc:
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
649
650
  	if (type == 501)
  		misc_deregister(&temp_miscdev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
651
  outrbt:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
652
653
654
655
  	unregister_reboot_notifier(&wdt_notifier);
  outirq:
  	free_irq(irq, NULL);
  outreg:
9f2d1f0da   Alan Cox   wdt: Cleanup and ...
656
  	release_region(io, 8);
04bedfa54   Alan Cox   [WATCHDOG] wdt.c:...
657
658
  out:
  	return ret;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
659
660
661
662
663
664
665
666
667
668
  }
  
  module_init(wdt_init);
  module_exit(wdt_exit);
  
  MODULE_AUTHOR("Alan Cox");
  MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)");
  MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
  MODULE_ALIAS_MISCDEV(TEMP_MINOR);
  MODULE_LICENSE("GPL");