7973bd5a36ae450ff052adcd462e041574454201
[reactos.git] / reactos / hal / halx86 / generic / timer.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2000 David Welch <welch@cwcom.net>
4 * Copyright (C) 1999 Gareth Owen <gaz@athene.co.uk>, Ramon von Handel
5 * Copyright (C) 1991, 1992 Linus Torvalds
6 *
7 * This software is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this software; see the file COPYING. If not, write
19 * to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
20 * MA 02139, USA.
21 *
22 */
23 /* $Id$
24 *
25 * PROJECT: ReactOS kernel
26 * FILE: ntoskrnl/hal/x86/udelay.c
27 * PURPOSE: Busy waiting
28 * PROGRAMMER: David Welch (david.welch@seh.ox.ac.uk)
29 * UPDATE HISTORY:
30 * 06/11/99 Created
31 */
32
33 /* INCLUDES ***************************************************************/
34
35 #define NDEBUG
36 #include <hal.h>
37
38 /* GLOBALS ******************************************************************/
39
40 #define TMR_CTRL 0x43 /* I/O for control */
41 #define TMR_CNT0 0x40 /* I/O for counter 0 */
42 #define TMR_CNT1 0x41 /* I/O for counter 1 */
43 #define TMR_CNT2 0x42 /* I/O for counter 2 */
44
45 #define TMR_SC0 0 /* Select channel 0 */
46 #define TMR_SC1 0x40 /* Select channel 1 */
47 #define TMR_SC2 0x80 /* Select channel 2 */
48
49 #define TMR_LOW 0x10 /* RW low byte only */
50 #define TMR_HIGH 0x20 /* RW high byte only */
51 #define TMR_BOTH 0x30 /* RW both bytes */
52
53 #define TMR_MD0 0 /* Mode 0 */
54 #define TMR_MD1 0x2 /* Mode 1 */
55 #define TMR_MD2 0x4 /* Mode 2 */
56 #define TMR_MD3 0x6 /* Mode 3 */
57 #define TMR_MD4 0x8 /* Mode 4 */
58 #define TMR_MD5 0xA /* Mode 5 */
59
60 #define TMR_BCD 1 /* BCD mode */
61
62 #define TMR_LATCH 0 /* Latch command */
63
64 #define TMR_READ 0xF0 /* Read command */
65 #define TMR_CNT 0x20 /* CNT bit (Active low, subtract it) */
66 #define TMR_STAT 0x10 /* Status bit (Active low, subtract it) */
67 #define TMR_CH2 0x8 /* Channel 2 bit */
68 #define TMR_CH1 0x4 /* Channel 1 bit */
69 #define TMR_CH0 0x2 /* Channel 0 bit */
70
71 #define MILLISEC 10 /* Number of millisec between interrupts */
72 #define HZ (1000 / MILLISEC) /* Number of interrupts per second */
73 #define CLOCK_TICK_RATE 1193182 /* Clock frequency of the timer chip */
74 #define LATCH (CLOCK_TICK_RATE / HZ) /* Count to program into the timer chip */
75 #define PRECISION 8 /* Number of bits to calibrate for delay loop */
76
77 static BOOLEAN UdelayCalibrated = FALSE;
78
79 /* FUNCTIONS **************************************************************/
80
81 /*
82 * NOTE: This function MUST NOT be optimized by the compiler!
83 * If it is, it obviously will not delay AT ALL, and the system
84 * will appear completely frozen at boot since
85 * HalpCalibrateStallExecution will never return.
86 * There are three options to stop optimization:
87 * 1. Use a volatile automatic variable. Making it delay quite a bit
88 * due to memory accesses, and keeping the code portable. However,
89 * as this involves memory access it depends on both the CPU cache,
90 * e.g. if the stack used is already in a cache line or not, and
91 * whether or not we're MP. If MP, another CPU could (probably would)
92 * also access RAM at the same time - making the delay imprecise.
93 * 2. Use compiler-specific #pragma's to disable optimization.
94 * 3. Use inline assembly, making it equally unportable as #2.
95 * For supported compilers we use inline assembler. For the others,
96 * portable plain C.
97 */
98 VOID STDCALL __attribute__((noinline))
99 __KeStallExecutionProcessor(ULONG Loops)
100 {
101 if (!Loops)
102 {
103 return;
104 }
105 #if defined(__GNUC__)
106 __asm__ __volatile__ (
107 "mov %0, %%eax\n"
108 "ROSL1: dec %%eax\n"
109 "jnz ROSL1" : : "d" (Loops));
110
111 #elif defined(_MSC_VER)
112 __asm mov eax, Loops
113 ROSL1:
114 __asm dec eax
115 __asm jnz ROSL1
116 #else
117 volatile unsigned int target = Loops;
118 unsigned int i;
119 for (i=0; i<target;i++);
120 #endif
121 }
122
123 VOID STDCALL KeStallExecutionProcessor(ULONG Microseconds)
124 {
125 PKIPCR Pcr = (PKIPCR)KeGetCurrentKPCR();
126
127 if (Pcr->PrcbData.FeatureBits & X86_FEATURE_TSC)
128 {
129 LARGE_INTEGER EndCount, CurrentCount;
130 Ki386RdTSC(EndCount);
131 EndCount.QuadPart += Microseconds * (ULONGLONG)Pcr->PrcbData.MHz;
132 do
133 {
134 Ki386RdTSC(CurrentCount);
135 }
136 while (CurrentCount.QuadPart < EndCount.QuadPart);
137 }
138 else
139 {
140 __KeStallExecutionProcessor((Pcr->StallScaleFactor*Microseconds)/1000);
141 }
142 }
143
144 static ULONG Read8254Timer(VOID)
145 {
146 ULONG Count;
147 ULONG flags;
148
149 /* save flags and disable interrupts */
150 Ki386SaveFlags(flags);
151 Ki386DisableInterrupts();
152
153 WRITE_PORT_UCHAR((PUCHAR) TMR_CTRL, TMR_SC0 | TMR_LATCH);
154 Count = READ_PORT_UCHAR((PUCHAR) TMR_CNT0);
155 Count |= READ_PORT_UCHAR((PUCHAR) TMR_CNT0) << 8;
156
157 /* restore flags */
158 Ki386RestoreFlags(flags);
159
160 return Count;
161 }
162
163
164 VOID WaitFor8254Wraparound(VOID)
165 {
166 ULONG CurCount, PrevCount = ~0;
167 LONG Delta;
168
169 CurCount = Read8254Timer();
170
171 do
172 {
173 PrevCount = CurCount;
174 CurCount = Read8254Timer();
175 Delta = CurCount - PrevCount;
176
177 /*
178 * This limit for delta seems arbitrary, but it isn't, it's
179 * slightly above the level of error a buggy Mercury/Neptune
180 * chipset timer can cause.
181 */
182
183 }
184 while (Delta < 300);
185 }
186
187 VOID HalpCalibrateStallExecution(VOID)
188 {
189 ULONG i;
190 ULONG calib_bit;
191 ULONG CurCount;
192 PKIPCR Pcr;
193 LARGE_INTEGER StartCount, EndCount;
194
195 if (UdelayCalibrated)
196 {
197 return;
198 }
199
200 UdelayCalibrated = TRUE;
201 Pcr = (PKIPCR)KeGetCurrentKPCR();
202
203 /* Initialise timer interrupt with MILLISEC ms interval */
204 WRITE_PORT_UCHAR((PUCHAR) TMR_CTRL, TMR_SC0 | TMR_BOTH | TMR_MD2); /* binary, mode 2, LSB/MSB, ch 0 */
205 WRITE_PORT_UCHAR((PUCHAR) TMR_CNT0, LATCH & 0xff); /* LSB */
206 WRITE_PORT_UCHAR((PUCHAR) TMR_CNT0, LATCH >> 8); /* MSB */
207
208 if (Pcr->PrcbData.FeatureBits & X86_FEATURE_TSC)
209 {
210
211 WaitFor8254Wraparound();
212 Ki386RdTSC(StartCount);
213
214 WaitFor8254Wraparound();
215 Ki386RdTSC(EndCount);
216
217 Pcr->PrcbData.MHz = (ULONG)(EndCount.QuadPart - StartCount.QuadPart) / 10000;
218 DPRINT("%dMHz\n", Pcr->PrcbData.MHz);
219 return;
220
221 }
222
223 DbgPrint("Calibrating delay loop... [");
224
225 /* Stage 1: Coarse calibration */
226
227 WaitFor8254Wraparound();
228
229 Pcr->StallScaleFactor = 1;
230
231 do
232 {
233 Pcr->StallScaleFactor <<= 1; /* Next delay count to try */
234
235 WaitFor8254Wraparound();
236
237 __KeStallExecutionProcessor(Pcr->StallScaleFactor); /* Do the delay */
238
239 CurCount = Read8254Timer();
240 }
241 while (CurCount > LATCH / 2);
242
243 Pcr->StallScaleFactor >>= 1; /* Get bottom value for delay */
244
245 /* Stage 2: Fine calibration */
246 DbgPrint("delay_count: %d", Pcr->StallScaleFactor);
247
248 calib_bit = Pcr->StallScaleFactor; /* Which bit are we going to test */
249
250 for (i = 0; i < PRECISION; i++)
251 {
252 calib_bit >>= 1; /* Next bit to calibrate */
253 if (!calib_bit)
254 {
255 break; /* If we have done all bits, stop */
256 }
257
258 Pcr->StallScaleFactor |= calib_bit; /* Set the bit in delay_count */
259
260 WaitFor8254Wraparound();
261
262 __KeStallExecutionProcessor(Pcr->StallScaleFactor); /* Do the delay */
263
264 CurCount = Read8254Timer();
265 if (CurCount <= LATCH / 2) /* If a tick has passed, turn the */
266 { /* calibrated bit back off */
267 Pcr->StallScaleFactor &= ~calib_bit;
268 }
269 }
270
271 /* We're finished: Do the finishing touches */
272
273 Pcr->StallScaleFactor /= (MILLISEC / 2); /* Calculate delay_count for 1ms */
274
275 DbgPrint("]\n");
276 DbgPrint("delay_count: %d\n", Pcr->StallScaleFactor);
277 DbgPrint("CPU speed: %d\n", Pcr->StallScaleFactor / 250);
278 #if 0
279 DbgPrint("About to start delay loop test\n");
280 DbgPrint("Waiting for five minutes...");
281 for (i = 0; i < (5*60*1000*20); i++)
282 {
283 KeStallExecutionProcessor(50);
284 }
285 DbgPrint("finished\n");
286 for(;;);
287 #endif
288 }
289
290
291 VOID STDCALL
292 HalCalibratePerformanceCounter(ULONG Count)
293 {
294 ULONG flags;
295
296 /* save flags and disable interrupts */
297 Ki386SaveFlags(flags);
298 Ki386DisableInterrupts();
299
300 __KeStallExecutionProcessor(Count);
301
302 /* restore flags */
303 Ki386RestoreFlags(flags);
304 }
305
306
307 LARGE_INTEGER STDCALL
308 KeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFreq)
309 /*
310 * FUNCTION: Queries the finest grained running count available in the system
311 * ARGUMENTS:
312 * PerformanceFreq (OUT) = The routine stores the number of
313 * performance counter ticks per second here
314 * RETURNS: The number of performance counter ticks since boot
315 */
316 {
317 PKIPCR Pcr;
318 LARGE_INTEGER Value;
319 ULONG Flags;
320
321 Ki386SaveFlags(Flags);
322 Ki386DisableInterrupts();
323
324 Pcr = (PKIPCR)KeGetCurrentKPCR();
325
326 if (Pcr->PrcbData.FeatureBits & X86_FEATURE_TSC)
327 {
328 Ki386RestoreFlags(Flags);
329 if (NULL != PerformanceFreq)
330 {
331 PerformanceFreq->QuadPart = Pcr->PrcbData.MHz * (ULONGLONG)1000000;
332 }
333 Ki386RdTSC(Value);
334 }
335 else
336 {
337 LARGE_INTEGER TicksOld;
338 LARGE_INTEGER TicksNew;
339 ULONG CountsLeft;
340
341 Ki386RestoreFlags(Flags);
342
343 if (NULL != PerformanceFreq)
344 {
345 PerformanceFreq->QuadPart = CLOCK_TICK_RATE;
346 }
347
348 do
349 {
350 KeQueryTickCount(&TicksOld);
351 CountsLeft = Read8254Timer();
352 Value.QuadPart = TicksOld.QuadPart * LATCH + (LATCH - CountsLeft);
353 KeQueryTickCount(&TicksNew);
354 }
355 while (TicksOld.QuadPart != TicksNew.QuadPart);
356 }
357 return Value;
358 }
359
360 /* EOF */