1 /*
2 * Copyright 2012 Tilera Corporation. All Rights Reserved.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation, version 2.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
11 * NON INFRINGEMENT. See the GNU General Public License for
12 * more details.
13 */
14
15 #define VDSO_BUILD /* avoid some shift warnings for -m32 in <asm/page.h> */
16 #include <linux/time.h>
17 #include <asm/timex.h>
18 #include <asm/unistd.h>
19 #include <asm/vdso.h>
20
21 #if CHIP_HAS_SPLIT_CYCLE()
get_cycles_inline(void)22 static inline cycles_t get_cycles_inline(void)
23 {
24 unsigned int high = __insn_mfspr(SPR_CYCLE_HIGH);
25 unsigned int low = __insn_mfspr(SPR_CYCLE_LOW);
26 unsigned int high2 = __insn_mfspr(SPR_CYCLE_HIGH);
27
28 while (unlikely(high != high2)) {
29 low = __insn_mfspr(SPR_CYCLE_LOW);
30 high = high2;
31 high2 = __insn_mfspr(SPR_CYCLE_HIGH);
32 }
33
34 return (((cycles_t)high) << 32) | low;
35 }
36 #define get_cycles get_cycles_inline
37 #endif
38
39 struct syscall_return_value {
40 long value;
41 long error;
42 };
43
44 /*
45 * Find out the vDSO data page address in the process address space.
46 */
get_datapage(void)47 inline unsigned long get_datapage(void)
48 {
49 unsigned long ret;
50
51 /* vdso data page located in the 2nd vDSO page. */
52 asm volatile ("lnk %0" : "=r"(ret));
53 ret &= ~(PAGE_SIZE - 1);
54 ret += PAGE_SIZE;
55
56 return ret;
57 }
58
vgetsns(struct vdso_data * vdso)59 static inline u64 vgetsns(struct vdso_data *vdso)
60 {
61 return ((get_cycles() - vdso->cycle_last) & vdso->mask) * vdso->mult;
62 }
63
do_realtime(struct vdso_data * vdso,struct timespec * ts)64 static inline int do_realtime(struct vdso_data *vdso, struct timespec *ts)
65 {
66 unsigned count;
67 u64 ns;
68
69 do {
70 count = raw_read_seqcount_begin(&vdso->tb_seq);
71 ts->tv_sec = vdso->wall_time_sec;
72 ns = vdso->wall_time_snsec;
73 ns += vgetsns(vdso);
74 ns >>= vdso->shift;
75 } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count)));
76
77 ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
78 ts->tv_nsec = ns;
79
80 return 0;
81 }
82
do_monotonic(struct vdso_data * vdso,struct timespec * ts)83 static inline int do_monotonic(struct vdso_data *vdso, struct timespec *ts)
84 {
85 unsigned count;
86 u64 ns;
87
88 do {
89 count = raw_read_seqcount_begin(&vdso->tb_seq);
90 ts->tv_sec = vdso->monotonic_time_sec;
91 ns = vdso->monotonic_time_snsec;
92 ns += vgetsns(vdso);
93 ns >>= vdso->shift;
94 } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count)));
95
96 ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
97 ts->tv_nsec = ns;
98
99 return 0;
100 }
101
do_realtime_coarse(struct vdso_data * vdso,struct timespec * ts)102 static inline int do_realtime_coarse(struct vdso_data *vdso,
103 struct timespec *ts)
104 {
105 unsigned count;
106
107 do {
108 count = raw_read_seqcount_begin(&vdso->tb_seq);
109 ts->tv_sec = vdso->wall_time_coarse_sec;
110 ts->tv_nsec = vdso->wall_time_coarse_nsec;
111 } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count)));
112
113 return 0;
114 }
115
do_monotonic_coarse(struct vdso_data * vdso,struct timespec * ts)116 static inline int do_monotonic_coarse(struct vdso_data *vdso,
117 struct timespec *ts)
118 {
119 unsigned count;
120
121 do {
122 count = raw_read_seqcount_begin(&vdso->tb_seq);
123 ts->tv_sec = vdso->monotonic_time_coarse_sec;
124 ts->tv_nsec = vdso->monotonic_time_coarse_nsec;
125 } while (unlikely(read_seqcount_retry(&vdso->tb_seq, count)));
126
127 return 0;
128 }
129
__vdso_gettimeofday(struct timeval * tv,struct timezone * tz)130 struct syscall_return_value __vdso_gettimeofday(struct timeval *tv,
131 struct timezone *tz)
132 {
133 struct syscall_return_value ret = { 0, 0 };
134 unsigned count;
135 struct vdso_data *vdso = (struct vdso_data *)get_datapage();
136
137 /* The use of the timezone is obsolete, normally tz is NULL. */
138 if (unlikely(tz != NULL)) {
139 do {
140 count = raw_read_seqcount_begin(&vdso->tz_seq);
141 tz->tz_minuteswest = vdso->tz_minuteswest;
142 tz->tz_dsttime = vdso->tz_dsttime;
143 } while (unlikely(read_seqcount_retry(&vdso->tz_seq, count)));
144 }
145
146 if (unlikely(tv == NULL))
147 return ret;
148
149 do_realtime(vdso, (struct timespec *)tv);
150 tv->tv_usec /= 1000;
151
152 return ret;
153 }
154
155 int gettimeofday(struct timeval *tv, struct timezone *tz)
156 __attribute__((weak, alias("__vdso_gettimeofday")));
157
vdso_fallback_gettime(long clock,struct timespec * ts)158 static struct syscall_return_value vdso_fallback_gettime(long clock,
159 struct timespec *ts)
160 {
161 struct syscall_return_value ret;
162 __asm__ __volatile__ (
163 "swint1"
164 : "=R00" (ret.value), "=R01" (ret.error)
165 : "R10" (__NR_clock_gettime), "R00" (clock), "R01" (ts)
166 : "r2", "r3", "r4", "r5", "r6", "r7",
167 "r8", "r9", "r11", "r12", "r13", "r14", "r15",
168 "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
169 "r24", "r25", "r26", "r27", "r28", "r29", "memory");
170 return ret;
171 }
172
__vdso_clock_gettime(clockid_t clock,struct timespec * ts)173 struct syscall_return_value __vdso_clock_gettime(clockid_t clock,
174 struct timespec *ts)
175 {
176 struct vdso_data *vdso = (struct vdso_data *)get_datapage();
177 struct syscall_return_value ret = { 0, 0 };
178
179 switch (clock) {
180 case CLOCK_REALTIME:
181 do_realtime(vdso, ts);
182 return ret;
183 case CLOCK_MONOTONIC:
184 do_monotonic(vdso, ts);
185 return ret;
186 case CLOCK_REALTIME_COARSE:
187 do_realtime_coarse(vdso, ts);
188 return ret;
189 case CLOCK_MONOTONIC_COARSE:
190 do_monotonic_coarse(vdso, ts);
191 return ret;
192 default:
193 return vdso_fallback_gettime(clock, ts);
194 }
195 }
196
197 int clock_gettime(clockid_t clock, struct timespec *ts)
198 __attribute__((weak, alias("__vdso_clock_gettime")));
199