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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  56
  57/* Includes */
  58#include <linux/module.h>               /* For module specific items */
  59#include <linux/moduleparam.h>          /* For new moduleparam's */
  60#include <linux/types.h>                /* For standard types (like size_t) */
  61#include <linux/errno.h>                /* For the -ENODEV/... values */
  62#include <linux/kernel.h>               /* For printk/panic/... */
  63#include <linux/miscdevice.h>           /* For MODULE_ALIAS_MISCDEV
  64                                                        (WATCHDOG_MINOR) */
  65#include <linux/watchdog.h>             /* For the watchdog specific items */
  66#include <linux/fs.h>                   /* For file operations */
  67#include <linux/ioport.h>               /* For io-port access */
  68#include <linux/platform_device.h>      /* For platform_driver framework */
  69#include <linux/init.h>                 /* For __init/__exit/... */
  70#include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  71#include <linux/io.h>                   /* For inb/outb/... */
  72
  73/* Module information */
  74#define DRV_NAME "acquirewdt"
  75#define WATCHDOG_NAME "Acquire WDT"
  76/* There is no way to see what the correct time-out period is */
  77#define WATCHDOG_HEARTBEAT 0
  78
  79/* internal variables */
  80/* the watchdog platform device */
  81static struct platform_device *acq_platform_device;
  82static unsigned long acq_is_open;
  83static char expect_close;
  84
  85/* module parameters */
  86/* You must set this - there is no sane way to probe for this board. */
  87static int wdt_stop = 0x43;
  88module_param(wdt_stop, int, 0);
  89MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
  90
  91/* You must set this - there is no sane way to probe for this board. */
  92static int wdt_start = 0x443;
  93module_param(wdt_start, int, 0);
  94MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
  95
  96static bool nowayout = WATCHDOG_NOWAYOUT;
  97module_param(nowayout, bool, 0);
  98MODULE_PARM_DESC(nowayout,
  99        "Watchdog cannot be stopped once started (default="
 100        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 101
 102/*
 103 *      Watchdog Operations
 104 */
 105
 106static void acq_keepalive(void)
 107{
 108        /* Write a watchdog value */
 109        inb_p(wdt_start);
 110}
 111
 112static void acq_stop(void)
 113{
 114        /* Turn the card off */
 115        inb_p(wdt_stop);
 116}
 117
 118/*
 119 *      /dev/watchdog handling
 120 */
 121
 122static ssize_t acq_write(struct file *file, const char __user *buf,
 123                                                size_t count, loff_t *ppos)
 124{
 125        /* See if we got the magic character 'V' and reload the timer */
 126        if (count) {
 127                if (!nowayout) {
 128                        size_t i;
 129                        /* note: just in case someone wrote the magic character
 130                           five months ago... */
 131                        expect_close = 0;
 132                        /* scan to see whether or not we got the
 133                           magic character */
 134                        for (i = 0; i != count; i++) {
 135                                char c;
 136                                if (get_user(c, buf + i))
 137                                        return -EFAULT;
 138                                if (c == 'V')
 139                                        expect_close = 42;
 140                        }
 141                }
 142                /* Well, anyhow someone wrote to us, we should
 143                                return that favour */
 144                acq_keepalive();
 145        }
 146        return count;
 147}
 148
 149static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 150{
 151        int options, retval = -EINVAL;
 152        void __user *argp = (void __user *)arg;
 153        int __user *p = argp;
 154        static const struct watchdog_info ident = {
 155                .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 156                .firmware_version = 1,
 157                .identity = WATCHDOG_NAME,
 158        };
 159
 160        switch (cmd) {
 161        case WDIOC_GETSUPPORT:
 162                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 163
 164        case WDIOC_GETSTATUS:
 165        case WDIOC_GETBOOTSTATUS:
 166                return put_user(0, p);
 167
 168        case WDIOC_SETOPTIONS:
 169        {
 170                if (get_user(options, p))
 171                        return -EFAULT;
 172                if (options & WDIOS_DISABLECARD) {
 173                        acq_stop();
 174                        retval = 0;
 175                }
 176                if (options & WDIOS_ENABLECARD) {
 177                        acq_keepalive();
 178                        retval = 0;
 179                }
 180                return retval;
 181        }
 182        case WDIOC_KEEPALIVE:
 183                acq_keepalive();
 184                return 0;
 185
 186        case WDIOC_GETTIMEOUT:
 187                return put_user(WATCHDOG_HEARTBEAT, p);
 188
 189        default:
 190                return -ENOTTY;
 191        }
 192}
 193
 194static int acq_open(struct inode *inode, struct file *file)
 195{
 196        if (test_and_set_bit(0, &acq_is_open))
 197                return -EBUSY;
 198
 199        if (nowayout)
 200                __module_get(THIS_MODULE);
 201
 202        /* Activate */
 203        acq_keepalive();
 204        return nonseekable_open(inode, file);
 205}
 206
 207static int acq_close(struct inode *inode, struct file *file)
 208{
 209        if (expect_close == 42) {
 210                acq_stop();
 211        } else {
 212                pr_crit("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 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                        pr_err("I/O address 0x%04x already in use\n", wdt_stop);
 250                        ret = -EIO;
 251                        goto out;
 252                }
 253        }
 254
 255        if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
 256                pr_err("I/O address 0x%04x already in use\n", wdt_start);
 257                ret = -EIO;
 258                goto unreg_stop;
 259        }
 260        ret = misc_register(&acq_miscdev);
 261        if (ret != 0) {
 262                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 263                       WATCHDOG_MINOR, ret);
 264                goto unreg_regions;
 265        }
 266        pr_info("initialized. (nowayout=%d)\n", nowayout);
 267
 268        return 0;
 269unreg_regions:
 270        release_region(wdt_start, 1);
 271unreg_stop:
 272        if (wdt_stop != wdt_start)
 273                release_region(wdt_stop, 1);
 274out:
 275        return ret;
 276}
 277
 278static int acq_remove(struct platform_device *dev)
 279{
 280        misc_deregister(&acq_miscdev);
 281        release_region(wdt_start, 1);
 282        if (wdt_stop != wdt_start)
 283                release_region(wdt_stop, 1);
 284
 285        return 0;
 286}
 287
 288static void acq_shutdown(struct platform_device *dev)
 289{
 290        /* Turn the WDT off if we have a soft shutdown */
 291        acq_stop();
 292}
 293
 294static struct platform_driver acquirewdt_driver = {
 295        .probe          = acq_probe,
 296        .remove         = acq_remove,
 297        .shutdown       = acq_shutdown,
 298        .driver         = {
 299                .owner  = THIS_MODULE,
 300                .name   = DRV_NAME,
 301        },
 302};
 303
 304static int __init acq_init(void)
 305{
 306        int err;
 307
 308        pr_info("WDT driver for Acquire single board computer initialising\n");
 309
 310        err = platform_driver_register(&acquirewdt_driver);
 311        if (err)
 312                return err;
 313
 314        acq_platform_device = platform_device_register_simple(DRV_NAME,
 315                                                                -1, NULL, 0);
 316        if (IS_ERR(acq_platform_device)) {
 317                err = PTR_ERR(acq_platform_device);
 318                goto unreg_platform_driver;
 319        }
 320        return 0;
 321
 322unreg_platform_driver:
 323        platform_driver_unregister(&acquirewdt_driver);
 324        return err;
 325}
 326
 327static void __exit acq_exit(void)
 328{
 329        platform_device_unregister(acq_platform_device);
 330        platform_driver_unregister(&acquirewdt_driver);
 331        pr_info("Watchdog Module Unloaded\n");
 332}
 333
 334module_init(acq_init);
 335module_exit(acq_exit);
 336
 337MODULE_AUTHOR("David Woodhouse");
 338MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
 339MODULE_LICENSE("GPL");
 340MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 341