[PATCH] x86-64: Disable local APIC timer use on AMD systems with C1E
[powerpc.git] / arch / i386 / kernel / apic.c
index e98b5c7..93aa911 100644 (file)
@@ -26,7 +26,9 @@
 #include <linux/sysdev.h>
 #include <linux/cpu.h>
 #include <linux/clockchips.h>
+#include <linux/acpi_pmtmr.h>
 #include <linux/module.h>
+#include <linux/dmi.h>
 
 #include <asm/atomic.h>
 #include <asm/smp.h>
@@ -37,7 +39,6 @@
 #include <asm/hpet.h>
 #include <asm/i8253.h>
 #include <asm/nmi.h>
-#include <asm/idle.h>
 
 #include <mach_apic.h>
 #include <mach_apicdef.h>
  */
 static int enable_local_apic __initdata = 0;
 
-/* Enable local APIC timer for highres/dyntick on UP */
-static int enable_local_apic_timer __initdata = 0;
+/* Local APIC timer verification ok */
+static int local_apic_timer_verify_ok;
+/* Disable local APIC timer from the kernel commandline or via dmi quirk */
+static int local_apic_timer_disabled;
+/* Local APIC timer works in C2 */
+int local_apic_timer_c2_ok;
+EXPORT_SYMBOL_GPL(local_apic_timer_c2_ok);
 
 /*
  * Debug level, exported for io_apic.c
@@ -82,7 +88,7 @@ static void apic_pm_activate(void);
 static struct clock_event_device lapic_clockevent = {
        .name           = "lapic",
        .features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT
-                       | CLOCK_EVT_FEAT_C3STOP,
+                       | CLOCK_EVT_FEAT_C3STOP | CLOCK_EVT_FEAT_DUMMY,
        .shift          = 32,
        .set_mode       = lapic_timer_setup,
        .set_next_event = lapic_next_event,
@@ -159,64 +165,8 @@ int lapic_get_maxlvt(void)
  * Local APIC timer
  */
 
-/*
- * This part sets up the APIC 32 bit clock in LVTT1, with HZ interrupts
- * per second. We assume that the caller has already set up the local
- * APIC.
- *
- * The APIC timer is not exactly sync with the external timer chip, it
- * closely follows bus clocks.
- */
-
-/*
- * FIXME: Move this to i8253.h. There is no need to keep the access to
- * the PIT scattered all around the place -tglx
- */
-
-/*
- * The timer chip is already set up at HZ interrupts per second here,
- * but we do not accept timer interrupts yet. We only allow the BP
- * to calibrate.
- */
-static unsigned int __devinit get_8254_timer_count(void)
-{
-       unsigned long flags;
-
-       unsigned int count;
-
-       spin_lock_irqsave(&i8253_lock, flags);
-
-       outb_p(0x00, PIT_MODE);
-       count = inb_p(PIT_CH0);
-       count |= inb_p(PIT_CH0) << 8;
-
-       spin_unlock_irqrestore(&i8253_lock, flags);
-
-       return count;
-}
-
-/* next tick in 8254 can be caught by catching timer wraparound */
-static void __devinit wait_8254_wraparound(void)
-{
-       unsigned int curr_count, prev_count;
-
-       curr_count = get_8254_timer_count();
-       do {
-               prev_count = curr_count;
-               curr_count = get_8254_timer_count();
-
-               /* workaround for broken Mercury/Neptune */
-               if (prev_count >= curr_count + 0x100)
-                       curr_count = get_8254_timer_count();
-
-       } while (prev_count >= curr_count);
-}
-
-/*
- * Default initialization for 8254 timers. If we use other timers like HPET,
- * we override this later
- */
-void (*wait_timer_tick)(void) __devinitdata = wait_8254_wraparound;
+/* Clock divisor is set to 16 */
+#define APIC_DIVISOR 16
 
 /*
  * This function sets up the local APIC timer, with a timeout of
@@ -228,9 +178,6 @@ void (*wait_timer_tick)(void) __devinitdata = wait_8254_wraparound;
  * We do reads before writes even if unnecessary, to get around the
  * P5 APIC double write bug.
  */
