Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

860 lines
20 KiB

/*++
Module Name:
mpclockc.c
Abstract:
Author:
Ron Mosgrove - Intel
Environment:
Kernel mode
Revision History:
--*/
#include "halp.h"
//
// Define global data used to communicate new clock rates to the clock
// interrupt service routine.
//
struct RtcTimeIncStruc {
ULONG RTCRegisterA; // The RTC register A value for this rate
ULONG RateIn100ns; // This rate in multiples of 100ns
ULONG RateAdjustmentNs; // Error Correction (in ns)
ULONG RateAdjustmentCnt; // Error Correction (as a fraction of 256)
ULONG IpiRate; // IPI Rate Count (as a fraction of 256)
};
//
// The adjustment is expressed in terms of a fraction of 256 so that
// the ISR can easily determine when a 100ns slice needs to be subtracted
// from the count passed to the kernel without any expensive operations
//
// Using 256 as a base means that anytime the count becomes greater
// than 256 the time slice must be incremented, the overflow can then
// be cleared by AND'ing the value with 0xff
//
#define AVAILABLE_INCREMENTS 5
struct RtcTimeIncStruc HalpRtcTimeIncrements[AVAILABLE_INCREMENTS] = {
{0x026, 9766, 38, 96, /* 3/8 of 256 */ 16},
{0x027, 19532, 75, 192, /* 3/4 of 256 */ 32},
{0x028, 39063, 50, 128, /* 1/2 of 256 */ 64},
{0x029, 78125, 0, 0, 128},
{0x02a, 156250, 0, 0, 256}
};
ULONG HalpInitialClockRateIndex = AVAILABLE_INCREMENTS-1;
extern ULONG HalpCurrentRTCRegisterA;
extern ULONG HalpCurrentClockRateIn100ns;
extern ULONG HalpCurrentClockRateAdjustment;
extern ULONG HalpCurrentIpiRate;
extern ULONG HalpNextMSRate;
#if !defined(_WIN64)
extern ULONG HalpClockWork;
extern BOOLEAN HalpClockSetMSRate;
#endif
VOID
HalpSetInitialClockRate (
VOID
);
VOID
HalpInitializeTimerResolution (
ULONG Rate
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGELK, HalpSetInitialClockRate)
#pragma alloc_text(INIT, HalpInitializeTimerResolution)
#endif
VOID
HalpInitializeTimerResolution (
ULONG Rate
)
/*++
Routine Description:
This function is called to initialize the timer resolution to be
something other then the default. The rate is set to the closest
supported setting below the requested rate.
Arguments:
Rate - in 100ns units
Return Value:
None
--*/
{
ULONG i, s;
//
// Find the table index of the rate to use
//
for (i=1; i < AVAILABLE_INCREMENTS; i++) {
if (HalpRtcTimeIncrements[i].RateIn100ns > Rate) {
break;
}
}
HalpInitialClockRateIndex = i - 1;
//
// Scale IpiRate according to max TimeIncr rate which can be used
//
s = AVAILABLE_INCREMENTS - HalpInitialClockRateIndex - 1;
for (i=0; i < AVAILABLE_INCREMENTS; i++) {
HalpRtcTimeIncrements[i].IpiRate <<= s;
}
}
VOID
HalpSetInitialClockRate (
VOID
)
/*++
Routine Description:
This function is called to set the initial clock interrupt rate
Arguments:
None
Return Value:
None
--*/
{
extern ULONG HalpNextMSRate;
//
// On ACPI timer machines, we need to init an index into the
// milisecond(s) array used by pmtimerc.c's Piix4 workaround
//
#ifdef ACPI_HAL
#ifdef NT_UP
extern ULONG HalpCurrentMSRateTableIndex;
HalpCurrentMSRateTableIndex = (1 << HalpInitialClockRateIndex) - 1;
//
// The Piix4 upper bound table ends at 15ms (index 14), so we'll have
// to map our 15.6ms entry to it as a special case
//
if (HalpCurrentMSRateTableIndex == 0xF) {
HalpCurrentMSRateTableIndex--;
}
#endif
#endif
HalpNextMSRate = HalpInitialClockRateIndex;
HalpCurrentClockRateIn100ns =
HalpRtcTimeIncrements[HalpNextMSRate].RateIn100ns;
HalpCurrentClockRateAdjustment =
HalpRtcTimeIncrements[HalpNextMSRate].RateAdjustmentCnt;
HalpCurrentRTCRegisterA =
HalpRtcTimeIncrements[HalpNextMSRate].RTCRegisterA;
HalpCurrentIpiRate =
HalpRtcTimeIncrements[HalpNextMSRate].IpiRate;
HalpClockWork = 0;
KeSetTimeIncrement (
HalpRtcTimeIncrements[HalpNextMSRate].RateIn100ns,
HalpRtcTimeIncrements[0].RateIn100ns
);
}
#ifdef MMTIMER
ULONG
HalpAcpiTimerSetTimeIncrement(
IN ULONG DesiredIncrement
)
#else
ULONG
HalSetTimeIncrement (
IN ULONG DesiredIncrement
)
#endif
/*++
Routine Description:
This function is called to set the clock interrupt rate to the frequency
required by the specified time increment value.
Arguments:
DesiredIncrement - Supplies desired number of 100ns units between clock
interrupts.
Return Value:
The actual time increment in 100ns units.
--*/
{
ULONG i;
KIRQL OldIrql;
//
// Set the new clock interrupt parameters, return the new time increment value.
//
for (i=1; i <= HalpInitialClockRateIndex; i++) {
if (HalpRtcTimeIncrements[i].RateIn100ns > DesiredIncrement) {
break;
}
}
i = i - 1;
KeRaiseIrql(HIGH_LEVEL,&OldIrql);
HalpNextMSRate = i + 1;
HalpClockSetMSRate = TRUE;
KeLowerIrql (OldIrql);
return (HalpRtcTimeIncrements[i].RateIn100ns);
}
#if defined(_WIN64)
#include "..\amd64\halcmn.h"
ULONG HalpCurrentMSRateTableIndex;
//
// Forward declared functions
//
VOID
HalpUpdateTimerWatchdog (
VOID
);
VOID
HalpClockInterruptWork (
VOID
);
VOID
HalpMcaQueueDpc (
VOID
);
extern PBOOLEAN KdEnteredDebugger;
extern PVOID HalpTimerWatchdogCurFrame;
extern PVOID HalpTimerWatchdogLastFrame;
extern ULONG HalpTimerWatchdogStorageOverflow;
extern ULONG HalpTimerWatchdogEnabled;
//
// Local constants
//
#define COUNTER_TICKS_FOR_AVG 16
#define FRAME_COPY_SIZE (64 * sizeof(ULONG))
#define APIC_ICR_CLOCK (DELIVER_FIXED | ICR_ALL_EXCL_SELF | APIC_CLOCK_VECTOR)
//
// Flags to tell clock routine when P0 can Ipi other processors
//
ULONG HalpIpiClock = 0;
//
// Timer latency watchdog variables
//
ULONG HalpWatchdogAvgCounter;
ULONG64 HalpWatchdogCount;
ULONG64 HalpWatchdogTsc;
HALP_CLOCKWORK_UNION HalpClockWorkUnion;
//
// Clock rate adjustment counter. This counter is used to keep a tally of
// adjustments needed to be applied to the RTC rate as passed to the kernel.
//
ULONG HalpCurrentRTCRegisterA;
ULONG HalpCurrentClockRateIn100ns = 0;
ULONG HalpCurrentClockRateAdjustment = 0;
ULONG HalpCurrentIpiRate = 0;
ULONG HalpNextMSRate = 0;
ULONG HalpPendingRate = 0;
//
// Other
//
BOOLEAN HalpUse8254 = FALSE;
UCHAR HalpSample8254 = 0;
UCHAR HalpRateAdjustment = 0;
ULONG HalpIpiRateCounter = 0;
VOID
HalpInitializeClock (
VOID
)
/*++
Routine Description:
This routine initialize system time clock using RTC to generate an
interrupt at every 15.6250 ms interval at APIC_CLOCK_VECTOR
It also initializes the 8254 if the 8254 is to be used for performance
counters.
See the definition of RegisterAClockValue if clock rate needs to be
changed.
This routine assumes it runs during Phase 0 on P0.
Arguments:
None
Return Value:
None.
--*/
{
ULONG flags;
UCHAR regB;
if (HalpTimerWatchdogEnabled != 0) {
HalpWatchdogAvgCounter = COUNTER_TICKS_FOR_AVG;
HalpWatchdogTsc = ReadTimeStampCounter();
HalpWatchdogCount = 0;
}
flags = HalpDisableInterrupts();
HalpSetInitialClockRate();
//
// Set the interrupt rate to what is actually needed
//
HalpAcquireCmosSpinLock();
CMOS_WRITE(CMOS_STATUS_A,(UCHAR)HalpCurrentRTCRegisterA);
//
// Don't clobber the Daylight Savings Time bit in register B, because we
// stash the LastKnownGood "environment variable" there.
//
regB = CMOS_READ(CMOS_STATUS_B);
regB &= 1;
regB |= REGISTER_B_ENABLE_PERIODIC_INTERRUPT;
//
// Write the register B value, then read C and D to initialize
//
CMOS_WRITE(CMOS_STATUS_B,regB);
CMOS_READ(CMOS_STATUS_C);
CMOS_READ(CMOS_STATUS_D);
HalpReleaseCmosSpinLock();
if (HalpUse8254 != FALSE) {
HalpAcquireSystemHardwareSpinLock();
//
// Program the 8254 to count down the maximum interval (8254 access
// is guarded with the system hardware spin lock).
//
// First program the count mode of the timer, then program the
// interval.
//
WRITE_PORT_UCHAR(TIMER1_CONTROL_PORT,
TIMER_COMMAND_COUNTER0 |
TIMER_COMMAND_RW_16BIT |
TIMER_COMMAND_MODE2);
IO_DELAY();
WRITE_PORT_USHORT_PAIR(TIMER1_DATA_PORT0,
TIMER1_DATA_PORT0,
PERFORMANCE_INTERVAL);
IO_DELAY();
HalpUse8254 |= PERF_8254_INITIALIZED;
HalpReleaseSystemHardwareSpinLock();
}
HalpRestoreInterrupts(flags);
}
BOOLEAN
HalpClockInterruptStub (
IN PKINTERRUPT Interrupt,
IN PVOID ServiceContext,
IN PKTRAP_FRAME TrapFrame
)
/*++
Routine Description:
This routine is entered as the result of an interrupt generated by
CLOCK2. Its function is to dismiss the interrupt and return.
This routine is executed on P0 during phase 0.
Arguments:
Interrupt - Supplies a pointer to the kernel interrupt object
ServiceContext - Supplies the service context
TrapFrame - Supplise a pointer to the trap frame
Return Value:
TRUE
--*/
{
UCHAR status;
UNREFERENCED_PARAMETER(Interrupt);
UNREFERENCED_PARAMETER(ServiceContext);
UNREFERENCED_PARAMETER(TrapFrame);
//
// Clear the interrupt flag on the RTC by banging on the CMOS. On some
// systems this doesn't work the first time we do it, so we do it twice.
// It is rumored that some machines require more than this, but that
// hasn't been observed with NT.
//
CMOS_READ(CMOS_STATUS_C);
do {
status = CMOS_READ(CMOS_STATUS_C);
} while ((status & 0x80) != 0);
return TRUE;
}
BOOLEAN
HalpClockInterrupt (
IN PKINTERRUPT Interrupt,
IN PVOID ServiceContext
)
/*++
Routine Description:
This routine is entered as the result of an interrupt generated by
CLOCK2. Its function is to dismiss the interrupt, raise system Irql to
CLOCK2_LEVEL, update performance the counter and transfer control to
the standard system routine to update the system time and the execution
time of the current thread and process.
Thie routine is executed only on P0
Arguments:
Interrupt - Supplies a pointer to the kernel interrupt object
ServiceContext - Supplies the service context
Return Value:
TRUE
--*/
{
ULONG timeIncrement;
ULONG flags;
ULONG64 timeStamp;
LONG64 timeStampDelta;
ULONG rateCounter;
if (HalpUse8254 != FALSE) {
if (HalpSample8254 == 0) {
//
// Call KeQueryPerformanceCounter() so that wrap-around of 8254
// is detected and the base value for performance counters
// updated. Ignore returned value and reset HalpSample8254.
//
// WARNING - change reset value above if maximum RTC time
// increment is increased to be more than the current maximum
// value of 15.625 ms. Currently the call will be made every
// 3rd timer tick.
//
HalpSample8254 = 2;
KeQueryPerformanceCounter(0);
} else {
HalpSample8254 -= 1;
}
}
//
// This is the RTC interrupt, so we have to clear the flag on the RTC.
//
HalpAcquireCmosSpinLock();
//
// Clear the interrupt flag on the RTC by banging on the CMOS. On some
// systems this doesn't work the first time we do it, so we do it twice.
// It is rumored that some machines require more than this, but that
// hasn't been observed with NT.
//
CMOS_READ(CMOS_STATUS_C);
CMOS_READ(CMOS_STATUS_C);
HalpReleaseCmosSpinLock();
//
// Adjust the tick count as needed.
//
timeIncrement = HalpCurrentClockRateIn100ns;
HalpRateAdjustment += (UCHAR)HalpCurrentClockRateAdjustment;
if (HalpRateAdjustment < HalpCurrentClockRateAdjustment) {
timeIncrement--;
}
//
// With an APIC based system we will force a clock interrupt to all other
// processors. This is not really an IPI in the NT sense of the word,
// it uses the Local Apic to generate interrupts to other CPUs.
//
#if !defined(NT_UP)
//
// See if we need to IPI anyone. This happens only at the lowest
// supported frequency (i.e. the value KeSetTimeIncrement is called
// with). We have an IPI Rate based on the current clock relative
// to the lowest clock rate.
//
rateCounter = HalpIpiRateCounter + HalpCurrentIpiRate;
HalpIpiRateCounter = rateCounter & 0xFF;
if (HalpIpiRateCounter != rateCounter &&
HalpIpiClock != 0) {
//
// Time to send an Ipi and at least one other processor is alive.
//
flags = HalpDisableInterrupts();
HalpStallWhileApicBusy();
LOCAL_APIC(LU_INT_CMD_LOW) = APIC_ICR_CLOCK;
HalpRestoreInterrupts(flags);
}
#endif // NT_UP
if (HalpTimerWatchdogEnabled != 0) {
HalpUpdateTimerWatchdog();
}
if (HalpClockWork != 0) {
HalpClockInterruptWork();
}
KeUpdateSystemTime(Interrupt->TrapFrame,timeIncrement);
return TRUE;
}
BOOLEAN
HalpClockInterruptPn (
IN PKINTERRUPT Interrupt,
IN PVOID ServiceContext
)
/*++
Routine Description:
This routine is entered as the result of an interrupt generated by
CLOCK2. Its function is to dismiss the interrupt, raise system Irql to
CLOCK2_LEVEL, update performance the counter and transfer control to
the standard system routine to update the system time and the execution
time of the current thread and process.
This routine is executed on all processors other than P0.
Arguments:
Interrupt - Supplies a pointer to the kernel interrupt object
ServiceContext - Supplies the service context
Return Value:
TRUE
--*/
{
UNREFERENCED_PARAMETER(ServiceContext);
HalpEnableInterrupts();
KeUpdateRunTime(Interrupt->TrapFrame);
return TRUE;
}
VOID
HalpClockInterruptWork (
VOID
)
{
struct RtcTimeIncStruc *timeIncStruc;
BOOLEAN changeRate;
//
// There is more clock interrupt work to do
//
if (HalpClockMcaQueueDpc != FALSE) {
//
// Queue an MCA dpc
//
HalpClockMcaQueueDpc = FALSE;
HalpMcaQueueDpc();
}
if (HalpClockSetMSRate != FALSE) {
//
// The clock frequency is being changed. See if we have changed
// rates since the last tick.
//
if (HalpPendingRate != 0) {
//
// A new rate was set during the last tick, so update
// globals.
//
// The next tick will occur at the rate which was programmed
// during the last tick. Update globals for the new rate
// which starts with the next tick.
//
// We will get here if there is a request for a rate change.
// There could have been two requests, that is why we are
// comparing the Pending with the NextRate.
//
timeIncStruc = &HalpRtcTimeIncrements[HalpPendingRate - 1];
HalpCurrentClockRateIn100ns = timeIncStruc->RateIn100ns;
HalpCurrentClockRateAdjustment = timeIncStruc->RateAdjustmentCnt;
HalpCurrentIpiRate = timeIncStruc->IpiRate;
if (HalpPendingRate != HalpNextMSRate) {
changeRate = TRUE;
} else {
changeRate = FALSE;
}
HalpPendingRate = 0;
if (changeRate != FALSE) {
//
// A new clock rate needs to be set. Setting the rate here
// will cause the tick after the next tick to be at the new
// rate.
//
// (The next tick is already in progress and will occur at
// the same rate as this tick.)
//
HalpAcquireCmosSpinLock();
HalpPendingRate = HalpNextMSRate;
timeIncStruc = &HalpRtcTimeIncrements[HalpPendingRate - 1];
HalpCurrentRTCRegisterA = timeIncStruc->RTCRegisterA;
CMOS_WRITE(CMOS_STATUS_A,(UCHAR)HalpCurrentRTCRegisterA);
if (HalpTimerWatchdogEnabled != FALSE) {
HalpWatchdogTsc = ReadTimeStampCounter();
HalpWatchdogCount = 0;
HalpWatchdogAvgCounter = COUNTER_TICKS_FOR_AVG;
}
HalpReleaseCmosSpinLock();
}
}
}
}
VOID
HalpUpdateTimerWatchdog (
VOID
)
{
ULONG stackStart;
ULONG64 timeStamp;
ULONG64 timeStampDelta;
timeStamp = ReadTimeStampCounter();
timeStampDelta = timeStamp - HalpWatchdogTsc;
HalpWatchdogTsc = timeStamp;
if ((LONG64)timeStampDelta < 0) {
//
// Bogus (negative) timestamp count.
//
return;
}
if (*KdEnteredDebugger != FALSE) {
//
// Skip if we're in the debugger.
//
return;
}
if (HalpPendingRate != 0) {
//
// A new rate was set during the last tick, discontinue
// processing
//
return;
}
if (HalpWatchdogAvgCounter != 0) {
//
// Increment the total counter, perform average when the count is
// reached.
//
HalpWatchdogCount += timeStampDelta;
HalpWatchdogAvgCounter -= 1;
if (HalpWatchdogAvgCounter == 0) {
HalpWatchdogCount /= COUNTER_TICKS_FOR_AVG;
}
return;
}
if (timeStampDelta <= HalpWatchdogCount) {
return;
}
if (HalpTimerWatchdogCurFrame != NULL &&
HalpTimerWatchdogStorageOverflow == FALSE) {
PVOID pSrc;
ULONG copyBytes;
//
// Copy FRAME_COPY_SIZE dwords from the stack, or to next
// page boundary, whichever is less.
//
pSrc = &stackStart;
copyBytes = (ULONG)(PAGE_SIZE - ((ULONG_PTR)pSrc & (PAGE_SIZE-1)));
if (copyBytes > FRAME_COPY_SIZE) {
copyBytes = FRAME_COPY_SIZE;
}
RtlCopyMemory(HalpTimerWatchdogCurFrame, pSrc, copyBytes);
(ULONG_PTR)HalpTimerWatchdogCurFrame += copyBytes;
//
// If we didn't copy an entire FRAME_COPY_SIZE buffer, zero
// fill.
//
copyBytes = FRAME_COPY_SIZE - copyBytes;
if (copyBytes > 0) {
RtlZeroMemory(HalpTimerWatchdogCurFrame,copyBytes);
(ULONG_PTR)HalpTimerWatchdogCurFrame += copyBytes;
}
if (HalpTimerWatchdogCurFrame >= HalpTimerWatchdogLastFrame) {
HalpTimerWatchdogStorageOverflow = TRUE;
}
}
}
#endif // _WIN64