| // 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; |
| } |