brintos

brintos / linux-shallow public Read only

0
0
Text · 5.1 KiB · ff9e448 Raw
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