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

it8712f_wdt.c (9999B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *	IT8712F "Smart Guardian" Watchdog support
      4 *
      5 *	Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net>
      6 *
      7 *	Based on info and code taken from:
      8 *
      9 *	drivers/char/watchdog/scx200_wdt.c
     10 *	drivers/hwmon/it87.c
     11 *	IT8712F EC-LPC I/O Preliminary Specification 0.8.2
     12 *	IT8712F EC-LPC I/O Preliminary Specification 0.9.3
     13 *
     14 *	The author(s) of this software shall not be held liable for damages
     15 *	of any nature resulting due to the use of this software. This
     16 *	software is provided AS-IS with no warranties.
     17 */
     18
     19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
     20
     21#include <linux/module.h>
     22#include <linux/moduleparam.h>
     23#include <linux/init.h>
     24#include <linux/miscdevice.h>
     25#include <linux/watchdog.h>
     26#include <linux/notifier.h>
     27#include <linux/reboot.h>
     28#include <linux/fs.h>
     29#include <linux/spinlock.h>
     30#include <linux/uaccess.h>
     31#include <linux/io.h>
     32#include <linux/ioport.h>
     33
     34#define NAME "it8712f_wdt"
     35
     36MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");
     37MODULE_DESCRIPTION("IT8712F Watchdog Driver");
     38MODULE_LICENSE("GPL");
     39
     40static int max_units = 255;
     41static int margin = 60;		/* in seconds */
     42module_param(margin, int, 0);
     43MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
     44
     45static bool nowayout = WATCHDOG_NOWAYOUT;
     46module_param(nowayout, bool, 0);
     47MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
     48
     49static unsigned long wdt_open;
     50static unsigned expect_close;
     51static unsigned char revision;
     52
     53/* Dog Food address - We use the game port address */
     54static unsigned short address;
     55
     56#define	REG		0x2e	/* The register to read/write */
     57#define	VAL		0x2f	/* The value to read/write */
     58
     59#define	LDN		0x07	/* Register: Logical device select */
     60#define	DEVID		0x20	/* Register: Device ID */
     61#define	DEVREV		0x22	/* Register: Device Revision */
     62#define ACT_REG		0x30	/* LDN Register: Activation */
     63#define BASE_REG	0x60	/* LDN Register: Base address */
     64
     65#define IT8712F_DEVID	0x8712
     66
     67#define LDN_GPIO	0x07	/* GPIO and Watch Dog Timer */
     68#define LDN_GAME	0x09	/* Game Port */
     69
     70#define WDT_CONTROL	0x71	/* WDT Register: Control */
     71#define WDT_CONFIG	0x72	/* WDT Register: Configuration */
     72#define WDT_TIMEOUT	0x73	/* WDT Register: Timeout Value */
     73
     74#define WDT_RESET_GAME	0x10	/* Reset timer on read or write to game port */
     75#define WDT_RESET_KBD	0x20	/* Reset timer on keyboard interrupt */
     76#define WDT_RESET_MOUSE	0x40	/* Reset timer on mouse interrupt */
     77#define WDT_RESET_CIR	0x80	/* Reset timer on consumer IR interrupt */
     78
     79#define WDT_UNIT_SEC	0x80	/* If 0 in MINUTES */
     80
     81#define WDT_OUT_PWROK	0x10	/* Pulse PWROK on timeout */
     82#define WDT_OUT_KRST	0x40	/* Pulse reset on timeout */
     83
     84static int wdt_control_reg = WDT_RESET_GAME;
     85module_param(wdt_control_reg, int, 0);
     86MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control "
     87		"register. The default WDT_RESET_GAME resets the timer on "
     88		"game port reads that this driver generates. You can also "
     89		"use KBD, MOUSE or CIR if you have some external way to "
     90		"generate those interrupts.");
     91
     92static int superio_inb(int reg)
     93{
     94	outb(reg, REG);
     95	return inb(VAL);
     96}
     97
     98static void superio_outb(int val, int reg)
     99{
    100	outb(reg, REG);
    101	outb(val, VAL);
    102}
    103
    104static int superio_inw(int reg)
    105{
    106	int val;
    107	outb(reg++, REG);
    108	val = inb(VAL) << 8;
    109	outb(reg, REG);
    110	val |= inb(VAL);
    111	return val;
    112}
    113
    114static inline void superio_select(int ldn)
    115{
    116	outb(LDN, REG);
    117	outb(ldn, VAL);
    118}
    119
    120static inline int superio_enter(void)
    121{
    122	/*
    123	 * Try to reserve REG and REG + 1 for exclusive access.
    124	 */
    125	if (!request_muxed_region(REG, 2, NAME))
    126		return -EBUSY;
    127
    128	outb(0x87, REG);
    129	outb(0x01, REG);
    130	outb(0x55, REG);
    131	outb(0x55, REG);
    132	return 0;
    133}
    134
    135static inline void superio_exit(void)
    136{
    137	outb(0x02, REG);
    138	outb(0x02, VAL);
    139	release_region(REG, 2);
    140}
    141
    142static inline void it8712f_wdt_ping(void)
    143{
    144	if (wdt_control_reg & WDT_RESET_GAME)
    145		inb(address);
    146}
    147
    148static void it8712f_wdt_update_margin(void)
    149{
    150	int config = WDT_OUT_KRST | WDT_OUT_PWROK;
    151	int units = margin;
    152
    153	/* Switch to minutes precision if the configured margin
    154	 * value does not fit within the register width.
    155	 */
    156	if (units <= max_units) {
    157		config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */
    158		pr_info("timer margin %d seconds\n", units);
    159	} else {
    160		units /= 60;
    161		pr_info("timer margin %d minutes\n", units);
    162	}
    163	superio_outb(config, WDT_CONFIG);
    164
    165	if (revision >= 0x08)
    166		superio_outb(units >> 8, WDT_TIMEOUT + 1);
    167	superio_outb(units, WDT_TIMEOUT);
    168}
    169
    170static int it8712f_wdt_get_status(void)
    171{
    172	if (superio_inb(WDT_CONTROL) & 0x01)
    173		return WDIOF_CARDRESET;
    174	else
    175		return 0;
    176}
    177
    178static int it8712f_wdt_enable(void)
    179{
    180	int ret = superio_enter();
    181	if (ret)
    182		return ret;
    183
    184	pr_debug("enabling watchdog timer\n");
    185	superio_select(LDN_GPIO);
    186
    187	superio_outb(wdt_control_reg, WDT_CONTROL);
    188
    189	it8712f_wdt_update_margin();
    190
    191	superio_exit();
    192
    193	it8712f_wdt_ping();
    194
    195	return 0;
    196}
    197
    198static int it8712f_wdt_disable(void)
    199{
    200	int ret = superio_enter();
    201	if (ret)
    202		return ret;
    203
    204	pr_debug("disabling watchdog timer\n");
    205	superio_select(LDN_GPIO);
    206
    207	superio_outb(0, WDT_CONFIG);
    208	superio_outb(0, WDT_CONTROL);
    209	if (revision >= 0x08)
    210		superio_outb(0, WDT_TIMEOUT + 1);
    211	superio_outb(0, WDT_TIMEOUT);
    212
    213	superio_exit();
    214	return 0;
    215}
    216
    217static int it8712f_wdt_notify(struct notifier_block *this,
    218		    unsigned long code, void *unused)
    219{
    220	if (code == SYS_HALT || code == SYS_POWER_OFF)
    221		if (!nowayout)
    222			it8712f_wdt_disable();
    223
    224	return NOTIFY_DONE;
    225}
    226
    227static struct notifier_block it8712f_wdt_notifier = {
    228	.notifier_call = it8712f_wdt_notify,
    229};
    230
    231static ssize_t it8712f_wdt_write(struct file *file, const char __user *data,
    232					size_t len, loff_t *ppos)
    233{
    234	/* check for a magic close character */
    235	if (len) {
    236		size_t i;
    237
    238		it8712f_wdt_ping();
    239
    240		expect_close = 0;
    241		for (i = 0; i < len; ++i) {
    242			char c;
    243			if (get_user(c, data + i))
    244				return -EFAULT;
    245			if (c == 'V')
    246				expect_close = 42;
    247		}
    248	}
    249
    250	return len;
    251}
    252
    253static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd,
    254							unsigned long arg)
    255{
    256	void __user *argp = (void __user *)arg;
    257	int __user *p = argp;
    258	static const struct watchdog_info ident = {
    259		.identity = "IT8712F Watchdog",
    260		.firmware_version = 1,
    261		.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
    262						WDIOF_MAGICCLOSE,
    263	};
    264	int value;
    265	int ret;
    266
    267	switch (cmd) {
    268	case WDIOC_GETSUPPORT:
    269		if (copy_to_user(argp, &ident, sizeof(ident)))
    270			return -EFAULT;
    271		return 0;
    272	case WDIOC_GETSTATUS:
    273		ret = superio_enter();
    274		if (ret)
    275			return ret;
    276		superio_select(LDN_GPIO);
    277
    278		value = it8712f_wdt_get_status();
    279
    280		superio_exit();
    281
    282		return put_user(value, p);
    283	case WDIOC_GETBOOTSTATUS:
    284		return put_user(0, p);
    285	case WDIOC_KEEPALIVE:
    286		it8712f_wdt_ping();
    287		return 0;
    288	case WDIOC_SETTIMEOUT:
    289		if (get_user(value, p))
    290			return -EFAULT;
    291		if (value < 1)
    292			return -EINVAL;
    293		if (value > (max_units * 60))
    294			return -EINVAL;
    295		margin = value;
    296		ret = superio_enter();
    297		if (ret)
    298			return ret;
    299		superio_select(LDN_GPIO);
    300
    301		it8712f_wdt_update_margin();
    302
    303		superio_exit();
    304		it8712f_wdt_ping();
    305		fallthrough;
    306	case WDIOC_GETTIMEOUT:
    307		if (put_user(margin, p))
    308			return -EFAULT;
    309		return 0;
    310	default:
    311		return -ENOTTY;
    312	}
    313}
    314
    315static int it8712f_wdt_open(struct inode *inode, struct file *file)
    316{
    317	int ret;
    318	/* only allow one at a time */
    319	if (test_and_set_bit(0, &wdt_open))
    320		return -EBUSY;
    321
    322	ret = it8712f_wdt_enable();
    323	if (ret)
    324		return ret;
    325	return stream_open(inode, file);
    326}
    327
    328static int it8712f_wdt_release(struct inode *inode, struct file *file)
    329{
    330	if (expect_close != 42) {
    331		pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
    332	} else if (!nowayout) {
    333		if (it8712f_wdt_disable())
    334			pr_warn("Watchdog disable failed\n");
    335	}
    336	expect_close = 0;
    337	clear_bit(0, &wdt_open);
    338
    339	return 0;
    340}
    341
    342static const struct file_operations it8712f_wdt_fops = {
    343	.owner = THIS_MODULE,
    344	.llseek = no_llseek,
    345	.write = it8712f_wdt_write,
    346	.unlocked_ioctl = it8712f_wdt_ioctl,
    347	.compat_ioctl = compat_ptr_ioctl,
    348	.open = it8712f_wdt_open,
    349	.release = it8712f_wdt_release,
    350};
    351
    352static struct miscdevice it8712f_wdt_miscdev = {
    353	.minor = WATCHDOG_MINOR,
    354	.name = "watchdog",
    355	.fops = &it8712f_wdt_fops,
    356};
    357
    358static int __init it8712f_wdt_find(unsigned short *address)
    359{
    360	int err = -ENODEV;
    361	int chip_type;
    362	int ret = superio_enter();
    363	if (ret)
    364		return ret;
    365
    366	chip_type = superio_inw(DEVID);
    367	if (chip_type != IT8712F_DEVID)
    368		goto exit;
    369
    370	superio_select(LDN_GAME);
    371	superio_outb(1, ACT_REG);
    372	if (!(superio_inb(ACT_REG) & 0x01)) {
    373		pr_err("Device not activated, skipping\n");
    374		goto exit;
    375	}
    376
    377	*address = superio_inw(BASE_REG);
    378	if (*address == 0) {
    379		pr_err("Base address not set, skipping\n");
    380		goto exit;
    381	}
    382
    383	err = 0;
    384	revision = superio_inb(DEVREV) & 0x0f;
    385
    386	/* Later revisions have 16-bit values per datasheet 0.9.1 */
    387	if (revision >= 0x08)
    388		max_units = 65535;
    389
    390	if (margin > (max_units * 60))
    391		margin = (max_units * 60);
    392
    393	pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n",
    394		chip_type, revision, *address);
    395
    396exit:
    397	superio_exit();
    398	return err;
    399}
    400
    401static int __init it8712f_wdt_init(void)
    402{
    403	int err = 0;
    404
    405	if (it8712f_wdt_find(&address))
    406		return -ENODEV;
    407
    408	if (!request_region(address, 1, "IT8712F Watchdog")) {
    409		pr_warn("watchdog I/O region busy\n");
    410		return -EBUSY;
    411	}
    412
    413	err = it8712f_wdt_disable();
    414	if (err) {
    415		pr_err("unable to disable watchdog timer\n");
    416		goto out;
    417	}
    418
    419	err = register_reboot_notifier(&it8712f_wdt_notifier);
    420	if (err) {
    421		pr_err("unable to register reboot notifier\n");
    422		goto out;
    423	}
    424
    425	err = misc_register(&it8712f_wdt_miscdev);
    426	if (err) {
    427		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
    428		       WATCHDOG_MINOR, err);
    429		goto reboot_out;
    430	}
    431
    432	return 0;
    433
    434
    435reboot_out:
    436	unregister_reboot_notifier(&it8712f_wdt_notifier);
    437out:
    438	release_region(address, 1);
    439	return err;
    440}
    441
    442static void __exit it8712f_wdt_exit(void)
    443{
    444	misc_deregister(&it8712f_wdt_miscdev);
    445	unregister_reboot_notifier(&it8712f_wdt_notifier);
    446	release_region(address, 1);
    447}
    448
    449module_init(it8712f_wdt_init);
    450module_exit(it8712f_wdt_exit);