Commit fbb16e24 authored by Thomas Gleixner's avatar Thomas Gleixner Committed by Linus Torvalds

[x86] Fix TSC calibration issues

Larry Finger reported at http://lkml.org/lkml/2008/9/1/90:
An ancient laptop of mine started throwing errors from b43legacy when
I started using 2.6.27 on it. This has been bisected to commit bfc0f594
"x86: merge tsc calibration".

The unification of the TSC code adopted mostly the 64bit code, which
prefers PMTIMER/HPET over the PIT calibration.

Larrys system has an AMD K6 CPU. Such systems are known to have
PMTIMER incarnations which run at double speed. This results in a
miscalibration of the TSC by factor 0.5. So the resulting calibrated
CPU/TSC speed is half of the real CPU speed, which means that the TSC
based delay loop will run half the time it should run. That might
explain why the b43legacy driver went berserk.

On the other hand we know about systems, where the PIT based
calibration results in random crap due to heavy SMI/SMM
disturbance. On those systems the PMTIMER/HPET based calibration logic
with SMI detection shows better results.

According to Alok also virtualized systems suffer from the PIT
calibration method.

The solution is to use a more wreckage aware aproach than the current
either/or decision.

1) reimplement the retry loop which was dropped from the 32bit code
during the merge. It repeats the calibration and selects the lowest
frequency value as this is probably the closest estimate to the real
frequency

2) Monitor the delta of the TSC values in the delay loop which waits
for the PIT counter to reach zero. If the maximum value is
significantly different from the minimum, then we have a pretty safe
indicator that the loop was disturbed by an SMI.

3) keep the pmtimer/hpet reference as a backup solution for systems
where the SMI disturbance is a permanent point of failure for PIT
based calibration

4) do the loop iteration for both methods, record the lowest value and
decide after all iterations finished.

5) Set a clear preference to PIT based calibration when the result
makes sense.

The implementation does the reference calibration based on
HPET/PMTIMER around the delay, which is necessary for the PIT anyway,
but keeps separate TSC values to ensure the "independency" of the
resulting calibration values.

