1 /* 2 * IBM Automatic Server Restart driver. 3 * 4 * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru> 5 * 6 * Based on driver written by Pete Reynolds. 7 * Copyright (c) IBM Corporation, 1998-2004. 8 * 9 * This software may be used and distributed according to the terms 10 * of the GNU Public License, incorporated herein by reference. 11 */ 12 13 #include <linux/fs.h> 14 #include <linux/kernel.h> 15 #include <linux/slab.h> 16 #include <linux/module.h> 17 #include <linux/pci.h> 18 #include <linux/timer.h> 19 #include <linux/miscdevice.h> 20 #include <linux/watchdog.h> 21 #include <linux/dmi.h> 22 23 #include <asm/io.h> 24 #include <asm/uaccess.h> 25 26 27 enum { 28 ASMTYPE_UNKNOWN, 29 ASMTYPE_TOPAZ, 30 ASMTYPE_JASPER, 31 ASMTYPE_PEARL, 32 ASMTYPE_JUNIPER, 33 ASMTYPE_SPRUCE, 34 }; 35 36 #define PFX "ibmasr: " 37 38 #define TOPAZ_ASR_REG_OFFSET 4 39 #define TOPAZ_ASR_TOGGLE 0x40 40 #define TOPAZ_ASR_DISABLE 0x80 41 42 /* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */ 43 #define PEARL_BASE 0xe04 44 #define PEARL_WRITE 0xe06 45 #define PEARL_READ 0xe07 46 47 #define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */ 48 #define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */ 49 50 /* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */ 51 #define JASPER_ASR_REG_OFFSET 0x38 52 53 #define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */ 54 #define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ 55 56 #define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */ 57 #define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */ 58 #define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ 59 60 #define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */ 61 #define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */ 62 #define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */ 63 64 65 static int nowayout = WATCHDOG_NOWAYOUT; 66 67 static unsigned long asr_is_open; 68 static char asr_expect_close; 69 70 static unsigned int asr_type, asr_base, asr_length; 71 static unsigned int asr_read_addr, asr_write_addr; 72 static unsigned char asr_toggle_mask, asr_disable_mask; 73 74 static void asr_toggle(void) 75 { 76 unsigned char reg = inb(asr_read_addr); 77 78 outb(reg & ~asr_toggle_mask, asr_write_addr); 79 reg = inb(asr_read_addr); 80 81 outb(reg | asr_toggle_mask, asr_write_addr); 82 reg = inb(asr_read_addr); 83 84 outb(reg & ~asr_toggle_mask, asr_write_addr); 85 reg = inb(asr_read_addr); 86 } 87 88 static void asr_enable(void) 89 { 90 unsigned char reg; 91 92 if (asr_type == ASMTYPE_TOPAZ) { 93 /* asr_write_addr == asr_read_addr */ 94 reg = inb(asr_read_addr); 95 outb(reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE), 96 asr_read_addr); 97 } else { 98 /* 99 * First make sure the hardware timer is reset by toggling 100 * ASR hardware timer line. 101 */ 102 asr_toggle(); 103 104 reg = inb(asr_read_addr); 105 outb(reg & ~asr_disable_mask, asr_write_addr); 106 } 107 reg = inb(asr_read_addr); 108 } 109 110 static void asr_disable(void) 111 { 112 unsigned char reg = inb(asr_read_addr); 113 114 if (asr_type == ASMTYPE_TOPAZ) 115 /* asr_write_addr == asr_read_addr */ 116 outb(reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE, 117 asr_read_addr); 118 else { 119 outb(reg | asr_toggle_mask, asr_write_addr); 120 reg = inb(asr_read_addr); 121 122 outb(reg | asr_disable_mask, asr_write_addr); 123 } 124 reg = inb(asr_read_addr); 125 } 126 127 static int __init asr_get_base_address(void) 128 { 129 unsigned char low, high; 130 const char *type = ""; 131 132 asr_length = 1; 133 134 switch (asr_type) { 135 case ASMTYPE_TOPAZ: 136 /* SELECT SuperIO CHIP FOR QUERYING (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ 137 outb(0x07, 0x2e); 138 outb(0x07, 0x2f); 139 140 /* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */ 141 outb(0x60, 0x2e); 142 high = inb(0x2f); 143 144 /* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */ 145 outb(0x61, 0x2e); 146 low = inb(0x2f); 147 148 asr_base = (high << 16) | low; 149 asr_read_addr = asr_write_addr = 150 asr_base + TOPAZ_ASR_REG_OFFSET; 151 asr_length = 5; 152 153 break; 154 155 case ASMTYPE_JASPER: 156 type = "Jaspers "; 157 158 /* FIXME: need to use pci_config_lock here, but it's not exported */ 159 160 /* spin_lock_irqsave(&pci_config_lock, flags);*/ 161 162 /* Select the SuperIO chip in the PCI I/O port register */ 163 outl(0x8000f858, 0xcf8); 164 165 /* 166 * Read the base address for the SuperIO chip. 167 * Only the lower 16 bits are valid, but the address is word 168 * aligned so the last bit must be masked off. 169 */ 170 asr_base = inl(0xcfc) & 0xfffe; 171 172 /* spin_unlock_irqrestore(&pci_config_lock, flags);*/ 173 174 asr_read_addr = asr_write_addr = 175 asr_base + JASPER_ASR_REG_OFFSET; 176 asr_toggle_mask = JASPER_ASR_TOGGLE_MASK; 177 asr_disable_mask = JASPER_ASR_DISABLE_MASK; 178 asr_length = JASPER_ASR_REG_OFFSET + 1; 179 180 break; 181 182 case ASMTYPE_PEARL: 183 type = "Pearls "; 184 asr_base = PEARL_BASE; 185 asr_read_addr = PEARL_READ; 186 asr_write_addr = PEARL_WRITE; 187 asr_toggle_mask = PEARL_ASR_TOGGLE_MASK; 188 asr_disable_mask = PEARL_ASR_DISABLE_MASK; 189 asr_length = 4; 190 break; 191 192 case ASMTYPE_JUNIPER: 193 type = "Junipers "; 194 asr_base = JUNIPER_BASE_ADDRESS; 195 asr_read_addr = asr_write_addr = asr_base; 196 asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK; 197 asr_disable_mask = JUNIPER_ASR_DISABLE_MASK; 198 break; 199 200 case ASMTYPE_SPRUCE: 201 type = "Spruce's "; 202 asr_base = SPRUCE_BASE_ADDRESS; 203 asr_read_addr = asr_write_addr = asr_base; 204 asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK; 205 asr_disable_mask = SPRUCE_ASR_DISABLE_MASK; 206 break; 207 } 208 209 if (!request_region(asr_base, asr_length, "ibmasr")) { 210 printk(KERN_ERR PFX "address %#x already in use\n", 211 asr_base); 212 return -EBUSY; 213 } 214 215 printk(KERN_INFO PFX "found %sASR @ addr %#x\n", type, asr_base); 216 217 return 0; 218 } 219 220 221 static ssize_t asr_write(struct file *file, const char __user *buf, 222 size_t count, loff_t *ppos) 223 { 224 if (count) { 225 if (!nowayout) { 226 size_t i; 227 228 /* In case it was set long ago */ 229 asr_expect_close = 0; 230 231 for (i = 0; i != count; i++) { 232 char c; 233 if (get_user(c, buf + i)) 234 return -EFAULT; 235 if (c == 'V') 236 asr_expect_close = 42; 237 } 238 } 239 asr_toggle(); 240 } 241 return count; 242 } 243 244 static int asr_ioctl(struct inode *inode, struct file *file, 245 unsigned int cmd, unsigned long arg) 246 { 247 static const struct watchdog_info ident = { 248 .options = WDIOF_KEEPALIVEPING | 249 WDIOF_MAGICCLOSE, 250 .identity = "IBM ASR" 251 }; 252 void __user *argp = (void __user *)arg; 253 int __user *p = argp; 254 int heartbeat; 255 256 switch (cmd) { 257 case WDIOC_GETSUPPORT: 258 return copy_to_user(argp, &ident, sizeof(ident)) ? 259 -EFAULT : 0; 260 261 case WDIOC_GETSTATUS: 262 case WDIOC_GETBOOTSTATUS: 263 return put_user(0, p); 264 265 case WDIOC_KEEPALIVE: 266 asr_toggle(); 267 return 0; 268 269 /* 270 * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT 271 * and WDIOC_GETTIMEOUT always returns 256. 272 */ 273 case WDIOC_GETTIMEOUT: 274 heartbeat = 256; 275 return put_user(heartbeat, p); 276 277 case WDIOC_SETOPTIONS: { 278 int new_options, retval = -EINVAL; 279 280 if (get_user(new_options, p)) 281 return -EFAULT; 282 283 if (new_options & WDIOS_DISABLECARD) { 284 asr_disable(); 285 retval = 0; 286 } 287 288 if (new_options & WDIOS_ENABLECARD) { 289 asr_enable(); 290 asr_toggle(); 291 retval = 0; 292 } 293 294 return retval; 295 } 296 } 297 298 return -ENOTTY; 299 } 300 301 static int asr_open(struct inode *inode, struct file *file) 302 { 303 if(test_and_set_bit(0, &asr_is_open)) 304 return -EBUSY; 305 306 asr_toggle(); 307 asr_enable(); 308 309 return nonseekable_open(inode, file); 310 } 311 312 static int asr_release(struct inode *inode, struct file *file) 313 { 314 if (asr_expect_close == 42) 315 asr_disable(); 316 else { 317 printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); 318 asr_toggle(); 319 } 320 clear_bit(0, &asr_is_open); 321 asr_expect_close = 0; 322 return 0; 323 } 324 325 static const struct file_operations asr_fops = { 326 .owner = THIS_MODULE, 327 .llseek = no_llseek, 328 .write = asr_write, 329 .ioctl = asr_ioctl, 330 .open = asr_open, 331 .release = asr_release, 332 }; 333 334 static struct miscdevice asr_miscdev = { 335 .minor = WATCHDOG_MINOR, 336 .name = "watchdog", 337 .fops = &asr_fops, 338 }; 339 340 341 struct ibmasr_id { 342 const char *desc; 343 int type; 344 }; 345 346 static struct ibmasr_id __initdata ibmasr_id_table[] = { 347 { "IBM Automatic Server Restart - eserver xSeries 220", ASMTYPE_TOPAZ }, 348 { "IBM Automatic Server Restart - Machine Type 8673", ASMTYPE_PEARL }, 349 { "IBM Automatic Server Restart - Machine Type 8480", ASMTYPE_JASPER }, 350 { "IBM Automatic Server Restart - Machine Type 8482", ASMTYPE_JUNIPER }, 351 { "IBM Automatic Server Restart - Machine Type 8648", ASMTYPE_SPRUCE }, 352 { NULL } 353 }; 354 355 static int __init ibmasr_init(void) 356 { 357 struct ibmasr_id *id; 358 int rc; 359 360 for (id = ibmasr_id_table; id->desc; id++) { 361 if (dmi_find_device(DMI_DEV_TYPE_OTHER, id->desc, NULL)) { 362 asr_type = id->type; 363 break; 364 } 365 } 366 367 if (!asr_type) 368 return -ENODEV; 369 370 rc = asr_get_base_address(); 371 if (rc) 372 return rc; 373 374 rc = misc_register(&asr_miscdev); 375 if (rc < 0) { 376 release_region(asr_base, asr_length); 377 printk(KERN_ERR PFX "failed to register misc device\n"); 378 return rc; 379 } 380 381 return 0; 382 } 383 384 static void __exit ibmasr_exit(void) 385 { 386 if (!nowayout) 387 asr_disable(); 388 389 misc_deregister(&asr_miscdev); 390 391 release_region(asr_base, asr_length); 392 } 393 394 module_init(ibmasr_init); 395 module_exit(ibmasr_exit); 396 397 module_param(nowayout, int, 0); 398 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 399 400 MODULE_DESCRIPTION("IBM Automatic Server Restart driver"); 401 MODULE_AUTHOR("Andrey Panin"); 402 MODULE_LICENSE("GPL"); 403 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 404