linux/drivers/watchdog/acquirewdt.c
<<
>>
Prefs
   1/*
   2 *      Acquire Single Board Computer Watchdog Timer driver
   3 *
   4 *      Based on wdt.c. Original copyright messages:
   5 *
   6 *      (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
   7 *                                              All Rights Reserved.
   8 *
   9 *      This program is free software; you can redistribute it and/or
  10 *      modify it under the terms of the GNU General Public License
  11 *      as published by the Free Software Foundation; either version
  12 *      2 of the License, or (at your option) any later version.
  13 *
  14 *      Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
  15 *      warranty for any of this software. This material is provided
  16 *      "AS-IS" and at no charge.
  17 *
  18 *      (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
  19 *
  20 *      14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
  21 *          Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
  22 *          Can't add timeout - driver doesn't allow changing value
  23 */
  24
  25/*
  26 *      Theory of Operation:
  27 *              The Watch-Dog Timer is provided to ensure that standalone
  28 *              Systems can always recover from catastrophic conditions that
  29 *              caused the CPU to crash. This condition may have occurred by
  30 *              external EMI or a software bug. When the CPU stops working
  31 *              correctly, hardware on the board will either perform a hardware
  32 *              reset (cold boot) or a non-maskable interrupt (NMI) to bring the
  33 *              system back to a known state.
  34 *
  35 *              The Watch-Dog Timer is controlled by two I/O Ports.
  36 *                443 hex       - Read  - Enable or refresh the Watch-Dog Timer
  37 *                043 hex       - Read  - Disable the Watch-Dog Timer
  38 *
  39 *              To enable the Watch-Dog Timer, a read from I/O port 443h must
  40 *              be performed. This will enable and activate the countdown timer
  41 *              which will eventually time out and either reset the CPU or cause
  42 *              an NMI depending on the setting of a jumper. To ensure that this
  43 *              reset condition does not occur, the Watch-Dog Timer must be
  44 *              periodically refreshed by reading the same I/O port 443h.
  45 *              The Watch-Dog Timer is disabled by reading I/O port 043h.
  46 *
  47 *              The Watch-Dog Timer Time-Out Period is set via jumpers.
  48 *              It can be 1, 2, 10, 20, 110 or 220 seconds.
  49 */
  50
  51/*
  52 *      Includes, defines, variables, module parameters, ...
  53 */
  54
  55/* Includes */
  56#include <linux/module.h>               /* For module specific items */
  57#include <linux/moduleparam.h>          /* For new moduleparam's */
  58#include <linux/types.h>                /* For standard types (like size_t) */
  59#include <linux/errno.h>                /* For the -ENODEV/... values */
  60#include <linux/kernel.h>               /* For printk/panic/... */
  61#include <linux/miscdevice.h>           /* For MODULE_ALIAS_MISCDEV
  62                                                        (WATCHDOG_MINOR) */
  63#include <linux/watchdog.h>             /* For the watchdog specific items */
  64#include <linux/fs.h>                   /* For file operations */
  65#include <linux/ioport.h>               /* For io-port access */
  66#include <linux/platform_device.h>      /* For platform_driver framework */
  67#include <linux/init.h>                 /* For __init/__exit/... */
  68#include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  69#include <linux/io.h>                   /* For inb/outb/... */
  70
  71/* Module information */
  72#define DRV_NAME "acquirewdt"
  73#define PFX DRV_NAME ": "
  74#define WATCHDOG_NAME "Acquire WDT"
  75/* There is no way to see what the correct time-out period is */
  76#define WATCHDOG_HEARTBEAT 0
  77
  78/* internal variables */
  79/* the watchdog platform device */
  80static struct platform_device *acq_platform_device;
  81static unsigned long acq_is_open;
  82static char expect_close;
  83
  84/* module parameters */
  85/* You must set this - there is no sane way to probe for this board. */
  86static int wdt_stop = 0x43;
  87module_param(wdt_stop, int, 0);
  88MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
  89
  90/* You must set this - there is no sane way to probe for this board. */
  91static int wdt_start = 0x443;
  92module_param(wdt_start, int, 0);
  93MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
  94
  95static int nowayout = WATCHDOG_NOWAYOUT;
  96module_param(nowayout, int, 0);
  97MODULE_PARM_DESC(nowayout,
  98        "Watchdog cannot be stopped once started (default="
  99        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 100
 101/*
 102 *      Watchdog Operations
 103 */
 104
 105static void acq_keepalive(void)
 106{
 107        /* Write a watchdog value */
 108        inb_p(wdt_start);
 109}
 110
 111static void acq_stop(void)
 112{
 113        /* Turn the card off */
 114        inb_p(wdt_stop);
 115}
 116
 117/*
 118 *      /dev/watchdog handling
 119 */
 120
 121static ssize_t acq_write(struct file *file, const char __user *buf,
 122                                                size_t count, loff_t *ppos)
 123{
 124        /* See if we got the magic character 'V' and reload the timer */
 125        if (count) {
 126                if (!nowayout) {
 127                        size_t i;
 128                        /* note: just in case someone wrote the magic character
 129                           five months ago... */
 130                        expect_close = 0;
 131                        /* scan to see whether or not we got the
 132                           magic character */
 133                        for (i = 0; i != count; i++) {
 134                                char c;
 135                                if (get_user(c, buf + i))
 136                                        return -EFAULT;
 137                                if (c == 'V')
 138                                        expect_close = 42;
 139                        }
 140                }
 141                /* Well, anyhow someone wrote to us, we should
 142                                return that favour */
 143                acq_keepalive();
 144        }
 145        return count;
 146}
 147
 148static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 149{
 150        int options, retval = -EINVAL;
 151        void __user *argp = (void __user *)arg;
 152        int __user *p = argp;
 153        static const struct watchdog_info ident = {
 154                .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 155                .firmware_version = 1,
 156                .identity = WATCHDOG_NAME,
 157        };
 158
 159        switch (cmd) {
 160        case WDIOC_GETSUPPORT:
 161                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 162
 163        case WDIOC_GETSTATUS:
 164        case WDIOC_GETBOOTSTATUS:
 165                return put_user(0, p);
 166
 167        case WDIOC_SETOPTIONS:
 168        {
 169                if (get_user(options, p))
 170                        return -EFAULT;
 171                if (options & WDIOS_DISABLECARD) {
 172                        acq_stop();
 173                        retval = 0;
 174                }
 175                if (options & WDIOS_ENABLECARD) {
 176                        acq_keepalive();
 177                        retval = 0;
 178                }
 179                return retval;
 180        }
 181        case WDIOC_KEEPALIVE:
 182                acq_keepalive();
 183                return 0;
 184
 185        case WDIOC_GETTIMEOUT:
 186                return put_user(WATCHDOG_HEARTBEAT, p);
 187
 188        default:
 189                return -ENOTTY;
 190        }
 191}
 192
 193static int acq_open(struct inode *inode, struct file *file)
 194{
 195        if (test_and_set_bit(0, &acq_is_open))
 196                return -EBUSY;
 197
 198        if (nowayout)
 199                __module_get(THIS_MODULE);
 200
 201        /* Activate */
 202        acq_keepalive();
 203        return nonseekable_open(inode, file);
 204}
 205
 206static int acq_close(struct inode *inode, struct file *file)
 207{
 208        if (expect_close == 42) {
 209                acq_stop();
 210        } else {
 211                printk(KERN_CRIT PFX
 212                        "Unexpected close, not stopping watchdog!\n");
 213                acq_keepalive();
 214        }
 215        clear_bit(0, &acq_is_open);
 216        expect_close = 0;
 217        return 0;
 218}
 219
 220/*
 221 *      Kernel Interfaces
 222 */
 223
 224static const struct file_operations acq_fops = {
 225        .owner          = THIS_MODULE,
 226        .llseek         = no_llseek,
 227        .write          = acq_write,
 228        .unlocked_ioctl = acq_ioctl,
 229        .open           = acq_open,
 230        .release        = acq_close,
 231};
 232
 233static struct miscdevice acq_miscdev = {
 234        .minor  = WATCHDOG_MINOR,
 235        .name   = "watchdog",
 236        .fops   = &acq_fops,
 237};
 238
 239/*
 240 *      Init & exit routines
 241 */
 242
 243static int __devinit acq_probe(struct platform_device *dev)
 244{
 245        int ret;
 246
 247        if (wdt_stop != wdt_start) {
 248                if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
 249                        printk(KERN_ERR PFX
 250                            "I/O address 0x%04x already in use\n", wdt_stop);
 251                        ret = -EIO;
 252                        goto out;
 253                }
 254        }
 255
 256        if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
 257                printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
 258                        wdt_start);
 259                ret = -EIO;
 260                goto unreg_stop;
 261        }
 262        ret = misc_register(&acq_miscdev);
 263        if (ret != 0) {
 264                printk(KERN_ERR PFX
 265                        "cannot register miscdev on minor=%d (err=%d)\n",
 266                                                        WATCHDOG_MINOR, ret);
 267                goto unreg_regions;
 268        }
 269        printk(KERN_INFO PFX "initialized. (nowayout=%d)\n", nowayout);
 270
 271        return 0;
 272unreg_regions:
 273        release_region(wdt_start, 1);
 274unreg_stop:
 275        if (wdt_stop != wdt_start)
 276                release_region(wdt_stop, 1);
 277out:
 278        return ret;
 279}
 280
 281static int __devexit acq_remove(struct platform_device *dev)
 282{
 283        misc_deregister(&acq_miscdev);
 284        release_region(wdt_start, 1);
 285        if (wdt_stop != wdt_start)
 286                release_region(wdt_stop, 1);
 287
 288        return 0;
 289}
 290
 291static void acq_shutdown(struct platform_device *dev)
 292{
 293        /* Turn the WDT off if we have a soft shutdown */
 294        acq_stop();
 295}
 296
 297static struct platform_driver acquirewdt_driver = {
 298        .probe          = acq_probe,
 299        .remove         = __devexit_p(acq_remove),
 300        .shutdown       = acq_shutdown,
 301        .driver         = {
 302                .owner  = THIS_MODULE,
 303                .name   = DRV_NAME,
 304        },
 305};
 306
 307static int __init acq_init(void)
 308{
 309        int err;
 310
 311        printk(KERN_INFO
 312              "WDT driver for Acquire single board computer initialising.\n");
 313
 314        err = platform_driver_register(&acquirewdt_driver);
 315        if (err)
 316                return err;
 317
 318        acq_platform_device = platform_device_register_simple(DRV_NAME,
 319                                                                -1, NULL, 0);
 320        if (IS_ERR(acq_platform_device)) {
 321                err = PTR_ERR(acq_platform_device);
 322                goto unreg_platform_driver;
 323        }
 324        return 0;
 325
 326unreg_platform_driver:
 327        platform_driver_unregister(&acquirewdt_driver);
 328        return err;
 329}
 330
 331static void __exit acq_exit(void)
 332{
 333        platform_device_unregister(acq_platform_device);
 334        platform_driver_unregister(&acquirewdt_driver);
 335        printk(KERN_INFO PFX "Watchdog Module Unloaded.\n");
 336}
 337
 338module_init(acq_init);
 339module_exit(acq_exit);
 340
 341MODULE_AUTHOR("David Woodhouse");
 342MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
 343MODULE_LICENSE("GPL");
 344MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 345