Wall clock accuracy

Rune Torgersen runet at innovsys.com
Wed Aug 10 01:23:19 EST 2005


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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: WallClock-fix
Type: application/octet-stream
Size: 3451 bytes
Desc: WallClock-fix
Url : http://ozlabs.org/pipermail/linuxppc-embedded/attachments/20050809/a63c7e89/attachment.obj 


More information about the Linuxppc-embedded mailing list