123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Generic userspace implementations of gettimeofday() and similar.
- */
- #include <vdso/datapage.h>
- #include <vdso/helpers.h>
- #ifndef vdso_calc_delta
- /*
- * Default implementation which works for all sane clocksources. That
- * obviously excludes x86/TSC.
- */
- static __always_inline
- u64 vdso_calc_delta(u64 cycles, u64 last, u64 mask, u32 mult)
- {
- return ((cycles - last) & mask) * mult;
- }
- #endif
- #ifndef vdso_shift_ns
- static __always_inline u64 vdso_shift_ns(u64 ns, u32 shift)
- {
- return ns >> shift;
- }
- #endif
- #ifndef __arch_vdso_hres_capable
- static inline bool __arch_vdso_hres_capable(void)
- {
- return true;
- }
- #endif
- #ifndef vdso_clocksource_ok
- static inline bool vdso_clocksource_ok(const struct vdso_data *vd)
- {
- return vd->clock_mode != VDSO_CLOCKMODE_NONE;
- }
- #endif
- #ifndef vdso_cycles_ok
- static inline bool vdso_cycles_ok(u64 cycles)
- {
- return true;
- }
- #endif
- #ifdef CONFIG_TIME_NS
- static __always_inline int do_hres_timens(const struct vdso_data *vdns, clockid_t clk,
- struct __kernel_timespec *ts)
- {
- const struct vdso_data *vd;
- const struct timens_offset *offs = &vdns->offset[clk];
- const struct vdso_timestamp *vdso_ts;
- u64 cycles, last, ns;
- u32 seq;
- s64 sec;
- vd = vdns - (clk == CLOCK_MONOTONIC_RAW ? CS_RAW : CS_HRES_COARSE);
- vd = __arch_get_timens_vdso_data(vd);
- if (clk != CLOCK_MONOTONIC_RAW)
- vd = &vd[CS_HRES_COARSE];
- else
- vd = &vd[CS_RAW];
- vdso_ts = &vd->basetime[clk];
- do {
- seq = vdso_read_begin(vd);
- if (unlikely(!vdso_clocksource_ok(vd)))
- return -1;
- cycles = __arch_get_hw_counter(vd->clock_mode, vd);
- if (unlikely(!vdso_cycles_ok(cycles)))
- return -1;
- ns = vdso_ts->nsec;
- last = vd->cycle_last;
- ns += vdso_calc_delta(cycles, last, vd->mask, vd->mult);
- ns = vdso_shift_ns(ns, vd->shift);
- sec = vdso_ts->sec;
- } while (unlikely(vdso_read_retry(vd, seq)));
- /* Add the namespace offset */
- sec += offs->sec;
- ns += offs->nsec;
- /*
- * Do this outside the loop: a race inside the loop could result
- * in __iter_div_u64_rem() being extremely slow.
- */
- ts->tv_sec = sec + __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
- ts->tv_nsec = ns;
- return 0;
- }
- #else
- static __always_inline
- const struct vdso_data *__arch_get_timens_vdso_data(const struct vdso_data *vd)
- {
- return NULL;
- }
- static __always_inline int do_hres_timens(const struct vdso_data *vdns, clockid_t clk,
- struct __kernel_timespec *ts)
- {
- return -EINVAL;
- }
- #endif
- static __always_inline int do_hres(const struct vdso_data *vd, clockid_t clk,
- struct __kernel_timespec *ts)
- {
- const struct vdso_timestamp *vdso_ts = &vd->basetime[clk];
- u64 cycles, last, sec, ns;
- u32 seq;
- /* Allows to compile the high resolution parts out */
- if (!__arch_vdso_hres_capable())
- return -1;
- do {
- /*
- * Open coded to handle VDSO_CLOCKMODE_TIMENS. Time namespace
- * enabled tasks have a special VVAR page installed which
- * has vd->seq set to 1 and vd->clock_mode set to
- * VDSO_CLOCKMODE_TIMENS. For non time namespace affected tasks
- * this does not affect performance because if vd->seq is
- * odd, i.e. a concurrent update is in progress the extra
- * check for vd->clock_mode is just a few extra
- * instructions while spin waiting for vd->seq to become
- * even again.
- */
- while (unlikely((seq = READ_ONCE(vd->seq)) & 1)) {
- if (IS_ENABLED(CONFIG_TIME_NS) &&
- vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
- return do_hres_timens(vd, clk, ts);
- cpu_relax();
- }
- smp_rmb();
- if (unlikely(!vdso_clocksource_ok(vd)))
- return -1;
- cycles = __arch_get_hw_counter(vd->clock_mode, vd);
- if (unlikely(!vdso_cycles_ok(cycles)))
- return -1;
- ns = vdso_ts->nsec;
- last = vd->cycle_last;
- ns += vdso_calc_delta(cycles, last, vd->mask, vd->mult);
- ns = vdso_shift_ns(ns, vd->shift);
- sec = vdso_ts->sec;
- } while (unlikely(vdso_read_retry(vd, seq)));
- /*
- * Do this outside the loop: a race inside the loop could result
- * in __iter_div_u64_rem() being extremely slow.
- */
- ts->tv_sec = sec + __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
- ts->tv_nsec = ns;
- return 0;
- }
- #ifdef CONFIG_TIME_NS
- static __always_inline int do_coarse_timens(const struct vdso_data *vdns, clockid_t clk,
- struct __kernel_timespec *ts)
- {
- const struct vdso_data *vd = __arch_get_timens_vdso_data(vdns);
- const struct vdso_timestamp *vdso_ts = &vd->basetime[clk];
- const struct timens_offset *offs = &vdns->offset[clk];
- u64 nsec;
- s64 sec;
- s32 seq;
- do {
- seq = vdso_read_begin(vd);
- sec = vdso_ts->sec;
- nsec = vdso_ts->nsec;
- } while (unlikely(vdso_read_retry(vd, seq)));
- /* Add the namespace offset */
- sec += offs->sec;
- nsec += offs->nsec;
- /*
- * Do this outside the loop: a race inside the loop could result
- * in __iter_div_u64_rem() being extremely slow.
- */
- ts->tv_sec = sec + __iter_div_u64_rem(nsec, NSEC_PER_SEC, &nsec);
- ts->tv_nsec = nsec;
- return 0;
- }
- #else
- static __always_inline int do_coarse_timens(const struct vdso_data *vdns, clockid_t clk,
- struct __kernel_timespec *ts)
- {
- return -1;
- }
- #endif
- static __always_inline int do_coarse(const struct vdso_data *vd, clockid_t clk,
- struct __kernel_timespec *ts)
- {
- const struct vdso_timestamp *vdso_ts = &vd->basetime[clk];
- u32 seq;
- do {
- /*
- * Open coded to handle VDSO_CLOCK_TIMENS. See comment in
- * do_hres().
- */
- while ((seq = READ_ONCE(vd->seq)) & 1) {
- if (IS_ENABLED(CONFIG_TIME_NS) &&
- vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
- return do_coarse_timens(vd, clk, ts);
- cpu_relax();
- }
- smp_rmb();
- ts->tv_sec = vdso_ts->sec;
- ts->tv_nsec = vdso_ts->nsec;
- } while (unlikely(vdso_read_retry(vd, seq)));
- return 0;
- }
- static __always_inline int
- __cvdso_clock_gettime_common(const struct vdso_data *vd, clockid_t clock,
- struct __kernel_timespec *ts)
- {
- u32 msk;
- /* Check for negative values or invalid clocks */
- if (unlikely((u32) clock >= MAX_CLOCKS))
- return -1;
- /*
- * Convert the clockid to a bitmask and use it to check which
- * clocks are handled in the VDSO directly.
- */
- msk = 1U << clock;
- if (likely(msk & VDSO_HRES))
- vd = &vd[CS_HRES_COARSE];
- else if (msk & VDSO_COARSE)
- return do_coarse(&vd[CS_HRES_COARSE], clock, ts);
- else if (msk & VDSO_RAW)
- vd = &vd[CS_RAW];
- else
- return -1;
- return do_hres(vd, clock, ts);
- }
- static __maybe_unused int
- __cvdso_clock_gettime_data(const struct vdso_data *vd, clockid_t clock,
- struct __kernel_timespec *ts)
- {
- int ret = __cvdso_clock_gettime_common(vd, clock, ts);
- if (unlikely(ret))
- return clock_gettime_fallback(clock, ts);
- return 0;
- }
- static __maybe_unused int
- __cvdso_clock_gettime(clockid_t clock, struct __kernel_timespec *ts)
- {
- return __cvdso_clock_gettime_data(__arch_get_vdso_data(), clock, ts);
- }
- #ifdef BUILD_VDSO32
- static __maybe_unused int
- __cvdso_clock_gettime32_data(const struct vdso_data *vd, clockid_t clock,
- struct old_timespec32 *res)
- {
- struct __kernel_timespec ts;
- int ret;
- ret = __cvdso_clock_gettime_common(vd, clock, &ts);
- if (unlikely(ret))
- return clock_gettime32_fallback(clock, res);
- /* For ret == 0 */
- res->tv_sec = ts.tv_sec;
- res->tv_nsec = ts.tv_nsec;
- return ret;
- }
- static __maybe_unused int
- __cvdso_clock_gettime32(clockid_t clock, struct old_timespec32 *res)
- {
- return __cvdso_clock_gettime32_data(__arch_get_vdso_data(), clock, res);
- }
- #endif /* BUILD_VDSO32 */
- static __maybe_unused int
- __cvdso_gettimeofday_data(const struct vdso_data *vd,
- struct __kernel_old_timeval *tv, struct timezone *tz)
- {
- if (likely(tv != NULL)) {
- struct __kernel_timespec ts;
- if (do_hres(&vd[CS_HRES_COARSE], CLOCK_REALTIME, &ts))
- return gettimeofday_fallback(tv, tz);
- tv->tv_sec = ts.tv_sec;
- tv->tv_usec = (u32)ts.tv_nsec / NSEC_PER_USEC;
- }
- if (unlikely(tz != NULL)) {
- if (IS_ENABLED(CONFIG_TIME_NS) &&
- vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
- vd = __arch_get_timens_vdso_data(vd);
- tz->tz_minuteswest = vd[CS_HRES_COARSE].tz_minuteswest;
- tz->tz_dsttime = vd[CS_HRES_COARSE].tz_dsttime;
- }
- return 0;
- }
- static __maybe_unused int
- __cvdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz)
- {
- return __cvdso_gettimeofday_data(__arch_get_vdso_data(), tv, tz);
- }
- #ifdef VDSO_HAS_TIME
- static __maybe_unused __kernel_old_time_t
- __cvdso_time_data(const struct vdso_data *vd, __kernel_old_time_t *time)
- {
- __kernel_old_time_t t;
- if (IS_ENABLED(CONFIG_TIME_NS) &&
- vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
- vd = __arch_get_timens_vdso_data(vd);
- t = READ_ONCE(vd[CS_HRES_COARSE].basetime[CLOCK_REALTIME].sec);
- if (time)
- *time = t;
- return t;
- }
- static __maybe_unused __kernel_old_time_t __cvdso_time(__kernel_old_time_t *time)
- {
- return __cvdso_time_data(__arch_get_vdso_data(), time);
- }
- #endif /* VDSO_HAS_TIME */
- #ifdef VDSO_HAS_CLOCK_GETRES
- static __maybe_unused
- int __cvdso_clock_getres_common(const struct vdso_data *vd, clockid_t clock,
- struct __kernel_timespec *res)
- {
- u32 msk;
- u64 ns;
- /* Check for negative values or invalid clocks */
- if (unlikely((u32) clock >= MAX_CLOCKS))
- return -1;
- if (IS_ENABLED(CONFIG_TIME_NS) &&
- vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
- vd = __arch_get_timens_vdso_data(vd);
- /*
- * Convert the clockid to a bitmask and use it to check which
- * clocks are handled in the VDSO directly.
- */
- msk = 1U << clock;
- if (msk & (VDSO_HRES | VDSO_RAW)) {
- /*
- * Preserves the behaviour of posix_get_hrtimer_res().
- */
- ns = READ_ONCE(vd[CS_HRES_COARSE].hrtimer_res);
- } else if (msk & VDSO_COARSE) {
- /*
- * Preserves the behaviour of posix_get_coarse_res().
- */
- ns = LOW_RES_NSEC;
- } else {
- return -1;
- }
- if (likely(res)) {
- res->tv_sec = 0;
- res->tv_nsec = ns;
- }
- return 0;
- }
- static __maybe_unused
- int __cvdso_clock_getres_data(const struct vdso_data *vd, clockid_t clock,
- struct __kernel_timespec *res)
- {
- int ret = __cvdso_clock_getres_common(vd, clock, res);
- if (unlikely(ret))
- return clock_getres_fallback(clock, res);
- return 0;
- }
- static __maybe_unused
- int __cvdso_clock_getres(clockid_t clock, struct __kernel_timespec *res)
- {
- return __cvdso_clock_getres_data(__arch_get_vdso_data(), clock, res);
- }
- #ifdef BUILD_VDSO32
- static __maybe_unused int
- __cvdso_clock_getres_time32_data(const struct vdso_data *vd, clockid_t clock,
- struct old_timespec32 *res)
- {
- struct __kernel_timespec ts;
- int ret;
- ret = __cvdso_clock_getres_common(vd, clock, &ts);
- if (unlikely(ret))
- return clock_getres32_fallback(clock, res);
- if (likely(res)) {
- res->tv_sec = ts.tv_sec;
- res->tv_nsec = ts.tv_nsec;
- }
- return ret;
- }
- static __maybe_unused int
- __cvdso_clock_getres_time32(clockid_t clock, struct old_timespec32 *res)
- {
- return __cvdso_clock_getres_time32_data(__arch_get_vdso_data(),
- clock, res);
- }
- #endif /* BUILD_VDSO32 */
- #endif /* VDSO_HAS_CLOCK_GETRES */
|