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

rc32434_wdt.c (8115B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *  IDT Interprise 79RC32434 watchdog driver
      4 *
      5 *  Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
      6 *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
      7 *
      8 *  based on
      9 *  SoftDog 0.05:	A Software Watchdog Device
     10 *
     11 *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
     12 *					All Rights Reserved.
     13 */
     14
     15#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
     16
     17#include <linux/module.h>		/* For module specific items */
     18#include <linux/moduleparam.h>		/* For new moduleparam's */
     19#include <linux/types.h>		/* For standard types (like size_t) */
     20#include <linux/errno.h>		/* For the -ENODEV/... values */
     21#include <linux/kernel.h>		/* For printk/panic/... */
     22#include <linux/fs.h>			/* For file operations */
     23#include <linux/miscdevice.h>		/* For struct miscdevice */
     24#include <linux/watchdog.h>		/* For the watchdog specific items */
     25#include <linux/init.h>			/* For __init/__exit/... */
     26#include <linux/platform_device.h>	/* For platform_driver framework */
     27#include <linux/spinlock.h>		/* For spin_lock/spin_unlock/... */
     28#include <linux/uaccess.h>		/* For copy_to_user/put_user/... */
     29#include <linux/io.h>			/* For devm_ioremap */
     30
     31#include <asm/mach-rc32434/integ.h>	/* For the Watchdog registers */
     32
     33#define VERSION "1.0"
     34
     35static struct {
     36	unsigned long inuse;
     37	spinlock_t io_lock;
     38} rc32434_wdt_device;
     39
     40static struct integ __iomem *wdt_reg;
     41
     42static int expect_close;
     43
     44/* Board internal clock speed in Hz,
     45 * the watchdog timer ticks at. */
     46extern unsigned int idt_cpu_freq;
     47
     48/* translate wtcompare value to seconds and vice versa */
     49#define WTCOMP2SEC(x)	(x / idt_cpu_freq)
     50#define SEC2WTCOMP(x)	(x * idt_cpu_freq)
     51
     52/* Use a default timeout of 20s. This should be
     53 * safe for CPU clock speeds up to 400MHz, as
     54 * ((2 ^ 32) - 1) / (400MHz / 2) = 21s.  */
     55#define WATCHDOG_TIMEOUT 20
     56
     57static int timeout = WATCHDOG_TIMEOUT;
     58module_param(timeout, int, 0);
     59MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default="
     60		__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
     61
     62static bool nowayout = WATCHDOG_NOWAYOUT;
     63module_param(nowayout, bool, 0);
     64MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
     65	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     66
     67/* apply or and nand masks to data read from addr and write back */
     68#define SET_BITS(addr, or, nand) \
     69	writel((readl(&addr) | or) & ~nand, &addr)
     70
     71static int rc32434_wdt_set(int new_timeout)
     72{
     73	int max_to = WTCOMP2SEC((u32)-1);
     74
     75	if (new_timeout < 0 || new_timeout > max_to) {
     76		pr_err("timeout value must be between 0 and %d\n", max_to);
     77		return -EINVAL;
     78	}
     79	timeout = new_timeout;
     80	spin_lock(&rc32434_wdt_device.io_lock);
     81	writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);
     82	spin_unlock(&rc32434_wdt_device.io_lock);
     83
     84	return 0;
     85}
     86
     87static void rc32434_wdt_start(void)
     88{
     89	u32 or, nand;
     90
     91	spin_lock(&rc32434_wdt_device.io_lock);
     92
     93	/* zero the counter before enabling */
     94	writel(0, &wdt_reg->wtcount);
     95
     96	/* don't generate a non-maskable interrupt,
     97	 * do a warm reset instead */
     98	nand = 1 << RC32434_ERR_WNE;
     99	or = 1 << RC32434_ERR_WRE;
    100
    101	/* reset the ERRCS timeout bit in case it's set */
    102	nand |= 1 << RC32434_ERR_WTO;
    103
    104	SET_BITS(wdt_reg->errcs, or, nand);
    105
    106	/* set the timeout (either default or based on module param) */
    107	rc32434_wdt_set(timeout);
    108
    109	/* reset WTC timeout bit and enable WDT */
    110	nand = 1 << RC32434_WTC_TO;
    111	or = 1 << RC32434_WTC_EN;
    112
    113	SET_BITS(wdt_reg->wtc, or, nand);
    114
    115	spin_unlock(&rc32434_wdt_device.io_lock);
    116	pr_info("Started watchdog timer\n");
    117}
    118
    119static void rc32434_wdt_stop(void)
    120{
    121	spin_lock(&rc32434_wdt_device.io_lock);
    122
    123	/* Disable WDT */
    124	SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);
    125
    126	spin_unlock(&rc32434_wdt_device.io_lock);
    127	pr_info("Stopped watchdog timer\n");
    128}
    129
    130static void rc32434_wdt_ping(void)
    131{
    132	spin_lock(&rc32434_wdt_device.io_lock);
    133	writel(0, &wdt_reg->wtcount);
    134	spin_unlock(&rc32434_wdt_device.io_lock);
    135}
    136
    137static int rc32434_wdt_open(struct inode *inode, struct file *file)
    138{
    139	if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
    140		return -EBUSY;
    141
    142	if (nowayout)
    143		__module_get(THIS_MODULE);
    144
    145	rc32434_wdt_start();
    146	rc32434_wdt_ping();
    147
    148	return stream_open(inode, file);
    149}
    150
    151static int rc32434_wdt_release(struct inode *inode, struct file *file)
    152{
    153	if (expect_close == 42) {
    154		rc32434_wdt_stop();
    155		module_put(THIS_MODULE);
    156	} else {
    157		pr_crit("device closed unexpectedly. WDT will not stop!\n");
    158		rc32434_wdt_ping();
    159	}
    160	clear_bit(0, &rc32434_wdt_device.inuse);
    161	return 0;
    162}
    163
    164static ssize_t rc32434_wdt_write(struct file *file, const char *data,
    165				size_t len, loff_t *ppos)
    166{
    167	if (len) {
    168		if (!nowayout) {
    169			size_t i;
    170
    171			/* In case it was set long ago */
    172			expect_close = 0;
    173
    174			for (i = 0; i != len; i++) {
    175				char c;
    176				if (get_user(c, data + i))
    177					return -EFAULT;
    178				if (c == 'V')
    179					expect_close = 42;
    180			}
    181		}
    182		rc32434_wdt_ping();
    183		return len;
    184	}
    185	return 0;
    186}
    187
    188static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
    189				unsigned long arg)
    190{
    191	void __user *argp = (void __user *)arg;
    192	int new_timeout;
    193	unsigned int value;
    194	static const struct watchdog_info ident = {
    195		.options =		WDIOF_SETTIMEOUT |
    196					WDIOF_KEEPALIVEPING |
    197					WDIOF_MAGICCLOSE,
    198		.identity =		"RC32434_WDT Watchdog",
    199	};
    200	switch (cmd) {
    201	case WDIOC_GETSUPPORT:
    202		if (copy_to_user(argp, &ident, sizeof(ident)))
    203			return -EFAULT;
    204		break;
    205	case WDIOC_GETSTATUS:
    206	case WDIOC_GETBOOTSTATUS:
    207		value = 0;
    208		if (copy_to_user(argp, &value, sizeof(int)))
    209			return -EFAULT;
    210		break;
    211	case WDIOC_SETOPTIONS:
    212		if (copy_from_user(&value, argp, sizeof(int)))
    213			return -EFAULT;
    214		switch (value) {
    215		case WDIOS_ENABLECARD:
    216			rc32434_wdt_start();
    217			break;
    218		case WDIOS_DISABLECARD:
    219			rc32434_wdt_stop();
    220			break;
    221		default:
    222			return -EINVAL;
    223		}
    224		break;
    225	case WDIOC_KEEPALIVE:
    226		rc32434_wdt_ping();
    227		break;
    228	case WDIOC_SETTIMEOUT:
    229		if (copy_from_user(&new_timeout, argp, sizeof(int)))
    230			return -EFAULT;
    231		if (rc32434_wdt_set(new_timeout))
    232			return -EINVAL;
    233		fallthrough;
    234	case WDIOC_GETTIMEOUT:
    235		return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;
    236	default:
    237		return -ENOTTY;
    238	}
    239
    240	return 0;
    241}
    242
    243static const struct file_operations rc32434_wdt_fops = {
    244	.owner		= THIS_MODULE,
    245	.llseek		= no_llseek,
    246	.write		= rc32434_wdt_write,
    247	.unlocked_ioctl	= rc32434_wdt_ioctl,
    248	.compat_ioctl	= compat_ptr_ioctl,
    249	.open		= rc32434_wdt_open,
    250	.release	= rc32434_wdt_release,
    251};
    252
    253static struct miscdevice rc32434_wdt_miscdev = {
    254	.minor	= WATCHDOG_MINOR,
    255	.name	= "watchdog",
    256	.fops	= &rc32434_wdt_fops,
    257};
    258
    259static int rc32434_wdt_probe(struct platform_device *pdev)
    260{
    261	int ret;
    262	struct resource *r;
    263
    264	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");
    265	if (!r) {
    266		pr_err("failed to retrieve resources\n");
    267		return -ENODEV;
    268	}
    269
    270	wdt_reg = devm_ioremap(&pdev->dev, r->start, resource_size(r));
    271	if (!wdt_reg) {
    272		pr_err("failed to remap I/O resources\n");
    273		return -ENXIO;
    274	}
    275
    276	spin_lock_init(&rc32434_wdt_device.io_lock);
    277
    278	/* Make sure the watchdog is not running */
    279	rc32434_wdt_stop();
    280
    281	/* Check that the heartbeat value is within it's range;
    282	 * if not reset to the default */
    283	if (rc32434_wdt_set(timeout)) {
    284		rc32434_wdt_set(WATCHDOG_TIMEOUT);
    285		pr_info("timeout value must be between 0 and %d\n",
    286			WTCOMP2SEC((u32)-1));
    287	}
    288
    289	ret = misc_register(&rc32434_wdt_miscdev);
    290	if (ret < 0) {
    291		pr_err("failed to register watchdog device\n");
    292		return ret;
    293	}
    294
    295	pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n",
    296		timeout);
    297
    298	return 0;
    299}
    300
    301static int rc32434_wdt_remove(struct platform_device *pdev)
    302{
    303	misc_deregister(&rc32434_wdt_miscdev);
    304	return 0;
    305}
    306
    307static void rc32434_wdt_shutdown(struct platform_device *pdev)
    308{
    309	rc32434_wdt_stop();
    310}
    311
    312static struct platform_driver rc32434_wdt_driver = {
    313	.probe		= rc32434_wdt_probe,
    314	.remove		= rc32434_wdt_remove,
    315	.shutdown	= rc32434_wdt_shutdown,
    316	.driver		= {
    317			.name = "rc32434_wdt",
    318	}
    319};
    320
    321module_platform_driver(rc32434_wdt_driver);
    322
    323MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
    324		"Florian Fainelli <florian@openwrt.org>");
    325MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
    326MODULE_LICENSE("GPL");