Blame view

drivers/watchdog/eurotechwdt.c 10.9 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
  /*
0f112a86a   Wim Van Sebroeck   [WATCHDOG] Eurote...
2
   *	Eurotech CPU-1220/1410/1420 on board WDT driver
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
3
4
5
6
7
8
9
10
   *
   *	(c) Copyright 2001 Ascensit <support@ascensit.com>
   *	(c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com>
   *	(c) Copyright 2002 Rob Radez <rob@osinvestor.com>
   *
   *	Based on wdt.c.
   *	Original copyright messages:
   *
143a2e54b   Wim Van Sebroeck   [WATCHDOG] More c...
11
   *	(c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
29fa0586d   Alan Cox   [PATCH] Switch al...
12
   *						All Rights Reserved.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
13
   *
143a2e54b   Wim Van Sebroeck   [WATCHDOG] More c...
14
15
16
17
   *	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.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
18
   *
143a2e54b   Wim Van Sebroeck   [WATCHDOG] More c...
19
20
21
   *	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.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
22
   *
143a2e54b   Wim Van Sebroeck   [WATCHDOG] More c...
23
   *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>*
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
24
25
26
27
   */
  
  /* Changelog:
   *
0f112a86a   Wim Van Sebroeck   [WATCHDOG] Eurote...
28
29
30
   * 2001 - Rodolfo Giometti
   *	Initial release
   *
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
31
32
33
34
35
36
37
38
   * 2002/04/25 - Rob Radez
   *	clean up #includes
   *	clean up locking
   *	make __setup param unique
   *	proper options in watchdog_info
   *	add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls
   *	add expect_close support
   *
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
39
   * 2002.05.30 - Joel Becker <joel.becker@oracle.com>
143a2e54b   Wim Van Sebroeck   [WATCHDOG] More c...
40
   *	Added Matt Domsch's nowayout module option.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
41
   */
0f112a86a   Wim Van Sebroeck   [WATCHDOG] Eurote...
42
43
44
45
  /*
   *	The eurotech CPU-1220/1410/1420's watchdog is a part
   *	of the on-board SUPER I/O device SMSC FDC 37B782.
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
46
47
48
49
50
51
52
53
54
55
56
  #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>
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
57
58
  #include <linux/io.h>
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
59

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
60
61
62
63
64
  #include <asm/system.h>
  
  static unsigned long eurwdt_is_open;
  static int eurwdt_timeout;
  static char eur_expect_close;
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
65
  static spinlock_t eurwdt_lock;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
66
67
68
  
  /*
   * You must set these - there is no sane way to probe for this board.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
69
70
71
72
73
74
75
   */
  
  static int io = 0x3f0;
  static int irq = 10;
  static char *ev = "int";
  
  #define WDT_TIMEOUT		60                /* 1 minute */
4bfdf3783   Andrey Panin   [PATCH] consolida...
76
  static int nowayout = WATCHDOG_NOWAYOUT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
