xref: /illumos-gate/usr/src/uts/i86pc/os/tscc_pit.c (revision dd72704bd9e794056c558153663c739e2012d721)
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/pit.h>
17 #include <sys/tsc.h>
18 #include <sys/archsystm.h>
19 #include <sys/prom_debug.h>
20 
21 extern uint64_t freq_tsc_pit(uint32_t *);
22 
23 /*
24  * Traditionally, the PIT has been used to calibrate both the TSC and the
25  * APIC. As we transition to supporting alternate TSC calibration sources
26  * and using the TSC to calibrate the APIC, we may still want (for diagnostic
27  * purposes) to know what would have happened if we had used the PIT
28  * instead. As a result, if we are using an alternate calibration source
29  * we will still measure the frequency using the PIT and save the result in
30  * pit_tsc_hz for use by the APIC (to similarly save the timings using the
31  * PIT).
32  *
33  * A wrinkle in this is that some systems no longer have a functioning PIT.
34  * In these instances, we simply have no way to provide the 'what if the PIT
35  * was used' values. When we try to use the PIT, we first perform a small
36  * test to see if it appears to be working (i.e. will it count down). If
37  * it does not, we set pit_is_broken to let the APIC calibration code that
38  * it shouldn't attempt to get PIC timings.
39  *
40  * While the systems without a functioning PIT don't seem to experience
41  * any undesirable behavior when attempting to use the non-functional/not
42  * present PIT (i.e. they don't lock up or otherwise act funny -- the counter
43  * values that are read just never change), we still allow pit_is_broken to be
44  * set in /etc/system to inform the system to avoid attempting to use the PIT
45  * at all.
46  *
47  * In the future, we could remove these transitional bits once we have more
48  * history built up using the alternative calibration sources.
49  */
50 uint64_t pit_tsc_hz;
51 int pit_is_broken;
52 
53 /*
54  * On all of the systems seen so far without functioning PITs, it appears
55  * that they always just return the values written to the PITCTR0_PORT (or
56  * more specifically when they've been programmed to start counting down from
57  * 0xFFFF, they always return 0xFFFF no matter how little/much time has
58  * elapsed).
59  *
60  * Since we have no better way to know if the PIT is broken, we use this
61  * behavior to sanity check the PIT. We program the PIT to count down from
62  * 0xFFFF and wait an amount of time and re-read the result. While we cannot
63  * rely on the TSC frequency being known at this point, we do know that
64  * we are almost certainly never going to see a TSC frequency below 1GHz
65  * on any supported system.
66  *
67  * As such, we (somewhat) arbitrarily pick 400,000 TSC ticks as the amount
68  * of time we wait before re-reading the PIT counter. On a 1GHz machine,
69  * 1 PIT tick would correspond to approximately 838 TSC ticks, therefore
70  * waiting 400,000 TSC ticks should correspond to approx 477 PIT ticks.
71  * On a (currently) theoritical 100GHz machine, 400,000 TSC ticks would still
72  * correspond to approx 4-5 PIT ticks, so this seems a reasonably safe value.
73  */
74 #define	TSC_MIN_TICKS	400000ULL
75 
76 static boolean_t
77 pit_sanity_check(void)
78 {
79 	uint64_t tsc_now, tsc_end;
80 	ulong_t flags;
81 	uint16_t pit_count;
82 
83 	flags = clear_int_flag();
84 
85 	tsc_now = tsc_read();
86 	tsc_end = tsc_now + TSC_MIN_TICKS;
87 
88 	/*
89 	 * Put the PIT in mode 0, "Interrupt On Terminal Count":
90 	 */
91 	outb(PITCTL_PORT, PIT_C0 | PIT_LOADMODE | PIT_ENDSIGMODE);
92 
93 	outb(PITCTR0_PORT, 0xFF);
94 	outb(PITCTR0_PORT, 0xFF);
95 
96 	while (tsc_now < tsc_end)
97 		tsc_now = tsc_read();
98 
99 	/*
100 	 * Latch the counter value and status for counter 0 with the
101 	 * readback command.
102 	 */
103 	outb(PITCTL_PORT, PIT_READBACK | PIT_READBACKC0);
104 
105 	/*
106 	 * In readback mode, reading from the counter port produces a
107 	 * status byte, the low counter byte, and finally the high counter byte.
108 	 *
109 	 * We ignore the status byte -- as noted above, we've delayed for an
110 	 * amount of time that should allow the counter to count off at least
111 	 * 4-5 ticks (and more realistically at least a hundred), so we just
112 	 * want to see if the count has changed at all.
113 	 */
114 	(void) inb(PITCTR0_PORT);
115 	pit_count = inb(PITCTR0_PORT);
116 	pit_count |= inb(PITCTR0_PORT) << 8;
117 
118 	restore_int_flag(flags);
119 
120 	if (pit_count == 0xFFFF) {
121 		pit_is_broken = 1;
122 		return (B_FALSE);
123 	}
124 
125 	return (B_TRUE);
126 }
127 
128 static boolean_t
129 tsc_calibrate_pit(uint64_t *freqp)
130 {
131 	uint64_t processor_clks;
132 	ulong_t flags;
133 	uint32_t pit_counter;
134 
135 	if (pit_is_broken)
136 		return (B_FALSE);
137 
138 	if (!pit_sanity_check())
139 		return (B_FALSE);
140 
141 	/*
142 	 * freq_tsc_pit() is a hand-rolled assembly function that returns
143 	 * the number of TSC ticks and sets pit_counter to the number
144 	 * of corresponding PIT ticks in the same time period.
145 	 */
146 	flags = clear_int_flag();
147 	processor_clks = freq_tsc_pit(&pit_counter);
148 	restore_int_flag(flags);
149 
150 	if (pit_counter == 0 || processor_clks == 0 ||
151 	    processor_clks > (((uint64_t)-1) / PIT_HZ)) {
152 		return (B_FALSE);
153 	}
154 
155 	*freqp = pit_tsc_hz = ((uint64_t)PIT_HZ * processor_clks) / pit_counter;
156 	return (B_TRUE);
157 }
158 
159 /*
160  * Typically a calibration source that allows the hardware or the hypervisor to
161  * simply declare a specific frequency, rather than requiring calibration at
162  * runtime, is going to provide better results than the using PIT.
163  */
164 static tsc_calibrate_t tsc_calibration_pit = {
165 	.tscc_source = "PIT",
166 	.tscc_preference = 10,
167 	.tscc_calibrate = tsc_calibrate_pit,
168 };
169 TSC_CALIBRATION_SOURCE(tsc_calibration_pit);
170