Linux MIPS Architecture development
 help / color / mirror / Atom feed
* [PATCH] loosing time with CPU counter timer
@ 2003-04-15  0:16 Jun Sun
  2003-04-15  0:54 ` Juan Quintela
  2003-04-21 21:22 ` Jun Sun
  0 siblings, 2 replies; 3+ messages in thread
From: Jun Sun @ 2003-04-15  0:16 UTC (permalink / raw)
  To: linux-mips; +Cc: jsun

[-- Attachment #1: Type: text/plain, Size: 715 bytes --]


This patch fixes an ancient timer bug.

If one uses CPU counter as the system timer, it looses time
over the time.

Basically, timer_interrupt() does the following when it serves
an cpu counter interrupt (only relavent part shown);

0) interrupt happens
1) read cpu counter;
2) add it with cycles_per_jiffy, and set the value to compare
   register.

The problem is that cpu counter could increase between 0) and 1),
say by delta units.  Then the next timer interrupt is set to
t0 + delta + cycles_per_unit, instead of t0 + cycles_per_unit,
(where t0 is the current timer interrupt time)

Similar bug also exists in old-time.c, but anybody really cares? :)
Especially it has been there for quite a while ....

Jun

[-- Attachment #2: 030414.loosing-time-with-cpu-counter-timer.patch --]
[-- Type: text/plain, Size: 3851 bytes --]

diff -Nru linux/arch/mips/ddb5xxx/ddb5477/setup.c.orig linux/arch/mips/ddb5xxx/ddb5477/setup.c
--- linux/arch/mips/ddb5xxx/ddb5477/setup.c.orig	Mon Apr 14 15:28:57 2003
+++ linux/arch/mips/ddb5xxx/ddb5477/setup.c	Mon Apr 14 16:08:35 2003
@@ -43,7 +43,7 @@
 #include "lcd44780.h"
 
 
-// #define	USE_CPU_COUNTER_TIMER	/* whether we use cpu counter */
+#define	USE_CPU_COUNTER_TIMER	/* whether we use cpu counter */
 
 #define	SP_TIMER_BASE			DDB_SPT1CTRL_L
 #define	SP_TIMER_IRQ			VRC5477_IRQ_SPT1
@@ -154,10 +154,6 @@
         /* we are using the cpu counter for timer interrupts */
 	setup_irq(CPU_IRQ_BASE + 7, irq);
 
-        /* to generate the first timer interrupt */
-        count = read_c0_count();
-        write_c0_compare(count + 1000);
-
 #else
 
 	/* if we use Special purpose timer 1 */
diff -Nru linux/arch/mips/kernel/time.c.orig linux/arch/mips/kernel/time.c
--- linux/arch/mips/kernel/time.c.orig	Fri Apr 11 11:05:18 2003
+++ linux/arch/mips/kernel/time.c	Mon Apr 14 16:51:13 2003
@@ -140,6 +140,24 @@
 /* Cycle counter value at the previous timer interrupt.. */
 static unsigned int timerhi, timerlo;
 
+/* 
+ * Cycle counter value after which next timer interrupt is considered "missed".
+ * Suppose we are serving timer interrupt scheduled at time t, the theorectical 
+ * expiriing point for next interrupt is t + 2 * cycles_per_jiffy.
+ * In practice, we set it a little earlier, which is 
+ * 	t + 2 * cycles_per_jiffy - EXTRA_CUSHION_CYCLES
+ * just to make sure we still have some time to set registers after we
+ * decide whether a timer interrupt is missed.
+ */
+static unsigned int expirehi, expirelo;
+
+/* 
+ * extra "cushion" cycles used when we decide whether we have missed an
+ * timer interrupt (in the case of using cpu counter).  It should be long
+ * enough to cover at least 20 instructions.
+ */
+#define	EXTRA_CUSHION_CYCLES	50
+
 /* last time when xtime and rtc are sync'ed up */
 static long last_rtc_update;
 
@@ -330,6 +348,16 @@
  * high-level timer interrupt service routines.  This function
  * is set as irqaction->handler and is invoked through do_IRQ.
  */
+static inline int 
+64bit_compare(unsigned hi1, unsigned lo1, unsigned hi2, unsigned lo2)
+{
+	if (hi1 == hi2) {
+		return lo1 - lo2;
+	} else {
+		return hi1 - hi2;
+	}
+}
+
 void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
 {
 	if (cpu_has_counter) {
@@ -343,14 +371,19 @@
 		timerhi += (count < timerlo);   /* Wrap around */
 		timerlo = count;
 
-		/*
-		 * set up for next timer interrupt - no harm if the machine
-		 * is using another timer interrupt source.
-		 * Note that writing to COMPARE register clears the interrupt
-		 */
-		write_c0_compare(
-					  count + cycles_per_jiffy);
-
+		/* check to see if we have missed a timer interrupt */
+		if (64bit_compare(timerhi, timerlo, expirehi, expirelo) < 0) {
+			unsigned int old_expirelo=expirelo;
+			expirelo += cycles_per_jiffy;
+			expirehi += (expirelo < old_expirelo);
+			write_c0_compare(old_expirelo + EXTRA_CUSHION_CYCLES);
+		} else {
+			// missed_timer_count ++;
+			/* we have missed at least one timer interrupt */
+			expirelo = timerlo + cycles_per_jiffy*2 - EXTRA_CUSHION_CYCLES;
+			expirehi = timerhi + (expirelo < timerlo);
+			write_c0_compare(timerlo + cycles_per_jiffy);
+		}
 	}
 
 	/*
@@ -504,8 +537,6 @@
 
 	/* caclulate cache parameters */
 	if (mips_counter_frequency) {
-		u32 count;
-
 		cycles_per_jiffy = mips_counter_frequency / HZ;
 
 		/* sll32_usecs_per_cycle = 10^6 * 2^32 / mips_counter_freq */
@@ -518,9 +549,9 @@
 		 * For those using cpu counter as timer,  this sets up the
 		 * first interrupt
 		 */
-		count = read_c0_count();
-		write_c0_compare(
-					  count + cycles_per_jiffy);
+		write_c0_compare(cycles_per_jiffy);
+		write_c0_count(0);
+		expirelo = cycles_per_jiffy * 2 - EXTRA_CUSHION_CYCLES;
 	}
 
 	/*

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] loosing time with CPU counter timer
  2003-04-15  0:16 [PATCH] loosing time with CPU counter timer Jun Sun
@ 2003-04-15  0:54 ` Juan Quintela
  2003-04-21 21:22 ` Jun Sun
  1 sibling, 0 replies; 3+ messages in thread
From: Juan Quintela @ 2003-04-15  0:54 UTC (permalink / raw)
  To: Jun Sun; +Cc: linux-mips

>>>>> "jun" == Jun Sun <jsun@mvista.com> writes:

Hi
        only aesthetical fixes:

jun> diff -Nru linux/arch/mips/ddb5xxx/ddb5477/setup.c.orig linux/arch/mips/ddb5xxx/ddb5477/setup.c
jun> --- linux/arch/mips/ddb5xxx/ddb5477/setup.c.orig	Mon Apr 14 15:28:57 2003
jun> +++ linux/arch/mips/ddb5xxx/ddb5477/setup.c	Mon Apr 14 16:08:35 2003
jun> @@ -330,6 +348,16 @@
jun> * high-level timer interrupt service routines.  This function
jun> * is set as irqaction->handler and is invoked through do_IRQ.
jun> */
jun> +static inline int 
jun> +64bit_compare(unsigned hi1, unsigned lo1, unsigned hi2, unsigned lo2)

Please, use "unsigned int" /me notices that file is not consistent, in
some places it uses "unsigned" and in other "unsigned int".

jun> +{
jun> +	if (hi1 == hi2) {
jun> +		return lo1 - lo2;
jun> +	} else {
jun> +		return hi1 - hi2;
jun> +	}
jun> +}

Ralf tax on braces apply here.

jun> +		/* check to see if we have missed a timer interrupt */
jun> +		if (64bit_compare(timerhi, timerlo, expirehi, expirelo) < 0) {
jun> +			unsigned int old_expirelo=expirelo;
jun> +			expirelo += cycles_per_jiffy;
jun> +			expirehi += (expirelo < old_expirelo);

Tax on parens :)

Later, Juan.

-- 
In theory, practice and theory are the same, but in practice they 
are different -- Larry McVoy

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] loosing time with CPU counter timer
  2003-04-15  0:16 [PATCH] loosing time with CPU counter timer Jun Sun
  2003-04-15  0:54 ` Juan Quintela
@ 2003-04-21 21:22 ` Jun Sun
  1 sibling, 0 replies; 3+ messages in thread
From: Jun Sun @ 2003-04-21 21:22 UTC (permalink / raw)
  To: linux-mips; +Cc: jsun

[-- Attachment #1: Type: text/plain, Size: 1117 bytes --]

On Mon, Apr 14, 2003 at 05:16:55PM -0700, Jun Sun wrote:
> 
> This patch fixes an ancient timer bug.
> 
> If one uses CPU counter as the system timer, it looses time
> over the time.
> 
> Basically, timer_interrupt() does the following when it serves
> an cpu counter interrupt (only relavent part shown);
> 
> 0) interrupt happens
> 1) read cpu counter;
> 2) add it with cycles_per_jiffy, and set the value to compare
>    register.
> 
> The problem is that cpu counter could increase between 0) and 1),
> say by delta units.  Then the next timer interrupt is set to
> t0 + delta + cycles_per_unit, instead of t0 + cycles_per_unit,
> (where t0 is the current timer interrupt time)
> 
> Similar bug also exists in old-time.c, but anybody really cares? :)
> Especially it has been there for quite a while ....
> 
<snip>

