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.
668 lines
15 KiB
668 lines
15 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
mmtimer.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the HAL's multimedia event timer support
|
|
|
|
Author:
|
|
|
|
Eric Nelson (enelson) July 7, 2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "halp.h"
|
|
#include "acpitabl.h"
|
|
#include "mmtimer.h"
|
|
#include "xxtimer.h"
|
|
|
|
//
|
|
// Event timer block context
|
|
//
|
|
static ETB_CONTEXT ETBContext = { 0, // Number of event timers
|
|
NULL, // VA of event timer block
|
|
{ 0, 0 }, // PA of event timer block
|
|
100, // Clock period in nanoseconds
|
|
100, // System clock frequency in Hz
|
|
100000, // System clock period in ticks
|
|
FALSE, // Multi media HW initialized?
|
|
FALSE }; // Change system clock frequency?
|
|
|
|
//
|
|
// Event timer block registers address usage
|
|
//
|
|
static ADDRESS_USAGE HalpmmTimerResource = {
|
|
NULL, CmResourceTypeMemory, DeviceUsage, { 0, 0x400, 0, 0 }
|
|
};
|
|
|
|
//
|
|
// Offset is the difference between the multi media timer HW's main
|
|
// 32-bit counter register and the HAL's 64-bit software PerfCount:
|
|
//
|
|
// ASSERT(PerfCount == ETBContext.EventTimer->MainCounter + Offset);
|
|
//
|
|
static LONGLONG Offset = 0;
|
|
static ULONGLONG PerfCount = 0;
|
|
|
|
#define HAL_PRIMARY_PROCESSOR 0
|
|
#define MAX_ULONG 0xFFFFFFFF
|
|
#define __4GB 0x100000000
|
|
|
|
#define __1MHz 1000000
|
|
#define __10MHz 10000000
|
|
#define __1GHz 1000000000
|
|
|
|
#define HALF(n) ((n) / 2)
|
|
|
|
#if DBG || MMTIMER_DEV
|
|
static ULONG CounterReads = 0;
|
|
#endif
|
|
|
|
#define MIN_LOOP_QUANTUM 1
|
|
static ULONG MinLoopCount = MIN_LOOP_QUANTUM;
|
|
static UCHAR StallCount = 0;
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT, HalpmmTimer)
|
|
#pragma alloc_text(INIT, HalpmmTimerInit)
|
|
#pragma alloc_text(INIT, HalpmmTimerClockInit)
|
|
#pragma alloc_text(INIT, HalpmmTimerCalibratePerfCount)
|
|
#endif
|
|
|
|
|
|
BOOLEAN
|
|
HalpmmTimer(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to determine if multi media timer HW is
|
|
present, and has been initialized
|
|
|
|
note: this routine should only used during HAL init
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE if the multi media timer HW is present, and has been initialized
|
|
|
|
--*/
|
|
{
|
|
return ETBContext.Initialized;
|
|
}
|
|
|
|
|
|
ULONG
|
|
HalpmmTimerSetTimeIncrement(
|
|
IN ULONG DesiredIncrement
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initialize system time clock to generate an
|
|
interrupt at every DesiredIncrement interval
|
|
|
|
Arguments:
|
|
|
|
DesiredIncrement - Desired interval between every timer tick (in
|
|
100ns unit)
|
|
|
|
Return Value:
|
|
|
|
The *REAL* time increment set
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// For starters we will only support a default system clock
|
|
// frequency of 10ms
|
|
//
|
|
// 100ns = 1/10MHz, and (1/SysClock) / (1/10MHz) == 10MHz/SysClock, .:.
|
|
//
|
|
return __10MHz / ETBContext.SystemClockFrequency;
|
|
}
|
|
|
|
|
|
VOID
|
|
HalpmmTimerClockInit(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the system clock using the multi media event
|
|
timer to generate an interrupt every 10ms
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
ULONG MinSysClockFreq;
|
|
ULONG MaxSysClockFreq;
|
|
ETB_GEN_CONF GenConf;
|
|
ETB_CONF_CAPS mmT0ConfCaps;
|
|
|
|
//
|
|
// Reset the main counter and its associated performance variables
|
|
// to 0, nobody should be using them this early
|
|
//
|
|
GenConf.AsULONG = ETBContext.EventTimer->GeneralConfig;
|
|
GenConf.GlobalIRQEnable = OFF;
|
|
ETBContext.EventTimer->GeneralConfig = GenConf.AsULONG;
|
|
ETBContext.EventTimer->MainCounter = 0;
|
|
Offset = 0;
|
|
PerfCount = 0;
|
|
|
|
//
|
|
// Initialize multi media context for a default system clock
|
|
// freuqency of 100Hz, with a period of 10ms
|
|
//
|
|
ETBContext.SystemClockFrequency = 100;
|
|
ETBContext.SystemClockTicks = __1GHz /
|
|
(ETBContext.SystemClockFrequency * ETBContext.ClockPeriod);
|
|
|
|
//
|
|
// Setup timer 0 for periodc mode
|
|
//
|
|
mmT0ConfCaps.AsULONG =
|
|
ETBContext.EventTimer->mmTimer[0].ConfigCapabilities;
|
|
|
|
ASSERT(mmT0ConfCaps.PeriodicCapable == ON);
|
|
|
|
mmT0ConfCaps.ValueSetConfig = ON;
|
|
mmT0ConfCaps.IRQEnable = ON;
|
|
mmT0ConfCaps.PeriodicModeEnable = ON;
|
|
ETBContext.EventTimer->mmTimer[0].ConfigCapabilities =
|
|
mmT0ConfCaps.AsULONG;
|
|
|
|
//
|
|
// Set comparator to the desired system clock frequency
|
|
//
|
|
ETBContext.EventTimer->mmTimer[0].Comparator = ETBContext.SystemClockTicks;
|
|
|
|
//
|
|
// Fire up the main counter
|
|
//
|
|
GenConf.AsULONG = ETBContext.EventTimer->GeneralConfig;
|
|
GenConf.GlobalIRQEnable = ON;
|
|
ETBContext.EventTimer->GeneralConfig = GenConf.AsULONG;
|
|
|
|
//
|
|
// Inform kernel of our supported system clock frequency range in
|
|
// 100ns units, but for starters we will only support 10ms default
|
|
//
|
|
MinSysClockFreq = __10MHz / ETBContext.SystemClockFrequency;
|
|
MaxSysClockFreq = MinSysClockFreq;
|
|
#ifndef MMTIMER_DEV
|
|
KeSetTimeIncrement(MinSysClockFreq, MaxSysClockFreq);
|
|
#endif
|
|
}
|
|
|
|
#ifdef MMTIMER_DEV
|
|
static ULONG HalpmmTimerClockInts = 0;
|
|
#endif
|
|
|
|
|
|
VOID
|
|
HalpmmTimerClockInterrupt(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is entered as the result of an interrupt generated by
|
|
CLOCK, update our performance count and change system clock frequency
|
|
if necessary
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Update PerfCount
|
|
//
|
|
PerfCount += ETBContext.SystemClockTicks;
|
|
|
|
//
|
|
// If the 32-bit counter has wrapped, update Offset accordingly
|
|
//
|
|
if (PerfCount - Offset > MAX_ULONG) {
|
|
Offset += __4GB;
|
|
}
|
|
|
|
#ifdef MMTIMER_DEV
|
|
HalpmmTimerClockInts++;
|
|
#endif
|
|
|
|
//
|
|
// Check if a new frequency has been requested
|
|
//
|
|
if (ETBContext.NewClockFrequency) {
|
|
|
|
//
|
|
// ???
|
|
//
|
|
|
|
ETBContext.NewClockFrequency = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
HalpmmTimerInit(
|
|
IN ULONG EventTimerBlockID,
|
|
IN ULONG BaseAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the multimedia event timer
|
|
|
|
Arguments:
|
|
|
|
EventTimerBlockID - Various bits of info, including number of Event
|
|
Timers
|
|
|
|
BaseAddress - Physical Base Address of 1st Event Timer Block
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
ULONG i;
|
|
ETB_GEN_CONF GenConf;
|
|
ETB_GEN_CAP_ID GenCaps;
|
|
PHYSICAL_ADDRESS PhysAddr;
|
|
PEVENT_TIMER_BLOCK EventTimer;
|
|
|
|
TIMER_FUNCTIONS TimerFunctions = { HalpmmTimerStallExecProc,
|
|
HalpmmTimerCalibratePerfCount,
|
|
HalpmmTimerQueryPerfCount,
|
|
HalpmmTimerSetTimeIncrement };
|
|
|
|
#if MMTIMER_DEV && PICACPI
|
|
{
|
|
UCHAR Data;
|
|
|
|
//
|
|
// (BUGBUG!) BIOS should enable the device
|
|
//
|
|
Data = 0x87;
|
|
HalpPhase0SetPciDataByOffset(0,
|
|
9,
|
|
&Data,
|
|
4,
|
|
sizeof(Data));
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Establish VA for Multimedia Timer HW Base Address
|
|
//
|
|
PhysAddr.QuadPart = BaseAddress;
|
|
EventTimer = HalpMapPhysicalMemoryWriteThrough(PhysAddr, 1);
|
|
|
|
//
|
|
// Register address usage
|
|
//
|
|
HalpmmTimerResource.Element[0].Start = BaseAddress;
|
|
HalpRegisterAddressUsage(&HalpmmTimerResource);
|
|
|
|
//
|
|
// Read the General Capabilities and ID Register
|
|
//
|
|
GenCaps.AsULONG = EventTimer->GeneralCapabilities;
|
|
|
|
//
|
|
// Save context
|
|
//
|
|
ETBContext.TimerCount = GenCaps.TimerCount + 1; // Convert from zero-based
|
|
ETBContext.BaseAddress.QuadPart = BaseAddress;
|
|
ETBContext.EventTimer = EventTimer;
|
|
ETBContext.NewClockFrequency = FALSE;
|
|
|
|
//
|
|
// Save clock period as nanoseconds, convert from femptoseconds so
|
|
// we don't have to worry about nasty overflow
|
|
//
|
|
#ifndef MMTIMER_DEV
|
|
ETBContext.ClockPeriod = EventTimer->ClockPeriod / __1MHz;
|
|
#else
|
|
ETBContext.ClockPeriod = 100; // Proto HW is 10MHz, with a period of 100ns
|
|
#endif
|
|
|
|
//
|
|
// Reset the main counter and its associated performance counter
|
|
// variables
|
|
//
|
|
GenConf.AsULONG = EventTimer->GeneralConfig;
|
|
GenConf.GlobalIRQEnable = ON;
|
|
//GenConf.LegacyIRQRouteEnable = ON;
|
|
EventTimer->MainCounter = 0;
|
|
Offset = 0;
|
|
PerfCount = 0;
|
|
EventTimer->GeneralConfig = GenConf.AsULONG;
|
|
|
|
//
|
|
// Set HAL timer functions to use Multimedia Timer HW
|
|
//
|
|
HalpSetTimerFunctions(&TimerFunctions);
|
|
|
|
ETBContext.Initialized = TRUE;
|
|
}
|
|
|
|
|
|
//ULONG
|
|
//HalpmmTimerTicks(
|
|
// IN ULONG StartCount,
|
|
// IN ULONG EndCount
|
|
// )
|
|
///*++
|
|
//
|
|
//Routine Description:
|
|
//
|
|
// Calculate the difference in ticks between StartCount and EndCount
|
|
// taking into consideraton counter rollover
|
|
//
|
|
//Arguments:
|
|
//
|
|
// StartCount - Value of main counter at time t0
|
|
//
|
|
// EndCount - Value of main counter at end time t1
|
|
//
|
|
//Return Value:
|
|
//
|
|
// Returns the positive number of ticks which have elapsed between time
|
|
// t0, and t1
|
|
//
|
|
//--*/
|
|
//
|
|
#define HalpmmTimerTicks(StartCount, EndCount) (((EndCount) >= (StartCount)) ? (EndCount) - (StartCount): (EndCount) + (MAX_ULONG - (StartCount)) + 1)
|
|
|
|
#define WHACK_HIGH_DIFF 0xFFFF0000
|
|
#define ULONG_BITS 32
|
|
|
|
|
|
VOID
|
|
HalpmmTimerStallExecProc(
|
|
IN ULONG MicroSeconds
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function stalls execution for the specified number of microseconds
|
|
|
|
Arguments:
|
|
|
|
MicroSeconds - Supplies the number of microseconds that execution is to be
|
|
stalled
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
ULONG i;
|
|
#ifndef i386
|
|
ULONG j;
|
|
ULONG Mirror;
|
|
#endif
|
|
ULONG EndCount;
|
|
ULONG StartCount;
|
|
ULONG TargetTicks;
|
|
ULONG ElapsedTicks;
|
|
ULONG CyclesStalled;
|
|
ULONG TicksPerMicroSec;
|
|
|
|
ElapsedTicks = 0;
|
|
CyclesStalled = 0;
|
|
#if DBG || MMTIMER_DEV
|
|
CounterReads = 0;
|
|
#endif
|
|
|
|
TicksPerMicroSec = 1000 / ETBContext.ClockPeriod;
|
|
TargetTicks = MicroSeconds * TicksPerMicroSec;
|
|
StartCount = ETBContext.EventTimer->MainCounter;
|
|
|
|
//
|
|
// BIAS: We've stalled for .5us already!
|
|
//
|
|
TargetTicks -= HALF(TicksPerMicroSec);
|
|
|
|
//
|
|
// Get a warm fuzzy for what it's like to stall for more than .5us
|
|
//
|
|
while (TRUE) {
|
|
|
|
#ifdef i386
|
|
_asm { rep nop }
|
|
#endif
|
|
|
|
i = MinLoopCount;
|
|
CyclesStalled += i;
|
|
|
|
while (i--) {
|
|
#ifdef i386
|
|
_asm {
|
|
xor eax, eax
|
|
cpuid
|
|
}
|
|
#else
|
|
Mirror = 0;
|
|
for (j = 0; j < ULONG_BITS; j++) {
|
|
Mirror <<= 1;
|
|
Mirror |= EndCount & 1;
|
|
EndCount >>= 1;
|
|
}
|
|
EndCount = Mirror;
|
|
#endif // i386
|
|
}
|
|
|
|
EndCount = ETBContext.EventTimer->MainCounter;
|
|
#if DBG || MMTIMER_DEV
|
|
CounterReads++;
|
|
#endif
|
|
ElapsedTicks = HalpmmTimerTicks(StartCount, EndCount);
|
|
|
|
if (ElapsedTicks >= HALF(TicksPerMicroSec)) {
|
|
break;
|
|
}
|
|
|
|
MinLoopCount += MIN_LOOP_QUANTUM;
|
|
}
|
|
|
|
#ifdef MMTIMER_DEV
|
|
//
|
|
// Something is whack, probably time went backwards! Act as if we
|
|
// hit our target of .5us and reset StartCount to the current value
|
|
// less ElapsedTicks
|
|
//
|
|
if (ElapsedTicks > WHACK_HIGH_DIFF) {
|
|
ElapsedTicks = HALF(TicksPerMicroSec);
|
|
StartCount = EndCount - ElapsedTicks;
|
|
}
|
|
#endif // MMTIMER_DEV
|
|
|
|
//
|
|
// Now that we have a warm fuzzy, try to approximate a workload that
|
|
// will keep us busy for the remainder of microsoeconds
|
|
//
|
|
while (TargetTicks > ElapsedTicks) {
|
|
|
|
#ifdef i386
|
|
_asm { rep nop }
|
|
#endif
|
|
|
|
i = (TargetTicks - ElapsedTicks) * CyclesStalled / ElapsedTicks;
|
|
CyclesStalled += i;
|
|
|
|
while (i--) {
|
|
#ifdef i386
|
|
_asm {
|
|
xor eax, eax
|
|
cpuid
|
|
}
|
|
#else
|
|
Mirror = 0;
|
|
for (j = 0; j < ULONG_BITS; j++) {
|
|
Mirror <<= 1;
|
|
Mirror |= EndCount & 1;
|
|
EndCount >>= 1;
|
|
}
|
|
EndCount = Mirror;
|
|
#endif // i386
|
|
}
|
|
|
|
EndCount = ETBContext.EventTimer->MainCounter;
|
|
#if DBG || MMTIMER_DEV
|
|
CounterReads++;
|
|
#endif
|
|
ElapsedTicks = HalpmmTimerTicks(StartCount, EndCount);
|
|
}
|
|
|
|
//
|
|
// Decrement MinimumLoopCount every 0x100 calls so we don't accidentally
|
|
// wind up stalling for longer periods
|
|
//
|
|
StallCount++;
|
|
if ((StallCount == 0) && (MinLoopCount > MIN_LOOP_QUANTUM)) {
|
|
MinLoopCount -= MIN_LOOP_QUANTUM;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
HalpmmTimerCalibratePerfCount(
|
|
IN LONG volatile *Number,
|
|
IN ULONGLONG NewCount
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine resets the performance counter value for the current
|
|
processor to zero, the reset is done such that the resulting value
|
|
is closely synchronized with other processors in the configuration
|
|
|
|
Arguments:
|
|
|
|
Number - Supplies a pointer to count of the number of processors in
|
|
the configuration
|
|
|
|
NewCount - Supplies the value to synchronize the counter too
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
ULONG MainCount;
|
|
|
|
//
|
|
// If this isn't the primary processor, then return
|
|
//
|
|
if (KeGetCurrentPrcb()->Number != HAL_PRIMARY_PROCESSOR) {
|
|
return;
|
|
}
|
|
|
|
MainCount = ETBContext.EventTimer->MainCounter;
|
|
|
|
PerfCount = NewCount;
|
|
|
|
Offset = PerfCount - MainCount;
|
|
}
|
|
|
|
|
|
LARGE_INTEGER
|
|
HalpmmTimerQueryPerfCount(
|
|
OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns current 64-bit performance counter and,
|
|
optionally, the Performance Frequency
|
|
|
|
N.B. The performace counter returned by this routine is
|
|
not necessary the value when this routine is just entered,
|
|
The value returned is actually the counter value at any point
|
|
between the routine is entered and is exited
|
|
|
|
Arguments:
|
|
|
|
PerformanceFrequency - optionally, supplies the address of a
|
|
variable to receive the performance counter
|
|
frequency
|
|
|
|
Return Value:
|
|
|
|
Current value of the performance counter will be returned
|
|
|
|
--*/
|
|
{
|
|
ULONG MainCount;
|
|
LARGE_INTEGER li;
|
|
|
|
//
|
|
// Clock period is in nanoseconds, help the calculation remain
|
|
// integer by asserting multi media HW clock frequency is between
|
|
// 1MHz and 1GHz, with a period between 1ns and 1Kns, seems
|
|
// reasonable to me?
|
|
//
|
|
if (PerformanceFrequency) {
|
|
|
|
ASSERT((ETBContext.ClockPeriod > 0) &&
|
|
(ETBContext.ClockPeriod <= 1000));
|
|
|
|
PerformanceFrequency->QuadPart =
|
|
(1000 / ETBContext.ClockPeriod) * __1MHz;
|
|
}
|
|
|
|
//
|
|
// Read main counter
|
|
//
|
|
MainCount = ETBContext.EventTimer->MainCounter;
|
|
|
|
//
|
|
// Check if our 32-bit counter has wrapped since we took our last
|
|
// clock tick
|
|
//
|
|
li.QuadPart = (PerfCount - Offset > MainCount) ?
|
|
Offset + __4GB + MainCount:
|
|
MainCount + Offset;
|
|
return li;
|
|
}
|