From: Dave Johnson <djohnson+linuxmips@xxxxxxxxxxxxxxxxxxxxxx> [MIPS] Fix for gettimeofday jumping backwards, then forwards. Below are 2 fixes to do with time jumping around as reported by gettimeofday(). One is SB1250 specific and one appears generic. The symptom is revealed by running multile copies (1 per cpu) of a simple test program that calls gettimeofday() as fast as possible looking for time to go backwards. When a jump is detected the program outputs a few samples before and after each jump: value delta 1121781527.912525: 1 1121781527.912525: 0 1121781527.912526: 1 1121781527.912526: 0 1121781527.912527: 1 1121781527.912527: 0 1121781527.912527: 0 1121781527.912527: 0 1121781527.911528: -999 1121781527.911529: 1 1121781527.911530: 1 1121781527.912532: 1002 1121781527.912533: 1 1121781527.912533: 0 1121781527.912534: 1 1121781527.912534: 0 1121781527.912535: 1 1121781527.912536: 1 value delta 1121781545.635524: 1 1121781545.635524: 0 1121781545.635525: 1 1121781545.635525: 0 1121781545.635526: 1 1121781545.635526: 0 1121781545.635527: 1 1121781545.635527: 0 1121781545.634527: -1000 1121781545.635527: 1000 1121781545.635528: 1 1121781545.635529: 1 1121781545.635529: 0 1121781545.635530: 1 1121781545.635530: 0 1121781545.635531: 1 1121781545.635531: 0 1121781545.635532: 1 1121781545.635533: 1 Time jumps backwards 1msec then forwards 1msec a few usec later. Usually lasts < 2us but I've seen it as long as 5us if the system is under load. First problem I found is that sb1250_gettimeoffset() simply reads the current cpu 0 timer remaining value, however once this counter reaches 0 and the interrupt is raised, it immediately resets and begins to count down again. If sb1250_gettimeoffset() is called on cpu 1 via do_gettimeofday() after the timer has reset but prior to cpu 0 processing the interrupt and taking write_seqlock() in timer_interrupt() it will return a full value (or close to it) causing time to jump backwards 1ms. Once cpu 0 handles the interrupt and timer_interrupt() gets far enough along it will jump forward 1ms. To fix this problem I implemented mips_hpt_*() on sb1250 using a spare timer unrelated to the existing periodic interrupt timers. It runs at 1Mhz with a full 23bit counter. This eliminated the custom do_gettimeoffset() for sb1250 and allowed use of the generic fixed_rate_gettimeoffset() using mips_hpt_*() and timerhi/timerlo. The second problem is that more of timer_interrupt() needs to be protected by xtime_lock: * do_timer() expects the arch-specific handler to take the lock as it modifies jiffies[_64] and xtime. * writing timerhi/lo in timer_interrupt() will mess up fixed_rate_gettimeoffset() which reads timerhi/lo. With both changes do_gettimeofday() works correctly on both cpu 0 and cpu 1. Other changes/cleanups: The existing sb1250 periodic timers were slow by 999ppm (given a perfect 100mhz reference). The timers need to be loaded with 1 less than the desired interval not the interval itself. M_SCD_TIMER_INIT and M_SCD_TIMER_CNT had the wrong field width (should be 23 bits not 20 bits) Signed-off-by: Dave Johnson <djohnson+linuxmips@xxxxxxxxxxxxxxxxxxxxxx> Signed-off-by: Martin Michlmayr <tbm@xxxxxxxxxx> --- arch/mips/kernel/time.c | 6 +- arch/mips/sibyte/sb1250/time.c | 77 ++++++++++++++++++++++++++--------- arch/mips/sibyte/swarm/setup.c | 7 +++ include/asm-mips/sibyte/sb1250.h | 2 include/asm-mips/sibyte/sb1250_scd.h | 5 +- 5 files changed, 73 insertions(+), 24 deletions(-) diff -urN linux-mips/arch/mips/kernel/time.c new/arch/mips/kernel/time.c --- linux-mips/arch/mips/kernel/time.c 2006-01-10 11:21:15.000000000 +0000 +++ new/arch/mips/kernel/time.c 2006-01-16 16:40:10.000000000 +0000 @@ -423,6 +423,8 @@ unsigned long j; unsigned int count; + write_seqlock(&xtime_lock); + count = mips_hpt_read(); mips_timer_ack(); @@ -440,7 +442,6 @@ * CMOS clock accordingly every ~11 minutes. rtc_set_time() has to be * called as close as possible to 500 ms before the new second starts. */ - write_seqlock(&xtime_lock); if (ntp_synced() && xtime.tv_sec > last_rtc_update + 660 && (xtime.tv_nsec / 1000) >= 500000 - ((unsigned) TICK_SIZE) / 2 && @@ -452,7 +453,6 @@ last_rtc_update = xtime.tv_sec - 600; } } - write_sequnlock(&xtime_lock); /* * If jiffies has overflown in this timer_interrupt, we must @@ -495,6 +495,8 @@ } } + write_sequnlock(&xtime_lock); + /* * In UP mode, we call local_timer_interrupt() to do profiling * and process accouting. diff -urN linux-mips/arch/mips/sibyte/sb1250/time.c new/arch/mips/sibyte/sb1250/time.c --- linux-mips/arch/mips/sibyte/sb1250/time.c 2006-01-10 11:21:16.000000000 +0000 +++ new/arch/mips/sibyte/sb1250/time.c 2006-01-16 16:39:27.000000000 +0000 @@ -47,23 +47,51 @@ #define IMR_IP3_VAL K_INT_MAP_I1 #define IMR_IP4_VAL K_INT_MAP_I2 +#define SB1250_HPT_NUM 3 +#define SB1250_HPT_VALUE M_SCD_TIMER_CNT /* max value */ +#define SB1250_HPT_SHIFT ((sizeof(unsigned int)*8)-V_SCD_TIMER_WIDTH) + + extern int sb1250_steal_irq(int irq); +static unsigned int sb1250_hpt_read(void); +static void sb1250_hpt_init(unsigned int); + +static unsigned int hpt_offset; + +void __init sb1250_hpt_setup(void) +{ + int cpu = smp_processor_id(); + + if (!cpu) { + /* Setup hpt using timer #3 but do not enable irq for it */ + __raw_writeq(0, IOADDR(A_SCD_TIMER_REGISTER(SB1250_HPT_NUM, R_SCD_TIMER_CFG))); + __raw_writeq(SB1250_HPT_VALUE, + IOADDR(A_SCD_TIMER_REGISTER(SB1250_HPT_NUM, R_SCD_TIMER_INIT))); + __raw_writeq(M_SCD_TIMER_ENABLE | M_SCD_TIMER_MODE_CONTINUOUS, + IOADDR(A_SCD_TIMER_REGISTER(SB1250_HPT_NUM, R_SCD_TIMER_CFG))); + + /* + * we need to fill 32 bits, so just use the upper 23 bits and pretend + * the timer is going 512Mhz instead of 1Mhz + */ + mips_hpt_frequency = V_SCD_TIMER_FREQ << SB1250_HPT_SHIFT; + mips_hpt_init = sb1250_hpt_init; + mips_hpt_read = sb1250_hpt_read; + } +} + + void sb1250_time_init(void) { int cpu = smp_processor_id(); int irq = K_INT_TIMER_0+cpu; - /* Only have 4 general purpose timers */ - if (cpu > 3) { + /* Only have 4 general purpose timers, and we use last one as hpt */ + if (cpu > 2) { BUG(); } - if (!cpu) { - /* Use our own gettimeoffset() routine */ - do_gettimeoffset = sb1250_gettimeoffset; - } - sb1250_mask_irq(cpu, irq); /* Map the timer interrupt to ip[4] of this cpu */ @@ -75,10 +103,10 @@ /* Disable the timer and set up the count */ __raw_writeq(0, IOADDR(A_SCD_TIMER_REGISTER(cpu, R_SCD_TIMER_CFG))); #ifdef CONFIG_SIMULATION - __raw_writeq(50000 / HZ, + __raw_writeq((50000 / HZ) - 1, IOADDR(A_SCD_TIMER_REGISTER(cpu, R_SCD_TIMER_INIT))); #else - __raw_writeq(1000000 / HZ, + __raw_writeq((V_SCD_TIMER_FREQ / HZ) - 1, IOADDR(A_SCD_TIMER_REGISTER(cpu, R_SCD_TIMER_INIT))); #endif @@ -103,7 +131,7 @@ int cpu = smp_processor_id(); int irq = K_INT_TIMER_0 + cpu; - /* Reset the timer */ + /* ACK interrupt */ ____raw_writeq(M_SCD_TIMER_ENABLE | M_SCD_TIMER_MODE_CONTINUOUS, IOADDR(A_SCD_TIMER_REGISTER(cpu, R_SCD_TIMER_CFG))); @@ -122,15 +150,26 @@ } /* - * We use our own do_gettimeoffset() instead of the generic one, - * because the generic one does not work for SMP case. - * In addition, since we use general timer 0 for system time, - * we can get accurate intra-jiffy offset without calibration. + * The HPT is free running from SB1250_HPT_VALUE down to 0 then starts over + * again. There's no easy way to set to a specific value so store init value + * in hpt_offset and subtract each time. + * + * Note: Timer isn't full 32bits so shift it into the upper part making + * it appear to run at a higher frequency. */ -unsigned long sb1250_gettimeoffset(void) +static unsigned int sb1250_hpt_read(void) { - unsigned long count = - __raw_readq(IOADDR(A_SCD_TIMER_REGISTER(0, R_SCD_TIMER_CNT))); + unsigned int count; - return 1000000/HZ - count; - } + count = G_SCD_TIMER_CNT(__raw_readq(IOADDR(A_SCD_TIMER_REGISTER(SB1250_HPT_NUM, R_SCD_TIMER_CNT)))); + + count = (SB1250_HPT_VALUE - count) << SB1250_HPT_SHIFT; + + return count - hpt_offset; +} + +static void sb1250_hpt_init(unsigned int count) +{ + hpt_offset = count; + return; +} diff -urN linux-mips/arch/mips/sibyte/swarm/setup.c new/arch/mips/sibyte/swarm/setup.c --- linux-mips/arch/mips/sibyte/swarm/setup.c 2006-01-10 11:21:16.000000000 +0000 +++ new/arch/mips/sibyte/swarm/setup.c 2006-01-16 16:39:27.000000000 +0000 @@ -70,6 +70,12 @@ return "SiByte " SIBYTE_BOARD_NAME; } +void __init swarm_time_init(void) +{ + /* Setup HPT */ + sb1250_hpt_setup(); +} + void __init swarm_timer_setup(struct irqaction *irq) { /* @@ -109,6 +115,7 @@ panic_timeout = 5; /* For debug. */ + board_time_init = swarm_time_init; board_timer_setup = swarm_timer_setup; board_be_handler = swarm_be_handler; diff -urN linux-mips/include/asm-mips/sibyte/sb1250.h new/include/asm-mips/sibyte/sb1250.h --- linux-mips/include/asm-mips/sibyte/sb1250.h 2006-01-10 11:21:59.000000000 +0000 +++ new/include/asm-mips/sibyte/sb1250.h 2006-01-16 16:39:27.000000000 +0000 @@ -45,8 +45,8 @@ extern unsigned int periph_rev; extern unsigned int zbbus_mhz; +extern void sb1250_hpt_setup(void); extern void sb1250_time_init(void); -extern unsigned long sb1250_gettimeoffset(void); extern void sb1250_mask_irq(int cpu, int irq); extern void sb1250_unmask_irq(int cpu, int irq); extern void sb1250_smp_finish(void); diff -urN linux-mips/include/asm-mips/sibyte/sb1250_scd.h new/include/asm-mips/sibyte/sb1250_scd.h --- linux-mips/include/asm-mips/sibyte/sb1250_scd.h 2006-01-10 11:21:59.000000000 +0000 +++ new/include/asm-mips/sibyte/sb1250_scd.h 2006-01-16 16:39:27.000000000 +0000 @@ -359,14 +359,15 @@ */ #define V_SCD_TIMER_FREQ 1000000 +#define V_SCD_TIMER_WIDTH 23 #define S_SCD_TIMER_INIT 0 -#define M_SCD_TIMER_INIT _SB_MAKEMASK(20,S_SCD_TIMER_INIT) +#define M_SCD_TIMER_INIT _SB_MAKEMASK(V_SCD_TIMER_WIDTH,S_SCD_TIMER_INIT) #define V_SCD_TIMER_INIT(x) _SB_MAKEVALUE(x,S_SCD_TIMER_INIT) #define G_SCD_TIMER_INIT(x) _SB_GETVALUE(x,S_SCD_TIMER_INIT,M_SCD_TIMER_INIT) #define S_SCD_TIMER_CNT 0 -#define M_SCD_TIMER_CNT _SB_MAKEMASK(20,S_SCD_TIMER_CNT) +#define M_SCD_TIMER_CNT _SB_MAKEMASK(V_SCD_TIMER_WIDTH,S_SCD_TIMER_CNT) #define V_SCD_TIMER_CNT(x) _SB_MAKEVALUE(x,S_SCD_TIMER_CNT) #define G_SCD_TIMER_CNT(x) _SB_GETVALUE(x,S_SCD_TIMER_CNT,M_SCD_TIMER_CNT) -- Martin Michlmayr http://www.cyrius.com/