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        .open           = acq_open,
 225        .release        = acq_close,
 226};
 227
 228static struct miscdevice acq_miscdev = {
 229        .minor  = WATCHDOG_MINOR,
 230        .name   = "watchdog",
 231        .fops   = &acq_fops,
 232};
 233
 234/*
 235 *      Init & exit routines
 236 */
 237
 238static int __init acq_probe(struct platform_device *dev)
 239{
 240        int ret;
 241
 242        if (wdt_stop != wdt_start) {
 243                if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
 244                        pr_err("I/O address 0x%04x already in use\n", wdt_stop);
 245                        ret = -EIO;
 246                        goto out;
 247                }
 248        }
 249
 250        if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
 251                pr_err("I/O address 0x%04x already in use\n", wdt_start);
 252                ret = -EIO;
 253                goto unreg_stop;
 254        }
 255        ret = misc_register(&acq_miscdev);
 256        if (ret != 0) {
 257                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 258                       WATCHDOG_MINOR, ret);
 259                goto unreg_regions;
 260        }
 261        pr_info("initialized. (nowayout=%d)\n", nowayout);
 262
 263        return 0;
 264unreg_regions:
 265        release_region(wdt_start, 1);
 266unreg_stop:
 267        if (wdt_stop != wdt_start)
 268                release_region(wdt_stop, 1);
 269out:
 270        return ret;
 271}
 272
 273static int acq_remove(struct platform_device *dev)
 274{
 275        misc_deregister(&acq_miscdev);
 276        release_region(wdt_start, 1);
 277        if (wdt_stop != wdt_start)
 278                release_region(wdt_stop, 1);
 279
 280        return 0;
 281}
 282
 283static void acq_shutdown(struct platform_device *dev)
 284{
 285        /* Turn the WDT off if we have a soft shutdown */
 286        acq_stop();
 287}
 288
 289static struct platform_driver acquirewdt_driver = {
 290        .remove         = acq_remove,
 291        .shutdown       = acq_shutdown,
 292        .driver         = {
 293                .name   = DRV_NAME,
 294        },
 295};
 296
 297static int __init acq_init(void)
 298{
 299        int err;
 300
 301        pr_info("WDT driver for Acquire single board computer initialising\n");
 302
 303        acq_platform_device = platform_device_register_simple(DRV_NAME,
 304                                                                -1, NULL, 0);
 305        if (IS_ERR(acq_platform_device))
 306                return PTR_ERR(acq_platform_device);
 307
 308        err = platform_driver_probe(&acquirewdt_driver, acq_probe);
 309        if (err)
 310                goto unreg_platform_device;
 311        return 0;
 312
 313unreg_platform_device:
 314        platform_device_unregister(acq_platform_device);
 315        return err;
 316}
 317
 318static void __exit acq_exit(void)
 319{
 320        platform_device_unregister(acq_platform_device);
 321        platform_driver_unregister(&acquirewdt_driver);
 322        pr_info("Watchdog Module Unloaded\n");
 323}
 324
 325module_init(acq_init);
 326module_exit(acq_exit);
 327
 328MODULE_AUTHOR("David Woodhouse");
 329MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
 330MODULE_LICENSE("GPL");
 331