Intel time stuff update

Ingo Molnar (mingo@pc5829.hil.siemens.at)
Fri, 24 Jan 1997 19:51:42 +0100 (MET)


this is a patch for those who are using 'gettimeofday' from irq handlers
and see 'time warps'. User space behaviour is unchanged. [well stuff got
faster]

such applications are:

- NTP
- newer 2.1 kernels + pentium + tcpdump timestamps

i'm curious wether this patch makes the NTP problems (sawtooth problem or
offset jump problem already discussed on this list) go away. I dont have
NTP myself.

the tcpdump problems i've seen are fixed with this patch.

additionally, some very rare user-space visible time warps on very slow
i386 systems should go away with this patch too.

it's against 2.1.22, have tested both the pentium and i386 time stuff. The
sched.c changes are harmless if you are worried about that :)

-- mingo

--- linux/kernel/sched.c.original Fri Jan 24 17:48:09 1997
+++ linux/kernel/sched.c Fri Jan 24 19:40:50 1997
@@ -1094,23 +1094,32 @@
#endif
}

-static unsigned long lost_ticks = 0;
+volatile unsigned long lost_ticks = 0;
static unsigned long lost_ticks_system = 0;

static inline void update_times(void)
{
unsigned long ticks;
+ unsigned long flags;

- ticks = xchg(&lost_ticks, 0);
+ save_flags(flags);
+ cli();
+
+ ticks = lost_ticks;
+ lost_ticks = 0;

if (ticks) {
unsigned long system;
-
system = xchg(&lost_ticks_system, 0);
+
calc_load(ticks);
update_wall_time(ticks);
+ restore_flags(flags);
+
update_process_times(ticks, system);
- }
+
+ } else
+ restore_flags(flags);
}

static void timer_bh(void)
--- linux/arch/i386/kernel/time.c.original Fri Jan 24 17:49:49 1997
+++ linux/arch/i386/kernel/time.c Fri Jan 24 19:35:48 1997
@@ -41,38 +41,33 @@
unsigned long high;
} init_timer_cc, last_timer_cc;

