xref: /freebsd/lib/libsys/__vdso_gettimeofday.c (revision 1edb7116)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2012 Konstantin Belousov <kib@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/elf.h>
29 #include <sys/time.h>
30 #include <sys/vdso.h>
31 #include <errno.h>
32 #include <stdbool.h>
33 #include <strings.h>
34 #include <time.h>
35 #include <machine/atomic.h>
36 #include "libc_private.h"
37 
38 static int
39 tc_delta(const struct vdso_timehands *th, u_int *delta)
40 {
41 	int error;
42 	u_int tc;
43 
44 	error = __vdso_gettc(th, &tc);
45 	if (error == 0)
46 		*delta = (tc - th->th_offset_count) & th->th_counter_mask;
47 	return (error);
48 }
49 
50 /*
51  * Calculate the absolute or boot-relative time from the
52  * machine-specific fast timecounter and the published timehands
53  * structure read from the shared page.
54  *
55  * The lockless reading scheme is similar to the one used to read the
56  * in-kernel timehands, see sys/kern/kern_tc.c:binuptime().  This code
57  * is based on the kernel implementation.
58  */
59 static int
60 binuptime(struct bintime *bt, struct vdso_timekeep *tk, bool abs)
61 {
62 	struct vdso_timehands *th;
63 	uint32_t curr, gen;
64 	uint64_t scale, x;
65 	u_int delta, scale_bits;
66 	int error;
67 
68 	do {
69 		if (!tk->tk_enabled)
70 			return (ENOSYS);
71 
72 		curr = atomic_load_acq_32(&tk->tk_current);
73 		th = &tk->tk_th[curr];
74 		gen = atomic_load_acq_32(&th->th_gen);
75 		*bt = th->th_offset;
76 		error = tc_delta(th, &delta);
77 		if (error == EAGAIN)
78 			continue;
79 		if (error != 0)
80 			return (error);
81 		scale = th->th_scale;
82 #ifdef _LP64
83 		scale_bits = flsl(scale);
84 #else
85 		scale_bits = flsll(scale);
86 #endif
87 		if (__predict_false(scale_bits + fls(delta) > 63)) {
88 			x = (scale >> 32) * delta;
89 			scale &= 0xffffffff;
90 			bt->sec += x >> 32;
91 			bintime_addx(bt, x << 32);
92 		}
93 		bintime_addx(bt, scale * delta);
94 		if (abs)
95 			bintime_add(bt, &th->th_boottime);
96 
97 		/*
98 		 * Ensure that the load of th_offset is completed
99 		 * before the load of th_gen.
100 		 */
101 		atomic_thread_fence_acq();
102 	} while (curr != tk->tk_current || gen == 0 || gen != th->th_gen);
103 	return (0);
104 }
105 
106 static int
107 getnanouptime(struct bintime *bt, struct vdso_timekeep *tk)
108 {
109 	struct vdso_timehands *th;
110 	uint32_t curr, gen;
111 
112 	do {
113 		if (!tk->tk_enabled)
114 			return (ENOSYS);
115 
116 		curr = atomic_load_acq_32(&tk->tk_current);
117 		th = &tk->tk_th[curr];
118 		gen = atomic_load_acq_32(&th->th_gen);
119 		*bt = th->th_offset;
120 
121 		/*
122 		 * Ensure that the load of th_offset is completed
123 		 * before the load of th_gen.
124 		 */
125 		atomic_thread_fence_acq();
126 	} while (curr != tk->tk_current || gen == 0 || gen != th->th_gen);
127 	return (0);
128 }
129 
130 static struct vdso_timekeep *tk;
131 
132 #pragma weak __vdso_gettimeofday
133 int
134 __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
135 {
136 	struct bintime bt;
137 	int error;
138 
139 	if (tz != NULL)
140 		return (ENOSYS);
141 	if (tk == NULL) {
142 		error = __vdso_gettimekeep(&tk);
143 		if (error != 0 || tk == NULL)
144 			return (ENOSYS);
145 	}
146 	if (tk->tk_ver != VDSO_TK_VER_CURR)
147 		return (ENOSYS);
148 	error = binuptime(&bt, tk, true);
149 	if (error != 0)
150 		return (error);
151 	bintime2timeval(&bt, tv);
152 	return (0);
153 }
154 
155 #pragma weak __vdso_clock_gettime
156 int
157 __vdso_clock_gettime(clockid_t clock_id, struct timespec *ts)
158 {
159 	struct bintime bt;
160 	int error;
161 
162 	if (tk == NULL) {
163 		error = _elf_aux_info(AT_TIMEKEEP, &tk, sizeof(tk));
164 		if (error != 0 || tk == NULL)
165 			return (ENOSYS);
166 	}
167 	if (tk->tk_ver != VDSO_TK_VER_CURR)
168 		return (ENOSYS);
169 	switch (clock_id) {
170 	case CLOCK_REALTIME:
171 	case CLOCK_REALTIME_PRECISE:
172 	case CLOCK_REALTIME_FAST:
173 	case CLOCK_SECOND:
174 		error = binuptime(&bt, tk, true);
175 		break;
176 	case CLOCK_MONOTONIC:
177 	case CLOCK_MONOTONIC_PRECISE:
178 	case CLOCK_UPTIME:
179 	case CLOCK_UPTIME_PRECISE:
180 		error = binuptime(&bt, tk, false);
181 		break;
182 	case CLOCK_MONOTONIC_FAST:
183 	case CLOCK_UPTIME_FAST:
184 		error = getnanouptime(&bt, tk);
185 		break;
186 	default:
187 		error = ENOSYS;
188 		break;
189 	}
190 	if (error != 0)
191 		return (error);
192 	bintime2timespec(&bt, ts);
193 	if (clock_id == CLOCK_SECOND)
194 		ts->tv_nsec = 0;
195 	return (0);
196 }
197