Subject: [PATCH] get time right for SMP machines

From: Jun Sun <[email-masked]>
Date: Mon, 9 Jun 2003 19:18:31 -0700

Fixes a couple of things:

1) extend xtime_lock protection to cover do_timer() call (the same
   as in i386 arch).  This enables other CPUs to use jiffie in a sane way.

2) It was not quite right to deliver ll_timer_interrupt() and
   ll_local_timer_interrupt() as two separate interrupts, because
   it may cause bottom half to execute ahead of the second interrupt.

3) for bcm1250, we now use zb bus counter for intra-jiffie offset.
   No more time running backward problem. 

(TODO, I think I probably need to check the chip revision to make
sure zb bus counter is there.  Otherwise we can use null_gettimeoffset())

The patch should apply to 64bit and 2.5 as well.  Comments?

Jun 
diff -Nru linux/arch/mips/kernel/time.c.orig linux/arch/mips/kernel/time.c
--- linux/arch/mips/kernel/time.c.orig	Fri Jun  6 12:44:08 2003
+++ linux/arch/mips/kernel/time.c	Fri Jun  6 12:45:19 2003
@@ -17,6 +17,7 @@
 #include <linux/sched.h>
 #include <linux/param.h>
 #include <linux/time.h>
+#include <linux/timer.h>
 #include <linux/smp.h>
 #include <linux/kernel_stat.h>
 #include <linux/spinlock.h>
@@ -79,7 +80,7 @@
 	 * is nonzero if the timer bottom half hasnt executed yet.
 	 */
 	if (jiffies - wall_jiffies)
-		tv->tv_usec += USECS_PER_JIFFY;
+		tv->tv_usec += USECS_PER_JIFFY * (jiffies - wall_jiffies);
 
 	read_unlock_irqrestore (&xtime_lock, flags);
 
@@ -344,7 +345,7 @@
 		count = read_c0_count();
 
 		/* check to see if we have missed any timer interrupts */
-		if ((count - expirelo) < 0x7fffffff) {
+		if (time_after(count, expirelo)) {
 			/* missed_timer_count ++; */
 			expirelo = count + cycles_per_jiffy;
 			write_c0_compare(expirelo);
@@ -355,6 +356,8 @@
 		timerlo = count;
 	}
 
+	write_lock (&xtime_lock);
+
 	/*
 	 * call the generic timer interrupt handling
 	 */
@@ -365,7 +368,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.
 	 */
-	read_lock (&xtime_lock);
 	if ((time_status & STA_UNSYNC) == 0 &&
 	    xtime.tv_sec > last_rtc_update + 660 &&
 	    xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 &&
@@ -377,7 +379,6 @@
 			/* do it again in 60 s */
 		}
 	}
-	read_unlock (&xtime_lock);
 
 	/*
 	 * If jiffies has overflowed in this timer_interrupt we must
@@ -388,26 +389,20 @@
 		timerhi = timerlo = 0;
 	}
 
-#if !defined(CONFIG_SMP)
+	write_unlock (&xtime_lock);
+
 	/*
-	 * In UP mode, we call local_timer_interrupt() to do profiling
-	 * and process accouting.
-	 *
-	 * In SMP mode, local_timer_interrupt() is invoked by appropriate
-	 * low-level local timer interrupt handler.
+	 * We call local_timer_interrupt() to do profiling and 
+	 * process accouting.
 	 */
 	local_timer_interrupt(0, NULL, regs);
 
-#else	/* CONFIG_SMP */
-
+#if defined(CONFIG_SMP)
 	if (emulate_local_timer_interrupt) {
 		/*
 		 * this is the place where we send out inter-process
 		 * interrupts and let each CPU do its own profiling
 		 * and process accouting.
-		 *
-		 * Obviously we need to call local_timer_interrupt() for
-		 * the current CPU too.
 		 */
 		panic("Not implemented yet!!!");
 	}
diff -Nru linux/arch/mips/sibyte/sb1250/time.c.orig linux/arch/mips/sibyte/sb1250/time.c
--- linux/arch/mips/sibyte/sb1250/time.c.orig	Fri Jun  6 12:44:08 2003
+++ linux/arch/mips/sibyte/sb1250/time.c	Mon Jun  9 19:01:43 2003
@@ -30,11 +30,13 @@
 #include <linux/sched.h>
 #include <linux/spinlock.h>
 #include <linux/kernel_stat.h>
+#include <linux/time.h>
 
 #include <asm/irq.h>
 #include <asm/ptrace.h>
 #include <asm/addrspace.h>
 #include <asm/time.h>
