/* * PROJECT: ReactOS HAL * LICENSE: GPL - See COPYING in the top level directory * PURPOSE: HAL Timer Routines * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) * Timo Kreuzer (timo.kreuzer@reactos.org) */ /* INCLUDES ******************************************************************/ #include #define NDEBUG #include /* GLOBALS *******************************************************************/ #define PIT_LATCH 0x00 extern HALP_ROLLOVER HalpRolloverTable[15]; LARGE_INTEGER HalpLastPerfCounter; LARGE_INTEGER HalpPerfCounter; ULONG HalpPerfCounterCutoff; BOOLEAN HalpClockSetMSRate; ULONG HalpCurrentTimeIncrement; ULONG HalpCurrentRollOver; ULONG HalpNextMSRate = 14; ULONG HalpLargestClockMS = 15; /* PRIVATE FUNCTIONS *********************************************************/ FORCEINLINE ULONG HalpRead8254Value(void) { ULONG TimerValue; /* Send counter latch command for channel 0 */ __outbyte(TIMER_CONTROL_PORT, PIT_LATCH); __nop(); /* Read the value, LSB first */ TimerValue = __inbyte(TIMER_CHANNEL0_DATA_PORT); __nop(); TimerValue |= __inbyte(TIMER_CHANNEL0_DATA_PORT) << 8; return TimerValue; } VOID NTAPI HalpSetTimerRollOver(USHORT RollOver) { ULONG_PTR Flags; TIMER_CONTROL_PORT_REGISTER TimerControl; /* Disable interrupts */ Flags = __readeflags(); _disable(); /* Program the PIT for binary mode */ TimerControl.BcdMode = FALSE; /* * Program the PIT to generate a normal rate wave (Mode 2) on channel 0. * Channel 0 is used for the IRQ0 clock interval timer, and channel * 1 is used for DRAM refresh. * * Mode 2 gives much better accuracy than Mode 3. */ TimerControl.OperatingMode = PitOperatingMode2; TimerControl.Channel = PitChannel0; /* Set the access mode that we'll use to program the reload value */ TimerControl.AccessMode = PitAccessModeLowHigh; /* Now write the programming bits */ __outbyte(TIMER_CONTROL_PORT, TimerControl.Bits); /* Next we write the reload value for channel 0 */ __outbyte(TIMER_CHANNEL0_DATA_PORT, RollOver & 0xFF); __outbyte(TIMER_CHANNEL0_DATA_PORT, RollOver >> 8); /* Restore interrupts if they were previously enabled */ __writeeflags(Flags); } CODE_SEG("INIT") VOID NTAPI HalpInitializeClock(VOID) { ULONG Increment; USHORT RollOver; DPRINT("HalpInitializeClock()\n"); #if defined(SARCH_PC98) HalpInitializeClockPc98(); #endif /* Get increment and rollover for the largest time clock ms possible */ Increment = HalpRolloverTable[HalpLargestClockMS - 1].Increment; RollOver = (USHORT)HalpRolloverTable[HalpLargestClockMS - 1].RollOver; /* Set the maximum and minimum increment with the kernel */ KeSetTimeIncrement(Increment, HalpRolloverTable[0].Increment); /* Set the rollover value for the timer */ HalpSetTimerRollOver(RollOver); /* Save rollover and increment */ HalpCurrentRollOver = RollOver; HalpCurrentTimeIncrement = Increment; } #ifdef _M_IX86 #ifndef _MINIHAL_ VOID FASTCALL HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame) { ULONG LastIncrement; KIRQL Irql; /* Enter trap */ KiEnterInterruptTrap(TrapFrame); /* Start the interrupt */ if (HalBeginSystemInterrupt(CLOCK2_LEVEL, PRIMARY_VECTOR_BASE + PIC_TIMER_IRQ, &Irql)) { /* Update the performance counter */ HalpPerfCounter.QuadPart += HalpCurrentRollOver; HalpPerfCounterCutoff = KiEnableTimerWatchdog; /* Save increment */ LastIncrement = HalpCurrentTimeIncrement; /* Check if someone changed the time rate */ if (HalpClockSetMSRate) { /* Update the global values */ HalpCurrentTimeIncrement = HalpRolloverTable[HalpNextMSRate - 1].Increment; HalpCurrentRollOver = HalpRolloverTable[HalpNextMSRate - 1].RollOver; /* Set new timer rollover */ HalpSetTimerRollOver((USHORT)HalpCurrentRollOver); /* We're done */ HalpClockSetMSRate = FALSE; } /* Update the system time -- the kernel will exit this trap */ KeUpdateSystemTime(TrapFrame, LastIncrement, Irql); } /* Spurious, just end the interrupt */ KiEoiHelper(TrapFrame); } VOID FASTCALL HalpProfileInterruptHandler(IN PKTRAP_FRAME TrapFrame) { KIRQL Irql; /* Enter trap */ KiEnterInterruptTrap(TrapFrame); /* Start the interrupt */ if (HalBeginSystemInterrupt(PROFILE_LEVEL, PRIMARY_VECTOR_BASE + PIC_RTC_IRQ, &Irql)) { #if defined(SARCH_PC98) /* Clear the interrupt flag */ HalpAcquireCmosSpinLock(); (VOID)__inbyte(RTC_IO_i_INTERRUPT_RESET); HalpReleaseCmosSpinLock(); #else /* Spin until the interrupt pending bit is clear */ HalpAcquireCmosSpinLock(); while (HalpReadCmos(RTC_REGISTER_C) & RTC_REG_C_IRQ) NOTHING; HalpReleaseCmosSpinLock(); #endif /* If profiling is enabled, call the kernel function */ if (!HalpProfilingStopped) { KeProfileInterrupt(TrapFrame); } /* Finish the interrupt */ _disable(); HalEndSystemInterrupt(Irql, TrapFrame); } /* Spurious, just end the interrupt */ KiEoiHelper(TrapFrame); } #endif /* !_MINIHAL_ */ #endif /* _M_IX86 */ /* PUBLIC FUNCTIONS ***********************************************************/ /* * @implemented */ VOID NTAPI HalCalibratePerformanceCounter(IN volatile PLONG Count, IN ULONGLONG NewCount) { ULONG_PTR Flags; /* Disable interrupts */ Flags = __readeflags(); _disable(); /* Do a decrement for this CPU */ _InterlockedDecrement(Count); /* Wait for other CPUs */ while (*Count); /* Restore interrupts if they were previously enabled */ __writeeflags(Flags); } /* * @implemented */ ULONG NTAPI HalSetTimeIncrement(IN ULONG Increment) { /* Round increment to ms */ Increment /= 10000; /* Normalize between our minimum (1 ms) and maximum (variable) setting */ if (Increment > HalpLargestClockMS) Increment = HalpLargestClockMS; if (Increment <= 0) Increment = 1; /* Set the rate and tell HAL we want to change it */ HalpNextMSRate = Increment; HalpClockSetMSRate = TRUE; /* Return the increment */ return HalpRolloverTable[Increment - 1].Increment; } LARGE_INTEGER NTAPI KeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFrequency) { LARGE_INTEGER CurrentPerfCounter; ULONG CounterValue, ClockDelta; KIRQL OldIrql; /* If caller wants performance frequency, return hardcoded value */ if (PerformanceFrequency) PerformanceFrequency->QuadPart = PIT_FREQUENCY; /* Check if we were called too early */ if (HalpCurrentRollOver == 0) return HalpPerfCounter; /* Check if interrupts are disabled */ if(!(__readeflags() & EFLAGS_INTERRUPT_MASK)) return HalpPerfCounter; /* Raise irql to DISPATCH_LEVEL */ OldIrql = KeGetCurrentIrql(); if (OldIrql < DISPATCH_LEVEL) KfRaiseIrql(DISPATCH_LEVEL); do { /* Get the current performance counter value */ CurrentPerfCounter = HalpPerfCounter; /* Read the 8254 counter value */ CounterValue = HalpRead8254Value(); /* Repeat if the value has changed (a clock interrupt happened) */ } while (CurrentPerfCounter.QuadPart != HalpPerfCounter.QuadPart); /* After someone changed the clock rate, during the first clock cycle we might see a counter value larger than the rollover. In this case we pretend it already has the new rollover value. */ if (CounterValue > HalpCurrentRollOver) CounterValue = HalpCurrentRollOver; /* The interrupt is issued on the falling edge of the OUT line, when the counter changes from 1 to max. Calculate a clock delta, so that directly after the interrupt it is 0, going up to (HalpCurrentRollOver - 1). */ ClockDelta = HalpCurrentRollOver - CounterValue; /* Add the clock delta */ CurrentPerfCounter.QuadPart += ClockDelta; /* Check if the value is smaller then before, this means, we somehow missed an interrupt. This is a sign that the timer interrupt is very inaccurate. Probably a virtual machine. */ if (CurrentPerfCounter.QuadPart < HalpLastPerfCounter.QuadPart) { /* We missed an interrupt. Assume we will receive it later */ CurrentPerfCounter.QuadPart += HalpCurrentRollOver; } /* Update the last counter value */ HalpLastPerfCounter = CurrentPerfCounter; /* Restore previous irql */ if (OldIrql < DISPATCH_LEVEL) KfLowerIrql(OldIrql); /* Return the result */ return CurrentPerfCounter; } /* EOF */