cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

acquirewdt.c (8504B)


      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 */
     76static struct platform_device *acq_platform_device;
     77static unsigned long acq_is_open;
     78static char expect_close;
     79
     80/* module parameters */
     81/* You must set this - there is no sane way to probe for this board. */
     82static int wdt_stop = 0x43;
     83module_param(wdt_stop, int, 0);
     84MODULE_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. */
     87static int wdt_start = 0x443;
     88module_param(wdt_start, int, 0);
     89MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
     90
     91static bool nowayout = WATCHDOG_NOWAYOUT;
     92module_param(nowayout, bool, 0);
     93MODULE_PARM_DESC(nowayout,
     94	"Watchdog cannot be stopped once started (default="
     95	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     96
     97/*
     98 *	Watchdog Operations
     99 */
    100
    101static void acq_keepalive(void)
    102{
    103	/* Write a watchdog value */
    104	inb_p(wdt_start);
    105}
    106
    107static void acq_stop(void)
    108{
    109	/* Turn the card off */
    110	inb_p(wdt_stop);
    111}
    112
    113/*
    114 *	/dev/watchdog handling
    115 */
    116
    117static 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
    144static 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
    189static 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
    202static 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
    219static 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
    229static struct miscdevice acq_miscdev = {
    230	.minor	= WATCHDOG_MINOR,
    231	.name	= "watchdog",
    232	.fops	= &acq_fops,
    233};
    234
    235/*
    236 *	Init & exit routines
    237 */
    238
    239static 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;
    265unreg_regions:
    266	release_region(wdt_start, 1);
    267unreg_stop:
    268	if (wdt_stop != wdt_start)
    269		release_region(wdt_stop, 1);
    270out:
    271	return ret;
    272}
    273
    274static int 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	return 0;
    282}
    283
    284static void acq_shutdown(struct platform_device *dev)
    285{
    286	/* Turn the WDT off if we have a soft shutdown */
    287	acq_stop();
    288}
    289
    290static struct platform_driver acquirewdt_driver = {
    291	.remove		= acq_remove,
    292	.shutdown	= acq_shutdown,
    293	.driver		= {
    294		.name	= DRV_NAME,
    295	},
    296};
    297
    298static int __init acq_init(void)
    299{
    300	int err;
    301
    302	pr_info("WDT driver for Acquire single board computer initialising\n");
    303
    304	acq_platform_device = platform_device_register_simple(DRV_NAME,
    305								-1, NULL, 0);
    306	if (IS_ERR(acq_platform_device))
    307		return PTR_ERR(acq_platform_device);
    308
    309	err = platform_driver_probe(&acquirewdt_driver, acq_probe);
    310	if (err)
    311		goto unreg_platform_device;
    312	return 0;
    313
    314unreg_platform_device:
    315	platform_device_unregister(acq_platform_device);
    316	return err;
    317}
    318
    319static void __exit acq_exit(void)
    320{
    321	platform_device_unregister(acq_platform_device);
    322	platform_driver_unregister(&acquirewdt_driver);
    323	pr_info("Watchdog Module Unloaded\n");
    324}
    325
    326module_init(acq_init);
    327module_exit(acq_exit);
    328
    329MODULE_AUTHOR("David Woodhouse");
    330MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
    331MODULE_LICENSE("GPL");