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