diff options
Diffstat (limited to 'sys/compat/linux/linux_vdso_gtod.inc')
-rw-r--r-- | sys/compat/linux/linux_vdso_gtod.inc | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/sys/compat/linux/linux_vdso_gtod.inc b/sys/compat/linux/linux_vdso_gtod.inc new file mode 100644 index 000000000000..a90b7dc8efdf --- /dev/null +++ b/sys/compat/linux/linux_vdso_gtod.inc @@ -0,0 +1,337 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2012 Konstantin Belousov <kib@FreeBSD.org> + * Copyright (c) 2021 Dmitry Chagin <dchagin@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +static int +__vdso_native_to_linux_timespec(struct l_timespec *lts, + struct timespec *nts) +{ + +#ifdef COMPAT_LINUX32 + if (nts->tv_sec > INT_MAX || nts->tv_sec < INT_MIN) + return (LINUX_EOVERFLOW); +#endif + lts->tv_sec = nts->tv_sec; + lts->tv_nsec = nts->tv_nsec; + return (0); +} + +static int +__vdso_native_to_linux_timeval(l_timeval *ltv, + struct timeval *ntv) +{ + +#ifdef COMPAT_LINUX32 + if (ntv->tv_sec > INT_MAX || ntv->tv_sec < INT_MIN) + return (LINUX_EOVERFLOW); +#endif + ltv->tv_sec = ntv->tv_sec; + ltv->tv_usec = ntv->tv_usec; + return (0); +} + + +#if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) +static int +__vdso_native_to_linux_timespec64(struct l_timespec64 *lts, + struct timespec *nts) +{ + + lts->tv_sec = nts->tv_sec; + lts->tv_nsec = nts->tv_nsec; + return (0); +} +#endif + +static int +__vdso_linux_to_native_clockid(clockid_t *n, clockid_t l) +{ + + switch (l) { + case LINUX_CLOCK_REALTIME: + *n = CLOCK_REALTIME; + break; + case LINUX_CLOCK_MONOTONIC: + *n = CLOCK_MONOTONIC; + break; + case LINUX_CLOCK_REALTIME_COARSE: + *n = CLOCK_REALTIME_FAST; + break; + case LINUX_CLOCK_MONOTONIC_COARSE: + case LINUX_CLOCK_MONOTONIC_RAW: + *n = CLOCK_MONOTONIC_FAST; + break; + case LINUX_CLOCK_BOOTTIME: + *n = CLOCK_UPTIME; + break; + default: + return (LINUX_EINVAL); + } + return (0); +} + +/* + * The code below adapted from + * lib/libc/sys/__vdso_gettimeofday.c + */ + +static inline void +__vdso_gettimekeep(struct vdso_timekeep **tk) +{ + + *tk = (struct vdso_timekeep *)kern_timekeep_base; +} + +static int +tc_delta(const struct vdso_timehands *th, u_int *delta) +{ + int error; + u_int tc; + + error = __vdso_gettc(th, &tc); + if (error == 0) + *delta = (tc - th->th_offset_count) & th->th_counter_mask; + return (error); +} + +/* + * Calculate the absolute or boot-relative time from the + * machine-specific fast timecounter and the published timehands + * structure read from the shared page. + * + * The lockless reading scheme is similar to the one used to read the + * in-kernel timehands, see sys/kern/kern_tc.c:binuptime(). This code + * is based on the kernel implementation. + */ +static int +freebsd_binuptime(struct bintime *bt, struct vdso_timekeep *tk, bool abs) +{ + struct vdso_timehands *th; + uint32_t curr, gen; + uint64_t scale, x; + u_int delta, scale_bits; + int error; + + do { + if (!tk->tk_enabled) + return (ENOSYS); + + curr = atomic_load_acq_32(&tk->tk_current); + th = &tk->tk_th[curr]; + gen = atomic_load_acq_32(&th->th_gen); + *bt = th->th_offset; + error = tc_delta(th, &delta); + if (error == EAGAIN) + continue; + if (error != 0) + return (error); + scale = th->th_scale; +#ifdef _LP64 + scale_bits = ffsl(scale); +#else + scale_bits = ffsll(scale); +#endif + if (__predict_false(scale_bits + fls(delta) > 63)) { + x = (scale >> 32) * delta; + scale &= 0xffffffff; + bt->sec += x >> 32; + bintime_addx(bt, x << 32); + } + bintime_addx(bt, scale * delta); + if (abs) + bintime_add(bt, &th->th_boottime); + + /* + * Ensure that the load of th_offset is completed + * before the load of th_gen. + */ + atomic_thread_fence_acq(); + } while (curr != tk->tk_current || gen == 0 || gen != th->th_gen); + return (0); +} + +static int +freebsd_getnanouptime(struct bintime *bt, struct vdso_timekeep *tk) +{ + struct vdso_timehands *th; + uint32_t curr, gen; + + do { + if (!tk->tk_enabled) + return (ENOSYS); + + curr = atomic_load_acq_32(&tk->tk_current); + th = &tk->tk_th[curr]; + gen = atomic_load_acq_32(&th->th_gen); + *bt = th->th_offset; + + /* + * Ensure that the load of th_offset is completed + * before the load of th_gen. + */ + atomic_thread_fence_acq(); + } while (curr != tk->tk_current || gen == 0 || gen != th->th_gen); + return (0); +} + +static int +freebsd_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + struct vdso_timekeep *tk; + struct bintime bt; + int error; + + if (tz != NULL) + return (ENOSYS); + __vdso_gettimekeep(&tk); + if (tk == NULL) + return (ENOSYS); + if (tk->tk_ver != VDSO_TK_VER_CURR) + return (ENOSYS); + error = freebsd_binuptime(&bt, tk, true); + if (error == 0) + bintime2timeval(&bt, tv); + return (error); +} + +static int +freebsd_clock_gettime(clockid_t clock_id, struct timespec *ts) +{ + struct vdso_timekeep *tk; + struct bintime bt; + int error; + + __vdso_gettimekeep(&tk); + if (tk == NULL) + return (ENOSYS); + if (tk->tk_ver != VDSO_TK_VER_CURR) + return (ENOSYS); + switch (clock_id) { + case CLOCK_REALTIME: + case CLOCK_REALTIME_PRECISE: + case CLOCK_REALTIME_FAST: + error = freebsd_binuptime(&bt, tk, true); + break; + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_PRECISE: + case CLOCK_UPTIME: + case CLOCK_UPTIME_PRECISE: + error = freebsd_binuptime(&bt, tk, false); + break; + case CLOCK_MONOTONIC_FAST: + case CLOCK_UPTIME_FAST: + error = freebsd_getnanouptime(&bt, tk); + break; + default: + error = ENOSYS; + break; + } + if (error == 0) + bintime2timespec(&bt, ts); + return (error); +} + +/* + * Linux vDSO interfaces + * + */ +int +__vdso_clock_gettime(clockid_t clock_id, struct l_timespec *lts) +{ + struct timespec ts; + clockid_t which; + int error; + + error = __vdso_linux_to_native_clockid(&which, clock_id); + if (error != 0) + return (__vdso_clock_gettime_fallback(clock_id, lts)); + error = freebsd_clock_gettime(which, &ts); + if (error == 0) + return (-__vdso_native_to_linux_timespec(lts, &ts)); + else + return (__vdso_clock_gettime_fallback(clock_id, lts)); +} + +int +__vdso_gettimeofday(l_timeval *ltv, struct timezone *tz) +{ + struct timeval tv; + int error; + + error = freebsd_gettimeofday(&tv, tz); + if (error != 0) + return (__vdso_gettimeofday_fallback(ltv, tz)); + return (-__vdso_native_to_linux_timeval(ltv, &tv)); +} + +int +__vdso_clock_getres(clockid_t clock_id, struct l_timespec *lts) +{ + + return (__vdso_clock_getres_fallback(clock_id, lts)); +} + +#if defined(__i386__) || defined(COMPAT_LINUX32) +int +__vdso_clock_gettime64(clockid_t clock_id, struct l_timespec64 *lts) +{ + struct timespec ts; + clockid_t which; + int error; + + error = __vdso_linux_to_native_clockid(&which, clock_id); + if (error != 0) + return (__vdso_clock_gettime64_fallback(clock_id, lts)); + error = freebsd_clock_gettime(which, &ts); + if (error == 0) + return(-__vdso_native_to_linux_timespec64(lts, &ts)); + else + return(__vdso_clock_gettime64_fallback(clock_id, lts)); +} + +int clock_gettime64(clockid_t clock_id, struct l_timespec64 *lts) + __attribute__((weak, alias("__vdso_clock_gettime64"))); +#endif + +#if defined(__amd64__) && !defined(COMPAT_LINUX32) +int +__vdso_getcpu(uint32_t *cpu, uint32_t *node, void *cache) +{ + + return (__vdso_getcpu_fallback(cpu, node, cache)); +} +#endif + +#if defined(__i386__) || defined(__amd64__) +int +__vdso_time(long *tm) +{ + + return (__vdso_time_fallback(tm)); +} +#endif |