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