Blame view

drivers/watchdog/machzwd.c 10 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
  /*
   *  MachZ ZF-Logic Watchdog Timer driver for Linux
   *
   *
   *  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 author does 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.
   *
   *  Author: Fernando Fuganti <fuganti@conectiva.com.br>
   *
   *  Based on sbc60xxwdt.c by Jakob Oestergaard
   *
   *
   *  We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the
   *  following periods:
   *      wd#1 - 2 seconds;
   *      wd#2 - 7.2 ms;
   *  After the expiration of wd#1, it can generate a NMI, SCI, SMI, or
af901ca18   AndrĂ© Goddard Rosa   tree-wide: fix as...
24
   *  a system RESET and it starts wd#2 that unconditionally will RESET
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
25
26
27
28
29
   *  the system when the counter reaches zero.
   *
   *  14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
   *      Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
30
31
32
33
34
35
36
37
38
39
40
41
  #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>
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
42
43
  #include <linux/io.h>
  #include <linux/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
44

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
45
46
47
48
49
50
51
52
53
54
55
  #include <asm/system.h>
  
  /* ports */
  #define ZF_IOBASE	0x218
  #define INDEX		0x218
  #define DATA_B		0x219
  #define DATA_W		0x21A
  #define DATA_D		0x21A
  
  /* indexes */			/* size */
  #define ZFL_VERSION	0x02	/* 16   */
5f3b27569   Wim Van Sebroeck   watchdog: cleanup...
56
  #define CONTROL		0x10	/* 16   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
57
58
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
86
87
88
89
90
91
92
93
  #define STATUS		0x12	/* 8    */
  #define COUNTER_1	0x0C	/* 16   */
  #define COUNTER_2	0x0E	/* 8    */
  #define PULSE_LEN	0x0F	/* 8    */
  
  /* controls */
  #define ENABLE_WD1	0x0001
  #define ENABLE_WD2	0x0002
  #define RESET_WD1	0x0010
  #define RESET_WD2	0x0020
  #define GEN_SCI		0x0100
  #define GEN_NMI		0x0200
  #define GEN_SMI		0x0400
  #define GEN_RESET	0x0800
  
  
  /* utilities */
  
  #define WD1	0
  #define WD2	1
  
  #define zf_writew(port, data)  { outb(port, INDEX); outw(data, DATA_W); }
  #define zf_writeb(port, data)  { outb(port, INDEX); outb(data, DATA_B); }
  #define zf_get_ZFL_version()   zf_readw(ZFL_VERSION)
  
  
  static unsigned short zf_readw(unsigned char port)
  {
  	outb(port, INDEX);
  	return inw(DATA_W);
  }
  
  
  MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>");
  MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
4bfdf3783   Andrey Panin   [PATCH] consolida...
94
  static int nowayout = WATCHDOG_NOWAYOUT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
95
  module_param(nowayout, int, 0);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
96
97
98
  MODULE_PARM_DESC(nowayout,
  		"Watchdog cannot be stopped once started (default="
  				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
99
100
  
  #define PFX "machzwd"
42747d712   Wim Van Sebroeck   [WATCHDOG] watchd...
101
  static const struct watchdog_info zf_info = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
102
103
104
105
106
107
108
109
110
111
112
113
114
115
  	.options		= WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
  	.firmware_version	= 1,
  	.identity		= "ZF-Logic watchdog",
  };
  
  
  /*
   * action refers to action taken when watchdog resets
   * 0 = GEN_RESET
   * 1 = GEN_SMI
   * 2 = GEN_NMI
   * 3 = GEN_SCI
   * defaults to GEN_RESET (0)
   */
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
116
  static int action;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
117
  module_param(action, int, 0);
a77dba7e4   Wim Van Sebroeck   [WATCHDOG] Some m...
118
119
  MODULE_PARM_DESC(action, "after watchdog resets, generate: "
  				"0 = RESET(*)  1 = SMI  2 = NMI  3 = SCI");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
120

82eb7c505   Jiri Slaby   [WATCHDOG] timers...
121
  static void zf_ping(unsigned long data);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
