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

w83877f_wdt.c (9997B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *	W83877F Computer Watchdog Timer driver
      4 *
      5 *      Based on acquirewdt.c by Alan Cox,
      6 *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
      7 *
      8 *	The authors do NOT admit liability nor provide warranty for
      9 *	any of this software. This material is provided "AS-IS" in
     10 *      the hope that it may be useful for others.
     11 *
     12 *	(c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
     13 *
     14 *           4/19 - 2001      [Initial revision]
     15 *           9/27 - 2001      Added spinlocking
     16 *           4/12 - 2002      [rob@osinvestor.com] Eliminate extra comments
     17 *                            Eliminate fop_read
     18 *                            Eliminate extra spin_unlock
     19 *                            Added KERN_* tags to printks
     20 *                            add CONFIG_WATCHDOG_NOWAYOUT support
     21 *                            fix possible wdt_is_open race
     22 *                            changed watchdog_info to correctly reflect what
     23 *			      the driver offers
     24 *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
     25 *			      WDIOC_SETTIMEOUT,
     26 *                            WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
     27 *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
     28 *                            added extra printk's for startup problems
     29 *                            use module_param
     30 *                            made timeout (the emulated heartbeat) a
     31 *			      module_param
     32 *                            made the keepalive ping an internal subroutine
     33 *
     34 *  This WDT driver is different from most other Linux WDT
     35 *  drivers in that the driver will ping the watchdog by itself,
     36 *  because this particular WDT has a very short timeout (1.6
     37 *  seconds) and it would be insane to count on any userspace
     38 *  daemon always getting scheduled within that time frame.
     39 */
     40
     41#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
     42
     43#include <linux/module.h>
     44#include <linux/moduleparam.h>
     45#include <linux/types.h>
     46#include <linux/timer.h>
     47#include <linux/jiffies.h>
     48#include <linux/miscdevice.h>
     49#include <linux/watchdog.h>
     50#include <linux/fs.h>
     51#include <linux/ioport.h>
     52#include <linux/notifier.h>
     53#include <linux/reboot.h>
     54#include <linux/init.h>
     55#include <linux/io.h>
     56#include <linux/uaccess.h>
     57
     58#define OUR_NAME "w83877f_wdt"
     59
     60#define ENABLE_W83877F_PORT 0x3F0
     61#define ENABLE_W83877F 0x87
     62#define DISABLE_W83877F 0xAA
     63#define WDT_PING 0x443
     64#define WDT_REGISTER 0x14
     65#define WDT_ENABLE 0x9C
     66#define WDT_DISABLE 0x8C
     67
     68/*
     69 * The W83877F seems to be fixed at 1.6s timeout (at least on the
     70 * EMACS PC-104 board I'm using). If we reset the watchdog every
     71 * ~250ms we should be safe.  */
     72
     73#define WDT_INTERVAL (HZ/4+1)
     74
     75/*
     76 * We must not require too good response from the userspace daemon.
     77 * Here we require the userspace daemon to send us a heartbeat
     78 * char to /dev/watchdog every 30 seconds.
     79 */
     80
     81#define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
     82/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
     83static int timeout = WATCHDOG_TIMEOUT;
     84module_param(timeout, int, 0);
     85MODULE_PARM_DESC(timeout,
     86	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
     87				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
     88
     89
     90static bool nowayout = WATCHDOG_NOWAYOUT;
     91module_param(nowayout, bool, 0);
     92MODULE_PARM_DESC(nowayout,
     93		"Watchdog cannot be stopped once started (default="
     94				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     95
     96static void wdt_timer_ping(struct timer_list *);
     97static DEFINE_TIMER(timer, wdt_timer_ping);
     98static unsigned long next_heartbeat;
     99static unsigned long wdt_is_open;
    100static char wdt_expect_close;
    101static DEFINE_SPINLOCK(wdt_spinlock);
    102
    103/*
    104 *	Whack the dog
    105 */
    106
    107static void wdt_timer_ping(struct timer_list *unused)
    108{
    109	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
    110	 * we agree to ping the WDT
    111	 */
    112	if (time_before(jiffies, next_heartbeat)) {
    113		/* Ping the WDT */
    114		spin_lock(&wdt_spinlock);
    115
    116		/* Ping the WDT by reading from WDT_PING */
    117		inb_p(WDT_PING);
    118
    119		/* Re-set the timer interval */
    120		mod_timer(&timer, jiffies + WDT_INTERVAL);
    121
    122		spin_unlock(&wdt_spinlock);
    123
    124	} else
    125		pr_warn("Heartbeat lost! Will not ping the watchdog\n");
    126}
    127
    128/*
    129 * Utility routines
    130 */
    131
    132static void wdt_change(int writeval)
    133{
    134	unsigned long flags;
    135	spin_lock_irqsave(&wdt_spinlock, flags);
    136
    137	/* buy some time */
    138	inb_p(WDT_PING);
    139
    140	/* make W83877F available */
    141	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
    142	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
    143
    144	/* enable watchdog */
    145	outb_p(WDT_REGISTER,    ENABLE_W83877F_PORT);
    146	outb_p(writeval,        ENABLE_W83877F_PORT+1);
    147
    148	/* lock the W8387FF away */
    149	outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT);
    150
    151	spin_unlock_irqrestore(&wdt_spinlock, flags);
    152}
    153
    154static void wdt_startup(void)
    155{
    156	next_heartbeat = jiffies + (timeout * HZ);
    157
    158	/* Start the timer */
    159	mod_timer(&timer, jiffies + WDT_INTERVAL);
    160
    161	wdt_change(WDT_ENABLE);
    162
    163	pr_info("Watchdog timer is now enabled\n");
    164}
    165
    166static void wdt_turnoff(void)
    167{
    168	/* Stop the timer */
    169	del_timer_sync(&timer);
    170
    171	wdt_change(WDT_DISABLE);
    172
    173	pr_info("Watchdog timer is now disabled...\n");
    174}
    175
    176static void wdt_keepalive(void)
    177{
    178	/* user land ping */
    179	next_heartbeat = jiffies + (timeout * HZ);
    180}
    181
    182/*
    183 * /dev/watchdog handling
    184 */
    185
    186static ssize_t fop_write(struct file *file, const char __user *buf,
    187						size_t count, loff_t *ppos)
    188{
    189	/* See if we got the magic character 'V' and reload the timer */
    190	if (count) {
    191		if (!nowayout) {
    192			size_t ofs;
    193
    194			/* note: just in case someone wrote the magic
    195			   character five months ago... */
    196			wdt_expect_close = 0;
    197
    198			/* scan to see whether or not we got the
    199			   magic character */
    200			for (ofs = 0; ofs != count; ofs++) {
    201				char c;
    202				if (get_user(c, buf + ofs))
    203					return -EFAULT;
    204				if (c == 'V')
    205					wdt_expect_close = 42;
    206			}
    207		}
    208
    209		/* someone wrote to us, we should restart timer */
    210		wdt_keepalive();
    211	}
    212	return count;
    213}
    214
    215static int fop_open(struct inode *inode, struct file *file)
    216{
    217	/* Just in case we're already talking to someone... */
    218	if (test_and_set_bit(0, &wdt_is_open))
    219		return -EBUSY;
    220
    221	/* Good, fire up the show */
    222	wdt_startup();
    223	return stream_open(inode, file);
    224}
    225
    226static int fop_close(struct inode *inode, struct file *file)
    227{
    228	if (wdt_expect_close == 42)
    229		wdt_turnoff();
    230	else {
    231		del_timer(&timer);
    232		pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
    233	}
    234	clear_bit(0, &wdt_is_open);
    235	wdt_expect_close = 0;
    236	return 0;
    237}
    238
    239static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    240{
    241	void __user *argp = (void __user *)arg;
    242	int __user *p = argp;
    243	static const struct watchdog_info ident = {
    244		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
    245							| WDIOF_MAGICCLOSE,
    246		.firmware_version = 1,
    247		.identity = "W83877F",
    248	};
    249
    250	switch (cmd) {
    251	case WDIOC_GETSUPPORT:
    252		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
    253	case WDIOC_GETSTATUS:
    254	case WDIOC_GETBOOTSTATUS:
    255		return put_user(0, p);
    256	case WDIOC_SETOPTIONS:
    257	{
    258		int new_options, retval = -EINVAL;
    259
    260		if (get_user(new_options, p))
    261			return -EFAULT;
    262
    263		if (new_options & WDIOS_DISABLECARD) {
    264			wdt_turnoff();
    265			retval = 0;
    266		}
    267
    268		if (new_options & WDIOS_ENABLECARD) {
    269			wdt_startup();
    270			retval = 0;
    271		}
    272
    273		return retval;
    274	}
    275	case WDIOC_KEEPALIVE:
    276		wdt_keepalive();
    277		return 0;
    278	case WDIOC_SETTIMEOUT:
    279	{
    280		int new_timeout;
    281
    282		if (get_user(new_timeout, p))
    283			return -EFAULT;
    284
    285		/* arbitrary upper limit */
    286		if (new_timeout < 1 || new_timeout > 3600)
    287			return -EINVAL;
    288
    289		timeout = new_timeout;
    290		wdt_keepalive();
    291	}
    292		fallthrough;
    293	case WDIOC_GETTIMEOUT:
    294		return put_user(timeout, p);
    295	default:
    296		return -ENOTTY;
    297	}
    298}
    299
    300static const struct file_operations wdt_fops = {
    301	.owner		= THIS_MODULE,
    302	.llseek		= no_llseek,
    303	.write		= fop_write,
    304	.open		= fop_open,
    305	.release	= fop_close,
    306	.unlocked_ioctl	= fop_ioctl,
    307	.compat_ioctl	= compat_ptr_ioctl,
    308};
    309
    310static struct miscdevice wdt_miscdev = {
    311	.minor	= WATCHDOG_MINOR,
    312	.name	= "watchdog",
    313	.fops	= &wdt_fops,
    314};
    315
    316/*
    317 *	Notifier for system down
    318 */
    319
    320static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
    321	void *unused)
    322{
    323	if (code == SYS_DOWN || code == SYS_HALT)
    324		wdt_turnoff();
    325	return NOTIFY_DONE;
    326}
    327
    328/*
    329 *	The WDT needs to learn about soft shutdowns in order to
    330 *	turn the timebomb registers off.
    331 */
    332
    333static struct notifier_block wdt_notifier = {
    334	.notifier_call = wdt_notify_sys,
    335};
    336
    337static void __exit w83877f_wdt_unload(void)
    338{
    339	wdt_turnoff();
    340
    341	/* Deregister */
    342	misc_deregister(&wdt_miscdev);
    343
    344	unregister_reboot_notifier(&wdt_notifier);
    345	release_region(WDT_PING, 1);
    346	release_region(ENABLE_W83877F_PORT, 2);
    347}
    348
    349static int __init w83877f_wdt_init(void)
    350{
    351	int rc = -EBUSY;
    352
    353	if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
    354		timeout = WATCHDOG_TIMEOUT;
    355		pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
    356			timeout);
    357	}
    358
    359	if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) {
    360		pr_err("I/O address 0x%04x already in use\n",
    361		       ENABLE_W83877F_PORT);
    362		rc = -EIO;
    363		goto err_out;
    364	}
    365
    366	if (!request_region(WDT_PING, 1, "W8387FF WDT")) {
    367		pr_err("I/O address 0x%04x already in use\n", WDT_PING);
    368		rc = -EIO;
    369		goto err_out_region1;
    370	}
    371
    372	rc = register_reboot_notifier(&wdt_notifier);
    373	if (rc) {
    374		pr_err("cannot register reboot notifier (err=%d)\n", rc);
    375		goto err_out_region2;
    376	}
    377
    378	rc = misc_register(&wdt_miscdev);
    379	if (rc) {
    380		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
    381		       wdt_miscdev.minor, rc);
    382		goto err_out_reboot;
    383	}
    384
    385	pr_info("WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
    386		timeout, nowayout);
    387
    388	return 0;
    389
    390err_out_reboot:
    391	unregister_reboot_notifier(&wdt_notifier);
    392err_out_region2:
    393	release_region(WDT_PING, 1);
    394err_out_region1:
    395	release_region(ENABLE_W83877F_PORT, 2);
    396err_out:
    397	return rc;
    398}
    399
    400module_init(w83877f_wdt_init);
    401module_exit(w83877f_wdt_unload);
    402
    403MODULE_AUTHOR("Scott and Bill Jennings");
    404MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip");
    405MODULE_LICENSE("GPL");