Module Name:    src
Committed By:   ad
Date:           Fri May  8 22:01:55 UTC 2020

Modified Files:
        src/sys/arch/x86/include: cpu_counter.h
        src/sys/arch/x86/x86: cpu.c tsc.c
        src/sys/dev/ic: hpet.c hpetvar.h

Log Message:
Fix the TSC timecounter (on the systems I have access to):

- Make the early i8254-based calculation of frequency a bit more accurate.

- Keep track of how far the HPET & TSC advance between HPET attach and
  secondary CPU boot, and use to compute an accurate value before attaching
  the timecounter.  Initial idea from joerg@.

- When determining skew and drift between CPUs, make each measurement 1000
  times and pick the lowest observed value.  Increase the error threshold to
  1000 clock cycles.

- Use the frequency computed on the boot CPU for secondary CPUs too.

- Remove cpu_counter_serializing().


To generate a diff of this commit:
cvs rdiff -u -r1.5 -r1.6 src/sys/arch/x86/include/cpu_counter.h
cvs rdiff -u -r1.189 -r1.190 src/sys/arch/x86/x86/cpu.c
cvs rdiff -u -r1.43 -r1.44 src/sys/arch/x86/x86/tsc.c
cvs rdiff -u -r1.15 -r1.16 src/sys/dev/ic/hpet.c
cvs rdiff -u -r1.6 -r1.7 src/sys/dev/ic/hpetvar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/x86/include/cpu_counter.h
diff -u src/sys/arch/x86/include/cpu_counter.h:1.5 src/sys/arch/x86/include/cpu_counter.h:1.6
--- src/sys/arch/x86/include/cpu_counter.h:1.5	Wed Feb  2 12:26:42 2011
+++ src/sys/arch/x86/include/cpu_counter.h	Fri May  8 22:01:54 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: cpu_counter.h,v 1.5 2011/02/02 12:26:42 bouyer Exp $	*/
+/*	$NetBSD: cpu_counter.h,v 1.6 2020/05/08 22:01:54 ad Exp $	*/
 
 /*-
  * Copyright (c) 2000, 2008 The NetBSD Foundation, Inc.
@@ -35,7 +35,6 @@
 #ifdef _KERNEL
 
 uint64_t	cpu_counter(void);
-uint64_t	cpu_counter_serializing(void);
 uint32_t	cpu_counter32(void);
 uint64_t	cpu_frequency(struct cpu_info *);
 int		cpu_hascounter(void);

Index: src/sys/arch/x86/x86/cpu.c
diff -u src/sys/arch/x86/x86/cpu.c:1.189 src/sys/arch/x86/x86/cpu.c:1.190
--- src/sys/arch/x86/x86/cpu.c:1.189	Sat May  2 16:44:36 2020
+++ src/sys/arch/x86/x86/cpu.c	Fri May  8 22:01:55 2020
@@ -1,7 +1,7 @@
-/*	$NetBSD: cpu.c,v 1.189 2020/05/02 16:44:36 bouyer Exp $	*/
+/*	$NetBSD: cpu.c,v 1.190 2020/05/08 22:01:55 ad Exp $	*/
 
 /*
- * Copyright (c) 2000-2012 NetBSD Foundation, Inc.
+ * Copyright (c) 2000-2020 NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
@@ -62,7 +62,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: cpu.c,v 1.189 2020/05/02 16:44:36 bouyer Exp $");
+__KERNEL_RCSID(0, "$NetBSD: cpu.c,v 1.190 2020/05/08 22:01:55 ad Exp $");
 
 #include "opt_ddb.h"
 #include "opt_mpbios.h"		/* for MPDEBUG */
