diff options
author | John Stultz <john.stultz@linaro.org> | 2012-03-02 01:11:09 -0500 |
---|---|---|
committer | John Stultz <john.stultz@linaro.org> | 2012-03-15 21:17:53 -0400 |
commit | a939e817aa7e199d2fff05a67cb745be32dd5c2d (patch) | |
tree | f55d3554476ce03c7fe6731284221a177e896d6b /arch/x86/vdso | |
parent | a078c6d0e6288fad6d83fb6d5edd91ddb7b6ab33 (diff) |
time: x86: Fix race switching from vsyscall to non-vsyscall clock
When switching from a vsyscall capable to a non-vsyscall capable
clocksource, there was a small race, where the last vsyscall
gettimeofday before the switch might return a invalid time value
using the new non-vsyscall enabled clocksource values after the
switch is complete.
This is due to the vsyscall code checking the vclock_mode once
outside of the seqcount protected section. After it reads the
vclock mode, it doesn't re-check that the sampled clock data
that is obtained in the seqcount critical section still matches.
The fix is to sample vclock_mode inside the protected section,
and as long as it isn't VCLOCK_NONE, return the calculated
value. If it has changed and is now VCLOCK_NONE, fall back
to the syscall gettime calculation.
v2:
* Cleanup checks as suggested by tglx
* Also fix same issue present in gettimeofday path
CC: Andy Lutomirski <luto@amacapital.net>
CC: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: John Stultz <john.stultz@linaro.org>
Diffstat (limited to 'arch/x86/vdso')
-rw-r--r-- | arch/x86/vdso/vclock_gettime.c | 72 |
1 files changed, 46 insertions, 26 deletions
diff --git a/arch/x86/vdso/vclock_gettime.c b/arch/x86/vdso/vclock_gettime.c index 6bc0e723b6e8..7eeb1f6188ee 100644 --- a/arch/x86/vdso/vclock_gettime.c +++ b/arch/x86/vdso/vclock_gettime.c | |||
@@ -70,14 +70,26 @@ notrace static long vdso_fallback_gettime(long clock, struct timespec *ts) | |||
70 | return ret; | 70 | return ret; |
71 | } | 71 | } |
72 | 72 | ||
73 | notrace static long vdso_fallback_gtod(struct timeval *tv, struct timezone *tz) | ||
74 | { | ||
75 | long ret; | ||
76 | |||
77 | asm("syscall" : "=a" (ret) : | ||
78 | "0" (__NR_gettimeofday), "D" (tv), "S" (tz) : "memory"); | ||
79 | return ret; | ||
80 | } | ||
81 | |||
82 | |||
73 | notrace static inline long vgetns(void) | 83 | notrace static inline long vgetns(void) |
74 | { | 84 | { |
75 | long v; | 85 | long v; |
76 | cycles_t cycles; | 86 | cycles_t cycles; |
77 | if (gtod->clock.vclock_mode == VCLOCK_TSC) | 87 | if (gtod->clock.vclock_mode == VCLOCK_TSC) |
78 | cycles = vread_tsc(); | 88 | cycles = vread_tsc(); |
79 | else | 89 | else if (gtod->clock.vclock_mode == VCLOCK_HPET) |
80 | cycles = vread_hpet(); | 90 | cycles = vread_hpet(); |
91 | else | ||
92 | return 0; | ||
81 | v = (cycles - gtod->clock.cycle_last) & gtod->clock.mask; | 93 | v = (cycles - gtod->clock.cycle_last) & gtod->clock.mask; |
82 | return (v * gtod->clock.mult) >> gtod->clock.shift; | 94 | return (v * gtod->clock.mult) >> gtod->clock.shift; |
83 | } | 95 | } |
@@ -85,21 +97,28 @@ notrace static inline long vgetns(void) | |||
85 | notrace static noinline int do_realtime(struct timespec *ts) | 97 | notrace static noinline int do_realtime(struct timespec *ts) |
86 | { | 98 | { |
87 | unsigned long seq, ns; | 99 | unsigned long seq, ns; |
100 | int mode; | ||
101 | |||
88 | do { | 102 | do { |
89 | seq = read_seqbegin(>od->lock); | 103 | seq = read_seqbegin(>od->lock); |
104 | mode = gtod->clock.vclock_mode; | ||
90 | ts->tv_sec = gtod->wall_time_sec; | 105 | ts->tv_sec = gtod->wall_time_sec; |
91 | ts->tv_nsec = gtod->wall_time_nsec; | 106 | ts->tv_nsec = gtod->wall_time_nsec; |
92 | ns = vgetns(); | 107 | ns = vgetns(); |
93 | } while (unlikely(read_seqretry(>od->lock, seq))); | 108 | } while (unlikely(read_seqretry(>od->lock, seq))); |
109 | |||
94 | timespec_add_ns(ts, ns); | 110 | timespec_add_ns(ts, ns); |
95 | return 0; | 111 | return mode; |
96 | } | 112 | } |
97 | 113 | ||
98 | notrace static noinline int do_monotonic(struct timespec *ts) | 114 | notrace static noinline int do_monotonic(struct timespec *ts) |
99 | { | 115 | { |
100 | unsigned long seq, ns, secs; | 116 | unsigned long seq, ns, secs; |
117 | int mode; | ||
118 | |||
101 | do { | 119 | do { |
102 | seq = read_seqbegin(>od->lock); | 120 | seq = read_seqbegin(>od->lock); |
121 | mode = gtod->clock.vclock_mode; | ||
103 | secs = gtod->wall_time_sec; | 122 | secs = gtod->wall_time_sec; |
104 | ns = gtod->wall_time_nsec + vgetns(); | 123 | ns = gtod->wall_time_nsec + vgetns(); |
105 | secs += gtod->wall_to_monotonic.tv_sec; | 124 | secs += gtod->wall_to_monotonic.tv_sec; |
@@ -116,7 +135,7 @@ notrace static noinline int do_monotonic(struct timespec *ts) | |||
116 | ts->tv_sec = secs; | 135 | ts->tv_sec = secs; |
117 | ts->tv_nsec = ns; | 136 | ts->tv_nsec = ns; |
118 | 137 | ||
119 | return 0; | 138 | return mode; |
120 | } | 139 | } |
121 | 140 | ||
122 | notrace static noinline int do_realtime_coarse(struct timespec *ts) | 141 | notrace static noinline int do_realtime_coarse(struct timespec *ts) |
@@ -156,14 +175,14 @@ notrace static noinline int do_monotonic_coarse(struct timespec *ts) | |||
156 | 175 | ||
157 | notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts) | 176 | notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts) |
158 | { | 177 | { |
178 | int ret = VCLOCK_NONE; | ||
179 | |||
159 | switch (clock) { | 180 | switch (clock) { |
160 | case CLOCK_REALTIME: | 181 | case CLOCK_REALTIME: |
161 | if (likely(gtod->clock.vclock_mode != VCLOCK_NONE)) | 182 | ret = do_realtime(ts); |
162 | return do_realtime(ts); | ||
163 | break; | 183 | break; |
164 | case CLOCK_MONOTONIC: | 184 | case CLOCK_MONOTONIC: |
165 | if (likely(gtod->clock.vclock_mode != VCLOCK_NONE)) | 185 | ret = do_monotonic(ts); |
166 | return do_monotonic(ts); | ||
167 | break; | 186 | break; |
168 | case CLOCK_REALTIME_COARSE: | 187 | case CLOCK_REALTIME_COARSE: |
169 | return do_realtime_coarse(ts); | 188 | return do_realtime_coarse(ts); |
@@ -171,32 +190,33 @@ notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts) | |||
171 | return do_monotonic_coarse(ts); | 190 | return do_monotonic_coarse(ts); |
172 | } | 191 | } |
173 | 192 | ||
174 | return vdso_fallback_gettime(clock, ts); | 193 | if (ret == VCLOCK_NONE) |
194 | return vdso_fallback_gettime(clock, ts); | ||
195 | return 0; | ||
175 | } | 196 | } |
176 | int clock_gettime(clockid_t, struct timespec *) | 197 | int clock_gettime(clockid_t, struct timespec *) |
177 | __attribute__((weak, alias("__vdso_clock_gettime"))); | 198 | __attribute__((weak, alias("__vdso_clock_gettime"))); |
178 | 199 | ||
179 | notrace int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz) | 200 | notrace int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz) |
180 | { | 201 | { |
181 | long ret; | 202 | long ret = VCLOCK_NONE; |
182 | if (likely(gtod->clock.vclock_mode != VCLOCK_NONE)) { | 203 | |
183 | if (likely(tv != NULL)) { | 204 | if (likely(tv != NULL)) { |
184 | BUILD_BUG_ON(offsetof(struct timeval, tv_usec) != | 205 | BUILD_BUG_ON(offsetof(struct timeval, tv_usec) != |
185 | offsetof(struct timespec, tv_nsec) || | 206 | offsetof(struct timespec, tv_nsec) || |
186 | sizeof(*tv) != sizeof(struct timespec)); | 207 | sizeof(*tv) != sizeof(struct timespec)); |
187 | do_realtime((struct timespec *)tv); | 208 | ret = do_realtime((struct timespec *)tv); |
188 | tv->tv_usec /= 1000; | 209 | tv->tv_usec /= 1000; |
189 | } | ||
190 | if (unlikely(tz != NULL)) { | ||
191 | /* Avoid memcpy. Some old compilers fail to inline it */ | ||
192 | tz->tz_minuteswest = gtod->sys_tz.tz_minuteswest; | ||
193 | tz->tz_dsttime = gtod->sys_tz.tz_dsttime; | ||
194 | } | ||
195 | return 0; | ||
196 | } | 210 | } |
197 | asm("syscall" : "=a" (ret) : | 211 | if (unlikely(tz != NULL)) { |
198 | "0" (__NR_gettimeofday), "D" (tv), "S" (tz) : "memory"); | 212 | /* Avoid memcpy. Some old compilers fail to inline it */ |
199 | return ret; | 213 | tz->tz_minuteswest = gtod->sys_tz.tz_minuteswest; |
214 | tz->tz_dsttime = gtod->sys_tz.tz_dsttime; | ||
215 | } | ||
216 | |||
217 | if (ret == VCLOCK_NONE) | ||
218 | return vdso_fallback_gtod(tv, tz); | ||
219 | return 0; | ||
200 | } | 220 | } |
201 | int gettimeofday(struct timeval *, struct timezone *) | 221 | int gettimeofday(struct timeval *, struct timezone *) |
202 | __attribute__((weak, alias("__vdso_gettimeofday"))); | 222 | __attribute__((weak, alias("__vdso_gettimeofday"))); |