Tested on various 32bit/64bit machines including Geode 266Mhz, AMD K6
(affected machine with a double speed pmtimer which I grabbed out of
the dump), Pentium class machines and AMD/Intel 64 bit boxen.
Bisected-by: default avatarLarry Finger <Larry.Finger@lwfinger.net>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarLarry Finger <Larry.Finger@lwfinger.net>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 8b76f46a
...@@ -127,75 +127,202 @@ static u64 tsc_read_refs(u64 *pm, u64 *hpet) ...@@ -127,75 +127,202 @@ static u64 tsc_read_refs(u64 *pm, u64 *hpet)
*/ */
unsigned long native_calibrate_tsc(void) unsigned long native_calibrate_tsc(void)
{ {
unsigned long flags; u64 tsc1, tsc2, tr1, tr2, tsc, delta, pm1, pm2, hpet1, hpet2;
u64 tsc1, tsc2, tr1, tr2, delta, pm1, pm2, hpet1, hpet2; unsigned long tsc_pit_min = ULONG_MAX, tsc_ref_min = ULONG_MAX;
int hpet = is_hpet_enabled(); unsigned long flags, tscmin, tscmax;
unsigned int tsc_khz_val = 0; int hpet = is_hpet_enabled(), pitcnt, i;
local_irq_save(flags); /*
* Run 5 calibration loops to get the lowest frequency value
tsc1 = tsc_read_refs(&pm1, hpet ? &hpet1 : NULL); * (the best estimate). We use two different calibration modes
* here:
outb((inb(0x61) & ~0x02) | 0x01, 0x61); *
* 1) PIT loop. We set the PIT Channel 2 to oneshot mode and
outb(0xb0, 0x43); * load a timeout of 50ms. We read the time right after we
outb((CLOCK_TICK_RATE / (1000 / 50)) & 0xff, 0x42); * started the timer and wait until the PIT count down reaches
outb((CLOCK_TICK_RATE / (1000 / 50)) >> 8, 0x42); * zero. In each wait loop iteration we read the TSC and check
tr1 = get_cycles(); * the delta to the previous read. We keep track of the min
while ((inb(0x61) & 0x20) == 0); * and max values of that delta. The delta is mostly defined
tr2 = get_cycles(); * by the IO time of the PIT access, so we can detect when a
* SMI/SMM disturbance happend between the two reads. If the
tsc2 = tsc_read_refs(&pm2, hpet ? &hpet2 : NULL); * maximum time is significantly larger than the minimum time,
* then we discard the result and have another try.
local_irq_restore(flags); *
* 2) Reference counter. If available we use the HPET or the
* PMTIMER as a reference to check the sanity of that value.
* We use separate TSC readouts and check inside of the
* reference read for a SMI/SMM disturbance. We dicard
* disturbed values here as well. We do that around the PIT
* calibration delay loop as we have to wait for a certain
* amount of time anyway.
*/
for (i = 0; i < 5; i++) {
tscmin = ULONG_MAX;
tscmax = 0;
pitcnt = 0;
local_irq_save(flags);
/*
* Read the start value and the reference count of
* hpet/pmtimer when available:
*/
tsc1 = tsc_read_refs(&pm1, hpet ? &hpet1 : NULL);
/* Set the Gate high, disable speaker */
outb((inb(0x61) & ~0x02) | 0x01, 0x61);
/*
* Setup CTC channel 2* for mode 0, (interrupt on terminal
* count mode), binary count. Set the latch register to 50ms
* (LSB then MSB) to begin countdown.
*
* Some devices need a delay here.
*/
outb(0xb0, 0x43);
outb((CLOCK_TICK_RATE / (1000 / 50)) & 0xff, 0x42);
outb((CLOCK_TICK_RATE / (1000 / 50)) >> 8, 0x42);
tsc = tr1 = tr2 = get_cycles();
while ((inb(0x61) & 0x20) == 0) {
tr2 = get_cycles();
delta = tr2 - tsc;
tsc = tr2;
if ((unsigned int) delta < tscmin)
tscmin = (unsigned int) delta;
if ((unsigned int) delta > tscmax)
tscmax = (unsigned int) delta;
pitcnt++;
}
/*
* We waited at least 50ms above. Now read
* pmtimer/hpet reference again
*/
tsc2 = tsc_read_refs(&pm2, hpet ? &hpet2 : NULL);
local_irq_restore(flags);
/*
* Sanity checks:
*
* If we were not able to read the PIT more than 5000
* times, then we have been hit by a massive SMI
*
* If the maximum is 10 times larger than the minimum,
* then we got hit by an SMI as well.
*/
if (pitcnt > 5000 && tscmax < 10 * tscmin) {
/* Calculate the PIT value */
delta = tr2 - tr1;
do_div(delta, 50);
/* We take the smallest value into account */
tsc_pit_min = min(tsc_pit_min, (unsigned long) delta);
}
/* hpet or pmtimer available ? */
if (!hpet && !pm1 && !pm2)
continue;
/* Check, whether the sampling was disturbed by an SMI */
if (tsc1 == ULLONG_MAX || tsc2 == ULLONG_MAX)
continue;
tsc2 = (tsc2 - tsc1) * 1000000LL;
if (hpet) {
if (hpet2 < hpet1)
hpet2 += 0x100000000ULL;
hpet2 -= hpet1;
tsc1 = ((u64)hpet2 * hpet_readl(HPET_PERIOD));
do_div(tsc1, 1000000);
} else {
if (pm2 < pm1)
pm2 += (u64)ACPI_PM_OVRRUN;
pm2 -= pm1;
tsc1 = pm2 * 1000000000LL;
do_div(tsc1, PMTMR_TICKS_PER_SEC);
}
do_div(tsc2, tsc1);
tsc_ref_min = min(tsc_ref_min, (unsigned long) tsc2);
}
/* /*
* Preset the result with the raw and inaccurate PIT * Now check the results.
* calibration value
*/ */
delta = (tr2 - tr1); if (tsc_pit_min == ULONG_MAX) {
do_div(delta, 50); /* PIT gave no useful value */
tsc_khz_val = delta; printk(KERN_WARNING "TSC: PIT calibration failed due to "
"SMI disturbance.\n");
/* We don't have an alternative source, disable TSC */
if (!hpet && !pm1 && !pm2) {
printk("TSC: No reference (HPET/PMTIMER) available\n");
return 0;
}
/* The alternative source failed as well, disable TSC */
if (tsc_ref_min == ULONG_MAX) {
printk(KERN_WARNING "TSC: HPET/PMTIMER calibration "
"failed due to SMI disturbance.\n");
return 0;
}
/* Use the alternative source */
printk(KERN_INFO "TSC: using %s reference calibration\n",
hpet ? "HPET" : "PMTIMER");
return tsc_ref_min;
}
/* hpet or pmtimer available ? */ /* We don't have an alternative source, use the PIT calibration value */
if (!hpet && !pm1 && !pm2) { if (!hpet && !pm1 && !pm2) {
printk(KERN_INFO "TSC calibrated against PIT\n"); printk(KERN_INFO "TSC: Using PIT calibration value\n");
goto out; return tsc_pit_min;
} }
/* Check, whether the sampling was disturbed by an SMI */ /* The alternative source failed, use the PIT calibration value */
if (tsc1 == ULLONG_MAX || tsc2 == ULLONG_MAX) { if (tsc_ref_min == ULONG_MAX) {
printk(KERN_WARNING "TSC calibration disturbed by SMI, " printk(KERN_WARNING "TSC: HPET/PMTIMER calibration failed due "
"using PIT calibration result\n"); "to SMI disturbance. Using PIT calibration\n");
goto out; return tsc_pit_min;
} }
tsc2 = (tsc2 - tsc1) * 1000000LL; /* Check the reference deviation */
delta = ((u64) tsc_pit_min) * 100;
if (hpet) { do_div(delta, tsc_ref_min);
printk(KERN_INFO "TSC calibrated against HPET\n");
if (hpet2 < hpet1) /*
hpet2 += 0x100000000ULL; * If both calibration results are inside a 5% window, the we
hpet2 -= hpet1; * use the lower frequency of those as it is probably the
tsc1 = ((u64)hpet2 * hpet_readl(HPET_PERIOD)); * closest estimate.
do_div(tsc1, 1000000); */
} else { if (delta >= 95 && delta <= 105) {
printk(KERN_INFO "TSC calibrated against PM_TIMER\n"); printk(KERN_INFO "TSC: PIT calibration confirmed by %s.\n",
if (pm2 < pm1) hpet ? "HPET" : "PMTIMER");
pm2 += (u64)ACPI_PM_OVRRUN; printk(KERN_INFO "TSC: using %s calibration value\n",
pm2 -= pm1; tsc_pit_min <= tsc_ref_min ? "PIT" :
tsc1 = pm2 * 1000000000LL; hpet ? "HPET" : "PMTIMER");
do_div(tsc1, PMTMR_TICKS_PER_SEC); return tsc_pit_min <= tsc_ref_min ? tsc_pit_min : tsc_ref_min;
} }
do_div(tsc2, tsc1); printk(KERN_WARNING "TSC: PIT calibration deviates from %s: %lu %lu.\n",
tsc_khz_val = tsc2; hpet ? "HPET" : "PMTIMER", tsc_pit_min, tsc_ref_min);
out: /*
return tsc_khz_val; * The calibration values differ too much. In doubt, we use
* the PIT value as we know that there are PMTIMERs around
* running at double speed.
*/
printk(KERN_INFO "TSC: Using PIT calibration value\n");
return tsc_pit_min;
} }
#ifdef CONFIG_X86_32 #ifdef CONFIG_X86_32
/* Only called from the Powernow K7 cpu freq driver */ /* Only called from the Powernow K7 cpu freq driver */
int recalibrate_cpu_khz(void) int recalibrate_cpu_khz(void)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment