linux/drivers/watchdog/scx200_wdt.c
<<
>>
Prefs
   1/* drivers/char/watchdog/scx200_wdt.c
   2
   3   National Semiconductor SCx200 Watchdog support
   4
   5   Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
   6
   7   Some code taken from:
   8   National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
   9   (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
  10
  11   This program is free software; you can redistribute it and/or
  12   modify it under the terms of the GNU General Public License as
  13   published by the Free Software Foundation; either version 2 of the
  14   License, or (at your option) any later version.
  15
  16   The author(s) of this software shall not be held liable for damages
  17   of any nature resulting due to the use of this software. This
  18   software is provided AS-IS with no warranties. */
  19
  20#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  21
  22#include <linux/module.h>
  23#include <linux/moduleparam.h>
  24#include <linux/init.h>
  25#include <linux/miscdevice.h>
  26#include <linux/watchdog.h>
  27#include <linux/notifier.h>
  28#include <linux/reboot.h>
  29#include <linux/fs.h>
  30#include <linux/ioport.h>
  31#include <linux/scx200.h>
  32#include <linux/uaccess.h>
  33#include <linux/io.h>
  34
  35#define DEBUG
  36
  37MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
  38MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
  39MODULE_LICENSE("GPL");
  40MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
  41
  42static int margin = 60;         /* in seconds */
  43module_param(margin, int, 0);
  44MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  45
  46static bool nowayout = WATCHDOG_NOWAYOUT;
  47module_param(nowayout, bool, 0);
  48MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  49
  50static u16 wdto_restart;
  51static char expect_close;
  52static unsigned long open_lock;
  53static DEFINE_SPINLOCK(scx_lock);
  54
  55/* Bits of the WDCNFG register */
  56#define W_ENABLE 0x00fa         /* Enable watchdog */
  57#define W_DISABLE 0x0000        /* Disable watchdog */
  58
  59/* The scaling factor for the timer, this depends on the value of W_ENABLE */
  60#define W_SCALE (32768/1024)
  61
  62static void scx200_wdt_ping(void)
  63{
  64        spin_lock(&scx_lock);
  65        outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
  66        spin_unlock(&scx_lock);
  67}
  68
  69static void scx200_wdt_update_margin(void)
  70{
  71        pr_info("timer margin %d seconds\n", margin);
  72        wdto_restart = margin * W_SCALE;
  73}
  74
  75static void scx200_wdt_enable(void)
  76{
  77        pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
  78
  79        spin_lock(&scx_lock);
  80        outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  81        outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  82        outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
  83        spin_unlock(&scx_lock);
  84
  85        scx200_wdt_ping();
  86}
  87
  88static void scx200_wdt_disable(void)
  89{
  90        pr_debug("disabling watchdog timer\n");
  91
  92        spin_lock(&scx_lock);
  93        outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  94        outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  95        outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
  96        spin_unlock(&scx_lock);
  97}
  98
  99static int scx200_wdt_open(struct inode *inode, struct file *file)
 100{
 101        /* only allow one at a time */
 102        if (test_and_set_bit(0, &open_lock))
 103                return -EBUSY;
 104        scx200_wdt_enable();
 105
 106        return nonseekable_open(inode, file);
 107}
 108
 109static int scx200_wdt_release(struct inode *inode, struct file *file)
 110{
 111        if (expect_close != 42)
 112                pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
 113        else if (!nowayout)
 114                scx200_wdt_disable();
 115        expect_close = 0;
 116        clear_bit(0, &open_lock);
 117
 118        return 0;
 119}
 120
 121static int scx200_wdt_notify_sys(struct notifier_block *this,
 122                                      unsigned long code, void *unused)
 123{
 124        if (code == SYS_HALT || code == SYS_POWER_OFF)
 125                if (!nowayout)
 126                        scx200_wdt_disable();
 127
 128        return NOTIFY_DONE;
 129}
 130
 131static struct notifier_block scx200_wdt_notifier = {
 132        .notifier_call = scx200_wdt_notify_sys,
 133};
 134
 135static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
 136                                     size_t len, loff_t *ppos)
 137{
 138        /* check for a magic close character */
 139        if (len) {
 140                size_t i;
 141
 142                scx200_wdt_ping();
 143
 144                expect_close = 0;
 145                for (i = 0; i < len; ++i) {
 146                        char c;
 147                        if (get_user(c, data + i))
 148                                return -EFAULT;
 149                        if (c == 'V')
 150                                expect_close = 42;
 151                }
 152
 153                return len;
 154        }
 155
 156        return 0;
 157}
 158
 159static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
 160                                                        unsigned long arg)
 161{
 162        void __user *argp = (void __user *)arg;
 163        int __user *p = argp;
 164        static const struct watchdog_info ident = {
 165                .identity = "NatSemi SCx200 Watchdog",
 166                .firmware_version = 1,
 167                .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 168                                                WDIOF_MAGICCLOSE,
 169        };
 170        int new_margin;
 171
 172        switch (cmd) {
 173        case WDIOC_GETSUPPORT:
 174                if (copy_to_user(argp, &ident, sizeof(ident)))
 175                        return -EFAULT;
 176                return 0;
 177        case WDIOC_GETSTATUS:
 178        case WDIOC_GETBOOTSTATUS:
 179                if (put_user(0, p))
 180                        return -EFAULT;
 181                return 0;
 182        case WDIOC_KEEPALIVE:
 183                scx200_wdt_ping();
 184                return 0;
 185        case WDIOC_SETTIMEOUT:
 186                if (get_user(new_margin, p))
 187                        return -EFAULT;
 188                if (new_margin < 1)
 189                        return -EINVAL;
 190                margin = new_margin;
 191                scx200_wdt_update_margin();
 192                scx200_wdt_ping();
 193        case WDIOC_GETTIMEOUT:
 194                if (put_user(margin, p))
 195                        return -EFAULT;
 196                return 0;
 197        default:
 198                return -ENOTTY;
 199        }
 200}
 201
 202static const struct file_operations scx200_wdt_fops = {
 203        .owner = THIS_MODULE,
 204        .llseek = no_llseek,
 205        .write = scx200_wdt_write,
 206        .unlocked_ioctl = scx200_wdt_ioctl,
 207        .open = scx200_wdt_open,
 208        .release = scx200_wdt_release,
 209};
 210
 211static struct miscdevice scx200_wdt_miscdev = {
 212        .minor = WATCHDOG_MINOR,
 213        .name = "watchdog",
 214        .fops = &scx200_wdt_fops,
 215};
 216
 217static int __init scx200_wdt_init(void)
 218{
 219        int r;
 220
 221        pr_debug("NatSemi SCx200 Watchdog Driver\n");
 222
 223        /* check that we have found the configuration block */
 224        if (!scx200_cb_present())
 225                return -ENODEV;
 226
 227        if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
 228                            SCx200_WDT_SIZE,
 229                            "NatSemi SCx200 Watchdog")) {
 230                pr_warn("watchdog I/O region busy\n");
 231                return -EBUSY;
 232        }
 233
 234        scx200_wdt_update_margin();
 235        scx200_wdt_disable();
 236
 237        r = register_reboot_notifier(&scx200_wdt_notifier);
 238        if (r) {
 239                pr_err("unable to register reboot notifier\n");
 240                release_region(scx200_cb_base + SCx200_WDT_OFFSET,
 241                                SCx200_WDT_SIZE);
 242                return r;
 243        }
 244
 245        r = misc_register(&scx200_wdt_miscdev);
 246        if (r) {
 247                unregister_reboot_notifier(&scx200_wdt_notifier);
 248                release_region(scx200_cb_base + SCx200_WDT_OFFSET,
 249                                SCx200_WDT_SIZE);
 250                return r;
 251        }
 252
 253        return 0;
 254}
 255
 256static void __exit scx200_wdt_cleanup(void)
 257{
 258        misc_deregister(&scx200_wdt_miscdev);
 259        unregister_reboot_notifier(&scx200_wdt_notifier);
 260        release_region(scx200_cb_base + SCx200_WDT_OFFSET,
 261                       SCx200_WDT_SIZE);
 262}
 263
 264module_init(scx200_wdt_init);
 265module_exit(scx200_wdt_cleanup);
 266
 267/*
 268    Local variables:
 269        compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
 270        c-basic-offset: 8
 271    End:
 272*/
 273
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.