File Index
Files and directories
Sign in
FILE

sbc_fitpc2_wdt.c

File preview with syntax highlighting.

linux-6.19.14/drivers/watchdog/sbc_fitpc2_wdt.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Watchdog driver for SBC-FITPC2 board
  4 *
  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: " fmt
 12
 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		0
 33#define WDT_OK_TO_CLOSE		1
 34
 35#define COMMAND_PORT		0x4c
 36#define DATA_PORT		0x48
 37
 38#define IFACE_ON_COMMAND	1
 39#define REBOOT_COMMAND		2
 40
 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");
~/ Read-only file preview.