w83877f_wdt.c 10.1 KB
Newer Older
L
Linus Torvalds 已提交
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
26 27 28 29
 *                            changed watchdog_info to correctly reflect what
 *			      the driver offers
 *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
 *			      WDIOC_SETTIMEOUT,
L
Linus Torvalds 已提交
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
34 35
 *                            made timeout (the emulated heartbeat) a
 *			      module_param
L
Linus Torvalds 已提交
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>
57 58
#include <linux/io.h>
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
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 */
86 87
/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
static int timeout = WATCHDOG_TIMEOUT;
L
Linus Torvalds 已提交
88
module_param(timeout, int, 0);
89 90 91
MODULE_PARM_DESC(timeout,
	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
L
Linus Torvalds 已提交
92 93


94
static int nowayout = WATCHDOG_NOWAYOUT;
L
Linus Torvalds 已提交
95
module_param(nowayout, int, 0);
96 97 98
MODULE_PARM_DESC(nowayout,
		"Watchdog cannot be stopped once started (default="
				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
L
Linus Torvalds 已提交
99 100

static void wdt_timer_ping(unsigned long);
J
Jiri Slaby 已提交
101
static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0);
L
Linus Torvalds 已提交
102 103 104
static unsigned long next_heartbeat;
static unsigned long wdt_is_open;
static char wdt_expect_close;
105
static DEFINE_SPINLOCK(wdt_spinlock);
L
Linus Torvalds 已提交
106 107 108 109 110 111 112 113 114 115

/*
 *	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
	 */
116
	if (time_before(jiffies, next_heartbeat)) {
L
Linus Torvalds 已提交
117 118 119 120 121 122 123
		/* Ping the WDT */
		spin_lock(&wdt_spinlock);

		/* Ping the WDT by reading from WDT_PING */
		inb_p(WDT_PING);

		/* Re-set the timer interval */
J
Jiri Slaby 已提交
124
		mod_timer(&timer, jiffies + WDT_INTERVAL);
L
Linus Torvalds 已提交
125 126 127

		spin_unlock(&wdt_spinlock);

128 129 130
	} else
		printk(KERN_WARNING PFX
			"Heartbeat lost! Will not ping the watchdog\n");
L
Linus Torvalds 已提交
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 163
}

/*
 * 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 */
J
Jiri Slaby 已提交
164
	mod_timer(&timer, jiffies + WDT_INTERVAL);
L
Linus Torvalds 已提交
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.\n");
}

static void wdt_turnoff(void)
{
	/* Stop the timer */
	del_timer(&timer);

	wdt_change(WDT_DISABLE);

	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
}

static void wdt_keepalive(void)
{
	/* user land ping */
	next_heartbeat = jiffies + (timeout * HZ);
}

/*
 * /dev/watchdog handling
 */

191 192
static ssize_t fop_write(struct file *file, const char __user *buf,
						size_t count, loff_t *ppos)
