xref: /illumos-gate/usr/src/uts/i86pc/os/tscc_hpet.c (revision 236cb9a89d936b4b681853751c9af1adccc35ef9)
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