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

ar7_wdt.c (7098B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * drivers/watchdog/ar7_wdt.c
      4 *
      5 * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
      6 * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
      7 *
      8 * Some code taken from:
      9 * National Semiconductor SCx200 Watchdog support
     10 * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
     11 *
     12 */
     13
     14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
     15
     16#include <linux/module.h>
     17#include <linux/moduleparam.h>
     18#include <linux/errno.h>
     19#include <linux/miscdevice.h>
     20#include <linux/platform_device.h>
     21#include <linux/watchdog.h>
     22#include <linux/fs.h>
     23#include <linux/ioport.h>
     24#include <linux/io.h>
     25#include <linux/uaccess.h>
     26#include <linux/clk.h>
     27
     28#include <asm/addrspace.h>
     29#include <asm/mach-ar7/ar7.h>
     30
     31#define LONGNAME "TI AR7 Watchdog Timer"
     32
     33MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
     34MODULE_DESCRIPTION(LONGNAME);
     35MODULE_LICENSE("GPL");
     36
     37static int margin = 60;
     38module_param(margin, int, 0);
     39MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
     40
     41static bool nowayout = WATCHDOG_NOWAYOUT;
     42module_param(nowayout, bool, 0);
     43MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
     44
     45#define READ_REG(x) readl((void __iomem *)&(x))
     46#define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
     47
     48struct ar7_wdt {
     49	u32 kick_lock;
     50	u32 kick;
     51	u32 change_lock;
     52	u32 change;
     53	u32 disable_lock;
     54	u32 disable;
     55	u32 prescale_lock;
     56	u32 prescale;
     57};
     58
     59static unsigned long wdt_is_open;
     60static unsigned expect_close;
     61static DEFINE_SPINLOCK(wdt_lock);
     62
     63/* XXX currently fixed, allows max margin ~68.72 secs */
     64#define prescale_value 0xffff
     65
     66/* Pointer to the remapped WDT IO space */
     67static struct ar7_wdt *ar7_wdt;
     68
     69static struct clk *vbus_clk;
     70
     71static void ar7_wdt_kick(u32 value)
     72{
     73	WRITE_REG(ar7_wdt->kick_lock, 0x5555);
     74	if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
     75		WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
     76		if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
     77			WRITE_REG(ar7_wdt->kick, value);
     78			return;
     79		}
     80	}
     81	pr_err("failed to unlock WDT kick reg\n");
     82}
     83
     84static void ar7_wdt_prescale(u32 value)
     85{
     86	WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
     87	if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
     88		WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
     89		if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
     90			WRITE_REG(ar7_wdt->prescale, value);
     91			return;
     92		}
     93	}
     94	pr_err("failed to unlock WDT prescale reg\n");
     95}
     96
     97static void ar7_wdt_change(u32 value)
     98{
     99	WRITE_REG(ar7_wdt->change_lock, 0x6666);
    100	if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
    101		WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
    102		if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
    103			WRITE_REG(ar7_wdt->change, value);
    104			return;
    105		}
    106	}
    107	pr_err("failed to unlock WDT change reg\n");
    108}
    109
    110static void ar7_wdt_disable(u32 value)
    111{
    112	WRITE_REG(ar7_wdt->disable_lock, 0x7777);
    113	if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
    114		WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
    115		if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
    116			WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
    117			if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
    118				WRITE_REG(ar7_wdt->disable, value);
    119				return;
    120			}
    121		}
    122	}
    123	pr_err("failed to unlock WDT disable reg\n");
    124}
    125
    126static void ar7_wdt_update_margin(int new_margin)
    127{
    128	u32 change;
    129	u32 vbus_rate;
    130
    131	vbus_rate = clk_get_rate(vbus_clk);
    132	change = new_margin * (vbus_rate / prescale_value);
    133	if (change < 1)
    134		change = 1;
    135	if (change > 0xffff)
    136		change = 0xffff;
    137	ar7_wdt_change(change);
    138	margin = change * prescale_value / vbus_rate;
    139	pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
    140		margin, prescale_value, change, vbus_rate);
    141}
    142
    143static void ar7_wdt_enable_wdt(void)
    144{
    145	pr_debug("enabling watchdog timer\n");
    146	ar7_wdt_disable(1);
    147	ar7_wdt_kick(1);
    148}
    149
    150static void ar7_wdt_disable_wdt(void)
    151{
    152	pr_debug("disabling watchdog timer\n");
    153	ar7_wdt_disable(0);
    154}
    155
    156static int ar7_wdt_open(struct inode *inode, struct file *file)
    157{
    158	/* only allow one at a time */
    159	if (test_and_set_bit(0, &wdt_is_open))
    160		return -EBUSY;
    161	ar7_wdt_enable_wdt();
    162	expect_close = 0;
    163
    164	return stream_open(inode, file);
    165}
    166
    167static int ar7_wdt_release(struct inode *inode, struct file *file)
    168{
    169	if (!expect_close)
    170		pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
    171	else if (!nowayout)
    172		ar7_wdt_disable_wdt();
    173	clear_bit(0, &wdt_is_open);
    174	return 0;
    175}
    176
    177static ssize_t ar7_wdt_write(struct file *file, const char *data,
    178			     size_t len, loff_t *ppos)
    179{
    180	/* check for a magic close character */
    181	if (len) {
    182		size_t i;
    183
    184		spin_lock(&wdt_lock);
    185		ar7_wdt_kick(1);
    186		spin_unlock(&wdt_lock);
    187
    188		expect_close = 0;
    189		for (i = 0; i < len; ++i) {
    190			char c;
    191			if (get_user(c, data + i))
    192				return -EFAULT;
    193			if (c == 'V')
    194				expect_close = 1;
    195		}
    196
    197	}
    198	return len;
    199}
    200
    201static long ar7_wdt_ioctl(struct file *file,
    202					unsigned int cmd, unsigned long arg)
    203{
    204	static const struct watchdog_info ident = {
    205		.identity = LONGNAME,
    206		.firmware_version = 1,
    207		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
    208						WDIOF_MAGICCLOSE),
    209	};
    210	int new_margin;
    211
    212	switch (cmd) {
    213	case WDIOC_GETSUPPORT:
    214		if (copy_to_user((struct watchdog_info *)arg, &ident,
    215				sizeof(ident)))
    216			return -EFAULT;
    217		return 0;
    218	case WDIOC_GETSTATUS:
    219	case WDIOC_GETBOOTSTATUS:
    220		if (put_user(0, (int *)arg))
    221			return -EFAULT;
    222		return 0;
    223	case WDIOC_KEEPALIVE:
    224		ar7_wdt_kick(1);
    225		return 0;
    226	case WDIOC_SETTIMEOUT:
    227		if (get_user(new_margin, (int *)arg))
    228			return -EFAULT;
    229		if (new_margin < 1)
    230			return -EINVAL;
    231
    232		spin_lock(&wdt_lock);
    233		ar7_wdt_update_margin(new_margin);
    234		ar7_wdt_kick(1);
    235		spin_unlock(&wdt_lock);
    236		fallthrough;
    237	case WDIOC_GETTIMEOUT:
    238		if (put_user(margin, (int *)arg))
    239			return -EFAULT;
    240		return 0;
    241	default:
    242		return -ENOTTY;
    243	}
    244}
    245
    246static const struct file_operations ar7_wdt_fops = {
    247	.owner		= THIS_MODULE,
    248	.write		= ar7_wdt_write,
    249	.unlocked_ioctl	= ar7_wdt_ioctl,
    250	.compat_ioctl	= compat_ptr_ioctl,
    251	.open		= ar7_wdt_open,
    252	.release	= ar7_wdt_release,
    253	.llseek		= no_llseek,
    254};
    255
    256static struct miscdevice ar7_wdt_miscdev = {
    257	.minor		= WATCHDOG_MINOR,
    258	.name		= "watchdog",
    259	.fops		= &ar7_wdt_fops,
    260};
    261
    262static int ar7_wdt_probe(struct platform_device *pdev)
    263{
    264	int rc;
    265
    266	ar7_wdt = devm_platform_ioremap_resource_byname(pdev, "regs");
    267	if (IS_ERR(ar7_wdt))
    268		return PTR_ERR(ar7_wdt);
    269
    270	vbus_clk = clk_get(NULL, "vbus");
    271	if (IS_ERR(vbus_clk)) {
    272		pr_err("could not get vbus clock\n");
    273		return PTR_ERR(vbus_clk);
    274	}
    275
    276	ar7_wdt_disable_wdt();
    277	ar7_wdt_prescale(prescale_value);
    278	ar7_wdt_update_margin(margin);
    279
    280	rc = misc_register(&ar7_wdt_miscdev);
    281	if (rc) {
    282		pr_err("unable to register misc device\n");
    283		goto out;
    284	}
    285	return 0;
    286
    287out:
    288	clk_put(vbus_clk);
    289	vbus_clk = NULL;
    290	return rc;
    291}
    292
    293static int ar7_wdt_remove(struct platform_device *pdev)
    294{
    295	misc_deregister(&ar7_wdt_miscdev);
    296	clk_put(vbus_clk);
    297	vbus_clk = NULL;
    298	return 0;
    299}
    300
    301static void ar7_wdt_shutdown(struct platform_device *pdev)
    302{
    303	if (!nowayout)
    304		ar7_wdt_disable_wdt();
    305}
    306
    307static struct platform_driver ar7_wdt_driver = {
    308	.probe = ar7_wdt_probe,
    309	.remove = ar7_wdt_remove,
    310	.shutdown = ar7_wdt_shutdown,
    311	.driver = {
    312		.name = "ar7_wdt",
    313	},
    314};
    315
    316module_platform_driver(ar7_wdt_driver);