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
watchdog_init(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
watchdog_pat()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
watchdog_suspend()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
watchdog_resume()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
watchdog_clear()236 watchdog_clear()
237 {
238 if (watchdog_enabled && watchdog_activated) {
239 config_watchdog(0, WDT_OFF);
240 }
241 }
242
243 static void
config_watchdog(uint64_t timeout,int new_state)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
watchdog_cyclic_init(hrtime_t wdt_cyclic_interval)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