xref: /freebsd/sys/kern/subr_clockcalib.c (revision c2705ceaeb09d8579661097fd358ffb5defb5624)
1*c2705ceaSColin Percival /*-
2*c2705ceaSColin Percival  * Copyright (c) 2022 Colin Percival
3*c2705ceaSColin Percival  * All rights reserved.
4*c2705ceaSColin Percival  *
5*c2705ceaSColin Percival  * Redistribution and use in source and binary forms, with or without
6*c2705ceaSColin Percival  * modification, are permitted provided that the following conditions
7*c2705ceaSColin Percival  * are met:
8*c2705ceaSColin Percival  * 1. Redistributions of source code must retain the above copyright
9*c2705ceaSColin Percival  *    notice, this list of conditions and the following disclaimer.
10*c2705ceaSColin Percival  * 2. Redistributions in binary form must reproduce the above copyright
11*c2705ceaSColin Percival  *    notice, this list of conditions and the following disclaimer in the
12*c2705ceaSColin Percival  *    documentation and/or other materials provided with the distribution.
13*c2705ceaSColin Percival  *
14*c2705ceaSColin Percival  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15*c2705ceaSColin Percival  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16*c2705ceaSColin Percival  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17*c2705ceaSColin Percival  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18*c2705ceaSColin Percival  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19*c2705ceaSColin Percival  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20*c2705ceaSColin Percival  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21*c2705ceaSColin Percival  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22*c2705ceaSColin Percival  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23*c2705ceaSColin Percival  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24*c2705ceaSColin Percival  * SUCH DAMAGE.
25*c2705ceaSColin Percival  */
26*c2705ceaSColin Percival 
27*c2705ceaSColin Percival #include <sys/cdefs.h>
28*c2705ceaSColin Percival __FBSDID("$FreeBSD$");
29*c2705ceaSColin Percival 
30*c2705ceaSColin Percival #include <sys/param.h>
31*c2705ceaSColin Percival #include <sys/systm.h>
32*c2705ceaSColin Percival #include <sys/timetc.h>
33*c2705ceaSColin Percival #include <sys/tslog.h>
34*c2705ceaSColin Percival #include <machine/cpu.h>
35*c2705ceaSColin Percival 
36*c2705ceaSColin Percival /**
37*c2705ceaSColin Percival  * clockcalib(clk, clkname):
38*c2705ceaSColin Percival  * Return the frequency of the provided timer, as calibrated against the
39*c2705ceaSColin Percival  * current best-available timecounter.
40*c2705ceaSColin Percival  */
41*c2705ceaSColin Percival uint64_t
42*c2705ceaSColin Percival clockcalib(uint64_t (*clk)(void), const char *clkname)
43*c2705ceaSColin Percival {
44*c2705ceaSColin Percival 	struct timecounter *tc = atomic_load_ptr(&timecounter);
45*c2705ceaSColin Percival 	uint64_t clk0, clk1, clk_delay, n, passes = 0;
46*c2705ceaSColin Percival 	uint64_t t0, t1, tadj, tlast;
47*c2705ceaSColin Percival 	double mu_clk = 0;
48*c2705ceaSColin Percival 	double mu_t = 0;
49*c2705ceaSColin Percival 	double va_clk = 0;
50*c2705ceaSColin Percival 	double va_t = 0;
51*c2705ceaSColin Percival 	double cva = 0;
52*c2705ceaSColin Percival 	double d1, d2;
53*c2705ceaSColin Percival 	double inv_n;
54*c2705ceaSColin Percival 	uint64_t freq;
55*c2705ceaSColin Percival 
56*c2705ceaSColin Percival 	TSENTER();
57*c2705ceaSColin Percival 	/*-
58*c2705ceaSColin Percival 	 * The idea here is to compute a best-fit linear regression between
59*c2705ceaSColin Percival 	 * the clock we're calibrating and the reference clock; the slope of
60*c2705ceaSColin Percival 	 * that line multiplied by the frequency of the reference clock gives
61*c2705ceaSColin Percival 	 * us the frequency we're looking for.
62*c2705ceaSColin Percival 	 *
63*c2705ceaSColin Percival 	 * To do this, we calculate the
64*c2705ceaSColin Percival 	 * (a) mean of the target clock measurements,
65*c2705ceaSColin Percival 	 * (b) variance of the target clock measurements,
66*c2705ceaSColin Percival 	 * (c) mean of the reference clock measurements,
67*c2705ceaSColin Percival 	 * (d) variance of the reference clock measurements, and
68*c2705ceaSColin Percival 	 * (e) covariance of the target clock and reference clock measurements
69*c2705ceaSColin Percival 	 * on an ongoing basis, updating all five values after each new data
70*c2705ceaSColin Percival 	 * point arrives, stopping when we're confident that we've accurately
71*c2705ceaSColin Percival 	 * measured the target clock frequency.
72*c2705ceaSColin Percival 	 *
73*c2705ceaSColin Percival 	 * Given those five values, the important formulas to remember from
74*c2705ceaSColin Percival 	 * introductory statistics are:
75*c2705ceaSColin Percival 	 * 1. slope of regression line = covariance(x, y) / variance(x)
76*c2705ceaSColin Percival 	 * 2. (relative uncertainty in slope)^2 =
77*c2705ceaSColin Percival 	 *    (variance(x) * variance(y) - covariance(x, y)^2)
78*c2705ceaSColin Percival 	 *    ------------------------------------------------
79*c2705ceaSColin Percival 	 *              covariance(x, y)^2 * (N - 2)
80*c2705ceaSColin Percival 	 *
81*c2705ceaSColin Percival 	 * We adjust the second formula slightly, adding a term to each of
82*c2705ceaSColin Percival 	 * the variance values to reflect the measurement quantization.
83*c2705ceaSColin Percival 	 *
84*c2705ceaSColin Percival 	 * Finally, we need to determine when to stop gathering data.  We
85*c2705ceaSColin Percival 	 * can't simply stop as soon as the computed uncertainty estimate
86*c2705ceaSColin Percival 	 * is below our threshold; this would make us overconfident since it
87*c2705ceaSColin Percival 	 * would introduce a multiple-comparisons problem (cf. sequential
88*c2705ceaSColin Percival 	 * analysis in clinical trials).  Instead, we stop with N data points
89*c2705ceaSColin Percival 	 * if the estimated uncertainty of the first k data points meets our
90*c2705ceaSColin Percival 	 * target for all N/2 < k <= N; this is not theoretically optimal,
91*c2705ceaSColin Percival 	 * but in practice works well enough.
92*c2705ceaSColin Percival 	 */
93*c2705ceaSColin Percival 
94*c2705ceaSColin Percival 	/*
95*c2705ceaSColin Percival 	 * Initial values for clocks; we'll subtract these off from values
96*c2705ceaSColin Percival 	 * we measure later in order to reduce floating-point rounding errors.
97*c2705ceaSColin Percival 	 * We keep track of an adjustment for values read from the reference
98*c2705ceaSColin Percival 	 * timecounter, since it can wrap.
99*c2705ceaSColin Percival 	 */
100*c2705ceaSColin Percival 	clk0 = clk();
101*c2705ceaSColin Percival 	t0 = tc->tc_get_timecount(tc) & tc->tc_counter_mask;
102*c2705ceaSColin Percival 	tadj = 0;
103*c2705ceaSColin Percival 	tlast = t0;
104*c2705ceaSColin Percival 
105*c2705ceaSColin Percival 	/* Loop until we give up or decide that we're calibrated. */
106*c2705ceaSColin Percival 	for (n = 1; ; n++) {
107*c2705ceaSColin Percival 		/* Get a new data point. */
108*c2705ceaSColin Percival 		clk1 = clk() - clk0;
109*c2705ceaSColin Percival 		t1 = tc->tc_get_timecount(tc) & tc->tc_counter_mask;
110*c2705ceaSColin Percival 		while (t1 + tadj < tlast)
111*c2705ceaSColin Percival 			tadj += tc->tc_counter_mask + 1;
112*c2705ceaSColin Percival 		tlast = t1 + tadj;
113*c2705ceaSColin Percival 		t1 += tadj - t0;
114*c2705ceaSColin Percival 
115*c2705ceaSColin Percival 		/* If we spent too long, bail. */
116*c2705ceaSColin Percival 		if (t1 > tc->tc_frequency) {
117*c2705ceaSColin Percival 			printf("Statistical %s calibration failed!  "
118*c2705ceaSColin Percival 			    "Clocks might be ticking at variable rates.\n",
119*c2705ceaSColin Percival 			     clkname);
120*c2705ceaSColin Percival 			printf("Falling back to slow %s calibration.\n",
121*c2705ceaSColin Percival 			    clkname);
122*c2705ceaSColin Percival 			freq = (double)(tc->tc_frequency) * clk1 / t1;
123*c2705ceaSColin Percival 			break;
124*c2705ceaSColin Percival 		}
125*c2705ceaSColin Percival 
126*c2705ceaSColin Percival 		/* Precompute to save on divisions later. */
127*c2705ceaSColin Percival 		inv_n = 1.0 / n;
128*c2705ceaSColin Percival 
129*c2705ceaSColin Percival 		/* Update mean and variance of recorded TSC values. */
130*c2705ceaSColin Percival 		d1 = clk1 - mu_clk;
131*c2705ceaSColin Percival 		mu_clk += d1 * inv_n;
132*c2705ceaSColin Percival 		d2 = d1 * (clk1 - mu_clk);
133*c2705ceaSColin Percival 		va_clk += (d2 - va_clk) * inv_n;
134*c2705ceaSColin Percival 
135*c2705ceaSColin Percival 		/* Update mean and variance of recorded time values. */
136*c2705ceaSColin Percival 		d1 = t1 - mu_t;
137*c2705ceaSColin Percival 		mu_t += d1 * inv_n;
138*c2705ceaSColin Percival 		d2 = d1 * (t1 - mu_t);
139*c2705ceaSColin Percival 		va_t += (d2 - va_t) * inv_n;
140*c2705ceaSColin Percival 
141*c2705ceaSColin Percival 		/* Update covariance. */
142*c2705ceaSColin Percival 		d2 = d1 * (clk1 - mu_clk);
143*c2705ceaSColin Percival 		cva += (d2 - cva) * inv_n;
144*c2705ceaSColin Percival 
145*c2705ceaSColin Percival 		/*
146*c2705ceaSColin Percival 		 * Count low-uncertainty iterations.  This is a rearrangement
147*c2705ceaSColin Percival 		 * of "relative uncertainty < 1 PPM" avoiding division.
148*c2705ceaSColin Percival 		 */
149*c2705ceaSColin Percival #define TSC_PPM_UNCERTAINTY	1
150*c2705ceaSColin Percival #define TSC_UNCERTAINTY		TSC_PPM_UNCERTAINTY * 0.000001
151*c2705ceaSColin Percival #define TSC_UNCERTAINTY_SQR	TSC_UNCERTAINTY * TSC_UNCERTAINTY
152*c2705ceaSColin Percival 		if (TSC_UNCERTAINTY_SQR * (n - 2) * cva * cva >
153*c2705ceaSColin Percival 		    (va_t + 4) * (va_clk + 4) - cva * cva)
154*c2705ceaSColin Percival 			passes++;
155*c2705ceaSColin Percival 		else
156*c2705ceaSColin Percival 			passes = 0;
157*c2705ceaSColin Percival 
158*c2705ceaSColin Percival 		/* Break if we're consistently certain. */
159*c2705ceaSColin Percival 		if (passes * 2 > n) {
160*c2705ceaSColin Percival 			freq = (double)(tc->tc_frequency) * cva / va_t;
161*c2705ceaSColin Percival 			if (bootverbose)
162*c2705ceaSColin Percival 				printf("Statistical %s calibration took"
163*c2705ceaSColin Percival 				    " %lu us and %lu data points\n",
164*c2705ceaSColin Percival 				    clkname, (unsigned long)(t1 *
165*c2705ceaSColin Percival 					1000000.0 / tc->tc_frequency),
166*c2705ceaSColin Percival 				    (unsigned long)n);
167*c2705ceaSColin Percival 			break;
168*c2705ceaSColin Percival 		}
169*c2705ceaSColin Percival 
170*c2705ceaSColin Percival 		/*
171*c2705ceaSColin Percival 		 * Add variable delay to avoid theoretical risk of aliasing
172*c2705ceaSColin Percival 		 * resulting from this loop synchronizing with the frequency
173*c2705ceaSColin Percival 		 * of the reference clock.  On the nth iteration, we spend
174*c2705ceaSColin Percival 		 * O(1 / n) time here -- long enough to avoid aliasing, but
175*c2705ceaSColin Percival 		 * short enough to be insignificant as n grows.
176*c2705ceaSColin Percival 		 */
177*c2705ceaSColin Percival 		clk_delay = clk() + (clk() - clk0) / (n * n);
178*c2705ceaSColin Percival 		while (clk() < clk_delay)
179*c2705ceaSColin Percival 			cpu_spinwait(); /* Do nothing. */
180*c2705ceaSColin Percival 	}
181*c2705ceaSColin Percival 	TSEXIT();
182*c2705ceaSColin Percival 	return (freq);
183*c2705ceaSColin Percival }
184