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 struct miscdevice */
  64#include <linux/watchdog.h>             /* For the watchdog specific items */
  65#include <linux/fs.h>                   /* For file operations */
  66#include <linux/ioport.h>               /* For io-port access */
  67#include <linux/platform_device.h>      /* For platform_driver framework */
  68#include <linux/init.h>                 /* For __init/__exit/... */
  69#include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  70#include <linux/io.h>                   /* For inb/outb/... */
  71
  72/* Module information */
  73#define DRV_NAME "acquirewdt"
  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 bool nowayout = WATCHDOG_NOWAYOUT;
  96module_param(nowayout, bool, 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                pr_crit("Unexpected close, not stopping watchdog!\n");
 212                acq_keepalive();
 213        }
 214        clear_bit(0, &acq_is_open);
 215        expect_close = 0;
 216        return 0;
 217}
 218
 219/*
 220 *      Kernel Interfaces
 221 */
 222
 223static const struct file_operations acq_fops = {
 224        .owner          = THIS_MODULE,
 225        .llseek         = no_llseek,
 226        .write          = acq_write,
 227        .unlocked_ioctl = acq_ioctl,
 228        .open           = acq_open,
 229        .release        = acq_close,
 230};
 231
 232static struct miscdevice acq_miscdev = {
 233        .minor  = WATCHDOG_MINOR,
 234        .name   = "watchdog",
 235        .fops   = &acq_fops,
 236};
 237
 238/*
 239 *      Init & exit routines
 240 */
 241
 242static int __init acq_probe(struct platform_device *dev)
 243{
 244        int ret;
 245
 246        if (wdt_stop != wdt_start) {
 247                if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
 248                        pr_err("I/O address 0x%04x already in use\n", wdt_stop);
 249                        ret = -EIO;
 250                        goto out;
 251                }
 252        }
 253
 254        if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
 255                pr_err("I/O address 0x%04x already in use\n", wdt_start);
 256                ret = -EIO;
 257                goto unreg_stop;
 258        }
 259        ret = misc_register(&acq_miscdev);
 260        if (ret != 0) {
 261                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 262                       WATCHDOG_MINOR, ret);
 263                goto unreg_regions;
 264        }
 265        pr_info("initialized. (nowayout=%d)\n", nowayout);
 266
 267        return 0;
 268unreg_regions:
 269        release_region(wdt_start, 1);
 270unreg_stop:
 271        if (wdt_stop != wdt_start)
 272                release_region(wdt_stop, 1);
 273out:
 274        return ret;
 275}
 276
 277static int acq_remove(struct platform_device *dev)
 278{
 279        misc_deregister(&acq_miscdev);
 280        release_region(wdt_start, 1);
 281        if (wdt_stop != wdt_start)
 282                release_region(wdt_stop, 1);
 283
 284        return 0;
 285}
 286
 287static void acq_shutdown(struct platform_device *dev)
 288{
 289        /* Turn the WDT off if we have a soft shutdown */
 290        acq_stop();
 291}
 292
 293static struct platform_driver acquirewdt_driver = {
 294        .remove         = acq_remove,
 295        .shutdown       = acq_shutdown,
 296        .driver         = {
 297                .name   = DRV_NAME,
 298        },
 299};
 300
 301static int __init acq_init(void)
 302{
 303        int err;
 304
 305        pr_info("WDT driver for Acquire single board computer initialising\n");
 306
 307        acq_platform_device = platform_device_register_simple(DRV_NAME,
 308                                                                -1, NULL, 0);
 309        if (IS_ERR(acq_platform_device))
 310                return PTR_ERR(acq_platform_device);
 311
 312        err = platform_driver_probe(&acquirewdt_driver, acq_probe);
 313        if (err)
 314                goto unreg_platform_device;
 315        return 0;
 316
 317unreg_platform_device:
 318        platform_device_unregister(acq_platform_device);
 319        return err;
 320}
 321
 322static void __exit acq_exit(void)
 323{
 324        platform_device_unregister(acq_platform_device);
 325        platform_driver_unregister(&acquirewdt_driver);
 326        pr_info("Watchdog Module Unloaded\n");
 327}
 328
 329module_init(acq_init);
 330module_exit(acq_exit);
 331
 332MODULE_AUTHOR("David Woodhouse");
 333MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
 334MODULE_LICENSE("GPL");
 335