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