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)
9 /* INCLUDES ******************************************************************/
16 /* GLOBALS *******************************************************************/
18 #define PIT_LATCH 0x00
20 extern HALP_ROLLOVER HalpRolloverTable
[15];
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;
31 /* PRIVATE FUNCTIONS *********************************************************/
35 HalpRead8254Value(void)
39 /* Send counter latch command for channel 0 */
40 __outbyte(TIMER_CONTROL_PORT
, PIT_LATCH
);
43 /* Read the value, LSB first */
44 TimerValue
= __inbyte(TIMER_CHANNEL0_DATA_PORT
);
46 TimerValue
|= __inbyte(TIMER_CHANNEL0_DATA_PORT
) << 8;
53 HalpSetTimerRollOver(USHORT RollOver
)
56 TIMER_CONTROL_PORT_REGISTER TimerControl
;
58 /* Disable interrupts */
59 Flags
= __readeflags();
62 /* Program the PIT for binary mode */
63 TimerControl
.BcdMode
= FALSE
;
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.
70 * Mode 2 gives much better accuracy than Mode 3.
72 TimerControl
.OperatingMode
= PitOperatingMode2
;
73 TimerControl
.Channel
= PitChannel0
;
75 /* Set the access mode that we'll use to program the reload value */
76 TimerControl
.AccessMode
= PitAccessModeLowHigh
;
78 /* Now write the programming bits */
79 __outbyte(TIMER_CONTROL_PORT
, TimerControl
.Bits
);
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);
85 /* Restore interrupts if they were previously enabled */
92 HalpInitializeClock(VOID
)
97 DPRINT("HalpInitializeClock()\n");
99 #if defined(SARCH_PC98)
100 HalpInitializeClockPc98();
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
;
107 /* Set the maximum and minimum increment with the kernel */
108 KeSetTimeIncrement(Increment
, HalpRolloverTable
[0].Increment
);
110 /* Set the rollover value for the timer */
111 HalpSetTimerRollOver(RollOver
);
113 /* Save rollover and increment */
114 HalpCurrentRollOver
= RollOver
;
115 HalpCurrentTimeIncrement
= Increment
;
122 HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame
)
128 KiEnterInterruptTrap(TrapFrame
);
130 /* Start the interrupt */
131 if (HalBeginSystemInterrupt(CLOCK2_LEVEL
, PRIMARY_VECTOR_BASE
+ PIC_TIMER_IRQ
, &Irql
))
133 /* Update the performance counter */
134 HalpPerfCounter
.QuadPart
+= HalpCurrentRollOver
;
135 HalpPerfCounterCutoff
= KiEnableTimerWatchdog
;
138 LastIncrement
= HalpCurrentTimeIncrement
;
140 /* Check if someone changed the time rate */
141 if (HalpClockSetMSRate
)
143 /* Update the global values */
144 HalpCurrentTimeIncrement
= HalpRolloverTable
[HalpNextMSRate
- 1].Increment
;
145 HalpCurrentRollOver
= HalpRolloverTable
[HalpNextMSRate
- 1].RollOver
;
147 /* Set new timer rollover */
148 HalpSetTimerRollOver((USHORT
)HalpCurrentRollOver
);
151 HalpClockSetMSRate
= FALSE
;
154 /* Update the system time -- the kernel will exit this trap */
155 KeUpdateSystemTime(TrapFrame
, LastIncrement
, Irql
);
158 /* Spurious, just end the interrupt */
159 KiEoiHelper(TrapFrame
);
164 HalpProfileInterruptHandler(IN PKTRAP_FRAME TrapFrame
)
169 KiEnterInterruptTrap(TrapFrame
);
171 /* Start the interrupt */
172 if (HalBeginSystemInterrupt(PROFILE_LEVEL
, PRIMARY_VECTOR_BASE
+ PIC_RTC_IRQ
, &Irql
))
174 #if defined(SARCH_PC98)
175 /* Clear the interrupt flag */
176 HalpAcquireCmosSpinLock();
177 (VOID
)__inbyte(RTC_IO_i_INTERRUPT_RESET
);
178 HalpReleaseCmosSpinLock();
180 /* Spin until the interrupt pending bit is clear */
181 HalpAcquireCmosSpinLock();
182 while (HalpReadCmos(RTC_REGISTER_C
) & RTC_REG_C_IRQ
)
184 HalpReleaseCmosSpinLock();
187 /* If profiling is enabled, call the kernel function */
188 if (!HalpProfilingStopped
)
190 KeProfileInterrupt(TrapFrame
);
193 /* Finish the interrupt */
195 HalEndSystemInterrupt(Irql
, TrapFrame
);
198 /* Spurious, just end the interrupt */
199 KiEoiHelper(TrapFrame
);
201 #endif /* !_MINIHAL_ */
205 /* PUBLIC FUNCTIONS ***********************************************************/
212 HalCalibratePerformanceCounter(IN
volatile PLONG Count
,
213 IN ULONGLONG NewCount
)
217 /* Disable interrupts */
218 Flags
= __readeflags();
221 /* Do a decrement for this CPU */
222 _InterlockedDecrement(Count
);
224 /* Wait for other CPUs */
227 /* Restore interrupts if they were previously enabled */
228 __writeeflags(Flags
);
236 HalSetTimeIncrement(IN ULONG Increment
)
238 /* Round increment to ms */
241 /* Normalize between our minimum (1 ms) and maximum (variable) setting */
242 if (Increment
> HalpLargestClockMS
) Increment
= HalpLargestClockMS
;
243 if (Increment
<= 0) Increment
= 1;
245 /* Set the rate and tell HAL we want to change it */
246 HalpNextMSRate
= Increment
;
247 HalpClockSetMSRate
= TRUE
;
249 /* Return the increment */
250 return HalpRolloverTable
[Increment
- 1].Increment
;
255 KeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFrequency
)
257 LARGE_INTEGER CurrentPerfCounter
;
258 ULONG CounterValue
, ClockDelta
;
261 /* If caller wants performance frequency, return hardcoded value */
262 if (PerformanceFrequency
) PerformanceFrequency
->QuadPart
= PIT_FREQUENCY
;
264 /* Check if we were called too early */
265 if (HalpCurrentRollOver
== 0) return HalpPerfCounter
;
267 /* Check if interrupts are disabled */
268 if(!(__readeflags() & EFLAGS_INTERRUPT_MASK
)) return HalpPerfCounter
;
270 /* Raise irql to DISPATCH_LEVEL */
271 OldIrql
= KeGetCurrentIrql();
272 if (OldIrql
< DISPATCH_LEVEL
) KfRaiseIrql(DISPATCH_LEVEL
);
276 /* Get the current performance counter value */
277 CurrentPerfCounter
= HalpPerfCounter
;
279 /* Read the 8254 counter value */
280 CounterValue
= HalpRead8254Value();
282 /* Repeat if the value has changed (a clock interrupt happened) */
283 } while (CurrentPerfCounter
.QuadPart
!= HalpPerfCounter
.QuadPart
);
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
;
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
;
295 /* Add the clock delta */
296 CurrentPerfCounter
.QuadPart
+= ClockDelta
;
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
)
303 /* We missed an interrupt. Assume we will receive it later */
304 CurrentPerfCounter
.QuadPart
+= HalpCurrentRollOver
;
307 /* Update the last counter value */
308 HalpLastPerfCounter
= CurrentPerfCounter
;
310 /* Restore previous irql */
311 if (OldIrql
< DISPATCH_LEVEL
) KfLowerIrql(OldIrql
);
313 /* Return the result */
314 return CurrentPerfCounter
;