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