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
|