Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[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>

authored by

Nathan Lynch and committed by
Paul Mackerras
5db9fa95 aa74a30b

+26 -8
+17 -8
arch/powerpc/kernel/time.c
··· 417 417 /* 418 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 422 unsigned long sec, usec; 423 423 u64 tb_ticks, xsec; ··· 431 431 * without a divide (and in fact, without a multiply) 432 432 */ 433 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 440 temp_tb_to_xs = temp_varp->tb_to_xs; 436 441 temp_stamp_xsec = temp_varp->stamp_xsec; 437 442 xsec = temp_stamp_xsec + mulhdu(tb_ticks, temp_tb_to_xs); ··· 469 464 tv->tv_usec = usec; 470 465 return; 471 466 } 472 - __do_gettimeofday(tv, get_tb()); 467 + __do_gettimeofday(tv); 473 468 } 474 469 475 470 EXPORT_SYMBOL(do_gettimeofday); ··· 655 650 int next_dec; 656 651 int cpu = smp_processor_id(); 657 652 unsigned long ticks; 653 + u64 tb_next_jiffy; 658 654 659 655 #ifdef CONFIG_PPC32 660 656 if (atomic_read(&ppc_n_lost_interrupts) != 0) ··· 697 691 continue; 698 692 699 693 write_seqlock(&xtime_lock); 700 - tb_last_jiffy += tb_ticks_per_jiffy; 701 - tb_last_stamp = per_cpu(last_jiffy, cpu); 702 - do_timer(regs); 703 - timer_recalc_offset(tb_last_jiffy); 704 - timer_check_rtc(); 694 + tb_next_jiffy = tb_last_jiffy + tb_ticks_per_jiffy; 695 + if (per_cpu(last_jiffy, cpu) >= tb_next_jiffy) { 696 + tb_last_jiffy = tb_next_jiffy; 697 + tb_last_stamp = per_cpu(last_jiffy, cpu); 698 + do_timer(regs); 699 + timer_recalc_offset(tb_last_jiffy); 700 + timer_check_rtc(); 701 + } 705 702 write_sequnlock(&xtime_lock); 706 703 } 707 704
+9
include/asm-powerpc/system.h
··· 53 53 #define smp_read_barrier_depends() do { } while(0) 54 54 #endif /* CONFIG_SMP */ 55 55 56 + /* 57 + * This is a barrier which prevents following instructions from being 58 + * started until the value of the argument x is known. For example, if 59 + * x is a variable loaded from memory, this prevents following 60 + * instructions from being executed until the load has been performed. 61 + */ 62 + #define data_barrier(x) \ 63 + asm volatile("twi 0,%0,0; isync" : : "r" (x) : "memory"); 64 + 56 65 struct task_struct; 57 66 struct pt_regs; 58 67