@@ -73,6 +73,7 @@ __KERNEL_RCSID(0, "$NetBSD: cpu.c,v 1.18
 #include "lapic.h"
 #include "ioapic.h"
 #include "acpica.h"
+#include "hpet.h"
 
 #include <sys/param.h>
 #include <sys/proc.h>
@@ -119,6 +120,7 @@ __KERNEL_RCSID(0, "$NetBSD: cpu.c,v 1.18
 #endif
 
 #include <dev/ic/mc146818reg.h>
+#include <dev/ic/hpetvar.h>
 #include <i386/isa/nvram.h>
 #include <dev/isa/isareg.h>
 
@@ -433,8 +435,14 @@ cpu_attach(device_t parent, device_t sel
 	 * must be done to allow booting other processors.
 	 */
 	if (!again) {
+		/* Make sure DELAY() (likely i8254_delay()) is initialized. */
+		DELAY(1);
+
+		/*
+		 * Basic init.  Compute an approximate frequency for the TSC
+		 * using the i8254.  If there's a HPET we'll redo it later.
+		 */
 		atomic_or_32(&ci->ci_flags, CPUF_PRESENT | CPUF_PRIMARY);
-		/* Basic init. */
 		cpu_intr_init(ci);
 		cpu_get_tsc_freq(ci);
 		cpu_init(ci);
@@ -451,8 +459,6 @@ cpu_attach(device_t parent, device_t sel
 				lapic_calibrate_timer(ci);
 		}
 #endif
-		/* Make sure DELAY() is initialized. */
-		DELAY(1);
 		kcsan_cpu_init(ci);
 		again = true;
 	}
@@ -718,7 +724,6 @@ cpu_init(struct cpu_info *ci)
 
 	if (ci != &cpu_info_primary) {
 		/* Synchronize TSC */
-		wbinvd();
 		atomic_or_32(&ci->ci_flags, CPUF_RUNNING);
 		tsc_sync_ap(ci);
 	} else {
@@ -734,6 +739,14 @@ cpu_boot_secondary_processors(void)
 	kcpuset_t *cpus;
 	u_long i;
 
+#if NHPET > 0
+	/* Use HPET delay, and re-calibrate TSC on boot CPU using HPET. */
+	if (hpet_delay_p() && x86_delay == i8254_delay) {
+		delay_func = x86_delay = hpet_delay;
+		cpu_get_tsc_freq(curcpu());
+	}
+#endif
+
 	/* Now that we know the number of CPUs, patch the text segment. */
 	x86_patch(false);
 
@@ -842,7 +855,6 @@ cpu_start_secondary(struct cpu_info *ci)
 		 */
 		psl = x86_read_psl();
 		x86_disable_intr();
-		wbinvd();
 		tsc_sync_bp(ci);
 		x86_write_psl(psl);
 	}
@@ -873,7 +885,6 @@ cpu_boot_secondary(struct cpu_info *ci)
 		drift = ci->ci_data.cpu_cc_skew;
 		psl = x86_read_psl();
 		x86_disable_intr();
-		wbinvd();
 		tsc_sync_bp(ci);
 		x86_write_psl(psl);
 		drift -= ci->ci_data.cpu_cc_skew;
@@ -919,7 +930,6 @@ cpu_hatch(void *v)
 	 * Synchronize the TSC for the first time. Note that interrupts are
 	 * off at this point.
 	 */
-	wbinvd();
 	atomic_or_32(&ci->ci_flags, CPUF_PRESENT);
 	tsc_sync_ap(ci);
 
@@ -1310,21 +1320,43 @@ cpu_shutdown(device_t dv, int how)
 void
 cpu_get_tsc_freq(struct cpu_info *ci)
 {
-	uint64_t freq = 0, last_tsc;
+	uint64_t freq = 0, t0, t1;
+	int64_t overhead;
 
-	if (cpu_hascounter())
+	if ((ci->ci_flags & CPUF_PRIMARY) != 0 && cpu_hascounter()) {
 		freq = cpu_tsc_freq_cpuid(ci);
+#if NHPET > 0
+		if (freq == 0)
+			freq = hpet_tsc_freq();
+#endif
+		if (freq == 0) {
+			/*
+			 * Work out the approximate overhead involved below. 
+			 * Discard the result of the first go around the
+			 * loop.
+			 */
+			overhead = 0;
+			for (int i = 0; i <= 8; i++) {
+				t0 = cpu_counter();
+				x86_delay(0);
+				t1 = cpu_counter();
+				if (i > 0) {
+					overhead += (t1 - t0);
+				}
+			}
+			overhead >>= 3;
 
-	if (freq != 0) {
-		/* Use TSC frequency taken from CPUID. */
-		ci->ci_data.cpu_cc_freq = freq;
+			/* Now do the calibration. */
+			t0 = cpu_counter();
+			x86_delay(100000);
+			t1 = cpu_counter();
+			freq = (t1 - t0 - overhead) * 10;
+		}
 	} else {
-		/* Calibrate TSC frequency. */
-		last_tsc = cpu_counter_serializing();
-		delay_func(100000);
-		ci->ci_data.cpu_cc_freq =
-		    (cpu_counter_serializing() - last_tsc) * 10;
+		freq = cpu_info_primary.ci_data.cpu_cc_freq;
 	}
+
+	ci->ci_data.cpu_cc_freq = freq;
 }
 
 void

Index: src/sys/arch/x86/x86/tsc.c
diff -u src/sys/arch/x86/x86/tsc.c:1.43 src/sys/arch/x86/x86/tsc.c:1.44
--- src/sys/arch/x86/x86/tsc.c:1.43	Sat Apr 25 15:26:18 2020
+++ src/sys/arch/x86/x86/tsc.c	Fri May  8 22:01:55 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: tsc.c,v 1.43 2020/04/25 15:26:18 bouyer Exp $	*/
+/*	$NetBSD: tsc.c,v 1.44 2020/05/08 22:01:55 ad Exp $	*/
 
 /*-
  * Copyright (c) 2008, 2020 The NetBSD Foundation, Inc.
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: tsc.c,v 1.43 2020/04/25 15:26:18 bouyer Exp $");
+__KERNEL_RCSID(0, "$NetBSD: tsc.c,v 1.44 2020/05/08 22:01:55 ad Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -47,12 +47,14 @@ __KERNEL_RCSID(0, "$NetBSD: tsc.c,v 1.43
 
 #include "tsc.h"
 
+#define	TSC_SYNC_ROUNDS		1000
+#define	ABS(a)			((a) >= 0 ? (a) : -(a))
+
 u_int	tsc_get_timecount(struct timecounter *);
 
 uint64_t	tsc_freq; /* exported for sysctl */
-static int64_t	tsc_drift_max = 250;	/* max cycles */
+static int64_t	tsc_drift_max = 1000;	/* max cycles */
 static int64_t	tsc_drift_observed;
-static bool	tsc_good;
 
 int tsc_user_enabled = 1;
 
@@ -158,9 +160,6 @@ tsc_tc_init(void)
 
 	ci = curcpu();
 	tsc_freq = ci->ci_data.cpu_cc_freq;
-	tsc_good = (cpu_feature[0] & CPUID_MSR) != 0 &&
-	    (rdmsr(MSR_TSC) != 0 || rdmsr(MSR_TSC) != 0);
-
 	invariant = tsc_is_invariant();
 	if (!invariant) {
 		aprint_debug("TSC not known invariant on this CPU\n");
@@ -206,13 +205,12 @@ tsc_read_bp(struct cpu_info *ci, uint64_
 
 	/* Flag it and read our TSC. */
 	atomic_or_uint(&ci->ci_flags, CPUF_SYNCTSC);
-	bptsc = (rdtsc() >> 1);
 
 	/* Wait for remote to complete, and read ours again. */
 	while ((ci->ci_flags & CPUF_SYNCTSC) != 0) {
 		__insn_barrier();
 	}
-	bptsc += (rdtsc() >> 1);
+	bptsc = rdtsc();
 
 	/* Wait for the results to come in. */
 	while (tsc_sync_cpu == ci) {
@@ -229,17 +227,21 @@ tsc_read_bp(struct cpu_info *ci, uint64_
 void
 tsc_sync_bp(struct cpu_info *ci)
 {
-	int64_t bptsc, aptsc, bsum = 0, asum = 0;
+	int64_t bptsc, aptsc, val, diff;
 
-	tsc_read_bp(ci, &bptsc, &aptsc); /* discarded - cache effects */
-	for (int i = 0; i < 8; i++) {
+	if (!cpu_hascounter())
+		return;
+
+	val = INT64_MAX;
+	for (int i = 0; i < TSC_SYNC_ROUNDS; i++) {
 		tsc_read_bp(ci, &bptsc, &aptsc);
-		bsum += bptsc;
-		asum += aptsc;
+		diff = bptsc - aptsc;
+		if (ABS(diff) < ABS(val)) {
+			val = diff;
+		}
 	}
 
-	/* Compute final value to adjust for skew. */
-	ci->ci_data.cpu_cc_skew = (bsum - asum) >> 3;
+	ci->ci_data.cpu_cc_skew = val;
 }
 
 /*
@@ -255,11 +257,10 @@ tsc_post_ap(struct cpu_info *ci)
 	while ((ci->ci_flags & CPUF_SYNCTSC) == 0) {
 		__insn_barrier();
 	}
-	tsc = (rdtsc() >> 1);
 
 	/* Instruct primary to read its counter. */
 	atomic_and_uint(&ci->ci_flags, ~CPUF_SYNCTSC);
-	tsc += (rdtsc() >> 1);
+	tsc = rdtsc();
 
 	/* Post result.  Ensure the whole value goes out atomically. */
 	(void)atomic_swap_64(&tsc_sync_val, tsc);
@@ -273,8 +274,10 @@ void
 tsc_sync_ap(struct cpu_info *ci)
 {
 
-	tsc_post_ap(ci);
-	for (int i = 0; i < 8; i++) {
+	if (!cpu_hascounter())
+		return;
+
+	for (int i = 0; i < TSC_SYNC_ROUNDS; i++) {
 		tsc_post_ap(ci);
 	}
 }
@@ -321,12 +324,3 @@ cpu_hascounter(void)
 
 	return cpu_feature[0] & CPUID_TSC;
 }
-
-uint64_t
-cpu_counter_serializing(void)
-{
-	if (tsc_good)
-		return rdmsr(MSR_TSC);
-	else
-		return cpu_counter();
-}

Index: src/sys/dev/ic/hpet.c
diff -u src/sys/dev/ic/hpet.c:1.15 src/sys/dev/ic/hpet.c:1.16
--- src/sys/dev/ic/hpet.c:1.15	Fri Apr 24 22:25:07 2020
+++ src/sys/dev/ic/hpet.c	Fri May  8 22:01:54 2020
@@ -1,4 +1,4 @@
-/* $NetBSD: hpet.c,v 1.15 2020/04/24 22:25:07 ad Exp $ */
+/* $NetBSD: hpet.c,v 1.16 2020/05/08 22:01:54 ad Exp $ */
 
 /*
  * Copyright (c) 2006 Nicolas Joly
@@ -33,7 +33,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: hpet.c,v 1.15 2020/04/24 22:25:07 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: hpet.c,v 1.16 2020/05/08 22:01:54 ad Exp $");
 
 #include <sys/systm.h>
 #include <sys/device.h>
@@ -45,6 +45,8 @@ __KERNEL_RCSID(0, "$NetBSD: hpet.c,v 1.1
 #include <sys/bus.h>
 #include <sys/lock.h>
 
+#include <machine/cpu_counter.h>
+
 #include <dev/ic/hpetreg.h>
 #include <dev/ic/hpetvar.h>
 
@@ -52,6 +54,8 @@ static u_int	hpet_get_timecount(struct t
 static bool	hpet_resume(device_t, const pmf_qual_t *);
 
 static struct hpet_softc *hpet0 __read_mostly;
+static uint32_t hpet_attach_val;
+static uint64_t hpet_attach_tsc;
 
 int
 hpet_detach(device_t dv, int flags)
@@ -143,6 +147,14 @@ hpet_attach_subr(device_t dv)
 	eval = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO);
 	val = eval - sval;
 	sc->sc_adj = (int64_t)val * sc->sc_period / 1000;
+
+	/* Store attach-time values for computing TSC frequency later. */
+	if (cpu_hascounter()) {
+		(void)cpu_counter();
+		val = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO);
+		hpet_attach_tsc = cpu_counter();
+		hpet_attach_val = val;
+	}
 }
 
 static u_int
@@ -198,6 +210,40 @@ hpet_delay(unsigned int us)
 	}
 }
 
+uint64_t
+hpet_tsc_freq(void)
+{
+	struct hpet_softc *sc;
+	uint64_t td, val, freq;
+	uint32_t hd;
+	int s;
+
+	if (hpet0 == NULL || !cpu_hascounter())
+		return 0;
+
+	/* Slow down if we got here from attach in under 0.1s. */
+	sc = hpet0;
+	hd = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO);
+	hd -= hpet_attach_val;
+	if (hd < (uint64_t)100000 * 1000000000 / sc->sc_period)
+		hpet_delay(100000);
+
+	/*
+	 * Determine TSC freq by comparing how far the TSC and HPET have
+	 * advanced since attach time.  Take the cost of reading HPET
+	 * register into account and round result to the nearest 1000.
+	 */
+	s = splhigh();
+	(void)cpu_counter();
+	hd = bus_space_read_4(sc->sc_memt, sc->sc_memh, HPET_MCOUNT_LO);
+	td = cpu_counter();
+	splx(s);
+	hd -= hpet_attach_val;
+	val = ((uint64_t)hd * sc->sc_period - sc->sc_adj) / 100000000;
+	freq = (td - hpet_attach_tsc) * 10000000 / val;
+	return rounddown(freq + 500, 1000);
+}
+
 MODULE(MODULE_CLASS_DRIVER, hpet, NULL);
 
 #ifdef _MODULE

Index: src/sys/dev/ic/hpetvar.h
diff -u src/sys/dev/ic/hpetvar.h:1.6 src/sys/dev/ic/hpetvar.h:1.7
--- src/sys/dev/ic/hpetvar.h:1.6	Fri Apr 24 23:29:17 2020
+++ src/sys/dev/ic/hpetvar.h	Fri May  8 22:01:54 2020
@@ -1,4 +1,4 @@
-/* $NetBSD: hpetvar.h,v 1.6 2020/04/24 23:29:17 ad Exp $ */
+/* $NetBSD: hpetvar.h,v 1.7 2020/05/08 22:01:54 ad Exp $ */
 
 /*
  * Copyright (c) 2006 Nicolas Joly
@@ -49,5 +49,6 @@ void	hpet_attach_subr(device_t);
 int	hpet_detach(device_t, int flags);
 void	hpet_delay(unsigned int);
 bool	hpet_delay_p(void);
+uint64_t hpet_tsc_freq(void);
 
 #endif /* _DEV_IC_HPETVAR_H_ */

Reply via email to