[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>
This commit is contained in:
committed by
Paul Mackerras
parent
aa74a30be9
commit
5db9fa9593
@@ -417,7 +417,7 @@ static __inline__ void timer_check_rtc(void)
|
|||||||
/*
|
/*
|
||||||
* This version of gettimeofday has microsecond resolution.
|
* This version of gettimeofday has microsecond resolution.
|
||||||
*/
|
*/
|
||||||
static inline void __do_gettimeofday(struct timeval *tv, u64 tb_val)
|
static inline void __do_gettimeofday(struct timeval *tv)
|
||||||
{
|
{
|
||||||
unsigned long sec, usec;
|
unsigned long sec, usec;
|
||||||
u64 tb_ticks, xsec;
|
u64 tb_ticks, xsec;
|
||||||
@@ -431,7 +431,12 @@ static inline void __do_gettimeofday(struct timeval *tv, u64 tb_val)
|
|||||||
* without a divide (and in fact, without a multiply)
|
* without a divide (and in fact, without a multiply)
|
||||||
*/
|
*/
|
||||||
temp_varp = do_gtod.varp;
|
temp_varp = do_gtod.varp;
|
||||||
tb_ticks = tb_val - temp_varp->tb_orig_stamp;
|
|
||||||
|
/* Sampling the time base must be done after loading
|
||||||
|
* do_gtod.varp in order to avoid racing with update_gtod.
|
||||||
|
*/
|
||||||
|
data_barrier(temp_varp);
|
||||||
|
tb_ticks = get_tb() - temp_varp->tb_orig_stamp;
|
||||||
temp_tb_to_xs = temp_varp->tb_to_xs;
|
temp_tb_to_xs = temp_varp->tb_to_xs;
|
||||||
temp_stamp_xsec = temp_varp->stamp_xsec;
|
temp_stamp_xsec = temp_varp->stamp_xsec;
|
||||||
xsec = temp_stamp_xsec + mulhdu(tb_ticks, temp_tb_to_xs);
|
xsec = temp_stamp_xsec + mulhdu(tb_ticks, temp_tb_to_xs);
|
||||||
@@ -464,7 +469,7 @@ void do_gettimeofday(struct timeval *tv)
|
|||||||
tv->tv_usec = usec;
|
tv->tv_usec = usec;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
__do_gettimeofday(tv, get_tb());
|
__do_gettimeofday(tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(do_gettimeofday);
|
EXPORT_SYMBOL(do_gettimeofday);
|
||||||
@@ -650,6 +655,7 @@ void timer_interrupt(struct pt_regs * regs)
|
|||||||
int next_dec;
|
int next_dec;
|
||||||
int cpu = smp_processor_id();
|
int cpu = smp_processor_id();
|
||||||
unsigned long ticks;
|
unsigned long ticks;
|
||||||
|
u64 tb_next_jiffy;
|
||||||
|
|
||||||
#ifdef CONFIG_PPC32
|
#ifdef CONFIG_PPC32
|
||||||
if (atomic_read(&ppc_n_lost_interrupts) != 0)
|
if (atomic_read(&ppc_n_lost_interrupts) != 0)
|
||||||
@@ -691,11 +697,14 @@ void timer_interrupt(struct pt_regs * regs)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
write_seqlock(&xtime_lock);
|
write_seqlock(&xtime_lock);
|
||||||
tb_last_jiffy += tb_ticks_per_jiffy;
|
tb_next_jiffy = tb_last_jiffy + tb_ticks_per_jiffy;
|
||||||
|
if (per_cpu(last_jiffy, cpu) >= tb_next_jiffy) {
|
||||||
|
tb_last_jiffy = tb_next_jiffy;
|
||||||
tb_last_stamp = per_cpu(last_jiffy, cpu);
|
tb_last_stamp = per_cpu(last_jiffy, cpu);
|
||||||
do_timer(regs);
|
do_timer(regs);
|
||||||
timer_recalc_offset(tb_last_jiffy);
|
timer_recalc_offset(tb_last_jiffy);
|
||||||
timer_check_rtc();
|
timer_check_rtc();
|
||||||
|
}
|
||||||
write_sequnlock(&xtime_lock);
|
write_sequnlock(&xtime_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -53,6 +53,15 @@
|
|||||||
#define smp_read_barrier_depends() do { } while(0)
|
#define smp_read_barrier_depends() do { } while(0)
|
||||||
#endif /* CONFIG_SMP */
|
#endif /* CONFIG_SMP */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a barrier which prevents following instructions from being
|
||||||
|
* started until the value of the argument x is known. For example, if
|
||||||
|
* x is a variable loaded from memory, this prevents following
|
||||||
|
* instructions from being executed until the load has been performed.
|
||||||
|
*/
|
||||||
|
#define data_barrier(x) \
|
||||||
|
asm volatile("twi 0,%0,0; isync" : : "r" (x) : "memory");
|
||||||
|
|
||||||
struct task_struct;
|
struct task_struct;
|
||||||
struct pt_regs;
|
struct pt_regs;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user