| /* |
| * Copyright 2012 Tilera Corporation. All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation, version 2. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or |
| * NON INFRINGEMENT. See the GNU General Public License for |
| * more details. |
| */ |
| |
| #define VDSO_BUILD /* avoid some shift warnings for -m32 in <asm/page.h> */ |
| #include <linux/time.h> |
| #include <asm/timex.h> |
| #include <asm/unistd.h> |
| #include <asm/vdso.h> |
| |
| #if CHIP_HAS_SPLIT_CYCLE() |
| static inline cycles_t get_cycles_inline(void) |
| { |
| unsigned int high = __insn_mfspr(SPR_CYCLE_HIGH); |
| unsigned int low = __insn_mfspr(SPR_CYCLE_LOW); |
| unsigned int high2 = __insn_mfspr(SPR_CYCLE_HIGH); |
| |
| while (unlikely(high != high2)) { |
| low = __insn_mfspr(SPR_CYCLE_LOW); |
| high = high2; |
| high2 = __insn_mfspr(SPR_CYCLE_HIGH); |
| } |
| |
| return (((cycles_t)high) << 32) | low; |
| } |
| #define get_cycles get_cycles_inline |
| #endif |
| |
| struct syscall_return_value { |
| long value; |
| long error; |
| }; |
| |
| /* |
| * Find out the vDSO data page address in the process address space. |
| */ |
| inline unsigned long get_datapage(void) |
| { |
| unsigned long ret; |
| |
| /* vdso data page located in the 2nd vDSO page. */ |
| asm volatile ("lnk %0" : "=r"(ret)); |
| ret &= ~(PAGE_SIZE - 1); |
| ret += PAGE_SIZE; |
| |
| return ret; |
| } |
| |
| static inline u64 vgetsns(struct vdso_data *vdso) |
| { |
| return ((get_cycles() - vdso->cycle_last) & vdso->mask) * vdso->mult; |
| } |
| |
| static inline int do_realtime(struct vdso_data *vdso, struct timespec *ts) |
| { |
| unsigned count; |
| u64 ns; |
| |
| do { |
| count = raw_read_seqcount_begin(&vdso->tb_seq); |
| ts->tv_sec = vdso->wall_time_sec; |
| ns = vdso->wall_time_snsec; |
| ns += vgetsns(vdso); |
| ns >>= vdso->shift; |
| } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count))); |
| |
| ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns); |
| ts->tv_nsec = ns; |
| |
| return 0; |
| } |
| |
| static inline int do_monotonic(struct vdso_data *vdso, struct timespec *ts) |
| { |
| unsigned count; |
| u64 ns; |
| |
| do { |
| count = raw_read_seqcount_begin(&vdso->tb_seq); |
| ts->tv_sec = vdso->monotonic_time_sec; |
| ns = vdso->monotonic_time_snsec; |
| ns += vgetsns(vdso); |
| ns >>= vdso->shift; |
| } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count))); |
| |
| ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns); |
| ts->tv_nsec = ns; |
| |
| return 0; |
| } |
| |
| static inline int do_realtime_coarse(struct vdso_data *vdso, |
| struct timespec *ts) |
| { |
| unsigned count; |
| |
| do { |
| count = raw_read_seqcount_begin(&vdso->tb_seq); |
| ts->tv_sec = vdso->wall_time_coarse_sec; |
| ts->tv_nsec = vdso->wall_time_coarse_nsec; |
| } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count))); |
| |
| return 0; |
| } |
| |
| static inline int do_monotonic_coarse(struct vdso_data *vdso, |
| struct timespec *ts) |
| { |
| unsigned count; |
| |
| do { |
| count = raw_read_seqcount_begin(&vdso->tb_seq); |
| ts->tv_sec = vdso->monotonic_time_coarse_sec; |
| ts->tv_nsec = vdso->monotonic_time_coarse_nsec; |
| } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count))); |
| |
| return 0; |
| } |
| |
| struct syscall_return_value __vdso_gettimeofday(struct timeval *tv, |
| struct timezone *tz) |
| { |
| struct syscall_return_value ret = { 0, 0 }; |
| unsigned count; |
| struct vdso_data *vdso = (struct vdso_data *)get_datapage(); |
| |
| /* The use of the timezone is obsolete, normally tz is NULL. */ |
| if (unlikely(tz != NULL)) { |
| do { |
| count = raw_read_seqcount_begin(&vdso->tz_seq); |
| tz->tz_minuteswest = vdso->tz_minuteswest; |
| tz->tz_dsttime = vdso->tz_dsttime; |
| } while (unlikely(read_seqcount_retry(&vdso->tz_seq, count))); |
| } |
| |
| if (unlikely(tv == NULL)) |
| return ret; |
| |
| do_realtime(vdso, (struct timespec *)tv); |
| tv->tv_usec /= 1000; |
| |
| return ret; |
| } |
| |
| int gettimeofday(struct timeval *tv, struct timezone *tz) |
| __attribute__((weak, alias("__vdso_gettimeofday"))); |
| |
| static struct syscall_return_value vdso_fallback_gettime(long clock, |
| struct timespec *ts) |
| { |
| struct syscall_return_value ret; |
| __asm__ __volatile__ ( |
| "swint1" |
| : "=R00" (ret.value), "=R01" (ret.error) |
| : "R10" (__NR_clock_gettime), "R00" (clock), "R01" (ts) |
| : "r2", "r3", "r4", "r5", "r6", "r7", |
| "r8", "r9", "r11", "r12", "r13", "r14", "r15", |
| "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", |
| "r24", "r25", "r26", "r27", "r28", "r29", "memory"); |
| return ret; |
| } |
| |
| struct syscall_return_value __vdso_clock_gettime(clockid_t clock, |
| struct timespec *ts) |
| { |
| struct vdso_data *vdso = (struct vdso_data *)get_datapage(); |
| struct syscall_return_value ret = { 0, 0 }; |
| |
| switch (clock) { |
| case CLOCK_REALTIME: |
| do_realtime(vdso, ts); |
| return ret; |
| case CLOCK_MONOTONIC: |
| do_monotonic(vdso, ts); |
| return ret; |
| case CLOCK_REALTIME_COARSE: |
| do_realtime_coarse(vdso, ts); |
| return ret; |
| case CLOCK_MONOTONIC_COARSE: |
| do_monotonic_coarse(vdso, ts); |
| return ret; |
| default: |
| return vdso_fallback_gettime(clock, ts); |
| } |
| } |
| |
| int clock_gettime(clockid_t clock, struct timespec *ts) |
| __attribute__((weak, alias("__vdso_clock_gettime"))); |