Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 109427
b: refs/heads/master
c: fbb16e2
h: refs/heads/master
i:
  109425: bb1d7f4
  109423: df45b13
v: v3
  • Loading branch information
Thomas Gleixner authored and Linus Torvalds committed Sep 3, 2008
1 parent 101de44 commit 6ec1799
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 55 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 8b76f46a2db29407fed66cf4aca19d61b3dcb3e1
refs/heads/master: fbb16e243887332dd5754e48ffe5b963378f3cd2
235 changes: 181 additions & 54 deletions trunk/arch/x86/kernel/tsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,75 +127,202 @@ static u64 tsc_read_refs(u64 *pm, u64 *hpet)
*/
unsigned long native_calibrate_tsc(void)
{
unsigned long flags;
u64 tsc1, tsc2, tr1, tr2, delta, pm1, pm2, hpet1, hpet2;
int hpet = is_hpet_enabled();
unsigned int tsc_khz_val = 0;
u64 tsc1, tsc2, tr1, tr2, tsc, delta, pm1, pm2, hpet1, hpet2;
unsigned long tsc_pit_min = ULONG_MAX, tsc_ref_min = ULONG_MAX;
unsigned long flags, tscmin, tscmax;
int hpet = is_hpet_enabled(), pitcnt, i;

local_irq_save(flags);

tsc1 = tsc_read_refs(&pm1, hpet ? &hpet1 : NULL);

outb((inb(0x61) & ~0x02) | 0x01, 0x61);

outb(0xb0, 0x43);
outb((CLOCK_TICK_RATE / (1000 / 50)) & 0xff, 0x42);
outb((CLOCK_TICK_RATE / (1000 / 50)) >> 8, 0x42);
tr1 = get_cycles();
while ((inb(0x61) & 0x20) == 0);
tr2 = get_cycles();

tsc2 = tsc_read_refs(&pm2, hpet ? &hpet2 : NULL);

local_irq_restore(flags);
/*
* Run 5 calibration loops to get the lowest frequency value
* (the best estimate). We use two different calibration modes
* here:
*
* 1) PIT loop. We set the PIT Channel 2 to oneshot mode and
* load a timeout of 50ms. We read the time right after we
* started the timer and wait until the PIT count down reaches
* zero. In each wait loop iteration we read the TSC and check
* the delta to the previous read. We keep track of the min
* and max values of that delta. The delta is mostly defined
* by the IO time of the PIT access, so we can detect when a
* SMI/SMM disturbance happend between the two reads. If the
* maximum time is significantly larger than the minimum time,
* then we discard the result and have another try.
*
* 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
* calibration value
* Now check the results.
*/
delta = (tr2 - tr1);
do_div(delta, 50);
tsc_khz_val = delta;
if (tsc_pit_min == ULONG_MAX) {
/* PIT gave no useful value */
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) {
printk(KERN_INFO "TSC calibrated against PIT\n");
goto out;
printk(KERN_INFO "TSC: Using PIT calibration value\n");
return tsc_pit_min;
}

/* Check, whether the sampling was disturbed by an SMI */
if (tsc1 == ULLONG_MAX || tsc2 == ULLONG_MAX) {
printk(KERN_WARNING "TSC calibration disturbed by SMI, "
"using PIT calibration result\n");
goto out;
/* The alternative source failed, use the PIT calibration value */
if (tsc_ref_min == ULONG_MAX) {
printk(KERN_WARNING "TSC: HPET/PMTIMER calibration failed due "
"to SMI disturbance. Using PIT calibration\n");
return tsc_pit_min;
}

tsc2 = (tsc2 - tsc1) * 1000000LL;

if (hpet) {
printk(KERN_INFO "TSC calibrated against HPET\n");
if (hpet2 < hpet1)
hpet2 += 0x100000000ULL;
hpet2 -= hpet1;
tsc1 = ((u64)hpet2 * hpet_readl(HPET_PERIOD));
do_div(tsc1, 1000000);
} else {
printk(KERN_INFO "TSC calibrated against PM_TIMER\n");
if (pm2 < pm1)
pm2 += (u64)ACPI_PM_OVRRUN;
pm2 -= pm1;
tsc1 = pm2 * 1000000000LL;
do_div(tsc1, PMTMR_TICKS_PER_SEC);
/* Check the reference deviation */
delta = ((u64) tsc_pit_min) * 100;
do_div(delta, tsc_ref_min);

/*
* If both calibration results are inside a 5% window, the we
* use the lower frequency of those as it is probably the
* closest estimate.
*/
if (delta >= 95 && delta <= 105) {
printk(KERN_INFO "TSC: PIT calibration confirmed by %s.\n",
hpet ? "HPET" : "PMTIMER");
printk(KERN_INFO "TSC: using %s calibration value\n",
tsc_pit_min <= tsc_ref_min ? "PIT" :
hpet ? "HPET" : "PMTIMER");
return tsc_pit_min <= tsc_ref_min ? tsc_pit_min : tsc_ref_min;
}

do_div(tsc2, tsc1);
tsc_khz_val = tsc2;
printk(KERN_WARNING "TSC: PIT calibration deviates from %s: %lu %lu.\n",
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
/* Only called from the Powernow K7 cpu freq driver */
int recalibrate_cpu_khz(void)
Expand Down

0 comments on commit 6ec1799

Please sign in to comment.