acquirewdt.c 8.6 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3
/*
 *	Acquire Single Board Computer Watchdog Timer driver
 *
4
 *	Based on wdt.c. Original copyright messages:
Linus Torvalds's avatar
Linus Torvalds committed
5
 *
6 7
 *	(c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
 *						All Rights Reserved.
Linus Torvalds's avatar
Linus Torvalds committed
8 9 10 11 12 13 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.
 *
 *	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.
 *
18
 *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
Linus Torvalds's avatar
Linus Torvalds committed
19
 *
20 21 22
 *	14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
 *	    Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
 *	    Can't add timeout - driver doesn't allow changing value
Linus Torvalds's avatar
Linus Torvalds committed
23 24 25 26 27 28
 */

/*
 *	Theory of Operation:
 *		The Watch-Dog Timer is provided to ensure that standalone
 *		Systems can always recover from catastrophic conditions that
29
 *		caused the CPU to crash. This condition may have occurred by
Linus Torvalds's avatar
Linus Torvalds committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
 *		external EMI or a software bug. When the CPU stops working
 *		correctly, hardware on the board will either perform a hardware
 *		reset (cold boot) or a non-maskable interrupt (NMI) to bring the
 *		system back to a known state.
 *
 *		The Watch-Dog Timer is controlled by two I/O Ports.
 *		  443 hex	- Read	- Enable or refresh the Watch-Dog Timer
 *		  043 hex	- Read	- Disable the Watch-Dog Timer
 *
 *		To enable the Watch-Dog Timer, a read from I/O port 443h must
 *		be performed. This will enable and activate the countdown timer
 *		which will eventually time out and either reset the CPU or cause
 *		an NMI depending on the setting of a jumper. To ensure that this
 *		reset condition does not occur, the Watch-Dog Timer must be
 *		periodically refreshed by reading the same I/O port 443h.
 *		The Watch-Dog Timer is disabled by reading I/O port 043h.
 *
 *		The Watch-Dog Timer Time-Out Period is set via jumpers.
 *		It can be 1, 2, 10, 20, 110 or 220 seconds.
 */

51 52 53
/*
 *	Includes, defines, variables, module parameters, ...
 */
Linus Torvalds's avatar
Linus Torvalds committed
54

55 56
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

57 58 59 60 61 62
/* Includes */
#include <linux/module.h>		/* For module specific items */
#include <linux/moduleparam.h>		/* For new moduleparam's */
#include <linux/types.h>		/* For standard types (like size_t) */
#include <linux/errno.h>		/* For the -ENODEV/... values */
#include <linux/kernel.h>		/* For printk/panic/... */
63 64
#include <linux/miscdevice.h>		/* For MODULE_ALIAS_MISCDEV
							(WATCHDOG_MINOR) */
65 66 67
#include <linux/watchdog.h>		/* For the watchdog specific items */
#include <linux/fs.h>			/* For file operations */
#include <linux/ioport.h>		/* For io-port access */
68
#include <linux/platform_device.h>	/* For platform_driver framework */
69
#include <linux/init.h>			/* For __init/__exit/... */
70 71
#include <linux/uaccess.h>		/* For copy_to_user/put_user/... */
#include <linux/io.h>			/* For inb/outb/... */
72 73 74

/* Module information */
#define DRV_NAME "acquirewdt"
Linus Torvalds's avatar
Linus Torvalds committed
75
#define WATCHDOG_NAME "Acquire WDT"
76 77
/* There is no way to see what the correct time-out period is */
#define WATCHDOG_HEARTBEAT 0
Linus Torvalds's avatar
Linus Torvalds committed
78

79
/* internal variables */
80 81
/* the watchdog platform device */
static struct platform_device *acq_platform_device;
Linus Torvalds's avatar
Linus Torvalds committed
82 83 84
static unsigned long acq_is_open;
static char expect_close;

85
/* module parameters */
86 87
/* You must set this - there is no sane way to probe for this board. */
static int wdt_stop = 0x43;
Linus Torvalds's avatar
Linus Torvalds committed
88 89 90
module_param(wdt_stop, int, 0);
MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");

