]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libc/x86/sys/__vdso_gettc.c
Update to ELF Tool Chain r3668
[FreeBSD/FreeBSD.git] / lib / libc / x86 / sys / __vdso_gettc.c
1 /*-
2  * Copyright (c) 2012 Konstantin Belousov <kib@FreeBSD.org>
3  * Copyright (c) 2016, 2017 The FreeBSD Foundation
4  * All rights reserved.
5  *
6  * Portions of this software were developed by Konstantin Belousov
7  * under sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33
34 #include <sys/param.h>
35 #include "namespace.h"
36 #include <sys/capsicum.h>
37 #include <sys/elf.h>
38 #include <sys/fcntl.h>
39 #include <sys/mman.h>
40 #include <sys/time.h>
41 #include <sys/vdso.h>
42 #include <errno.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include "un-namespace.h"
46 #include <machine/atomic.h>
47 #include <machine/cpufunc.h>
48 #include <machine/specialreg.h>
49 #include <dev/acpica/acpi_hpet.h>
50 #ifdef WANT_HYPERV
51 #include <dev/hyperv/hyperv.h>
52 #endif
53 #include "libc_private.h"
54
55 static enum LMB {
56         LMB_UNKNOWN,
57         LMB_NONE,
58         LMB_MFENCE,
59         LMB_LFENCE
60 } lfence_works = LMB_UNKNOWN;
61
62 static void
63 cpuidp(u_int leaf, u_int p[4])
64 {
65
66         __asm __volatile(
67 #if defined(__i386__)
68             "   pushl   %%ebx\n"
69 #endif
70             "   cpuid\n"
71 #if defined(__i386__)
72             "   movl    %%ebx,%1\n"
73             "   popl    %%ebx"
74 #endif
75             : "=a" (p[0]),
76 #if defined(__i386__)
77             "=r" (p[1]),
78 #elif defined(__amd64__)
79             "=b" (p[1]),
80 #else
81 #error "Arch"
82 #endif
83             "=c" (p[2]), "=d" (p[3])
84             :  "0" (leaf));
85 }
86
87 static enum LMB
88 select_lmb(void)
89 {
90         u_int p[4];
91         static const char intel_id[] = "GenuntelineI";
92
93         cpuidp(0, p);
94         return (memcmp(p + 1, intel_id, sizeof(intel_id) - 1) == 0 ?
95             LMB_LFENCE : LMB_MFENCE);
96 }
97
98 static void
99 init_fence(void)
100 {
101 #if defined(__i386__)
102         u_int cpuid_supported, p[4];
103
104         lfence_works = LMB_NONE;
105         __asm __volatile(
106             "   pushfl\n"
107             "   popl    %%eax\n"
108             "   movl    %%eax,%%ecx\n"
109             "   xorl    $0x200000,%%eax\n"
110             "   pushl   %%eax\n"
111             "   popfl\n"
112             "   pushfl\n"
113             "   popl    %%eax\n"
114             "   xorl    %%eax,%%ecx\n"
115             "   je      1f\n"
116             "   movl    $1,%0\n"
117             "   jmp     2f\n"
118             "1: movl    $0,%0\n"
119             "2:\n"
120             : "=r" (cpuid_supported) : : "eax", "ecx", "cc");
121         if (cpuid_supported) {
122                 cpuidp(0x1, p);
123                 if ((p[3] & CPUID_SSE2) != 0)
124                         lfence_works = select_lmb();
125         }
126 #elif defined(__amd64__)
127         lfence_works = select_lmb();
128 #else
129 #error "Arch"
130 #endif
131 }
132
133 static void
134 rdtsc_mb(void)
135 {
136
137 again:
138         if (__predict_true(lfence_works == LMB_LFENCE)) {
139                 lfence();
140                 return;
141         } else if (lfence_works == LMB_MFENCE) {
142                 mfence();
143                 return;
144         } else if (lfence_works == LMB_NONE) {
145                 return;
146         }
147         init_fence();
148         goto again;
149 }
150
151 static u_int
152 __vdso_gettc_rdtsc_low(const struct vdso_timehands *th)
153 {
154         u_int rv;
155
156         rdtsc_mb();
157         __asm __volatile("rdtsc; shrd %%cl, %%edx, %0"
158             : "=a" (rv) : "c" (th->th_x86_shift) : "edx");
159         return (rv);
160 }
161
162 static u_int
163 __vdso_rdtsc32(void)
164 {
165
166         rdtsc_mb();
167         return (rdtsc32());
168 }
169
170 #define HPET_DEV_MAP_MAX        10
171 static volatile char *hpet_dev_map[HPET_DEV_MAP_MAX];
172
173 static void
174 __vdso_init_hpet(uint32_t u)
175 {
176         static const char devprefix[] = "/dev/hpet";
177         char devname[64], *c, *c1, t;
178         volatile char *new_map, *old_map;
179         unsigned int mode;
180         uint32_t u1;
181         int fd;
182
183         c1 = c = stpcpy(devname, devprefix);
184         u1 = u;
185         do {
186                 *c++ = u1 % 10 + '0';
187                 u1 /= 10;
188         } while (u1 != 0);
189         *c = '\0';
190         for (c--; c1 != c; c1++, c--) {
191                 t = *c1;
192                 *c1 = *c;
193                 *c = t;
194         }
195
196         old_map = hpet_dev_map[u];
197         if (old_map != NULL)
198                 return;
199
200         /*
201          * Explicitely check for the capability mode to avoid
202          * triggering trap_enocap on the device open by absolute path.
203          */
204         if ((cap_getmode(&mode) == 0 && mode != 0) ||
205             (fd = _open(devname, O_RDONLY)) == -1) {
206                 /* Prevent the caller from re-entering. */
207                 atomic_cmpset_rel_ptr((volatile uintptr_t *)&hpet_dev_map[u],
208                     (uintptr_t)old_map, (uintptr_t)MAP_FAILED);
209                 return;
210         }
211
212         new_map = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, 0);
213         _close(fd);
214         if (atomic_cmpset_rel_ptr((volatile uintptr_t *)&hpet_dev_map[u],
215             (uintptr_t)old_map, (uintptr_t)new_map) == 0 &&
216             new_map != MAP_FAILED)
217                 munmap((void *)new_map, PAGE_SIZE);
218 }
219
220 #ifdef WANT_HYPERV
221
222 #define HYPERV_REFTSC_DEVPATH   "/dev/" HYPERV_REFTSC_DEVNAME
223
224 /*
225  * NOTE:
226  * We use 'NULL' for this variable to indicate that initialization
227  * is required.  And if this variable is 'MAP_FAILED', then Hyper-V
228  * reference TSC can not be used, e.g. in misconfigured jail.
229  */
230 static struct hyperv_reftsc *hyperv_ref_tsc;
231
232 static void
233 __vdso_init_hyperv_tsc(void)
234 {
235         int fd;
236         unsigned int mode;
237
238         if (cap_getmode(&mode) == 0 && mode != 0)
239                 goto fail;
240
241         fd = _open(HYPERV_REFTSC_DEVPATH, O_RDONLY);
242         if (fd < 0)
243                 goto fail;
244         hyperv_ref_tsc = mmap(NULL, sizeof(*hyperv_ref_tsc), PROT_READ,
245             MAP_SHARED, fd, 0);
246         _close(fd);
247
248         return;
249 fail:
250         /* Prevent the caller from re-entering. */
251         hyperv_ref_tsc = MAP_FAILED;
252 }
253
254 static int
255 __vdso_hyperv_tsc(struct hyperv_reftsc *tsc_ref, u_int *tc)
256 {
257         uint64_t disc, ret, tsc, scale;
258         uint32_t seq;
259         int64_t ofs;
260
261         while ((seq = atomic_load_acq_int(&tsc_ref->tsc_seq)) != 0) {
262                 scale = tsc_ref->tsc_scale;
263                 ofs = tsc_ref->tsc_ofs;
264
265                 rdtsc_mb();
266                 tsc = rdtsc();
267
268                 /* ret = ((tsc * scale) >> 64) + ofs */
269                 __asm__ __volatile__ ("mulq %3" :
270                     "=d" (ret), "=a" (disc) :
271                     "a" (tsc), "r" (scale));
272                 ret += ofs;
273
274                 atomic_thread_fence_acq();
275                 if (tsc_ref->tsc_seq == seq) {
276                         *tc = ret;
277                         return (0);
278                 }
279
280                 /* Sequence changed; re-sync. */
281         }
282         return (ENOSYS);
283 }
284
285 #endif  /* WANT_HYPERV */
286
287 #pragma weak __vdso_gettc
288 int
289 __vdso_gettc(const struct vdso_timehands *th, u_int *tc)
290 {
291         volatile char *map;
292         uint32_t idx;
293
294         switch (th->th_algo) {
295         case VDSO_TH_ALGO_X86_TSC:
296                 *tc = th->th_x86_shift > 0 ? __vdso_gettc_rdtsc_low(th) :
297                     __vdso_rdtsc32();
298                 return (0);
299         case VDSO_TH_ALGO_X86_HPET:
300                 idx = th->th_x86_hpet_idx;
301                 if (idx >= HPET_DEV_MAP_MAX)
302                         return (ENOSYS);
303                 map = (volatile char *)atomic_load_acq_ptr(
304                     (volatile uintptr_t *)&hpet_dev_map[idx]);
305                 if (map == NULL) {
306                         __vdso_init_hpet(idx);
307                         map = (volatile char *)atomic_load_acq_ptr(
308                             (volatile uintptr_t *)&hpet_dev_map[idx]);
309                 }
310                 if (map == MAP_FAILED)
311                         return (ENOSYS);
312                 *tc = *(volatile uint32_t *)(map + HPET_MAIN_COUNTER);
313                 return (0);
314 #ifdef WANT_HYPERV
315         case VDSO_TH_ALGO_X86_HVTSC:
316                 if (hyperv_ref_tsc == NULL)
317                         __vdso_init_hyperv_tsc();
318                 if (hyperv_ref_tsc == MAP_FAILED)
319                         return (ENOSYS);
320                 return (__vdso_hyperv_tsc(hyperv_ref_tsc, tc));
321 #endif
322         default:
323                 return (ENOSYS);
324         }
325 }
326
327 #pragma weak __vdso_gettimekeep
328 int
329 __vdso_gettimekeep(struct vdso_timekeep **tk)
330 {
331
332         return (_elf_aux_info(AT_TIMEKEEP, tk, sizeof(*tk)));
333 }