mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-26 22:24:09 +08:00
x86: apic copy calibrate_APIC_clock to each other in apic_32/64.c
Signed-off-by: Yinghai Lu <yhlu.kernel@gmail.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
parent
dc1528dd86
commit
2f04fa888d
@ -424,6 +424,90 @@ static void __cpuinit setup_APIC_timer(void)
|
||||
clockevents_register_device(levt);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
/*
|
||||
* In this function we calibrate APIC bus clocks to the external
|
||||
* timer. Unfortunately we cannot use jiffies and the timer irq
|
||||
* to calibrate, since some later bootup code depends on getting
|
||||
* the first irq? Ugh.
|
||||
*
|
||||
* We want to do the calibration only once since we
|
||||
* want to have local timer irqs syncron. CPUs connected
|
||||
* by the same APIC bus have the very same bus frequency.
|
||||
* And we want to have irqs off anyways, no accidental
|
||||
* APIC irq that way.
|
||||
*/
|
||||
|
||||
#define TICK_COUNT 100000000
|
||||
|
||||
static int __init calibrate_APIC_clock(void)
|
||||
{
|
||||
unsigned apic, apic_start;
|
||||
unsigned long tsc, tsc_start;
|
||||
int result;
|
||||
|
||||
local_irq_disable();
|
||||
|
||||
/*
|
||||
* Put whatever arbitrary (but long enough) timeout
|
||||
* value into the APIC clock, we just want to get the
|
||||
* counter running for calibration.
|
||||
*
|
||||
* No interrupt enable !
|
||||
*/
|
||||
__setup_APIC_LVTT(250000000, 0, 0);
|
||||
|
||||
apic_start = apic_read(APIC_TMCCT);
|
||||
#ifdef CONFIG_X86_PM_TIMER
|
||||
if (apic_calibrate_pmtmr && pmtmr_ioport) {
|
||||
pmtimer_wait(5000); /* 5ms wait */
|
||||
apic = apic_read(APIC_TMCCT);
|
||||
result = (apic_start - apic) * 1000L / 5;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
rdtscll(tsc_start);
|
||||
|
||||
do {
|
||||
apic = apic_read(APIC_TMCCT);
|
||||
rdtscll(tsc);
|
||||
} while ((tsc - tsc_start) < TICK_COUNT &&
|
||||
(apic_start - apic) < TICK_COUNT);
|
||||
|
||||
result = (apic_start - apic) * 1000L * tsc_khz /
|
||||
(tsc - tsc_start);
|
||||
}
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
printk(KERN_DEBUG "APIC timer calibration result %d\n", result);
|
||||
|
||||
printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n",
|
||||
result / 1000 / 1000, result / 1000 % 1000);
|
||||
|
||||
/* Calculate the scaled math multiplication factor */
|
||||
lapic_clockevent.mult = div_sc(result, NSEC_PER_SEC,
|
||||
lapic_clockevent.shift);
|
||||
lapic_clockevent.max_delta_ns =
|
||||
clockevent_delta2ns(0x7FFFFF, &lapic_clockevent);
|
||||
lapic_clockevent.min_delta_ns =
|
||||
clockevent_delta2ns(0xF, &lapic_clockevent);
|
||||
|
||||
calibration_result = (result * APIC_DIVISOR) / HZ;
|
||||
|
||||
/*
|
||||
* Do a sanity check on the APIC calibration result
|
||||
*/
|
||||
if (calibration_result < (1000000 / HZ)) {
|
||||
printk(KERN_WARNING
|
||||
"APIC frequency too slow, disabling apic timer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
/*
|
||||
* In this functions we calibrate APIC bus clocks to the external timer.
|
||||
*
|
||||
@ -635,6 +719,8 @@ static int __init calibrate_APIC_clock(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Setup the boot APIC
|
||||
*
|
||||
|
@ -478,6 +478,7 @@ static void __cpuinit setup_APIC_timer(void)
|
||||
clockevents_register_device(levt);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
/*
|
||||
* In this function we calibrate APIC bus clocks to the external
|
||||
* timer. Unfortunately we cannot use jiffies and the timer irq
|
||||
@ -560,6 +561,220 @@ static int __init calibrate_APIC_clock(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
/*
|
||||
* In this functions we calibrate APIC bus clocks to the external timer.
|
||||
*
|
||||
* We want to do the calibration only once since we want to have local timer
|
||||
* irqs syncron. CPUs connected by the same APIC bus have the very same bus
|
||||
* frequency.
|
||||
*
|
||||
* This was previously done by reading the PIT/HPET and waiting for a wrap
|
||||
* around to find out, that a tick has elapsed. I have a box, where the PIT
|
||||
* readout is broken, so it never gets out of the wait loop again. This was
|
||||
* also reported by others.
|
||||
*
|
||||
* Monitoring the jiffies value is inaccurate and the clockevents
|
||||
* infrastructure allows us to do a simple substitution of the interrupt
|
||||
* handler.
|
||||
*
|
||||
* The calibration routine also uses the pm_timer when possible, as the PIT
|
||||
* happens to run way too slow (factor 2.3 on my VAIO CoreDuo, which goes
|
||||
* back to normal later in the boot process).
|
||||
*/
|
||||
|
||||
#define LAPIC_CAL_LOOPS (HZ/10)
|
||||
|
||||
static __initdata int lapic_cal_loops = -1;
|
||||
static __initdata long lapic_cal_t1, lapic_cal_t2;
|
||||
static __initdata unsigned long long lapic_cal_tsc1, lapic_cal_tsc2;
|
||||
static __initdata unsigned long lapic_cal_pm1, lapic_cal_pm2;
|
||||
static __initdata unsigned long lapic_cal_j1, lapic_cal_j2;
|
||||
|
||||
/*
|
||||
* Temporary interrupt handler.
|
||||
*/
|
||||
static void __init lapic_cal_handler(struct clock_event_device *dev)
|
||||
{
|
||||
unsigned long long tsc = 0;
|
||||
long tapic = apic_read(APIC_TMCCT);
|
||||
unsigned long pm = acpi_pm_read_early();
|
||||
|
||||
if (cpu_has_tsc)
|
||||
rdtscll(tsc);
|
||||
|
||||
switch (lapic_cal_loops++) {
|
||||
case 0:
|
||||
lapic_cal_t1 = tapic;
|
||||
lapic_cal_tsc1 = tsc;
|
||||
lapic_cal_pm1 = pm;
|
||||
lapic_cal_j1 = jiffies;
|
||||
break;
|
||||
|
||||
case LAPIC_CAL_LOOPS:
|
||||
lapic_cal_t2 = tapic;
|
||||
lapic_cal_tsc2 = tsc;
|
||||
if (pm < lapic_cal_pm1)
|
||||
pm += ACPI_PM_OVRRUN;
|
||||
lapic_cal_pm2 = pm;
|
||||
lapic_cal_j2 = jiffies;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int __init calibrate_APIC_clock(void)
|
||||
{
|
||||
struct clock_event_device *levt = &__get_cpu_var(lapic_events);
|
||||
const long pm_100ms = PMTMR_TICKS_PER_SEC/10;
|
||||
const long pm_thresh = pm_100ms/100;
|
||||
void (*real_handler)(struct clock_event_device *dev);
|
||||
unsigned long deltaj;
|
||||
long delta, deltapm;
|
||||
int pm_referenced = 0;
|
||||
|
||||
local_irq_disable();
|
||||
|
||||
/* Replace the global interrupt handler */
|
||||
real_handler = global_clock_event->event_handler;
|
||||
global_clock_event->event_handler = lapic_cal_handler;
|
||||
|
||||
/*
|
||||
* Setup the APIC counter to 1e9. There is no way the lapic
|
||||
* can underflow in the 100ms detection time frame
|
||||
*/
|
||||
__setup_APIC_LVTT(1000000000, 0, 0);
|
||||
|
||||
/* Let the interrupts run */
|
||||
local_irq_enable();
|
||||
|
||||
while (lapic_cal_loops <= LAPIC_CAL_LOOPS)
|
||||
cpu_relax();
|
||||
|
||||
local_irq_disable();
|
||||
|
||||
/* Restore the real event handler */
|
||||
global_clock_event->event_handler = real_handler;
|
||||
|
||||
/* Build delta t1-t2 as apic timer counts down */
|
||||
delta = lapic_cal_t1 - lapic_cal_t2;
|
||||
apic_printk(APIC_VERBOSE, "... lapic delta = %ld\n", delta);
|
||||
|
||||
/* Check, if the PM timer is available */
|
||||
deltapm = lapic_cal_pm2 - lapic_cal_pm1;
|
||||
apic_printk(APIC_VERBOSE, "... PM timer delta = %ld\n", deltapm);
|
||||
|
||||
if (deltapm) {
|
||||
unsigned long mult;
|
||||
u64 res;
|
||||
|
||||
mult = clocksource_hz2mult(PMTMR_TICKS_PER_SEC, 22);
|
||||
|
||||
if (deltapm > (pm_100ms - pm_thresh) &&
|
||||
deltapm < (pm_100ms + pm_thresh)) {
|
||||
apic_printk(APIC_VERBOSE, "... PM timer result ok\n");
|
||||
} else {
|
||||
res = (((u64) deltapm) * mult) >> 22;
|
||||
do_div(res, 1000000);
|
||||
printk(KERN_WARNING "APIC calibration not consistent "
|
||||
"with PM Timer: %ldms instead of 100ms\n",
|
||||
(long)res);
|
||||
/* Correct the lapic counter value */
|
||||
res = (((u64) delta) * pm_100ms);
|
||||
do_div(res, deltapm);
|
||||
printk(KERN_INFO "APIC delta adjusted to PM-Timer: "
|
||||
"%lu (%ld)\n", (unsigned long) res, delta);
|
||||
delta = (long) res;
|
||||
}
|
||||
pm_referenced = 1;
|
||||
}
|
||||
|
||||
/* Calculate the scaled math multiplication factor */
|
||||
lapic_clockevent.mult = div_sc(delta, TICK_NSEC * LAPIC_CAL_LOOPS,
|
||||
lapic_clockevent.shift);
|
||||
lapic_clockevent.max_delta_ns =
|
||||
clockevent_delta2ns(0x7FFFFF, &lapic_clockevent);
|
||||
lapic_clockevent.min_delta_ns =
|
||||
clockevent_delta2ns(0xF, &lapic_clockevent);
|
||||
|
||||
calibration_result = (delta * APIC_DIVISOR) / LAPIC_CAL_LOOPS;
|
||||
|
||||
apic_printk(APIC_VERBOSE, "..... delta %ld\n", delta);
|
||||
apic_printk(APIC_VERBOSE, "..... mult: %ld\n", lapic_clockevent.mult);
|
||||
apic_printk(APIC_VERBOSE, "..... calibration result: %u\n",
|
||||
calibration_result);
|
||||
|
||||
if (cpu_has_tsc) {
|
||||
delta = (long)(lapic_cal_tsc2 - lapic_cal_tsc1);
|
||||
apic_printk(APIC_VERBOSE, "..... CPU clock speed is "
|
||||
"%ld.%04ld MHz.\n",
|
||||
(delta / LAPIC_CAL_LOOPS) / (1000000 / HZ),
|
||||
(delta / LAPIC_CAL_LOOPS) % (1000000 / HZ));
|
||||
}
|
||||
|
||||
apic_printk(APIC_VERBOSE, "..... host bus clock speed is "
|
||||
"%u.%04u MHz.\n",
|
||||
calibration_result / (1000000 / HZ),
|
||||
calibration_result % (1000000 / HZ));
|
||||
|
||||
/*
|
||||
* Do a sanity check on the APIC calibration result
|
||||
*/
|
||||
if (calibration_result < (1000000 / HZ)) {
|
||||
local_irq_enable();
|
||||
printk(KERN_WARNING
|
||||
"APIC frequency too slow, disabling apic timer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
levt->features &= ~CLOCK_EVT_FEAT_DUMMY;
|
||||
|
||||
/* We trust the pm timer based calibration */
|
||||
if (!pm_referenced) {
|
||||
apic_printk(APIC_VERBOSE, "... verify APIC timer\n");
|
||||
|
||||
/*
|
||||
* Setup the apic timer manually
|
||||
*/
|
||||
levt->event_handler = lapic_cal_handler;
|
||||
lapic_timer_setup(CLOCK_EVT_MODE_PERIODIC, levt);
|
||||
lapic_cal_loops = -1;
|
||||
|
||||
/* Let the interrupts run */
|
||||
local_irq_enable();
|
||||
|
||||
while (lapic_cal_loops <= LAPIC_CAL_LOOPS)
|
||||
cpu_relax();
|
||||
|
||||
local_irq_disable();
|
||||
|
||||
/* Stop the lapic timer */
|
||||
lapic_timer_setup(CLOCK_EVT_MODE_SHUTDOWN, levt);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
/* Jiffies delta */
|
||||
deltaj = lapic_cal_j2 - lapic_cal_j1;
|
||||
apic_printk(APIC_VERBOSE, "... jiffies delta = %lu\n", deltaj);
|
||||
|
||||
/* Check, if the jiffies result is consistent */
|
||||
if (deltaj >= LAPIC_CAL_LOOPS-2 && deltaj <= LAPIC_CAL_LOOPS+2)
|
||||
apic_printk(APIC_VERBOSE, "... jiffies result ok\n");
|
||||
else
|
||||
levt->features |= CLOCK_EVT_FEAT_DUMMY;
|
||||
} else
|
||||
local_irq_enable();
|
||||
|
||||
if (levt->features & CLOCK_EVT_FEAT_DUMMY) {
|
||||
printk(KERN_WARNING
|
||||
"APIC timer disabled due to verification failure.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Setup the boot APIC
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user