91 92
/* You must set this - there is no sane way to probe for this board. */
static int wdt_start = 0x443;
Linus Torvalds's avatar
Linus Torvalds committed
93 94 95
module_param(wdt_start, int, 0);
MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");

96 97
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
98 99 100
MODULE_PARM_DESC(nowayout,
	"Watchdog cannot be stopped once started (default="
	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
Linus Torvalds's avatar
Linus Torvalds committed
101 102

/*
103
 *	Watchdog Operations
Linus Torvalds's avatar
Linus Torvalds committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
 */

static void acq_keepalive(void)
{
	/* Write a watchdog value */
	inb_p(wdt_start);
}

static void acq_stop(void)
{
	/* Turn the card off */
	inb_p(wdt_stop);
}

/*
119
 *	/dev/watchdog handling
Linus Torvalds's avatar
Linus Torvalds committed
120 121
 */

122 123
static ssize_t acq_write(struct file *file, const char __user *buf,
						size_t count, loff_t *ppos)
Linus Torvalds's avatar
Linus Torvalds committed
124 125
{
	/* See if we got the magic character 'V' and reload the timer */
126
	if (count) {
Linus Torvalds's avatar
Linus Torvalds committed
127 128 129
		if (!nowayout) {
			size_t i;
			/* note: just in case someone wrote the magic character
130
			   five months ago... */
Linus Torvalds's avatar
Linus Torvalds committed
131
			expect_close = 0;
132 133
			/* scan to see whether or not we got the
			   magic character */
Linus Torvalds's avatar
Linus Torvalds committed
134 135 136 137 138 139 140 141
			for (i = 0; i != count; i++) {
				char c;
				if (get_user(c, buf + i))
					return -EFAULT;
				if (c == 'V')
					expect_close = 42;
			}
		}
142 143
		/* Well, anyhow someone wrote to us, we should
				return that favour */
Linus Torvalds's avatar
Linus Torvalds committed
144 145 146 147 148
		acq_keepalive();
	}
	return count;
}

149
static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
Linus Torvalds's avatar
Linus Torvalds committed
150 151 152 153
{
	int options, retval = -EINVAL;
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
154
	static const struct watchdog_info ident = {
Linus Torvalds's avatar
Linus Torvalds committed
155 156
		.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
		.firmware_version = 1,
157
		.identity = WATCHDOG_NAME,
Linus Torvalds's avatar
Linus Torvalds committed
158 159
	};

160
	switch (cmd) {
Linus Torvalds's avatar
Linus Torvalds committed
161
	case WDIOC_GETSUPPORT:
162
		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
Linus Torvalds's avatar
Linus Torvalds committed
163 164 165

	case WDIOC_GETSTATUS:
	case WDIOC_GETBOOTSTATUS:
166
		return put_user(0, p);
Linus Torvalds's avatar
Linus Torvalds committed
167 168 169

	case WDIOC_SETOPTIONS:
	{
170 171 172 173 174 175 176 177 178 179 180
		if (get_user(options, p))
			return -EFAULT;
		if (options & WDIOS_DISABLECARD) {
			acq_stop();
			retval = 0;
		}
		if (options & WDIOS_ENABLECARD) {
			acq_keepalive();
			retval = 0;
		}
		return retval;
Linus Torvalds's avatar
Linus Torvalds committed
181
	}
182 183 184 185 186 187 188
	case WDIOC_KEEPALIVE:
		acq_keepalive();
		return 0;

	case WDIOC_GETTIMEOUT:
		return put_user(WATCHDOG_HEARTBEAT, p);

Linus Torvalds's avatar
Linus Torvalds committed
189
	default:
190
		return -ENOTTY;
Linus Torvalds's avatar
Linus Torvalds committed
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
	}
}

static int acq_open(struct inode *inode, struct file *file)
{
	if (test_and_set_bit(0, &acq_is_open))
		return -EBUSY;

	if (nowayout)
		__module_get(THIS_MODULE);

	/* Activate */
	acq_keepalive();
	return nonseekable_open(inode, file);
}

static int acq_close(struct inode *inode, struct file *file)
{
	if (expect_close == 42) {
		acq_stop();
	} else {
212
		pr_crit("Unexpected close, not stopping watchdog!\n");
Linus Torvalds's avatar
Linus Torvalds committed
213 214 215 216 217 218 219 220 221 222 223
		acq_keepalive();
	}
	clear_bit(0, &acq_is_open);
	expect_close = 0;
	return 0;
}

/*
 *	Kernel Interfaces
 */

224
static const struct file_operations acq_fops = {
Linus Torvalds's avatar
Linus Torvalds committed
225 226 227
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.write		= acq_write,
228
	.unlocked_ioctl	= acq_ioctl,
Linus Torvalds's avatar
Linus Torvalds committed
229 230 231 232
	.open		= acq_open,
	.release	= acq_close,
};

233 234 235 236
static struct miscdevice acq_miscdev = {
	.minor	= WATCHDOG_MINOR,
	.name	= "watchdog",
	.fops	= &acq_fops,
Linus Torvalds's avatar
Linus Torvalds committed
237 238
};

239 240 241 242
/*
 *	Init & exit routines
 */

243
static int acq_probe(struct platform_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
244 245 246 247 248
{
	int ret;

	if (wdt_stop != wdt_start) {
		if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
249
			pr_err("I/O address 0x%04x already in use\n", wdt_stop);
Linus Torvalds's avatar
Linus Torvalds committed
250 251 252 253 254 255
			ret = -EIO;
			goto out;
		}
	}

	if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
256
		pr_err("I/O address 0x%04x already in use\n", wdt_start);
Linus Torvalds's avatar
Linus Torvalds committed
257 258 259 260 261
		ret = -EIO;
		goto unreg_stop;
	}
	ret = misc_register(&acq_miscdev);
	if (ret != 0) {
262 263
		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
		       WATCHDOG_MINOR, ret);
264
		goto unreg_regions;
Linus Torvalds's avatar
Linus Torvalds committed
265
	}
