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