1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2020 Joyent, Inc.
14 */
15
16 #include <sys/tsc.h>
17 #include <sys/prom_debug.h>
18 #include <sys/hpet.h>
19 #include <sys/clock.h>
20
21 /*
22 * The amount of time (in microseconds) between tsc samples. This is
23 * somewhat arbitrary, but seems reasonable. A frequency of 1GHz is
24 * 1,000,000,000 ticks / sec (10^9). 100us is 10^(-6) * 10^2 => 10^(-4), so
25 * 100us would represent 10^5 (100,000) ticks.
26 */
27 #define HPET_SAMPLE_INTERVAL_US (100)
28
29 /*
30 * The same as above, but in nanoseconds (for ease in converting to HPET
31 * ticks)
32 */
33 #define HPET_SAMPLE_INTERVAL_NS (USEC2NSEC(HPET_SAMPLE_INTERVAL_US))
34
35 /* The amount of HPET sample ticks to wait */
36 #define HPET_SAMPLE_TICKS (HRTIME_TO_HPET_TICKS(HPET_SAMPLE_INTERVAL_NS))
37
38 #define TSC_NUM_SAMPLES 10
39
40 static boolean_t
tsc_calibrate_hpet(uint64_t * freqp)41 tsc_calibrate_hpet(uint64_t *freqp)
42 {
43 uint64_t hpet_sum = 0;
44 uint64_t tsc_sum = 0;
45 uint_t i;
46
47 PRM_POINT("Attempting to use HPET for TSC calibration...");
48
49 if (hpet_early_init() != DDI_SUCCESS)
50 return (B_FALSE);
51
52 /*
53 * The expansion of HPET_SAMPLE_TICKS (specifically
54 * HRTIME_TO_HPET_TICKS) uses the HPET period to calculate the number
55 * of HPET ticks for the given time period. Therefore, we cannot
56 * set hpet_num_ticks until after the early HPET initialization has
57 * been performed by hpet_early_init() (and the HPET period is known).
58 */
59 const uint64_t hpet_num_ticks = HPET_SAMPLE_TICKS;
60
61 for (i = 0; i < TSC_NUM_SAMPLES; i++) {
62 uint64_t hpet_now, hpet_end;
63 uint64_t tsc_start, tsc_end;
64
65 hpet_now = hpet_read_timer();
66 hpet_end = hpet_now + hpet_num_ticks;
67
68 tsc_start = tsc_read();
69 while (hpet_now < hpet_end)
70 hpet_now = hpet_read_timer();
71
72 tsc_end = tsc_read();
73
74 /*
75 * If our TSC isn't advancing after 100us, we're pretty much
76 * hosed.
77 */
78 VERIFY3P(tsc_end, >, tsc_start);
79
80 tsc_sum += tsc_end - tsc_start;
81
82 /*
83 * We likely did not end exactly HPET_SAMPLE_TICKS after
84 * we started, so save the actual amount.
85 */
86 hpet_sum += hpet_num_ticks + hpet_now - hpet_end;
87 }
88
89 uint64_t hpet_avg = hpet_sum / TSC_NUM_SAMPLES;
90 uint64_t tsc_avg = tsc_sum / TSC_NUM_SAMPLES;
91 uint64_t hpet_ns = hpet_avg * hpet_info.period / HPET_FEMTO_TO_NANO;
92
93 PRM_POINT("HPET calibration complete");
94
95 *freqp = tsc_avg * NANOSEC / hpet_ns;
96 PRM_DEBUG(*freqp);
97
98 return (B_TRUE);
99 }
100
101 /*
102 * Reports from the field suggest that HPET calibration is currently producing
103 * a substantially greater error than PIT calibration on a wide variety of
104 * systems. We are placing it last in the preference order until that can be
105 * resolved. HPET calibration cannot be disabled completely, as some systems
106 * no longer emulate the PIT at all.
107 */
108 static tsc_calibrate_t tsc_calibration_hpet = {
109 .tscc_source = "HPET",
110 .tscc_preference = 1,
111 .tscc_calibrate = tsc_calibrate_hpet,
112 };
113 TSC_CALIBRATION_SOURCE(tsc_calibration_hpet);
114