xref: /illumos-gate/usr/src/uts/sun4v/os/wdt.c (revision 99f63845ee65f89a523460ce4b6b0603a06eceb7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/types.h>
29 #include <sys/hsvc.h>
30 #include <sys/wdt.h>
31 #include <sys/cmn_err.h>
32 #include <sys/cyclic.h>
33 #include <sys/kmem.h>
34 #include <sys/systm.h>
35 #include <sys/sysmacros.h>
36 #include <sys/hypervisor_api.h>
37 #include <sys/mach_descrip.h>
38 #include <sys/mdesc.h>
39 
40 #define	WDT_ON			1
41 #define	WDT_OFF			0
42 
43 /*
44  * MILLISEC defines the number of milliseconds in a second.
45  */
46 #define	WDT_DEFAULT_RESOLUTION	(1 * MILLISEC)	/* Default resolution = 1s */
47 #define	WDT_MIN_TIMEOUT		(1 * MILLISEC)	/* Minimum timeout = 1s */
48 #define	WDT_REGULAR_TIMEOUT	(10 * MILLISEC)	/* Default timeout = 10s */
49 #define	WDT_LONG_TIMEOUT	(60 * MILLISEC)	/* Long timeout = 60s */
50 
51 #define	WDT_MIN_COREAPI_MAJOR	1
52 #define	WDT_MIN_COREAPI_MINOR	1
53 
54 static void config_watchdog(uint64_t, int);
55 static void watchdog_cyclic_init(hrtime_t);
56 
57 /*
58  * Flag used to pat/suspend/resume the watchdog timer.
59  */
60 int watchdog_activated = WDT_OFF;
61 
62 /*
63  * Tuneable to control watchdog functionality. Watchdog can be
64  * disabled via /etc/system.
65  */
66 int watchdog_enabled = 1;
67 static int watchdog_initialized = 0;
68 
69 /*
70  * The following tuneable can be set via /etc/system to control
71  * watchdog pat frequency, which is set to approximately 44% of
72  * the timeout value.
73  */
74 static uint64_t watchdog_timeout = WDT_REGULAR_TIMEOUT;
75 
76 static uint64_t watchdog_long_timeout = WDT_LONG_TIMEOUT;
77 static uint64_t watchdog_resolution = WDT_DEFAULT_RESOLUTION;
78 
79 void
80 watchdog_init(void)
81 {
82 	int num_nodes;
83 	int nplat;
84 	md_t *mdp;
85 	mde_cookie_t *listp = NULL;
86 	int listsz;
87 	uint64_t major;
88 	uint64_t minor;
89 	uint64_t watchdog_max_timeout;
90 	hrtime_t cyclic_interval;
91 
92 	if (!watchdog_enabled) {
93 		return;
94 	}
95 
96 	if (hsvc_version(HSVC_GROUP_CORE, &major, &minor) != 0 ||
97 	    major != WDT_MIN_COREAPI_MAJOR ||
98 	    minor < WDT_MIN_COREAPI_MINOR) {
99 		cmn_err(CE_NOTE, "Disabling watchdog as watchdog services are "
100 		    "not available\n");
101 		watchdog_enabled = 0;
102 		return;
103 	}
104 
105 	/*
106 	 * Get the watchdog-max-timeout and watchdog-resolution MD properties.
107 	 */
108 	if ((mdp = md_get_handle()) == NULL) {
109 		cmn_err(CE_WARN, "Unable to initialize machine description, "
110 		    "watchdog is disabled.");
111 		watchdog_enabled = 0;
112 		return;
113 	}
114 
115 	num_nodes = md_node_count(mdp);
116 	ASSERT(num_nodes > 0);
117 
118 	listsz = num_nodes * sizeof (mde_cookie_t);
119 	listp = kmem_zalloc(listsz, KM_SLEEP);
120 
121 	nplat = md_scan_dag(mdp, md_root_node(mdp),
122 	    md_find_name(mdp, "platform"), md_find_name(mdp, "fwd"), listp);
123 
124 	ASSERT(nplat == 1);
125 
126 	if (md_get_prop_val(mdp, listp[0], "watchdog-max-timeout",
127 	    &watchdog_max_timeout) || watchdog_max_timeout < WDT_MIN_TIMEOUT) {
128 		cmn_err(CE_WARN, "Invalid watchdog-max-timeout, watchdog "
129 		    "is disabled.");
130 		watchdog_enabled = 0;
131 		kmem_free(listp, listsz);
132 		(void) md_fini_handle(mdp);
133 		return;
134 	}
135 
136 	/*
137 	 * Make sure that watchdog timeout value is within limits.
138 	 */
139 	if (watchdog_timeout < WDT_MIN_TIMEOUT)
140 		watchdog_timeout = WDT_MIN_TIMEOUT;
141 	else if (watchdog_timeout > WDT_LONG_TIMEOUT)
142 		watchdog_timeout = WDT_LONG_TIMEOUT;
143 
144 	if (watchdog_timeout > watchdog_max_timeout)
145 		watchdog_timeout = watchdog_max_timeout;
146 
147 	if (watchdog_long_timeout > watchdog_max_timeout)
148 		watchdog_long_timeout = watchdog_max_timeout;
149 
150 	if (md_get_prop_val(mdp, listp[0], "watchdog-resolution",
151 	    &watchdog_resolution)) {
152 		cmn_err(CE_WARN, "Cannot read watchdog-resolution, watchdog "
153 		    "is disabled.");
154 		watchdog_enabled = 0;
155 		kmem_free(listp, listsz);
156 		(void) md_fini_handle(mdp);
157 		return;
158 	}
159 
160 	if (watchdog_resolution == 0 ||
161 	    watchdog_resolution > WDT_DEFAULT_RESOLUTION)
162 		watchdog_resolution = WDT_DEFAULT_RESOLUTION;
163 
164 	kmem_free(listp, listsz);
165 	(void) md_fini_handle(mdp);
166 
167 	/*
168 	 * round the timeout to the nearest smaller value.
169 	 */
170 	watchdog_long_timeout -=
171 	    watchdog_long_timeout % watchdog_resolution;
172 	watchdog_timeout -=
173 	    watchdog_timeout % watchdog_resolution;
174 
175 	/*
176 	 * Cyclic need to be fired twice the frequency of regular
177 	 * watchdog timeout. Pedantic here and setting cyclic
178 	 * frequency to approximately 44% of watchdog_timeout.
179 	 */
180 	cyclic_interval = (watchdog_timeout >> 1) - (watchdog_timeout >> 4);
181 	/*
182 	 * Note that regular timeout interval is in millisecond,
183 	 * therefore to get cyclic interval in nanosecond need to
184 	 * multiply by MICROSEC.
185 	 */
186 	cyclic_interval *= MICROSEC;
187 
188 	watchdog_cyclic_init(cyclic_interval);
189 	watchdog_initialized = 1;
190 	config_watchdog(watchdog_timeout, WDT_ON);
191 }
192 
193 /*
194  * Pat the watchdog timer periodically using the hypervisor API.
195  * Regular pat occurs when the system runs normally.
196  * Long pat is when system panics.
197  */
198 void
199 watchdog_pat()
200 {
201 	if (watchdog_enabled && watchdog_activated) {
202 		if (panicstr)
203 			config_watchdog(watchdog_long_timeout, WDT_ON);
204 		else
205 			config_watchdog(watchdog_timeout, WDT_ON);
206 	}
207 }
208 
209 /*
210  * We don't save/restore the remaining watchdog timeout time at present.
211  */
212 void
213 watchdog_suspend()
214 {
215 	if (watchdog_enabled && watchdog_activated) {
216 		config_watchdog(0, WDT_OFF);
217 	}
218 }
219 
220 /*
221  * We don't save/restore the remaining watchdog timeout time at present.
222  */
223 void
224 watchdog_resume()
225 {
226 	if (watchdog_enabled && !watchdog_activated) {
227 		if (panicstr) {
228 			config_watchdog(watchdog_long_timeout, WDT_ON);
229 		} else {
230 			config_watchdog(watchdog_timeout, WDT_ON);
231 		}
232 	}
233 }
234 
235 void
236 watchdog_clear()
237 {
238 	if (watchdog_enabled && watchdog_activated) {
239 		config_watchdog(0, WDT_OFF);
240 	}
241 }
242 
243 static void
244 config_watchdog(uint64_t timeout, int new_state)
245 {
246 	uint64_t time_remaining;
247 	uint64_t ret;
248 
249 	if (watchdog_initialized) {
250 		watchdog_activated = new_state;
251 		ret = hv_mach_set_watchdog(timeout, &time_remaining);
252 		if (ret != H_EOK) {
253 			cmn_err(CE_WARN, "Failed to operate on the watchdog. "
254 			    "Error = 0x%lx", ret);
255 			watchdog_enabled = 0;
256 		}
257 	}
258 }
259 
260 /*
261  * Once the watchdog cyclic is initialized, it won't be removed.
262  * The only way to not add the watchdog cyclic is to disable the watchdog
263  * by setting the watchdog_enabled to 0 in /etc/system file.
264  */
265 static void
266 watchdog_cyclic_init(hrtime_t wdt_cyclic_interval)
267 {
268 	cyc_handler_t hdlr;
269 	cyc_time_t when;
270 
271 	hdlr.cyh_func = (cyc_func_t)watchdog_pat;
272 	hdlr.cyh_level = CY_HIGH_LEVEL;
273 	hdlr.cyh_arg = NULL;
274 
275 	when.cyt_when = 0;
276 	when.cyt_interval = wdt_cyclic_interval;
277 
278 	mutex_enter(&cpu_lock);
279 	(void) cyclic_add(&hdlr, &when);
280 	mutex_exit(&cpu_lock);
281 }
282