Wall clock accuracy
From: Rune Torgersen <hidden>
Date: 2005-08-09 15:35:27
Hi I have discovered that the accuracy of the wallclock (xtime) on ppc is not very good. I am running a custom telco board based on a 8266, and the main busclock is derived off of a T1 reference clock. I was noticing a huge number of logentries fron OpenNTPD about adjustiing the clock, so I started to check. The drift of the walltime was a little over 7 seconds in 15 hours (7 seconds slow) (equals about 130us per s) When I checked around, I found that xtime is updated in kernel/time.c and is updated by time_nsec nanoseconds per timer interrupt. This value is hardcoded (through some obscure macros) to be 999848 ns/timertick. Tis is 152ns slow per timertick. This works out to be 152us per second, almos the value I found. I have tried a few workarounds, and made one that modifies time_nsec before update_wall_time_one_tick() is called. With this fix (which is 8260 specific at the time) I have less than 1 ms drift after 5 days. The fix works by calculating how many ns and fractions of ns there is in one timer-tick (one jiffie). Then, at each timer interrupt, I accumulate the ns fractions, and add whole ns to the timer when needed. This is tested on 8266 under 80.00MHz and 82.575360MHz main busclock. It also works correct if HZ=1024 instead of 1000 It is not meant as a patch to be added to the kernel right now, but more of a possible solution to a problem that it doesn't seem anybody has noticed before. Index: arch/ppc/kernel/time.c ===================================================================
--- arch/ppc/kernel/time.c (revision 20)
+++ arch/ppc/kernel/time.c (working copy)@@ -119,6 +119,11 @@ EXPORT_SYMBOL(profile_pc); #endif + +#ifdef CONFIG_8260 +extern unsigned long get_ns_delta(void); /*
arch/ppc/syslib/m8260_setup.c */ +extern unsigned long tick_nsec; /* kernel/time.c */ +#endif /* * timer_interrupt - gets called when the decrementer overflows, * with interrupts disabled.
@@ -148,6 +153,10 @@ /* We are in an interrupt, no need to save/restore flags
*/ write_seqlock(&xtime_lock); tb_last_stamp = jiffy_stamp; + +#ifdef CONFIG_8260 + tick_nsec = get_ns_delta(); +#endif do_timer(regs); /*
@@ -179,7 +188,7 @@ write_sequnlock(&xtime_lock); } if ( !disarm_decr[smp_processor_id()] ) - set_dec(next_dec); + set_dec(next_dec-1); last_jiffy_stamp(cpu) = jiffy_stamp; if (ppc_md.heartbeat && !ppc_md.heartbeat_count--)
Index: arch/ppc/syslib/m8260_setup.c ===================================================================
--- arch/ppc/syslib/m8260_setup.c (revision 20)
+++ arch/ppc/syslib/m8260_setup.c (working copy)@@ -31,6 +31,8 @@ #include "cpm2_pic.h" +#include <linux/module.h> + unsigned char __res[sizeof(bd_t)]; extern void cpm2_reset(void);
@@ -82,16 +84,63 @@ /* The decrementer counts at the system (internal) clock frequency * divided by four. */ +unsigned long tb_time_nsec; +unsigned long tb_frac_nsec; +unsigned long tb_frac_limit; + +extern uint32_t __div64_32(uint64_t *dividend, uint32_t divisor); + static void __init m8260_calibrate_decr(void) +{ + bd_t *binfo = (bd_t *)__res; + int freq, divisor, tick_freq; + uint64_t tmp; + printk("Calibrating Decrementer\n"); + + freq = binfo->bi_busfreq; + divisor = 4; + + /* get rounded off decrementer frequency */ + tick_freq = (freq + 3) / 4; + + /* get number of timebase ticks per jiffy (timerint) */ + tb_ticks_per_jiffy = (tick_freq + (HZ/2)) / HZ; + tb_to_us = mulhwu_scale_factor(tick_freq, 1000000); + + /* calculate timer interrupt interval in nano seconds */ + tmp = 1000000000ULL * (u64)tb_ticks_per_jiffy; + __div64_32(&tmp, tick_freq); + tb_time_nsec = tmp; + + /* calculate fractions of nanosecons per timer interrupt */ + tb_frac_limit = 100000UL; + tmp = 1000000000ULL * (u64)tb_ticks_per_jiffy * tb_frac_limit; + __div64_32(&tmp, tick_freq); + tb_frac_nsec = (u32)(tmp - (u64)tb_time_nsec * (u64)tb_frac_limit); + + printk("busfreq = %u\n", freq); + printk("tb_ticks_per_jiffy = %u\ntb_to_us = %u\n",
tb_ticks_per_jiffy, tb_to_us);
+ printk("tick_freq = %u\ntime_nsec = %u\ntb_frac_nsec = %u\n",
tick_freq, tb_time_nsec, tb_frac_nsec);
+}
+
+unsigned long get_ns_delta(void)
{
- bd_t *binfo = (bd_t *)__res;
- int freq, divisor;
+ static uint32_t frac = 0;
- freq = binfo->bi_busfreq;
- divisor = 4;
- tb_ticks_per_jiffy = freq / HZ / divisor;
- tb_to_us = mulhwu_scale_factor(freq / divisor, 1000000);
+ unsigned long delta_ns;
+
+ delta_ns = tb_time_nsec;
+
+ /* adjust for inaccuracy in calculation */
+ frac += tb_frac_nsec;
+ if (frac >= tb_frac_limit)
+ {
+ delta_ns += frac / tb_frac_limit;
+ frac %= tb_frac_limit;
+ }
+
+ return delta_ns;
}
/* The 8260 has an internal 1-second timer update register that
Rune Torgersen
System Developer
Innovative Systems LLC
1000 Innovative Drive
Mitchell, SD 57301
Ph: 605-995-6120
www.innovsys.com