linux/drivers/watchdog/sbc60xxwdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *      60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x
   4 *
   5 *      Based on acquirewdt.c by Alan Cox.
   6 *
   7 *      The author does NOT admit liability nor provide warranty for
   8 *      any of this software. This material is provided "AS-IS" in
   9 *      the hope that it may be useful for others.
  10 *
  11 *      (c) Copyright 2000    Jakob Oestergaard <jakob@unthought.net>
  12 *
  13 *           12/4 - 2000      [Initial revision]
  14 *           25/4 - 2000      Added /dev/watchdog support
  15 *           09/5 - 2001      [smj@oro.net] fixed fop_write to "return 1"
  16 *                                      on success
  17 *           12/4 - 2002      [rob@osinvestor.com] eliminate fop_read
  18 *                            fix possible wdt_is_open race
  19 *                            add CONFIG_WATCHDOG_NOWAYOUT support
  20 *                            remove lock_kernel/unlock_kernel pairs
  21 *                            added KERN_* to printk's
  22 *                            got rid of extraneous comments
  23 *                            changed watchdog_info to correctly reflect what
  24 *                            the driver offers
  25 *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
  26 *                            WDIOC_SETTIMEOUT, WDIOC_GETTIMEOUT, and
  27 *                            WDIOC_SETOPTIONS ioctls
  28 *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
  29 *                            use module_param
  30 *                            made timeout (the emulated heartbeat) a
  31 *                            module_param
  32 *                            made the keepalive ping an internal subroutine
  33 *                            made wdt_stop and wdt_start module params
  34 *                            added extra printk's for startup problems
  35 *                            added MODULE_AUTHOR and MODULE_DESCRIPTION info
  36 *
  37 *  This WDT driver is different from the other Linux WDT
  38 *  drivers in the following ways:
  39 *  *)  The driver will ping the watchdog by itself, because this
  40 *      particular WDT has a very short timeout (one second) and it
  41 *      would be insane to count on any userspace daemon always
  42 *      getting scheduled within that time frame.
  43 */
  44
  45#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  46
  47#include <linux/module.h>
  48#include <linux/moduleparam.h>
  49#include <linux/types.h>
  50#include <linux/timer.h>
  51#include <linux/jiffies.h>
  52#include <linux/miscdevice.h>
  53#include <linux/watchdog.h>
  54#include <linux/fs.h>
  55#include <linux/ioport.h>
  56#include <linux/notifier.h>
  57#include <linux/reboot.h>
  58#include <linux/init.h>
  59#include <linux/io.h>
  60#include <linux/uaccess.h>
  61
  62
  63#define OUR_NAME "sbc60xxwdt"
  64#define PFX OUR_NAME ": "
  65
  66/*
  67 * You must set these - The driver cannot probe for the settings
  68 */
  69
  70static int wdt_stop = 0x45;
  71module_param(wdt_stop, int, 0);
  72MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
  73
  74static int wdt_start = 0x443;
  75module_param(wdt_start, int, 0);
  76MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
  77
  78/*
  79 * The 60xx board can use watchdog timeout values from one second
  80 * to several minutes.  The default is one second, so if we reset
  81 * the watchdog every ~250ms we should be safe.
  82 */
  83
  84#define WDT_INTERVAL (HZ/4+1)
  85
  86/*
  87 * We must not require too good response from the userspace daemon.
  88 * Here we require the userspace daemon to send us a heartbeat
  89 * char to /dev/watchdog every 30 seconds.
  90 * If the daemon pulses us every 25 seconds, we can still afford
  91 * a 5 second scheduling delay on the (high priority) daemon. That
  92 * should be sufficient for a box under any load.
  93 */
  94
  95#define WATCHDOG_TIMEOUT 30             /* 30 sec default timeout */
  96static int timeout = WATCHDOG_TIMEOUT;  /* in seconds, multiplied by HZ to
  97                                           get seconds to wait for a ping */
  98module_param(timeout, int, 0);
  99MODULE_PARM_DESC(timeout,
 100        "Watchdog timeout in seconds. (1<=timeout<=3600, default="
 101                                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
 102
 103static bool nowayout = WATCHDOG_NOWAYOUT;
 104module_param(nowayout, bool, 0);
 105MODULE_PARM_DESC(nowayout,
 106        "Watchdog cannot be stopped once started (default="
 107                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 108
 109static void wdt_timer_ping(struct timer_list *);
 110static DEFINE_TIMER(timer, wdt_timer_ping);
 111static unsigned long next_heartbeat;
 112static unsigned long wdt_is_open;
 113static char wdt_expect_close;
 114
 115/*
 116 *      Whack the dog
 117 */
 118
 119static void wdt_timer_ping(struct timer_list *unused)
 120{
 121        /* If we got a heartbeat pulse within the WDT_US_INTERVAL
 122         * we agree to ping the WDT
 123         */
 124        if (time_before(jiffies, next_heartbeat)) {
 125                /* Ping the WDT by reading from wdt_start */
 126                inb_p(wdt_start);
 127                /* Re-set the timer interval */
 128                mod_timer(&timer, jiffies + WDT_INTERVAL);
 129        } else
 130                pr_warn("Heartbeat lost! Will not ping the watchdog\n");
 131}
 132
 133/*
 134 * Utility routines
 135 */
 136
 137static void wdt_startup(void)
 138{
 139        next_heartbeat = jiffies + (timeout * HZ);
 140
 141        /* Start the timer */
 142        mod_timer(&timer, jiffies + WDT_INTERVAL);
 143        pr_info("Watchdog timer is now enabled\n");
 144}
 145
 146static void wdt_turnoff(void)
 147{
 148        /* Stop the timer */
 149        del_timer_sync(&timer);
 150        inb_p(wdt_stop);
 151        pr_info("Watchdog timer is now disabled...\n");
 152}
 153
 154static void wdt_keepalive(void)
 155{
 156        /* user land ping */
 157        next_heartbeat = jiffies + (timeout * HZ);
 158}
 159
 160/*
 161 * /dev/watchdog handling
 162 */
 163
 164static ssize_t fop_write(struct file *file, const char __user *buf,
 165                                                size_t count, loff_t *ppos)
 166{
 167        /* See if we got the magic character 'V' and reload the timer */
 168        if (count) {
 169                if (!nowayout) {
 170                        size_t ofs;
 171
 172                        /* note: just in case someone wrote the
 173                           magic character five months ago... */
 174                        wdt_expect_close = 0;
 175
 176                        /* scan to see whether or not we got the
 177                           magic character */
 178                        for (ofs = 0; ofs != count; ofs++) {
 179                                char c;
 180                                if (get_user(c, buf + ofs))
 181                                        return -EFAULT;
 182                                if (c == 'V')
 183                                        wdt_expect_close = 42;
 184                        }
 185                }
 186
 187                /* Well, anyhow someone wrote to us, we should
 188                   return that favour */
 189                wdt_keepalive();
 190        }
 191        return count;
 192}
 193
 194static int fop_open(struct inode *inode, struct file *file)
 195{
 196        /* Just in case we're already talking to someone... */
 197        if (test_and_set_bit(0, &wdt_is_open))
 198                return -EBUSY;
 199
 200        if (nowayout)
 201                __module_get(THIS_MODULE);
 202
 203        /* Good, fire up the show */
 204        wdt_startup();
 205        return stream_open(inode, file);
 206}
 207
 208static int fop_close(struct inode *inode, struct file *file)
 209{
 210        if (wdt_expect_close == 42)
 211                wdt_turnoff();
 212        else {
 213                del_timer(&timer);
 214                pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
 215        }
 216        clear_bit(0, &wdt_is_open);
 217        wdt_expect_close = 0;
 218        return 0;
 219}
 220
 221static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 222{
 223        void __user *argp = (void __user *)arg;
 224        int __user *p = argp;
 225        static const struct watchdog_info ident = {
 226                .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
 227                                                        WDIOF_MAGICCLOSE,
 228                .firmware_version = 1,
 229                .identity = "SBC60xx",
 230        };
 231
 232        switch (cmd) {
 233        case WDIOC_GETSUPPORT:
 234                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 235        case WDIOC_GETSTATUS:
 236        case WDIOC_GETBOOTSTATUS:
 237                return put_user(0, p);
 238        case WDIOC_SETOPTIONS:
 239        {
 240                int new_options, retval = -EINVAL;
 241                if (get_user(new_options, p))
 242                        return -EFAULT;
 243                if (new_options & WDIOS_DISABLECARD) {
 244                        wdt_turnoff();
 245                        retval = 0;
 246                }
 247                if (new_options & WDIOS_ENABLECARD) {
 248                        wdt_startup();
 249                        retval = 0;
 250                }
 251                return retval;
 252        }
 253        case WDIOC_KEEPALIVE:
 254                wdt_keepalive();
 255                return 0;
 256        case WDIOC_SETTIMEOUT:
 257        {
 258                int new_timeout;
 259                if (get_user(new_timeout, p))
 260                        return -EFAULT;
 261                /* arbitrary upper limit */
 262                if (new_timeout < 1 || new_timeout > 3600)
 263                        return -EINVAL;
 264
 265                timeout = new_timeout;
 266                wdt_keepalive();
 267        }
 268                fallthrough;
 269        case WDIOC_GETTIMEOUT:
 270                return put_user(timeout, p);
 271        default:
 272                return -ENOTTY;
 273        }
 274}
 275
 276static const struct file_operations wdt_fops = {
 277        .owner          = THIS_MODULE,
 278        .llseek         = no_llseek,
 279        .write          = fop_write,
 280        .open           = fop_open,
 281        .release        = fop_close,
 282        .unlocked_ioctl = fop_ioctl,
 283        .compat_ioctl   = compat_ptr_ioctl,
 284};
 285
 286static struct miscdevice wdt_miscdev = {
 287        .minor = WATCHDOG_MINOR,
 288        .name = "watchdog",
 289        .fops = &wdt_fops,
 290};
 291
 292/*
 293 *      Notifier for system down
 294 */
 295
 296static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 297        void *unused)
 298{
 299        if (code == SYS_DOWN || code == SYS_HALT)
 300                wdt_turnoff();
 301        return NOTIFY_DONE;
 302}
 303
 304/*
 305 *      The WDT needs to learn about soft shutdowns in order to
 306 *      turn the timebomb registers off.
 307 */
 308
 309static struct notifier_block wdt_notifier = {
 310        .notifier_call = wdt_notify_sys,
 311};
 312
 313static void __exit sbc60xxwdt_unload(void)
 314{
 315        wdt_turnoff();
 316
 317        /* Deregister */
 318        misc_deregister(&wdt_miscdev);
 319
 320        unregister_reboot_notifier(&wdt_notifier);
 321        if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
 322                release_region(wdt_stop, 1);
 323        release_region(wdt_start, 1);
 324}
 325
 326static int __init sbc60xxwdt_init(void)
 327{
 328        int rc = -EBUSY;
 329
 330        if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
 331                timeout = WATCHDOG_TIMEOUT;
 332                pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
 333                        timeout);
 334        }
 335
 336        if (!request_region(wdt_start, 1, "SBC 60XX WDT")) {
 337                pr_err("I/O address 0x%04x already in use\n", wdt_start);
 338                rc = -EIO;
 339                goto err_out;
 340        }
 341
 342        /* We cannot reserve 0x45 - the kernel already has! */
 343        if (wdt_stop != 0x45 && wdt_stop != wdt_start) {
 344                if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) {
 345                        pr_err("I/O address 0x%04x already in use\n", wdt_stop);
 346                        rc = -EIO;
 347                        goto err_out_region1;
 348                }
 349        }
 350
 351        rc = register_reboot_notifier(&wdt_notifier);
 352        if (rc) {
 353                pr_err("cannot register reboot notifier (err=%d)\n", rc);
 354                goto err_out_region2;
 355        }
 356
 357        rc = misc_register(&wdt_miscdev);
 358        if (rc) {
 359                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 360                       wdt_miscdev.minor, rc);
 361                goto err_out_reboot;
 362        }
 363        pr_info("WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
 364                timeout, nowayout);
 365
 366        return 0;
 367
 368err_out_reboot:
 369        unregister_reboot_notifier(&wdt_notifier);
 370err_out_region2:
 371        if (wdt_stop != 0x45 && wdt_stop != wdt_start)
 372                release_region(wdt_stop, 1);
 373err_out_region1:
 374        release_region(wdt_start, 1);
 375err_out:
 376        return rc;
 377}
 378
 379module_init(sbc60xxwdt_init);
 380module_exit(sbc60xxwdt_unload);
 381
 382MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
 383MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
 384MODULE_LICENSE("GPL");
 385