1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *	NANO7240 SBC Watchdog device driver
4  *
5  *	Based on w83877f.c by Scott Jennings,
6  *
7  *	(c) Copyright 2007  Gilles GIGAN <gilles.gigan@jcu.edu.au>
8  */
9 
10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11 
12 #include <linux/fs.h>
13 #include <linux/init.h>
14 #include <linux/ioport.h>
15 #include <linux/jiffies.h>
16 #include <linux/module.h>
17 #include <linux/moduleparam.h>
18 #include <linux/miscdevice.h>
19 #include <linux/notifier.h>
20 #include <linux/reboot.h>
21 #include <linux/types.h>
22 #include <linux/watchdog.h>
23 #include <linux/io.h>
24 #include <linux/uaccess.h>
25 #include <linux/atomic.h>
26 
27 #define SBC7240_ENABLE_PORT		0x443
28 #define SBC7240_DISABLE_PORT		0x043
29 #define SBC7240_SET_TIMEOUT_PORT	SBC7240_ENABLE_PORT
30 #define SBC7240_MAGIC_CHAR		'V'
31 
32 #define SBC7240_TIMEOUT		30
33 #define SBC7240_MAX_TIMEOUT		255
34 static int timeout = SBC7240_TIMEOUT;	/* in seconds */
35 module_param(timeout, int, 0);
36 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<="
37 		 __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default="
38 		 __MODULE_STRING(SBC7240_TIMEOUT) ")");
39 
40 static bool nowayout = WATCHDOG_NOWAYOUT;
41 module_param(nowayout, bool, 0);
42 MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file");
43 
44 #define SBC7240_OPEN_STATUS_BIT		0
45 #define SBC7240_ENABLED_STATUS_BIT	1
46 #define SBC7240_EXPECT_CLOSE_STATUS_BIT	2
47 static unsigned long wdt_status;
48 
49 /*
50  * Utility routines
51  */
52 
wdt_disable(void)53 static void wdt_disable(void)
54 {
55 	/* disable the watchdog */
56 	if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
57 		inb_p(SBC7240_DISABLE_PORT);
58 		pr_info("Watchdog timer is now disabled\n");
59 	}
60 }
61 
wdt_enable(void)62 static void wdt_enable(void)
63 {
64 	/* enable the watchdog */
65 	if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
66 		inb_p(SBC7240_ENABLE_PORT);
67 		pr_info("Watchdog timer is now enabled\n");
68 	}
69 }
70 
wdt_set_timeout(int t)71 static int wdt_set_timeout(int t)
72 {
73 	if (t < 1 || t > SBC7240_MAX_TIMEOUT) {
74 		pr_err("timeout value must be 1<=x<=%d\n", SBC7240_MAX_TIMEOUT);
75 		return -1;
76 	}
77 	/* set the timeout */
78 	outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT);
79 	timeout = t;
80 	pr_info("timeout set to %d seconds\n", t);
81 	return 0;
82 }
83 
84 /* Whack the dog */
wdt_keepalive(void)85 static inline void wdt_keepalive(void)
86 {
87 	if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status))
88 		inb_p(SBC7240_ENABLE_PORT);
89 }
90 
91 /*
92  * /dev/watchdog handling
93  */
fop_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)94 static ssize_t fop_write(struct file *file, const char __user *buf,
95 			 size_t count, loff_t *ppos)
96 {
97 	size_t i;
98 	char c;
99 
100 	if (count) {
101 		if (!nowayout) {
102 			clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
103 				&wdt_status);
104 
105 			/* is there a magic char ? */
106 			for (i = 0; i != count; i++) {
107 				if (get_user(c, buf + i))
108 					return -EFAULT;
109 				if (c == SBC7240_MAGIC_CHAR) {
110 					set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
111 						&wdt_status);
112 					break;
113 				}
114 			}
115 		}
116 
117 		wdt_keepalive();
118 	}
119 
120 	return count;
121 }
122 
fop_open(struct inode * inode,struct file * file)123 static int fop_open(struct inode *inode, struct file *file)
124 {
125 	if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status))
126 		return -EBUSY;
127 
128 	wdt_enable();
129 
130 	return stream_open(inode, file);
131 }
132 
fop_close(struct inode * inode,struct file * file)133 static int fop_close(struct inode *inode, struct file *file)
134 {
135 	if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status)
136 	    || !nowayout) {
137 		wdt_disable();
138 	} else {
139 		pr_crit("Unexpected close, not stopping watchdog!\n");
140 		wdt_keepalive();
141 	}
142 
143 	clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status);
144 	return 0;
145 }
146 
147 static const struct watchdog_info ident = {
148 	.options = WDIOF_KEEPALIVEPING|
149 		   WDIOF_SETTIMEOUT|
150 		   WDIOF_MAGICCLOSE,
151 	.firmware_version = 1,
152 	.identity = "SBC7240",
153 };
154 
155 
fop_ioctl(struct file * file,unsigned int cmd,unsigned long arg)156 static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
157 {
158 	switch (cmd) {
159 	case WDIOC_GETSUPPORT:
160 		return copy_to_user((void __user *)arg, &ident, sizeof(ident))
161 						 ? -EFAULT : 0;
162 	case WDIOC_GETSTATUS:
163 	case WDIOC_GETBOOTSTATUS:
164 		return put_user(0, (int __user *)arg);
165 	case WDIOC_SETOPTIONS:
166 	{
167 		int options;
168 		int retval = -EINVAL;
169 
170 		if (get_user(options, (int __user *)arg))
171 			return -EFAULT;
172 
173 		if (options & WDIOS_DISABLECARD) {
174 			wdt_disable();
175 			retval = 0;
176 		}
177 
178 		if (options & WDIOS_ENABLECARD) {
179 			wdt_enable();
180 			retval = 0;
181 		}
182 
183 		return retval;
184 	}
185 	case WDIOC_KEEPALIVE:
186 		wdt_keepalive();
187 		return 0;
188 	case WDIOC_SETTIMEOUT:
189 	{
190 		int new_timeout;
191 
192 		if (get_user(new_timeout, (int __user *)arg))
193 			return -EFAULT;
194 
195 		if (wdt_set_timeout(new_timeout))
196 			return -EINVAL;
197 	}
198 		fallthrough;
199 	case WDIOC_GETTIMEOUT:
200 		return put_user(timeout, (int __user *)arg);
201 	default:
202 		return -ENOTTY;
203 	}
204 }
205 
206 static const struct file_operations wdt_fops = {
207 	.owner = THIS_MODULE,
208 	.llseek = no_llseek,
209 	.write = fop_write,
210 	.open = fop_open,
211 	.release = fop_close,
212 	.unlocked_ioctl = fop_ioctl,
213 	.compat_ioctl = compat_ptr_ioctl,
214 };
215 
216 static struct miscdevice wdt_miscdev = {
217 	.minor = WATCHDOG_MINOR,
218 	.name = "watchdog",
219 	.fops = &wdt_fops,
220 };
221 
222 /*
223  *	Notifier for system down
224  */
225 
wdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)226 static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
227 			  void *unused)
228 {
229 	if (code == SYS_DOWN || code == SYS_HALT)
230 		wdt_disable();
231 	return NOTIFY_DONE;
232 }
233 
234 static struct notifier_block wdt_notifier = {
235 	.notifier_call = wdt_notify_sys,
236 };
237 
sbc7240_wdt_unload(void)238 static void __exit sbc7240_wdt_unload(void)
239 {
240 	pr_info("Removing watchdog\n");
241 	misc_deregister(&wdt_miscdev);
242 
243 	unregister_reboot_notifier(&wdt_notifier);
244 	release_region(SBC7240_ENABLE_PORT, 1);
245 }
246 
sbc7240_wdt_init(void)247 static int __init sbc7240_wdt_init(void)
248 {
249 	int rc = -EBUSY;
250 
251 	if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {
252 		pr_err("I/O address 0x%04x already in use\n",
253 		       SBC7240_ENABLE_PORT);
254 		rc = -EIO;
255 		goto err_out;
256 	}
257 
258 	/* The IO port 0x043 used to disable the watchdog
259 	 * is already claimed by the system timer, so we
260 	 * can't request_region() it ...*/
261 
262 	if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {
263 		timeout = SBC7240_TIMEOUT;
264 		pr_info("timeout value must be 1<=x<=%d, using %d\n",
265 			SBC7240_MAX_TIMEOUT, timeout);
266 	}
267 	wdt_set_timeout(timeout);
268 	wdt_disable();
269 
270 	rc = register_reboot_notifier(&wdt_notifier);
271 	if (rc) {
272 		pr_err("cannot register reboot notifier (err=%d)\n", rc);
273 		goto err_out_region;
274 	}
275 
276 	rc = misc_register(&wdt_miscdev);
277 	if (rc) {
278 		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
279 		       wdt_miscdev.minor, rc);
280 		goto err_out_reboot_notifier;
281 	}
282 
283 	pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n",
284 		nowayout);
285 
286 	return 0;
287 
288 err_out_reboot_notifier:
289 	unregister_reboot_notifier(&wdt_notifier);
290 err_out_region:
291 	release_region(SBC7240_ENABLE_PORT, 1);
292 err_out:
293 	return rc;
294 }
295 
296 module_init(sbc7240_wdt_init);
297 module_exit(sbc7240_wdt_unload);
298 
299 MODULE_AUTHOR("Gilles Gigan");
300 MODULE_DESCRIPTION("Watchdog device driver for single board"
301 		   " computers EPIC Nano 7240 from iEi");
302 MODULE_LICENSE("GPL");
303