-
-#define APIC_DIVISOR 16
-
 static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen)
 {
        unsigned int lvtt_value, tmp_value;
@@ -277,6 +224,10 @@ static void lapic_timer_setup(enum clock_event_mode mode,
        unsigned long flags;
        unsigned int v;
 
+       /* Lapic used for broadcast ? */
+       if (!local_apic_timer_verify_ok)
+               return;
+
        local_irq_save(flags);
 
        switch (mode) {
@@ -321,111 +272,238 @@ static void __devinit setup_APIC_timer(void)
 }
 
 /*
- * 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.
+ * 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.
  *
- * TODO: Fix this rather than saying "Ugh" -tglx
+ * Monitoring the jiffies value is inaccurate and the clockevents
+ * infrastructure allows us to do a simple substitution of the interrupt
+ * handler.
  *
- * 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.
+ * 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).
  */
 
-static int __init calibrate_APIC_clock(void)
+#define LAPIC_CAL_LOOPS                (HZ/10)
+
+static __initdata volatile 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 t1 = 0, t2 = 0;
-       long tt1, tt2;
-       long result;
-       int i;
-       const int LOOPS = HZ/10;
+       unsigned long long tsc = 0;
+       long tapic = apic_read(APIC_TMCCT);
+       unsigned long pm = acpi_pm_read_early();
 
-       apic_printk(APIC_VERBOSE, "calibrating APIC timer ...\n");
+       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;
 
-       /*
-        * Put whatever arbitrary (but long enough) timeout
-        * value into the APIC clock, we just want to get the
-        * counter running for calibration.
-        */
-       __setup_APIC_LVTT(1000000000, 0, 0);
+       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;
+       }
+}
 
-       /*
-        * The timer chip counts down to zero. Let's wait
-        * for a wraparound to start exact measurement:
-        * (the current tick might have been already half done)
-        */
+/*
+ * Setup the boot APIC
+ *
+ * Calibrate and verify the result.
+ */
+void __init setup_boot_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;
 
-       wait_timer_tick();
+       if (boot_cpu_has(X86_FEATURE_LAPIC_TIMER_BROKEN))
+               local_apic_timer_disabled = 1;
 
        /*
-        * We wrapped around just now. Let's start:
+        * The local apic timer can be disabled via the kernel
+        * commandline or from the test above. Register the lapic
+        * timer as a dummy clock event source on SMP systems, so the
+        * broadcast mechanism is used. On UP systems simply ignore it.
         */
-       if (cpu_has_tsc)
-               rdtscll(t1);
-       tt1 = apic_read(APIC_TMCCT);
+       if (local_apic_timer_disabled) {
+               /* No broadcast on UP ! */
+               if (num_possible_cpus() > 1)
+                       setup_APIC_timer();
+               return;
+       }
 
-       /*
-        * Let's wait LOOPS wraprounds:
-        */
-       for (i = 0; i < LOOPS; i++)
-               wait_timer_tick();
+       apic_printk(APIC_VERBOSE, "Using local APIC timer interrupts.\n"
+                   "calibrating APIC timer ...\n");
 
-       tt2 = apic_read(APIC_TMCCT);
-       if (cpu_has_tsc)
-               rdtscll(t2);
+       local_irq_disable();
+
+       /* Replace the global interrupt handler */
+       real_handler = global_clock_event->event_handler;
+       global_clock_event->event_handler = lapic_cal_handler;
 
        /*
-        * The APIC bus clock counter is 32 bits only, it
-        * might have overflown, but note that we use signed
-        * longs, thus no extra care needed.
-        *
-        * underflown to be exact, as the timer counts down ;)
+        * 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);
 
-       result = (tt1-tt2)*APIC_DIVISOR/LOOPS;
+       /* 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(tt1-tt2, TICK_NSEC * LOOPS, 32);
+       lapic_clockevent.mult = div_sc(delta, TICK_NSEC * LAPIC_CAL_LOOPS, 32);
        lapic_clockevent.max_delta_ns =
                clockevent_delta2ns(0x7FFFFF, &lapic_clockevent);
        lapic_clockevent.min_delta_ns =
                clockevent_delta2ns(0xF, &lapic_clockevent);
 
-       apic_printk(APIC_VERBOSE, "..... tt1-tt2 %ld\n", tt1 - tt2);
+       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: %ld\n", result);
+       apic_printk(APIC_VERBOSE, "..... calibration result: %u\n",
+                   calibration_result);
 
-       if (cpu_has_tsc)
+       if (cpu_has_tsc) {
+               delta = (long)(lapic_cal_tsc2 - lapic_cal_tsc1);
                apic_printk(APIC_VERBOSE, "..... CPU clock speed is "
-                       "%ld.%04ld MHz.\n",
-                       ((long)(t2-t1)/LOOPS)/(1000000/HZ),
-                       ((long)(t2-t1)/LOOPS)%(1000000/HZ));
+                           "%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 "
-               "%ld.%04ld MHz.\n",
-               result/(1000000/HZ),
-               result%(1000000/HZ));
+                   "%u.%04u MHz.\n",
+                   calibration_result / (1000000 / HZ),
+                   calibration_result % (1000000 / HZ));
 
-       return result;
-}
+       local_apic_timer_verify_ok = 1;
 
-void __init setup_boot_APIC_clock(void)
-{
-       unsigned long flags;
-       apic_printk(APIC_VERBOSE, "Using local APIC timer interrupts.\n");
+       /* We trust the pm timer based calibration */
+       if (!pm_referenced) {
+               apic_printk(APIC_VERBOSE, "... verify APIC timer\n");
 
-       local_irq_save(flags);
+               /*
+                * Setup the apic timer manually
+                */
+               levt->event_handler = lapic_cal_handler;
+               lapic_timer_setup(CLOCK_EVT_MODE_PERIODIC, levt);
+               lapic_cal_loops = -1;
 
-       calibration_result = calibrate_APIC_clock();
-       /*
-        * Now set up the timer for real.
-        */
-       setup_APIC_timer();
+               /* Let the interrupts run */
+               local_irq_enable();
 
-       local_irq_restore(flags);
+               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
+                       local_apic_timer_verify_ok = 0;
+       } else
+               local_irq_enable();
+
+       if (!local_apic_timer_verify_ok) {
+               printk(KERN_WARNING
+                      "APIC timer disabled due to verification failure.\n");
+               /* No broadcast on UP ! */
+               if (num_possible_cpus() == 1)
+                       return;
+       } else {
+               /*
+                * If nmi_watchdog is set to IO_APIC, we need the
+                * PIT/HPET going.  Otherwise register lapic as a dummy
+                * device.
+                */
+               if (nmi_watchdog != NMI_IO_APIC)
+                       lapic_clockevent.features &= ~CLOCK_EVT_FEAT_DUMMY;
+       }
+
+       /* Setup the lapic or request the broadcast */
+       setup_APIC_timer();
 }
 
 void __devinit setup_secondary_APIC_clock(void)
