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