1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * MixCom Watchdog: A Simple Hardware Watchdog Device
4*4882a593Smuzhiyun * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis
5*4882a593Smuzhiyun *
6*4882a593Smuzhiyun * Author: Gergely Madarasz <gorgo@itc.hu>
7*4882a593Smuzhiyun *
8*4882a593Smuzhiyun * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu>
9*4882a593Smuzhiyun *
10*4882a593Smuzhiyun * Version 0.1 (99/04/15):
11*4882a593Smuzhiyun * - first version
12*4882a593Smuzhiyun *
13*4882a593Smuzhiyun * Version 0.2 (99/06/16):
14*4882a593Smuzhiyun * - added kernel timer watchdog ping after close
15*4882a593Smuzhiyun * since the hardware does not support watchdog shutdown
16*4882a593Smuzhiyun *
17*4882a593Smuzhiyun * Version 0.3 (99/06/21):
18*4882a593Smuzhiyun * - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls
19*4882a593Smuzhiyun *
20*4882a593Smuzhiyun * Version 0.3.1 (99/06/22):
21*4882a593Smuzhiyun * - allow module removal while internal timer is active,
22*4882a593Smuzhiyun * print warning about probable reset
23*4882a593Smuzhiyun *
24*4882a593Smuzhiyun * Version 0.4 (99/11/15):
25*4882a593Smuzhiyun * - support for one more type board
26*4882a593Smuzhiyun *
27*4882a593Smuzhiyun * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com>
28*4882a593Smuzhiyun * - added nowayout module option to override
29*4882a593Smuzhiyun * CONFIG_WATCHDOG_NOWAYOUT
30*4882a593Smuzhiyun *
31*4882a593Smuzhiyun * Version 0.6 (2002/04/12): Rob Radez <rob@osinvestor.com>
32*4882a593Smuzhiyun * - make mixcomwd_opened unsigned,
33*4882a593Smuzhiyun * removed lock_kernel/unlock_kernel from mixcomwd_release,
34*4882a593Smuzhiyun * modified ioctl a bit to conform to API
35*4882a593Smuzhiyun */
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun #define VERSION "0.6"
40*4882a593Smuzhiyun #define WATCHDOG_NAME "mixcomwd"
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun #include <linux/module.h>
43*4882a593Smuzhiyun #include <linux/moduleparam.h>
44*4882a593Smuzhiyun #include <linux/types.h>
45*4882a593Smuzhiyun #include <linux/miscdevice.h>
46*4882a593Smuzhiyun #include <linux/ioport.h>
47*4882a593Smuzhiyun #include <linux/watchdog.h>
48*4882a593Smuzhiyun #include <linux/fs.h>
49*4882a593Smuzhiyun #include <linux/reboot.h>
50*4882a593Smuzhiyun #include <linux/init.h>
51*4882a593Smuzhiyun #include <linux/jiffies.h>
52*4882a593Smuzhiyun #include <linux/timer.h>
53*4882a593Smuzhiyun #include <linux/uaccess.h>
54*4882a593Smuzhiyun #include <linux/io.h>
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun /*
57*4882a593Smuzhiyun * We have two types of cards that can be probed:
58*4882a593Smuzhiyun * 1) The Mixcom cards: these cards can be found at addresses
59*4882a593Smuzhiyun * 0x180, 0x280, 0x380 with an additional offset of 0xc10.
60*4882a593Smuzhiyun * (Or 0xd90, 0xe90, 0xf90).
61*4882a593Smuzhiyun * 2) The FlashCOM cards: these cards can be set up at
62*4882a593Smuzhiyun * 0x300 -> 0x378, in 0x8 jumps with an offset of 0x04.
63*4882a593Smuzhiyun * (Or 0x304 -> 0x37c in 0x8 jumps).
64*4882a593Smuzhiyun * Each card has it's own ID.
65*4882a593Smuzhiyun */
66*4882a593Smuzhiyun #define MIXCOM_ID 0x11
67*4882a593Smuzhiyun #define FLASHCOM_ID 0x18
68*4882a593Smuzhiyun static struct {
69*4882a593Smuzhiyun int ioport;
70*4882a593Smuzhiyun int id;
71*4882a593Smuzhiyun } mixcomwd_io_info[] = {
72*4882a593Smuzhiyun /* The Mixcom cards */
73*4882a593Smuzhiyun {0x0d90, MIXCOM_ID},
74*4882a593Smuzhiyun {0x0e90, MIXCOM_ID},
75*4882a593Smuzhiyun {0x0f90, MIXCOM_ID},
76*4882a593Smuzhiyun /* The FlashCOM cards */
77*4882a593Smuzhiyun {0x0304, FLASHCOM_ID},
78*4882a593Smuzhiyun {0x030c, FLASHCOM_ID},
79*4882a593Smuzhiyun {0x0314, FLASHCOM_ID},
80*4882a593Smuzhiyun {0x031c, FLASHCOM_ID},
81*4882a593Smuzhiyun {0x0324, FLASHCOM_ID},
82*4882a593Smuzhiyun {0x032c, FLASHCOM_ID},
83*4882a593Smuzhiyun {0x0334, FLASHCOM_ID},
84*4882a593Smuzhiyun {0x033c, FLASHCOM_ID},
85*4882a593Smuzhiyun {0x0344, FLASHCOM_ID},
86*4882a593Smuzhiyun {0x034c, FLASHCOM_ID},
87*4882a593Smuzhiyun {0x0354, FLASHCOM_ID},
88*4882a593Smuzhiyun {0x035c, FLASHCOM_ID},
89*4882a593Smuzhiyun {0x0364, FLASHCOM_ID},
90*4882a593Smuzhiyun {0x036c, FLASHCOM_ID},
91*4882a593Smuzhiyun {0x0374, FLASHCOM_ID},
92*4882a593Smuzhiyun {0x037c, FLASHCOM_ID},
93*4882a593Smuzhiyun /* The end of the list */
94*4882a593Smuzhiyun {0x0000, 0},
95*4882a593Smuzhiyun };
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun static void mixcomwd_timerfun(struct timer_list *unused);
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun static int watchdog_port;
102*4882a593Smuzhiyun static int mixcomwd_timer_alive;
103*4882a593Smuzhiyun static DEFINE_TIMER(mixcomwd_timer, mixcomwd_timerfun);
104*4882a593Smuzhiyun static char expect_close;
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
107*4882a593Smuzhiyun module_param(nowayout, bool, 0);
108*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout,
109*4882a593Smuzhiyun "Watchdog cannot be stopped once started (default="
110*4882a593Smuzhiyun __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
111*4882a593Smuzhiyun
mixcomwd_ping(void)112*4882a593Smuzhiyun static void mixcomwd_ping(void)
113*4882a593Smuzhiyun {
114*4882a593Smuzhiyun outb_p(55, watchdog_port);
115*4882a593Smuzhiyun return;
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun
mixcomwd_timerfun(struct timer_list * unused)118*4882a593Smuzhiyun static void mixcomwd_timerfun(struct timer_list *unused)
119*4882a593Smuzhiyun {
120*4882a593Smuzhiyun mixcomwd_ping();
121*4882a593Smuzhiyun mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
122*4882a593Smuzhiyun }
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun /*
125*4882a593Smuzhiyun * Allow only one person to hold it open
126*4882a593Smuzhiyun */
127*4882a593Smuzhiyun
mixcomwd_open(struct inode * inode,struct file * file)128*4882a593Smuzhiyun static int mixcomwd_open(struct inode *inode, struct file *file)
129*4882a593Smuzhiyun {
130*4882a593Smuzhiyun if (test_and_set_bit(0, &mixcomwd_opened))
131*4882a593Smuzhiyun return -EBUSY;
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun mixcomwd_ping();
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun if (nowayout)
136*4882a593Smuzhiyun /*
137*4882a593Smuzhiyun * fops_get() code via open() has already done
138*4882a593Smuzhiyun * a try_module_get() so it is safe to do the
139*4882a593Smuzhiyun * __module_get().
140*4882a593Smuzhiyun */
141*4882a593Smuzhiyun __module_get(THIS_MODULE);
142*4882a593Smuzhiyun else {
143*4882a593Smuzhiyun if (mixcomwd_timer_alive) {
144*4882a593Smuzhiyun del_timer(&mixcomwd_timer);
145*4882a593Smuzhiyun mixcomwd_timer_alive = 0;
146*4882a593Smuzhiyun }
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun return stream_open(inode, file);
149*4882a593Smuzhiyun }
150*4882a593Smuzhiyun
mixcomwd_release(struct inode * inode,struct file * file)151*4882a593Smuzhiyun static int mixcomwd_release(struct inode *inode, struct file *file)
152*4882a593Smuzhiyun {
153*4882a593Smuzhiyun if (expect_close == 42) {
154*4882a593Smuzhiyun if (mixcomwd_timer_alive) {
155*4882a593Smuzhiyun pr_err("release called while internal timer alive\n");
156*4882a593Smuzhiyun return -EBUSY;
157*4882a593Smuzhiyun }
158*4882a593Smuzhiyun mixcomwd_timer_alive = 1;
159*4882a593Smuzhiyun mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
160*4882a593Smuzhiyun } else
161*4882a593Smuzhiyun pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun clear_bit(0, &mixcomwd_opened);
164*4882a593Smuzhiyun expect_close = 0;
165*4882a593Smuzhiyun return 0;
166*4882a593Smuzhiyun }
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun
mixcomwd_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)169*4882a593Smuzhiyun static ssize_t mixcomwd_write(struct file *file, const char __user *data,
170*4882a593Smuzhiyun size_t len, loff_t *ppos)
171*4882a593Smuzhiyun {
172*4882a593Smuzhiyun if (len) {
173*4882a593Smuzhiyun if (!nowayout) {
174*4882a593Smuzhiyun size_t i;
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun /* In case it was set long ago */
177*4882a593Smuzhiyun expect_close = 0;
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun for (i = 0; i != len; i++) {
180*4882a593Smuzhiyun char c;
181*4882a593Smuzhiyun if (get_user(c, data + i))
182*4882a593Smuzhiyun return -EFAULT;
183*4882a593Smuzhiyun if (c == 'V')
184*4882a593Smuzhiyun expect_close = 42;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun }
187*4882a593Smuzhiyun mixcomwd_ping();
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun return len;
190*4882a593Smuzhiyun }
191*4882a593Smuzhiyun
mixcomwd_ioctl(struct file * file,unsigned int cmd,unsigned long arg)192*4882a593Smuzhiyun static long mixcomwd_ioctl(struct file *file,
193*4882a593Smuzhiyun unsigned int cmd, unsigned long arg)
194*4882a593Smuzhiyun {
195*4882a593Smuzhiyun void __user *argp = (void __user *)arg;
196*4882a593Smuzhiyun int __user *p = argp;
197*4882a593Smuzhiyun int status;
198*4882a593Smuzhiyun static const struct watchdog_info ident = {
199*4882a593Smuzhiyun .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
200*4882a593Smuzhiyun .firmware_version = 1,
201*4882a593Smuzhiyun .identity = "MixCOM watchdog",
202*4882a593Smuzhiyun };
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun switch (cmd) {
205*4882a593Smuzhiyun case WDIOC_GETSUPPORT:
206*4882a593Smuzhiyun if (copy_to_user(argp, &ident, sizeof(ident)))
207*4882a593Smuzhiyun return -EFAULT;
208*4882a593Smuzhiyun break;
209*4882a593Smuzhiyun case WDIOC_GETSTATUS:
210*4882a593Smuzhiyun status = mixcomwd_opened;
211*4882a593Smuzhiyun if (!nowayout)
212*4882a593Smuzhiyun status |= mixcomwd_timer_alive;
213*4882a593Smuzhiyun return put_user(status, p);
214*4882a593Smuzhiyun case WDIOC_GETBOOTSTATUS:
215*4882a593Smuzhiyun return put_user(0, p);
216*4882a593Smuzhiyun case WDIOC_KEEPALIVE:
217*4882a593Smuzhiyun mixcomwd_ping();
218*4882a593Smuzhiyun break;
219*4882a593Smuzhiyun default:
220*4882a593Smuzhiyun return -ENOTTY;
221*4882a593Smuzhiyun }
222*4882a593Smuzhiyun return 0;
223*4882a593Smuzhiyun }
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun static const struct file_operations mixcomwd_fops = {
226*4882a593Smuzhiyun .owner = THIS_MODULE,
227*4882a593Smuzhiyun .llseek = no_llseek,
228*4882a593Smuzhiyun .write = mixcomwd_write,
229*4882a593Smuzhiyun .unlocked_ioctl = mixcomwd_ioctl,
230*4882a593Smuzhiyun .compat_ioctl = compat_ptr_ioctl,
231*4882a593Smuzhiyun .open = mixcomwd_open,
232*4882a593Smuzhiyun .release = mixcomwd_release,
233*4882a593Smuzhiyun };
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun static struct miscdevice mixcomwd_miscdev = {
236*4882a593Smuzhiyun .minor = WATCHDOG_MINOR,
237*4882a593Smuzhiyun .name = "watchdog",
238*4882a593Smuzhiyun .fops = &mixcomwd_fops,
239*4882a593Smuzhiyun };
240*4882a593Smuzhiyun
checkcard(int port,int card_id)241*4882a593Smuzhiyun static int __init checkcard(int port, int card_id)
242*4882a593Smuzhiyun {
243*4882a593Smuzhiyun int id;
244*4882a593Smuzhiyun
245*4882a593Smuzhiyun if (!request_region(port, 1, "MixCOM watchdog"))
246*4882a593Smuzhiyun return 0;
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun id = inb_p(port);
249*4882a593Smuzhiyun if (card_id == MIXCOM_ID)
250*4882a593Smuzhiyun id &= 0x3f;
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun if (id != card_id) {
253*4882a593Smuzhiyun release_region(port, 1);
254*4882a593Smuzhiyun return 0;
255*4882a593Smuzhiyun }
256*4882a593Smuzhiyun return 1;
257*4882a593Smuzhiyun }
258*4882a593Smuzhiyun
mixcomwd_init(void)259*4882a593Smuzhiyun static int __init mixcomwd_init(void)
260*4882a593Smuzhiyun {
261*4882a593Smuzhiyun int i, ret, found = 0;
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun for (i = 0; !found && mixcomwd_io_info[i].ioport != 0; i++) {
264*4882a593Smuzhiyun if (checkcard(mixcomwd_io_info[i].ioport,
265*4882a593Smuzhiyun mixcomwd_io_info[i].id)) {
266*4882a593Smuzhiyun found = 1;
267*4882a593Smuzhiyun watchdog_port = mixcomwd_io_info[i].ioport;
268*4882a593Smuzhiyun }
269*4882a593Smuzhiyun }
270*4882a593Smuzhiyun
271*4882a593Smuzhiyun if (!found) {
272*4882a593Smuzhiyun pr_err("No card detected, or port not available\n");
273*4882a593Smuzhiyun return -ENODEV;
274*4882a593Smuzhiyun }
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun ret = misc_register(&mixcomwd_miscdev);
277*4882a593Smuzhiyun if (ret) {
278*4882a593Smuzhiyun pr_err("cannot register miscdev on minor=%d (err=%d)\n",
279*4882a593Smuzhiyun WATCHDOG_MINOR, ret);
280*4882a593Smuzhiyun goto error_misc_register_watchdog;
281*4882a593Smuzhiyun }
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun pr_info("MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",
284*4882a593Smuzhiyun VERSION, watchdog_port);
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun return 0;
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun error_misc_register_watchdog:
289*4882a593Smuzhiyun release_region(watchdog_port, 1);
290*4882a593Smuzhiyun watchdog_port = 0x0000;
291*4882a593Smuzhiyun return ret;
292*4882a593Smuzhiyun }
293*4882a593Smuzhiyun
mixcomwd_exit(void)294*4882a593Smuzhiyun static void __exit mixcomwd_exit(void)
295*4882a593Smuzhiyun {
296*4882a593Smuzhiyun if (!nowayout) {
297*4882a593Smuzhiyun if (mixcomwd_timer_alive) {
298*4882a593Smuzhiyun pr_warn("I quit now, hardware will probably reboot!\n");
299*4882a593Smuzhiyun del_timer_sync(&mixcomwd_timer);
300*4882a593Smuzhiyun mixcomwd_timer_alive = 0;
301*4882a593Smuzhiyun }
302*4882a593Smuzhiyun }
303*4882a593Smuzhiyun misc_deregister(&mixcomwd_miscdev);
304*4882a593Smuzhiyun release_region(watchdog_port, 1);
305*4882a593Smuzhiyun }
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun module_init(mixcomwd_init);
308*4882a593Smuzhiyun module_exit(mixcomwd_exit);
309*4882a593Smuzhiyun
310*4882a593Smuzhiyun MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
311*4882a593Smuzhiyun MODULE_DESCRIPTION("MixCom Watchdog driver");
312*4882a593Smuzhiyun MODULE_VERSION(VERSION);
313*4882a593Smuzhiyun MODULE_LICENSE("GPL");
314