1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (C) 2005-2017 Andes Technology Corporation
3
4 #include <linux/compiler.h>
5 #include <linux/hrtimer.h>
6 #include <linux/time.h>
7 #include <asm/io.h>
8 #include <asm/barrier.h>
9 #include <asm/bug.h>
10 #include <asm/page.h>
11 #include <asm/unistd.h>
12 #include <asm/vdso_datapage.h>
13 #include <asm/vdso_timer_info.h>
14 #include <asm/asm-offsets.h>
15
16 #define X(x) #x
17 #define Y(x) X(x)
18
19 extern struct vdso_data *__get_datapage(void);
20 extern struct vdso_data *__get_timerpage(void);
21
__vdso_read_begin(const struct vdso_data * vdata)22 static notrace unsigned int __vdso_read_begin(const struct vdso_data *vdata)
23 {
24 u32 seq;
25 repeat:
26 seq = READ_ONCE(vdata->seq_count);
27 if (seq & 1) {
28 cpu_relax();
29 goto repeat;
30 }
31 return seq;
32 }
33
vdso_read_begin(const struct vdso_data * vdata)34 static notrace unsigned int vdso_read_begin(const struct vdso_data *vdata)
35 {
36 unsigned int seq;
37
38 seq = __vdso_read_begin(vdata);
39
40 smp_rmb(); /* Pairs with smp_wmb in vdso_write_end */
41 return seq;
42 }
43
vdso_read_retry(const struct vdso_data * vdata,u32 start)44 static notrace int vdso_read_retry(const struct vdso_data *vdata, u32 start)
45 {
46 smp_rmb(); /* Pairs with smp_wmb in vdso_write_begin */
47 return vdata->seq_count != start;
48 }
49
clock_gettime_fallback(clockid_t _clkid,struct timespec * _ts)50 static notrace long clock_gettime_fallback(clockid_t _clkid,
51 struct timespec *_ts)
52 {
53 register struct timespec *ts asm("$r1") = _ts;
54 register clockid_t clkid asm("$r0") = _clkid;
55 register long ret asm("$r0");
56
57 asm volatile ("movi $r15, %3\n"
58 "syscall 0x0\n"
59 :"=r" (ret)
60 :"r"(clkid), "r"(ts), "i"(__NR_clock_gettime)
61 :"$r15", "memory");
62
63 return ret;
64 }
65
do_realtime_coarse(struct timespec * ts,struct vdso_data * vdata)66 static notrace int do_realtime_coarse(struct timespec *ts,
67 struct vdso_data *vdata)
68 {
69 u32 seq;
70
71 do {
72 seq = vdso_read_begin(vdata);
73
74 ts->tv_sec = vdata->xtime_coarse_sec;
75 ts->tv_nsec = vdata->xtime_coarse_nsec;
76
77 } while (vdso_read_retry(vdata, seq));
78 return 0;
79 }
80
do_monotonic_coarse(struct timespec * ts,struct vdso_data * vdata)81 static notrace int do_monotonic_coarse(struct timespec *ts,
82 struct vdso_data *vdata)
83 {
84 struct timespec tomono;
85 u32 seq;
86
87 do {
88 seq = vdso_read_begin(vdata);
89
90 ts->tv_sec = vdata->xtime_coarse_sec;
91 ts->tv_nsec = vdata->xtime_coarse_nsec;
92
93 tomono.tv_sec = vdata->wtm_clock_sec;
94 tomono.tv_nsec = vdata->wtm_clock_nsec;
95
96 } while (vdso_read_retry(vdata, seq));
97
98 ts->tv_sec += tomono.tv_sec;
99 timespec_add_ns(ts, tomono.tv_nsec);
100 return 0;
101 }
102
vgetsns(struct vdso_data * vdso)103 static notrace inline u64 vgetsns(struct vdso_data *vdso)
104 {
105 u32 cycle_now;
106 u32 cycle_delta;
107 u32 *timer_cycle_base;
108
109 timer_cycle_base =
110 (u32 *) ((char *)__get_timerpage() + vdso->cycle_count_offset);
111 cycle_now = readl_relaxed(timer_cycle_base);
112 if (true == vdso->cycle_count_down)
113 cycle_now = ~(*timer_cycle_base);
114 cycle_delta = cycle_now - (u32) vdso->cs_cycle_last;
115 return ((u64) cycle_delta & vdso->cs_mask) * vdso->cs_mult;
116 }
117
do_realtime(struct timespec * ts,struct vdso_data * vdata)118 static notrace int do_realtime(struct timespec *ts, struct vdso_data *vdata)
119 {
120 unsigned count;
121 u64 ns;
122 do {
123 count = vdso_read_begin(vdata);
124 ts->tv_sec = vdata->xtime_clock_sec;
125 ns = vdata->xtime_clock_nsec;
126 ns += vgetsns(vdata);
127 ns >>= vdata->cs_shift;
128 } while (vdso_read_retry(vdata, count));
129
130 ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
131 ts->tv_nsec = ns;
132
133 return 0;
134 }
135
do_monotonic(struct timespec * ts,struct vdso_data * vdata)136 static notrace int do_monotonic(struct timespec *ts, struct vdso_data *vdata)
137 {
138 struct timespec tomono;
139 u64 nsecs;
140 u32 seq;
141
142 do {
143 seq = vdso_read_begin(vdata);
144
145 ts->tv_sec = vdata->xtime_clock_sec;
146 nsecs = vdata->xtime_clock_nsec;
147 nsecs += vgetsns(vdata);
148 nsecs >>= vdata->cs_shift;
149
150 tomono.tv_sec = vdata->wtm_clock_sec;
151 tomono.tv_nsec = vdata->wtm_clock_nsec;
152
153 } while (vdso_read_retry(vdata, seq));
154
155 ts->tv_sec += tomono.tv_sec;
156 ts->tv_nsec = 0;
157 timespec_add_ns(ts, nsecs + tomono.tv_nsec);
158 return 0;
159 }
160
__vdso_clock_gettime(clockid_t clkid,struct timespec * ts)161 notrace int __vdso_clock_gettime(clockid_t clkid, struct timespec *ts)
162 {
163 struct vdso_data *vdata;
164 int ret = -1;
165
166 vdata = __get_datapage();
167 if (vdata->cycle_count_offset == EMPTY_REG_OFFSET)
168 return clock_gettime_fallback(clkid, ts);
169
170 switch (clkid) {
171 case CLOCK_REALTIME_COARSE:
172 ret = do_realtime_coarse(ts, vdata);
173 break;
174 case CLOCK_MONOTONIC_COARSE:
175 ret = do_monotonic_coarse(ts, vdata);
176 break;
177 case CLOCK_REALTIME:
178 ret = do_realtime(ts, vdata);
179 break;
180 case CLOCK_MONOTONIC:
181 ret = do_monotonic(ts, vdata);
182 break;
183 default:
184 break;
185 }
186
187 if (ret)
188 ret = clock_gettime_fallback(clkid, ts);
189
190 return ret;
191 }
192
clock_getres_fallback(clockid_t _clk_id,struct timespec * _res)193 static notrace int clock_getres_fallback(clockid_t _clk_id,
194 struct timespec *_res)
195 {
196 register clockid_t clk_id asm("$r0") = _clk_id;
197 register struct timespec *res asm("$r1") = _res;
198 register int ret asm("$r0");
199
200 asm volatile ("movi $r15, %3\n"
201 "syscall 0x0\n"
202 :"=r" (ret)
203 :"r"(clk_id), "r"(res), "i"(__NR_clock_getres)
204 :"$r15", "memory");
205
206 return ret;
207 }
208
__vdso_clock_getres(clockid_t clk_id,struct timespec * res)209 notrace int __vdso_clock_getres(clockid_t clk_id, struct timespec *res)
210 {
211 struct vdso_data *vdata = __get_datapage();
212
213 if (res == NULL)
214 return 0;
215 switch (clk_id) {
216 case CLOCK_REALTIME:
217 case CLOCK_MONOTONIC:
218 case CLOCK_MONOTONIC_RAW:
219 res->tv_sec = 0;
220 res->tv_nsec = vdata->hrtimer_res;
221 break;
222 case CLOCK_REALTIME_COARSE:
223 case CLOCK_MONOTONIC_COARSE:
224 res->tv_sec = 0;
225 res->tv_nsec = CLOCK_COARSE_RES;
226 break;
227 default:
228 return clock_getres_fallback(clk_id, res);
229 }
230 return 0;
231 }
232
gettimeofday_fallback(struct timeval * _tv,struct timezone * _tz)233 static notrace inline int gettimeofday_fallback(struct timeval *_tv,
234 struct timezone *_tz)
235 {
236 register struct timeval *tv asm("$r0") = _tv;
237 register struct timezone *tz asm("$r1") = _tz;
238 register int ret asm("$r0");
239
240 asm volatile ("movi $r15, %3\n"
241 "syscall 0x0\n"
242 :"=r" (ret)
243 :"r"(tv), "r"(tz), "i"(__NR_gettimeofday)
244 :"$r15", "memory");
245
246 return ret;
247 }
248
__vdso_gettimeofday(struct timeval * tv,struct timezone * tz)249 notrace int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
250 {
251 struct timespec ts;
252 struct vdso_data *vdata;
253 int ret;
254
255 vdata = __get_datapage();
256
257 if (vdata->cycle_count_offset == EMPTY_REG_OFFSET)
258 return gettimeofday_fallback(tv, tz);
259
260 ret = do_realtime(&ts, vdata);
261
262 if (tv) {
263 tv->tv_sec = ts.tv_sec;
264 tv->tv_usec = ts.tv_nsec / 1000;
265 }
266 if (tz) {
267 tz->tz_minuteswest = vdata->tz_minuteswest;
268 tz->tz_dsttime = vdata->tz_dsttime;
269 }
270
271 return ret;
272 }
273