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