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
fmd_time_gettimeofday(struct timeval * tvp)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
fmd_time_gethrtime(void)46 fmd_time_gethrtime(void)
47 {
48 return (fmd.d_clockops->fto_gethrtime());
49 }
50
51 void
fmd_time_addhrtime(hrtime_t delta)52 fmd_time_addhrtime(hrtime_t delta)
53 {
54 fmd.d_clockops->fto_addhrtime(delta);
55 }
56
57 void
fmd_time_waithrtime(hrtime_t delta)58 fmd_time_waithrtime(hrtime_t delta)
59 {
60 fmd.d_clockops->fto_waithrtime(delta);
61 }
62
63 void
fmd_time_waitcancel(pthread_t tid)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
fmd_time_sync(fmd_timeval_t * ftv,hrtime_t * hrp,uint_t samples)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
fmd_time_hrt2ftv(hrtime_t hrt,fmd_timeval_t * ftv)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
fmd_time_hrt2tod(hrtime_t hrt_base,const fmd_timeval_t * tod_base,hrtime_t hrt,fmd_timeval_t * ftv)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
fmd_time_tod2hrt(hrtime_t hrt_base,const fmd_timeval_t * tod_base,const fmd_timeval_t * ftv,hrtime_t * hrtp)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
fmd_time_ena2hrt(hrtime_t hrt,uint64_t ena)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 *
fmd_simulator_init(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
fmd_simulator_fini(void * fts)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
fmd_simulator_tod(struct timeval * tvp,void * tzp)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
fmd_simulator_hrt(void)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
fmd_simulator_add(hrtime_t delta)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
fmd_simulator_wait(hrtime_t delta)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
fmd_simulator_cancel(pthread_t tid)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
fmd_native_wait(hrtime_t delta)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
fmd_native_cancel(pthread_t tid)364 fmd_native_cancel(pthread_t tid)
365 {
366 (void) pthread_kill(tid, SIGALRM);
367 }
368
369 static void
fmd_time_vnop(void)370 fmd_time_vnop(void)
371 {
372 }
373
374 static void *
fmd_time_nop(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