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