git: b6c207a8301e - stable/13 - x86: Defer early TSC timecounter calibration to SI_SUB_CPU

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Thu, 31 Mar 2022 16:07:51 UTC
The branch stable/13 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=b6c207a8301e3da94b6282bdf4a798f1e898fc29

commit b6c207a8301e3da94b6282bdf4a798f1e898fc29
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2022-03-05 00:34:43 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2022-03-31 16:05:46 +0000

    x86: Defer early TSC timecounter calibration to SI_SUB_CPU
    
    If we can't determine the TSC frequency using CPU registers, we need to
    give a chance for Hyper-V drivers to register a timecounter (during
    SI_SUB_HYPERVISOR) since an emulated 8254 might not be available.
    Thus, split probe_tsc_freq() into early and late stages, and wait until
    the latter to attempt calibration using a reference clock.
    
    Fixes:          84369dd52369 ("x86: Probe the TSC frequency earlier")
    Reported and tested by: khng, Shawn Webb
    Sponsored by:   The FreeBSD Foundation
    
    (cherry picked from commit 075e2779aca7cbd8f201ce0e1bb60318d0cbd8b8)
---
 sys/x86/x86/tsc.c | 50 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 14 deletions(-)

diff --git a/sys/x86/x86/tsc.c b/sys/x86/x86/tsc.c
index 024800077e86..e821246ae293 100644
--- a/sys/x86/x86/tsc.c
+++ b/sys/x86/x86/tsc.c
@@ -240,7 +240,7 @@ tsc_freq_intel_brand(uint64_t *res)
 }
 
 static void
-tsc_freq_8254(uint64_t *res)
+tsc_freq_tc(uint64_t *res)
 {
 	uint64_t tsc1, tsc2;
 	int64_t overhead;
@@ -262,8 +262,15 @@ tsc_freq_8254(uint64_t *res)
 	tsc_freq = (tsc2 - tsc1 - overhead) * 10;
 }
 
+/*
+ * Try to determine the TSC frequency using CPUID or hypercalls.  If successful,
+ * this lets use the TSC for early DELAY() calls instead of the 8254 timer,
+ * which may be unreliable or entirely absent on contemporary systems.  However,
+ * avoid calibrating using the 8254 here so as to give hypervisors a chance to
+ * register a timecounter that can be used instead.
+ */
 static void
-probe_tsc_freq(void)
+probe_tsc_freq_early(void)
 {
 #ifdef __i386__
 	/* The TSC is known to be broken on certain CPUs. */
@@ -364,7 +371,20 @@ probe_tsc_freq(void)
 		if (bootverbose)
 			printf("Early TSC frequency %juHz derived from CPUID\n",
 			    (uintmax_t)tsc_freq);
-	} else if (tsc_skip_calibration) {
+	}
+}
+
+/*
+ * If we were unable to determine the TSC frequency via CPU registers, try
+ * to calibrate against a known clock.
+ */
+static void
+probe_tsc_freq_late(void)
+{
+	if (tsc_freq != 0)
+		return;
+
+	if (tsc_skip_calibration) {
 		/*
 		 * Try to parse the brand string to obtain the nominal TSC
 		 * frequency.
@@ -380,15 +400,24 @@ probe_tsc_freq(void)
 		}
 	} else {
 		/*
-		 * Calibrate against the 8254 PIT.  This estimate will be
-		 * refined later in tsc_calib().
+		 * Calibrate against a timecounter or the 8254 PIT.  This
+		 * estimate will be refined later in tsc_calib().
 		 */
-		tsc_freq_8254(&tsc_freq);
+		tsc_freq_tc(&tsc_freq);
 		if (bootverbose)
 			printf(
 		    "Early TSC frequency %juHz calibrated from 8254 PIT\n",
 			    (uintmax_t)tsc_freq);
 	}
+}
+
+void
+start_TSC(void)
+{
+	if ((cpu_feature & CPUID_TSC) == 0 || tsc_disabled)
+		return;
+
+	probe_tsc_freq_late();
 
 	if (cpu_power_ecx & CPUID_PERF_STAT) {
 		/*
@@ -401,13 +430,6 @@ probe_tsc_freq(void)
 		if (rdmsr(MSR_MPERF) > 0 && rdmsr(MSR_APERF) > 0)
 			tsc_perf_stat = 1;
 	}
-}
-
-void
-start_TSC(void)
-{
-	if ((cpu_feature & CPUID_TSC) == 0 || tsc_disabled)
-		return;
 
 	/*
 	 * Inform CPU accounting about our boot-time clock rate.  This will
@@ -716,7 +738,7 @@ tsc_init(void)
 	if ((cpu_feature & CPUID_TSC) == 0 || tsc_disabled)
 		return;
 
-	probe_tsc_freq();
+	probe_tsc_freq_early();
 }
 
 /*