linux/drivers/watchdog/ibmasr.c
<<
>>
Prefs
   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
  27enum {
  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
  65static int nowayout = WATCHDOG_NOWAYOUT;
  66
  67static unsigned long asr_is_open;
  68static char asr_expect_close;
  69
  70static unsigned int asr_type, asr_base, asr_length;
  71static unsigned int asr_read_addr, asr_write_addr;
  72static unsigned char asr_toggle_mask, asr_disable_mask;
  73
  74static 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
  88static 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
 110static 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
 127static 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
 221static 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
 244static 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
 301static 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
 312static 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
 325static 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
 334static struct miscdevice asr_miscdev = {
 335        .minor =        WATCHDOG_MINOR,
 336        .name =         "watchdog",
 337        .fops =         &asr_fops,
 338};
 339
 340
 341struct ibmasr_id {
 342        const char *desc;
 343        int type;
 344};
 345
 346static 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
 355static 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
 384static 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
 394module_init(ibmasr_init);
 395module_exit(ibmasr_exit);
 396
 397module_param(nowayout, int, 0);
 398MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 399
 400MODULE_DESCRIPTION("IBM Automatic Server Restart driver");
 401MODULE_AUTHOR("Andrey Panin");
 402MODULE_LICENSE("GPL");
 403MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 404