L
Linus Torvalds 已提交
193 194
{
	/* See if we got the magic character 'V' and reload the timer */
195 196
	if (count) {
		if (!nowayout) {
L
Linus Torvalds 已提交
197 198
			size_t ofs;

199 200
			/* note: just in case someone wrote the magic
			   character five months ago... */
L
Linus Torvalds 已提交
201 202
			wdt_expect_close = 0;

203 204 205
			/* scan to see whether or not we got the
			   magic character */
			for (ofs = 0; ofs != count; ofs++) {
L
Linus Torvalds 已提交
206 207 208 209 210 211 212 213 214 215 216 217 218 219
				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;
}

220
static int fop_open(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
221 222
{
	/* Just in case we're already talking to someone... */
223
	if (test_and_set_bit(0, &wdt_is_open))
L
Linus Torvalds 已提交
224 225 226 227 228 229 230
		return -EBUSY;

	/* Good, fire up the show */
	wdt_startup();
	return nonseekable_open(inode, file);
}

231
static int fop_close(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
232
{
233
	if (wdt_expect_close == 42)
L
Linus Torvalds 已提交
234 235 236
		wdt_turnoff();
	else {
		del_timer(&timer);
237 238
		printk(KERN_CRIT PFX
		  "device file closed unexpectedly. Will not stop the WDT!\n");
L
Linus Torvalds 已提交
239 240 241 242 243 244
	}
	clear_bit(0, &wdt_is_open);
	wdt_expect_close = 0;
	return 0;
}

245
static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
246 247 248
{
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
249 250 251
	static const struct watchdog_info ident = {
		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
							| WDIOF_MAGICCLOSE,
L
Linus Torvalds 已提交
252 253 254 255
		.firmware_version = 1,
		.identity = "W83877F",
	};

256 257 258 259 260 261 262
	switch (cmd) {
	case WDIOC_GETSUPPORT:
		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
	case WDIOC_GETSTATUS:
	case WDIOC_GETBOOTSTATUS:
		return put_user(0, p);
	case WDIOC_SETOPTIONS:
L
Linus Torvalds 已提交
263
	{
264
		int new_options, retval = -EINVAL;
L
Linus Torvalds 已提交
265

266 267
		if (get_user(new_options, p))
			return -EFAULT;
L
Linus Torvalds 已提交
268

269 270 271
		if (new_options & WDIOS_DISABLECARD) {
			wdt_turnoff();
			retval = 0;
L
Linus Torvalds 已提交
272 273
		}

274 275 276 277
		if (new_options & WDIOS_ENABLECARD) {
			wdt_startup();
			retval = 0;
		}
L
Linus Torvalds 已提交
278

279 280
		return retval;
	}
281 282 283
	case WDIOC_KEEPALIVE:
		wdt_keepalive();
		return 0;
284 285 286
	case WDIOC_SETTIMEOUT:
	{
		int new_timeout;
L
Linus Torvalds 已提交
287

288 289 290 291 292 293 294 295 296 297 298 299 300
		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);
301 302
	default:
		return -ENOTTY;
L
Linus Torvalds 已提交
303 304 305
	}
}

306
static const struct file_operations wdt_fops = {
L
Linus Torvalds 已提交
307 308 309 310 311
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.write		= fop_write,
	.open		= fop_open,
	.release	= fop_close,
312
	.unlocked_ioctl	= fop_ioctl,
L
Linus Torvalds 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
};

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)
{
328
	if (code == SYS_DOWN || code == SYS_HALT)
L
Linus Torvalds 已提交
329 330 331 332 333 334 335 336 337
		wdt_turnoff();
	return NOTIFY_DONE;
}

/*
 *	The WDT needs to learn about soft shutdowns in order to
 *	turn the timebomb registers off.
 */

338
static struct notifier_block wdt_notifier = {
L
Linus Torvalds 已提交
339 340 341 342 343 344 345 346 347 348 349
	.notifier_call = wdt_notify_sys,
};

static void __exit w83877f_wdt_unload(void)
{
	wdt_turnoff();

	/* Deregister */
	misc_deregister(&wdt_miscdev);

	unregister_reboot_notifier(&wdt_notifier);
350 351
	release_region(WDT_PING, 1);
	release_region(ENABLE_W83877F_PORT, 2);
L
Linus Torvalds 已提交
352 353 354 355 356 357
}

static int __init w83877f_wdt_init(void)
{
	int rc = -EBUSY;

358
	if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
L
Linus Torvalds 已提交
359
		timeout = WATCHDOG_TIMEOUT;
360 361 362
		printk(KERN_INFO PFX
			"timeout value must be 1 <= x <= 3600, using %d\n",
							timeout);
L
Linus Torvalds 已提交
363 364
	}

365
	if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) {
L
Linus Torvalds 已提交
366 367 368 369 370 371
		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
			ENABLE_W83877F_PORT);
		rc = -EIO;
		goto err_out;
	}

372
	if (!request_region(WDT_PING, 1, "W8387FF WDT")) {
L
Linus Torvalds 已提交
373 374 375 376 377 378
		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
			WDT_PING);
		rc = -EIO;
		goto err_out_region1;
	}

379
	rc = register_reboot_notifier(&wdt_notifier);
380 381 382
	if (rc) {
		printk(KERN_ERR PFX
			"cannot register reboot notifier (err=%d)\n", rc);
L
Linus Torvalds 已提交
383 384 385
		goto err_out_region2;
	}

386
	rc = misc_register(&wdt_miscdev);
387 388 389 390
	if (rc) {
		printk(KERN_ERR PFX
			"cannot register miscdev on minor=%d (err=%d)\n",
							wdt_miscdev.minor, rc);
391
		goto err_out_reboot;
L
Linus Torvalds 已提交
392 393
	}

394 395
	printk(KERN_INFO PFX
	  "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
L
Linus Torvalds 已提交
396 397 398 399
		timeout, nowayout);

	return 0;

400 401
err_out_reboot:
	unregister_reboot_notifier(&wdt_notifier);
L
Linus Torvalds 已提交
402
err_out_region2:
403
	release_region(WDT_PING, 1);
L
Linus Torvalds 已提交
404
err_out_region1:
405
	release_region(ENABLE_W83877F_PORT, 2);
L
Linus Torvalds 已提交
406 407 408 409 410 411 412 413 414 415 416
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);