1 /* 2 * IndyDog 0.3 A Hardware Watchdog Device for SGI IP22 3 * 4 * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, 5 * All Rights Reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 10 * 2 of the License, or (at your option) any later version. 11 * 12 * based on softdog.c by Alan Cox <alan@lxorguk.ukuu.org.uk> 13 */ 14 15 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 16 17 #include <linux/module.h> 18 #include <linux/moduleparam.h> 19 #include <linux/types.h> 20 #include <linux/kernel.h> 21 #include <linux/fs.h> 22 #include <linux/mm.h> 23 #include <linux/miscdevice.h> 24 #include <linux/watchdog.h> 25 #include <linux/notifier.h> 26 #include <linux/reboot.h> 27 #include <linux/init.h> 28 #include <linux/uaccess.h> 29 #include <asm/sgi/mc.h> 30 31 static unsigned long indydog_alive; 32 static DEFINE_SPINLOCK(indydog_lock); 33 34 #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ 35 36 static bool nowayout = WATCHDOG_NOWAYOUT; 37 module_param(nowayout, bool, 0); 38 MODULE_PARM_DESC(nowayout, 39 "Watchdog cannot be stopped once started (default=" 40 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 41 42 static void indydog_start(void) 43 { 44 spin_lock(&indydog_lock); 45 sgimc->cpuctrl0 |= SGIMC_CCTRL0_WDOG; 46 spin_unlock(&indydog_lock); 47 } 48 49 static void indydog_stop(void) 50 { 51 spin_lock(&indydog_lock); 52 sgimc->cpuctrl0 &= ~SGIMC_CCTRL0_WDOG; 53 spin_unlock(&indydog_lock); 54 55 pr_info("Stopped watchdog timer\n"); 56 } 57 58 static void indydog_ping(void) 59 { 60 sgimc->watchdogt = 0; 61 } 62 63 /* 64 * Allow only one person to hold it open 65 */ 66 static int indydog_open(struct inode *inode, struct file *file) 67 { 68 if (test_and_set_bit(0, &indydog_alive)) 69 return -EBUSY; 70 71 if (nowayout) 72 __module_get(THIS_MODULE); 73 74 /* Activate timer */ 75 indydog_start(); 76 indydog_ping(); 77 78 pr_info("Started watchdog timer\n"); 79 80 return nonseekable_open(inode, file); 81 } 82 83 static int indydog_release(struct inode *inode, struct file *file) 84 { 85 /* Shut off the timer. 86 * Lock it in if it's a module and we defined ...NOWAYOUT */ 87 if (!nowayout) 88 indydog_stop(); /* Turn the WDT off */ 89 clear_bit(0, &indydog_alive); 90 return 0; 91 } 92 93 static ssize_t indydog_write(struct file *file, const char *data, 94 size_t len, loff_t *ppos) 95 { 96 /* Refresh the timer. */ 97 if (len) 98 indydog_ping(); 99 return len; 100 } 101 102 static long indydog_ioctl(struct file *file, unsigned int cmd, 103 unsigned long arg) 104 { 105 int options, retval = -EINVAL; 106 static const struct watchdog_info ident = { 107 .options = WDIOF_KEEPALIVEPING, 108 .firmware_version = 0, 109 .identity = "Hardware Watchdog for SGI IP22", 110 }; 111 112 switch (cmd) { 113 case WDIOC_GETSUPPORT: 114 if (copy_to_user((struct watchdog_info *)arg, 115 &ident, sizeof(ident))) 116 return -EFAULT; 117 return 0; 118 case WDIOC_GETSTATUS: 119 case WDIOC_GETBOOTSTATUS: 120 return put_user(0, (int *)arg); 121 case WDIOC_SETOPTIONS: 122 { 123 if (get_user(options, (int *)arg)) 124 return -EFAULT; 125 if (options & WDIOS_DISABLECARD) { 126 indydog_stop(); 127 retval = 0; 128 } 129 if (options & WDIOS_ENABLECARD) { 130 indydog_start(); 131 retval = 0; 132 } 133 return retval; 134 } 135 case WDIOC_KEEPALIVE: 136 indydog_ping(); 137 return 0; 138 case WDIOC_GETTIMEOUT: 139 return put_user(WATCHDOG_TIMEOUT, (int *)arg); 140 default: 141 return -ENOTTY; 142 } 143 } 144 145 static int indydog_notify_sys(struct notifier_block *this, 146 unsigned long code, void *unused) 147 { 148 if (code == SYS_DOWN || code == SYS_HALT) 149 indydog_stop(); /* Turn the WDT off */ 150 151 return NOTIFY_DONE; 152 } 153 154 static const struct file_operations indydog_fops = { 155 .owner = THIS_MODULE, 156 .llseek = no_llseek, 157 .write = indydog_write, 158 .unlocked_ioctl = indydog_ioctl, 159 .open = indydog_open, 160 .release = indydog_release, 161 }; 162 163 static struct miscdevice indydog_miscdev = { 164 .minor = WATCHDOG_MINOR, 165 .name = "watchdog", 166 .fops = &indydog_fops, 167 }; 168 169 static struct notifier_block indydog_notifier = { 170 .notifier_call = indydog_notify_sys, 171 }; 172 173 static int __init watchdog_init(void) 174 { 175 int ret; 176 177 ret = register_reboot_notifier(&indydog_notifier); 178 if (ret) { 179 pr_err("cannot register reboot notifier (err=%d)\n", ret); 180 return ret; 181 } 182 183 ret = misc_register(&indydog_miscdev); 184 if (ret) { 185 pr_err("cannot register miscdev on minor=%d (err=%d)\n", 186 WATCHDOG_MINOR, ret); 187 unregister_reboot_notifier(&indydog_notifier); 188 return ret; 189 } 190 191 pr_info("Hardware Watchdog Timer for SGI IP22: 0.3\n"); 192 193 return 0; 194 } 195 196 static void __exit watchdog_exit(void) 197 { 198 misc_deregister(&indydog_miscdev); 199 unregister_reboot_notifier(&indydog_notifier); 200 } 201 202 module_init(watchdog_init); 203 module_exit(watchdog_exit); 204 205 MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); 206 MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); 207 MODULE_LICENSE("GPL"); 208