mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
4.3 KiB
187 lines
4.3 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* sun4v watchdog timer |
|
* (c) Copyright 2016 Oracle Corporation |
|
* |
|
* Implement a simple watchdog driver using the built-in sun4v hypervisor |
|
* watchdog support. If time expires, the hypervisor stops or bounces |
|
* the guest domain. |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/errno.h> |
|
#include <linux/init.h> |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/moduleparam.h> |
|
#include <linux/watchdog.h> |
|
#include <asm/hypervisor.h> |
|
#include <asm/mdesc.h> |
|
|
|
#define WDT_TIMEOUT 60 |
|
#define WDT_MAX_TIMEOUT 31536000 |
|
#define WDT_MIN_TIMEOUT 1 |
|
#define WDT_DEFAULT_RESOLUTION_MS 1000 /* 1 second */ |
|
|
|
static unsigned int timeout; |
|
module_param(timeout, uint, 0); |
|
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" |
|
__MODULE_STRING(WDT_TIMEOUT) ")"); |
|
|
|
static bool nowayout = WATCHDOG_NOWAYOUT; |
|
module_param(nowayout, bool, S_IRUGO); |
|
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
|
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
|
|
|
static int sun4v_wdt_stop(struct watchdog_device *wdd) |
|
{ |
|
sun4v_mach_set_watchdog(0, NULL); |
|
|
|
return 0; |
|
} |
|
|
|
static int sun4v_wdt_ping(struct watchdog_device *wdd) |
|
{ |
|
int hverr; |
|
|
|
/* |
|
* HV watchdog timer will round up the timeout |
|
* passed in to the nearest multiple of the |
|
* watchdog resolution in milliseconds. |
|
*/ |
|
hverr = sun4v_mach_set_watchdog(wdd->timeout * 1000, NULL); |
|
if (hverr == HV_EINVAL) |
|
return -EINVAL; |
|
|
|
return 0; |
|
} |
|
|
|
static int sun4v_wdt_set_timeout(struct watchdog_device *wdd, |
|
unsigned int timeout) |
|
{ |
|
wdd->timeout = timeout; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct watchdog_info sun4v_wdt_ident = { |
|
.options = WDIOF_SETTIMEOUT | |
|
WDIOF_MAGICCLOSE | |
|
WDIOF_KEEPALIVEPING, |
|
.identity = "sun4v hypervisor watchdog", |
|
.firmware_version = 0, |
|
}; |
|
|
|
static const struct watchdog_ops sun4v_wdt_ops = { |
|
.owner = THIS_MODULE, |
|
.start = sun4v_wdt_ping, |
|
.stop = sun4v_wdt_stop, |
|
.ping = sun4v_wdt_ping, |
|
.set_timeout = sun4v_wdt_set_timeout, |
|
}; |
|
|
|
static struct watchdog_device wdd = { |
|
.info = &sun4v_wdt_ident, |
|
.ops = &sun4v_wdt_ops, |
|
.min_timeout = WDT_MIN_TIMEOUT, |
|
.max_timeout = WDT_MAX_TIMEOUT, |
|
.timeout = WDT_TIMEOUT, |
|
}; |
|
|
|
static int __init sun4v_wdt_init(void) |
|
{ |
|
struct mdesc_handle *handle; |
|
u64 node; |
|
const u64 *value; |
|
int err = 0; |
|
unsigned long major = 1, minor = 1; |
|
|
|
/* |
|
* There are 2 properties that can be set from the control |
|
* domain for the watchdog. |
|
* watchdog-resolution |
|
* watchdog-max-timeout |
|
* |
|
* We can expect a handle to be returned otherwise something |
|
* serious is wrong. Correct to return -ENODEV here. |
|
*/ |
|
|
|
handle = mdesc_grab(); |
|
if (!handle) |
|
return -ENODEV; |
|
|
|
node = mdesc_node_by_name(handle, MDESC_NODE_NULL, "platform"); |
|
err = -ENODEV; |
|
if (node == MDESC_NODE_NULL) |
|
goto out_release; |
|
|
|
/* |
|
* This is a safe way to validate if we are on the right |
|
* platform. |
|
*/ |
|
if (sun4v_hvapi_register(HV_GRP_CORE, major, &minor)) |
|
goto out_hv_unreg; |
|
|
|
/* Allow value of watchdog-resolution up to 1s (default) */ |
|
value = mdesc_get_property(handle, node, "watchdog-resolution", NULL); |
|
err = -EINVAL; |
|
if (value) { |
|
if (*value == 0 || |
|
*value > WDT_DEFAULT_RESOLUTION_MS) |
|
goto out_hv_unreg; |
|
} |
|
|
|
value = mdesc_get_property(handle, node, "watchdog-max-timeout", NULL); |
|
if (value) { |
|
/* |
|
* If the property value (in ms) is smaller than |
|
* min_timeout, return -EINVAL. |
|
*/ |
|
if (*value < wdd.min_timeout * 1000) |
|
goto out_hv_unreg; |
|
|
|
/* |
|
* If the property value is smaller than |
|
* default max_timeout then set watchdog max_timeout to |
|
* the value of the property in seconds. |
|
*/ |
|
if (*value < wdd.max_timeout * 1000) |
|
wdd.max_timeout = *value / 1000; |
|
} |
|
|
|
watchdog_init_timeout(&wdd, timeout, NULL); |
|
|
|
watchdog_set_nowayout(&wdd, nowayout); |
|
|
|
err = watchdog_register_device(&wdd); |
|
if (err) |
|
goto out_hv_unreg; |
|
|
|
pr_info("initialized (timeout=%ds, nowayout=%d)\n", |
|
wdd.timeout, nowayout); |
|
|
|
mdesc_release(handle); |
|
|
|
return 0; |
|
|
|
out_hv_unreg: |
|
sun4v_hvapi_unregister(HV_GRP_CORE); |
|
|
|
out_release: |
|
mdesc_release(handle); |
|
return err; |
|
} |
|
|
|
static void __exit sun4v_wdt_exit(void) |
|
{ |
|
sun4v_hvapi_unregister(HV_GRP_CORE); |
|
watchdog_unregister_device(&wdd); |
|
} |
|
|
|
module_init(sun4v_wdt_init); |
|
module_exit(sun4v_wdt_exit); |
|
|
|
MODULE_AUTHOR("Wim Coekaerts <[email protected]>"); |
|
MODULE_DESCRIPTION("sun4v watchdog driver"); |
|
MODULE_LICENSE("GPL");
|
|
|