dacb16b1a0
I just found out that some precision is unnecessarily lost in the arch/i386/kernel/timers/timer_tsc.c:set_cyc2ns_scale function. It uses a cpu_mhz parameter when it could use a cpu_khz. In the specific case of an Intel P4 running at 3001.171 Mhz, the truncation to 3001 Mhz leads to an imprecision of 19 microseconds per second : this is very sad for a timer with nearly nanosecond accuracy. Fix the x86_64 architecture too. Cc: george anzinger <george@mvista.com> Cc: john stultz <johnstul@us.ibm.com> Cc: Andi Kleen <ak@muc.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
218 lines
6.0 KiB
C
218 lines
6.0 KiB
C
/*
|
|
* This code largely moved from arch/i386/kernel/time.c.
|
|
* See comments there for proper credits.
|
|
*/
|
|
|
|
#include <linux/spinlock.h>
|
|
#include <linux/init.h>
|
|
#include <linux/timex.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/string.h>
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <asm/timer.h>
|
|
#include <asm/io.h>
|
|
#include <asm/processor.h>
|
|
|
|
#include "io_ports.h"
|
|
#include "mach_timer.h"
|
|
#include <asm/hpet.h>
|
|
|
|
static unsigned long hpet_usec_quotient __read_mostly; /* convert hpet clks to usec */
|
|
static unsigned long tsc_hpet_quotient __read_mostly; /* convert tsc to hpet clks */
|
|
static unsigned long hpet_last; /* hpet counter value at last tick*/
|
|
static unsigned long last_tsc_low; /* lsb 32 bits of Time Stamp Counter */
|
|
static unsigned long last_tsc_high; /* msb 32 bits of Time Stamp Counter */
|
|
static unsigned long long monotonic_base;
|
|
static seqlock_t monotonic_lock = SEQLOCK_UNLOCKED;
|
|
|
|
/* convert from cycles(64bits) => nanoseconds (64bits)
|
|
* basic equation:
|
|
* ns = cycles / (freq / ns_per_sec)
|
|
* ns = cycles * (ns_per_sec / freq)
|
|
* ns = cycles * (10^9 / (cpu_khz * 10^3))
|
|
* ns = cycles * (10^6 / cpu_khz)
|
|
*
|
|
* Then we use scaling math (suggested by george@mvista.com) to get:
|
|
* ns = cycles * (10^6 * SC / cpu_khz) / SC
|
|
* ns = cycles * cyc2ns_scale / SC
|
|
*
|
|
* And since SC is a constant power of two, we can convert the div
|
|
* into a shift.
|
|
*
|
|
* We can use khz divisor instead of mhz to keep a better percision, since
|
|
* cyc2ns_scale is limited to 10^6 * 2^10, which fits in 32 bits.
|
|
* (mathieu.desnoyers@polymtl.ca)
|
|
*
|
|
* -johnstul@us.ibm.com "math is hard, lets go shopping!"
|
|
*/
|
|
static unsigned long cyc2ns_scale;
|
|
#define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */
|
|
|
|
static inline void set_cyc2ns_scale(unsigned long cpu_khz)
|
|
{
|
|
cyc2ns_scale = (1000000 << CYC2NS_SCALE_FACTOR)/cpu_khz;
|
|
}
|
|
|
|
static inline unsigned long long cycles_2_ns(unsigned long long cyc)
|
|
{
|
|
return (cyc * cyc2ns_scale) >> CYC2NS_SCALE_FACTOR;
|
|
}
|
|
|
|
static unsigned long long monotonic_clock_hpet(void)
|
|
{
|
|
unsigned long long last_offset, this_offset, base;
|
|
unsigned seq;
|
|
|
|
/* atomically read monotonic base & last_offset */
|
|
do {
|
|
seq = read_seqbegin(&monotonic_lock);
|
|
last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
|
|
base = monotonic_base;
|
|
} while (read_seqretry(&monotonic_lock, seq));
|
|
|
|
/* Read the Time Stamp Counter */
|
|
rdtscll(this_offset);
|
|
|
|
/* return the value in ns */
|
|
return base + cycles_2_ns(this_offset - last_offset);
|
|
}
|
|
|
|
static unsigned long get_offset_hpet(void)
|
|
{
|
|
register unsigned long eax, edx;
|
|
|
|
eax = hpet_readl(HPET_COUNTER);
|
|
eax -= hpet_last; /* hpet delta */
|
|
eax = min(hpet_tick, eax);
|
|
/*
|
|
* Time offset = (hpet delta) * ( usecs per HPET clock )
|
|
* = (hpet delta) * ( usecs per tick / HPET clocks per tick)
|
|
* = (hpet delta) * ( hpet_usec_quotient ) / (2^32)
|
|
*
|
|
* Where,
|
|
* hpet_usec_quotient = (2^32 * usecs per tick)/HPET clocks per tick
|
|
*
|
|
* Using a mull instead of a divl saves some cycles in critical path.
|
|
*/
|
|
ASM_MUL64_REG(eax, edx, hpet_usec_quotient, eax);
|
|
|
|
/* our adjusted time offset in microseconds */
|
|
return edx;
|
|
}
|
|
|
|
static void mark_offset_hpet(void)
|
|
{
|
|
unsigned long long this_offset, last_offset;
|
|
unsigned long offset;
|
|
|
|
write_seqlock(&monotonic_lock);
|
|
last_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
|
|
rdtsc(last_tsc_low, last_tsc_high);
|
|
|
|
if (hpet_use_timer)
|
|
offset = hpet_readl(HPET_T0_CMP) - hpet_tick;
|
|
else
|
|
offset = hpet_readl(HPET_COUNTER);
|
|
if (unlikely(((offset - hpet_last) >= (2*hpet_tick)) && (hpet_last != 0))) {
|
|
int lost_ticks = ((offset - hpet_last) / hpet_tick) - 1;
|
|
jiffies_64 += lost_ticks;
|
|
}
|
|
hpet_last = offset;
|
|
|
|
/* update the monotonic base value */
|
|
this_offset = ((unsigned long long)last_tsc_high<<32)|last_tsc_low;
|
|
monotonic_base += cycles_2_ns(this_offset - last_offset);
|
|
write_sequnlock(&monotonic_lock);
|
|
}
|
|
|
|
static void delay_hpet(unsigned long loops)
|
|
{
|
|
unsigned long hpet_start, hpet_end;
|
|
unsigned long eax;
|
|
|
|
/* loops is the number of cpu cycles. Convert it to hpet clocks */
|
|
ASM_MUL64_REG(eax, loops, tsc_hpet_quotient, loops);
|
|
|
|
hpet_start = hpet_readl(HPET_COUNTER);
|
|
do {
|
|
rep_nop();
|
|
hpet_end = hpet_readl(HPET_COUNTER);
|
|
} while ((hpet_end - hpet_start) < (loops));
|
|
}
|
|
|
|
static struct timer_opts timer_hpet;
|
|
|
|
static int __init init_hpet(char* override)
|
|
{
|
|
unsigned long result, remain;
|
|
|
|
/* check clock override */
|
|
if (override[0] && strncmp(override,"hpet",4))
|
|
return -ENODEV;
|
|
|
|
if (!is_hpet_enabled())
|
|
return -ENODEV;
|
|
|
|
printk("Using HPET for gettimeofday\n");
|
|
if (cpu_has_tsc) {
|
|
unsigned long tsc_quotient = calibrate_tsc_hpet(&tsc_hpet_quotient);
|
|
if (tsc_quotient) {
|
|
/* report CPU clock rate in Hz.
|
|
* The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
|
|
* clock/second. Our precision is about 100 ppm.
|
|
*/
|
|
{ unsigned long eax=0, edx=1000;
|
|
ASM_DIV64_REG(cpu_khz, edx, tsc_quotient,
|
|
eax, edx);
|
|
printk("Detected %u.%03u MHz processor.\n",
|
|
cpu_khz / 1000, cpu_khz % 1000);
|
|
}
|
|
set_cyc2ns_scale(cpu_khz);
|
|
}
|
|
/* set this only when cpu_has_tsc */
|
|
timer_hpet.read_timer = read_timer_tsc;
|
|
}
|
|
|
|
/*
|
|
* Math to calculate hpet to usec multiplier
|
|
* Look for the comments at get_offset_hpet()
|
|
*/
|
|
ASM_DIV64_REG(result, remain, hpet_tick, 0, KERNEL_TICK_USEC);
|
|
if (remain > (hpet_tick >> 1))
|
|
result++; /* rounding the result */
|
|
hpet_usec_quotient = result;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hpet_resume(void)
|
|
{
|
|
write_seqlock(&monotonic_lock);
|
|
/* Assume this is the last mark offset time */
|
|
rdtsc(last_tsc_low, last_tsc_high);
|
|
|
|
if (hpet_use_timer)
|
|
hpet_last = hpet_readl(HPET_T0_CMP) - hpet_tick;
|
|
else
|
|
hpet_last = hpet_readl(HPET_COUNTER);
|
|
write_sequnlock(&monotonic_lock);
|
|
return 0;
|
|
}
|
|
/************************************************************/
|
|
|
|
/* tsc timer_opts struct */
|
|
static struct timer_opts timer_hpet __read_mostly = {
|
|
.name = "hpet",
|
|
.mark_offset = mark_offset_hpet,
|
|
.get_offset = get_offset_hpet,
|
|
.monotonic_clock = monotonic_clock_hpet,
|
|
.delay = delay_hpet,
|
|
.resume = hpet_resume,
|
|
};
|
|
|
|
struct init_timer_opts __initdata timer_hpet_init = {
|
|
.init = init_hpet,
|
|
.opts = &timer_hpet,
|
|
};
|