0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
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;
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");