266
	pr_info("initialized. (nowayout=%d)\n", nowayout);
Linus Torvalds's avatar
Linus Torvalds committed
267 268 269 270 271 272 273 274 275 276 277

	return 0;
unreg_regions:
	release_region(wdt_start, 1);
unreg_stop:
	if (wdt_stop != wdt_start)
		release_region(wdt_stop, 1);
out:
	return ret;
}

278
static int acq_remove(struct platform_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
279 280
{
	misc_deregister(&acq_miscdev);
281 282 283
	release_region(wdt_start, 1);
	if (wdt_stop != wdt_start)
		release_region(wdt_stop, 1);
284 285 286 287

	return 0;
}

288 289 290 291 292 293
static void acq_shutdown(struct platform_device *dev)
{
	/* Turn the WDT off if we have a soft shutdown */
	acq_stop();
}

294 295
static struct platform_driver acquirewdt_driver = {
	.probe		= acq_probe,
296
	.remove		= acq_remove,
297
	.shutdown	= acq_shutdown,
298 299 300 301 302 303 304 305 306 307
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= DRV_NAME,
	},
};

static int __init acq_init(void)
{
	int err;

308
	pr_info("WDT driver for Acquire single board computer initialising\n");
309 310 311 312 313

	err = platform_driver_register(&acquirewdt_driver);
	if (err)
		return err;

314 315
	acq_platform_device = platform_device_register_simple(DRV_NAME,
								-1, NULL, 0);
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
	if (IS_ERR(acq_platform_device)) {
		err = PTR_ERR(acq_platform_device);
		goto unreg_platform_driver;
	}
	return 0;

unreg_platform_driver:
	platform_driver_unregister(&acquirewdt_driver);
	return err;
}

static void __exit acq_exit(void)
{
	platform_device_unregister(acq_platform_device);
	platform_driver_unregister(&acquirewdt_driver);
331
	pr_info("Watchdog Module Unloaded\n");
Linus Torvalds's avatar
Linus Torvalds committed
332 333 334 335 336 337 338 339 340
}

module_init(acq_init);
module_exit(acq_exit);

MODULE_AUTHOR("David Woodhouse");
MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);