| /* | |
| * 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"))); |