@@ -442,16 +520,15 @@ static void local_apic_timer_interrupt(void)
        struct clock_event_device *evt = &per_cpu(lapic_events, cpu);
 
        /*
-        * Normally we should not be here till LAPIC has been
-        * initialized but in some cases like kdump, its possible that
-        * there is a pending LAPIC timer interrupt from previous
-        * kernel's context and is delivered in new kernel the moment
-        * interrupts are enabled.
+        * Normally we should not be here till LAPIC has been initialized but
+        * in some cases like kdump, its possible that there is a pending LAPIC
+        * timer interrupt from previous kernel's context and is delivered in
+        * new kernel the moment interrupts are enabled.
         *
-        * Interrupts are enabled early and LAPIC is setup much later,
-        * hence its possible that when we get here evt->event_handler
-        * is NULL. Check for event_handler being NULL and discard
-        * the interrupt as spurious.
+        * Interrupts are enabled early and LAPIC is setup much later, hence
+        * its possible that when we get here evt->event_handler is NULL.
+        * Check for event_handler being NULL and discard the interrupt as
+        * spurious.
         */
        if (!evt->event_handler) {
                printk(KERN_WARNING
@@ -489,7 +566,6 @@ void fastcall smp_apic_timer_interrupt(struct pt_regs *regs)
         * Besides, if we don't timer interrupts ignore the global
         * interrupt lock, which is the WrongThing (tm) to do.
         */
-       exit_idle();
        irq_enter();
        local_apic_timer_interrupt();
        irq_exit();
@@ -1126,12 +1202,19 @@ static int __init parse_nolapic(char *arg)
 }
 early_param("nolapic", parse_nolapic);
 
-static int __init apic_enable_lapic_timer(char *str)
+static int __init parse_disable_lapic_timer(char *arg)
+{
+       local_apic_timer_disabled = 1;
+       return 0;
+}
+early_param("nolapic_timer", parse_disable_lapic_timer);
+
+static int __init parse_lapic_timer_c2_ok(char *arg)
 {
-       enable_local_apic_timer = 1;
+       local_apic_timer_c2_ok = 1;
        return 0;
 }
-early_param("lapictimer", apic_enable_lapic_timer);
+early_param("lapic_timer_c2_ok", parse_lapic_timer_c2_ok);
 
 static int __init apic_set_verbosity(char *str)
 {
@@ -1156,7 +1239,6 @@ void smp_spurious_interrupt(struct pt_regs *regs)
 {
        unsigned long v;
 
-       exit_idle();
        irq_enter();
        /*
         * Check if this really is a spurious interrupt and ACK it
@@ -1180,7 +1262,6 @@ void smp_error_interrupt(struct pt_regs *regs)
 {
        unsigned long v, v1;
 
-       exit_idle();
        irq_enter();
        /* First tickle the hardware, only then report what went on. -- REW */
        v = apic_read(APIC_ESR);