xref: /openbmc/linux/drivers/watchdog/sun4v_wdt.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1*2874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2ca0bb079Swim.coekaerts@oracle.com /*
3ca0bb079Swim.coekaerts@oracle.com  *	sun4v watchdog timer
4ca0bb079Swim.coekaerts@oracle.com  *	(c) Copyright 2016 Oracle Corporation
5ca0bb079Swim.coekaerts@oracle.com  *
6ca0bb079Swim.coekaerts@oracle.com  *	Implement a simple watchdog driver using the built-in sun4v hypervisor
7ca0bb079Swim.coekaerts@oracle.com  *	watchdog support. If time expires, the hypervisor stops or bounces
8ca0bb079Swim.coekaerts@oracle.com  *	the guest domain.
9ca0bb079Swim.coekaerts@oracle.com  */
10ca0bb079Swim.coekaerts@oracle.com 
11ca0bb079Swim.coekaerts@oracle.com #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12ca0bb079Swim.coekaerts@oracle.com 
13ca0bb079Swim.coekaerts@oracle.com #include <linux/errno.h>
14ca0bb079Swim.coekaerts@oracle.com #include <linux/init.h>
15ca0bb079Swim.coekaerts@oracle.com #include <linux/kernel.h>
16ca0bb079Swim.coekaerts@oracle.com #include <linux/module.h>
17ca0bb079Swim.coekaerts@oracle.com #include <linux/moduleparam.h>
18ca0bb079Swim.coekaerts@oracle.com #include <linux/watchdog.h>
19ca0bb079Swim.coekaerts@oracle.com #include <asm/hypervisor.h>
20ca0bb079Swim.coekaerts@oracle.com #include <asm/mdesc.h>
21ca0bb079Swim.coekaerts@oracle.com 
22ca0bb079Swim.coekaerts@oracle.com #define WDT_TIMEOUT			60
23ca0bb079Swim.coekaerts@oracle.com #define WDT_MAX_TIMEOUT			31536000
24ca0bb079Swim.coekaerts@oracle.com #define WDT_MIN_TIMEOUT			1
25ca0bb079Swim.coekaerts@oracle.com #define WDT_DEFAULT_RESOLUTION_MS	1000	/* 1 second */
26ca0bb079Swim.coekaerts@oracle.com 
27ca0bb079Swim.coekaerts@oracle.com static unsigned int timeout;
28ca0bb079Swim.coekaerts@oracle.com module_param(timeout, uint, 0);
29ca0bb079Swim.coekaerts@oracle.com MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
30ca0bb079Swim.coekaerts@oracle.com 	__MODULE_STRING(WDT_TIMEOUT) ")");
31ca0bb079Swim.coekaerts@oracle.com 
32ca0bb079Swim.coekaerts@oracle.com static bool nowayout = WATCHDOG_NOWAYOUT;
33ca0bb079Swim.coekaerts@oracle.com module_param(nowayout, bool, S_IRUGO);
34ca0bb079Swim.coekaerts@oracle.com MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
35ca0bb079Swim.coekaerts@oracle.com 	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
36ca0bb079Swim.coekaerts@oracle.com 
sun4v_wdt_stop(struct watchdog_device * wdd)37ca0bb079Swim.coekaerts@oracle.com static int sun4v_wdt_stop(struct watchdog_device *wdd)
38ca0bb079Swim.coekaerts@oracle.com {
39ca0bb079Swim.coekaerts@oracle.com 	sun4v_mach_set_watchdog(0, NULL);
40ca0bb079Swim.coekaerts@oracle.com 
41ca0bb079Swim.coekaerts@oracle.com 	return 0;
42ca0bb079Swim.coekaerts@oracle.com }
43ca0bb079Swim.coekaerts@oracle.com 
sun4v_wdt_ping(struct watchdog_device * wdd)44ca0bb079Swim.coekaerts@oracle.com static int sun4v_wdt_ping(struct watchdog_device *wdd)
45ca0bb079Swim.coekaerts@oracle.com {
46ca0bb079Swim.coekaerts@oracle.com 	int hverr;
47ca0bb079Swim.coekaerts@oracle.com 
48ca0bb079Swim.coekaerts@oracle.com 	/*
49ca0bb079Swim.coekaerts@oracle.com 	 * HV watchdog timer will round up the timeout
50ca0bb079Swim.coekaerts@oracle.com 	 * passed in to the nearest multiple of the
51ca0bb079Swim.coekaerts@oracle.com 	 * watchdog resolution in milliseconds.
52ca0bb079Swim.coekaerts@oracle.com 	 */
53ca0bb079Swim.coekaerts@oracle.com 	hverr = sun4v_mach_set_watchdog(wdd->timeout * 1000, NULL);
54ca0bb079Swim.coekaerts@oracle.com 	if (hverr == HV_EINVAL)
55ca0bb079Swim.coekaerts@oracle.com 		return -EINVAL;
56ca0bb079Swim.coekaerts@oracle.com 
57ca0bb079Swim.coekaerts@oracle.com 	return 0;
58ca0bb079Swim.coekaerts@oracle.com }
59ca0bb079Swim.coekaerts@oracle.com 
sun4v_wdt_set_timeout(struct watchdog_device * wdd,unsigned int timeout)60ca0bb079Swim.coekaerts@oracle.com static int sun4v_wdt_set_timeout(struct watchdog_device *wdd,
61ca0bb079Swim.coekaerts@oracle.com 				 unsigned int timeout)
62ca0bb079Swim.coekaerts@oracle.com {
63ca0bb079Swim.coekaerts@oracle.com 	wdd->timeout = timeout;
64ca0bb079Swim.coekaerts@oracle.com 
65ca0bb079Swim.coekaerts@oracle.com 	return 0;
66ca0bb079Swim.coekaerts@oracle.com }
67ca0bb079Swim.coekaerts@oracle.com 
68ca0bb079Swim.coekaerts@oracle.com static const struct watchdog_info sun4v_wdt_ident = {
69ca0bb079Swim.coekaerts@oracle.com 	.options =	WDIOF_SETTIMEOUT |
70ca0bb079Swim.coekaerts@oracle.com 			WDIOF_MAGICCLOSE |
71ca0bb079Swim.coekaerts@oracle.com 			WDIOF_KEEPALIVEPING,
72ca0bb079Swim.coekaerts@oracle.com 	.identity =	"sun4v hypervisor watchdog",
73ca0bb079Swim.coekaerts@oracle.com 	.firmware_version = 0,
74ca0bb079Swim.coekaerts@oracle.com };
75ca0bb079Swim.coekaerts@oracle.com 
76b893e344SBhumika Goyal static const struct watchdog_ops sun4v_wdt_ops = {
77ca0bb079Swim.coekaerts@oracle.com 	.owner =	THIS_MODULE,
78ca0bb079Swim.coekaerts@oracle.com 	.start =	sun4v_wdt_ping,
79ca0bb079Swim.coekaerts@oracle.com 	.stop =		sun4v_wdt_stop,
80ca0bb079Swim.coekaerts@oracle.com 	.ping =		sun4v_wdt_ping,
81ca0bb079Swim.coekaerts@oracle.com 	.set_timeout =	sun4v_wdt_set_timeout,
82ca0bb079Swim.coekaerts@oracle.com };
83ca0bb079Swim.coekaerts@oracle.com 
84ca0bb079Swim.coekaerts@oracle.com static struct watchdog_device wdd = {
85ca0bb079Swim.coekaerts@oracle.com 	.info = &sun4v_wdt_ident,
86ca0bb079Swim.coekaerts@oracle.com 	.ops = &sun4v_wdt_ops,
87ca0bb079Swim.coekaerts@oracle.com 	.min_timeout = WDT_MIN_TIMEOUT,
88ca0bb079Swim.coekaerts@oracle.com 	.max_timeout = WDT_MAX_TIMEOUT,
89ca0bb079Swim.coekaerts@oracle.com 	.timeout = WDT_TIMEOUT,
90ca0bb079Swim.coekaerts@oracle.com };
91ca0bb079Swim.coekaerts@oracle.com 
sun4v_wdt_init(void)92ca0bb079Swim.coekaerts@oracle.com static int __init sun4v_wdt_init(void)
93ca0bb079Swim.coekaerts@oracle.com {
94ca0bb079Swim.coekaerts@oracle.com 	struct mdesc_handle *handle;
95ca0bb079Swim.coekaerts@oracle.com 	u64 node;
96ca0bb079Swim.coekaerts@oracle.com 	const u64 *value;
97ca0bb079Swim.coekaerts@oracle.com 	int err = 0;
98ca0bb079Swim.coekaerts@oracle.com 	unsigned long major = 1, minor = 1;
99ca0bb079Swim.coekaerts@oracle.com 
100ca0bb079Swim.coekaerts@oracle.com 	/*
101ca0bb079Swim.coekaerts@oracle.com 	 * There are 2 properties that can be set from the control
102ca0bb079Swim.coekaerts@oracle.com 	 * domain for the watchdog.
103ca0bb079Swim.coekaerts@oracle.com 	 * watchdog-resolution
104ca0bb079Swim.coekaerts@oracle.com 	 * watchdog-max-timeout
105ca0bb079Swim.coekaerts@oracle.com 	 *
106ca0bb079Swim.coekaerts@oracle.com 	 * We can expect a handle to be returned otherwise something
107ca0bb079Swim.coekaerts@oracle.com 	 * serious is wrong. Correct to return -ENODEV here.
108ca0bb079Swim.coekaerts@oracle.com 	 */
109ca0bb079Swim.coekaerts@oracle.com 
110ca0bb079Swim.coekaerts@oracle.com 	handle = mdesc_grab();
111ca0bb079Swim.coekaerts@oracle.com 	if (!handle)
112ca0bb079Swim.coekaerts@oracle.com 		return -ENODEV;
113ca0bb079Swim.coekaerts@oracle.com 
114ca0bb079Swim.coekaerts@oracle.com 	node = mdesc_node_by_name(handle, MDESC_NODE_NULL, "platform");
115ca0bb079Swim.coekaerts@oracle.com 	err = -ENODEV;
116ca0bb079Swim.coekaerts@oracle.com 	if (node == MDESC_NODE_NULL)
117ca0bb079Swim.coekaerts@oracle.com 		goto out_release;
118ca0bb079Swim.coekaerts@oracle.com 
119ca0bb079Swim.coekaerts@oracle.com 	/*
120ca0bb079Swim.coekaerts@oracle.com 	 * This is a safe way to validate if we are on the right
121ca0bb079Swim.coekaerts@oracle.com 	 * platform.
122ca0bb079Swim.coekaerts@oracle.com 	 */
123ca0bb079Swim.coekaerts@oracle.com 	if (sun4v_hvapi_register(HV_GRP_CORE, major, &minor))
124ca0bb079Swim.coekaerts@oracle.com 		goto out_hv_unreg;
125ca0bb079Swim.coekaerts@oracle.com 
126ca0bb079Swim.coekaerts@oracle.com 	/* Allow value of watchdog-resolution up to 1s (default) */
127ca0bb079Swim.coekaerts@oracle.com 	value = mdesc_get_property(handle, node, "watchdog-resolution", NULL);
128ca0bb079Swim.coekaerts@oracle.com 	err = -EINVAL;
129ca0bb079Swim.coekaerts@oracle.com 	if (value) {
130ca0bb079Swim.coekaerts@oracle.com 		if (*value == 0 ||
131ca0bb079Swim.coekaerts@oracle.com 		    *value > WDT_DEFAULT_RESOLUTION_MS)
132ca0bb079Swim.coekaerts@oracle.com 			goto out_hv_unreg;
133ca0bb079Swim.coekaerts@oracle.com 	}
134ca0bb079Swim.coekaerts@oracle.com 
135ca0bb079Swim.coekaerts@oracle.com 	value = mdesc_get_property(handle, node, "watchdog-max-timeout", NULL);
136ca0bb079Swim.coekaerts@oracle.com 	if (value) {
137ca0bb079Swim.coekaerts@oracle.com 		/*
138ca0bb079Swim.coekaerts@oracle.com 		 * If the property value (in ms) is smaller than
139ca0bb079Swim.coekaerts@oracle.com 		 * min_timeout, return -EINVAL.
140ca0bb079Swim.coekaerts@oracle.com 		 */
141ca0bb079Swim.coekaerts@oracle.com 		if (*value < wdd.min_timeout * 1000)
142ca0bb079Swim.coekaerts@oracle.com 			goto out_hv_unreg;
143ca0bb079Swim.coekaerts@oracle.com 
144ca0bb079Swim.coekaerts@oracle.com 		/*
145ca0bb079Swim.coekaerts@oracle.com 		 * If the property value is smaller than
146ca0bb079Swim.coekaerts@oracle.com 		 * default max_timeout  then set watchdog max_timeout to
147ca0bb079Swim.coekaerts@oracle.com 		 * the value of the property in seconds.
148ca0bb079Swim.coekaerts@oracle.com 		 */
149ca0bb079Swim.coekaerts@oracle.com 		if (*value < wdd.max_timeout * 1000)
150ca0bb079Swim.coekaerts@oracle.com 			wdd.max_timeout = *value  / 1000;
151ca0bb079Swim.coekaerts@oracle.com 	}
152ca0bb079Swim.coekaerts@oracle.com 
153ca0bb079Swim.coekaerts@oracle.com 	watchdog_init_timeout(&wdd, timeout, NULL);
154ca0bb079Swim.coekaerts@oracle.com 
155ca0bb079Swim.coekaerts@oracle.com 	watchdog_set_nowayout(&wdd, nowayout);
156ca0bb079Swim.coekaerts@oracle.com 
157ca0bb079Swim.coekaerts@oracle.com 	err = watchdog_register_device(&wdd);
158ca0bb079Swim.coekaerts@oracle.com 	if (err)
159ca0bb079Swim.coekaerts@oracle.com 		goto out_hv_unreg;
160ca0bb079Swim.coekaerts@oracle.com 
161ca0bb079Swim.coekaerts@oracle.com 	pr_info("initialized (timeout=%ds, nowayout=%d)\n",
162ca0bb079Swim.coekaerts@oracle.com 		 wdd.timeout, nowayout);
163ca0bb079Swim.coekaerts@oracle.com 
164ca0bb079Swim.coekaerts@oracle.com 	mdesc_release(handle);
165ca0bb079Swim.coekaerts@oracle.com 
166ca0bb079Swim.coekaerts@oracle.com 	return 0;
167ca0bb079Swim.coekaerts@oracle.com 
168ca0bb079Swim.coekaerts@oracle.com out_hv_unreg:
169ca0bb079Swim.coekaerts@oracle.com 	sun4v_hvapi_unregister(HV_GRP_CORE);
170ca0bb079Swim.coekaerts@oracle.com 
171ca0bb079Swim.coekaerts@oracle.com out_release:
172ca0bb079Swim.coekaerts@oracle.com 	mdesc_release(handle);
173ca0bb079Swim.coekaerts@oracle.com 	return err;
174ca0bb079Swim.coekaerts@oracle.com }
175ca0bb079Swim.coekaerts@oracle.com 
sun4v_wdt_exit(void)176ca0bb079Swim.coekaerts@oracle.com static void __exit sun4v_wdt_exit(void)
177ca0bb079Swim.coekaerts@oracle.com {
178ca0bb079Swim.coekaerts@oracle.com 	sun4v_hvapi_unregister(HV_GRP_CORE);
179ca0bb079Swim.coekaerts@oracle.com 	watchdog_unregister_device(&wdd);
180ca0bb079Swim.coekaerts@oracle.com }
181ca0bb079Swim.coekaerts@oracle.com 
182ca0bb079Swim.coekaerts@oracle.com module_init(sun4v_wdt_init);
183ca0bb079Swim.coekaerts@oracle.com module_exit(sun4v_wdt_exit);
184ca0bb079Swim.coekaerts@oracle.com 
185ca0bb079Swim.coekaerts@oracle.com MODULE_AUTHOR("Wim Coekaerts <wim.coekaerts@oracle.com>");
186ca0bb079Swim.coekaerts@oracle.com MODULE_DESCRIPTION("sun4v watchdog driver");
187ca0bb079Swim.coekaerts@oracle.com MODULE_LICENSE("GPL");
188