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