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

ie6xx_wdt.c (7602B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 *      Intel Atom E6xx Watchdog driver
      4 *
      5 *      Copyright (C) 2011 Alexander Stein
      6 *                <alexander.stein@systec-electronic.com>
      7 */
      8
      9#include <linux/module.h>
     10#include <linux/moduleparam.h>
     11#include <linux/platform_device.h>
     12#include <linux/io.h>
     13#include <linux/kernel.h>
     14#include <linux/types.h>
     15#include <linux/watchdog.h>
     16#include <linux/seq_file.h>
     17#include <linux/debugfs.h>
     18#include <linux/uaccess.h>
     19#include <linux/spinlock.h>
     20
     21#define DRIVER_NAME "ie6xx_wdt"
     22
     23#define PV1	0x00
     24#define PV2	0x04
     25
     26#define RR0	0x0c
     27#define RR1	0x0d
     28#define WDT_RELOAD	0x01
     29#define WDT_TOUT	0x02
     30
     31#define WDTCR	0x10
     32#define WDT_PRE_SEL	0x04
     33#define WDT_RESET_SEL	0x08
     34#define WDT_RESET_EN	0x10
     35#define WDT_TOUT_EN	0x20
     36
     37#define DCR	0x14
     38
     39#define WDTLR	0x18
     40#define WDT_LOCK	0x01
     41#define WDT_ENABLE	0x02
     42#define WDT_TOUT_CNF	0x03
     43
     44#define MIN_TIME	1
     45#define MAX_TIME	(10 * 60) /* 10 minutes */
     46#define DEFAULT_TIME	60
     47
     48static unsigned int timeout = DEFAULT_TIME;
     49module_param(timeout, uint, 0);
     50MODULE_PARM_DESC(timeout,
     51		"Default Watchdog timer setting ("
     52		__MODULE_STRING(DEFAULT_TIME) "s)."
     53		"The range is from 1 to 600");
     54
     55static bool nowayout = WATCHDOG_NOWAYOUT;
     56module_param(nowayout, bool, 0);
     57MODULE_PARM_DESC(nowayout,
     58	"Watchdog cannot be stopped once started (default="
     59		__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     60
     61static u8 resetmode = 0x10;
     62module_param(resetmode, byte, 0);
     63MODULE_PARM_DESC(resetmode,
     64	"Resetmode bits: 0x08 warm reset (cold reset otherwise), "
     65	"0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
     66
     67static struct {
     68	unsigned short sch_wdtba;
     69	spinlock_t unlock_sequence;
     70#ifdef CONFIG_DEBUG_FS
     71	struct dentry *debugfs;
     72#endif
     73} ie6xx_wdt_data;
     74
     75/*
     76 * This is needed to write to preload and reload registers
     77 * struct ie6xx_wdt_data.unlock_sequence must be used
     78 * to prevent sequence interrupts
     79 */
     80static void ie6xx_wdt_unlock_registers(void)
     81{
     82	outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
     83	outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
     84}
     85
     86static int ie6xx_wdt_ping(struct watchdog_device *wdd)
     87{
     88	spin_lock(&ie6xx_wdt_data.unlock_sequence);
     89	ie6xx_wdt_unlock_registers();
     90	outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
     91	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
     92	return 0;
     93}
     94
     95static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
     96{
     97	u32 preload;
     98	u64 clock;
     99	u8 wdtcr;
    100
    101	/* Watchdog clock is PCI Clock (33MHz) */
    102	clock = 33000000;
    103	/* and the preload value is loaded into [34:15] of the down counter */
    104	preload = (t * clock) >> 15;
    105	/*
    106	 * Manual states preload must be one less.
    107	 * Does not wrap as t is at least 1
    108	 */
    109	preload -= 1;
    110
    111	spin_lock(&ie6xx_wdt_data.unlock_sequence);
    112
    113	/* Set ResetMode & Enable prescaler for range 10ms to 10 min */
    114	wdtcr = resetmode & 0x38;
    115	outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
    116
    117	ie6xx_wdt_unlock_registers();
    118	outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
    119
    120	ie6xx_wdt_unlock_registers();
    121	outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
    122
    123	ie6xx_wdt_unlock_registers();
    124	outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
    125
    126	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
    127
    128	wdd->timeout = t;
    129	return 0;
    130}
    131
    132static int ie6xx_wdt_start(struct watchdog_device *wdd)
    133{
    134	ie6xx_wdt_set_timeout(wdd, wdd->timeout);
    135
    136	/* Enable the watchdog timer */
    137	spin_lock(&ie6xx_wdt_data.unlock_sequence);
    138	outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
    139	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
    140
    141	return 0;
    142}
    143
    144static int ie6xx_wdt_stop(struct watchdog_device *wdd)
    145{
    146	if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
    147		return -1;
    148
    149	/* Disable the watchdog timer */
    150	spin_lock(&ie6xx_wdt_data.unlock_sequence);
    151	outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
    152	spin_unlock(&ie6xx_wdt_data.unlock_sequence);
    153
    154	return 0;
    155}
    156
    157static const struct watchdog_info ie6xx_wdt_info = {
    158	.identity =	"Intel Atom E6xx Watchdog",
    159	.options =	WDIOF_SETTIMEOUT |
    160			WDIOF_MAGICCLOSE |
    161			WDIOF_KEEPALIVEPING,
    162};
    163
    164static const struct watchdog_ops ie6xx_wdt_ops = {
    165	.owner =	THIS_MODULE,
    166	.start =	ie6xx_wdt_start,
    167	.stop =		ie6xx_wdt_stop,
    168	.ping =		ie6xx_wdt_ping,
    169	.set_timeout =	ie6xx_wdt_set_timeout,
    170};
    171
    172static struct watchdog_device ie6xx_wdt_dev = {
    173	.info =		&ie6xx_wdt_info,
    174	.ops =		&ie6xx_wdt_ops,
    175	.min_timeout =	MIN_TIME,
    176	.max_timeout =	MAX_TIME,
    177};
    178
    179#ifdef CONFIG_DEBUG_FS
    180
    181static int ie6xx_wdt_show(struct seq_file *s, void *unused)
    182{
    183	seq_printf(s, "PV1   = 0x%08x\n",
    184		inl(ie6xx_wdt_data.sch_wdtba + PV1));
    185	seq_printf(s, "PV2   = 0x%08x\n",
    186		inl(ie6xx_wdt_data.sch_wdtba + PV2));
    187	seq_printf(s, "RR    = 0x%08x\n",
    188		inw(ie6xx_wdt_data.sch_wdtba + RR0));
    189	seq_printf(s, "WDTCR = 0x%08x\n",
    190		inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
    191	seq_printf(s, "DCR   = 0x%08x\n",
    192		inl(ie6xx_wdt_data.sch_wdtba + DCR));
    193	seq_printf(s, "WDTLR = 0x%08x\n",
    194		inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
    195
    196	seq_printf(s, "\n");
    197	return 0;
    198}
    199
    200DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt);
    201
    202static void ie6xx_wdt_debugfs_init(void)
    203{
    204	/* /sys/kernel/debug/ie6xx_wdt */
    205	ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
    206		S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_fops);
    207}
    208
    209static void ie6xx_wdt_debugfs_exit(void)
    210{
    211	debugfs_remove(ie6xx_wdt_data.debugfs);
    212}
    213
    214#else
    215static void ie6xx_wdt_debugfs_init(void)
    216{
    217}
    218
    219static void ie6xx_wdt_debugfs_exit(void)
    220{
    221}
    222#endif
    223
    224static int ie6xx_wdt_probe(struct platform_device *pdev)
    225{
    226	struct resource *res;
    227	u8 wdtlr;
    228	int ret;
    229
    230	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
    231	if (!res)
    232		return -ENODEV;
    233
    234	if (!request_region(res->start, resource_size(res), pdev->name)) {
    235		dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
    236			(u64)res->start);
    237		return -EBUSY;
    238	}
    239
    240	ie6xx_wdt_data.sch_wdtba = res->start;
    241	dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
    242
    243	ie6xx_wdt_dev.timeout = timeout;
    244	watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
    245	ie6xx_wdt_dev.parent = &pdev->dev;
    246
    247	spin_lock_init(&ie6xx_wdt_data.unlock_sequence);
    248
    249	wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR);
    250	if (wdtlr & WDT_LOCK)
    251		dev_warn(&pdev->dev,
    252			"Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr);
    253
    254	ie6xx_wdt_debugfs_init();
    255
    256	ret = watchdog_register_device(&ie6xx_wdt_dev);
    257	if (ret)
    258		goto misc_register_error;
    259
    260	return 0;
    261
    262misc_register_error:
    263	ie6xx_wdt_debugfs_exit();
    264	release_region(res->start, resource_size(res));
    265	ie6xx_wdt_data.sch_wdtba = 0;
    266	return ret;
    267}
    268
    269static int ie6xx_wdt_remove(struct platform_device *pdev)
    270{
    271	struct resource *res;
    272
    273	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
    274	ie6xx_wdt_stop(NULL);
    275	watchdog_unregister_device(&ie6xx_wdt_dev);
    276	ie6xx_wdt_debugfs_exit();
    277	release_region(res->start, resource_size(res));
    278	ie6xx_wdt_data.sch_wdtba = 0;
    279
    280	return 0;
    281}
    282
    283static struct platform_driver ie6xx_wdt_driver = {
    284	.probe		= ie6xx_wdt_probe,
    285	.remove		= ie6xx_wdt_remove,
    286	.driver		= {
    287		.name	= DRIVER_NAME,
    288	},
    289};
    290
    291static int __init ie6xx_wdt_init(void)
    292{
    293	/* Check boot parameters to verify that their initial values */
    294	/* are in range. */
    295	if ((timeout < MIN_TIME) ||
    296	    (timeout > MAX_TIME)) {
    297		pr_err("Watchdog timer: value of timeout %d (dec) "
    298		  "is out of range from %d to %d (dec)\n",
    299		  timeout, MIN_TIME, MAX_TIME);
    300		return -EINVAL;
    301	}
    302
    303	return platform_driver_register(&ie6xx_wdt_driver);
    304}
    305
    306static void __exit ie6xx_wdt_exit(void)
    307{
    308	platform_driver_unregister(&ie6xx_wdt_driver);
    309}
    310
    311late_initcall(ie6xx_wdt_init);
    312module_exit(ie6xx_wdt_exit);
    313
    314MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
    315MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
    316MODULE_LICENSE("GPL");
    317MODULE_ALIAS("platform:" DRIVER_NAME);