xref: /haiku/src/system/boot/platform/bios_ia32/cpu.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
1 /*
2  * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * calculate_cpu_conversion_factor() was written by Travis Geiselbrecht and
6  * licensed under the NewOS license.
7  */
8 
9 
10 #include "cpu.h"
11 
12 #include <OS.h>
13 #include <boot/platform.h>
14 #include <boot/stdio.h>
15 #include <boot/kernel_args.h>
16 #include <boot/stage2.h>
17 #include <arch/cpu.h>
18 #include <arch_kernel.h>
19 #include <arch_system_info.h>
20 
21 #include <string.h>
22 
23 
24 //#define TRACE_CPU
25 #ifdef TRACE_CPU
26 #	define TRACE(x) dprintf x
27 #else
28 #	define TRACE(x) ;
29 #endif
30 
31 
32 extern "C" uint64 rdtsc();
33 
34 uint32 gTimeConversionFactor;
35 
36 #define TIMER_CLKNUM_HZ (14318180/12)
37 
38 #define CPUID_EFLAGS	(1UL << 21)
39 #define RDTSC_FEATURE	(1UL << 4)
40 
41 
42 static void
43 calculate_cpu_conversion_factor()
44 {
45 	uint32 s_low, s_high;
46 	uint32 low, high;
47 	uint32 expired;
48 	uint64 t1, t2;
49 	uint64 p1, p2, p3;
50 	double r1, r2, r3;
51 
52 	out8(0x34, 0x43);	/* program the timer to count down mode */
53 	out8(0xff, 0x40);	/* low and then high */
54 	out8(0xff, 0x40);
55 
56 	/* quick sample */
57 quick_sample:
58 	do {
59 		out8(0x00, 0x43); /* latch counter value */
60 		s_low = in8(0x40);
61 		s_high = in8(0x40);
62 	} while (s_high != 255);
63 	t1 = rdtsc();
64 	do {
65 		out8(0x00, 0x43); /* latch counter value */
66 		low = in8(0x40);
67 		high = in8(0x40);
68 	} while (high > 224);
69 	t2 = rdtsc();
70 
71 	p1 = t2-t1;
72 	r1 = (double)(p1) / (double)(((s_high << 8) | s_low) - ((high << 8) | low));
73 
74 	/* not so quick sample */
75 not_so_quick_sample:
76 	do {
77 		out8(0x00, 0x43); /* latch counter value */
78 		s_low = in8(0x40);
79 		s_high = in8(0x40);
80 	} while (s_high != 255);
81 	t1 = rdtsc();
82 	do {
83 		out8(0x00, 0x43); /* latch counter value */
84 		low = in8(0x40);
85 		high = in8(0x40);
86 	} while (high > 192);
87 	t2 = rdtsc();
88 	p2 = t2-t1;
89 	r2 = (double)(p2) / (double)(((s_high << 8) | s_low) - ((high << 8) | low));
90 	if ((r1/r2) > 1.01) {
91 		//dprintf("Tuning loop(1)\n");
92 		goto quick_sample;
93 	}
94 	if ((r1/r2) < 0.99) {
95 		//dprintf("Tuning loop(1)\n");
96 		goto quick_sample;
97 	}
98 
99 	/* slow sample */
100 	do {
101 		out8(0x00, 0x43); /* latch counter value */
102 		s_low = in8(0x40);
103 		s_high = in8(0x40);
104 	} while (s_high != 255);
105 	t1 = rdtsc();
106 	do {
107 		out8(0x00, 0x43); /* latch counter value */
108 		low = in8(0x40);
109 		high = in8(0x40);
110 	} while (high > 128);
111 	t2 = rdtsc();
112 
113 	p3 = t2-t1;
114 	r3 = (double)(p3) / (double)(((s_high << 8) | s_low) - ((high << 8) | low));
115 	if ((r2/r3) > 1.01) {
116 		TRACE(("Tuning loop(2)\n"));
117 		goto not_so_quick_sample;
118 	}
119 	if ((r2/r3) < 0.99) {
120 		TRACE(("Tuning loop(2)\n"));
121 		goto not_so_quick_sample;
122 	}
123 
124 	expired = ((s_high << 8) | s_low) - ((high << 8) | low);
125 	p3 *= TIMER_CLKNUM_HZ;
126 
127 	/*
128 	 * cv_factor contains time in usecs per CPU cycle * 2^32
129 	 *
130 	 * The code below is a bit fancy. Originally Michael Noistering
131 	 * had it like:
132 	 *
133 	 *     cv_factor = ((uint64)1000000<<32) * expired / p3;
134 	 *
135 	 * whic is perfect, but unfortunately 1000000ULL<<32*expired
136 	 * may overflow in fast cpus with the long sampling period
137 	 * i put there for being as accurate as possible under
138 	 * vmware.
139 	 *
140 	 * The below calculation is based in that we are trying
141 	 * to calculate:
142 	 *
143 	 *     (C*expired)/p3 -> (C*(x0<<k + x1))/p3 ->
144 	 *     (C*(x0<<k))/p3 + (C*x1)/p3
145 	 *
146 	 * Now the term (C*(x0<<k))/p3 is rewritten as:
147 	 *
148 	 *     (C*(x0<<k))/p3 -> ((C*x0)/p3)<<k + reminder
149 	 *
150 	 * where reminder is:
151 	 *
152 	 *     floor((1<<k)*decimalPart((C*x0)/p3))
153 	 *
154 	 * which is approximated as:
155 	 *
156 	 *     floor((1<<k)*decimalPart(((C*x0)%p3)/p3)) ->
157 	 *     (((C*x0)%p3)<<k)/p3
158 	 *
159 	 * So the final expression is:
160 	 *
161 	 *     ((C*x0)/p3)<<k + (((C*x0)%p3)<<k)/p3 + (C*x1)/p3
162 	 */
163 	 /*
164 	 * To get the highest accuracy with this method
165 	 * x0 should have the 12 most significant bits of expired
166 	 * to minimize the error upon <<k.
167 	 */
168 	 /*
169 	 * Of course, you are not expected to understand any of this.
170 	 */
171 	{
172 		unsigned i;
173 		unsigned k;
174 		uint64 C;
175 		uint64 x0;
176 		uint64 x1;
177 		uint64 a, b, c;
178 
179 		/* first calculate k*/
180 		k = 0;
181 		for (i = 12; i < 16; i++) {
182 			if (expired & (1<<i))
183 				k = i - 11;
184 		}
185 
186 		C = 1000000ULL << 32;
187 		x0 = expired >> k;
188 		x1 = expired & ((1 << k) - 1);
189 
190 		a = ((C * x0) / p3) << k;
191 		b = (((C * x0) % p3) << k) / p3;
192 		c = (C * x1) / p3;
193 #if 0
194 		dprintf("a=%Ld\n", a);
195 		dprintf("b=%Ld\n", b);
196 		dprintf("c=%Ld\n", c);
197 		dprintf("%d %Ld\n", expired, p3);
198 #endif
199 		gTimeConversionFactor = a + b + c;
200 #if 0
201 		dprintf("cvf=%Ld\n", cv_factor);
202 #endif
203 	}
204 
205 #ifdef TRACE_CPU
206 	if (p3 / expired / 1000000000LL)
207 		dprintf("CPU at %Ld.%03Ld GHz\n", p3/expired/1000000000LL, ((p3/expired)%1000000000LL)/1000000LL);
208 	else
209 		dprintf("CPU at %Ld.%03Ld MHz\n", p3/expired/1000000LL, ((p3/expired)%1000000LL)/1000LL);
210 #endif
211 
212 	gKernelArgs.arch_args.system_time_cv_factor = gTimeConversionFactor;
213 	gKernelArgs.arch_args.cpu_clock_speed = p3/expired;
214 }
215 
216 
217 static status_t
218 check_cpu_features()
219 {
220 	// check the eflags register to see if the cpuid instruction exists
221 	if ((get_eflags() & CPUID_EFLAGS) == 0) {
222 		// it's not set yet, but maybe we can set it manually
223 		set_eflags(get_eflags() | CPUID_EFLAGS);
224 		if ((get_eflags() & CPUID_EFLAGS) == 0)
225 			return B_ERROR;
226 	}
227 
228 	cpuid_info info;
229 	if (get_current_cpuid(&info, 1) != B_OK)
230 		return B_ERROR;
231 
232 	if ((info.eax_1.features & RDTSC_FEATURE) == 0) {
233 		// we currently require RDTSC
234 		return B_ERROR;
235 	}
236 
237 	return B_OK;
238 }
239 
240 
241 //	#pragma mark -
242 
243 
244 extern "C" void
245 spin(bigtime_t microseconds)
246 {
247 	bigtime_t time = system_time();
248 
249 	while ((system_time() - time) < microseconds)
250 		asm volatile ("pause;");
251 }
252 
253 
254 extern "C" void
255 cpu_init()
256 {
257 	if (check_cpu_features() != B_OK)
258 		panic("You need a Pentium or higher in order to boot!\n");
259 
260 	calculate_cpu_conversion_factor();
261 
262 	gKernelArgs.num_cpus = 1;
263 		// this will eventually be corrected later on
264 }
265 
266