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