[PATCH] x86_64: Fix drift with HPET timer enabled

If the HPET timer is enabled, the clock can drift by ~3 seconds a day.
This is due to the HPET timer not being initialized with the correct
setting (still using PIT count).

If HZ changes, this drift can become even more pronounced.

HPET patch initializes tick_nsec with correct tick_nsec settings for
HPET timer.

Vojtech comments:

  "It's not entirely correct (it assumes the HPET ticks totally
   exactly), but it's significantly better than assuming the PIT error
   there."

Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Jordan Hargrave 2006-04-07 19:50:18 +02:00 committed by Linus Torvalds
parent 49c93e84d8
commit b20367a6c2
5 changed files with 12 additions and 1 deletions

View File

@ -917,6 +917,8 @@ void __init time_init(void)
vxtime.hpet_address = 0; vxtime.hpet_address = 0;
if (hpet_use_timer) { if (hpet_use_timer) {
/* set tick_nsec to use the proper rate for HPET */
tick_nsec = TICK_NSEC_HPET;
cpu_khz = hpet_calibrate_tsc(); cpu_khz = hpet_calibrate_tsc();
timename = "HPET"; timename = "HPET";
#ifdef CONFIG_X86_PM_TIMER #ifdef CONFIG_X86_PM_TIMER

View File

@ -89,6 +89,7 @@
* then 32 bit HPET counter wrapsaround in less than 0.5 sec. * then 32 bit HPET counter wrapsaround in less than 0.5 sec.
*/ */
#define HPET_MIN_PERIOD (100000UL) #define HPET_MIN_PERIOD (100000UL)
#define HPET_TICK_RATE (HZ * 100000UL)
extern unsigned long hpet_tick; /* hpet clks count per tick */ extern unsigned long hpet_tick; /* hpet clks count per tick */
extern unsigned long hpet_address; /* hpet memory map physical address */ extern unsigned long hpet_address; /* hpet memory map physical address */

View File

@ -51,6 +51,8 @@
#define HPET_TN_ROUTE_SHIFT 9 #define HPET_TN_ROUTE_SHIFT 9
#define HPET_TICK_RATE (HZ * 100000UL)
extern int is_hpet_enabled(void); extern int is_hpet_enabled(void);
extern int hpet_rtc_timer_init(void); extern int hpet_rtc_timer_init(void);
extern int oem_force_hpet_timer(void); extern int oem_force_hpet_timer(void);

View File

@ -36,6 +36,8 @@
/* LATCH is used in the interval timer and ftape setup. */ /* LATCH is used in the interval timer and ftape setup. */
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */ #define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */
#define LATCH_HPET ((HPET_TICK_RATE + HZ/2) / HZ)
/* Suppose we want to devide two numbers NOM and DEN: NOM/DEN, the we can /* Suppose we want to devide two numbers NOM and DEN: NOM/DEN, the we can
* improve accuracy by shifting LSH bits, hence calculating: * improve accuracy by shifting LSH bits, hence calculating:
* (NOM << LSH) / DEN * (NOM << LSH) / DEN
@ -51,9 +53,13 @@
/* HZ is the requested value. ACTHZ is actual HZ ("<< 8" is for accuracy) */ /* HZ is the requested value. ACTHZ is actual HZ ("<< 8" is for accuracy) */
#define ACTHZ (SH_DIV (CLOCK_TICK_RATE, LATCH, 8)) #define ACTHZ (SH_DIV (CLOCK_TICK_RATE, LATCH, 8))
#define ACTHZ_HPET (SH_DIV (HPET_TICK_RATE, LATCH_HPET, 8))
/* TICK_NSEC is the time between ticks in nsec assuming real ACTHZ */ /* TICK_NSEC is the time between ticks in nsec assuming real ACTHZ */
#define TICK_NSEC (SH_DIV (1000000UL * 1000, ACTHZ, 8)) #define TICK_NSEC (SH_DIV (1000000UL * 1000, ACTHZ, 8))
#define TICK_NSEC_HPET (SH_DIV(1000000UL * 1000, ACTHZ_HPET, 8))
/* TICK_USEC is the time between ticks in usec assuming fake USER_HZ */ /* TICK_USEC is the time between ticks in usec assuming fake USER_HZ */
#define TICK_USEC ((1000000UL + USER_HZ/2) / USER_HZ) #define TICK_USEC ((1000000UL + USER_HZ/2) / USER_HZ)

View File

@ -1455,7 +1455,7 @@ static void time_interpolator_update(long delta_nsec)
*/ */
if (jiffies % INTERPOLATOR_ADJUST == 0) if (jiffies % INTERPOLATOR_ADJUST == 0)
{ {
if (time_interpolator->skips == 0 && time_interpolator->offset > TICK_NSEC) if (time_interpolator->skips == 0 && time_interpolator->offset > tick_nsec)
time_interpolator->nsec_per_cyc--; time_interpolator->nsec_per_cyc--;
if (time_interpolator->ns_skipped > INTERPOLATOR_MAX_SKIP && time_interpolator->offset == 0) if (time_interpolator->ns_skipped > INTERPOLATOR_MAX_SKIP && time_interpolator->offset == 0)
time_interpolator->nsec_per_cyc++; time_interpolator->nsec_per_cyc++;