77
  module_param(nowayout, int, 0);
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
78
79
80
  MODULE_PARM_DESC(nowayout,
  		"Watchdog cannot be stopped once started (default="
  				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
  
  /*
   * Some symbolic names
   */
  
  #define WDT_CTRL_REG		0x30
  #define WDT_OUTPIN_CFG		0xe2
  #define WDT_EVENT_INT		0x00
  #define WDT_EVENT_REBOOT	0x08
  #define WDT_UNIT_SEL		0xf1
  #define WDT_UNIT_SECS		0x80
  #define WDT_TIMEOUT_VAL		0xf2
  #define WDT_TIMER_CFG		0xf3
  
  
  module_param(io, int, 0);
  MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)");
  module_param(irq, int, 0);
  MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)");
  module_param(ev, charp, 0);
  MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')");
  
  
  /*
   * Programming support
   */
  
  static inline void eurwdt_write_reg(u8 index, u8 data)
  {
  	outb(index, io);
  	outb(data, io+1);
  }
  
  static inline void eurwdt_lock_chip(void)
  {
  	outb(0xaa, io);
  }
  
  static inline void eurwdt_unlock_chip(void)
  {
  	outb(0x55, io);
  	eurwdt_write_reg(0x07, 0x08);	/* set the logical device */
  }
  
  static inline void eurwdt_set_timeout(int timeout)
  {
  	eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout);
  }
  
  static inline void eurwdt_disable_timer(void)
  {
  	eurwdt_set_timeout(0);
  }
  
  static void eurwdt_activate_timer(void)
  {
  	eurwdt_disable_timer();
  	eurwdt_write_reg(WDT_CTRL_REG, 0x01);	/* activate the WDT */
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
139
140
  	eurwdt_write_reg(WDT_OUTPIN_CFG,
  		!strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
141
142
143
144
145
146
147
148
149
150
  
  	/* Setting interrupt line */
  	if (irq == 2 || irq > 15 || irq < 0) {
  		printk(KERN_ERR ": invalid irq number
  ");
  		irq = 0;	/* if invalid we disable interrupt */
  	}
  	if (irq == 0)
  		printk(KERN_INFO ": interrupt disabled
  ");
143a2e54b   Wim Van Sebroeck   [WATCHDOG] More c...
151
  	eurwdt_write_reg(WDT_TIMER_CFG, irq << 4);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152
153
154
155
156
157
158
159
160
  
  	eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS);	/* we use seconds */
  	eurwdt_set_timeout(0);	/* the default timeout */
  }
  
  
  /*
   * Kernel methods.
   */
7d12e780e   David Howells   IRQ: Maintain reg...
161
  static irqreturn_t eurwdt_interrupt(int irq, void *dev_id)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
162
163
164
165
166
167
168
169
170
171
  {
  	printk(KERN_CRIT "timeout WDT timeout
  ");
  
  #ifdef ONLY_TESTING
  	printk(KERN_CRIT "Would Reboot.
  ");
  #else
  	printk(KERN_CRIT "Initiating system reboot.
  ");
cc1d3a9a7   Andrew Morton   [PATCH] eurotechw...
172
  	emergency_restart();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
  #endif
  	return IRQ_HANDLED;
  }
  
  
  /**
   * eurwdt_ping:
   *
   * Reload counter one with the watchdog timeout.
   */
  
  static void eurwdt_ping(void)
  {
  	/* Write the watchdog default value */
  	eurwdt_set_timeout(eurwdt_timeout);
  }
  
  /**
   * eurwdt_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.
   */
  
  static ssize_t eurwdt_write(struct file *file, const char __user *buf,
  size_t count, loff_t *ppos)
  {
5f3b27569   Wim Van Sebroeck   watchdog: cleanup...
204
  	if (count) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
205
206
207
208
209
210
211
  		if (!nowayout) {
  			size_t i;
  
  			eur_expect_close = 0;
  
  			for (i = 0; i != count; i++) {
  				char c;
7944d3a5a   Wim Van Sebroeck   [WATCHDOG] more c...
212
  				if (get_user(c, buf + i))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
213
214
215
216
217
  					return -EFAULT;
  				if (c == 'V')
  					eur_expect_close = 42;
  			}
  		}
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
218
  		spin_lock(&eurwdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
219
  		eurwdt_ping();	/* the default timeout */
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
220
  		spin_unlock(&eurwdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
221
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
222
223
224
225
226
  	return count;
  }
  
  /**
   * eurwdt_ioctl:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
227
228
229
230
231
232
233
   * @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.
   */
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
234
235
  static long eurwdt_ioctl(struct file *file,
  					unsigned int cmd, unsigned long arg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
237
238
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
42747d712   Wim Van Sebroeck   [WATCHDOG] watchd...
239
  	static const struct watchdog_info ident = {
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
240
241
  		.options	  = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
  							| WDIOF_MAGICCLOSE,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
242
243
244
245
246
247
  		.firmware_version = 1,
  		.identity	  = "WDT Eurotech CPU-1220/1410",
  	};
  
  	int time;
  	int options, retval = -EINVAL;
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
248
  	switch (cmd) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
249
250
251
252
253
254
  	case WDIOC_GETSUPPORT:
  		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
  
  	case WDIOC_GETSTATUS:
  	case WDIOC_GETBOOTSTATUS:
  		return put_user(0, p);
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
  	case WDIOC_SETOPTIONS:
  		if (get_user(options, p))
  			return -EFAULT;
  		spin_lock(&eurwdt_lock);
  		if (options & WDIOS_DISABLECARD) {
  			eurwdt_disable_timer();
  			retval = 0;
  		}
  		if (options & WDIOS_ENABLECARD) {
  			eurwdt_activate_timer();
  			eurwdt_ping();
  			retval = 0;
  		}
  		spin_unlock(&eurwdt_lock);
  		return retval;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
270
  	case WDIOC_KEEPALIVE:
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
271
  		spin_lock(&eurwdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
272
  		eurwdt_ping();
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
273
  		spin_unlock(&eurwdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
274
275
276
277
278
279
280
281
282
  		return 0;
  
  	case WDIOC_SETTIMEOUT:
  		if (copy_from_user(&time, p, sizeof(int)))
  			return -EFAULT;
  
  		/* Sanity check */
  		if (time < 0 || time > 255)
  			return -EINVAL;
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
283
  		spin_lock(&eurwdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
284
285
  		eurwdt_timeout = time;
  		eurwdt_set_timeout(time);
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
286
  		spin_unlock(&eurwdt_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
287
288
289
290
  		/* Fall */
  
  	case WDIOC_GETTIMEOUT:
  		return put_user(eurwdt_timeout, p);
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
291
292
  	default:
  		return -ENOTTY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
  	}
  }
  
  /**
   * eurwdt_open:
   * @inode: inode of device
   * @file: file handle to device
   *
   * The misc device has been opened. The watchdog device is single
   * open and on opening we load the counter.
   */
  
  static int eurwdt_open(struct inode *inode, struct file *file)
  {
  	if (test_and_set_bit(0, &eurwdt_is_open))
  		return -EBUSY;
  	eurwdt_timeout = WDT_TIMEOUT;	/* initial timeout */
  	/* Activate the WDT */
  	eurwdt_activate_timer();
  	return nonseekable_open(inode, file);
  }
  
  /**
   * eurwdt_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 eurwdt_release(struct inode *inode, struct file *file)
  {
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
329
  	if (eur_expect_close == 42)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
330
  		eurwdt_disable_timer();
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
331
332
333
334
  	else {
  		printk(KERN_CRIT
  			"eurwdt: Unexpected close, not stopping watchdog!
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  		eurwdt_ping();
  	}
  	clear_bit(0, &eurwdt_is_open);
  	eur_expect_close = 0;
  	return 0;
  }
  
  /**
   * eurwdt_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 eurwdt_notify_sys(struct notifier_block *this, unsigned long code,
  	void *unused)
  {
7944d3a5a   Wim Van Sebroeck   [WATCHDOG] more c...
357
358
  	if (code == SYS_DOWN || code == SYS_HALT)
  		eurwdt_disable_timer();	/* Turn the card off */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
359
360
361
362
363
364
365
  
  	return NOTIFY_DONE;
  }
  
  /*
   * Kernel Interfaces
   */
62322d255   Arjan van de Ven   [PATCH] make more...
366
  static const struct file_operations eurwdt_fops = {
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
367
368
369
370
371
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.write		= eurwdt_write,
  	.unlocked_ioctl	= eurwdt_ioctl,
  	.open		= eurwdt_open,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
  	.release	= eurwdt_release,
  };
  
  static struct miscdevice eurwdt_miscdev = {
  	.minor	= WATCHDOG_MINOR,
  	.name	= "watchdog",
  	.fops	= &eurwdt_fops,
  };
  
  /*
   * The WDT card needs to learn about soft shutdowns in order to
   * turn the timebomb registers off.
   */
  
  static struct notifier_block eurwdt_notifier = {
  	.notifier_call = eurwdt_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 eurwdt_exit(void)
  {
  	eurwdt_lock_chip();
  
  	misc_deregister(&eurwdt_miscdev);
  
  	unregister_reboot_notifier(&eurwdt_notifier);
  	release_region(io, 2);
  	free_irq(irq, NULL);
  }
  
  /**
   * eurwdt_init:
   *
   * Set up the WDT watchdog board. After grabbing the resources
   * we require we need also to unlock the device.
   * The open() function will actually kick the board off.
   */
  
  static int __init eurwdt_init(void)
  {
  	int ret;
0f2ed4c6b   Thomas Gleixner   [PATCH] irq-flags...
422
  	ret = request_irq(irq, eurwdt_interrupt, IRQF_DISABLED, "eurwdt", NULL);
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
423
  	if (ret) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
424
425
  		printk(KERN_ERR "eurwdt: IRQ %d is not free.
  ", irq);
fb8f7ba07   Alexey Dobriyan   [WATCHDOG] Semi-t...
426
  		goto out;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
427
428
429
430
431
432
433
434
435
436
437
  	}
  
  	if (!request_region(io, 2, "eurwdt")) {
  		printk(KERN_ERR "eurwdt: IO %X is not free.
  ", io);
  		ret = -EBUSY;
  		goto outirq;
  	}
  
  	ret = register_reboot_notifier(&eurwdt_notifier);
  	if (ret) {
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
438
439
440
  		printk(KERN_ERR
  		    "eurwdt: can't register reboot notifier (err=%d)
  ", ret);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
441
442
  		goto outreg;
  	}
89ea24298   Alan Cox   [WATCHDOG 10/57] ...
443
  	spin_lock_init(&eurwdt_lock);
fb8f7ba07   Alexey Dobriyan   [WATCHDOG] Semi-t...
444
445
446
447
448
449
450
  	ret = misc_register(&eurwdt_miscdev);
  	if (ret) {
  		printk(KERN_ERR "eurwdt: can't misc_register on minor=%d
  ",
  		WATCHDOG_MINOR);
  		goto outreboot;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
451
452
453
454
455
456
457
458
459
460
  	eurwdt_unlock_chip();
  
  	ret = 0;
  	printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)"
  		" - timeout event: %s
  ",
  		io, irq, (!strcmp("int", ev) ? "int" : "reboot"));
  
  out:
  	return ret;
fb8f7ba07   Alexey Dobriyan   [WATCHDOG] Semi-t...
461
462
  outreboot:
  	unregister_reboot_notifier(&eurwdt_notifier);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
463
464
465
466
467
  outreg:
  	release_region(io, 2);
  
  outirq:
  	free_irq(irq, NULL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
468
469
470
471
472
473
474
475
476
477
  	goto out;
  }
  
  module_init(eurwdt_init);
  module_exit(eurwdt_exit);
  
  MODULE_AUTHOR("Rodolfo Giometti");
  MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);