1096 lines
27 KiB
C
1096 lines
27 KiB
C
/*++
|
||
|
||
Copyright (c) 1998 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
pmtimerc.c
|
||
|
||
Abstract:
|
||
|
||
Implements workarounds for PIIX4 bugs. The
|
||
nature of the ACPI timer in PIIX4 is that it
|
||
occasionally returns completely bogus data.
|
||
Intel claims that this happens about 0.02% of
|
||
the time when it is polled continuously. As NT
|
||
almost never polls it continuously, we don't
|
||
really know what the real behavior is.
|
||
|
||
The workaround is something like this: On
|
||
every clock tick, we read the timer. Using this
|
||
value, we compute an upper bound for what the
|
||
timer may read by the next clock tick. We also
|
||
record the minimum value ever returned. If, at
|
||
any time, we read the timer and it does not fall
|
||
within the minimum and upper bound, then we read
|
||
it again. If it either falls within the bounds
|
||
or it is very close to the last read, we use it.
|
||
If not, we read it again.
|
||
|
||
This behavior allows us to read the timer only
|
||
once almost all the time that we need a time
|
||
stamp. Exiting the debugger is almost guaranteed
|
||
to cause the read-twice behavior.
|
||
|
||
Author:
|
||
|
||
Jake Oshins (jakeo) 30-Oct-1998
|
||
|
||
Environment:
|
||
|
||
Kernel mode only.
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "halp.h"
|
||
#ifdef APIC_HAL
|
||
#include "apic.inc"
|
||
#include "ntapic.inc"
|
||
#endif
|
||
|
||
BOOLEAN
|
||
HalpPmTimerSpecialStall(
|
||
IN ULONG Ticks
|
||
);
|
||
|
||
BOOLEAN
|
||
HalpPmTimerScaleTimers(
|
||
VOID
|
||
);
|
||
|
||
LARGE_INTEGER
|
||
HalpPmTimerQueryPerfCount(
|
||
OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL
|
||
);
|
||
|
||
VOID
|
||
HalpAdjustUpperBoundTable2X(
|
||
VOID
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(INIT, HalpPmTimerScaleTimers)
|
||
#pragma alloc_text(INIT, HalpPmTimerSpecialStall)
|
||
#pragma alloc_text(INIT, HalpAdjustUpperBoundTable2X)
|
||
#endif
|
||
|
||
typedef struct {
|
||
ULONG CurrentTimePort;
|
||
volatile ULONG TimeLow;
|
||
volatile ULONG TimeHigh2;
|
||
volatile ULONG TimeHigh1;
|
||
ULONG MsbMask;
|
||
ULONG BiasLow;
|
||
ULONG BiasHigh;
|
||
volatile ULONG UpperBoundLow;
|
||
volatile ULONG UpperBoundHigh2;
|
||
volatile ULONG UpperBoundHigh1;
|
||
} TIMER_INFO, *PTIMER_INFO;
|
||
|
||
typedef struct {
|
||
ULONG RawRead[2];
|
||
ULONG AdjustedLow[2];
|
||
ULONG AdjustedHigh[2];
|
||
ULONG TITL;
|
||
ULONG TITH;
|
||
ULONG UBL;
|
||
ULONG UBH;
|
||
ULONG ReturnedLow;
|
||
ULONG ReturnedHigh;
|
||
ULONG ReadCount;
|
||
ULONG TickMin;
|
||
ULONG TickCount;
|
||
ULONG TickNewUB;
|
||
//UCHAR padding[4];
|
||
} TIMER_PERF_INDEX ,*PTIMER_PERF_INDEX;
|
||
|
||
extern TIMER_INFO TimerInfo;
|
||
|
||
#if DBG
|
||
ULONG LastKQPCValue[3] = {0};
|
||
static ULARGE_INTEGER LastClockSkew = { 0, 0 };
|
||
#endif
|
||
|
||
#ifndef NO_PM_KEQPC
|
||
|
||
extern ULONG HalpCurrentMSRateTableIndex;
|
||
extern UCHAR HalpBrokenAcpiTimer;
|
||
extern UCHAR HalpPiix4;
|
||
extern PVOID QueryTimer;
|
||
|
||
#if DBG
|
||
extern TIMER_PERF_INDEX TimerPerf[];
|
||
extern ULONG TimerPerfIndex;
|
||
#endif
|
||
|
||
//
|
||
// The UpperBoundTable contains the values which should be added
|
||
// to the current counter value to ensure that the upper bound is
|
||
// reasonable. Values listed here are for all the 15 possible
|
||
// timer tick lengths. The unit is "PM Timer Ticks" and the
|
||
// value corresponds to the number of ticks that will pass in
|
||
// roughly two timer ticks at this rate.
|
||
//
|
||
#define UPPER_BOUND_TABLE_SIZE 15
|
||
static ULONG HalpPiix4UpperBoundTable[] = {
|
||
#if 0
|
||
20000 , // 1 ms
|
||
35000 , // 2 ms
|
||
50000 , // 3 ms
|
||
65000 , // 4 ms
|
||
85000 , // 5 ms
|
||
100000, // 6 ms
|
||
115000, // 7 ms
|
||
130000, // 8 ms
|
||
150000, // 9 ms
|
||
165000, // 10 ms
|
||
180000, // 11 ms
|
||
195000, // 12 ms
|
||
211000, // 13 ms
|
||
230000, // 14 ms
|
||
250000 // 15 ms
|
||
#endif
|
||
14318,
|
||
28636,
|
||
42954,
|
||
57272,
|
||
71590,
|
||
85908,
|
||
100226,
|
||
114544,
|
||
128862,
|
||
143180,
|
||
157498,
|
||
171818,
|
||
186136,
|
||
200454,
|
||
214772
|
||
};
|
||
|
||
VOID
|
||
HalpAdjustUpperBoundTable2X(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This adjusts the upper bound table for PM timer running at 2X
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG Looper;
|
||
|
||
for (Looper = 0; Looper < UPPER_BOUND_TABLE_SIZE; Looper++) {
|
||
HalpPiix4UpperBoundTable[Looper] *= 2;
|
||
}
|
||
}
|
||
|
||
|
||
ULONG
|
||
HalpQuery8254Counter(
|
||
VOID
|
||
);
|
||
|
||
static ULARGE_INTEGER ClockSkew = { 0, 0 };
|
||
static BOOLEAN PositiveSkew = TRUE;
|
||
|
||
#ifdef TIMER_DBG
|
||
static BOOLEAN DoItOnce = TRUE;
|
||
static BOOLEAN PacketLog = TRUE;
|
||
static BOOLEAN TimerTick = FALSE;
|
||
|
||
static ULONG NegativeGlitches = 0;
|
||
static ULONG PositiveGlitches = 0;
|
||
|
||
static ULONG PacketLogCount = 5;
|
||
|
||
typedef struct _TIMER_PACKET {
|
||
ULONG Hardware;
|
||
ULARGE_INTEGER CurrentRead0;
|
||
ULARGE_INTEGER TimeStamp;
|
||
ULARGE_INTEGER Minimum;
|
||
ULARGE_INTEGER Maximum;
|
||
BOOLEAN PositiveSkew;
|
||
BOOLEAN TimerTick;
|
||
UCHAR Reserved[2];
|
||
ULARGE_INTEGER Skew;
|
||
ULARGE_INTEGER CurrentRead1;
|
||
} TIMER_PACKET, *PTIMER_PACKET;
|
||
|
||
#define MAX_TIMER_PACKETS 10
|
||
static ULONG PacketIndex = 0;
|
||
static TIMER_PACKET TimerLog[MAX_TIMER_PACKETS];
|
||
#endif // TIMER_DBG
|
||
|
||
#define A_FEW_TICKS 3
|
||
|
||
|
||
ULARGE_INTEGER
|
||
FASTCALL
|
||
HalpQueryBrokenPiix4(
|
||
VOID
|
||
)
|
||
{
|
||
ULARGE_INTEGER lastRead;
|
||
ULARGE_INTEGER currentRead;
|
||
ULARGE_INTEGER currentRead0;
|
||
ULARGE_INTEGER minTime;
|
||
ULARGE_INTEGER upperBound;
|
||
ULONG hardwareVal;
|
||
ULONG bitsInHardware;
|
||
ULONG flags;
|
||
ULONG ClockBits;
|
||
ULARGE_INTEGER RollOver;
|
||
ULARGE_INTEGER SkewedTime;
|
||
#ifndef NT_UP
|
||
KIRQL Irql;
|
||
#endif
|
||
|
||
#ifdef TIMER_DBG
|
||
ULONG Index;
|
||
#endif
|
||
|
||
#if DBG
|
||
ULONG readCount = 0;
|
||
PTIMER_PERF_INDEX timerPerfRecord =
|
||
&(TimerPerf[TimerPerfIndex]);
|
||
|
||
RtlZeroMemory(timerPerfRecord, sizeof(TIMER_PERF_INDEX));
|
||
#endif
|
||
|
||
//
|
||
// N.B. This is, of course, not MP safe. But none
|
||
// of the PIIX4 workaround code is. MP machines don't
|
||
// use PIIX4 code.
|
||
//
|
||
_asm {
|
||
pushfd
|
||
pop eax
|
||
mov flags, eax
|
||
cli
|
||
}
|
||
|
||
lastRead.QuadPart = 0;
|
||
bitsInHardware = (TimerInfo.MsbMask << 1) - 1;
|
||
|
||
//
|
||
// Get current minimum reported time.
|
||
//
|
||
minTime.HighPart = TimerInfo.TimeHigh2;
|
||
minTime.LowPart = TimerInfo.TimeLow;
|
||
|
||
#if DBG
|
||
timerPerfRecord->TITL = TimerInfo.TimeLow;
|
||
timerPerfRecord->TITH = TimerInfo.TimeHigh1;
|
||
#endif
|
||
|
||
//
|
||
// Loop until we get a time that we can believe.
|
||
//
|
||
RollOver.QuadPart = 0;
|
||
while (TRUE) {
|
||
|
||
//
|
||
// Read the hardware.
|
||
//
|
||
|
||
hardwareVal = READ_PORT_ULONG((PULONG)TimerInfo.CurrentTimePort);
|
||
|
||
#ifdef TIMER_DBG
|
||
if (DoItOnce) {
|
||
RtlZeroMemory(&TimerLog[0], sizeof(TIMER_PACKET) *
|
||
MAX_TIMER_PACKETS);
|
||
DoItOnce = FALSE;
|
||
}
|
||
|
||
if (FALSE) { //((hardwareVal & 0xFFFF8000) == 0xFFFF8000) {
|
||
PacketLog = TRUE;
|
||
PacketLogCount = 5;
|
||
}
|
||
|
||
if (PacketLog) {
|
||
|
||
if (PacketLogCount == 0) {
|
||
PacketLog = FALSE;
|
||
}
|
||
|
||
if (PacketLogCount > 0) {
|
||
Index = PacketIndex++ % MAX_TIMER_PACKETS;
|
||
RtlZeroMemory(&TimerLog[Index], sizeof(TIMER_PACKET));
|
||
TimerLog[Index].Hardware = hardwareVal;
|
||
TimerLog[Index].TimerTick = TimerTick;
|
||
|
||
{
|
||
ULONG TSCounterHigh;
|
||
ULONG TSCounterLow;
|
||
|
||
_asm { rdtsc
|
||
mov TSCounterLow, eax
|
||
mov TSCounterHigh, edx };
|
||
|
||
TimerLog[Index].TimeStamp.HighPart = TSCounterHigh;
|
||
TimerLog[Index].TimeStamp.LowPart = TSCounterLow;
|
||
}
|
||
|
||
TimerLog[Index].Minimum = minTime;
|
||
TimerLog[Index].PositiveSkew = PositiveSkew;
|
||
TimerLog[Index].Skew = ClockSkew;
|
||
|
||
if ((PacketLogCount < 4) && (PacketLogCount > 0)) {
|
||
PacketLogCount--;
|
||
}
|
||
}
|
||
}
|
||
#endif // TIMER_DBG
|
||
|
||
currentRead.HighPart = minTime.HighPart;
|
||
currentRead.LowPart = (minTime.LowPart & (~bitsInHardware)) |
|
||
hardwareVal;
|
||
|
||
currentRead0 = currentRead;
|
||
|
||
//
|
||
// Check for rollover, since this function is called during each
|
||
// system clock interrupt, if the HW has really rolled over, then it
|
||
// should be within upper bound ticks since that is approximately
|
||
// twice the number of ticks we expect during each system clock
|
||
// interrupt, however, some broken timers occasionally tick backward
|
||
// a few ticks, and if this happens, we may accidentally detect
|
||
// more than one rollover during this period depending upon how
|
||
// frequently applications are calling this API, and how often the
|
||
// HW glitches, and this can cause applications to jerk like mad,
|
||
// but we cannot apply heuristics to try to throw out any of these
|
||
// detected rolls during this interval because we could accidentally
|
||
// throw out the one and only legitimate rollover
|
||
//
|
||
if (RollOver.QuadPart > 0) {
|
||
currentRead.QuadPart += RollOver.QuadPart;
|
||
|
||
} else {
|
||
SkewedTime = minTime;
|
||
|
||
//
|
||
// If time is skewed, we need to remove the skew to accurately
|
||
// assess whether the timer has wrapped
|
||
//
|
||
if (ClockSkew.QuadPart > 0) {
|
||
if (PositiveSkew) {
|
||
SkewedTime.QuadPart -= ClockSkew.QuadPart;
|
||
} else {
|
||
SkewedTime.QuadPart += ClockSkew.QuadPart;
|
||
}
|
||
}
|
||
|
||
if (((ULONG)(SkewedTime.LowPart & bitsInHardware) > hardwareVal) &&
|
||
(hardwareVal < (HalpPiix4UpperBoundTable[HalpCurrentMSRateTableIndex] / 2))) {
|
||
|
||
RollOver.QuadPart = (UINT64)(TimerInfo.MsbMask) << 1;
|
||
currentRead.QuadPart += RollOver.QuadPart;
|
||
}
|
||
}
|
||
|
||
#ifdef TIMER_DBG
|
||
if (PacketLog) {
|
||
TimerLog[Index].CurrentRead0 = currentRead;
|
||
}
|
||
#endif
|
||
|
||
#if DBG
|
||
readCount = timerPerfRecord->ReadCount;
|
||
readCount &= 1;
|
||
timerPerfRecord->RawRead[readCount] = hardwareVal;
|
||
timerPerfRecord->AdjustedLow[readCount] = currentRead.LowPart;
|
||
timerPerfRecord->AdjustedHigh[readCount] = currentRead.HighPart;
|
||
timerPerfRecord->ReadCount++;
|
||
#endif
|
||
|
||
//
|
||
// Get the current upper bound.
|
||
//
|
||
upperBound.HighPart = TimerInfo.UpperBoundHigh2;
|
||
upperBound.LowPart = TimerInfo.UpperBoundLow;
|
||
|
||
#ifdef TIMER_DBG
|
||
if (PacketLog) {
|
||
TimerLog[Index].Maximum = upperBound;
|
||
}
|
||
#endif
|
||
|
||
if ((minTime.QuadPart <= currentRead.QuadPart) &&
|
||
(currentRead.QuadPart <= upperBound.QuadPart)) {
|
||
|
||
//
|
||
// This value from the counter is within the boundaries
|
||
// that we expect.
|
||
//
|
||
//
|
||
// If there was previously a skew, unskew
|
||
//
|
||
ClockSkew.QuadPart = 0;
|
||
break;
|
||
}
|
||
|
||
if (ClockSkew.QuadPart > 0) {
|
||
SkewedTime = currentRead;
|
||
|
||
if (PositiveSkew) {
|
||
SkewedTime.QuadPart += ClockSkew.QuadPart;
|
||
} else {
|
||
SkewedTime.QuadPart -= ClockSkew.QuadPart;
|
||
}
|
||
|
||
if ((minTime.QuadPart <= SkewedTime.QuadPart) &&
|
||
(SkewedTime.QuadPart <= upperBound.QuadPart)) {
|
||
|
||
//
|
||
// This value from the counter is within the boundaries
|
||
// that we accept
|
||
//
|
||
currentRead = SkewedTime;
|
||
break;
|
||
}
|
||
}
|
||
|
||
//
|
||
// We are guaranteed to break out of this as soon as we read
|
||
// two consectutive non-decreasing values from the timer whose
|
||
// difference is less than or equal to 0xfff-- is this
|
||
// too much to ask?
|
||
//
|
||
if ((currentRead.QuadPart - lastRead.QuadPart) > 0xfff) {
|
||
lastRead = currentRead;
|
||
continue;
|
||
}
|
||
|
||
#ifdef TIMER_DBG
|
||
if (PacketLog) {
|
||
if (PacketLogCount > 0) {
|
||
PacketLogCount--;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Now we are really screwed-- we are consistently reading values
|
||
// from the timer, that are not within the boundaries we expect
|
||
//
|
||
// We are going to record/apply a skew that will get us back on
|
||
// track
|
||
//
|
||
if (currentRead.QuadPart < minTime.QuadPart) {
|
||
|
||
//
|
||
// Time jumped backward a small fraction, just add a few ticks
|
||
//
|
||
if ((minTime.QuadPart - currentRead.QuadPart) < 0x40) {
|
||
SkewedTime.QuadPart = minTime.QuadPart + A_FEW_TICKS;
|
||
|
||
//
|
||
// Time jumped backward quite a bit, add half a system clock
|
||
// interrupt worth of ticks since we know this routine
|
||
// gets called every clock interrupt
|
||
//
|
||
} else {
|
||
SkewedTime.QuadPart = minTime.QuadPart +
|
||
(HalpPiix4UpperBoundTable[HalpCurrentMSRateTableIndex] /
|
||
8);
|
||
}
|
||
|
||
#ifdef TIMER_DBG
|
||
PositiveGlitches++;
|
||
if (PacketLog) {
|
||
TimerLog[Index].PositiveSkew = TRUE;
|
||
TimerLog[Index].Skew.QuadPart =
|
||
SkewedTime.QuadPart - currentRead.QuadPart;
|
||
}
|
||
#endif // TIMER_DBG
|
||
|
||
PositiveSkew = TRUE;
|
||
ClockSkew.QuadPart = SkewedTime.QuadPart - currentRead.QuadPart;
|
||
|
||
//
|
||
// currentRead > upperBound
|
||
//
|
||
} else {
|
||
|
||
//
|
||
// Time jumped forward more than a system clock, interrupts
|
||
// may have been disabled by some unruly driver, or maybe
|
||
// we were hung up in the debugger, at any rate, let's add
|
||
// a full system clock interrupt worth of ticks
|
||
//
|
||
SkewedTime.QuadPart = minTime.QuadPart +
|
||
(HalpPiix4UpperBoundTable[HalpCurrentMSRateTableIndex] /
|
||
4);
|
||
|
||
#ifdef TIMER_DBG
|
||
NegativeGlitches++;
|
||
|
||
if (PacketLog) {
|
||
TimerLog[Index].PositiveSkew = FALSE;
|
||
TimerLog[Index].Skew.QuadPart =
|
||
currentRead.QuadPart - SkewedTime.QuadPart;
|
||
}
|
||
#endif // TIMER_DBG
|
||
|
||
PositiveSkew = FALSE;
|
||
ClockSkew.QuadPart = currentRead.QuadPart - SkewedTime.QuadPart;
|
||
}
|
||
|
||
currentRead = SkewedTime;
|
||
break;
|
||
}
|
||
|
||
#ifdef TIMER_DBG
|
||
if (PacketLog) {
|
||
TimerLog[Index].CurrentRead1 = currentRead;
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// If we detected a rollover, and there is negative skew, then we
|
||
// should recalculate the skew as positive skew to avoid making
|
||
// an erroneous correction on the next read
|
||
//
|
||
if ((ClockSkew.QuadPart > 0) && (RollOver.QuadPart > 0) &&
|
||
(PositiveSkew == FALSE) && (ClockSkew.QuadPart > hardwareVal)) {
|
||
|
||
//
|
||
// I still want to study this case, even though it is handled
|
||
// by the if statement below, it may very well be that when we
|
||
// hit this case, we are double-wrapping the timer by mistake
|
||
//
|
||
ASSERT(currentRead.QuadPart >= currentRead0.QuadPart);
|
||
|
||
if (currentRead.QuadPart >= currentRead0.QuadPart) {
|
||
ClockSkew.QuadPart = currentRead.QuadPart - currentRead0.QuadPart;
|
||
PositiveSkew = TRUE;
|
||
}
|
||
#if TIMER_DBG
|
||
else {
|
||
if ((PacketLog) && (PacketLogCount > 3)) {
|
||
PacketLogCount = 3;
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// Similarly, if there is no rollover, but positive skew is causing
|
||
// the timer to rollover, then we need to readjust the skew also to
|
||
// avoid the possibility of making an erroneous correction on the next
|
||
// read
|
||
//
|
||
if ((ClockSkew.QuadPart > 0) && (RollOver.QuadPart == 0) &&
|
||
(PositiveSkew == TRUE) && ((currentRead.QuadPart & ~bitsInHardware) >
|
||
(minTime.QuadPart & ~bitsInHardware))) {
|
||
|
||
//
|
||
// I'm not sure what this means, or how it can happen, but I will
|
||
// endeavor to decipher the condition if and when it occurs
|
||
//
|
||
ASSERT(currentRead0.QuadPart + bitsInHardware + 1 >
|
||
currentRead.QuadPart);
|
||
|
||
if (currentRead0.QuadPart + bitsInHardware + 1 >
|
||
currentRead.QuadPart) {
|
||
ClockSkew.QuadPart = currentRead0.QuadPart + bitsInHardware + 1 -
|
||
currentRead.QuadPart;
|
||
PositiveSkew = FALSE;
|
||
}
|
||
#if TIMER_DBG
|
||
else {
|
||
if ((PacketLog) && (PacketLogCount > 3)) {
|
||
PacketLogCount = 3;
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// Compute new upper bound.
|
||
//
|
||
upperBound.QuadPart = currentRead.QuadPart +
|
||
HalpPiix4UpperBoundTable[HalpCurrentMSRateTableIndex];
|
||
|
||
//
|
||
// Update upper and lower bounds.
|
||
//
|
||
TimerInfo.TimeHigh1 = currentRead.HighPart;
|
||
TimerInfo.TimeLow = currentRead.LowPart;
|
||
TimerInfo.TimeHigh2 = currentRead.HighPart;
|
||
|
||
TimerInfo.UpperBoundHigh1 = upperBound.HighPart;
|
||
TimerInfo.UpperBoundLow = upperBound.LowPart;
|
||
TimerInfo.UpperBoundHigh2 = upperBound.HighPart;
|
||
|
||
#if DBG
|
||
LastClockSkew = ClockSkew;
|
||
#endif
|
||
|
||
_asm {
|
||
mov eax, flags
|
||
push eax
|
||
popfd
|
||
}
|
||
|
||
#if DBG
|
||
timerPerfRecord->ReturnedLow = currentRead.LowPart;
|
||
timerPerfRecord->ReturnedHigh = currentRead.HighPart;
|
||
timerPerfRecord->UBL = upperBound.LowPart;
|
||
timerPerfRecord->UBH = upperBound.HighPart;
|
||
TimerPerfIndex = (TimerPerfIndex + 1) % (4096 / sizeof(TIMER_PERF_INDEX));
|
||
#endif
|
||
|
||
return currentRead;
|
||
}
|
||
|
||
VOID
|
||
HalpBrokenPiix4TimerTick(
|
||
VOID
|
||
)
|
||
{
|
||
ULARGE_INTEGER currentCount;
|
||
ULARGE_INTEGER upperBound;
|
||
|
||
#if DBG
|
||
PTIMER_PERF_INDEX timerPerfRecord;
|
||
#endif
|
||
|
||
#ifdef TIMER_DBG
|
||
TimerTick = TRUE;
|
||
#endif
|
||
|
||
currentCount =
|
||
HalpQueryBrokenPiix4();
|
||
|
||
#ifdef TIMER_DBG
|
||
TimerTick = FALSE;
|
||
#endif
|
||
|
||
#if DBG
|
||
timerPerfRecord = &(TimerPerf[TimerPerfIndex]);
|
||
timerPerfRecord->TickMin = currentCount.LowPart;
|
||
timerPerfRecord->TickNewUB = TimerInfo.UpperBoundLow;
|
||
timerPerfRecord->TickCount++;
|
||
#endif
|
||
}
|
||
|
||
#endif // NO_PM_KEQPC
|
||
|
||
VOID
|
||
HalaAcpiTimerInit(
|
||
IN ULONG TimerPort,
|
||
IN BOOLEAN TimerValExt
|
||
)
|
||
{
|
||
TimerInfo.CurrentTimePort = TimerPort;
|
||
|
||
if (TimerValExt) {
|
||
TimerInfo.MsbMask = 0x80000000;
|
||
}
|
||
|
||
#ifndef NO_PM_KEQPC
|
||
if (HalpBrokenAcpiTimer) {
|
||
QueryTimer = HalpQueryBrokenPiix4;
|
||
|
||
#if DBG
|
||
{
|
||
KIRQL oldIrql;
|
||
|
||
KeRaiseIrql(HIGH_LEVEL, &oldIrql);
|
||
LastKQPCValue[0] = 0;
|
||
LastKQPCValue[1] = 0;
|
||
LastKQPCValue[2] = 0;
|
||
KeLowerIrql(oldIrql);
|
||
}
|
||
#endif
|
||
}
|
||
#endif // NO_PM_KEQPC
|
||
}
|
||
|
||
#define PIT_FREQUENCY 1193182
|
||
#define PM_TMR_FREQ 3579545
|
||
|
||
#define EIGHTH_SECOND_PM_TICKS 447443
|
||
|
||
ULONG PMTimerFreq = PM_TMR_FREQ;
|
||
|
||
#ifdef SPEEDY_BOOT
|
||
|
||
static ULONG HalpFoundPrime = 0;
|
||
|
||
VOID
|
||
HalpPrimeSearch(
|
||
IN ULONG Primer,
|
||
IN ULONG BitMask
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
The objective of this routine is to waste as many CPU cycles as possible
|
||
by searching for prime numbers. To be fairly consistent in the amount
|
||
of time it wastes, it is severly less than optimal-- we force Primer
|
||
to be odd by or-ing in 15, then we and it with the BitMask, and or
|
||
in BitMask+1, and finally we continue testing after we discover the
|
||
Primer's not prime, until out test factor squared is greater than, or
|
||
equal to the Primer.
|
||
|
||
Arguments:
|
||
|
||
Primer - The number to search (seed)
|
||
|
||
BitMask - How many bits of primer to use in search, controls amount
|
||
of time wasted
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG Index;
|
||
BOOLEAN FoundPrime;
|
||
|
||
Primer |= 0xF;
|
||
BitMask |= 0xF;
|
||
Primer &= BitMask;
|
||
Primer |= (BitMask + 1);
|
||
|
||
FoundPrime = TRUE;
|
||
for (Index = 3; (Index * Index) < Primer; Index += 2) {
|
||
if ((Primer % Index) == 0) {
|
||
FoundPrime = FALSE;
|
||
// Do not break-- we're trying to waste time, remember?
|
||
}
|
||
}
|
||
|
||
//
|
||
// Stuff prime(s) in global so sneaky optimizing compiler
|
||
// doesn't optimize out this B.S.
|
||
//
|
||
if (FoundPrime) {
|
||
HalpFoundPrime = Primer;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
BOOLEAN
|
||
HalpPmTimerSpecialStall(
|
||
IN ULONG Ticks
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Arguments:
|
||
|
||
Ticks - Number of PM timer ticks to stall
|
||
|
||
Return Value:
|
||
|
||
TRUE if we were able to stall for the correct interval,
|
||
otherwise FALSE
|
||
|
||
--*/
|
||
{
|
||
BOOLEAN TimerWrap;
|
||
LARGE_INTEGER TimerWrapBias;
|
||
LARGE_INTEGER LastRead;
|
||
LARGE_INTEGER InitialTicks;
|
||
LARGE_INTEGER TargetTicks;
|
||
LARGE_INTEGER CurrentTicks;
|
||
ULONG ZeroElapsedTickReads;
|
||
|
||
InitialTicks = HalpPmTimerQueryPerfCount(NULL);
|
||
|
||
//
|
||
// Let's test the rollover action...
|
||
//
|
||
CurrentTicks = InitialTicks;
|
||
LastRead.QuadPart = InitialTicks.QuadPart;
|
||
ZeroElapsedTickReads = 0;
|
||
TimerWrapBias.QuadPart = 0;
|
||
TimerWrap = FALSE;
|
||
|
||
TargetTicks.QuadPart = InitialTicks.QuadPart + Ticks;
|
||
|
||
while (CurrentTicks.QuadPart < TargetTicks.QuadPart) {
|
||
|
||
//
|
||
// Now let's really chew up some cycles and see if we can find
|
||
// some prime numbers while we're at it
|
||
//
|
||
HalpPrimeSearch(CurrentTicks.LowPart, 0x7FFF);
|
||
|
||
CurrentTicks = HalpPmTimerQueryPerfCount(NULL);
|
||
CurrentTicks.QuadPart += TimerWrapBias.QuadPart;
|
||
|
||
//
|
||
// Did the timer wrap, or is it broken?
|
||
//
|
||
if (CurrentTicks.QuadPart < LastRead.QuadPart) {
|
||
|
||
//
|
||
// The timer can wrap once, otherwise something's amiss
|
||
//
|
||
if (!TimerWrap) {
|
||
|
||
TimerWrapBias.QuadPart = (UINT64)(TimerInfo.MsbMask) << 1;
|
||
CurrentTicks.QuadPart += TimerWrapBias.QuadPart;
|
||
TimerWrap = TRUE;
|
||
|
||
//
|
||
// Something is whack, considering our elaborate stall
|
||
// algorithm, this difference is still too significant,
|
||
// maybe it's time to upgrade that 200MHz CPU if you
|
||
// want fast boot!
|
||
//
|
||
if ((CurrentTicks.QuadPart - LastRead.QuadPart) > 0x1000) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// We already had one decreasing read, looser!
|
||
//
|
||
} else {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Is the timer really ticking? In practice it is virtually
|
||
// impossible to read the timer so quickly that you get the same
|
||
// answer twice, but in theory it should be possible, so to avoid
|
||
// the possibility of getting stuck in this loop for all eternity
|
||
// we will permit this condition to occur one thousand times
|
||
// before we give up
|
||
//
|
||
if (CurrentTicks.QuadPart == LastRead.QuadPart) ZeroElapsedTickReads++;
|
||
if (ZeroElapsedTickReads > 1000) {
|
||
return FALSE;
|
||
}
|
||
|
||
LastRead = CurrentTicks;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static BOOLEAN SpecialStallSuccess = TRUE;
|
||
|
||
#define TSC 0x10
|
||
|
||
LONGLONG ReadCycleCounter(VOID) { _asm { rdtsc } }
|
||
|
||
#define TIMER_ROUNDING 10000
|
||
#define __1MHz 1000000
|
||
|
||
|
||
BOOLEAN
|
||
HalpPmTimerScaleTimers(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determines the frequency of the APIC timer, this routine is run
|
||
during initialization
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG Flags;
|
||
ULONG ReadBack;
|
||
PHALPCR HalPCR;
|
||
PKPCR pPCR;
|
||
ULONG ApicHz;
|
||
ULONGLONG TscHz;
|
||
ULONG RoundApicHz;
|
||
ULONGLONG RoundTscHz;
|
||
ULONGLONG RoundTscMhz;
|
||
|
||
//
|
||
// If we ever failed before, don't bother wasting any more time
|
||
//
|
||
if (!SpecialStallSuccess) {
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Don't interrupt us!
|
||
//
|
||
_asm {
|
||
pushfd
|
||
pop eax
|
||
mov Flags, eax
|
||
cli
|
||
}
|
||
|
||
pPCR = KeGetPcr();
|
||
HalPCR = (PHALPCR)(KeGetPcr()->HalReserved);
|
||
|
||
//
|
||
// Configure APIC timer
|
||
//
|
||
pLocalApic[LU_TIMER_VECTOR / 4] = INTERRUPT_MASKED |
|
||
PERIODIC_TIMER | APIC_PROFILE_VECTOR;
|
||
pLocalApic[LU_DIVIDER_CONFIG / 4] = LU_DIVIDE_BY_1;
|
||
|
||
//
|
||
// Make sure the write has happened ???
|
||
//
|
||
ReadBack = pLocalApic[LU_DIVIDER_CONFIG / 4];
|
||
|
||
//
|
||
// Zero the perf counter
|
||
//
|
||
HalPCR->PerfCounterLow = 0;
|
||
HalPCR->PerfCounterHigh = 0;
|
||
|
||
//
|
||
// Fence ???
|
||
//
|
||
_asm { xor eax, eax
|
||
cpuid }
|
||
|
||
//
|
||
// Reset APIC counter and TSC
|
||
//
|
||
pLocalApic[LU_INITIAL_COUNT / 4] = (ULONG)-1;
|
||
WRMSR(TSC, 0);
|
||
|
||
//
|
||
// Stall for an eigth second
|
||
//
|
||
SpecialStallSuccess = HalpPmTimerSpecialStall(EIGHTH_SECOND_PM_TICKS);
|
||
|
||
if (SpecialStallSuccess) {
|
||
|
||
//
|
||
// Read/compute APIC clock and TSC Frequencies (ticks * 8)
|
||
//
|
||
TscHz = ReadCycleCounter() * 8;
|
||
ApicHz = ((ULONG)-1 - pLocalApic[LU_CURRENT_COUNT / 4]) * 8;
|
||
|
||
//
|
||
// Round APIC frequency
|
||
//
|
||
RoundApicHz = ((ApicHz + (TIMER_ROUNDING / 2)) / TIMER_ROUNDING) *
|
||
TIMER_ROUNDING;
|
||
|
||
HalPCR->ApicClockFreqHz = RoundApicHz;
|
||
|
||
//
|
||
// Round TSC frequency
|
||
//
|
||
RoundTscHz = ((TscHz + (TIMER_ROUNDING / 2)) / TIMER_ROUNDING) *
|
||
TIMER_ROUNDING;
|
||
HalPCR->TSCHz = (ULONG)RoundTscHz; // ASSERT(RoundTscHz < __4GHz);
|
||
|
||
//
|
||
// Convert TSC frequency to MHz
|
||
//
|
||
RoundTscMhz = (RoundTscHz + (__1MHz / 2)) / __1MHz;
|
||
pPCR->StallScaleFactor = (ULONG)RoundTscMhz;
|
||
|
||
HalPCR->ProfileCountDown = RoundApicHz;
|
||
pLocalApic[LU_INITIAL_COUNT / 4] = RoundApicHz;
|
||
}
|
||
|
||
//
|
||
// Restore interrupt state-- can this be done without _asm ???
|
||
//
|
||
_asm {
|
||
mov eax, Flags
|
||
push eax
|
||
popfd
|
||
}
|
||
|
||
return SpecialStallSuccess;
|
||
}
|
||
|
||
#endif // SPEEDY_BOOT
|
||
|
||
#ifndef NO_PM_KEQPC
|
||
|
||
static ULONG PIT_Ticks = 0xBADCEEDE;
|
||
|
||
VOID
|
||
HalpAcpiTimerPerfCountHack(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Some cheezy PIC-based laptops seemed to have wired their ACPI timer to
|
||
the wrong frequecy crystal, and their perf counter freq is twice what
|
||
it should be. These systems seem to run fine in every other way
|
||
except for midi file playback, or anything else that goes by KeQuery-
|
||
PerformanceCounter's return frequency value, so we perform a simple
|
||
check late during init to see if this clock is twice what we expect,
|
||
and if it is we return twice the ACPI frequency in KeQuery...
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG T0_Count = 0;
|
||
ULONG T1_Count = 1;
|
||
ULONG Retry = 10;
|
||
|
||
//
|
||
// If we happen to hit the rollover just do it again
|
||
//
|
||
while ((T0_Count < T1_Count) && (Retry--)) {
|
||
T0_Count = HalpQuery8254Counter();
|
||
KeStallExecutionProcessor(1000);
|
||
T1_Count = HalpQuery8254Counter();
|
||
}
|
||
|
||
if (T0_Count < T1_Count) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// We should have read ~1200 ticks during this interval, so if we
|
||
// recorded between 575 and 725 we can reasonably assume the ACPI
|
||
// Timer is running at 2 * spec
|
||
//
|
||
PIT_Ticks = T0_Count - T1_Count;
|
||
if ((PIT_Ticks < 725) && (PIT_Ticks > 575)) {
|
||
PMTimerFreq = 2 * PM_TMR_FREQ;
|
||
HalpAdjustUpperBoundTable2X();
|
||
}
|
||
}
|
||
|
||
#endif // NO_PM_KEQPC
|