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 2015, Joyent, Inc.
14 */
15
16 /*
17 * The microfind() routine is used to calibrate the delay provided by
18 * tenmicrosec(). Early in boot gethrtime() is not yet configured and
19 * available for accurate delays, but some drivers still need to be able to
20 * pause execution for rough increments of ten microseconds. To that end,
21 * microfind() will measure the wall time elapsed during a simple delay loop
22 * using the Intel 8254 Programmable Interval Timer (PIT), and attempt to find
23 * a loop count that approximates a ten microsecond delay.
24 *
25 * This mechanism is accurate enough when running unvirtualised on real CPUs,
26 * but is somewhat less efficacious in a virtual machine. In a virtualised
27 * guest the relationship between instruction completion and elapsed wall time
28 * is, at best, variable; on such machines the calibration is merely a rough
29 * guess.
30 */
31
32 #include <sys/types.h>
33 #include <sys/dl.h>
34 #include <sys/param.h>
35 #include <sys/pit.h>
36 #include <sys/inline.h>
37 #include <sys/machlock.h>
38 #include <sys/avintr.h>
39 #include <sys/smp_impldefs.h>
40 #include <sys/archsystm.h>
41 #include <sys/systm.h>
42 #include <sys/machsystm.h>
43
44 /*
45 * Loop count for 10 microsecond wait. MUST be initialized for those who
46 * insist on calling "tenmicrosec" before the clock has been initialized.
47 */
48 unsigned int microdata = 50;
49
50 /*
51 * These values, used later in microfind(), are stored in globals to allow them
52 * to be adjusted more easily via kmdb.
53 */
54 unsigned int microdata_trial_count = 7;
55 unsigned int microdata_allowed_failures = 3;
56
57
58 static void
microfind_pit_reprogram_for_bios(void)59 microfind_pit_reprogram_for_bios(void)
60 {
61 /*
62 * Restore PIT counter 0 for BIOS use in mode 3 -- "Square Wave
63 * Generator".
64 */
65 outb(PITCTL_PORT, PIT_C0 | PIT_LOADMODE | PIT_SQUAREMODE);
66
67 /*
68 * Load an initial counter value of zero.
69 */
70 outb(PITCTR0_PORT, 0);
71 outb(PITCTR0_PORT, 0);
72 }
73
74 /*
75 * Measure the run time of tenmicrosec() using the Intel 8254 Programmable
76 * Interval Timer. The timer operates at 1.193182 Mhz, so each timer tick
77 * represents 0.8381 microseconds of wall time. This function returns the
78 * number of such ticks that passed while tenmicrosec() was running, or
79 * -1 if the delay was too long to measure with the PIT.
80 */
81 static int
microfind_pit_delta(void)82 microfind_pit_delta(void)
83 {
84 unsigned char status;
85 int count;
86
87 /*
88 * Configure PIT counter 0 in mode 0 -- "Interrupt On Terminal Count".
89 * In this mode, the PIT will count down from the loaded value and
90 * set its output bit high once it reaches zero. The PIT will pause
91 * until we write the low byte and then the high byte to the counter
92 * port.
93 */
94 outb(PITCTL_PORT, PIT_LOADMODE);
95
96 /*
97 * Load the maximum counter value, 0xffff, into the counter port.
98 */
99 outb(PITCTR0_PORT, 0xff);
100 outb(PITCTR0_PORT, 0xff);
101
102 /*
103 * Run the delay function.
104 */
105 tenmicrosec();
106
107 /*
108 * Latch the counter value and status for counter 0 with the read
109 * back command.
110 */
111 outb(PITCTL_PORT, PIT_READBACK | PIT_READBACKC0);
112
113 /*
114 * In read back mode, three values are read from the counter port
115 * in order: the status byte, followed by the low byte and high
116 * byte of the counter value.
117 */
118 status = inb(PITCTR0_PORT);
119 count = inb(PITCTR0_PORT);
120 count |= inb(PITCTR0_PORT) << 8;
121
122 /*
123 * Verify that the counter started counting down. The null count
124 * flag in the status byte is set when we load a value, and cleared
125 * when counting operation begins.
126 */
127 if (status & (1 << PITSTAT_NULLCNT)) {
128 /*
129 * The counter did not begin. This means the loop count
130 * used by tenmicrosec is too small for this CPU. We return
131 * a zero count to represent that the delay was too small
132 * to measure.
133 */
134 return (0);
135 }
136
137 /*
138 * Verify that the counter did not wrap around. The output pin is
139 * reset when we load a new counter value, and set once the counter
140 * reaches zero.
141 */
142 if (status & (1 << PITSTAT_OUTPUT)) {
143 /*
144 * The counter reached zero before we were able to read the
145 * value. This means the loop count used by tenmicrosec is too
146 * large for this CPU.
147 */
148 return (-1);
149 }
150
151 /*
152 * The PIT counts from our initial load value of 0xffff down to zero.
153 * Return the number of timer ticks that passed while tenmicrosec was
154 * running.
155 */
156 VERIFY(count <= 0xffff);
157 return (0xffff - count);
158 }
159
160 static int
microfind_pit_delta_avg(int trials,int allowed_failures)161 microfind_pit_delta_avg(int trials, int allowed_failures)
162 {
163 int tc = 0;
164 int failures = 0;
165 long long int total = 0;
166
167 while (tc < trials) {
168 int d;
169
170 if ((d = microfind_pit_delta()) < 0) {
171 /*
172 * If the counter wrapped, we cannot use this
173 * data point in the average. Record the failure
174 * and try again.
175 */
176 if (++failures > allowed_failures) {
177 /*
178 * Too many failures.
179 */
180 return (-1);
181 }
182 continue;
183 }
184
185 total += d;
186 tc++;
187 }
188
189 return (total / tc);
190 }
191
192 void
microfind(void)193 microfind(void)
194 {
195 int ticks = -1;
196 ulong_t s;
197
198 /*
199 * Disable interrupts while we measure the speed of the CPU.
200 */
201 s = clear_int_flag();
202
203 /*
204 * Start at the smallest loop count, i.e. 1, and keep doubling
205 * until a delay of ~10ms can be measured.
206 */
207 microdata = 1;
208 for (;;) {
209 int ticksprev = ticks;
210
211 /*
212 * We use a trial count of 7 to attempt to smooth out jitter
213 * caused by the scheduling of virtual machines. We only allow
214 * three failures, as each failure represents a wrapped counter
215 * and an expired wall time of at least ~55ms.
216 */
217 if ((ticks = microfind_pit_delta_avg(microdata_trial_count,
218 microdata_allowed_failures)) < 0) {
219 /*
220 * The counter wrapped. Halve the counter, restore the
221 * previous ticks count and break out of the loop.
222 */
223 if (microdata <= 1) {
224 /*
225 * If the counter wrapped on the first try,
226 * then we have some serious problems.
227 */
228 panic("microfind: pit counter always wrapped");
229 }
230 microdata = microdata >> 1;
231 ticks = ticksprev;
232 break;
233 }
234
235 if (ticks > 0x3000) {
236 /*
237 * The loop ran for at least ~10ms worth of 0.8381us
238 * PIT ticks.
239 */
240 break;
241 } else if (microdata > (UINT_MAX >> 1)) {
242 /*
243 * Doubling the loop count again would cause an
244 * overflow. Use what we have.
245 */
246 break;
247 } else {
248 /*
249 * Double and try again.
250 */
251 microdata = microdata << 1;
252 }
253 }
254
255 if (ticks < 1) {
256 /*
257 * If we were unable to measure a positive PIT tick count, then
258 * we will be unable to scale the value of "microdata"
259 * correctly.
260 */
261 panic("microfind: could not calibrate delay loop");
262 }
263
264 /*
265 * Calculate the loop count based on the final PIT tick count and the
266 * loop count. Each PIT tick represents a duration of ~0.8381us, so we
267 * want to adjust microdata to represent a duration of 12 ticks, or
268 * ~10us.
269 */
270 microdata = (long long)microdata * 12LL / (long long)ticks;
271
272 /*
273 * Try and leave things as we found them.
274 */
275 microfind_pit_reprogram_for_bios();
276
277 /*
278 * Restore previous interrupt state.
279 */
280 restore_int_flag(s);
281 }
282