Merge tag 'x86_urgent_for_v5.15_rc5' of git://git.kernel.org/pub/scm/linux/kernel...
[sfrench/cifs-2.6.git] / arch / x86 / kernel / hpet.c
index 42fc41dd0e1f17058724bbba75dfa1ede83994c8..882213df37130249b8712322810e5cbec3b92785 100644 (file)
@@ -10,6 +10,7 @@
 #include <asm/irq_remapping.h>
 #include <asm/hpet.h>
 #include <asm/time.h>
 #include <asm/irq_remapping.h>
 #include <asm/hpet.h>
 #include <asm/time.h>
+#include <asm/mwait.h>
 
 #undef  pr_fmt
 #define pr_fmt(fmt) "hpet: " fmt
 
 #undef  pr_fmt
 #define pr_fmt(fmt) "hpet: " fmt
@@ -916,6 +917,83 @@ static bool __init hpet_counting(void)
        return false;
 }
 
        return false;
 }
 
+static bool __init mwait_pc10_supported(void)
+{
+       unsigned int eax, ebx, ecx, mwait_substates;
+
+       if (boot_cpu_data.x86_vendor != X86_VENDOR_INTEL)
+               return false;
+
+       if (!cpu_feature_enabled(X86_FEATURE_MWAIT))
+               return false;
+
+       if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
+               return false;
+
+       cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &mwait_substates);
+
+       return (ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) &&
+              (ecx & CPUID5_ECX_INTERRUPT_BREAK) &&
+              (mwait_substates & (0xF << 28));
+}
+
+/*
+ * Check whether the system supports PC10. If so force disable HPET as that
+ * stops counting in PC10. This check is overbroad as it does not take any
+ * of the following into account:
+ *
+ *     - ACPI tables
+ *     - Enablement of intel_idle
+ *     - Command line arguments which limit intel_idle C-state support
+ *
+ * That's perfectly fine. HPET is a piece of hardware designed by committee
+ * and the only reasons why it is still in use on modern systems is the
+ * fact that it is impossible to reliably query TSC and CPU frequency via
+ * CPUID or firmware.
+ *
+ * If HPET is functional it is useful for calibrating TSC, but this can be
+ * done via PMTIMER as well which seems to be the last remaining timer on
+ * X86/INTEL platforms that has not been completely wreckaged by feature
+ * creep.
+ *
+ * In theory HPET support should be removed altogether, but there are older
+ * systems out there which depend on it because TSC and APIC timer are
+ * dysfunctional in deeper C-states.
+ *
+ * It's only 20 years now that hardware people have been asked to provide
+ * reliable and discoverable facilities which can be used for timekeeping
+ * and per CPU timer interrupts.
+ *
+ * The probability that this problem is going to be solved in the
+ * forseeable future is close to zero, so the kernel has to be cluttered
+ * with heuristics to keep up with the ever growing amount of hardware and
+ * firmware trainwrecks. Hopefully some day hardware people will understand
+ * that the approach of "This can be fixed in software" is not sustainable.
+ * Hope dies last...
+ */
+static bool __init hpet_is_pc10_damaged(void)
+{
+       unsigned long long pcfg;
+
+       /* Check whether PC10 substates are supported */
+       if (!mwait_pc10_supported())
+               return false;
+
+       /* Check whether PC10 is enabled in PKG C-state limit */
+       rdmsrl(MSR_PKG_CST_CONFIG_CONTROL, pcfg);
+       if ((pcfg & 0xF) < 8)
+               return false;
+
+       if (hpet_force_user) {
+               pr_warn("HPET force enabled via command line, but dysfunctional in PC10.\n");
+               return false;
+       }
+
+       pr_info("HPET dysfunctional in PC10. Force disabled.\n");
+       boot_hpet_disable = true;
+       return true;
+}
+
 /**
  * hpet_enable - Try to setup the HPET timer. Returns 1 on success.
  */
 /**
  * hpet_enable - Try to setup the HPET timer. Returns 1 on success.
  */
@@ -929,6 +1007,9 @@ int __init hpet_enable(void)
        if (!is_hpet_capable())
                return 0;
 
        if (!is_hpet_capable())
                return 0;
 
+       if (hpet_is_pc10_damaged())
+               return 0;
+
        hpet_set_mapping();
        if (!hpet_virt_address)
                return 0;
        hpet_set_mapping();
        if (!hpet_virt_address)
                return 0;