x86/timer: Skip PIT initialization on modern chipsets
authorThomas Gleixner <tglx@linutronix.de>
Fri, 28 Jun 2019 07:23:07 +0000 (15:23 +0800)
committerThomas Gleixner <tglx@linutronix.de>
Sat, 29 Jun 2019 09:35:35 +0000 (11:35 +0200)
Recent Intel chipsets including Skylake and ApolloLake have a special
ITSSPRC register which allows the 8254 PIT to be gated.  When gated, the
8254 registers can still be programmed as normal, but there are no IRQ0
timer interrupts.

Some products such as the Connex L1430 and exone go Rugged E11 use this
register to ship with the PIT gated by default. This causes Linux to fail
to boot:

  Kernel panic - not syncing: IO-APIC + timer doesn't work! Boot with
  apic=debug and send a report.

The panic happens before the framebuffer is initialized, so to the user, it
appears as an early boot hang on a black screen.

Affected products typically have a BIOS option that can be used to enable
the 8254 and make Linux work (Chipset -> South Cluster Configuration ->
Miscellaneous Configuration -> 8254 Clock Gating), however it would be best
to make Linux support the no-8254 case.

Modern sytems allow to discover the TSC and local APIC timer frequencies,
so the calibration against the PIT is not required. These systems have
always running timers and the local APIC timer works also in deep power
states.

So the setup of the PIT including the IO-APIC timer interrupt delivery
checks are a pointless exercise.

Skip the PIT setup and the IO-APIC timer interrupt checks on these systems,
which avoids the panic caused by non ticking PITs and also speeds up the
boot process.

Thanks to Daniel for providing the changelog, initial analysis of the
problem and testing against a variety of machines.

Reported-by: Daniel Drake <drake@endlessm.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Daniel Drake <drake@endlessm.com>
Cc: bp@alien8.de
Cc: hpa@zytor.com
Cc: linux@endlessm.com
Cc: rafael.j.wysocki@intel.com
Cc: hdegoede@redhat.com
Link: https://lkml.kernel.org/r/20190628072307.24678-1-drake@endlessm.com
arch/x86/include/asm/apic.h
arch/x86/include/asm/time.h
arch/x86/kernel/apic/apic.c
arch/x86/kernel/apic/io_apic.c
arch/x86/kernel/i8253.c
arch/x86/kernel/time.c

index c986e32b5a481b5b6578475de7a1c7fcb6a74b03..693a0ad5601989f93f41f02f1ffbb9848b8c59b6 100644 (file)
@@ -173,6 +173,7 @@ extern void lapic_assign_system_vectors(void);
 extern void lapic_assign_legacy_vector(unsigned int isairq, bool replace);
 extern void lapic_online(void);
 extern void lapic_offline(void);
+extern bool apic_needs_pit(void);
 
 #else /* !CONFIG_X86_LOCAL_APIC */
 static inline void lapic_shutdown(void) { }
@@ -186,6 +187,7 @@ static inline void init_bsp_APIC(void) { }
 static inline void apic_intr_mode_init(void) { }
 static inline void lapic_assign_system_vectors(void) { }
 static inline void lapic_assign_legacy_vector(unsigned int i, bool r) { }
+static inline bool apic_needs_pit(void) { return true; }
 #endif /* !CONFIG_X86_LOCAL_APIC */
 
 #ifdef CONFIG_X86_X2APIC
index cef818b16045f6b1d8e511133081b72173d52d8b..8ac563abb567b3f4254beb5b35214cd83e30da3e 100644 (file)
@@ -7,6 +7,7 @@
 
 extern void hpet_time_init(void);
 extern void time_init(void);
+extern bool pit_timer_init(void);
 
 extern struct clock_event_device *global_clock_event;
 
index dc4ed655dbbbf90a337a2434225e096a5cca1f1f..29fd50840b55a039d2a876eef70989426023b1c7 100644 (file)
@@ -820,6 +820,33 @@ static int __init lapic_init_clockevent(void)
        return 0;
 }
 
+bool __init apic_needs_pit(void)
+{
+       /*
+        * If the frequencies are not known, PIT is required for both TSC
+        * and apic timer calibration.
+        */
+       if (!tsc_khz || !cpu_khz)
+               return true;
+
+       /* Is there an APIC at all? */
+       if (!boot_cpu_has(X86_FEATURE_APIC))
+               return true;
+
+       /* Deadline timer is based on TSC so no further PIT action required */
+       if (boot_cpu_has(X86_FEATURE_TSC_DEADLINE_TIMER))
+               return false;
+
+       /* APIC timer disabled? */
+       if (disable_apic_timer)
+               return true;
+       /*
+        * The APIC timer frequency is known already, no PIT calibration
+        * required. If unknown, let the PIT be initialized.
+        */
+       return lapic_timer_period == 0;
+}
+
 static int __init calibrate_APIC_clock(void)
 {
        struct clock_event_device *levt = this_cpu_ptr(&lapic_events);
index 53aa234a6803f295aa7d24fc81e17c38dd99b216..1bb8647988001fb3569e660b6a32ec86d8fd75ba 100644 (file)
@@ -58,6 +58,7 @@
 #include <asm/acpi.h>
 #include <asm/dma.h>
 #include <asm/timer.h>
+#include <asm/time.h>
 #include <asm/i8259.h>
 #include <asm/setup.h>
 #include <asm/irq_remapping.h>
@@ -2083,6 +2084,9 @@ static inline void __init check_timer(void)
        unsigned long flags;
        int no_pin1 = 0;
 
+       if (!global_clock_event)
+               return;
+
        local_irq_save(flags);
 
        /*
index 0d307a657abbb253d4679471bd1ac202279ff447..2b7999a1a50a83b07084e93903786e15479684b8 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/timex.h>
 #include <linux/i8253.h>
 
+#include <asm/apic.h>
 #include <asm/hpet.h>
 #include <asm/time.h>
 #include <asm/smp.h>
  */
 struct clock_event_device *global_clock_event;
 
-void __init setup_pit_timer(void)
+/*
+ * Modern chipsets can disable the PIT clock which makes it unusable. It
+ * would be possible to enable the clock but the registers are chipset
+ * specific and not discoverable. Avoid the whack a mole game.
+ *
+ * These platforms have discoverable TSC/CPU frequencies but this also
+ * requires to know the local APIC timer frequency as it normally is
+ * calibrated against the PIT interrupt.
+ */
+static bool __init use_pit(void)
+{
+       if (!IS_ENABLED(CONFIG_X86_TSC) || !boot_cpu_has(X86_FEATURE_TSC))
+               return true;
+
+       /* This also returns true when APIC is disabled */
+       return apic_needs_pit();
+}
+
+bool __init pit_timer_init(void)
 {
+       if (!use_pit())
+               return false;
+
        clockevent_i8253_init(true);
        global_clock_event = &i8253_clockevent;
+       return true;
 }
 
 #ifndef CONFIG_X86_64
index 0e14f6c0d35e0f7ef0998963390eab9587d8186e..07c0e960b3f3b2d4ba84aa37c80a20ddf0288050 100644 (file)
@@ -82,8 +82,11 @@ static void __init setup_default_timer_irq(void)
 /* Default timer init function */
 void __init hpet_time_init(void)
 {
-       if (!hpet_enable())
-               setup_pit_timer();
+       if (!hpet_enable()) {
+               if (!pit_timer_init())
+                       return;
+       }
+
        setup_default_timer_irq();
 }