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