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