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

sbc60xxwdt.c (10172B)


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