After a refreshing weekend I realize there exists a much more
elegant fix for the problem.  :) See the patch attached.

Unfortuately I have already checked in the not-so-elegant fix.
So the actual patch to check in this time will be diff between 
this one and the previous patch.

Comments?

Jun


[-- Attachment #2: junk1 --]
[-- Type: text/plain, Size: 2028 bytes --]

diff -u arch/mips/kernel/time.c ./arch/mips/kernel/time.c
--- arch/mips/kernel/time.c	17 Apr 2003 22:50:37 -0000
+++ ./arch/mips/kernel/time.c	Mon Apr 21 14:02:06 2003
@@ -140,6 +140,9 @@
 /* Cycle counter value at the previous timer interrupt.. */
 static unsigned int timerhi, timerlo;
 
+/* expirelo is the count value for next CPU timer interrupt */
+static unsigned int expirelo;
+
 /* last time when xtime and rtc are sync'ed up */
 static long last_rtc_update;
 
@@ -335,22 +338,21 @@
 	if (cpu_has_counter) {
 		unsigned int count;
 
-		/*
-		 * The cycle counter is only 32 bit which is good for about
-		 * a minute at current count rates of upto 150MHz or so.
-		 */
+		/* ack timer interrupt, and try to set next interrupt */
+		expirelo += cycles_per_jiffy;
+		write_c0_compare(expirelo);
 		count = read_c0_count();
-		timerhi += (count < timerlo);   /* Wrap around */
-		timerlo = count;
 
-		/*
-		 * set up for next timer interrupt - no harm if the machine
-		 * is using another timer interrupt source.
-		 * Note that writing to COMPARE register clears the interrupt
-		 */
-		write_c0_compare(
-					  count + cycles_per_jiffy);
+		/* check to see if we have missed any timer interrupts */
+		if ((count - expirelo) < 0x7fffffff) {
+			/* missed_timer_count ++; */
+			expirelo = count + cycles_per_jiffy;
+			write_c0_compare(expirelo);
+		}
 
+		/* Update timerhi/timerlo for intra-jiffy calibration. */
+		timerhi += count < timerlo;	/* Wrap around */
+		timerlo = count;
 	}
 
 	/*
@@ -504,8 +506,6 @@
 
 	/* caclulate cache parameters */
 	if (mips_counter_frequency) {
-		u32 count;
-
 		cycles_per_jiffy = mips_counter_frequency / HZ;
 
 		/* sll32_usecs_per_cycle = 10^6 * 2^32 / mips_counter_freq */
@@ -518,9 +518,9 @@
 		 * For those using cpu counter as timer,  this sets up the
 		 * first interrupt
 		 */
-		count = read_c0_count();
-		write_c0_compare(
-					  count + cycles_per_jiffy);
+		write_c0_compare(cycles_per_jiffy);
+		write_c0_count(0);
+		expirelo = cycles_per_jiffy;
 	}
 
 	/*

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2003-04-21 21:22 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2003-04-15  0:16 [PATCH] loosing time with CPU counter timer Jun Sun
2003-04-15  0:54 ` Juan Quintela
2003-04-21 21:22 ` Jun Sun

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox