linux/drivers/watchdog/mixcomwd.c
<<
>>
Prefs
   1/*
   2 * MixCom Watchdog: A Simple Hardware Watchdog Device
   3 * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis
   4 *
   5 * Author: Gergely Madarasz <gorgo@itc.hu>
   6 *
   7 * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu>
   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 * Version 0.1 (99/04/15):
  15 *              - first version
  16 *
  17 * Version 0.2 (99/06/16):
  18 *              - added kernel timer watchdog ping after close
  19 *                since the hardware does not support watchdog shutdown
  20 *
  21 * Version 0.3 (99/06/21):
  22 *              - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls
  23 *
  24 * Version 0.3.1 (99/06/22):
  25 *              - allow module removal while internal timer is active,
  26 *                print warning about probable reset
  27 *
  28 * Version 0.4 (99/11/15):
  29 *              - support for one more type board
  30 *
  31 * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com>
  32 *              - added nowayout module option to override
  33 *                CONFIG_WATCHDOG_NOWAYOUT
  34 *
  35 * Version 0.6 (2002/04/12): Rob Radez <rob@osinvestor.com>
  36 *              - make mixcomwd_opened unsigned,
  37 *                removed lock_kernel/unlock_kernel from mixcomwd_release,
  38 *                modified ioctl a bit to conform to API
  39 *
  40 */
  41
  42#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  43
  44#define VERSION "0.6"
  45#define WATCHDOG_NAME "mixcomwd"
  46
  47#include <linux/module.h>
  48#include <linux/moduleparam.h>
  49#include <linux/types.h>
  50#include <linux/miscdevice.h>
  51#include <linux/ioport.h>
  52#include <linux/watchdog.h>
  53#include <linux/fs.h>
  54#include <linux/reboot.h>
  55#include <linux/init.h>
  56#include <linux/jiffies.h>
  57#include <linux/timer.h>
  58#include <linux/uaccess.h>
  59#include <linux/io.h>
  60
  61/*
  62 * We have two types of cards that can be probed:
  63 * 1) The Mixcom cards: these cards can be found at addresses
  64 *    0x180, 0x280, 0x380 with an additional offset of 0xc10.
  65 *    (Or 0xd90, 0xe90, 0xf90).
  66 * 2) The FlashCOM cards: these cards can be set up at
  67 *    0x300 -> 0x378, in 0x8 jumps with an offset of 0x04.
  68 *    (Or 0x304 -> 0x37c in 0x8 jumps).
  69 *    Each card has it's own ID.
  70 */
  71#define MIXCOM_ID 0x11
  72#define FLASHCOM_ID 0x18
  73static struct {
  74        int ioport;
  75        int id;
  76} mixcomwd_io_info[] = {
  77        /* The Mixcom cards */
  78        {0x0d90, MIXCOM_ID},
  79        {0x0e90, MIXCOM_ID},
  80        {0x0f90, MIXCOM_ID},
  81        /* The FlashCOM cards */
  82        {0x0304, FLASHCOM_ID},
  83        {0x030c, FLASHCOM_ID},
  84        {0x0314, FLASHCOM_ID},
  85        {0x031c, FLASHCOM_ID},
  86        {0x0324, FLASHCOM_ID},
  87        {0x032c, FLASHCOM_ID},
  88        {0x0334, FLASHCOM_ID},
  89        {0x033c, FLASHCOM_ID},
  90        {0x0344, FLASHCOM_ID},
  91        {0x034c, FLASHCOM_ID},
  92        {0x0354, FLASHCOM_ID},
  93        {0x035c, FLASHCOM_ID},
  94        {0x0364, FLASHCOM_ID},
  95        {0x036c, FLASHCOM_ID},
  96        {0x0374, FLASHCOM_ID},
  97        {0x037c, FLASHCOM_ID},
  98        /* The end of the list */
  99        {0x0000, 0},
 100};
 101
 102static void mixcomwd_timerfun(unsigned long d);
 103
 104static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */
 105
 106static int watchdog_port;
 107static int mixcomwd_timer_alive;
 108static DEFINE_TIMER(mixcomwd_timer, mixcomwd_timerfun, 0, 0);
 109static char expect_close;
 110
 111static bool nowayout = WATCHDOG_NOWAYOUT;
 112module_param(nowayout, bool, 0);
 113MODULE_PARM_DESC(nowayout,
 114                "Watchdog cannot be stopped once started (default="
 115                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 116
 117static void mixcomwd_ping(void)
 118{
 119        outb_p(55, watchdog_port);
 120        return;
 121}
 122
 123static void mixcomwd_timerfun(unsigned long d)
 124{
 125        mixcomwd_ping();
 126        mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
 127}
 128
 129/*
 130 *      Allow only one person to hold it open
 131 */
 132
 133static int mixcomwd_open(struct inode *inode, struct file *file)
 134{
 135        if (test_and_set_bit(0, &mixcomwd_opened))
 136                return -EBUSY;
 137
 138        mixcomwd_ping();
 139
 140        if (nowayout)
 141                /*
 142                 * fops_get() code via open() has already done
 143                 * a try_module_get() so it is safe to do the
 144                 * __module_get().
 145                 */
 146                __module_get(THIS_MODULE);
 147        else {
 148                if (mixcomwd_timer_alive) {
 149                        del_timer(&mixcomwd_timer);
 150                        mixcomwd_timer_alive = 0;
 151                }
 152        }
 153        return nonseekable_open(inode, file);
 154}
 155
 156static int mixcomwd_release(struct inode *inode, struct file *file)
 157{
 158        if (expect_close == 42) {
 159                if (mixcomwd_timer_alive) {
 160                        pr_err("release called while internal timer alive\n");
 161                        return -EBUSY;
 162                }
 163                mixcomwd_timer_alive = 1;
 164                mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
 165        } else
 166                pr_crit("WDT device closed unexpectedly.  WDT will not stop!\n");
 167
 168        clear_bit(0, &mixcomwd_opened);
 169        expect_close = 0;
 170        return 0;
 171}
 172
 173
 174static ssize_t mixcomwd_write(struct file *file, const char __user *data,
 175                                                size_t len, loff_t *ppos)
 176{
 177        if (len) {
 178                if (!nowayout) {
 179                        size_t i;
 180
 181                        /* In case it was set long ago */
 182                        expect_close = 0;
 183
 184                        for (i = 0; i != len; i++) {
 185                                char c;
 186                                if (get_user(c, data + i))
 187                                        return -EFAULT;
 188                                if (c == 'V')
 189                                        expect_close = 42;
 190                        }
 191                }
 192                mixcomwd_ping();
 193        }
 194        return len;
 195}
 196
 197static long mixcomwd_ioctl(struct file *file,
 198                                unsigned int cmd, unsigned long arg)
 199{
 200        void __user *argp = (void __user *)arg;
 201        int __user *p = argp;
 202        int status;
 203        static const struct watchdog_info ident = {
 204                .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 205                .firmware_version = 1,
 206                .identity = "MixCOM watchdog",
 207        };
 208
 209        switch (cmd) {
 210        case WDIOC_GETSUPPORT:
 211                if (copy_to_user(argp, &ident, sizeof(ident)))
 212                        return -EFAULT;
 213                break;
 214        case WDIOC_GETSTATUS:
 215                status = mixcomwd_opened;
 216                if (!nowayout)
 217                        status |= mixcomwd_timer_alive;
 218                return put_user(status, p);
 219        case WDIOC_GETBOOTSTATUS:
 220                return put_user(0, p);
 221        case WDIOC_KEEPALIVE:
 222                mixcomwd_ping();
 223                break;
 224        default:
 225                return -ENOTTY;
 226        }
 227        return 0;
 228}
 229
 230static const struct file_operations mixcomwd_fops = {
 231        .owner          = THIS_MODULE,
 232        .llseek         = no_llseek,
 233        .write          = mixcomwd_write,
 234        .unlocked_ioctl = mixcomwd_ioctl,
 235        .open           = mixcomwd_open,
 236        .release        = mixcomwd_release,
 237};
 238
 239static struct miscdevice mixcomwd_miscdev = {
 240        .minor  = WATCHDOG_MINOR,
 241        .name   = "watchdog",
 242        .fops   = &mixcomwd_fops,
 243};
 244
 245static int __init checkcard(int port, int card_id)
 246{
 247        int id;
 248
 249        if (!request_region(port, 1, "MixCOM watchdog"))
 250                return 0;
 251
 252        id = inb_p(port);
 253        if (card_id == MIXCOM_ID)
 254                id &= 0x3f;
 255
 256        if (id != card_id) {
 257                release_region(port, 1);
 258                return 0;
 259        }
 260        return 1;
 261}
 262
 263static int __init mixcomwd_init(void)
 264{
 265        int i, ret, found = 0;
 266
 267        for (i = 0; !found && mixcomwd_io_info[i].ioport != 0; i++) {
 268                if (checkcard(mixcomwd_io_info[i].ioport,
 269                              mixcomwd_io_info[i].id)) {
 270                        found = 1;
 271                        watchdog_port = mixcomwd_io_info[i].ioport;
 272                }
 273        }
 274
 275        if (!found) {
 276                pr_err("No card detected, or port not available\n");
 277                return -ENODEV;
 278        }
 279
 280        ret = misc_register(&mixcomwd_miscdev);
 281        if (ret) {
 282                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 283                       WATCHDOG_MINOR, ret);
 284                goto error_misc_register_watchdog;
 285        }
 286
 287        pr_info("MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",
 288                VERSION, watchdog_port);
 289
 290        return 0;
 291
 292error_misc_register_watchdog:
 293        release_region(watchdog_port, 1);
 294        watchdog_port = 0x0000;
 295        return ret;
 296}
 297
 298static void __exit mixcomwd_exit(void)
 299{
 300        if (!nowayout) {
 301                if (mixcomwd_timer_alive) {
 302                        pr_warn("I quit now, hardware will probably reboot!\n");
 303                        del_timer_sync(&mixcomwd_timer);
 304                        mixcomwd_timer_alive = 0;
 305                }
 306        }
 307        misc_deregister(&mixcomwd_miscdev);
 308        release_region(watchdog_port, 1);
 309}
 310
 311module_init(mixcomwd_init);
 312module_exit(mixcomwd_exit);
 313
 314MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
 315MODULE_DESCRIPTION("MixCom Watchdog driver");
 316MODULE_VERSION(VERSION);
 317MODULE_LICENSE("GPL");
 318