/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2020 Joyent, Inc. * Copyright 2026 Bill Sommerfeld <sommerfeld@hamachi.org> */ #include <sys/tsc.h> #include <sys/prom_debug.h> #include <sys/hpet.h> #include <sys/clock.h> #include <sys/sysmacros.h> /* * The amount of time (in microseconds) between tsc samples. This needs to be * long enough to allow the ratio between the TSC and the HPET to be * accurately measured. * * The HPET spec cautions that "[w]ithin any 100-microsecond period, * the timer is permitted to report a time that is up to 2 ticks too * early or too late.". Worst case measurement error would occur when * it reads 2 ticks early at one end and 2 ticks late at the other. * * A 100us measurement baseline produced frequency error well in * excess of 200ppm with large boot-to-boot variance, which together * rendered the ntp.drift file useless; it could take the better part * of a day for ntpd to find the new frequency correction after boot. * A longer baseline of 10ms permits a more accurate measurement of * the ratio between the clocks. * * The HPET is specified to run at 10MHz or faster, so each tick is no more * than 100ns; over 10ms this implies a measurement error of no more than * 400ns/10ms or 40ppm on the HPET side. Observed behavior is is somewhat * better than this. */ #define HPET_SAMPLE_INTERVAL_US (10000) /* * The same as above, but in nanoseconds (for ease in converting to HPET * ticks) */ #define HPET_SAMPLE_INTERVAL_NS (USEC2NSEC(HPET_SAMPLE_INTERVAL_US)) /* The amount of HPET sample ticks to wait */ #define HPET_SAMPLE_TICKS (HRTIME_TO_HPET_TICKS(HPET_SAMPLE_INTERVAL_NS)) static boolean_t tsc_calibrate_hpet(uint64_t *freqp) { uint_t i; PRM_POINT("Attempting to use HPET for TSC calibration..."); if (hpet_early_init() != DDI_SUCCESS) return (B_FALSE); /* * The expansion of HPET_SAMPLE_TICKS (specifically * HRTIME_TO_HPET_TICKS) uses the HPET period to calculate the number * of HPET ticks for the given time period. Therefore, we cannot set * hpet_num_ticks until after the early HPET initialization has been * performed by hpet_early_init() (and the HPET period is known). * * For safety, cap hpet_num_ticks to no more than 1<<30; we are * unlikely to see this on real hardware (this would about 107 seconds * at the slowest possible HPET frequency of 10MHz or about 1 second * for a hypothetical fast HPET clocked at 1GHz). */ const uint64_t hpet_num_ticks = MIN(HPET_SAMPLE_TICKS, 1 << 30); uint32_t hpet_start, hpet_now; uint64_t tsc_start, tsc_end; /* * Do a short dry run to warm up caches, then run the full 10ms * duration calibration. */ for (i = 0; i < 2; i++) { uint64_t hpet_limit = (i == 0) ? 100 : hpet_num_ticks; hpet_start = hpet_read_timer_32(); tsc_start = tsc_read(); /* * Loop until the HPET timer advances at least hpet_limit * ticks. * * We use only the low order 32 bits of the HPET counter to * avoid inconsistent read issues during calibration (see * section 2.4.7 of revision 1.0a of the HPET specification). * * This is safe because arithmetic on uint32_t has defined * behavior (results modulo 2^32) on under/overflow. As long * as the delta between two back-to-back reads of the HPET in * this loop is small relative to 2^32, the difference will * exceed hpet_limit before we wrap a second time. */ do { hpet_now = hpet_read_timer_32(); } while ((hpet_now - hpet_start) < hpet_limit); tsc_end = tsc_read(); /* * If our TSC isn't advancing after 100us, we're pretty much * hosed. */ VERIFY3P(tsc_end, >, tsc_start); } /* * We use the actual hpet difference rather than the nominal * duration of hpet_num_ticks as back-to-back reads of the hpet * typically differ by more than a few ticks. */ uint64_t tsc_ticks = tsc_end - tsc_start; uint64_t hpet_ticks = hpet_now - hpet_start; uint64_t hpet_ns = hpet_ticks * hpet_info.period / HPET_FEMTO_TO_NANO; PRM_POINT("HPET calibration complete"); *freqp = tsc_ticks * NANOSEC / hpet_ns; PRM_DEBUG(*freqp); cmn_err(CE_CONT, "?TSC calibration: " "%lu tsc ticks, %lu hpet ticks, %lu hpet ns\n", tsc_ticks, hpet_ticks, hpet_ns); return (B_TRUE); } /* * Reports from the field suggest that HPET calibration is currently producing * a substantially greater error than PIT calibration on a wide variety of * systems. We are placing it last in the preference order until that can be * resolved. HPET calibration cannot be disabled completely, as some systems * no longer emulate the PIT at all. */ static tsc_calibrate_t tsc_calibration_hpet = { .tscc_source = "HPET", .tscc_preference = 1, .tscc_calibrate = tsc_calibrate_hpet, }; TSC_CALIBRATION_SOURCE(tsc_calibration_hpet);