669 lines
15 KiB
C
669 lines
15 KiB
C
/*++
|
||
|
||
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 (KeGetPcr()->Prcb->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;
|
||
}
|