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

gpio_wdt.c (4507B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * Driver for watchdog device controlled through GPIO-line
      4 *
      5 * Author: 2013, Alexander Shiyan <shc_work@mail.ru>
      6 */
      7
      8#include <linux/err.h>
      9#include <linux/delay.h>
     10#include <linux/module.h>
     11#include <linux/gpio/consumer.h>
     12#include <linux/of.h>
     13#include <linux/platform_device.h>
     14#include <linux/watchdog.h>
     15
     16static bool nowayout = WATCHDOG_NOWAYOUT;
     17module_param(nowayout, bool, 0);
     18MODULE_PARM_DESC(nowayout,
     19		"Watchdog cannot be stopped once started (default="
     20				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
     21
     22#define SOFT_TIMEOUT_MIN	1
     23#define SOFT_TIMEOUT_DEF	60
     24
     25enum {
     26	HW_ALGO_TOGGLE,
     27	HW_ALGO_LEVEL,
     28};
     29
     30struct gpio_wdt_priv {
     31	struct gpio_desc	*gpiod;
     32	bool			state;
     33	bool			always_running;
     34	unsigned int		hw_algo;
     35	struct watchdog_device	wdd;
     36};
     37
     38static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
     39{
     40	/* Eternal ping */
     41	gpiod_set_value_cansleep(priv->gpiod, 1);
     42
     43	/* Put GPIO back to tristate */
     44	if (priv->hw_algo == HW_ALGO_TOGGLE)
     45		gpiod_direction_input(priv->gpiod);
     46}
     47
     48static int gpio_wdt_ping(struct watchdog_device *wdd)
     49{
     50	struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
     51
     52	switch (priv->hw_algo) {
     53	case HW_ALGO_TOGGLE:
     54		/* Toggle output pin */
     55		priv->state = !priv->state;
     56		gpiod_set_value_cansleep(priv->gpiod, priv->state);
     57		break;
     58	case HW_ALGO_LEVEL:
     59		/* Pulse */
     60		gpiod_set_value_cansleep(priv->gpiod, 1);
     61		udelay(1);
     62		gpiod_set_value_cansleep(priv->gpiod, 0);
     63		break;
     64	}
     65	return 0;
     66}
     67
     68static int gpio_wdt_start(struct watchdog_device *wdd)
     69{
     70	struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
     71
     72	priv->state = 0;
     73	gpiod_direction_output(priv->gpiod, priv->state);
     74
     75	set_bit(WDOG_HW_RUNNING, &wdd->status);
     76
     77	return gpio_wdt_ping(wdd);
     78}
     79
     80static int gpio_wdt_stop(struct watchdog_device *wdd)
     81{
     82	struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
     83
     84	if (!priv->always_running) {
     85		gpio_wdt_disable(priv);
     86	} else {
     87		set_bit(WDOG_HW_RUNNING, &wdd->status);
     88	}
     89
     90	return 0;
     91}
     92
     93static const struct watchdog_info gpio_wdt_ident = {
     94	.options	= WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
     95			  WDIOF_SETTIMEOUT,
     96	.identity	= "GPIO Watchdog",
     97};
     98
     99static const struct watchdog_ops gpio_wdt_ops = {
    100	.owner		= THIS_MODULE,
    101	.start		= gpio_wdt_start,
    102	.stop		= gpio_wdt_stop,
    103	.ping		= gpio_wdt_ping,
    104};
    105
    106static int gpio_wdt_probe(struct platform_device *pdev)
    107{
    108	struct device *dev = &pdev->dev;
    109	struct device_node *np = dev->of_node;
    110	struct gpio_wdt_priv *priv;
    111	enum gpiod_flags gflags;
    112	unsigned int hw_margin;
    113	const char *algo;
    114	int ret;
    115
    116	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    117	if (!priv)
    118		return -ENOMEM;
    119
    120	platform_set_drvdata(pdev, priv);
    121
    122	ret = of_property_read_string(np, "hw_algo", &algo);
    123	if (ret)
    124		return ret;
    125	if (!strcmp(algo, "toggle")) {
    126		priv->hw_algo = HW_ALGO_TOGGLE;
    127		gflags = GPIOD_IN;
    128	} else if (!strcmp(algo, "level")) {
    129		priv->hw_algo = HW_ALGO_LEVEL;
    130		gflags = GPIOD_OUT_LOW;
    131	} else {
    132		return -EINVAL;
    133	}
    134
    135	priv->gpiod = devm_gpiod_get(dev, NULL, gflags);
    136	if (IS_ERR(priv->gpiod))
    137		return PTR_ERR(priv->gpiod);
    138
    139	ret = of_property_read_u32(np,
    140				   "hw_margin_ms", &hw_margin);
    141	if (ret)
    142		return ret;
    143	/* Disallow values lower than 2 and higher than 65535 ms */
    144	if (hw_margin < 2 || hw_margin > 65535)
    145		return -EINVAL;
    146
    147	priv->always_running = of_property_read_bool(np,
    148						     "always-running");
    149
    150	watchdog_set_drvdata(&priv->wdd, priv);
    151
    152	priv->wdd.info		= &gpio_wdt_ident;
    153	priv->wdd.ops		= &gpio_wdt_ops;
    154	priv->wdd.min_timeout	= SOFT_TIMEOUT_MIN;
    155	priv->wdd.max_hw_heartbeat_ms = hw_margin;
    156	priv->wdd.parent	= dev;
    157	priv->wdd.timeout	= SOFT_TIMEOUT_DEF;
    158
    159	watchdog_init_timeout(&priv->wdd, 0, dev);
    160	watchdog_set_nowayout(&priv->wdd, nowayout);
    161
    162	watchdog_stop_on_reboot(&priv->wdd);
    163
    164	if (priv->always_running)
    165		gpio_wdt_start(&priv->wdd);
    166
    167	return devm_watchdog_register_device(dev, &priv->wdd);
    168}
    169
    170static const struct of_device_id gpio_wdt_dt_ids[] = {
    171	{ .compatible = "linux,wdt-gpio", },
    172	{ }
    173};
    174MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
    175
    176static struct platform_driver gpio_wdt_driver = {
    177	.driver	= {
    178		.name		= "gpio-wdt",
    179		.of_match_table	= gpio_wdt_dt_ids,
    180	},
    181	.probe	= gpio_wdt_probe,
    182};
    183
    184#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
    185static int __init gpio_wdt_init(void)
    186{
    187	return platform_driver_register(&gpio_wdt_driver);
    188}
    189arch_initcall(gpio_wdt_init);
    190#else
    191module_platform_driver(gpio_wdt_driver);
    192#endif
    193
    194MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
    195MODULE_DESCRIPTION("GPIO Watchdog");
    196MODULE_LICENSE("GPL");