xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_time.c (revision 4eaa471005973e11a6110b69fe990530b3b95a38)
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_nop(void)
371 {
372 	return (NULL);
373 }
374 
375 const fmd_timeops_t fmd_timeops_native = {
376 	(void *(*)())fmd_time_nop,	/* fto_init */
377 	(void (*)())fmd_time_nop,	/* fto_fini */
378 	gettimeofday,			/* fto_gettimeofday */
379 	gethrtime,			/* fto_gethrtime */
380 	(void (*)())fmd_time_nop,	/* fto_addhrtime */
381 	fmd_native_wait,		/* fto_waithrtime */
382 	fmd_native_cancel,		/* fto_waitcancel */
383 };
384 
385 const fmd_timeops_t fmd_timeops_simulated = {
386 	fmd_simulator_init,		/* fto_init */
387 	fmd_simulator_fini,		/* fto_fini */
388 	fmd_simulator_tod,		/* fto_gettimeofday */
389 	fmd_simulator_hrt,		/* fto_gethrtime */
390 	fmd_simulator_add,		/* fto_addhrtime */
391 	fmd_simulator_wait,		/* fto_waithrtime */
392 	fmd_simulator_cancel,		/* fto_waitcancel */
393 };
394