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@redhat.com> 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 indydog_alive = 1; 87 printk(KERN_INFO "Started watchdog timer.\n"); 88 89 return nonseekable_open(inode, file); 90 } 91 92 static int indydog_release(struct inode *inode, struct file *file) 93 { 94 /* Shut off the timer. 95 * Lock it in if it's a module and we defined ...NOWAYOUT */ 96 if (!nowayout) 97 indydog_stop(); /* Turn the WDT off */ 98 clear_bit(0, &indydog_alive); 99 return 0; 100 } 101 102 static ssize_t indydog_write(struct file *file, const char *data, 103 size_t len, loff_t *ppos) 104 { 105 /* Refresh the timer. */ 106 if (len) 107 indydog_ping(); 108 return len; 109 } 110 111 static long indydog_ioctl(struct file *file, unsigned int cmd, 112 unsigned long arg) 113 { 114 int options, retval = -EINVAL; 115 static struct watchdog_info ident = { 116 .options = WDIOF_KEEPALIVEPING | 117 WDIOF_MAGICCLOSE, 118 .firmware_version = 0, 119 .identity = "Hardware Watchdog for SGI IP22", 120 }; 121 122 switch (cmd) { 123 case WDIOC_GETSUPPORT: 124 if (copy_to_user((struct watchdog_info *)arg, 125 &ident, sizeof(ident))) 126 return -EFAULT; 127 return 0; 128 case WDIOC_GETSTATUS: 129 case WDIOC_GETBOOTSTATUS: 130 return put_user(0, (int *)arg); 131 case WDIOC_SETOPTIONS: 132 { 133 if (get_user(options, (int *)arg)) 134 return -EFAULT; 135 if (options & WDIOS_DISABLECARD) { 136 indydog_stop(); 137 retval = 0; 138 } 139 if (options & WDIOS_ENABLECARD) { 140 indydog_start(); 141 retval = 0; 142 } 143 return retval; 144 } 145 case WDIOC_KEEPALIVE: 146 indydog_ping(); 147 return 0; 148 case WDIOC_GETTIMEOUT: 149 return put_user(WATCHDOG_TIMEOUT, (int *)arg); 150 default: 151 return -ENOTTY; 152 } 153 } 154 155 static int indydog_notify_sys(struct notifier_block *this, 156 unsigned long code, void *unused) 157 { 158 if (code == SYS_DOWN || code == SYS_HALT) 159 indydog_stop(); /* Turn the WDT off */ 160 161 return NOTIFY_DONE; 162 } 163 164 static const struct file_operations indydog_fops = { 165 .owner = THIS_MODULE, 166 .llseek = no_llseek, 167 .write = indydog_write, 168 .unlocked_ioctl = indydog_ioctl, 169 .open = indydog_open, 170 .release = indydog_release, 171 }; 172 173 static struct miscdevice indydog_miscdev = { 174 .minor = WATCHDOG_MINOR, 175 .name = "watchdog", 176 .fops = &indydog_fops, 177 }; 178 179 static struct notifier_block indydog_notifier = { 180 .notifier_call = indydog_notify_sys, 181 }; 182 183 static char banner[] __initdata = 184 KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n"; 185 186 static int __init watchdog_init(void) 187 { 188 int ret; 189 190 spin_lock_init(&indydog_lock); 191 192 ret = register_reboot_notifier(&indydog_notifier); 193 if (ret) { 194 printk(KERN_ERR PFX 195 "cannot register reboot notifier (err=%d)\n", ret); 196 return ret; 197 } 198 199 ret = misc_register(&indydog_miscdev); 200 if (ret) { 201 printk(KERN_ERR PFX 202 "cannot register miscdev on minor=%d (err=%d)\n", 203 WATCHDOG_MINOR, ret); 204 unregister_reboot_notifier(&indydog_notifier); 205 return ret; 206 } 207 208 printk(banner); 209 210 return 0; 211 } 212 213 static void __exit watchdog_exit(void) 214 { 215 misc_deregister(&indydog_miscdev); 216 unregister_reboot_notifier(&indydog_notifier); 217 } 218 219 module_init(watchdog_init); 220 module_exit(watchdog_exit); 221 222 MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); 223 MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); 224 MODULE_LICENSE("GPL"); 225 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 226