Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Watchdog driver for SBC-FITPC2 board
0003  *
0004  * Author: Denis Turischev <denis@compulab.co.il>
0005  *
0006  * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
0007  *
0008  * This file is licensed under  the terms of the GNU General Public
0009  * License version 2. This program is licensed "as is" without any
0010  * warranty of any kind, whether express or implied.
0011  */
0012 
0013 #define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
0014 
0015 #include <linux/module.h>
0016 #include <linux/types.h>
0017 #include <linux/miscdevice.h>
0018 #include <linux/watchdog.h>
0019 #include <linux/ioport.h>
0020 #include <linux/delay.h>
0021 #include <linux/fs.h>
0022 #include <linux/init.h>
0023 #include <linux/moduleparam.h>
0024 #include <linux/dmi.h>
0025 #include <linux/io.h>
0026 #include <linux/uaccess.h>
0027 
0028 
0029 static bool nowayout = WATCHDOG_NOWAYOUT;
0030 static unsigned int margin = 60;    /* (secs) Default is 1 minute */
0031 static unsigned long wdt_status;
0032 static DEFINE_MUTEX(wdt_lock);
0033 
0034 #define WDT_IN_USE      0
0035 #define WDT_OK_TO_CLOSE     1
0036 
0037 #define COMMAND_PORT        0x4c
0038 #define DATA_PORT       0x48
0039 
0040 #define IFACE_ON_COMMAND    1
0041 #define REBOOT_COMMAND      2
0042 
0043 #define WATCHDOG_NAME       "SBC-FITPC2 Watchdog"
0044 
0045 static void wdt_send_data(unsigned char command, unsigned char data)
0046 {
0047     outb(data, DATA_PORT);
0048     msleep(200);
0049     outb(command, COMMAND_PORT);
0050     msleep(100);
0051 }
0052 
0053 static void wdt_enable(void)
0054 {
0055     mutex_lock(&wdt_lock);
0056     wdt_send_data(IFACE_ON_COMMAND, 1);
0057     wdt_send_data(REBOOT_COMMAND, margin);
0058     mutex_unlock(&wdt_lock);
0059 }
0060 
0061 static void wdt_disable(void)
0062 {
0063     mutex_lock(&wdt_lock);
0064     wdt_send_data(IFACE_ON_COMMAND, 0);
0065     wdt_send_data(REBOOT_COMMAND, 0);
0066     mutex_unlock(&wdt_lock);
0067 }
0068 
0069 static int fitpc2_wdt_open(struct inode *inode, struct file *file)
0070 {
0071     if (test_and_set_bit(WDT_IN_USE, &wdt_status))
0072         return -EBUSY;
0073 
0074     clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
0075 
0076     wdt_enable();
0077 
0078     return stream_open(inode, file);
0079 }
0080 
0081 static ssize_t fitpc2_wdt_write(struct file *file, const char __user *data,
0082                         size_t len, loff_t *ppos)
0083 {
0084     size_t i;
0085 
0086     if (!len)
0087         return 0;
0088 
0089     if (nowayout) {
0090         len = 0;
0091         goto out;
0092     }
0093 
0094     clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
0095 
0096     for (i = 0; i != len; i++) {
0097         char c;
0098 
0099         if (get_user(c, data + i))
0100             return -EFAULT;
0101 
0102         if (c == 'V')
0103             set_bit(WDT_OK_TO_CLOSE, &wdt_status);
0104     }
0105 
0106 out:
0107     wdt_enable();
0108 
0109     return len;
0110 }
0111 
0112 
0113 static const struct watchdog_info ident = {
0114     .options    = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
0115                 WDIOF_KEEPALIVEPING,
0116     .identity   = WATCHDOG_NAME,
0117 };
0118 
0119 
0120 static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
0121                             unsigned long arg)
0122 {
0123     int ret = -ENOTTY;
0124     int time;
0125 
0126     switch (cmd) {
0127     case WDIOC_GETSUPPORT:
0128         ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
0129                    sizeof(ident)) ? -EFAULT : 0;
0130         break;
0131 
0132     case WDIOC_GETSTATUS:
0133         ret = put_user(0, (int __user *)arg);
0134         break;
0135 
0136     case WDIOC_GETBOOTSTATUS:
0137         ret = put_user(0, (int __user *)arg);
0138         break;
0139 
0140     case WDIOC_KEEPALIVE:
0141         wdt_enable();
0142         ret = 0;
0143         break;
0144 
0145     case WDIOC_SETTIMEOUT:
0146         ret = get_user(time, (int __user *)arg);
0147         if (ret)
0148             break;
0149 
0150         if (time < 31 || time > 255) {
0151             ret = -EINVAL;
0152             break;
0153         }
0154 
0155         margin = time;
0156         wdt_enable();
0157         fallthrough;
0158 
0159     case WDIOC_GETTIMEOUT:
0160         ret = put_user(margin, (int __user *)arg);
0161         break;
0162     }
0163 
0164     return ret;
0165 }
0166 
0167 static int fitpc2_wdt_release(struct inode *inode, struct file *file)
0168 {
0169     if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
0170         wdt_disable();
0171         pr_info("Device disabled\n");
0172     } else {
0173         pr_warn("Device closed unexpectedly - timer will not stop\n");
0174         wdt_enable();
0175     }
0176 
0177     clear_bit(WDT_IN_USE, &wdt_status);
0178     clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
0179 
0180     return 0;
0181 }
0182 
0183 
0184 static const struct file_operations fitpc2_wdt_fops = {
0185     .owner      = THIS_MODULE,
0186     .llseek     = no_llseek,
0187     .write      = fitpc2_wdt_write,
0188     .unlocked_ioctl = fitpc2_wdt_ioctl,
0189     .compat_ioctl   = compat_ptr_ioctl,
0190     .open       = fitpc2_wdt_open,
0191     .release    = fitpc2_wdt_release,
0192 };
0193 
0194 static struct miscdevice fitpc2_wdt_miscdev = {
0195     .minor      = WATCHDOG_MINOR,
0196     .name       = "watchdog",
0197     .fops       = &fitpc2_wdt_fops,
0198 };
0199 
0200 static int __init fitpc2_wdt_init(void)
0201 {
0202     int err;
0203     const char *brd_name;
0204 
0205     brd_name = dmi_get_system_info(DMI_BOARD_NAME);
0206 
0207     if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
0208         return -ENODEV;
0209 
0210     pr_info("%s found\n", brd_name);
0211 
0212     if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
0213         pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
0214         return -EIO;
0215     }
0216 
0217     if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
0218         pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
0219         err = -EIO;
0220         goto err_data_port;
0221     }
0222 
0223     if (margin < 31 || margin > 255) {
0224         pr_err("margin must be in range 31 - 255 seconds, you tried to set %d\n",
0225                margin);
0226         err = -EINVAL;
0227         goto err_margin;
0228     }
0229 
0230     err = misc_register(&fitpc2_wdt_miscdev);
0231     if (err) {
0232         pr_err("cannot register miscdev on minor=%d (err=%d)\n",
0233                WATCHDOG_MINOR, err);
0234         goto err_margin;
0235     }
0236 
0237     return 0;
0238 
0239 err_margin:
0240     release_region(DATA_PORT, 1);
0241 err_data_port:
0242     release_region(COMMAND_PORT, 1);
0243 
0244     return err;
0245 }
0246 
0247 static void __exit fitpc2_wdt_exit(void)
0248 {
0249     misc_deregister(&fitpc2_wdt_miscdev);
0250     release_region(DATA_PORT, 1);
0251     release_region(COMMAND_PORT, 1);
0252 }
0253 
0254 module_init(fitpc2_wdt_init);
0255 module_exit(fitpc2_wdt_exit);
0256 
0257 MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
0258 MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
0259 
0260 module_param(margin, int, 0);
0261 MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
0262 
0263 module_param(nowayout, bool, 0);
0264 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
0265 
0266 MODULE_LICENSE("GPL");