diff options
author | Nathan Lynch <ntl@pobox.com> | 2006-08-22 21:36:05 -0400 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2006-08-23 01:51:18 -0400 |
commit | 5db9fa9593e2ff69f2b95f9d59229dc4faaa564d (patch) | |
tree | d58ba58c05408edf8993d0da5a31b3ea3df1a053 /arch/powerpc | |
parent | aa74a30be971c632d734e487df42278b1cf85151 (diff) |
[POWERPC] Fix gettimeofday inaccuracies
There are two problems in the powerpc gettimeofday code which can
cause incorrect results to be returned.
The first is that there is a race between do_gettimeofday and the
timer interrupt:
1. do_gettimeofday does get_tb()
2. decrementer exception on boot cpu which runs timer_recalc_offset,
which also samples the timebase and updates the do_gtod structure
with a greater timebase value.
3. do_gettimeofday calls __do_gettimeofday, which leads to the
negative result from tb_val - temp_varp->tb_orig_stamp.
The second is caused by taking the boot cpu offline, which can cause
the value of tb_last_jiffy to be increased past the currently
available timebase, causing the same underflow as above.
[paulus@samba.org - define and use data_barrier() instead of mb().]
Signed-off-by: Nathan Lynch <ntl@pobox.com>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc')
-rw-r--r-- | arch/powerpc/kernel/time.c | 25 |
1 files changed, 17 insertions, 8 deletions
diff --git a/arch/powerpc/kernel/time.c b/arch/powerpc/kernel/time.c index 774c0a3c5019..18e59e43d2b3 100644 --- a/arch/powerpc/kernel/time.c +++ b/arch/powerpc/kernel/time.c | |||
@@ -417,7 +417,7 @@ static __inline__ void timer_check_rtc(void) | |||
417 | /* | 417 | /* |
418 | * This version of gettimeofday has microsecond resolution. | 418 | * This version of gettimeofday has microsecond resolution. |
419 | */ | 419 | */ |
420 | static inline void __do_gettimeofday(struct timeval *tv, u64 tb_val) | 420 | static inline void __do_gettimeofday(struct timeval *tv) |
421 | { | 421 | { |
422 | unsigned long sec, usec; | 422 | unsigned long sec, usec; |
423 | u64 tb_ticks, xsec; | 423 | u64 tb_ticks, xsec; |
@@ -431,7 +431,12 @@ static inline void __do_gettimeofday(struct timeval *tv, u64 tb_val) | |||
431 | * without a divide (and in fact, without a multiply) | 431 | * without a divide (and in fact, without a multiply) |
432 | */ | 432 | */ |
433 | temp_varp = do_gtod.varp; | 433 | temp_varp = do_gtod.varp; |
434 | tb_ticks = tb_val - temp_varp->tb_orig_stamp; | 434 | |
435 | /* Sampling the time base must be done after loading | ||
436 | * do_gtod.varp in order to avoid racing with update_gtod. | ||
437 | */ | ||
438 | data_barrier(temp_varp); | ||
439 | tb_ticks = get_tb() - temp_varp->tb_orig_stamp; | ||
435 | temp_tb_to_xs = temp_varp->tb_to_xs; | 440 | temp_tb_to_xs = temp_varp->tb_to_xs; |
436 | temp_stamp_xsec = temp_varp->stamp_xsec; | 441 | temp_stamp_xsec = temp_varp->stamp_xsec; |
437 | xsec = temp_stamp_xsec + mulhdu(tb_ticks, temp_tb_to_xs); | 442 | xsec = temp_stamp_xsec + mulhdu(tb_ticks, temp_tb_to_xs); |
@@ -464,7 +469,7 @@ void do_gettimeofday(struct timeval *tv) | |||
464 | tv->tv_usec = usec; | 469 | tv->tv_usec = usec; |
465 | return; | 470 | return; |
466 | } | 471 | } |
467 | __do_gettimeofday(tv, get_tb()); | 472 | __do_gettimeofday(tv); |
468 | } | 473 | } |
469 | 474 | ||
470 | EXPORT_SYMBOL(do_gettimeofday); | 475 | EXPORT_SYMBOL(do_gettimeofday); |
@@ -650,6 +655,7 @@ void timer_interrupt(struct pt_regs * regs) | |||
650 | int next_dec; | 655 | int next_dec; |
651 | int cpu = smp_processor_id(); | 656 | int cpu = smp_processor_id(); |
652 | unsigned long ticks; | 657 | unsigned long ticks; |
658 | u64 tb_next_jiffy; | ||
653 | 659 | ||
654 | #ifdef CONFIG_PPC32 | 660 | #ifdef CONFIG_PPC32 |
655 | if (atomic_read(&ppc_n_lost_interrupts) != 0) | 661 | if (atomic_read(&ppc_n_lost_interrupts) != 0) |
@@ -691,11 +697,14 @@ void timer_interrupt(struct pt_regs * regs) | |||
691 | continue; | 697 | continue; |
692 | 698 | ||
693 | write_seqlock(&xtime_lock); | 699 | write_seqlock(&xtime_lock); |
694 | tb_last_jiffy += tb_ticks_per_jiffy; | 700 | tb_next_jiffy = tb_last_jiffy + tb_ticks_per_jiffy; |
695 | tb_last_stamp = per_cpu(last_jiffy, cpu); | 701 | if (per_cpu(last_jiffy, cpu) >= tb_next_jiffy) { |
696 | do_timer(regs); | 702 | tb_last_jiffy = tb_next_jiffy; |
697 | timer_recalc_offset(tb_last_jiffy); | 703 | tb_last_stamp = per_cpu(last_jiffy, cpu); |
698 | timer_check_rtc(); | 704 | do_timer(regs); |
705 | timer_recalc_offset(tb_last_jiffy); | ||
706 | timer_check_rtc(); | ||
707 | } | ||
699 | write_sequnlock(&xtime_lock); | 708 | write_sequnlock(&xtime_lock); |
700 | } | 709 | } |
701 | 710 | ||