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 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #include <sys/fm/protocol.h> 28 #include <signal.h> 29 #include <limits.h> 30 #include <time.h> 31 32 #include <fmd_time.h> 33 #include <fmd_alloc.h> 34 #include <fmd_error.h> 35 #include <fmd_subr.h> 36 #include <fmd.h> 37 38 void 39 fmd_time_gettimeofday(struct timeval *tvp) 40 { 41 if (fmd.d_clockops->fto_gettimeofday(tvp, NULL) != 0) 42 fmd_panic("failed to read time-of-day clock"); 43 } 44 45 hrtime_t 46 fmd_time_gethrtime(void) 47 { 48 return (fmd.d_clockops->fto_gethrtime()); 49 } 50 51 void 52 fmd_time_addhrtime(hrtime_t delta) 53 { 54 fmd.d_clockops->fto_addhrtime(delta); 55 } 56 57 void 58 fmd_time_waithrtime(hrtime_t delta) 59 { 60 fmd.d_clockops->fto_waithrtime(delta); 61 } 62 63 void 64 fmd_time_waitcancel(pthread_t tid) 65 { 66 fmd.d_clockops->fto_waitcancel(tid); 67 } 68 69 /* 70 * To synchronize TOD with a gethrtime() source, we repeatedly sample TOD in 71 * between two calls to gethrtime(), which places a reasonably tight bound on 72 * the high-resolution time that matches the TOD value we sampled. We repeat 73 * this process several times and ultimately select the sample where the two 74 * values of gethrtime() were closest. We then assign the average of those 75 * two high-resolution times to be the gethrtime() associated with that TOD. 76 */ 77 void 78 fmd_time_sync(fmd_timeval_t *ftv, hrtime_t *hrp, uint_t samples) 79 { 80 const fmd_timeops_t *ftop = fmd.d_clockops; 81 hrtime_t hrtbase, hrtmin = INT64_MAX; 82 struct timeval todbase; 83 uint_t i; 84 85 for (i = 0; i < samples; i++) { 86 hrtime_t t0, t1, delta; 87 struct timeval tod; 88 89 t0 = ftop->fto_gethrtime(); 90 (void) ftop->fto_gettimeofday(&tod, NULL); 91 t1 = ftop->fto_gethrtime(); 92 delta = t1 - t0; 93 94 if (delta < hrtmin) { 95 hrtmin = delta; 96 hrtbase = t0 + delta / 2; 97 todbase = tod; 98 } 99 } 100 101 if (ftv != NULL) { 102 ftv->ftv_sec = todbase.tv_sec; 103 ftv->ftv_nsec = todbase.tv_usec * (NANOSEC / MICROSEC); 104 } 105 106 if (hrp != NULL) 107 *hrp = hrtbase; 108 } 109 110 /* 111 * Convert a high-resolution timestamp into 64-bit seconds and nanoseconds. 112 * For efficiency, the multiplication and division are expanded using the 113 * clever algorithm originally designed for the kernel in hrt2ts(). Refer to 114 * the comments in uts/common/os/timers.c for an explanation of how it works. 115 */ 116 static void 117 fmd_time_hrt2ftv(hrtime_t hrt, fmd_timeval_t *ftv) 118 { 119 uint32_t sec, nsec, tmp; 120 121 tmp = (uint32_t)(hrt >> 30); 122 sec = tmp - (tmp >> 2); 123 sec = tmp - (sec >> 5); 124 sec = tmp + (sec >> 1); 125 sec = tmp - (sec >> 6) + 7; 126 sec = tmp - (sec >> 3); 127 sec = tmp + (sec >> 1); 128 sec = tmp + (sec >> 3); 129 sec = tmp + (sec >> 4); 130 tmp = (sec << 7) - sec - sec - sec; 131 tmp = (tmp << 7) - tmp - tmp - tmp; 132 tmp = (tmp << 7) - tmp - tmp - tmp; 133 nsec = (uint32_t)hrt - (tmp << 9); 134 135 while (nsec >= NANOSEC) { 136 nsec -= NANOSEC; 137 sec++; 138 } 139 140 ftv->ftv_sec = sec; 141 ftv->ftv_nsec = nsec; 142 } 143 144 /* 145 * Convert a high-resolution time from gethrtime() to a TOD (fmd_timeval_t). 146 * We convert 'tod_base' to nanoseconds, adjust it based on the difference 147 * between the corresponding 'hrt_base' and the event high-res time 'hrt', 148 * and then repack the result into ftv_sec and ftv_nsec for our output. 149 */ 150 void 151 fmd_time_hrt2tod(hrtime_t hrt_base, const fmd_timeval_t *tod_base, 152 hrtime_t hrt, fmd_timeval_t *ftv) 153 { 154 fmd_time_hrt2ftv(tod_base->ftv_sec * NANOSEC + 155 tod_base->ftv_nsec + (hrt - hrt_base), ftv); 156 } 157 158 /* 159 * Convert a TOD (fmd_timeval_t) to a high-resolution time from gethrtime(). 160 * Note that since TOD occurred in the past, the resulting value may be a 161 * negative number according the current gethrtime() clock value. 162 */ 163 void 164 fmd_time_tod2hrt(hrtime_t hrt_base, const fmd_timeval_t *tod_base, 165 const fmd_timeval_t *ftv, hrtime_t *hrtp) 166 { 167 hrtime_t tod_hrt = tod_base->ftv_sec * NANOSEC + tod_base->ftv_nsec; 168 hrtime_t ftv_hrt = ftv->ftv_sec * NANOSEC + ftv->ftv_nsec; 169 170 *hrtp = hrt_base - (tod_hrt - ftv_hrt); 171 } 172 173 /* 174 * Adjust a high-resolution time based on the low bits of time stored in ENA. 175 * The assumption here in that ENA won't wrap between the time it is computed 176 * and the time the error is queued (when we capture a full 64-bits of hrtime). 177 * We extract the relevant ENA time bits as 't0' and subtract the difference 178 * between these bits and the corresponding low bits of 'hrt' from 'hrt'. 179 * 180 * Under xVM dom0, the UE ereport is prepared after panic, therefore 181 * the full 64-bit hrtime of 't0' can be bigger than 'hrt'. In such case, 182 * we should just return 'hrt'. 183 * 184 * 't0' contains only the low bits of 64bit hrtime. It is tricky to tell 185 * whether 'hrt' or 't0' happened first. We assume there should be short 186 * period between 'hrt' and 't0', therefore to check which one came first, we 187 * test their subtraction against the highest bit of mask, if the bit is not 188 * set, then 't0' is earlier. This is equivalent to 189 * ((hrt - t0) & mask) < ((mask + 1) / 2) 190 */ 191 hrtime_t 192 fmd_time_ena2hrt(hrtime_t hrt, uint64_t ena) 193 { 194 hrtime_t t0, mask; 195 196 switch (ENA_FORMAT(ena)) { 197 case FM_ENA_FMT1: 198 t0 = (ena & ENA_FMT1_TIME_MASK) >> ENA_FMT1_TIME_SHFT; 199 mask = ENA_FMT1_TIME_MASK >> ENA_FMT1_TIME_SHFT; 200 if (((hrt - t0) & ((mask + 1) >> 1)) == 0) 201 hrt -= (hrt - t0) & mask; 202 break; 203 case FM_ENA_FMT2: 204 t0 = (ena & ENA_FMT2_TIME_MASK) >> ENA_FMT2_TIME_SHFT; 205 mask = ENA_FMT2_TIME_MASK >> ENA_FMT2_TIME_SHFT; 206 if (((hrt - t0) & ((mask + 1) >> 1)) == 0) 207 hrt -= (hrt - t0) & mask; 208 break; 209 } 210 211 return (hrt); 212 } 213 214 /* 215 * To implement a simulated clock, we keep track of an hrtime_t value which 216 * starts at zero and is incremented only by fmd_time_addhrtime() (i.e. when 217 * the driver of the simulation requests that the clock advance). We sample 218 * the native time-of-day clock once at the start of the simulation and then 219 * return subsequent time-of-day values by adjusting TOD using the hrtime_t 220 * clock setting. Simulated nanosleep (fmd_time_waithrtime() entry point) is 221 * implemented by waiting on fts->fts_cv for the hrtime_t to increment. 222 */ 223 static void * 224 fmd_simulator_init(void) 225 { 226 fmd_timesim_t *fts = fmd_alloc(sizeof (fmd_timesim_t), FMD_SLEEP); 227 struct timeval tv; 228 229 (void) pthread_mutex_init(&fts->fts_lock, NULL); 230 (void) pthread_cond_init(&fts->fts_cv, NULL); 231 (void) gettimeofday(&tv, NULL); 232 233 fts->fts_tod = (hrtime_t)tv.tv_sec * NANOSEC + 234 (hrtime_t)tv.tv_usec * (NANOSEC / MICROSEC); 235 236 fts->fts_hrt = 0; 237 fts->fts_cancel = 0; 238 239 fmd_dprintf(FMD_DBG_TMR, "simulator tod base tv_sec=%lx hrt=%llx\n", 240 tv.tv_sec, fts->fts_tod); 241 242 return (fts); 243 } 244 245 static void 246 fmd_simulator_fini(void *fts) 247 { 248 if (fts != NULL) 249 fmd_free(fts, sizeof (fmd_timesim_t)); 250 } 251 252 /*ARGSUSED*/ 253 static int 254 fmd_simulator_tod(struct timeval *tvp, void *tzp) 255 { 256 fmd_timesim_t *fts = fmd.d_clockptr; 257 hrtime_t tod, hrt, sec, rem; 258 259 (void) pthread_mutex_lock(&fts->fts_lock); 260 261 tod = fts->fts_tod; 262 hrt = fts->fts_hrt; 263 264 (void) pthread_mutex_unlock(&fts->fts_lock); 265 266 sec = tod / NANOSEC + hrt / NANOSEC; 267 rem = tod % NANOSEC + hrt % NANOSEC; 268 269 tvp->tv_sec = sec + rem / NANOSEC; 270 tvp->tv_usec = (rem % NANOSEC) / (NANOSEC / MICROSEC); 271 272 return (0); 273 } 274 275 static hrtime_t 276 fmd_simulator_hrt(void) 277 { 278 fmd_timesim_t *fts = fmd.d_clockptr; 279 hrtime_t hrt; 280 281 (void) pthread_mutex_lock(&fts->fts_lock); 282 hrt = fts->fts_hrt; 283 (void) pthread_mutex_unlock(&fts->fts_lock); 284 285 return (hrt); 286 } 287 288 static void 289 fmd_simulator_add(hrtime_t delta) 290 { 291 fmd_timesim_t *fts = fmd.d_clockptr; 292 293 (void) pthread_mutex_lock(&fts->fts_lock); 294 295 if (fts->fts_hrt + delta < fts->fts_hrt) 296 fts->fts_hrt = INT64_MAX; /* do not increment past apocalypse */ 297 else 298 fts->fts_hrt += delta; 299 300 TRACE((FMD_DBG_TMR, "hrt clock set %llx", fts->fts_hrt)); 301 fmd_dprintf(FMD_DBG_TMR, "hrt clock set %llx\n", fts->fts_hrt); 302 303 (void) pthread_cond_broadcast(&fts->fts_cv); 304 (void) pthread_mutex_unlock(&fts->fts_lock); 305 } 306 307 static void 308 fmd_simulator_wait(hrtime_t delta) 309 { 310 fmd_timesim_t *fts = fmd.d_clockptr; 311 uint64_t hrt; 312 313 (void) pthread_mutex_lock(&fts->fts_lock); 314 315 /* 316 * If the delta causes time to wrap because we've reached the simulated 317 * apocalypse, then wait forever. We make 'hrt' unsigned so that the 318 * while-loop comparison fts_hrt < UINT64_MAX will always return true. 319 */ 320 if (fts->fts_hrt + delta < fts->fts_hrt) 321 hrt = UINT64_MAX; 322 else 323 hrt = fts->fts_hrt + delta; 324 325 while (fts->fts_hrt < hrt && fts->fts_cancel == 0) 326 (void) pthread_cond_wait(&fts->fts_cv, &fts->fts_lock); 327 328 if (fts->fts_cancel != 0) 329 fts->fts_cancel--; /* cancel has been processed */ 330 331 (void) pthread_mutex_unlock(&fts->fts_lock); 332 } 333 334 /*ARGSUSED*/ 335 static void 336 fmd_simulator_cancel(pthread_t tid) 337 { 338 fmd_timesim_t *fts = fmd.d_clockptr; 339 340 (void) pthread_mutex_lock(&fts->fts_lock); 341 fts->fts_cancel++; 342 (void) pthread_cond_signal(&fts->fts_cv); 343 (void) pthread_mutex_unlock(&fts->fts_lock); 344 } 345 346 /* 347 * Native time is implemented by calls to gethrtime() and gettimeofday(), which 348 * are stored directly in the native time ops-vector defined below. To wait on 349 * the native clock we use nanosleep(), which we can abort using a signal. The 350 * implementation assumes that callers will have a SIGALRM handler installed. 351 */ 352 static void 353 fmd_native_wait(hrtime_t delta) 354 { 355 timespec_t tv; 356 357 tv.tv_sec = delta / NANOSEC; 358 tv.tv_nsec = delta % NANOSEC; 359 360 (void) nanosleep(&tv, NULL); 361 } 362 363 static void 364 fmd_native_cancel(pthread_t tid) 365 { 366 (void) pthread_kill(tid, SIGALRM); 367 } 368 369 static void 370 fmd_time_vnop(void) 371 { 372 } 373 374 static void * 375 fmd_time_nop(void) 376 { 377 return (NULL); 378 } 379 380 const fmd_timeops_t fmd_timeops_native = { 381 (void *(*)())fmd_time_nop, /* fto_init */ 382 (void (*)())fmd_time_vnop, /* fto_fini */ 383 gettimeofday, /* fto_gettimeofday */ 384 gethrtime, /* fto_gethrtime */ 385 (void (*)())fmd_time_vnop, /* fto_addhrtime */ 386 fmd_native_wait, /* fto_waithrtime */ 387 fmd_native_cancel, /* fto_waitcancel */ 388 }; 389 390 const fmd_timeops_t fmd_timeops_simulated = { 391 fmd_simulator_init, /* fto_init */ 392 fmd_simulator_fini, /* fto_fini */ 393 fmd_simulator_tod, /* fto_gettimeofday */ 394 fmd_simulator_hrt, /* fto_gethrtime */ 395 fmd_simulator_add, /* fto_addhrtime */ 396 fmd_simulator_wait, /* fto_waithrtime */ 397 fmd_simulator_cancel, /* fto_waitcancel */ 398 }; 399