diff options
author | Alex Smith <alex.smith@imgtec.com> | 2015-10-21 04:57:44 -0400 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2015-11-11 02:36:41 -0500 |
commit | a7f4df4e21dd8a8dab96e88acd2c9c5017b83fc6 (patch) | |
tree | 36c00d0b92be2e2a39da15839f4bfe09be72b572 /arch/mips/vdso | |
parent | c0a9f72c156baf1e88c33c6ba4450647af1b8804 (diff) |
MIPS: VDSO: Add implementations of gettimeofday() and clock_gettime()
Add user-mode implementations of gettimeofday() and clock_gettime() to
the VDSO. This is currently usable with 2 clocksources: the CP0 count
register, which is accessible to user-mode via RDHWR on R2 and later
cores, or the MIPS Global Interrupt Controller (GIC) timer, which
provides a "user-mode visible" section containing a mirror of its
counter registers. This section must be mapped into user memory, which
is done below the VDSO data page.
When a supported clocksource is not in use, the VDSO functions will
return -ENOSYS, which causes libc to fall back on the standard syscall
path.
When support for neither of these clocksources is compiled into the
kernel at all, the VDSO still provides clock_gettime(), as the coarse
realtime/monotonic clocks can still be implemented. However,
gettimeofday() is not provided in this case as nothing can be done
without a suitable clocksource. This causes the symbol lookup to fail
in libc and it will then always use the standard syscall path.
This patch includes a workaround for a bug in QEMU which results in
RDHWR on the CP0 count register always returning a constant (incorrect)
value. A fix for this has been submitted, and the workaround can be
removed after the fix has been in stable releases for a reasonable
amount of time.
A simple performance test which calls gettimeofday() 1000 times in a
loop and calculates the average execution time gives the following
results on a Malta + I6400 (running at 20MHz):
- Syscall: ~31000 ns
- VDSO (GIC): ~15000 ns
- VDSO (CP0): ~9500 ns
[markos.chandras@imgtec.com:
- Minor code re-arrangements in order for mappings to be made
in the order they appear to the process' address space.
- Move do_{monotonic, realtime} outside of the MIPS_CLOCK_VSYSCALL ifdef
- Use gic_get_usm_range so we can do the GIC mapping in the
arch/mips/kernel/vdso instead of the GIC irqchip driver]
Signed-off-by: Alex Smith <alex.smith@imgtec.com>
Signed-off-by: Markos Chandras <markos.chandras@imgtec.com>
Cc: linux-kernel@vger.kernel.org
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/11338/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/vdso')
-rw-r--r-- | arch/mips/vdso/gettimeofday.c | 232 | ||||
-rw-r--r-- | arch/mips/vdso/vdso.h | 9 | ||||
-rw-r--r-- | arch/mips/vdso/vdso.lds.S | 5 |
3 files changed, 246 insertions, 0 deletions
diff --git a/arch/mips/vdso/gettimeofday.c b/arch/mips/vdso/gettimeofday.c new file mode 100644 index 000000000000..ce89c9e294f9 --- /dev/null +++ b/arch/mips/vdso/gettimeofday.c | |||
@@ -0,0 +1,232 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Imagination Technologies | ||
3 | * Author: Alex Smith <alex.smith@imgtec.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License as published by the | ||
7 | * Free Software Foundation; either version 2 of the License, or (at your | ||
8 | * option) any later version. | ||
9 | */ | ||
10 | |||
11 | #include "vdso.h" | ||
12 | |||
13 | #include <linux/compiler.h> | ||
14 | #include <linux/irqchip/mips-gic.h> | ||
15 | #include <linux/time.h> | ||
16 | |||
17 | #include <asm/clocksource.h> | ||
18 | #include <asm/io.h> | ||
19 | #include <asm/mips-cm.h> | ||
20 | #include <asm/unistd.h> | ||
21 | #include <asm/vdso.h> | ||
22 | |||
23 | static __always_inline int do_realtime_coarse(struct timespec *ts, | ||
24 | const union mips_vdso_data *data) | ||
25 | { | ||
26 | u32 start_seq; | ||
27 | |||
28 | do { | ||
29 | start_seq = vdso_data_read_begin(data); | ||
30 | |||
31 | ts->tv_sec = data->xtime_sec; | ||
32 | ts->tv_nsec = data->xtime_nsec >> data->cs_shift; | ||
33 | } while (vdso_data_read_retry(data, start_seq)); | ||
34 | |||
35 | return 0; | ||
36 | } | ||
37 | |||
38 | static __always_inline int do_monotonic_coarse(struct timespec *ts, | ||
39 | const union mips_vdso_data *data) | ||
40 | { | ||
41 | u32 start_seq; | ||
42 | u32 to_mono_sec; | ||
43 | u32 to_mono_nsec; | ||
44 | |||
45 | do { | ||
46 | start_seq = vdso_data_read_begin(data); | ||
47 | |||
48 | ts->tv_sec = data->xtime_sec; | ||
49 | ts->tv_nsec = data->xtime_nsec >> data->cs_shift; | ||
50 | |||
51 | to_mono_sec = data->wall_to_mono_sec; | ||
52 | to_mono_nsec = data->wall_to_mono_nsec; | ||
53 | } while (vdso_data_read_retry(data, start_seq)); | ||
54 | |||
55 | ts->tv_sec += to_mono_sec; | ||
56 | timespec_add_ns(ts, to_mono_nsec); | ||
57 | |||
58 | return 0; | ||
59 | } | ||
60 | |||
61 | #ifdef CONFIG_CSRC_R4K | ||
62 | |||
63 | static __always_inline u64 read_r4k_count(void) | ||
64 | { | ||
65 | unsigned int count; | ||
66 | |||
67 | __asm__ __volatile__( | ||
68 | " .set push\n" | ||
69 | " .set mips32r2\n" | ||
70 | " rdhwr %0, $2\n" | ||
71 | " .set pop\n" | ||
72 | : "=r" (count)); | ||
73 | |||
74 | return count; | ||
75 | } | ||
76 | |||
77 | #endif | ||
78 | |||
79 | #ifdef CONFIG_CLKSRC_MIPS_GIC | ||
80 | |||
81 | static __always_inline u64 read_gic_count(const union mips_vdso_data *data) | ||
82 | { | ||
83 | void __iomem *gic = get_gic(data); | ||
84 | u32 hi, hi2, lo; | ||
85 | |||
86 | do { | ||
87 | hi = __raw_readl(gic + GIC_UMV_SH_COUNTER_63_32_OFS); | ||
88 | lo = __raw_readl(gic + GIC_UMV_SH_COUNTER_31_00_OFS); | ||
89 | hi2 = __raw_readl(gic + GIC_UMV_SH_COUNTER_63_32_OFS); | ||
90 | } while (hi2 != hi); | ||
91 | |||
92 | return (((u64)hi) << 32) + lo; | ||
93 | } | ||
94 | |||
95 | #endif | ||
96 | |||
97 | static __always_inline u64 get_ns(const union mips_vdso_data *data) | ||
98 | { | ||
99 | u64 cycle_now, delta, nsec; | ||
100 | |||
101 | switch (data->clock_mode) { | ||
102 | #ifdef CONFIG_CSRC_R4K | ||
103 | case VDSO_CLOCK_R4K: | ||
104 | cycle_now = read_r4k_count(); | ||
105 | break; | ||
106 | #endif | ||
107 | #ifdef CONFIG_CLKSRC_MIPS_GIC | ||
108 | case VDSO_CLOCK_GIC: | ||
109 | cycle_now = read_gic_count(data); | ||
110 | break; | ||
111 | #endif | ||
112 | default: | ||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | delta = (cycle_now - data->cs_cycle_last) & data->cs_mask; | ||
117 | |||
118 | nsec = (delta * data->cs_mult) + data->xtime_nsec; | ||
119 | nsec >>= data->cs_shift; | ||
120 | |||
121 | return nsec; | ||
122 | } | ||
123 | |||
124 | static __always_inline int do_realtime(struct timespec *ts, | ||
125 | const union mips_vdso_data *data) | ||
126 | { | ||
127 | u32 start_seq; | ||
128 | u64 ns; | ||
129 | |||
130 | do { | ||
131 | start_seq = vdso_data_read_begin(data); | ||
132 | |||
133 | if (data->clock_mode == VDSO_CLOCK_NONE) | ||
134 | return -ENOSYS; | ||
135 | |||
136 | ts->tv_sec = data->xtime_sec; | ||
137 | ns = get_ns(data); | ||
138 | } while (vdso_data_read_retry(data, start_seq)); | ||
139 | |||
140 | ts->tv_nsec = 0; | ||
141 | timespec_add_ns(ts, ns); | ||
142 | |||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | static __always_inline int do_monotonic(struct timespec *ts, | ||
147 | const union mips_vdso_data *data) | ||
148 | { | ||
149 | u32 start_seq; | ||
150 | u64 ns; | ||
151 | u32 to_mono_sec; | ||
152 | u32 to_mono_nsec; | ||
153 | |||
154 | do { | ||
155 | start_seq = vdso_data_read_begin(data); | ||
156 | |||
157 | if (data->clock_mode == VDSO_CLOCK_NONE) | ||
158 | return -ENOSYS; | ||
159 | |||
160 | ts->tv_sec = data->xtime_sec; | ||
161 | ns = get_ns(data); | ||
162 | |||
163 | to_mono_sec = data->wall_to_mono_sec; | ||
164 | to_mono_nsec = data->wall_to_mono_nsec; | ||
165 | } while (vdso_data_read_retry(data, start_seq)); | ||
166 | |||
167 | ts->tv_sec += to_mono_sec; | ||
168 | ts->tv_nsec = 0; | ||
169 | timespec_add_ns(ts, ns + to_mono_nsec); | ||
170 | |||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | #ifdef CONFIG_MIPS_CLOCK_VSYSCALL | ||
175 | |||
176 | /* | ||
177 | * This is behind the ifdef so that we don't provide the symbol when there's no | ||
178 | * possibility of there being a usable clocksource, because there's nothing we | ||
179 | * can do without it. When libc fails the symbol lookup it should fall back on | ||
180 | * the standard syscall path. | ||
181 | */ | ||
182 | int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz) | ||
183 | { | ||
184 | const union mips_vdso_data *data = get_vdso_data(); | ||
185 | struct timespec ts; | ||
186 | int ret; | ||
187 | |||
188 | ret = do_realtime(&ts, data); | ||
189 | if (ret) | ||
190 | return ret; | ||
191 | |||
192 | if (tv) { | ||
193 | tv->tv_sec = ts.tv_sec; | ||
194 | tv->tv_usec = ts.tv_nsec / 1000; | ||
195 | } | ||
196 | |||
197 | if (tz) { | ||
198 | tz->tz_minuteswest = data->tz_minuteswest; | ||
199 | tz->tz_dsttime = data->tz_dsttime; | ||
200 | } | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | #endif /* CONFIG_CLKSRC_MIPS_GIC */ | ||
206 | |||
207 | int __vdso_clock_gettime(clockid_t clkid, struct timespec *ts) | ||
208 | { | ||
209 | const union mips_vdso_data *data = get_vdso_data(); | ||
210 | int ret; | ||
211 | |||
212 | switch (clkid) { | ||
213 | case CLOCK_REALTIME_COARSE: | ||
214 | ret = do_realtime_coarse(ts, data); | ||
215 | break; | ||
216 | case CLOCK_MONOTONIC_COARSE: | ||
217 | ret = do_monotonic_coarse(ts, data); | ||
218 | break; | ||
219 | case CLOCK_REALTIME: | ||
220 | ret = do_realtime(ts, data); | ||
221 | break; | ||
222 | case CLOCK_MONOTONIC: | ||
223 | ret = do_monotonic(ts, data); | ||
224 | break; | ||
225 | default: | ||
226 | ret = -ENOSYS; | ||
227 | break; | ||
228 | } | ||
229 | |||
230 | /* If we return -ENOSYS libc should fall back to a syscall. */ | ||
231 | return ret; | ||
232 | } | ||
diff --git a/arch/mips/vdso/vdso.h b/arch/mips/vdso/vdso.h index 0bb6b1adc385..cfb1be441dec 100644 --- a/arch/mips/vdso/vdso.h +++ b/arch/mips/vdso/vdso.h | |||
@@ -77,4 +77,13 @@ static inline const union mips_vdso_data *get_vdso_data(void) | |||
77 | return (const union mips_vdso_data *)(get_vdso_base() - PAGE_SIZE); | 77 | return (const union mips_vdso_data *)(get_vdso_base() - PAGE_SIZE); |
78 | } | 78 | } |
79 | 79 | ||
80 | #ifdef CONFIG_CLKSRC_MIPS_GIC | ||
81 | |||
82 | static inline void __iomem *get_gic(const union mips_vdso_data *data) | ||
83 | { | ||
84 | return (void __iomem *)data - PAGE_SIZE; | ||
85 | } | ||
86 | |||
87 | #endif /* CONFIG_CLKSRC_MIPS_GIC */ | ||
88 | |||
80 | #endif /* __ASSEMBLY__ */ | 89 | #endif /* __ASSEMBLY__ */ |
diff --git a/arch/mips/vdso/vdso.lds.S b/arch/mips/vdso/vdso.lds.S index 21655b6fefc5..8df7dd53e8e0 100644 --- a/arch/mips/vdso/vdso.lds.S +++ b/arch/mips/vdso/vdso.lds.S | |||
@@ -95,6 +95,11 @@ PHDRS | |||
95 | VERSION | 95 | VERSION |
96 | { | 96 | { |
97 | LINUX_2.6 { | 97 | LINUX_2.6 { |
98 | #ifndef DISABLE_MIPS_VDSO | ||
99 | global: | ||
100 | __vdso_clock_gettime; | ||
101 | __vdso_gettimeofday; | ||
102 | #endif | ||
98 | local: *; | 103 | local: *; |
99 | }; | 104 | }; |
100 | } | 105 | } |