Doxygen

timer.c
Go to the documentation of this file.
00001 /*
00002  * PROJECT:         ReactOS HAL
00003  * LICENSE:         GPL - See COPYING in the top level directory
00004  * FILE:            hal/halx86/generic/timer.c
00005  * PURPOSE:         HAL Timer Routines
00006  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
00007  *                  Timo Kreuzer (timo.kreuzer@reactos.org)
00008  */
00009 
00010 /* INCLUDES ******************************************************************/
00011 
00012 #include <hal.h>
00013 #define NDEBUG
00014 #include <debug.h>
00015 
00016 /* GLOBALS *******************************************************************/
00017 
00018 #define PIT_LATCH  0x00
00019 
00020 LARGE_INTEGER HalpLastPerfCounter;
00021 LARGE_INTEGER HalpPerfCounter;
00022 ULONG HalpPerfCounterCutoff;
00023 BOOLEAN HalpClockSetMSRate;
00024 ULONG HalpCurrentTimeIncrement;
00025 ULONG HalpCurrentRollOver;
00026 ULONG HalpNextMSRate = 14;
00027 ULONG HalpLargestClockMS = 15;
00028 
00029 static struct _HALP_ROLLOVER
00030 {
00031     ULONG RollOver;
00032     ULONG Increment;
00033 } HalpRolloverTable[15] =
00034 {
00035     {1197, 10032},
00036     {2394, 20064},
00037     {3591, 30096},
00038     {4767, 39952},
00039     {5964, 49984},
00040     {7161, 60016},
00041     {8358, 70048},
00042     {9555, 80080},
00043     {10731, 89936},
00044     {11949, 100144},
00045     {13125, 110000},
00046     {14322, 120032},
00047     {15519, 130064},
00048     {16695, 139920},
00049     {17892, 149952}
00050 };
00051 
00052 /* PRIVATE FUNCTIONS *********************************************************/
00053 
00054 FORCEINLINE
00055 ULONG
00056 HalpRead8254Value(void)
00057 {
00058     ULONG TimerValue;
00059 
00060     /* Send counter latch command for channel 0 */
00061     __outbyte(TIMER_CONTROL_PORT, PIT_LATCH);
00062     __nop();
00063 
00064     /* Read the value, LSB first */
00065     TimerValue = __inbyte(TIMER_CHANNEL0_DATA_PORT);
00066     __nop();
00067     TimerValue |= __inbyte(TIMER_CHANNEL0_DATA_PORT) << 8;
00068 
00069     return TimerValue;
00070 }
00071 
00072 VOID
00073 NTAPI
00074 HalpSetTimerRollOver(USHORT RollOver)
00075 {
00076     ULONG_PTR Flags;
00077     TIMER_CONTROL_PORT_REGISTER TimerControl;
00078 
00079     /* Disable interrupts */
00080     Flags = __readeflags();
00081     _disable();
00082 
00083     /* Program the PIT for binary mode */
00084     TimerControl.BcdMode = FALSE;
00085 
00086     /*
00087      * Program the PIT to generate a normal rate wave (Mode 3) on channel 0.
00088      * Channel 0 is used for the IRQ0 clock interval timer, and channel
00089      * 1 is used for DRAM refresh.
00090      *
00091      * Mode 2 gives much better accuracy than Mode 3.
00092      */
00093     TimerControl.OperatingMode = PitOperatingMode2;
00094     TimerControl.Channel = PitChannel0;
00095 
00096     /* Set the access mode that we'll use to program the reload value */
00097     TimerControl.AccessMode = PitAccessModeLowHigh;
00098 
00099     /* Now write the programming bits */
00100     __outbyte(TIMER_CONTROL_PORT, TimerControl.Bits);
00101 
00102     /* Next we write the reload value for channel 0 */
00103     __outbyte(TIMER_CHANNEL0_DATA_PORT, RollOver & 0xFF);
00104     __outbyte(TIMER_CHANNEL0_DATA_PORT, RollOver >> 8);
00105 
00106     /* Restore interrupts if they were previously enabled */
00107     __writeeflags(Flags);
00108 }
00109 
00110 VOID
00111 NTAPI
00112 INIT_FUNCTION
00113 HalpInitializeClock(VOID)
00114 {
00115     ULONG Increment;
00116     USHORT RollOver;
00117 
00118     DPRINT("HalpInitializeClock()\n");
00119 
00120     /* Get increment and rollover for the largest time clock ms possible */
00121     Increment = HalpRolloverTable[HalpLargestClockMS - 1].Increment;
00122     RollOver = (USHORT)HalpRolloverTable[HalpLargestClockMS - 1].RollOver;
00123 
00124     /* Set the maximum and minimum increment with the kernel */
00125     KeSetTimeIncrement(Increment, HalpRolloverTable[0].Increment);
00126 
00127     /* Set the rollover value for the timer */
00128     HalpSetTimerRollOver(RollOver);
00129 
00130     /* Save rollover and increment */
00131     HalpCurrentRollOver = RollOver;
00132     HalpCurrentTimeIncrement = Increment;
00133 }
00134 
00135 #ifdef _M_IX86
00136 #ifndef _MINIHAL_
00137 VOID
00138 FASTCALL
00139 HalpClockInterruptHandler(IN PKTRAP_FRAME TrapFrame)
00140 {
00141     ULONG LastIncrement;
00142     KIRQL Irql;
00143 
00144     /* Enter trap */
00145     KiEnterInterruptTrap(TrapFrame);
00146 
00147     /* Start the interrupt */
00148     if (HalBeginSystemInterrupt(CLOCK2_LEVEL, PRIMARY_VECTOR_BASE, &Irql))
00149     {
00150         /* Update the performance counter */
00151         HalpPerfCounter.QuadPart += HalpCurrentRollOver;
00152         HalpPerfCounterCutoff = KiEnableTimerWatchdog;
00153 
00154         /* Save increment */
00155         LastIncrement = HalpCurrentTimeIncrement;
00156 
00157         /* Check if someone changed the time rate */
00158         if (HalpClockSetMSRate)
00159         {
00160             /* Update the global values */
00161             HalpCurrentTimeIncrement = HalpRolloverTable[HalpNextMSRate - 1].Increment;
00162             HalpCurrentRollOver = HalpRolloverTable[HalpNextMSRate - 1].RollOver;
00163 
00164             /* Set new timer rollover */
00165             HalpSetTimerRollOver((USHORT)HalpCurrentRollOver);
00166 
00167             /* We're done */
00168             HalpClockSetMSRate = FALSE;
00169         }
00170 
00171         /* Update the system time -- the kernel will exit this trap  */
00172         KeUpdateSystemTime(TrapFrame, LastIncrement, Irql);
00173     }
00174 
00175     /* Spurious, just end the interrupt */
00176     KiEoiHelper(TrapFrame);
00177 }
00178 
00179 VOID
00180 FASTCALL
00181 HalpProfileInterruptHandler(IN PKTRAP_FRAME TrapFrame)
00182 {
00183     KIRQL Irql;
00184 
00185     /* Enter trap */
00186     KiEnterInterruptTrap(TrapFrame);
00187 
00188     /* Start the interrupt */
00189     if (HalBeginSystemInterrupt(PROFILE_LEVEL, PRIMARY_VECTOR_BASE + 8, &Irql))
00190     {
00191         /* Profiling isn't yet enabled */
00192         UNIMPLEMENTED;
00193         ASSERT(FALSE);
00194     }
00195 
00196     /* Spurious, just end the interrupt */
00197     KiEoiHelper(TrapFrame);
00198 }
00199 #endif
00200 
00201 #endif
00202 
00203 /* PUBLIC FUNCTIONS ***********************************************************/
00204 
00205 /*
00206  * @implemented
00207  */
00208 VOID
00209 NTAPI
00210 HalCalibratePerformanceCounter(IN volatile PLONG Count,
00211                                IN ULONGLONG NewCount)
00212 {
00213     ULONG_PTR Flags;
00214 
00215     /* Disable interrupts */
00216     Flags = __readeflags();
00217     _disable();
00218 
00219     /* Do a decrement for this CPU */
00220     _InterlockedDecrement(Count);
00221 
00222     /* Wait for other CPUs */
00223     while (*Count);
00224 
00225     /* Restore interrupts if they were previously enabled */
00226     __writeeflags(Flags);
00227 }
00228 
00229 /*
00230  * @implemented
00231  */
00232 ULONG
00233 NTAPI
00234 HalSetTimeIncrement(IN ULONG Increment)
00235 {
00236     /* Round increment to ms */
00237     Increment /= 10000;
00238 
00239     /* Normalize between our minimum (1 ms) and maximum (variable) setting */
00240     if (Increment > HalpLargestClockMS) Increment = HalpLargestClockMS;
00241     if (Increment <= 0) Increment = 1;
00242 
00243     /* Set the rate and tell HAL we want to change it */
00244     HalpNextMSRate = Increment;
00245     HalpClockSetMSRate = TRUE;
00246 
00247     /* Return the increment */
00248     return HalpRolloverTable[Increment - 1].Increment;
00249 }
00250 
00251 LARGE_INTEGER
00252 NTAPI
00253 KeQueryPerformanceCounter(PLARGE_INTEGER PerformanceFrequency)
00254 {
00255     LARGE_INTEGER CurrentPerfCounter;
00256     ULONG CounterValue, ClockDelta;
00257     KIRQL OldIrql;
00258 
00259     /* If caller wants performance frequency, return hardcoded value */
00260     if (PerformanceFrequency) PerformanceFrequency->QuadPart = PIT_FREQUENCY;
00261 
00262     /* Check if we were called too early */
00263     if (HalpCurrentRollOver == 0) return HalpPerfCounter;
00264 
00265     /* Check if interrupts are disabled */
00266     if(!(__readeflags() & EFLAGS_INTERRUPT_MASK)) return HalpPerfCounter;
00267 
00268     /* Raise irql to DISPATCH_LEVEL */
00269     OldIrql = KeGetCurrentIrql();
00270     if (OldIrql < DISPATCH_LEVEL) KfRaiseIrql(DISPATCH_LEVEL);
00271 
00272     do
00273     {
00274         /* Get the current performance counter value */
00275         CurrentPerfCounter = HalpPerfCounter;
00276 
00277         /* Read the 8254 counter value */
00278         CounterValue = HalpRead8254Value();
00279 
00280     /* Repeat if the value has changed (a clock interrupt happened) */
00281     } while (CurrentPerfCounter.QuadPart != HalpPerfCounter.QuadPart);
00282 
00283     /* After someone changed the clock rate, during the first clock cycle we
00284        might see a counter value larger than the rollover. In this case we
00285        pretend it already has the new rollover value. */
00286     if (CounterValue > HalpCurrentRollOver) CounterValue = HalpCurrentRollOver;
00287 
00288     /* The interrupt is issued on the falling edge of the OUT line, when the
00289        counter changes from 1 to max. Calculate a clock delta, so that directly
00290        after the interrupt it is 0, going up to (HalpCurrentRollOver - 1). */
00291     ClockDelta = HalpCurrentRollOver - CounterValue;
00292 
00293     /* Add the clock delta */
00294     CurrentPerfCounter.QuadPart += ClockDelta;
00295 
00296     /* Check if the value is smaller then before, this means, we somehow
00297        missed an interrupt. This is a sign that the timer interrupt
00298        is very inaccurate. Probably a virtual machine. */
00299     if (CurrentPerfCounter.QuadPart < HalpLastPerfCounter.QuadPart)
00300     {
00301         /* We missed an interrupt. Assume we will receive it later */
00302         CurrentPerfCounter.QuadPart += HalpCurrentRollOver;
00303     }
00304 
00305     /* Update the last counter value */
00306     HalpLastPerfCounter = CurrentPerfCounter;
00307 
00308     /* Restore previous irql */
00309     if (OldIrql < DISPATCH_LEVEL) KfLowerIrql(OldIrql);
00310 
00311     /* Return the result */
00312     return CurrentPerfCounter;
00313 }
00314 
00315 /* EOF */