xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_time.c (revision f808c858)
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 2005 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_cond_broadcast(&fts->fts_cv);
293 	(void) pthread_mutex_unlock(&fts->fts_lock);
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_cond_signal(&fts->fts_cv);
332 	(void) pthread_mutex_unlock(&fts->fts_lock);
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