aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Mackerras <paulus@samba.org>2006-03-14 21:47:15 -0500
committerPaul Mackerras <paulus@samba.org>2006-03-16 00:54:55 -0500
commit0a45d4491d0f172e02126370f312405c5d473363 (patch)
treef3f3465d4a24704518e7625334d78add58008b1f
parent82dfdcae0d57c842e02f037758687eef42fb7af6 (diff)
powerpc: Fix problem with time going backwards
The recent changes to keep gettimeofday in sync with xtime had the side effect that it was occasionally possible for the time reported by gettimeofday to go back by a microsecond. There were two reasons: (1) when we recalculated the offsets used by gettimeofday every 2^31 timebase ticks, we lost an accumulated fractional microsecond, and (2) because the update is done some time after the notional start of jiffy, if ntp is slowing the clock, it is possible to see time go backwards when the timebase factor gets reduced. This fixes it by (a) slowing the gettimeofday clock by about 1us in 2^31 timebase ticks (a factor of less than 1 in 3.7 million), and (b) adjusting the timebase offsets in the rare case that the gettimeofday result could possibly go backwards (i.e. when ntp is slowing the clock and the timer interrupt is late). In this case the adjustment will reduce to zero eventually because of (a). Signed-off-by: Paul Mackerras <paulus@samba.org>
-rw-r--r--arch/powerpc/kernel/time.c48
1 files changed, 34 insertions, 14 deletions
diff --git a/arch/powerpc/kernel/time.c b/arch/powerpc/kernel/time.c
index 2a7ddc579379..86f7e3d154d8 100644
--- a/arch/powerpc/kernel/time.c
+++ b/arch/powerpc/kernel/time.c
@@ -283,9 +283,9 @@ static inline void update_gtod(u64 new_tb_stamp, u64 new_stamp_xsec,
283 * the two values of tb_update_count match and are even then the 283 * the two values of tb_update_count match and are even then the
284 * tb_to_xs and stamp_xsec values are consistent. If not, then it 284 * tb_to_xs and stamp_xsec values are consistent. If not, then it
285 * loops back and reads them again until this criteria is met. 285 * loops back and reads them again until this criteria is met.
286 * We expect the caller to have done the first increment of
287 * vdso_data->tb_update_count already.
286 */ 288 */
287 ++(vdso_data->tb_update_count);
288 smp_wmb();
289 vdso_data->tb_orig_stamp = new_tb_stamp; 289 vdso_data->tb_orig_stamp = new_tb_stamp;
290 vdso_data->stamp_xsec = new_stamp_xsec; 290 vdso_data->stamp_xsec = new_stamp_xsec;
291 vdso_data->tb_to_xs = new_tb_to_xs; 291 vdso_data->tb_to_xs = new_tb_to_xs;
@@ -310,20 +310,15 @@ static __inline__ void timer_recalc_offset(u64 cur_tb)
310 unsigned long offset; 310 unsigned long offset;
311 u64 new_stamp_xsec; 311 u64 new_stamp_xsec;
312 u64 tlen, t2x; 312 u64 tlen, t2x;
313 u64 tb, xsec_old, xsec_new;
314 struct gettimeofday_vars *varp;
313 315
314 if (__USE_RTC()) 316 if (__USE_RTC())
315 return; 317 return;
316 tlen = current_tick_length(); 318 tlen = current_tick_length();
317 offset = cur_tb - do_gtod.varp->tb_orig_stamp; 319 offset = cur_tb - do_gtod.varp->tb_orig_stamp;
318 if (tlen == last_tick_len && offset < 0x80000000u) { 320 if (tlen == last_tick_len && offset < 0x80000000u)
319 /* check that we're still in sync; if not, resync */ 321 return;
320 struct timeval tv;
321 __do_gettimeofday(&tv, cur_tb);
322 if (tv.tv_sec <= xtime.tv_sec &&
323 (tv.tv_sec < xtime.tv_sec ||
324 tv.tv_usec * 1000 <= xtime.tv_nsec))
325 return;
326 }
327 if (tlen != last_tick_len) { 322 if (tlen != last_tick_len) {
328 t2x = mulhdu(tlen << TICKLEN_SHIFT, ticklen_to_xs); 323 t2x = mulhdu(tlen << TICKLEN_SHIFT, ticklen_to_xs);
329 last_tick_len = tlen; 324 last_tick_len = tlen;
@@ -332,6 +327,21 @@ static __inline__ void timer_recalc_offset(u64 cur_tb)
332 new_stamp_xsec = (u64) xtime.tv_nsec * XSEC_PER_SEC; 327 new_stamp_xsec = (u64) xtime.tv_nsec * XSEC_PER_SEC;
333 do_div(new_stamp_xsec, 1000000000); 328 do_div(new_stamp_xsec, 1000000000);
334 new_stamp_xsec += (u64) xtime.tv_sec * XSEC_PER_SEC; 329 new_stamp_xsec += (u64) xtime.tv_sec * XSEC_PER_SEC;
330
331 ++vdso_data->tb_update_count;
332 smp_mb();
333
334 /*
335 * Make sure time doesn't go backwards for userspace gettimeofday.
336 */
337 tb = get_tb();
338 varp = do_gtod.varp;
339 xsec_old = mulhdu(tb - varp->tb_orig_stamp, varp->tb_to_xs)
340 + varp->stamp_xsec;
341 xsec_new = mulhdu(tb - cur_tb, t2x) + new_stamp_xsec;
342 if (xsec_new < xsec_old)
343 new_stamp_xsec += xsec_old - xsec_new;
344
335 update_gtod(cur_tb, new_stamp_xsec, t2x); 345 update_gtod(cur_tb, new_stamp_xsec, t2x);
336} 346}
337 347
@@ -564,6 +574,10 @@ int do_settimeofday(struct timespec *tv)
564 } 574 }
565#endif 575#endif
566 576
577 /* Make userspace gettimeofday spin until we're done. */
578 ++vdso_data->tb_update_count;
579 smp_mb();
580
567 /* 581 /*
568 * Subtract off the number of nanoseconds since the 582 * Subtract off the number of nanoseconds since the
569 * beginning of the last tick. 583 * beginning of the last tick.
@@ -724,10 +738,16 @@ void __init time_init(void)
724 * It is computed as: 738 * It is computed as:
725 * ticklen_to_xs = 2^N / (tb_ticks_per_jiffy * 1e9) 739 * ticklen_to_xs = 2^N / (tb_ticks_per_jiffy * 1e9)
726 * where N = 64 + 20 - TICKLEN_SCALE - TICKLEN_SHIFT 740 * where N = 64 + 20 - TICKLEN_SCALE - TICKLEN_SHIFT
727 * so as to give the result as a 0.64 fixed-point fraction. 741 * which turns out to be N = 51 - SHIFT_HZ.
742 * This gives the result as a 0.64 fixed-point fraction.
743 * That value is reduced by an offset amounting to 1 xsec per
744 * 2^31 timebase ticks to avoid problems with time going backwards
745 * by 1 xsec when we do timer_recalc_offset due to losing the
746 * fractional xsec. That offset is equal to ppc_tb_freq/2^51
747 * since there are 2^20 xsec in a second.
728 */ 748 */
729 div128_by_32(1ULL << (64 + 20 - TICKLEN_SCALE - TICKLEN_SHIFT), 0, 749 div128_by_32((1ULL << 51) - ppc_tb_freq, 0,
730 tb_ticks_per_jiffy, &res); 750 tb_ticks_per_jiffy << SHIFT_HZ, &res);
731 div128_by_32(res.result_high, res.result_low, NSEC_PER_SEC, &res); 751 div128_by_32(res.result_high, res.result_low, NSEC_PER_SEC, &res);
732 ticklen_to_xs = res.result_low; 752 ticklen_to_xs = res.result_low;
733 753