linux/drivers/watchdog/sbc_fitpc2_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for SBC-FITPC2 board
   3 *
   4 * Author: Denis Turischev <denis@compulab.co.il>
   5 *
   6 * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
   7 *
   8 * This file is licensed under  the terms of the GNU General Public
   9 * License version 2. This program is licensed "as is" without any
  10 * warranty of any kind, whether express or implied.
  11 */
  12
  13#define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
  14
  15#include <linux/module.h>
  16#include <linux/types.h>
  17#include <linux/miscdevice.h>
  18#include <linux/watchdog.h>
  19#include <linux/ioport.h>
  20#include <linux/delay.h>
  21#include <linux/fs.h>
  22#include <linux/init.h>
  23#include <linux/moduleparam.h>
  24#include <linux/dmi.h>
  25#include <linux/io.h>
  26#include <linux/uaccess.h>
  27
  28
  29static bool nowayout = WATCHDOG_NOWAYOUT;
  30static unsigned int margin = 60;        /* (secs) Default is 1 minute */
  31static unsigned long wdt_status;
  32static DEFINE_MUTEX(wdt_lock);
  33
  34#define WDT_IN_USE              0
  35#define WDT_OK_TO_CLOSE         1
  36
  37#define COMMAND_PORT            0x4c
  38#define DATA_PORT               0x48
  39
  40#define IFACE_ON_COMMAND        1
  41#define REBOOT_COMMAND          2
  42
  43#define WATCHDOG_NAME           "SBC-FITPC2 Watchdog"
  44
  45static void wdt_send_data(unsigned char command, unsigned char data)
  46{
  47        outb(data, DATA_PORT);
  48        msleep(200);
  49        outb(command, COMMAND_PORT);
  50        msleep(100);
  51}
  52
  53static void wdt_enable(void)
  54{
  55        mutex_lock(&wdt_lock);
  56        wdt_send_data(IFACE_ON_COMMAND, 1);
  57        wdt_send_data(REBOOT_COMMAND, margin);
  58        mutex_unlock(&wdt_lock);
  59}
  60
  61static void wdt_disable(void)
  62{
  63        mutex_lock(&wdt_lock);
  64        wdt_send_data(IFACE_ON_COMMAND, 0);
  65        wdt_send_data(REBOOT_COMMAND, 0);
  66        mutex_unlock(&wdt_lock);
  67}
  68
  69static int fitpc2_wdt_open(struct inode *inode, struct file *file)
  70{
  71        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  72                return -EBUSY;
  73
  74        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  75
  76        wdt_enable();
  77
  78        return nonseekable_open(inode, file);
  79}
  80
  81static ssize_t fitpc2_wdt_write(struct file *file, const char *data,
  82                                                size_t len, loff_t *ppos)
  83{
  84        size_t i;
  85
  86        if (!len)
  87                return 0;
  88
  89        if (nowayout) {
  90                len = 0;
  91                goto out;
  92        }
  93
  94        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  95
  96        for (i = 0; i != len; i++) {
  97                char c;
  98
  99                if (get_user(c, data + i))
 100                        return -EFAULT;
 101
 102                if (c == 'V')
 103                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 104        }
 105
 106out:
 107        wdt_enable();
 108
 109        return len;
 110}
 111
 112
 113static const struct watchdog_info ident = {
 114        .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
 115                                WDIOF_KEEPALIVEPING,
 116        .identity       = WATCHDOG_NAME,
 117};
 118
 119
 120static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
 121                                                        unsigned long arg)
 122{
 123        int ret = -ENOTTY;
 124        int time;
 125
 126        switch (cmd) {
 127        case WDIOC_GETSUPPORT:
 128                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 129                                   sizeof(ident)) ? -EFAULT : 0;
 130                break;
 131
 132        case WDIOC_GETSTATUS:
 133                ret = put_user(0, (int *)arg);
 134                break;
 135
 136        case WDIOC_GETBOOTSTATUS:
 137                ret = put_user(0, (int *)arg);
 138                break;
 139
 140        case WDIOC_KEEPALIVE:
 141                wdt_enable();
 142                ret = 0;
 143                break;
 144
 145        case WDIOC_SETTIMEOUT:
 146                ret = get_user(time, (int *)arg);
 147                if (ret)
 148                        break;
 149
 150                if (time < 31 || time > 255) {
 151                        ret = -EINVAL;
 152                        break;
 153                }
 154
 155                margin = time;
 156                wdt_enable();
 157                /* Fall through */
 158
 159        case WDIOC_GETTIMEOUT:
 160                ret = put_user(margin, (int *)arg);
 161                break;
 162        }
 163
 164        return ret;
 165}
 166
 167static int fitpc2_wdt_release(struct inode *inode, struct file *file)
 168{
 169        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
 170                wdt_disable();
 171                pr_info("Device disabled\n");
 172        } else {
 173                pr_warn("Device closed unexpectedly - timer will not stop\n");
 174                wdt_enable();
 175        }
 176
 177        clear_bit(WDT_IN_USE, &wdt_status);
 178        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 179
 180        return 0;
 181}
 182
 183
 184static const struct file_operations fitpc2_wdt_fops = {
 185        .owner          = THIS_MODULE,
 186        .llseek         = no_llseek,
 187        .write          = fitpc2_wdt_write,
 188        .unlocked_ioctl = fitpc2_wdt_ioctl,
 189        .open           = fitpc2_wdt_open,
 190        .release        = fitpc2_wdt_release,
 191};
 192
 193static struct miscdevice fitpc2_wdt_miscdev = {
 194        .minor          = WATCHDOG_MINOR,
 195        .name           = "watchdog",
 196        .fops           = &fitpc2_wdt_fops,
 197};
 198
 199static int __init fitpc2_wdt_init(void)
 200{
 201        int err;
 202        const char *brd_name;
 203
 204        brd_name = dmi_get_system_info(DMI_BOARD_NAME);
 205
 206        if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
 207                return -ENODEV;
 208
 209        pr_info("%s found\n", brd_name);
 210
 211        if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
 212                pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
 213                return -EIO;
 214        }
 215
 216        if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
 217                pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
 218                err = -EIO;
 219                goto err_data_port;
 220        }
 221
 222        if (margin < 31 || margin > 255) {
 223                pr_err("margin must be in range 31 - 255 seconds, you tried to set %d\n",
 224                       margin);
 225                err = -EINVAL;
 226                goto err_margin;
 227        }
 228
 229        err = misc_register(&fitpc2_wdt_miscdev);
 230        if (err) {
 231                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 232                       WATCHDOG_MINOR, err);
 233                goto err_margin;
 234        }
 235
 236        return 0;
 237
 238err_margin:
 239        release_region(DATA_PORT, 1);
 240err_data_port:
 241        release_region(COMMAND_PORT, 1);
 242
 243        return err;
 244}
 245
 246static void __exit fitpc2_wdt_exit(void)
 247{
 248        misc_deregister(&fitpc2_wdt_miscdev);
 249        release_region(DATA_PORT, 1);
 250        release_region(COMMAND_PORT, 1);
 251}
 252
 253module_init(fitpc2_wdt_init);
 254module_exit(fitpc2_wdt_exit);
 255
 256MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
 257MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
 258
 259module_param(margin, int, 0);
 260MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
 261
 262module_param(nowayout, bool, 0);
 263MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 264
 265MODULE_LICENSE("GPL");
 266