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 29 static bool nowayout = WATCHDOG_NOWAYOUT; 30 static unsigned int margin = 60; /* (secs) Default is 1 minute */ 31 static unsigned long wdt_status; 32 static 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 45 static 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 53 static 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 61 static 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 69 static 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 81 static 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 106 out: 107 wdt_enable(); 108 109 return len; 110 } 111 112 113 static const struct watchdog_info ident = { 114 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | 115 WDIOF_KEEPALIVEPING, 116 .identity = WATCHDOG_NAME, 117 }; 118 119 120 static 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 167 static 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 184 static 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 194 static struct miscdevice fitpc2_wdt_miscdev = { 195 .minor = WATCHDOG_MINOR, 196 .name = "watchdog", 197 .fops = &fitpc2_wdt_fops, 198 }; 199 200 static 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 239 err_margin: 240 release_region(DATA_PORT, 1); 241 err_data_port: 242 release_region(COMMAND_PORT, 1); 243 244 return err; 245 } 246 247 static 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 254 module_init(fitpc2_wdt_init); 255 module_exit(fitpc2_wdt_exit); 256 257 MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); 258 MODULE_DESCRIPTION("SBC-FITPC2 Watchdog"); 259 260 module_param(margin, int, 0); 261 MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); 262 263 module_param(nowayout, bool, 0); 264 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 265 266 MODULE_LICENSE("GPL"); 267