xref: /reactos/hal/halx86/generic/timer.c (revision 4567e13e)
1 /*
2  * PROJECT:         ReactOS HAL
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            hal/halx86/generic/timer.c
5  * PURPOSE:         HAL Timer Routines
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  *                  Timo Kreuzer (timo.kreuzer@reactos.org)
8  */
9 
10 /* INCLUDES ******************************************************************/
11 
12 #include <hal.h>
13 #define NDEBUG
14 #include <debug.h>
15 
16 #if defined(ALLOC_PRAGMA) && !defined(_MINIHAL_)
17 #pragma alloc_text(INIT, HalpInitializeClock)
18 #endif
19 
20 /* GLOBALS *******************************************************************/
21 
22 #define PIT_LATCH  0x00
23 
24 LARGE_INTEGER HalpLastPerfCounter;
25 LARGE_INTEGER HalpPerfCounter;
26 ULONG HalpPerfCounterCutoff;
27 BOOLEAN HalpClockSetMSRate;
28 ULONG HalpCurrentTimeIncrement;
29 ULONG HalpCurrentRollOver;
30 ULONG HalpNextMSRate = 14;
31 ULONG HalpLargestClockMS = 15;
32 
33 static struct _HALP_ROLLOVER
34 {
35     ULONG RollOver;
36     ULONG Increment;
37 } HalpRolloverTable[15] =
38 {
39     {1197, 10032},
40     {2394, 20064},
41     {3591, 30096},
42     {4767, 39952},
43     {5964, 49984},
44     {7161, 60016},
45     {8358, 70048},
46     {9555, 80080},
47     {10731, 89936},
48     {11949, 100144},
49     {13125, 110000},
50     {14322, 120032},
51     {15519, 130064},
52     {16695, 139920},
53     {17892, 149952}
54 };
55 
56 /* PRIVATE FUNCTIONS *********************************************************/
57 
58 FORCEINLINE
59 ULONG
60 HalpRead8254Value(void)
61 {
62     ULONG TimerValue;
63 
64     /* Send counter latch command for channel 0 */
65     __outbyte(TIMER_CONTROL_PORT, PIT_LATCH);
66     __nop();
67 
68     /* Read the value, LSB first */
69     TimerValue = __inbyte(TIMER_CHANNEL0_DATA_PORT);
70     __nop();
71     TimerValue |= __inbyte(TIMER_CHANNEL0_DATA_PORT) << 8;
72 
73     return TimerValue;
74 }
75 
76 VOID
77 NTAPI
78 HalpSetTimerRollOver(USHORT RollOver)
79 {
80     ULONG_PTR Flags;
81     TIMER_CONTROL_PORT_REGISTER TimerControl;
82 
83     /* Disable interrupts */
84     Flags = __readeflags();
85     _disable();
86 
87     /* Program the PIT for binary mode */
88     TimerControl.BcdMode = FALSE;
89 
90     /*
91      * Program the PIT to generate a normal rate wave (Mode 3) on channel 0.
92      * Channel 0 is used for the IRQ0 clock interval timer, and channel
93      * 1 is used for DRAM refresh.
94      *
95      * Mode 2 gives much better accuracy than Mode 3.
96      */
97     TimerControl.OperatingMode = PitOperatingMode2;
98     TimerControl.Channel = PitChannel0;
99 
100     /* Set the access mode that we'll use to program the reload value */
101     TimerControl.AccessMode = PitAccessModeLowHigh;
102 
103     /* Now write the programming bits */
104     __outbyte(TIMER_CONTROL_PORT, TimerControl.Bits);
105 
106     /* Next we write the reload value for channel 0 */
107     __outbyte(TIMER_CHANNEL0_DATA_PORT, RollOver & 0xFF);
108     __outbyte(TIMER_CHANNEL0_DATA_PORT, RollOver >> 8);
109 
110     /* Restore interrupts if they were previously enabled */
111     __writeeflags(Flags);
112 }
113 
114 INIT_FUNCTION
115 VOID
116 NTAPI
117 HalpInitializeClock(VOID)
118 {
119     ULONG Increment;
120     USHORT RollOver;
121 
122     DPRINT("HalpInitializeClock()\n");
123 
124     /* Get increment and rollover for the largest time clock ms possible */
125     Increment = HalpRolloverTable[HalpLargestClockMS - 1].Increment;
126     RollOver = (USHORT)HalpRolloverTable[HalpLargestClockMS - 1].RollOver;
127 
128     /* Set the maximum and minimum increment with the kernel */
129     KeSetTimeIncrement(Increment, HalpRolloverTable[0].Increment);
130 
131     /* Set the rollover value for the timer */
132     HalpSetTimerRollOver(RollOver);
133 
134     /* Save rollover and increment */
135     HalpCurrentRollOver = RollOver;
136     HalpCurrentTimeIncrement = Increment;
137 }
138 
139 #ifdef _M_IX86
140 #ifndef _MINIHAL_
141 VOID
142 FASTCALL
143 HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame)
144 {
145     ULONG LastIncrement;
146     KIRQL Irql;
147 
148     /* Enter trap */
149     KiEnterInterruptTrap(TrapFrame);
150 
151     /* Start the interrupt */
152     if (HalBeginSystemInterrupt(CLOCK2_LEVEL, PRIMARY_VECTOR_BASE, &Irql))
153     {
154         /* Update the performance counter */
155         HalpPerfCounter.QuadPart += HalpCurrentRollOver;
156         HalpPerfCounterCutoff = KiEnableTimerWatchdog;
157 
158         /* Save increment */
159         LastIncrement = HalpCurrentTimeIncrement;
160 
161         /* Check if someone changed the time rate */
162         if (HalpClockSetMSRate)
163         {
164             /* Update the global values */
165             HalpCurrentTimeIncrement = HalpRolloverTable[HalpNextMSRate - 1].Increment;
166             HalpCurrentRollOver = HalpRolloverTable[HalpNextMSRate - 1].RollOver;
167 
168             /* Set new timer rollover */
169             HalpSetTimerRollOver((USHORT)HalpCurrentRollOver);
170 
171             /* We're done */
172             HalpClockSetMSRate = FALSE;
173         }
174 
175         /* Update the system time -- the kernel will exit this trap  */
176         KeUpdateSystemTime(TrapFrame, LastIncrement, Irql);
177     }
178 
179     /* Spurious, just end the interrupt */
180     KiEoiHelper(TrapFrame);
181 }
182 
183 VOID
184 FASTCALL
185 HalpProfileInterruptHandler(IN PKTRAP_FRAME TrapFrame)
186 {
187     KIRQL Irql;
188 
189     /* Enter trap */
190     KiEnterInterruptTrap(TrapFrame);
191 
192     /* Start the interrupt */
193     if (HalBeginSystemInterrupt(PROFILE_LEVEL, PRIMARY_VECTOR_BASE + 8, &Irql))
194     {
195         /* Spin until the interrupt pending bit is clear */
196         HalpAcquireCmosSpinLock();
197         while (HalpReadCmos(RTC_REGISTER_C) & RTC_REG_C_IRQ)
198             ;
199         HalpReleaseCmosSpinLock();
200 
201         /* If profiling is enabled, call the kernel function */
202         if (!HalpProfilingStopped)
203         {
204             KeProfileInterrupt(TrapFrame);
205         }
206 
207         /* Finish the interrupt */
208         _disable();
209         HalEndSystemInterrupt(Irql, TrapFrame);
210     }
211 
212     /* Spurious, just end the interrupt */
213     KiEoiHelper(TrapFrame);
214 }
215 #endif
216 
217 #endif
218 
219 /* PUBLIC FUNCTIONS ***********************************************************/
220 
221 /*
222  * @implemented
223  */
224 VOID
225 NTAPI
226 HalCalibratePerformanceCounter(IN volatile PLONG Count,
227                                IN ULONGLONG NewCount)
228 {
229     ULONG_PTR Flags;
230 
231     /* Disable interrupts */
232     Flags = __readeflags();
233     _disable();
234 
235     /* Do a decrement for this CPU */
236     _InterlockedDecrement(Count);
237 
238     /* Wait for other CPUs */
239     while (*Count);
240 
241     /* Restore interrupts if they were previously enabled */
242     __writeeflags(Flags);
243 }
244 
245 /*
246  * @implemented
247  */
248 ULONG
249 NTAPI
250 HalSetTimeIncrement(IN ULONG Increment)
251 {
252     /* Round increment to ms */
253     Increment /= 10000;
254 
255     /* Normalize between our minimum (1 ms) and maximum (variable) setting */
256     if (Increment > HalpLargestClockMS) Increment = HalpLargestClockMS;
257     if (Increment <= 0) Increment = 1;
258 
259     /* Set the rate and tell HAL we want to change it */
260     HalpNextMSRate = Increment;
261     HalpClockSetMSRate = TRUE;
262 
263     /* Return the increment */
264     return HalpRolloverTable[Increment - 1].Increment;
265 }
266 
267 LARGE_INTEGER
268 NTAPI
269 KeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFrequency)
270 {
271     LARGE_INTEGER CurrentPerfCounter;
272     ULONG CounterValue, ClockDelta;
273     KIRQL OldIrql;
274 
275     /* If caller wants performance frequency, return hardcoded value */
276     if (PerformanceFrequency) PerformanceFrequency->QuadPart = PIT_FREQUENCY;
277 
278     /* Check if we were called too early */
279     if (HalpCurrentRollOver == 0) return HalpPerfCounter;
280 
281     /* Check if interrupts are disabled */
282     if(!(__readeflags() & EFLAGS_INTERRUPT_MASK)) return HalpPerfCounter;
283 
284     /* Raise irql to DISPATCH_LEVEL */
285     OldIrql = KeGetCurrentIrql();
286     if (OldIrql < DISPATCH_LEVEL) KfRaiseIrql(DISPATCH_LEVEL);
287 
288     do
289     {
290         /* Get the current performance counter value */
291         CurrentPerfCounter = HalpPerfCounter;
292 
293         /* Read the 8254 counter value */
294         CounterValue = HalpRead8254Value();
295 
296     /* Repeat if the value has changed (a clock interrupt happened) */
297     } while (CurrentPerfCounter.QuadPart != HalpPerfCounter.QuadPart);
298 
299     /* After someone changed the clock rate, during the first clock cycle we
300        might see a counter value larger than the rollover. In this case we
301        pretend it already has the new rollover value. */
302     if (CounterValue > HalpCurrentRollOver) CounterValue = HalpCurrentRollOver;
303 
304     /* The interrupt is issued on the falling edge of the OUT line, when the
305        counter changes from 1 to max. Calculate a clock delta, so that directly
306        after the interrupt it is 0, going up to (HalpCurrentRollOver - 1). */
307     ClockDelta = HalpCurrentRollOver - CounterValue;
308 
309     /* Add the clock delta */
310     CurrentPerfCounter.QuadPart += ClockDelta;
311 
312     /* Check if the value is smaller then before, this means, we somehow
313        missed an interrupt. This is a sign that the timer interrupt
314        is very inaccurate. Probably a virtual machine. */
315     if (CurrentPerfCounter.QuadPart < HalpLastPerfCounter.QuadPart)
316     {
317         /* We missed an interrupt. Assume we will receive it later */
318         CurrentPerfCounter.QuadPart += HalpCurrentRollOver;
319     }
320 
321     /* Update the last counter value */
322     HalpLastPerfCounter = CurrentPerfCounter;
323 
324     /* Restore previous irql */
325     if (OldIrql < DISPATCH_LEVEL) KfLowerIrql(OldIrql);
326 
327     /* Return the result */
328     return CurrentPerfCounter;
329 }
330 
331 /* EOF */
332