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 stream_open(inode, file);
  79}
  80
  81static ssize_t fitpc2_wdt_write(struct file *file, const char __user *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 __user *)arg, &ident,
 129                                   sizeof(ident)) ? -EFAULT : 0;
 130                break;
 131
 132        case WDIOC_GETSTATUS:
 133                ret = put_user(0, (int __user *)arg);
 134                break;
 135
 136        case WDIOC_GETBOOTSTATUS:
 137                ret = put_user(0, (int __user *)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 __user *)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                fallthrough;
 158
 159        case WDIOC_GETTIMEOUT:
 160                ret = put_user(margin, (int __user *)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        .compat_ioctl   = compat_ptr_ioctl,
 190        .open           = fitpc2_wdt_open,
 191        .release        = fitpc2_wdt_release,
 192};
 193
 194static struct miscdevice fitpc2_wdt_miscdev = {
 195        .minor          = WATCHDOG_MINOR,
 196        .name           = "watchdog",
 197        .fops           = &fitpc2_wdt_fops,
 198};
 199
 200static int __init fitpc2_wdt_init(void)
 201{
 202        int err;
 203        const char *brd_name;
 204
 205        brd_name = dmi_get_system_info(DMI_BOARD_NAME);
 206
 207        if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
 208                return -ENODEV;
 209
 210        pr_info("%s found\n", brd_name);
 211
 212        if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
 213                pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
 214                return -EIO;
 215        }
 216
 217        if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
 218                pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
 219                err = -EIO;
 220                goto err_data_port;
 221        }
 222
 223        if (margin < 31 || margin > 255) {
 224                pr_err("margin must be in range 31 - 255 seconds, you tried to set %d\n",
 225                       margin);
 226                err = -EINVAL;
 227                goto err_margin;
 228        }
 229
 230        err = misc_register(&fitpc2_wdt_miscdev);
 231        if (err) {
 232                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 233                       WATCHDOG_MINOR, err);
 234                goto err_margin;
 235        }
 236
 237        return 0;
 238
 239err_margin:
 240        release_region(DATA_PORT, 1);
 241err_data_port:
 242        release_region(COMMAND_PORT, 1);
 243
 244        return err;
 245}
 246
 247static void __exit fitpc2_wdt_exit(void)
 248{
 249        misc_deregister(&fitpc2_wdt_miscdev);
 250        release_region(DATA_PORT, 1);
 251        release_region(COMMAND_PORT, 1);
 252}
 253
 254module_init(fitpc2_wdt_init);
 255module_exit(fitpc2_wdt_exit);
 256
 257MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
 258MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
 259
 260module_param(margin, int, 0);
 261MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
 262
 263module_param(nowayout, bool, 0);
 264MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 265
 266MODULE_LICENSE("GPL");
 267