122
123
124
  static int zf_action = GEN_RESET;
  static unsigned long zf_is_open;
  static char zf_expect_close;
c7dfd0cca   Alexey Dobriyan   [WATCHDOG] spin_l...
125
  static DEFINE_SPINLOCK(zf_port_lock);
82eb7c505   Jiri Slaby   [WATCHDOG] timers...
126
  static DEFINE_TIMER(zf_timer, zf_ping, 0, 0);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
127
  static unsigned long next_heartbeat;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
128
129
130
131
132
133
134
135
136
137
138
139
140
141
  
  
  /* timeout for user land heart beat (10 seconds) */
  #define ZF_USER_TIMEO (HZ*10)
  
  /* timeout for hardware watchdog (~500ms) */
  #define ZF_HW_TIMEO (HZ/2)
  
  /* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */
  #define ZF_CTIMEOUT 0xffff
  
  #ifndef ZF_DEBUG
  #	define dprintk(format, args...)
  #else
29c3e8c8d   Nicolas Kaiser   watchdog: MachZ: ...
142
  #	define dprintk(format, args...) printk(KERN_DEBUG PFX \
a77dba7e4   Wim Van Sebroeck   [WATCHDOG] Some m...
143
  				":%s:%d: " format, __func__, __LINE__ , ## args)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  #endif
  
  
  static inline void zf_set_status(unsigned char new)
  {
  	zf_writeb(STATUS, new);
  }
  
  
  /* CONTROL register functions */
  
  static inline unsigned short zf_get_control(void)
  {
  	return zf_readw(CONTROL);
  }
  
  static inline void zf_set_control(unsigned short new)
  {
  	zf_writew(CONTROL, new);
  }
  
  
  /* WD#? counter functions */
  /*
   *	Just set counter value
   */
  
  static inline void zf_set_timer(unsigned short new, unsigned char n)
  {
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
173
174
175
176
177
178
179
  	switch (n) {
  	case WD1:
  		zf_writew(COUNTER_1, new);
  	case WD2:
  		zf_writeb(COUNTER_2, new > 0xff ? 0xff : new);
  	default:
  		return;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
  	}
  }
  
  /*
   * stop hardware timer
   */
  static void zf_timer_off(void)
  {
  	unsigned int ctrl_reg = 0;
  	unsigned long flags;
  
  	/* stop internal ping */
  	del_timer_sync(&zf_timer);
  
  	spin_lock_irqsave(&zf_port_lock, flags);
  	/* stop watchdog timer */
  	ctrl_reg = zf_get_control();
  	ctrl_reg |= (ENABLE_WD1|ENABLE_WD2);	/* disable wd1 and wd2 */
  	ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2);
  	zf_set_control(ctrl_reg);
  	spin_unlock_irqrestore(&zf_port_lock, flags);
  
  	printk(KERN_INFO PFX ": Watchdog timer is now disabled
  ");
  }
  
  
  /*
   * start hardware timer
   */
  static void zf_timer_on(void)
  {
  	unsigned int ctrl_reg = 0;
  	unsigned long flags;
  
  	spin_lock_irqsave(&zf_port_lock, flags);
  
  	zf_writeb(PULSE_LEN, 0xff);
  
  	zf_set_timer(ZF_CTIMEOUT, WD1);
  
  	/* user land ping */
  	next_heartbeat = jiffies + ZF_USER_TIMEO;
  
  	/* start the timer for internal ping */
82eb7c505   Jiri Slaby   [WATCHDOG] timers...
225
  	mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
  
  	/* start watchdog timer */
  	ctrl_reg = zf_get_control();
  	ctrl_reg |= (ENABLE_WD1|zf_action);
  	zf_set_control(ctrl_reg);
  	spin_unlock_irqrestore(&zf_port_lock, flags);
  
  	printk(KERN_INFO PFX ": Watchdog timer is now enabled
  ");
  }
  
  
  static void zf_ping(unsigned long data)
  {
  	unsigned int ctrl_reg = 0;
  	unsigned long flags;
  
  	zf_writeb(COUNTER_2, 0xff);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
244
  	if (time_before(jiffies, next_heartbeat)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
245
246
  		dprintk("time_before: %ld
  ", next_heartbeat - jiffies);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  		/*
  		 * reset event is activated by transition from 0 to 1 on
  		 * RESET_WD1 bit and we assume that it is already zero...
  		 */
  
  		spin_lock_irqsave(&zf_port_lock, flags);
  		ctrl_reg = zf_get_control();
  		ctrl_reg |= RESET_WD1;
  		zf_set_control(ctrl_reg);
  
  		/* ...and nothing changes until here */
  		ctrl_reg &= ~(RESET_WD1);
  		zf_set_control(ctrl_reg);
  		spin_unlock_irqrestore(&zf_port_lock, flags);
82eb7c505   Jiri Slaby   [WATCHDOG] timers...
261
  		mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
262
  	} else
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
263
264
  		printk(KERN_CRIT PFX ": I will reset your machine
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
265
266
267
268
269
270
  }
  
  static ssize_t zf_write(struct file *file, const char __user *buf, size_t count,
  								loff_t *ppos)
  {
  	/* See if we got the magic character */
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
271
  	if (count) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
272
273
274
275
276
277
  		/*
  		 * no need to check for close confirmation
  		 * no way to disable watchdog ;)
  		 */
  		if (!nowayout) {
  			size_t ofs;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
278
279
280
281
282
283
284
  			/*
  			 * note: just in case someone wrote the magic character
  			 * five months ago...
  			 */
  			zf_expect_close = 0;
  
  			/* now scan */
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
285
  			for (ofs = 0; ofs != count; ofs++) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
286
287
288
  				char c;
  				if (get_user(c, buf + ofs))
  					return -EFAULT;
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
289
  				if (c == 'V') {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
290
291
292
293
294
295
296
297
298
299
300
301
302
303
  					zf_expect_close = 42;
  					dprintk("zf_expect_close = 42
  ");
  				}
  			}
  		}
  
  		/*
  		 * Well, anyhow someone wrote to us,
  		 * we should return that favour
  		 */
  		next_heartbeat = jiffies + ZF_USER_TIMEO;
  		dprintk("user ping at %ld
  ", jiffies);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
304
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
305
306
  	return count;
  }
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
307
  static long zf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
308
309
310
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
bad77057e   Andrew Morton   [PATCH] machzwd w...
311
312
313
314
315
  	switch (cmd) {
  	case WDIOC_GETSUPPORT:
  		if (copy_to_user(argp, &zf_info, sizeof(zf_info)))
  			return -EFAULT;
  		break;
bad77057e   Andrew Morton   [PATCH] machzwd w...
316
  	case WDIOC_GETSTATUS:
5c4eb61b3   Wim Van Sebroeck   [WATCHDOG] WDIOC_...
317
  	case WDIOC_GETBOOTSTATUS:
bad77057e   Andrew Morton   [PATCH] machzwd w...
318
  		return put_user(0, p);
bad77057e   Andrew Morton   [PATCH] machzwd w...
319
320
321
  	case WDIOC_KEEPALIVE:
  		zf_ping(0);
  		break;
bad77057e   Andrew Morton   [PATCH] machzwd w...
322
323
  	default:
  		return -ENOTTY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
324
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
325
326
327
328
329
  	return 0;
  }
  
  static int zf_open(struct inode *inode, struct file *file)
  {
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
330
  	if (test_and_set_bit(0, &zf_is_open))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
331
  		return -EBUSY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
332
333
  	if (nowayout)
  		__module_get(THIS_MODULE);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
334
  	zf_timer_on();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
335
336
337
338
339
  	return nonseekable_open(inode, file);
  }
  
  static int zf_close(struct inode *inode, struct file *file)
  {
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
340
  	if (zf_expect_close == 42)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
341
  		zf_timer_off();
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
342
  	else {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
343
  		del_timer(&zf_timer);
a77dba7e4   Wim Van Sebroeck   [WATCHDOG] Some m...
344
345
346
  		printk(KERN_ERR PFX ": device file closed unexpectedly. "
  						"Will not stop the WDT!
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
347
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
348
  	clear_bit(0, &zf_is_open);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
349
  	zf_expect_close = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
350
351
352
353
354
355
356
357
358
359
  	return 0;
  }
  
  /*
   * Notifier for system down
   */
  
  static int zf_notify_sys(struct notifier_block *this, unsigned long code,
  								void *unused)
  {
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
360
  	if (code == SYS_DOWN || code == SYS_HALT)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
361
  		zf_timer_off();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
362
363
  	return NOTIFY_DONE;
  }
62322d255   Arjan van de Ven   [PATCH] make more...
364
  static const struct file_operations zf_fops = {
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
365
366
367
368
369
370
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.write		= zf_write,
  	.unlocked_ioctl = zf_ioctl,
  	.open		= zf_open,
  	.release	= zf_close,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
371
372
373
374
375
376
377
  };
  
  static struct miscdevice zf_miscdev = {
  	.minor = WATCHDOG_MINOR,
  	.name = "watchdog",
  	.fops = &zf_fops,
  };
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
378

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
379
380
381
382
383
384
385
386
387
388
389
  
  /*
   * The device needs to learn about soft shutdowns in order to
   * turn the timebomb registers off.
   */
  static struct notifier_block zf_notifier = {
  	.notifier_call = zf_notify_sys,
  };
  
  static void __init zf_show_action(int act)
  {
a2b89cd85   Joe Perches   watchdog: Use st...
390
  	static const char * const str[] = { "RESET", "SMI", "NMI", "SCI" };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
391
392
393
394
395
396
397
398
  
  	printk(KERN_INFO PFX ": Watchdog using action = %s
  ", str[act]);
  }
  
  static int __init zf_init(void)
  {
  	int ret;
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
399
400
401
  	printk(KERN_INFO PFX
  		": MachZ ZF-Logic Watchdog driver initializing.
  ");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
402
403
  
  	ret = zf_get_ZFL_version();
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
404
  	if (!ret || ret == 0xffff) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
405
406
407
408
  		printk(KERN_WARNING PFX ": no ZF-Logic found
  ");
  		return -ENODEV;
  	}
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
409
410
411
  	if (action <= 3 && action >= 0)
  		zf_action = zf_action >> action;
  	else
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
412
413
414
  		action = 0;
  
  	zf_show_action(action);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
415
  	if (!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
416
417
418
419
420
421
422
423
  		printk(KERN_ERR "cannot reserve I/O ports at %d
  ",
  							ZF_IOBASE);
  		ret = -EBUSY;
  		goto no_region;
  	}
  
  	ret = register_reboot_notifier(&zf_notifier);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
424
  	if (ret) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
425
426
427
428
429
  		printk(KERN_ERR "can't register reboot notifier (err=%d)
  ",
  									ret);
  		goto no_reboot;
  	}
fb8f7ba07   Alexey Dobriyan   [WATCHDOG] Semi-t...
430
  	ret = misc_register(&zf_miscdev);
325ea4d3a   Alan Cox   [WATCHDOG 24/57] ...
431
  	if (ret) {
fb8f7ba07   Alexey Dobriyan   [WATCHDOG] Semi-t...
432
433
434
435
436
  		printk(KERN_ERR "can't misc_register on minor=%d
  ",
  							WATCHDOG_MINOR);
  		goto no_misc;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
437
438
  	zf_set_status(0);
  	zf_set_control(0);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
439
  	return 0;
fb8f7ba07   Alexey Dobriyan   [WATCHDOG] Semi-t...
440
441
  no_misc:
  	unregister_reboot_notifier(&zf_notifier);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
442
443
444
  no_reboot:
  	release_region(ZF_IOBASE, 3);
  no_region:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
  	return ret;
  }
  
  
  static void __exit zf_exit(void)
  {
  	zf_timer_off();
  
  	misc_deregister(&zf_miscdev);
  	unregister_reboot_notifier(&zf_notifier);
  	release_region(ZF_IOBASE, 3);
  }
  
  module_init(zf_init);
  module_exit(zf_exit);