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 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * tod driver module for Serengeti 28 * This module implements a soft tod since 29 * Serengeti has no tod part. 30 */ 31 32 #include <sys/modctl.h> 33 #include <sys/systm.h> 34 #include <sys/cpuvar.h> 35 #include <sys/promif.h> 36 #include <sys/sgsbbc_iosram.h> 37 #include <sys/todsg.h> 38 #include <sys/cmn_err.h> 39 #include <sys/time.h> 40 #include <sys/sysmacros.h> 41 #include <sys/clock.h> 42 43 #if defined(DEBUG) || defined(lint) 44 static int todsg_debug = 0; 45 #define DCMNERR if (todsg_debug) cmn_err 46 #else 47 #define DCMNERR 48 #endif /* DEBUG */ 49 50 #define OFFSET(base, field) ((char *)&base.field - (char *)&base) 51 #define SC_DOWN_COUNT_THRESHOLD 2 52 #define SC_TOD_MIN_REV 2 53 54 static timestruc_t todsg_get(void); 55 static void todsg_set(timestruc_t); 56 static uint32_t todsg_set_watchdog_timer(uint_t); 57 static uint32_t todsg_clear_watchdog_timer(void); 58 static void todsg_set_power_alarm(timestruc_t); 59 static void todsg_clear_power_alarm(void); 60 static uint64_t todsg_get_cpufrequency(void); 61 static int update_heartbeat(void); 62 static int verify_sc_tod_version(void); 63 static int update_tod_skew(time_t skew); 64 65 static uint32_t i_am_alive = 0; 66 static uint32_t sc_tod_version = 0; 67 static time_t skew_adjust = 0; 68 static int is_sc_down = 0; 69 static int adjust_sc_down = 0; 70 71 /* 72 * Module linkage information for the kernel. 73 */ 74 static struct modlmisc modlmisc = { 75 &mod_miscops, "Serengeti tod module" 76 }; 77 78 static struct modlinkage modlinkage = { 79 MODREV_1, (void *)&modlmisc, NULL 80 }; 81 82 int 83 _init(void) 84 { 85 86 DCMNERR(CE_NOTE, "todsg:_init(): begins"); 87 88 if (strcmp(tod_module_name, "todsg") == 0) { 89 time_t ssc_time = (time_t)0; 90 char obp_string[80]; 91 92 /* 93 * To obtain the initial start of day time, we use an 94 * OBP callback; this is because the iosram is not yet 95 * accessible from the OS at this early stage of startup. 96 */ 97 98 /* 99 * Set the string to pass to OBP 100 */ 101 (void) sprintf(obp_string, 102 "h# %p \" unix-get-tod\" $find if execute else " 103 "3drop then", 104 (void *)&ssc_time); 105 106 prom_interpret(obp_string, 0, 0, 0, 0, 0); 107 108 if (ssc_time == (time_t)0) { 109 cmn_err(CE_WARN, "Initial date is invalid. " 110 "This can be caused by older firmware."); 111 cmn_err(CE_CONT, "Please flashupdate the System " 112 "Controller firmware to the latest version.\n"); 113 cmn_err(CE_CONT, "Attempting to set the date and time " 114 "based on the last shutdown.\n"); 115 cmn_err(CE_CONT, "Please inspect the date and time and " 116 "correct if necessary.\n"); 117 } 118 119 hrestime.tv_sec = ssc_time; 120 121 DCMNERR(CE_NOTE, "todsg: _init(): time from OBP 0x%lX", 122 ssc_time); 123 /* 124 * Verify whether the received date/clock has overflowed 125 * an integer(32bit), so that we capture any corrupted 126 * date from SC, thereby preventing boot failure. 127 */ 128 if (TIMESPEC_OVERFLOW(&hrestime)) { 129 cmn_err(CE_WARN, "Date overflow detected."); 130 cmn_err(CE_CONT, "Attempting to set the date and time " 131 "based on the last shutdown.\n"); 132 cmn_err(CE_CONT, "Please inspect the date and time and " 133 "correct if necessary.\n"); 134 135 /* 136 * By setting hrestime.tv_sec to zero 137 * we force the vfs_mountroot() to set 138 * the date from the last shutdown. 139 */ 140 hrestime.tv_sec = (time_t)0; 141 /* 142 * Save the skew so that we can update 143 * IOSRAM when it becomes accessible. 144 */ 145 skew_adjust = -ssc_time; 146 } 147 148 DCMNERR(CE_NOTE, "todsg:_init(): set tod_ops"); 149 150 tod_ops.tod_get = todsg_get; 151 tod_ops.tod_set = todsg_set; 152 tod_ops.tod_set_watchdog_timer = todsg_set_watchdog_timer; 153 tod_ops.tod_clear_watchdog_timer = todsg_clear_watchdog_timer; 154 tod_ops.tod_set_power_alarm = todsg_set_power_alarm; 155 tod_ops.tod_clear_power_alarm = todsg_clear_power_alarm; 156 tod_ops.tod_get_cpufrequency = todsg_get_cpufrequency; 157 } 158 159 return (mod_install(&modlinkage)); 160 161 } 162 163 int 164 _fini(void) 165 { 166 if (strcmp(tod_module_name, "todsg") == 0) 167 return (EBUSY); 168 else 169 return (mod_remove(&modlinkage)); 170 } 171 172 int 173 _info(struct modinfo *modinfop) 174 { 175 return (mod_info(&modlinkage, modinfop)); 176 } 177 178 static int 179 update_heartbeat(void) 180 { 181 tod_iosram_t tod_buf; 182 int complained = 0; 183 184 /* Update the heartbeat */ 185 if (i_am_alive == UINT32_MAX) 186 i_am_alive = 0; 187 else 188 i_am_alive++; 189 if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_i_am_alive), 190 (char *)&i_am_alive, sizeof (uint32_t))) { 191 complained++; 192 cmn_err(CE_WARN, "update_heartbeat(): write heartbeat failed"); 193 } 194 return (complained); 195 } 196 197 static int 198 verify_sc_tod_version(void) 199 { 200 uint32_t magic; 201 tod_iosram_t tod_buf; 202 203 if (!todsg_use_sc) 204 return (FALSE); 205 /* 206 * read tod_version only when the first time and 207 * when there has been a previous sc down time 208 */ 209 if (!sc_tod_version || is_sc_down >= SC_DOWN_COUNT_THRESHOLD) { 210 if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_magic), 211 (char *)&magic, sizeof (uint32_t)) || 212 magic != TODSG_MAGIC) { 213 cmn_err(CE_WARN, "get_sc_tod_version(): " 214 "TOD SRAM magic error"); 215 return (FALSE); 216 } 217 if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_version), 218 (char *)&sc_tod_version, sizeof (uint32_t))) { 219 cmn_err(CE_WARN, "get_sc_tod_version(): " 220 "read tod version failed"); 221 sc_tod_version = 0; 222 return (FALSE); 223 } 224 } 225 if (sc_tod_version >= SC_TOD_MIN_REV) { 226 return (TRUE); 227 } else { 228 todsg_use_sc = 0; 229 cmn_err(CE_WARN, 230 "todsg_get(): incorrect firmware version, " 231 "(%d): expected version >= %d.", 232 sc_tod_version, SC_TOD_MIN_REV); 233 } 234 return (FALSE); 235 } 236 237 static int 238 update_tod_skew(time_t skew) 239 { 240 time_t domain_skew; 241 tod_iosram_t tod_buf; 242 int complained = 0; 243 244 DCMNERR(CE_NOTE, "update_tod_skew(): skew 0x%lX", skew); 245 246 if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_domain_skew), 247 (char *)&domain_skew, sizeof (time_t))) { 248 complained++; 249 cmn_err(CE_WARN, "update_tod_skew(): " 250 "read tod domain skew failed"); 251 } 252 domain_skew += skew; 253 /* we shall update the skew_adjust too now */ 254 domain_skew += skew_adjust; 255 if (!complained && iosram_write(SBBC_TOD_KEY, 256 OFFSET(tod_buf, tod_domain_skew), 257 (char *)&domain_skew, sizeof (time_t))) { 258 complained++; 259 cmn_err(CE_WARN, "update_tod_skew(): " 260 "write domain skew failed"); 261 } 262 if (!complained) 263 skew_adjust = 0; 264 return (complained); 265 } 266 267 268 /* 269 * Return time value read from IOSRAM. 270 * Must be called with tod_lock held. 271 */ 272 static timestruc_t 273 todsg_get(void) 274 { 275 tod_iosram_t tod_buf; 276 time_t seconds; 277 time_t domain_skew; 278 int complained = 0; 279 static time_t pre_seconds = (time_t)0; 280 281 ASSERT(MUTEX_HELD(&tod_lock)); 282 283 if (!verify_sc_tod_version()) { 284 /* if we can't use SC */ 285 goto return_hrestime; 286 } 287 if (watchdog_activated != 0 || watchdog_enable != 0) 288 complained = update_heartbeat(); 289 if (!complained && (iosram_read(SBBC_TOD_KEY, 290 OFFSET(tod_buf, tod_get_value), 291 (char *)&seconds, sizeof (time_t)))) { 292 complained++; 293 cmn_err(CE_WARN, "todsg_get(): read 64-bit tod value failed"); 294 } 295 if (!complained && skew_adjust) { 296 /* 297 * This is our first chance to update IOSRAM 298 * with local copy of the skew, so update it. 299 */ 300 complained = update_tod_skew(0); 301 } 302 if (!complained && iosram_read(SBBC_TOD_KEY, 303 OFFSET(tod_buf, tod_domain_skew), 304 (char *)&domain_skew, sizeof (time_t))) { 305 complained++; 306 cmn_err(CE_WARN, "todsg_get(): read tod domain skew failed"); 307 } 308 309 if (complained) { 310 cmn_err(CE_WARN, "todsg_get(): turned off using tod"); 311 todsg_use_sc = 0; 312 goto return_hrestime; 313 } 314 315 /* 316 * If the SC gets rebooted, and we are using NTP, then we need 317 * to sync the IOSRAM to hrestime when the SC comes back. We 318 * can determine that either NTP slew (or date -a) was called if 319 * the global timedelta was non-zero at any point while the SC 320 * was away. If timedelta remains zero throughout, then the 321 * default action will be to sync hrestime to IOSRAM 322 */ 323 if (seconds != pre_seconds) { /* SC still alive */ 324 pre_seconds = seconds; 325 if (is_sc_down >= SC_DOWN_COUNT_THRESHOLD && adjust_sc_down) { 326 skew_adjust = hrestime.tv_sec - (seconds + domain_skew); 327 complained = update_tod_skew(0); 328 if (!complained && (iosram_read(SBBC_TOD_KEY, 329 OFFSET(tod_buf, tod_domain_skew), 330 (char *)&domain_skew, sizeof (time_t)))) { 331 complained++; 332 cmn_err(CE_WARN, "todsg_get(): " 333 "read tod domain skew failed"); 334 } 335 } 336 is_sc_down = 0; 337 adjust_sc_down = 0; 338 339 /* 340 * If complained then domain_skew is invalid. 341 * Hand back hrestime instead. 342 */ 343 if (!complained) { 344 timestruc_t ts = {0, 0}; 345 ts.tv_sec = seconds + domain_skew; 346 return (ts); 347 } else { 348 goto return_hrestime; 349 } 350 } 351 352 /* SC/TOD is down */ 353 is_sc_down++; 354 if (timedelta != 0) { 355 adjust_sc_down = 1; 356 } 357 358 return_hrestime: 359 /* 360 * We need to inform the tod_validate code to stop checking till 361 * SC come back up again. Note that we will return hrestime below 362 * which can be different that the previous TOD value we returned 363 */ 364 tod_fault_reset(); 365 return (hrestime); 366 } 367 368 static void 369 todsg_set(timestruc_t ts) 370 { 371 int complained = 0; 372 tod_iosram_t tod_buf; 373 time_t domain_skew; 374 time_t seconds; 375 time_t hwtod; 376 377 ASSERT(MUTEX_HELD(&tod_lock)); 378 379 if (!verify_sc_tod_version()) { 380 /* if we can't use SC */ 381 return; 382 } 383 /* 384 * If the SC is down just note the fact that we should 385 * have adjusted the hardware skew which caters for calls 386 * to stime(). (eg NTP step, as opposed to NTP skew) 387 */ 388 if (is_sc_down) { 389 adjust_sc_down = 1; 390 return; 391 } 392 /* 393 * reason to update i_am_alive here: 394 * To work around a generic Solaris bug that can 395 * cause tod_get() to be starved by too frequent 396 * calls to the stime() system call. 397 */ 398 if (watchdog_activated != 0 || watchdog_enable != 0) 399 complained = update_heartbeat(); 400 401 /* 402 * We are passed hrestime from clock.c so we need to read the 403 * IOSRAM for the hardware's idea of the time to see if we need 404 * to update the skew. 405 */ 406 if (!complained && (iosram_read(SBBC_TOD_KEY, 407 OFFSET(tod_buf, tod_get_value), 408 (char *)&seconds, sizeof (time_t)))) { 409 complained++; 410 cmn_err(CE_WARN, "todsg_set(): read 64-bit tod value failed"); 411 } 412 413 if (!complained && iosram_read(SBBC_TOD_KEY, 414 OFFSET(tod_buf, tod_domain_skew), 415 (char *)&domain_skew, sizeof (time_t))) { 416 complained++; 417 cmn_err(CE_WARN, "todsg_set(): read tod domain skew failed"); 418 } 419 420 /* 421 * Only update the skew if the time passed differs from 422 * what the hardware thinks & no errors talking to SC 423 */ 424 if (!complained && (ts.tv_sec != (seconds + domain_skew))) { 425 hwtod = seconds + domain_skew; 426 complained = update_tod_skew(ts.tv_sec - hwtod); 427 428 DCMNERR(CE_NOTE, "todsg_set(): set time %lX (%lX)%s", 429 ts.tv_sec, hwtod, complained ? " failed" : ""); 430 431 } 432 433 if (complained) { 434 cmn_err(CE_WARN, "todsg_set(): turned off using tod"); 435 todsg_use_sc = 0; 436 } 437 } 438 439 static uint32_t 440 todsg_set_watchdog_timer(uint32_t timeoutval) 441 { 442 tod_iosram_t tod_buf; 443 444 ASSERT(MUTEX_HELD(&tod_lock)); 445 446 if (!verify_sc_tod_version()) { 447 DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): " 448 "verify_sc_tod_version failed"); 449 return (0); 450 } 451 DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): " 452 "set watchdog timer value = %d", timeoutval); 453 454 if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period), 455 (char *)&timeoutval, sizeof (uint32_t))) { 456 DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): " 457 "write new timeout value failed"); 458 return (0); 459 } 460 watchdog_activated = 1; 461 return (timeoutval); 462 } 463 464 static uint32_t 465 todsg_clear_watchdog_timer(void) 466 { 467 tod_iosram_t tod_buf; 468 uint32_t r_timeout_period; 469 uint32_t w_timeout_period; 470 471 ASSERT(MUTEX_HELD(&tod_lock)); 472 473 if ((watchdog_activated == 0) || !verify_sc_tod_version()) { 474 DCMNERR(CE_NOTE, "todsg_set_watchdog_timer(): " 475 "either watchdog not activated or " 476 "verify_sc_tod_version failed"); 477 return (0); 478 } 479 if (iosram_read(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period), 480 (char *)&r_timeout_period, sizeof (uint32_t))) { 481 DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): " 482 "read timeout value failed"); 483 return (0); 484 } 485 DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): " 486 "clear watchdog timer (old value=%d)", r_timeout_period); 487 w_timeout_period = 0; 488 if (iosram_write(SBBC_TOD_KEY, OFFSET(tod_buf, tod_timeout_period), 489 (char *)&w_timeout_period, sizeof (uint32_t))) { 490 DCMNERR(CE_NOTE, "todsg_clear_watchdog_timer(): " 491 "write zero timeout value failed"); 492 return (0); 493 } 494 watchdog_activated = 0; 495 return (r_timeout_period); 496 } 497 498 /* 499 * Null function. 500 */ 501 /* ARGSUSED */ 502 static void 503 todsg_set_power_alarm(timestruc_t ts) 504 { 505 ASSERT(MUTEX_HELD(&tod_lock)); 506 } 507 508 /* 509 * Null function 510 */ 511 static void 512 todsg_clear_power_alarm() 513 { 514 ASSERT(MUTEX_HELD(&tod_lock)); 515 } 516 517 /* 518 * Get clock freq from the cpunode 519 */ 520 uint64_t 521 todsg_get_cpufrequency(void) 522 { 523 524 DCMNERR(CE_NOTE, "todsg_get_cpufrequency(): frequency=%ldMHz", 525 cpunodes[CPU->cpu_id].clock_freq/1000000); 526 527 return (cpunodes[CPU->cpu_id].clock_freq); 528 } 529