1575694f6SJason King /*
2575694f6SJason King * This file and its contents are supplied under the terms of the
3575694f6SJason King * Common Development and Distribution License ("CDDL"), version 1.0.
4575694f6SJason King * You may only use this file in accordance with the terms of version
5575694f6SJason King * 1.0 of the CDDL.
6575694f6SJason King *
7575694f6SJason King * A full copy of the text of the CDDL should have accompanied this
8575694f6SJason King * source. A copy of the CDDL is also available via the Internet at
9575694f6SJason King * http://www.illumos.org/license/CDDL.
10575694f6SJason King */
11575694f6SJason King
12575694f6SJason King /*
13575694f6SJason King * Copyright 2020 Joyent, Inc.
14575694f6SJason King */
15575694f6SJason King
16575694f6SJason King #include <sys/pit.h>
17575694f6SJason King #include <sys/tsc.h>
18575694f6SJason King #include <sys/archsystm.h>
19575694f6SJason King #include <sys/prom_debug.h>
20575694f6SJason King
21575694f6SJason King extern uint64_t freq_tsc_pit(uint32_t *);
22575694f6SJason King
23575694f6SJason King /*
24575694f6SJason King * Traditionally, the PIT has been used to calibrate both the TSC and the
25575694f6SJason King * APIC. As we transition to supporting alternate TSC calibration sources
26575694f6SJason King * and using the TSC to calibrate the APIC, we may still want (for diagnostic
27575694f6SJason King * purposes) to know what would have happened if we had used the PIT
28575694f6SJason King * instead. As a result, if we are using an alternate calibration source
29575694f6SJason King * we will still measure the frequency using the PIT and save the result in
30575694f6SJason King * pit_tsc_hz for use by the APIC (to similarly save the timings using the
31575694f6SJason King * PIT).
32575694f6SJason King *
33575694f6SJason King * A wrinkle in this is that some systems no longer have a functioning PIT.
34575694f6SJason King * In these instances, we simply have no way to provide the 'what if the PIT
35575694f6SJason King * was used' values. When we try to use the PIT, we first perform a small
36575694f6SJason King * test to see if it appears to be working (i.e. will it count down). If
37575694f6SJason King * it does not, we set pit_is_broken to let the APIC calibration code that
38575694f6SJason King * it shouldn't attempt to get PIC timings.
39575694f6SJason King *
40575694f6SJason King * While the systems without a functioning PIT don't seem to experience
41575694f6SJason King * any undesirable behavior when attempting to use the non-functional/not
42575694f6SJason King * present PIT (i.e. they don't lock up or otherwise act funny -- the counter
43575694f6SJason King * values that are read just never change), we still allow pit_is_broken to be
44575694f6SJason King * set in /etc/system to inform the system to avoid attempting to use the PIT
45575694f6SJason King * at all.
46575694f6SJason King *
47575694f6SJason King * In the future, we could remove these transitional bits once we have more
48575694f6SJason King * history built up using the alternative calibration sources.
49575694f6SJason King */
50575694f6SJason King uint64_t pit_tsc_hz;
51575694f6SJason King int pit_is_broken;
52575694f6SJason King
53575694f6SJason King /*
54575694f6SJason King * On all of the systems seen so far without functioning PITs, it appears
55575694f6SJason King * that they always just return the values written to the PITCTR0_PORT (or
56575694f6SJason King * more specifically when they've been programmed to start counting down from
57575694f6SJason King * 0xFFFF, they always return 0xFFFF no matter how little/much time has
58575694f6SJason King * elapsed).
59575694f6SJason King *
60575694f6SJason King * Since we have no better way to know if the PIT is broken, we use this
61575694f6SJason King * behavior to sanity check the PIT. We program the PIT to count down from
62575694f6SJason King * 0xFFFF and wait an amount of time and re-read the result. While we cannot
63575694f6SJason King * rely on the TSC frequency being known at this point, we do know that
64575694f6SJason King * we are almost certainly never going to see a TSC frequency below 1GHz
65575694f6SJason King * on any supported system.
66575694f6SJason King *
67575694f6SJason King * As such, we (somewhat) arbitrarily pick 400,000 TSC ticks as the amount
68575694f6SJason King * of time we wait before re-reading the PIT counter. On a 1GHz machine,
69575694f6SJason King * 1 PIT tick would correspond to approximately 838 TSC ticks, therefore
70575694f6SJason King * waiting 400,000 TSC ticks should correspond to approx 477 PIT ticks.
71575694f6SJason King * On a (currently) theoritical 100GHz machine, 400,000 TSC ticks would still
72575694f6SJason King * correspond to approx 4-5 PIT ticks, so this seems a reasonably safe value.
73575694f6SJason King */
74575694f6SJason King #define TSC_MIN_TICKS 400000ULL
75575694f6SJason King
76575694f6SJason King static boolean_t
pit_sanity_check(void)77575694f6SJason King pit_sanity_check(void)
78575694f6SJason King {
79575694f6SJason King uint64_t tsc_now, tsc_end;
80575694f6SJason King ulong_t flags;
81575694f6SJason King uint16_t pit_count;
82575694f6SJason King
83575694f6SJason King flags = clear_int_flag();
84575694f6SJason King
85575694f6SJason King tsc_now = tsc_read();
86575694f6SJason King tsc_end = tsc_now + TSC_MIN_TICKS;
87575694f6SJason King
88575694f6SJason King /*
89575694f6SJason King * Put the PIT in mode 0, "Interrupt On Terminal Count":
90575694f6SJason King */
91575694f6SJason King outb(PITCTL_PORT, PIT_C0 | PIT_LOADMODE | PIT_ENDSIGMODE);
92575694f6SJason King
93575694f6SJason King outb(PITCTR0_PORT, 0xFF);
94575694f6SJason King outb(PITCTR0_PORT, 0xFF);
95575694f6SJason King
96575694f6SJason King while (tsc_now < tsc_end)
97575694f6SJason King tsc_now = tsc_read();
98575694f6SJason King
99575694f6SJason King /*
100575694f6SJason King * Latch the counter value and status for counter 0 with the
101575694f6SJason King * readback command.
102575694f6SJason King */
103575694f6SJason King outb(PITCTL_PORT, PIT_READBACK | PIT_READBACKC0);
104575694f6SJason King
105575694f6SJason King /*
106575694f6SJason King * In readback mode, reading from the counter port produces a
107575694f6SJason King * status byte, the low counter byte, and finally the high counter byte.
108575694f6SJason King *
109575694f6SJason King * We ignore the status byte -- as noted above, we've delayed for an
110575694f6SJason King * amount of time that should allow the counter to count off at least
111575694f6SJason King * 4-5 ticks (and more realistically at least a hundred), so we just
112575694f6SJason King * want to see if the count has changed at all.
113575694f6SJason King */
114575694f6SJason King (void) inb(PITCTR0_PORT);
115575694f6SJason King pit_count = inb(PITCTR0_PORT);
116575694f6SJason King pit_count |= inb(PITCTR0_PORT) << 8;
117575694f6SJason King
118575694f6SJason King restore_int_flag(flags);
119575694f6SJason King
120575694f6SJason King if (pit_count == 0xFFFF) {
121575694f6SJason King pit_is_broken = 1;
122575694f6SJason King return (B_FALSE);
123575694f6SJason King }
124575694f6SJason King
125575694f6SJason King return (B_TRUE);
126575694f6SJason King }
127575694f6SJason King
128575694f6SJason King static boolean_t
tsc_calibrate_pit(uint64_t * freqp)129575694f6SJason King tsc_calibrate_pit(uint64_t *freqp)
130575694f6SJason King {
131575694f6SJason King uint64_t processor_clks;
132575694f6SJason King ulong_t flags;
133575694f6SJason King uint32_t pit_counter;
134575694f6SJason King
135575694f6SJason King if (pit_is_broken)
136575694f6SJason King return (B_FALSE);
137575694f6SJason King
138575694f6SJason King if (!pit_sanity_check())
139575694f6SJason King return (B_FALSE);
140575694f6SJason King
141575694f6SJason King /*
142575694f6SJason King * freq_tsc_pit() is a hand-rolled assembly function that returns
143575694f6SJason King * the number of TSC ticks and sets pit_counter to the number
144575694f6SJason King * of corresponding PIT ticks in the same time period.
145575694f6SJason King */
146575694f6SJason King flags = clear_int_flag();
147575694f6SJason King processor_clks = freq_tsc_pit(&pit_counter);
148575694f6SJason King restore_int_flag(flags);
149575694f6SJason King
150575694f6SJason King if (pit_counter == 0 || processor_clks == 0 ||
151575694f6SJason King processor_clks > (((uint64_t)-1) / PIT_HZ)) {
152575694f6SJason King return (B_FALSE);
153575694f6SJason King }
154575694f6SJason King
155575694f6SJason King *freqp = pit_tsc_hz = ((uint64_t)PIT_HZ * processor_clks) / pit_counter;
156575694f6SJason King return (B_TRUE);
157575694f6SJason King }
158575694f6SJason King
159575694f6SJason King /*
160*236cb9a8SJoshua M. Clulow * Typically a calibration source that allows the hardware or the hypervisor to
161*236cb9a8SJoshua M. Clulow * simply declare a specific frequency, rather than requiring calibration at
162*236cb9a8SJoshua M. Clulow * runtime, is going to provide better results than the using PIT.
163575694f6SJason King */
164575694f6SJason King static tsc_calibrate_t tsc_calibration_pit = {
165575694f6SJason King .tscc_source = "PIT",
166575694f6SJason King .tscc_preference = 10,
167575694f6SJason King .tscc_calibrate = tsc_calibrate_pit,
168575694f6SJason King };
169575694f6SJason King TSC_CALIBRATION_SOURCE(tsc_calibration_pit);
170