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