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