| /* |
| * Watchdog implementation based on z/VM Watchdog Timer API |
| * |
| * The user space watchdog daemon can use this driver as |
| * /dev/vmwatchdog to have z/VM execute the specified CP |
| * command when the timeout expires. The default command is |
| * "IPL", which which cause an immediate reboot. |
| */ |
| #include <linux/init.h> |
| #include <linux/fs.h> |
| #include <linux/kernel.h> |
| #include <linux/miscdevice.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/watchdog.h> |
| |
| #include <asm/ebcdic.h> |
| #include <asm/io.h> |
| #include <asm/uaccess.h> |
| |
| #define MAX_CMDLEN 240 |
| #define MIN_INTERVAL 15 |
| static char vmwdt_cmd[MAX_CMDLEN] = "IPL"; |
| static int vmwdt_conceal; |
| |
| #ifdef CONFIG_WATCHDOG_NOWAYOUT |
| static int vmwdt_nowayout = 1; |
| #else |
| static int vmwdt_nowayout = 0; |
| #endif |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>"); |
| MODULE_DESCRIPTION("z/VM Watchdog Timer"); |
| module_param_string(cmd, vmwdt_cmd, MAX_CMDLEN, 0644); |
| MODULE_PARM_DESC(cmd, "CP command that is run when the watchdog triggers"); |
| module_param_named(conceal, vmwdt_conceal, bool, 0644); |
| MODULE_PARM_DESC(conceal, "Enable the CONCEAL CP option while the watchdog " |
| " is active"); |
| module_param_named(nowayout, vmwdt_nowayout, bool, 0); |
| MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started" |
| " (default=CONFIG_WATCHDOG_NOWAYOUT)"); |
| MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
| |
| static unsigned int vmwdt_interval = 60; |
| static unsigned long vmwdt_is_open; |
| static int vmwdt_expect_close; |
| |
| enum vmwdt_func { |
| /* function codes */ |
| wdt_init = 0, |
| wdt_change = 1, |
| wdt_cancel = 2, |
| /* flags */ |
| wdt_conceal = 0x80000000, |
| }; |
| |
| static int __diag288(enum vmwdt_func func, unsigned int timeout, |
| char *cmd, size_t len) |
| { |
| register unsigned long __func asm("2"); |
| register unsigned long __timeout asm("3"); |
| register unsigned long __cmdp asm("4"); |
| register unsigned long __cmdl asm("5"); |
| int err; |
| |
| __func = func; |
| __timeout = timeout; |
| __cmdp = virt_to_phys(cmd); |
| __cmdl = len; |
| err = 0; |
| asm volatile ( |
| #ifdef __s390x__ |
| "diag %2,%4,0x288\n" |
| "1: \n" |
| ".section .fixup,\"ax\"\n" |
| "2: lghi %0,%1\n" |
| " jg 1b\n" |
| ".previous\n" |
| ".section __ex_table,\"a\"\n" |
| " .align 8\n" |
| " .quad 1b,2b\n" |
| ".previous\n" |
| #else |
| "diag %2,%4,0x288\n" |
| "1: \n" |
| ".section .fixup,\"ax\"\n" |
| "2: lhi %0,%1\n" |
| " bras 1,3f\n" |
| " .long 1b\n" |
| "3: l 1,0(1)\n" |
| " br 1\n" |
| ".previous\n" |
| ".section __ex_table,\"a\"\n" |
| " .align 4\n" |
| " .long 1b,2b\n" |
| ".previous\n" |
| #endif |
| : "+&d"(err) |
| : "i"(-EINVAL), "d"(__func), "d"(__timeout), |
| "d"(__cmdp), "d"(__cmdl) |
| : "1", "cc"); |
| return err; |
| } |
| |
| static int vmwdt_keepalive(void) |
| { |
| /* we allocate new memory every time to avoid having |
| * to track the state. static allocation is not an |
| * option since that might not be contiguous in real |
| * storage in case of a modular build */ |
| static char *ebc_cmd; |
| size_t len; |
| int ret; |
| unsigned int func; |
| |
| ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL); |
| if (!ebc_cmd) |
| return -ENOMEM; |
| |
| len = strlcpy(ebc_cmd, vmwdt_cmd, MAX_CMDLEN); |
| ASCEBC(ebc_cmd, MAX_CMDLEN); |
| EBC_TOUPPER(ebc_cmd, MAX_CMDLEN); |
| |
| func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init; |
| ret = __diag288(func, vmwdt_interval, ebc_cmd, len); |
| kfree(ebc_cmd); |
| |
| if (ret) { |
| printk(KERN_WARNING "%s: problem setting interval %d, " |
| "cmd %s\n", __FUNCTION__, vmwdt_interval, |
| vmwdt_cmd); |
| } |
| return ret; |
| } |
| |
| static int vmwdt_disable(void) |
| { |
| int ret = __diag288(wdt_cancel, 0, "", 0); |
| if (ret) { |
| printk(KERN_WARNING "%s: problem disabling watchdog\n", |
| __FUNCTION__); |
| } |
| return ret; |
| } |
| |
| static int __init vmwdt_probe(void) |
| { |
| /* there is no real way to see if the watchdog is supported, |
| * so we try initializing it with a NOP command ("BEGIN") |
| * that won't cause any harm even if the following disable |
| * fails for some reason */ |
| static char __initdata ebc_begin[] = { |
| 194, 197, 199, 201, 213 |
| }; |
| if (__diag288(wdt_init, 15, ebc_begin, sizeof(ebc_begin)) != 0) { |
| printk(KERN_INFO "z/VM watchdog not available\n"); |
| return -EINVAL; |
| } |
| return vmwdt_disable(); |
| } |
| |
| static int vmwdt_open(struct inode *i, struct file *f) |
| { |
| int ret; |
| if (test_and_set_bit(0, &vmwdt_is_open)) |
| return -EBUSY; |
| ret = vmwdt_keepalive(); |
| if (ret) |
| clear_bit(0, &vmwdt_is_open); |
| return ret ? ret : nonseekable_open(i, f); |
| } |
| |
| static int vmwdt_close(struct inode *i, struct file *f) |
| { |
| if (vmwdt_expect_close == 42) |
| vmwdt_disable(); |
| vmwdt_expect_close = 0; |
| clear_bit(0, &vmwdt_is_open); |
| return 0; |
| } |
| |
| static struct watchdog_info vmwdt_info = { |
| .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
| .firmware_version = 0, |
| .identity = "z/VM Watchdog Timer", |
| }; |
| |
| static int vmwdt_ioctl(struct inode *i, struct file *f, |
| unsigned int cmd, unsigned long arg) |
| { |
| switch (cmd) { |
| case WDIOC_GETSUPPORT: |
| if (copy_to_user((void __user *)arg, &vmwdt_info, |
| sizeof(vmwdt_info))) |
| return -EFAULT; |
| return 0; |
| case WDIOC_GETSTATUS: |
| case WDIOC_GETBOOTSTATUS: |
| return put_user(0, (int *)arg); |
| case WDIOC_GETTEMP: |
| return -EINVAL; |
| case WDIOC_SETOPTIONS: |
| { |
| int options, ret; |
| if (get_user(options, (int __user *)arg)) |
| return -EFAULT; |
| ret = -EINVAL; |
| if (options & WDIOS_DISABLECARD) { |
| ret = vmwdt_disable(); |
| if (ret) |
| return ret; |
| } |
| if (options & WDIOS_ENABLECARD) { |
| ret = vmwdt_keepalive(); |
| } |
| return ret; |
| } |
| case WDIOC_GETTIMEOUT: |
| return put_user(vmwdt_interval, (int __user *)arg); |
| case WDIOC_SETTIMEOUT: |
| { |
| int interval; |
| if (get_user(interval, (int __user *)arg)) |
| return -EFAULT; |
| if (interval < MIN_INTERVAL) |
| return -EINVAL; |
| vmwdt_interval = interval; |
| } |
| return vmwdt_keepalive(); |
| case WDIOC_KEEPALIVE: |
| return vmwdt_keepalive(); |
| } |
| |
| return -EINVAL; |
| } |
| |
| static ssize_t vmwdt_write(struct file *f, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| if(count) { |
| if (!vmwdt_nowayout) { |
| size_t i; |
| |
| /* note: just in case someone wrote the magic character |
| * five months ago... */ |
| vmwdt_expect_close = 0; |
| |
| for (i = 0; i != count; i++) { |
| char c; |
| if (get_user(c, buf+i)) |
| return -EFAULT; |
| if (c == 'V') |
| vmwdt_expect_close = 42; |
| } |
| } |
| /* someone wrote to us, we should restart timer */ |
| vmwdt_keepalive(); |
| } |
| return count; |
| } |
| |
| static struct file_operations vmwdt_fops = { |
| .open = &vmwdt_open, |
| .release = &vmwdt_close, |
| .ioctl = &vmwdt_ioctl, |
| .write = &vmwdt_write, |
| .owner = THIS_MODULE, |
| }; |
| |
| static struct miscdevice vmwdt_dev = { |
| .minor = WATCHDOG_MINOR, |
| .name = "watchdog", |
| .fops = &vmwdt_fops, |
| }; |
| |
| static int __init vmwdt_init(void) |
| { |
| int ret; |
| |
| ret = vmwdt_probe(); |
| if (ret) |
| return ret; |
| return misc_register(&vmwdt_dev); |
| } |
| module_init(vmwdt_init); |
| |
| static void __exit vmwdt_exit(void) |
| { |
| WARN_ON(misc_deregister(&vmwdt_dev) != 0); |
| } |
| module_exit(vmwdt_exit); |