-/*
- * This is more assembly than C, but it's also rather
- * timing-critical and we have to use assembler to get
- * reasonable 64-bit arithmetic
- */
+extern volatile unsigned long lost_ticks;
+
+/* change this if you have some constant time drift */
+#define USECS_PER_JIFFY (1000020/HZ)
+
static unsigned long do_fast_gettimeoffset(void)
{
register unsigned long eax asm("ax");
register unsigned long edx asm("dx");
- unsigned long tmp, quotient, low_timer, missing_time;
+ unsigned long tmp, quotient, low_timer;

- /* Last jiffy when do_fast_gettimeoffset() was called.. */
+ /* Last jiffy when do_fast_gettimeoffset() was called. */
static unsigned long last_jiffies=0;

- /* Cached "clocks per usec" value.. */
+ /*
+ * Cached "1/(clocks per usec)*2^32" value.
+ * It has to be recalculated once each jiffy.
+ */
static unsigned long cached_quotient=0;

- /* The "clocks per usec" value is calculated once each jiffy */
tmp = jiffies;
+
quotient = cached_quotient;
low_timer = last_timer_cc.low;
- missing_time = 0;
+
if (last_jiffies != tmp) {
last_jiffies = tmp;
- /*
- * test for hanging bottom handler (this means xtime is not
- * updated yet)
- */
- if (test_bit(TIMER_BH, &bh_active) )
- {
- missing_time = 1000020/HZ;
- }

/* Get last timer tick in absolute kernel time */
eax = low_timer;
@@ -86,14 +81,16 @@
* Divide the 64-bit time with the 32-bit jiffy counter,
* getting the quotient in clocks.
*
- * Giving quotient = "average internal clocks per usec"
+ * Giving quotient = "1/(average internal clocks per usec)*2^32"
+ * we do this '1/...' trick to get the 'mull' into the critical
+ * path. 'mull' is much faster than divl (10 vs. 41 clocks)
*/
__asm__("divl %2"
:"=a" (eax), "=d" (edx)
:"r" (tmp),
"0" (eax), "1" (edx));

- edx = 1000020/HZ;
+ edx = USECS_PER_JIFFY;
tmp = eax;
eax = 0;

@@ -114,7 +111,7 @@
eax -= low_timer;

/*
- * Time offset = (1000020/HZ * time_low) / quotient.
+ * Time offset = (USECS_PER_JIFFY * time_low) * quotient.
*/

__asm__("mull %2"
@@ -123,15 +120,13 @@
"0" (eax), "1" (edx));

/*
- * Due to rounding errors (and jiffies inconsistencies),
- * we need to check the result so that we'll get a timer
- * that is monotonic.
+ * Due to possible jiffies inconsistencies, we need to check
+ * the result so that we'll get a timer that is monotonic.
*/
- if (edx >= 1000020/HZ)
- edx = 1000020/HZ-1;
+ if (edx >= USECS_PER_JIFFY)
+ edx = USECS_PER_JIFFY-1;

- eax = edx + missing_time;
- return eax;
+ return edx;
}
#endif

@@ -172,8 +167,8 @@
static unsigned long do_slow_gettimeoffset(void)
{
int count;
- static int count_p = 0;
- unsigned long offset = 0;
+
+ static int count_p = LATCH; /* for the first call after boot */
static unsigned long jiffies_p = 0;

/*
@@ -183,53 +178,69 @@

/* timer count may underflow right here */
outb_p(0x00, 0x43); /* latch the count ASAP */
+
count = inb_p(0x40); /* read the latched count */
- count |= inb(0x40) << 8;

+ /*
+ * We do this guaranteed double memory access instead of a _p
+ * postfix in the previous port access. Wheee, hackady hack
+ */
jiffies_t = jiffies;

+ count |= inb_p(0x40) << 8;
+
/*
* avoiding timer inconsistencies (they are rare, but they happen)...
- * there are three kinds of problems that must be avoided here:
+ * there are two kinds of problems that must be avoided here:
* 1. the timer counter underflows
* 2. hardware problem with the timer, not giving us continuous time,
* the counter does small "jumps" upwards on some Pentium systems,
- * thus causes time warps
- * 3. we are after the timer interrupt, but the bottom half handler
- * hasn't executed yet.
- */
- if( count > count_p ) {
- if( jiffies_t == jiffies_p ) {
- if( count > LATCH-LATCH/100 )
- offset = TICK_SIZE;
- else
+ * (see c't 95/10 page 335 for Neptun bug.)
+ */
+
+/* you can safely undefine this if you dont have the Neptun chipset */
+
+#define BUGGY_NEPTUN_TIMER
+
+ if( jiffies_t == jiffies_p ) {
+ if( count > count_p ) {
+ /* the nutcase */
+
+ outb_p(0x0A, 0x20);
+
+ /* assumption about timer being IRQ1 */
+ if( inb(0x20) & 0x01 ) {
/*
- * argh, the timer is bugging we cant do nothing
- * but to give the previous clock value.
+ * We cannot detect lost timer interrupts ...
+ * well, thats why we call them lost, dont we? :)
+ * [hmm, on the Pentium and Alpha we can ... sort of]
*/
- count = count_p;
- } else {
- if( test_bit(TIMER_BH, &bh_active) ) {
- /*
- * we have detected a counter underflow.
- */
- offset = TICK_SIZE;
- count_p = count;
+ count -= LATCH;
} else {
- count_p = count;
- jiffies_p = jiffies_t;
+#ifdef BUGGY_NEPTUN_TIMER
+ /*
+ * for the Neptun bug we know that the 'latch'
+ * command doesnt latch the high and low value
+ * of the counter atomically. Thus we have to
+ * substract 256 from the counter
+ * ... funny, isnt it? :)
+ */
+
+ count -= 256;
+#else
+ printk("do_slow_gettimeoffset(): hardware timer problem?\n");
+#endif
}
}
- } else {
- count_p = count;
+ } else
jiffies_p = jiffies_t;
- }

+ count_p = count;

count = ((LATCH-1) - count) * TICK_SIZE;
count = (count + LATCH/2) / LATCH;

- return offset + count;
+ return count;
}

/*
@@ -253,11 +264,20 @@
cli();
*tv = xtime;
tv->tv_usec += do_gettimeoffset();
+
+ /*
+ * xtime is atomically updated in timer_bh. lost_ticks is
+ * nonzero if the timer bottom half hasnt executed yet.
+ */
+ if (lost_ticks)
+ tv->tv_usec += USECS_PER_JIFFY;
+
+ restore_flags(flags);
+
if (tv->tv_usec >= 1000000) {
tv->tv_usec -= 1000000;
tv->tv_sec++;
}
- restore_flags(flags);
}

void do_settimeofday(struct timeval *tv)