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