xref: /titanic_50/usr/src/uts/i86pc/io/microfind.c (revision 29e54759b5b8e21fb481d44ee504a764aa7b6ea2)
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