11da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
21da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Watchdog implementation based on z/VM Watchdog Timer API
31da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * Copyright IBM Corp. 2004,2009
558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger *
61da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * The user space watchdog daemon can use this driver as
71da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * /dev/vmwatchdog to have z/VM execute the specified CP
81da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * command when the timeout expires. The default command is
91da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * "IPL", which which cause an immediate reboot.
101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger#define KMSG_COMPONENT "vmwatchdog"
1258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
1358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger
141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/init.h>
151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/fs.h>
161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/kernel.h>
171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/miscdevice.h>
181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/module.h>
191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/moduleparam.h>
205a0e3ad6af8660be21ca98a971cd00f331318c05Tejun Heo#include <linux/slab.h>
2158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger#include <linux/suspend.h>
221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/watchdog.h>
231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/ebcdic.h>
251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/io.h>
261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/uaccess.h>
271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define MAX_CMDLEN 240
291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define MIN_INTERVAL 15
301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic char vmwdt_cmd[MAX_CMDLEN] = "IPL";
3190ab5ee94171b3e28de6bb42ee30b527014e0be7Rusty Russellstatic bool vmwdt_conceal;
321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3390ab5ee94171b3e28de6bb42ee30b527014e0be7Rusty Russellstatic bool vmwdt_nowayout = WATCHDOG_NOWAYOUT;
341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_LICENSE("GPL");
361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_DESCRIPTION("z/VM Watchdog Timer");
381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param_string(cmd, vmwdt_cmd, MAX_CMDLEN, 0644);
391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(cmd, "CP command that is run when the watchdog triggers");
401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param_named(conceal, vmwdt_conceal, bool, 0644);
411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(conceal, "Enable the CONCEAL CP option while the watchdog "
421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		" is active");
431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param_named(nowayout, vmwdt_nowayout, bool, 0);
441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		" (default=CONFIG_WATCHDOG_NOWAYOUT)");
461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned int vmwdt_interval = 60;
491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long vmwdt_is_open;
501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int vmwdt_expect_close;
511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
52feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaeferstatic DEFINE_MUTEX(vmwdt_mutex);
53feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer
5458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger#define VMWDT_OPEN	0	/* devnode is open or suspend in progress */
5558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger#define VMWDT_RUNNING	1	/* The watchdog is armed */
5658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger
571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsenum vmwdt_func {
581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* function codes */
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_init   = 0,
601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_change = 1,
611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_cancel = 2,
621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* flags */
631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_conceal = 0x80000000,
641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int __diag288(enum vmwdt_func func, unsigned int timeout,
671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			    char *cmd, size_t len)
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
6994c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky	register unsigned long __func asm("2") = func;
7094c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky	register unsigned long __timeout asm("3") = timeout;
7194c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky	register unsigned long __cmdp asm("4") = virt_to_phys(cmd);
7294c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky	register unsigned long __cmdl asm("5") = len;
731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int err;
741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
7594c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky	err = -EINVAL;
7694c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky	asm volatile(
7794c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky		"	diag	%1,%3,0x288\n"
7894c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky		"0:	la	%0,0\n"
7994c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky		"1:\n"
8094c12cc7d196bab34aaa98d38521549fa1e5ef76Martin Schwidefsky		EX_TABLE(0b,1b)
812b12f996c992334f65f6d9e0a3e731ec049fdad3Heiko Carstens		: "+d" (err) : "d"(__func), "d"(__timeout),
822b12f996c992334f65f6d9e0a3e731ec049fdad3Heiko Carstens		  "d"(__cmdp), "d"(__cmdl) : "1", "cc");
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return err;
841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int vmwdt_keepalive(void)
871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* we allocate new memory every time to avoid having
891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * to track the state. static allocation is not an
901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * option since that might not be contiguous in real
911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * storage in case of a modular build */
921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	static char *ebc_cmd;
931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	size_t len;
941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int ret;
951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned int func;
961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL);
981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!ebc_cmd)
991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -ENOMEM;
1001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	len = strlcpy(ebc_cmd, vmwdt_cmd, MAX_CMDLEN);
1021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	ASCEBC(ebc_cmd, MAX_CMDLEN);
1031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
1041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init;
10658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	set_bit(VMWDT_RUNNING, &vmwdt_is_open);
1071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	ret = __diag288(func, vmwdt_interval, ebc_cmd, len);
1080d130066801af8f0a0ea8c70c9c7374c51fd1a92Martin Schwidefsky	WARN_ON(ret != 0);
1091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	kfree(ebc_cmd);
1101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return ret;
1111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int vmwdt_disable(void)
1141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int ret = __diag288(wdt_cancel, 0, "", 0);
1160d130066801af8f0a0ea8c70c9c7374c51fd1a92Martin Schwidefsky	WARN_ON(ret != 0);
11758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	clear_bit(VMWDT_RUNNING, &vmwdt_is_open);
1181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return ret;
1191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int __init vmwdt_probe(void)
1221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* there is no real way to see if the watchdog is supported,
1241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * so we try initializing it with a NOP command ("BEGIN")
1251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * that won't cause any harm even if the following disable
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * fails for some reason */
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	static char __initdata ebc_begin[] = {
1281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		194, 197, 199, 201, 213
1291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	};
1300d130066801af8f0a0ea8c70c9c7374c51fd1a92Martin Schwidefsky	if (__diag288(wdt_init, 15, ebc_begin, sizeof(ebc_begin)) != 0)
1311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EINVAL;
1321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return vmwdt_disable();
1331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int vmwdt_open(struct inode *i, struct file *f)
1361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int ret;
138feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	if (test_and_set_bit(VMWDT_OPEN, &vmwdt_is_open))
1391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EBUSY;
1401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	ret = vmwdt_keepalive();
1411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (ret)
14258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		clear_bit(VMWDT_OPEN, &vmwdt_is_open);
1431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return ret ? ret : nonseekable_open(i, f);
1441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int vmwdt_close(struct inode *i, struct file *f)
1471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (vmwdt_expect_close == 42)
1491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		vmwdt_disable();
1501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	vmwdt_expect_close = 0;
15158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	clear_bit(VMWDT_OPEN, &vmwdt_is_open);
1521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
1531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic struct watchdog_info vmwdt_info = {
1561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
1571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.firmware_version = 0,
1581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.identity = "z/VM Watchdog Timer",
1591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
1601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
161feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaeferstatic int __vmwdt_ioctl(unsigned int cmd, unsigned long arg)
1621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	switch (cmd) {
1641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_GETSUPPORT:
1651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (copy_to_user((void __user *)arg, &vmwdt_info,
1661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					sizeof(vmwdt_info)))
1671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return -EFAULT;
1681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return 0;
1691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_GETSTATUS:
1701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_GETBOOTSTATUS:
171d2c993d845781d160a7ef759a3e65c6892c4a270Heiko Carstens		return put_user(0, (int __user *)arg);
1721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_GETTEMP:
1731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EINVAL;
1741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_SETOPTIONS:
1751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		{
1761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			int options, ret;
1771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (get_user(options, (int __user *)arg))
1781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return -EFAULT;
1791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			ret = -EINVAL;
1801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (options & WDIOS_DISABLECARD) {
1811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				ret = vmwdt_disable();
1821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if (ret)
1831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					return ret;
1841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
1851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (options & WDIOS_ENABLECARD) {
1861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				ret = vmwdt_keepalive();
1871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
1881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return ret;
1891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
1901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_GETTIMEOUT:
1911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return put_user(vmwdt_interval, (int __user *)arg);
1921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_SETTIMEOUT:
1931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		{
1941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			int interval;
1951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (get_user(interval, (int __user *)arg))
1961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return -EFAULT;
1971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (interval < MIN_INTERVAL)
1981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return -EINVAL;
1991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			vmwdt_interval = interval;
2001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return vmwdt_keepalive();
2021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	case WDIOC_KEEPALIVE:
2031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return vmwdt_keepalive();
2041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return -EINVAL;
2061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
208feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaeferstatic long vmwdt_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
209feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer{
210feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	int rc;
211feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer
212feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	mutex_lock(&vmwdt_mutex);
213feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	rc = __vmwdt_ioctl(cmd, arg);
214feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	mutex_unlock(&vmwdt_mutex);
215feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	return (long) rc;
216feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer}
217feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer
2181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic ssize_t vmwdt_write(struct file *f, const char __user *buf,
2191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				size_t count, loff_t *ppos)
2201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(count) {
2221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (!vmwdt_nowayout) {
2231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size_t i;
2241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* note: just in case someone wrote the magic character
2261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			 * five months ago... */
2271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			vmwdt_expect_close = 0;
2281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			for (i = 0; i != count; i++) {
2301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				char c;
2311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if (get_user(c, buf+i))
2321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					return -EFAULT;
2331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if (c == 'V')
2341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					vmwdt_expect_close = 42;
2351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
2361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* someone wrote to us, we should restart timer */
2381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		vmwdt_keepalive();
2391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return count;
2411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
24358872d5f367876a81dcda82465381d80d321a81dChristian Borntraegerstatic int vmwdt_resume(void)
24458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger{
24558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	clear_bit(VMWDT_OPEN, &vmwdt_is_open);
24658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	return NOTIFY_DONE;
24758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger}
24858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger
24958872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger/*
25058872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * It makes no sense to go into suspend while the watchdog is running.
25158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * Depending on the memory size, the watchdog might trigger, while we
25258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * are still saving the memory.
25358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * We reuse the open flag to ensure that suspend and watchdog open are
25458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * exclusive operations
25558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger */
25658872d5f367876a81dcda82465381d80d321a81dChristian Borntraegerstatic int vmwdt_suspend(void)
25758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger{
25858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	if (test_and_set_bit(VMWDT_OPEN, &vmwdt_is_open)) {
2592c48c4d631970b70d60a4f926b0f68f194a0d559Christian Borntraeger		pr_err("The system cannot be suspended while the watchdog"
2602c48c4d631970b70d60a4f926b0f68f194a0d559Christian Borntraeger			" is in use\n");
261f0c077a8b7f9dce590c760a7b2f3c417dffa52d1Akinobu Mita		return notifier_from_errno(-EBUSY);
26258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	}
26358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	if (test_bit(VMWDT_RUNNING, &vmwdt_is_open)) {
26458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		clear_bit(VMWDT_OPEN, &vmwdt_is_open);
2652c48c4d631970b70d60a4f926b0f68f194a0d559Christian Borntraeger		pr_err("The system cannot be suspended while the watchdog"
2662c48c4d631970b70d60a4f926b0f68f194a0d559Christian Borntraeger			" is running\n");
267f0c077a8b7f9dce590c760a7b2f3c417dffa52d1Akinobu Mita		return notifier_from_errno(-EBUSY);
26858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	}
26958872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	return NOTIFY_DONE;
27058872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger}
27158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger
27258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger/*
27358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger * This function is called for suspend and resume.
27458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger */
27558872d5f367876a81dcda82465381d80d321a81dChristian Borntraegerstatic int vmwdt_power_event(struct notifier_block *this, unsigned long event,
27658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger			     void *ptr)
27758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger{
27858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	switch (event) {
27958872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	case PM_POST_HIBERNATION:
28058872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	case PM_POST_SUSPEND:
28158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		return vmwdt_resume();
28258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	case PM_HIBERNATION_PREPARE:
28358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	case PM_SUSPEND_PREPARE:
28458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		return vmwdt_suspend();
28558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	default:
28658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		return NOTIFY_DONE;
28758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	}
28858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger}
28958872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger
29058872d5f367876a81dcda82465381d80d321a81dChristian Borntraegerstatic struct notifier_block vmwdt_power_notifier = {
29158872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	.notifier_call = vmwdt_power_event,
29258872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger};
29358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger
294d54b1fdb1d9f82e375a299e22bd366aad52d4c34Arjan van de Venstatic const struct file_operations vmwdt_fops = {
2951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.open    = &vmwdt_open,
2961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.release = &vmwdt_close,
297feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	.unlocked_ioctl = &vmwdt_ioctl,
2981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.write   = &vmwdt_write,
2991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.owner   = THIS_MODULE,
3006038f373a3dc1f1c26496e60b6c40b164716f07eArnd Bergmann	.llseek  = noop_llseek,
3011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
3021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic struct miscdevice vmwdt_dev = {
3041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.minor      = WATCHDOG_MINOR,
3051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.name       = "watchdog",
3061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.fops       = &vmwdt_fops,
3071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
3081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int __init vmwdt_init(void)
3101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int ret;
3121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	ret = vmwdt_probe();
3141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (ret)
3151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return ret;
31658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	ret = register_pm_notifier(&vmwdt_power_notifier);
31758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	if (ret)
31858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		return ret;
319feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	/*
320feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	 * misc_register() has to be the last action in module_init(), because
321feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	 * file operations will be available right after this.
322feb5c5a450a5e56f3be09a18b541996c86bd94bcGerald Schaefer	 */
32358872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	ret = misc_register(&vmwdt_dev);
32458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	if (ret) {
32558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		unregister_pm_notifier(&vmwdt_power_notifier);
32658872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger		return ret;
32758872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	}
32858872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	return 0;
3291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_init(vmwdt_init);
3311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void __exit vmwdt_exit(void)
3331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
33458872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	unregister_pm_notifier(&vmwdt_power_notifier);
33558872d5f367876a81dcda82465381d80d321a81dChristian Borntraeger	misc_deregister(&vmwdt_dev);
3361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_exit(vmwdt_exit);
338