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