+#include <asm/div64.h>
 
 #include <asm/sibyte/sb1250.h>
 #include <asm/sibyte/sb1250_regs.h>
@@ -47,13 +49,42 @@
 #define IMR_IP3_VAL	K_INT_MAP_I1
 #define IMR_IP4_VAL	K_INT_MAP_I2
 
+extern rwlock_t xtime_lock;
+
 extern int sb1250_steal_irq(int irq);
 
+#define	zbcycle2msec_shift	32
+#define USECS_PER_JIFFY 	(1000000/HZ)
+
+static unsigned zbbus_freq;
+static unsigned scaled_usec_per_zbcycle;
+
+/*
+ * return (u32)( ((u64)x * (u64)y) >> shift ), where shift <= 32
+ */
+static inline unsigned 
+scaled_mult(unsigned x, unsigned y, int shift)
+{
+	unsigned hi, lo;
+
+	__asm__("multu\t%2,%3\n\t"
+		"mfhi\t%0\n\t"
+		"mflo\t%1"
+		:"=r" (hi), "=r" (lo)
+		:"r" (x), "r" (y));
+	if (shift == 32) {
+		return hi;
+	} else {
+		return (hi << (32 - shift)) | (lo >> shift);
+	}
+}
+
 void sb1250_time_init(void)
 {
 	int cpu = smp_processor_id();
 	int irq = K_INT_TIMER_0+cpu;
-
+	u64 temp;
+	
 	/* Only have 4 general purpose timers */
 	if (cpu > 3) {
 		BUG();
@@ -95,6 +126,23 @@
 	 * called directly from irq_handler.S when IP[4] is set during an
 	 * interrupt
 	 */
+
+        temp = G_SYS_PLL_DIV(in64(IO_SPACE_BASE | A_SCD_SYSTEM_CFG));
+        temp = ((temp >> 1) * 50) + ((temp & 1) * 25);
+        zbbus_freq = temp * 1000000;
+
+	temp = 1000000ULL << zbcycle2msec_shift;
+	do_div(temp, zbbus_freq);
+	scaled_usec_per_zbcycle = temp;
+}
+
+static unsigned last_jiffie;
+static u64 last_zbcount;
+
+static inline u64
+read_zbcount(void)
+{
+	return in64(KSEG1 + A_SCD_ZBBUS_CYCLE_COUNT);
 }
 
 void sb1250_timer_interrupt(struct pt_regs *regs)
@@ -102,7 +150,6 @@
 	int cpu = smp_processor_id();
 	int irq = K_INT_TIMER_0+cpu;
 
-	kstat.irqs[cpu][irq]++;
 	/* Reset the timer */
 	out64(M_SCD_TIMER_ENABLE|M_SCD_TIMER_MODE_CONTINUOUS,
 	      KSEG1 + A_SCD_TIMER_REGISTER(cpu, R_SCD_TIMER_CFG));
@@ -111,25 +158,45 @@
 	 * CPU 0 handles the global timer interrupt job
 	 */
 	if (cpu == 0) {
+		write_lock(&xtime_lock);
+		last_jiffie = jiffies;
+		last_zbcount = read_zbcount();
+		write_unlock(&xtime_lock);
 		ll_timer_interrupt(irq, regs);
-	}
-
-	/*
-	 * every CPU should do profiling and process accouting
-	 */
-	ll_local_timer_interrupt(irq, regs);
+	} else 
+		ll_local_timer_interrupt(irq, regs);
 }
 
 /*
  * 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.
+ *
+ * It turns out to be very hard to get monotonically increasing time offset.
+ * We have to resort to zb bus counter.
  */
 unsigned long sb1250_gettimeoffset(void)
 {
-	unsigned long count =
-		in64(KSEG1 + A_SCD_TIMER_REGISTER(0, R_SCD_TIMER_CNT));
+	u64 count;
+	unsigned ret;
+	unsigned count_diff;
+
+	/* we have xtime lock, irq disabled */
+	count = read_zbcount();
+	count_diff = count - last_zbcount;
+	ret = scaled_mult(count_diff,
+			scaled_usec_per_zbcycle, 
+			zbcycle2msec_shift);
+
+	if (jiffies == last_jiffie) {
+		// we are in a narrow window where timer interrupt
+		// has happened but jiffies has not been increased yet
+		// The offset should be about 1 jiffie.  We just return
+		// the maximum intra-jiffie offset here.
+		ret = USECS_PER_JIFFY-1;
+	}
 
-	return 1000000/HZ - count;
- }
+	if (ret >= USECS_PER_JIFFY)
+		ret = USECS_PER_JIFFY-1;
+
+	return ret;
+}