xref: /openbmc/linux/drivers/watchdog/acquirewdt.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
1  // SPDX-License-Identifier: GPL-2.0+
2  /*
3   *	Acquire Single Board Computer Watchdog Timer driver
4   *
5   *	Based on wdt.c. Original copyright messages:
6   *
7   *	(c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
8   *						All Rights Reserved.
9   *
10   *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
11   *	warranty for any of this software. This material is provided
12   *	"AS-IS" and at no charge.
13   *
14   *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
15   *
16   *	14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
17   *	    Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
18   *	    Can't add timeout - driver doesn't allow changing value
19   */
20  
21  /*
22   *	Theory of Operation:
23   *		The Watch-Dog Timer is provided to ensure that standalone
24   *		Systems can always recover from catastrophic conditions that
25   *		caused the CPU to crash. This condition may have occurred by
26   *		external EMI or a software bug. When the CPU stops working
27   *		correctly, hardware on the board will either perform a hardware
28   *		reset (cold boot) or a non-maskable interrupt (NMI) to bring the
29   *		system back to a known state.
30   *
31   *		The Watch-Dog Timer is controlled by two I/O Ports.
32   *		  443 hex	- Read	- Enable or refresh the Watch-Dog Timer
33   *		  043 hex	- Read	- Disable the Watch-Dog Timer
34   *
35   *		To enable the Watch-Dog Timer, a read from I/O port 443h must
36   *		be performed. This will enable and activate the countdown timer
37   *		which will eventually time out and either reset the CPU or cause
38   *		an NMI depending on the setting of a jumper. To ensure that this
39   *		reset condition does not occur, the Watch-Dog Timer must be
40   *		periodically refreshed by reading the same I/O port 443h.
41   *		The Watch-Dog Timer is disabled by reading I/O port 043h.
42   *
43   *		The Watch-Dog Timer Time-Out Period is set via jumpers.
44   *		It can be 1, 2, 10, 20, 110 or 220 seconds.
45   */
46  
47  /*
48   *	Includes, defines, variables, module parameters, ...
49   */
50  
51  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
52  
53  /* Includes */
54  #include <linux/module.h>		/* For module specific items */
55  #include <linux/moduleparam.h>		/* For new moduleparam's */
56  #include <linux/types.h>		/* For standard types (like size_t) */
57  #include <linux/errno.h>		/* For the -ENODEV/... values */
58  #include <linux/kernel.h>		/* For printk/panic/... */
59  #include <linux/miscdevice.h>		/* For struct miscdevice */
60  #include <linux/watchdog.h>		/* For the watchdog specific items */
61  #include <linux/fs.h>			/* For file operations */
62  #include <linux/ioport.h>		/* For io-port access */
63  #include <linux/platform_device.h>	/* For platform_driver framework */
64  #include <linux/init.h>			/* For __init/__exit/... */
65  #include <linux/uaccess.h>		/* For copy_to_user/put_user/... */
66  #include <linux/io.h>			/* For inb/outb/... */
67  
68  /* Module information */
69  #define DRV_NAME "acquirewdt"
70  #define WATCHDOG_NAME "Acquire WDT"
71  /* There is no way to see what the correct time-out period is */
72  #define WATCHDOG_HEARTBEAT 0
73  
74  /* internal variables */
75  /* the watchdog platform device */
76  static struct platform_device *acq_platform_device;
77  static unsigned long acq_is_open;
78  static char expect_close;
79  
80  /* module parameters */
81  /* You must set this - there is no sane way to probe for this board. */
82  static int wdt_stop = 0x43;
83  module_param(wdt_stop, int, 0);
84  MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
85  
86  /* You must set this - there is no sane way to probe for this board. */
87  static int wdt_start = 0x443;
88  module_param(wdt_start, int, 0);
89  MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
90  
91  static bool nowayout = WATCHDOG_NOWAYOUT;
92  module_param(nowayout, bool, 0);
93  MODULE_PARM_DESC(nowayout,
94  	"Watchdog cannot be stopped once started (default="
95  	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
96  
97  /*
98   *	Watchdog Operations
99   */
100  
acq_keepalive(void)101  static void acq_keepalive(void)
102  {
103  	/* Write a watchdog value */
104  	inb_p(wdt_start);
105  }
106  
acq_stop(void)107  static void acq_stop(void)
108  {
109  	/* Turn the card off */
110  	inb_p(wdt_stop);
111  }
112  
113  /*
114   *	/dev/watchdog handling
115   */
116  
acq_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)117  static ssize_t acq_write(struct file *file, const char __user *buf,
118  						size_t count, loff_t *ppos)
119  {
120  	/* See if we got the magic character 'V' and reload the timer */
121  	if (count) {
122  		if (!nowayout) {
123  			size_t i;
124  			/* note: just in case someone wrote the magic character
125  			   five months ago... */
126  			expect_close = 0;
127  			/* scan to see whether or not we got the
128  			   magic character */
129  			for (i = 0; i != count; i++) {
130  				char c;
131  				if (get_user(c, buf + i))
132  					return -EFAULT;
133  				if (c == 'V')
134  					expect_close = 42;
135  			}
136  		}
137  		/* Well, anyhow someone wrote to us, we should
138  				return that favour */
139  		acq_keepalive();
140  	}
141  	return count;
142  }
143  
acq_ioctl(struct file * file,unsigned int cmd,unsigned long arg)144  static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
145  {
146  	int options, retval = -EINVAL;
147  	void __user *argp = (void __user *)arg;
148  	int __user *p = argp;
149  	static const struct watchdog_info ident = {
150  		.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
151  		.firmware_version = 1,
152  		.identity = WATCHDOG_NAME,
153  	};
154  
155  	switch (cmd) {
156  	case WDIOC_GETSUPPORT:
157  		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
158  
159  	case WDIOC_GETSTATUS:
160  	case WDIOC_GETBOOTSTATUS:
161  		return put_user(0, p);
162  
163  	case WDIOC_SETOPTIONS:
164  	{
165  		if (get_user(options, p))
166  			return -EFAULT;
167  		if (options & WDIOS_DISABLECARD) {
168  			acq_stop();
169  			retval = 0;
170  		}
171  		if (options & WDIOS_ENABLECARD) {
172  			acq_keepalive();
173  			retval = 0;
174  		}
175  		return retval;
176  	}
177  	case WDIOC_KEEPALIVE:
178  		acq_keepalive();
179  		return 0;
180  
181  	case WDIOC_GETTIMEOUT:
182  		return put_user(WATCHDOG_HEARTBEAT, p);
183  
184  	default:
185  		return -ENOTTY;
186  	}
187  }
188  
acq_open(struct inode * inode,struct file * file)189  static int acq_open(struct inode *inode, struct file *file)
190  {
191  	if (test_and_set_bit(0, &acq_is_open))
192  		return -EBUSY;
193  
194  	if (nowayout)
195  		__module_get(THIS_MODULE);
196  
197  	/* Activate */
198  	acq_keepalive();
199  	return stream_open(inode, file);
200  }
201  
acq_close(struct inode * inode,struct file * file)202  static int acq_close(struct inode *inode, struct file *file)
203  {
204  	if (expect_close == 42) {
205  		acq_stop();
206  	} else {
207  		pr_crit("Unexpected close, not stopping watchdog!\n");
208  		acq_keepalive();
209  	}
210  	clear_bit(0, &acq_is_open);
211  	expect_close = 0;
212  	return 0;
213  }
214  
215  /*
216   *	Kernel Interfaces
217   */
218  
219  static const struct file_operations acq_fops = {
220  	.owner		= THIS_MODULE,
221  	.llseek		= no_llseek,
222  	.write		= acq_write,
223  	.unlocked_ioctl	= acq_ioctl,
224  	.compat_ioctl	= compat_ptr_ioctl,
225  	.open		= acq_open,
226  	.release	= acq_close,
227  };
228  
229  static struct miscdevice acq_miscdev = {
230  	.minor	= WATCHDOG_MINOR,
231  	.name	= "watchdog",
232  	.fops	= &acq_fops,
233  };
234  
235  /*
236   *	Init & exit routines
237   */
238  
acq_probe(struct platform_device * dev)239  static int __init acq_probe(struct platform_device *dev)
240  {
241  	int ret;
242  
243  	if (wdt_stop != wdt_start) {
244  		if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
245  			pr_err("I/O address 0x%04x already in use\n", wdt_stop);
246  			ret = -EIO;
247  			goto out;
248  		}
249  	}
250  
251  	if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
252  		pr_err("I/O address 0x%04x already in use\n", wdt_start);
253  		ret = -EIO;
254  		goto unreg_stop;
255  	}
256  	ret = misc_register(&acq_miscdev);
257  	if (ret != 0) {
258  		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
259  		       WATCHDOG_MINOR, ret);
260  		goto unreg_regions;
261  	}
262  	pr_info("initialized. (nowayout=%d)\n", nowayout);
263  
264  	return 0;
265  unreg_regions:
266  	release_region(wdt_start, 1);
267  unreg_stop:
268  	if (wdt_stop != wdt_start)
269  		release_region(wdt_stop, 1);
270  out:
271  	return ret;
272  }
273  
acq_remove(struct platform_device * dev)274  static void acq_remove(struct platform_device *dev)
275  {
276  	misc_deregister(&acq_miscdev);
277  	release_region(wdt_start, 1);
278  	if (wdt_stop != wdt_start)
279  		release_region(wdt_stop, 1);
280  }
281  
acq_shutdown(struct platform_device * dev)282  static void acq_shutdown(struct platform_device *dev)
283  {
284  	/* Turn the WDT off if we have a soft shutdown */
285  	acq_stop();
286  }
287  
288  static struct platform_driver acquirewdt_driver = {
289  	.remove_new	= acq_remove,
290  	.shutdown	= acq_shutdown,
291  	.driver		= {
292  		.name	= DRV_NAME,
293  	},
294  };
295  
acq_init(void)296  static int __init acq_init(void)
297  {
298  	int err;
299  
300  	pr_info("WDT driver for Acquire single board computer initialising\n");
301  
302  	acq_platform_device = platform_device_register_simple(DRV_NAME,
303  								-1, NULL, 0);
304  	if (IS_ERR(acq_platform_device))
305  		return PTR_ERR(acq_platform_device);
306  
307  	err = platform_driver_probe(&acquirewdt_driver, acq_probe);
308  	if (err)
309  		goto unreg_platform_device;
310  	return 0;
311  
312  unreg_platform_device:
313  	platform_device_unregister(acq_platform_device);
314  	return err;
315  }
316  
acq_exit(void)317  static void __exit acq_exit(void)
318  {
319  	platform_device_unregister(acq_platform_device);
320  	platform_driver_unregister(&acquirewdt_driver);
321  	pr_info("Watchdog Module Unloaded\n");
322  }
323  
324  module_init(acq_init);
325  module_exit(acq_exit);
326  
327  MODULE_AUTHOR("David Woodhouse");
328  MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
329  MODULE_LICENSE("GPL");
330