diff options
Diffstat (limited to 'arch/nds32/kernel/vdso/gettimeofday.c')
-rw-r--r-- | arch/nds32/kernel/vdso/gettimeofday.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/arch/nds32/kernel/vdso/gettimeofday.c b/arch/nds32/kernel/vdso/gettimeofday.c new file mode 100644 index 000000000..9ec03cf0e --- /dev/null +++ b/arch/nds32/kernel/vdso/gettimeofday.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2005-2017 Andes Technology Corporation + +#include <linux/compiler.h> +#include <linux/hrtimer.h> +#include <linux/time.h> +#include <asm/io.h> +#include <asm/barrier.h> +#include <asm/bug.h> +#include <asm/page.h> +#include <asm/unistd.h> +#include <asm/vdso_datapage.h> +#include <asm/vdso_timer_info.h> +#include <asm/asm-offsets.h> + +#define X(x) #x +#define Y(x) X(x) + +extern struct vdso_data *__get_datapage(void); +extern struct vdso_data *__get_timerpage(void); + +static notrace unsigned int __vdso_read_begin(const struct vdso_data *vdata) +{ + u32 seq; +repeat: + seq = READ_ONCE(vdata->seq_count); + if (seq & 1) { + cpu_relax(); + goto repeat; + } + return seq; +} + +static notrace unsigned int vdso_read_begin(const struct vdso_data *vdata) +{ + unsigned int seq; + + seq = __vdso_read_begin(vdata); + + smp_rmb(); /* Pairs with smp_wmb in vdso_write_end */ + return seq; +} + +static notrace int vdso_read_retry(const struct vdso_data *vdata, u32 start) +{ + smp_rmb(); /* Pairs with smp_wmb in vdso_write_begin */ + return vdata->seq_count != start; +} + +static notrace long clock_gettime_fallback(clockid_t _clkid, + struct __kernel_old_timespec *_ts) +{ + register struct __kernel_old_timespec *ts asm("$r1") = _ts; + register clockid_t clkid asm("$r0") = _clkid; + register long ret asm("$r0"); + + asm volatile ("movi $r15, %3\n" + "syscall 0x0\n" + :"=r" (ret) + :"r"(clkid), "r"(ts), "i"(__NR_clock_gettime) + :"$r15", "memory"); + + return ret; +} + +static notrace int do_realtime_coarse(struct __kernel_old_timespec *ts, + struct vdso_data *vdata) +{ + u32 seq; + + do { + seq = vdso_read_begin(vdata); + + ts->tv_sec = vdata->xtime_coarse_sec; + ts->tv_nsec = vdata->xtime_coarse_nsec; + + } while (vdso_read_retry(vdata, seq)); + return 0; +} + +static notrace int do_monotonic_coarse(struct __kernel_old_timespec *ts, + struct vdso_data *vdata) +{ + u32 seq; + u64 ns; + + do { + seq = vdso_read_begin(vdata); + + ts->tv_sec = vdata->xtime_coarse_sec + vdata->wtm_clock_sec; + ns = vdata->xtime_coarse_nsec + vdata->wtm_clock_nsec; + + } while (vdso_read_retry(vdata, seq)); + + ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns); + ts->tv_nsec = ns; + + return 0; +} + +static notrace inline u64 vgetsns(struct vdso_data *vdso) +{ + u32 cycle_now; + u32 cycle_delta; + u32 *timer_cycle_base; + + timer_cycle_base = + (u32 *) ((char *)__get_timerpage() + vdso->cycle_count_offset); + cycle_now = readl_relaxed(timer_cycle_base); + if (true == vdso->cycle_count_down) + cycle_now = ~(*timer_cycle_base); + cycle_delta = cycle_now - (u32) vdso->cs_cycle_last; + return ((u64) cycle_delta & vdso->cs_mask) * vdso->cs_mult; +} + +static notrace int do_realtime(struct __kernel_old_timespec *ts, struct vdso_data *vdata) +{ + unsigned count; + u64 ns; + do { + count = vdso_read_begin(vdata); + ts->tv_sec = vdata->xtime_clock_sec; + ns = vdata->xtime_clock_nsec; + ns += vgetsns(vdata); + ns >>= vdata->cs_shift; + } while (vdso_read_retry(vdata, count)); + + ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns); + ts->tv_nsec = ns; + + return 0; +} + +static notrace int do_monotonic(struct __kernel_old_timespec *ts, struct vdso_data *vdata) +{ + u64 ns; + u32 seq; + + do { + seq = vdso_read_begin(vdata); + + ts->tv_sec = vdata->xtime_clock_sec; + ns = vdata->xtime_clock_nsec; + ns += vgetsns(vdata); + ns >>= vdata->cs_shift; + + ts->tv_sec += vdata->wtm_clock_sec; + ns += vdata->wtm_clock_nsec; + + } while (vdso_read_retry(vdata, seq)); + + ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns); + ts->tv_nsec = ns; + + return 0; +} + +notrace int __vdso_clock_gettime(clockid_t clkid, struct __kernel_old_timespec *ts) +{ + struct vdso_data *vdata; + int ret = -1; + + vdata = __get_datapage(); + if (vdata->cycle_count_offset == EMPTY_REG_OFFSET) + return clock_gettime_fallback(clkid, ts); + + switch (clkid) { + case CLOCK_REALTIME_COARSE: + ret = do_realtime_coarse(ts, vdata); + break; + case CLOCK_MONOTONIC_COARSE: + ret = do_monotonic_coarse(ts, vdata); + break; + case CLOCK_REALTIME: + ret = do_realtime(ts, vdata); + break; + case CLOCK_MONOTONIC: + ret = do_monotonic(ts, vdata); + break; + default: + break; + } + + if (ret) + ret = clock_gettime_fallback(clkid, ts); + + return ret; +} + +static notrace int clock_getres_fallback(clockid_t _clk_id, + struct __kernel_old_timespec *_res) +{ + register clockid_t clk_id asm("$r0") = _clk_id; + register struct __kernel_old_timespec *res asm("$r1") = _res; + register int ret asm("$r0"); + + asm volatile ("movi $r15, %3\n" + "syscall 0x0\n" + :"=r" (ret) + :"r"(clk_id), "r"(res), "i"(__NR_clock_getres) + :"$r15", "memory"); + + return ret; +} + +notrace int __vdso_clock_getres(clockid_t clk_id, struct __kernel_old_timespec *res) +{ + struct vdso_data *vdata = __get_datapage(); + + if (res == NULL) + return 0; + switch (clk_id) { + case CLOCK_REALTIME: + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_RAW: + res->tv_sec = 0; + res->tv_nsec = vdata->hrtimer_res; + break; + case CLOCK_REALTIME_COARSE: + case CLOCK_MONOTONIC_COARSE: + res->tv_sec = 0; + res->tv_nsec = CLOCK_COARSE_RES; + break; + default: + return clock_getres_fallback(clk_id, res); + } + return 0; +} + +static notrace inline int gettimeofday_fallback(struct __kernel_old_timeval *_tv, + struct timezone *_tz) +{ + register struct __kernel_old_timeval *tv asm("$r0") = _tv; + register struct timezone *tz asm("$r1") = _tz; + register int ret asm("$r0"); + + asm volatile ("movi $r15, %3\n" + "syscall 0x0\n" + :"=r" (ret) + :"r"(tv), "r"(tz), "i"(__NR_gettimeofday) + :"$r15", "memory"); + + return ret; +} + +notrace int __vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz) +{ + struct __kernel_old_timespec ts; + struct vdso_data *vdata; + int ret; + + vdata = __get_datapage(); + + if (vdata->cycle_count_offset == EMPTY_REG_OFFSET) + return gettimeofday_fallback(tv, tz); + + ret = do_realtime(&ts, vdata); + + if (tv) { + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; + } + if (tz) { + tz->tz_minuteswest = vdata->tz_minuteswest; + tz->tz_dsttime = vdata->tz_dsttime; + } + + return ret; +} |