NT4/private/ntos/nthals/halalpha/inithal.c
2020-09-30 17:12:29 +02:00

1053 lines
25 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1991 Microsoft Corporation
Copyright (c) 1992, 1993 Digital Equipment Corporation
Module Name:
inithal.c
Abstract:
This module implements the initialization of the system dependent
functions that define the Hardware Architecture Layer (HAL) for an
Alpha machine
Author:
David N. Cutler (davec) 25-Apr-1991
Miche Baker-Harvey (miche) 18-May-1992
Environment:
Kernel mode only.
Revision History:
28-Jul-1992 Jeff McLeman (mcleman)
Add code to allocate a mapping buffer for buffered DMA
14-Jul-1992 Jeff McLeman (mcleman)
Add call to HalpCachePcrValues, which will call the PALcode to
cache values of the PCR that need fast access.
10-Jul-1992 Jeff McLeman (mcleman)
Remove reference to initializing the fixed TB entries, since Alpha
does not have fixed TB entries.
24-Sep-1993 Joe Notarangelo
Restructured to make this module platform-independent.
--*/
#include "halp.h"
#include "eisa.h"
//
// Declare the extern variable UncorrectableError declared in
// inithal.c.
//
PERROR_FRAME PUncorrectableError;
//
// external
//
ULONG HalDisablePCIParityChecking = 0xffffffff;
//
// Define HAL spinlocks.
//
KSPIN_LOCK HalpBeepLock;
KSPIN_LOCK HalpDisplayAdapterLock;
KSPIN_LOCK HalpSystemInterruptLock;
//
// Mask of all of the processors that are currently active.
//
KAFFINITY HalpActiveProcessors;
//
// Mapping of the logical processor numbers to the physical processors.
//
ULONG HalpLogicalToPhysicalProcessor[HAL_MAXIMUM_PROCESSOR+1];
ULONG AlreadySet = 0;
//
// HalpClockFrequency is the processor cycle counter frequency in units
// of cycles per second (Hertz). It is a large number (e.g., 125,000,000)
// but will still fit in a ULONG.
//
// HalpClockMegaHertz is the processor cycle counter frequency in units
// of megahertz. It is a small number (e.g., 125) and is also the number
// of cycles per microsecond. The assumption here is that clock rates will
// always be an integral number of megahertz.
//
// Having the frequency available in both units avoids multiplications, or
// especially divisions in time critical code.
//
ULONG HalpClockFrequency;
ULONG HalpClockMegaHertz = DEFAULT_PROCESSOR_FREQUENCY_MHZ;
ULONGLONG HalpContiguousPhysicalMemorySize;
//
// Use the square wave mode of the PIT to measure the processor
// speed. The timer has a frequency of 1.193MHz. We want a
// square wave with a period of 50ms so we must initialize the
// pit with a count of:
// 50ms*1.193MHz = 59650 cycles
//
#define TIMER_REF_VALUE 59650
VOID
HalpVerifyPrcbVersion(
VOID
);
VOID
HalpRecurseLoaderBlock(
IN PCONFIGURATION_COMPONENT_DATA CurrentEntry
);
ULONG
HalpQuerySystemFrequency(
ULONG SampleTime
);
VOID
HalpAllocateUncorrectableFrame(
VOID
);
BOOLEAN
HalInitSystem (
IN ULONG Phase,
IN PLOADER_PARAMETER_BLOCK LoaderBlock
)
/*++
Routine Description:
This function initializes the Hardware Architecture Layer (HAL) for an
Alpha system.
Arguments:
Phase - Supplies the initialization phase (zero or one).
LoaderBlock - Supplies a pointer to a loader parameter block.
Return Value:
A value of TRUE is returned is the initialization was successfully
complete. Otherwise a value of FALSE is returend.
--*/
{
PKPRCB Prcb;
ULONG BuildType = 0;
Prcb = PCR->Prcb;
//
// Perform initialization for the primary processor.
//
if( Prcb->Number == HAL_PRIMARY_PROCESSOR ){
if (Phase == 0) {
#if HALDBG
DbgPrint( "HAL/HalInitSystem: Phase = %d\n", Phase );
DbgBreakPoint();
HalpDumpMemoryDescriptors( LoaderBlock );
#endif //HALDBG
//
// Get the memory Size.
//
HalpContiguousPhysicalMemorySize =
HalpGetContiguousMemorySize( LoaderBlock );
//
// Set second level cache size
// NOTE: Although we set the PCR with the right cache size this
// could be overridden by setting the Registry key
// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet
// \Control\Session Manager
// \Memory Management\SecondLevelDataCache.
//
// If the secondlevel cache size is 0 or 512KB then it is
// possible that the firmware is an old one. In which case
// we determine the cache size here. If the value is anything
// other than these then it is a new firmware and probably
// reporting the correct cache size hence use this value.
//
if(LoaderBlock->u.Alpha.SecondLevelDcacheSize == 0 ||
LoaderBlock->u.Alpha.SecondLevelDcacheSize == 512*__1K){
PCR->SecondLevelCacheSize = HalpGetBCacheSize(
HalpContiguousPhysicalMemorySize
);
} else {
PCR->SecondLevelCacheSize =
LoaderBlock->u.Alpha.SecondLevelDcacheSize;
}
//
// Initialize HAL spinlocks.
//
KeInitializeSpinLock(&HalpBeepLock);
KeInitializeSpinLock(&HalpDisplayAdapterLock);
KeInitializeSpinLock(&HalpSystemInterruptLock);
//
// Fill in handlers for APIs which this HAL supports
//
HalQuerySystemInformation = HaliQuerySystemInformation;
HalSetSystemInformation = HaliSetSystemInformation;
//
// Phase 0 initialization.
//
HalpSetTimeIncrement();
HalpMapIoSpace();
HalpEstablishErrorHandler();
HalpInitializeDisplay(LoaderBlock);
HalpInitializeMachineDependent( Phase, LoaderBlock );
HalpCreateDmaStructures(LoaderBlock);
HalpInitializeInterrupts();
HalpVerifyPrcbVersion();
//
// Set the processor active in the HAL active processor mask.
//
HalpActiveProcessors = 1 << Prcb->Number;
//
// Initialize the logical to physical processor mapping
// for the primary processor.
//
HalpLogicalToPhysicalProcessor[0] = HAL_PRIMARY_PROCESSOR;
return TRUE;
} else {
#if HALDBG
DbgPrint( "HAL/HalInitSystem: Phase = %d\n", Phase );
DbgBreakPoint();
#endif //HALDBG
//
// Phase 1 initialization.
//
HalpInitializeClockInterrupts();
HalpInitializeMachineDependent( Phase, LoaderBlock );
//
// Allocate memory for the uncorrectable frame
//
HalpAllocateUncorrectableFrame();
//
// Initialize the Buffer for Uncorrectable Error.
//
HalpInitializeUncorrectableErrorFrame();
return TRUE;
}
}
//
// Perform necessary processor-specific initialization for
// secondary processors. Phase is ignored as this will be called
// only once on each secondary processor.
//
HalpMapIoSpace();
HalpInitializeInterrupts();
HalpInitializeMachineDependent( Phase, LoaderBlock );
//
// Set the processor active in the HAL active processor mask.
//
HalpActiveProcessors |= 1 << Prcb->Number;
#if HALDBG
DbgPrint( "Secondary %d is alive\n", Prcb->Number );
#endif //HALDBG
return TRUE;
}
VOID
HalpAllocateUncorrectableFrame(
VOID
)
/*++
Routine Description:
This function is called after the Phase1 Machine Dependent initialization.
It must be called only after Phase1 machine-dependent initialization.
This function allocates the necessary amountof memory for storing the
uncorrectable error frame. This function makes a call to a machine-
dependent function 'HalpGetMachineDependentErrorFrameSizes' for
getting the size of the Processor Specific and System Specific error
frame size. The machine-dependent code will know the size of these
frames after the machine-dependent Phase1 initialization.
Arguments:
None.
Return Value:
None.
--*/
{
ULONG RawProcessorFrameSize;
ULONG RawSystemFrameSize;
ULONG EntireErrorFrameSize;
//
// First get the machine-dependent error frame sizes.
//
HalpGetMachineDependentErrorFrameSizes(
&RawProcessorFrameSize,
&RawSystemFrameSize);
//
// Compute the total size of the error frame
//
EntireErrorFrameSize = sizeof(ERROR_FRAME) + RawProcessorFrameSize +
RawSystemFrameSize;
//
// Allocate space to store the error frame.
// Not sure if it is OK to use ExAllocatePool at this instant.
// We will give this a try if it doesn't work What do we do??!!
//
PUncorrectableError = ExAllocatePool(NonPagedPool,
EntireErrorFrameSize);
if(PUncorrectableError == NULL) {
return;
}
PUncorrectableError->LengthOfEntireErrorFrame = EntireErrorFrameSize;
//
// if the size is not equal to zero then set the RawInformation pointers
// to point to the right place. If not set the pointer to NULL and set
// size to 0.
//
//
// make Raw processor info to point right after the error frame.
//
if(RawProcessorFrameSize) {
PUncorrectableError->UncorrectableFrame.RawProcessorInformation =
(PVOID)((PUCHAR)PUncorrectableError + sizeof(ERROR_FRAME) );
PUncorrectableError->UncorrectableFrame.RawProcessorInformationLength =
RawProcessorFrameSize;
}
else{
PUncorrectableError->UncorrectableFrame.RawProcessorInformation =
NULL;
PUncorrectableError->UncorrectableFrame.RawProcessorInformationLength =
0;
}
if(RawSystemFrameSize){
PUncorrectableError->UncorrectableFrame.RawSystemInformation =
(PVOID)((PUCHAR)PUncorrectableError->UncorrectableFrame.
RawProcessorInformation + RawProcessorFrameSize);
PUncorrectableError->UncorrectableFrame.RawSystemInformationLength =
RawSystemFrameSize;
}
else{
PUncorrectableError->UncorrectableFrame.RawSystemInformation =
NULL;
PUncorrectableError->UncorrectableFrame.RawSystemInformationLength =
0;
}
}
VOID
HalpGetProcessorInfo(
PPROCESSOR_INFO pProcessorInfo
)
/*++
Routine Description:
Collects the Processor Information and fills in the buffer.
Arguments:
pProcessorInfo - Pointer to the PROCESSOR_INFO structure into which
the processor information will be filled in.
Return Value:
None.
--*/
{
PKPRCB Prcb;
pProcessorInfo->ProcessorType = PCR->ProcessorType;
pProcessorInfo->ProcessorRevision = PCR->ProcessorRevision;
Prcb = PCR->Prcb;
pProcessorInfo->LogicalProcessorNumber = Prcb->Number;
pProcessorInfo->PhysicalProcessorNumber =
HalpLogicalToPhysicalProcessor[Prcb->Number];
return;
}
VOID
HalInitializeProcessor (
IN ULONG Number
)
/*++
Routine Description:
This function is called early in the initialization of the kernel
to perform platform dependent initialization for each processor
before the HAL Is fully functional.
N.B. When this routine is called, the PCR is present but is not
fully initialized.
Arguments:
Number - Supplies the number of the processor to initialize.
Return Value:
None.
--*/
{
//jnfix - for mp systems if processor != 0 then init PALcode?
//either here or in next module?
return;
}
BOOLEAN
HalStartNextProcessor (
IN PLOADER_PARAMETER_BLOCK LoaderBlock,
IN PKPROCESSOR_STATE ProcessorState
)
/*++
Routine Description:
This function is called to start the next processor.
Arguments:
LoaderBlock - Supplies a pointer to the loader parameter block.
ProcessorState - Supplies a pointer to the processor state to be
used to start the processor.
Return Value:
If a processor is successfully started, then a value of TRUE is
returned. Otherwise a value of FALSE is returned. If a value of
TRUE is returned, then the logical processor number is stored
in the processor control block specified by the loader block.
--*/
{
ULONG LogicalNumber;
PRESTART_BLOCK NextRestartBlock;
ULONG PhysicalNumber;
PKPRCB Prcb;
#if !defined(NT_UP)
//
// If the address of the first restart parameter block is NULL, then
// the host system is a uniprocessor system running with old firmware.
// Otherwise, the host system may be a multiprocessor system if more
// than one restart block is present.
//
// N.B. The first restart parameter block must be for the boot master
// and must represent logical processor 0.
//
NextRestartBlock = SYSTEM_BLOCK->RestartBlock;
if (NextRestartBlock == NULL) {
return FALSE;
}
//
// Scan the restart parameter blocks for a processor that is ready,
// but not running. If a processor is found, then fill in the restart
// processor state, set the logical processor number, and set start
// in the boot status.
//
LogicalNumber = 0;
PhysicalNumber = 0;
do {
//
// If the processor is not ready then we assume that it is not
// present. We must increment the physical processor number but
// the logical processor number does not changed.
//
if( NextRestartBlock->BootStatus.ProcessorReady == FALSE ){
PhysicalNumber += 1;
} else {
//
// Check if this processor has already been started.
// If it has not then start it now.
//
if( NextRestartBlock->BootStatus.ProcessorStart == FALSE ){
RtlZeroMemory( &NextRestartBlock->u.Alpha,
sizeof(ALPHA_RESTART_STATE));
NextRestartBlock->u.Alpha.IntA0 =
ProcessorState->ContextFrame.IntA0;
NextRestartBlock->u.Alpha.IntSp =
ProcessorState->ContextFrame.IntSp;
NextRestartBlock->u.Alpha.ReiRestartAddress =
(ULONG)ProcessorState->ContextFrame.Fir;
Prcb = (PKPRCB)(LoaderBlock->Prcb);
Prcb->Number = (CCHAR)LogicalNumber;
Prcb->RestartBlock = NextRestartBlock;
NextRestartBlock->BootStatus.ProcessorStart = 1;
HalpLogicalToPhysicalProcessor[LogicalNumber] = PhysicalNumber;
return TRUE;
} else {
//
// Ensure that the logical to physical mapping has been
// established for this processor.
//
HalpLogicalToPhysicalProcessor[LogicalNumber] = PhysicalNumber;
}
LogicalNumber += 1;
PhysicalNumber += 1;
}
NextRestartBlock = NextRestartBlock->NextRestartBlock;
} while (NextRestartBlock != NULL);
#endif // !defined(NT_UP)
return FALSE;
}
VOID
HalpVerifyPrcbVersion(
VOID
)
/*++
Routine Description:
This function verifies that the HAL matches the kernel. If there
is a mismatch the HAL bugchecks the system.
Arguments:
None.
Return Value:
None.
--*/
{
PKPRCB Prcb;
//
// Verify Prcb major version number, and build options are
// all conforming to this binary image
//
Prcb = KeGetCurrentPrcb();
#if DBG
if (!(Prcb->BuildType & PRCB_BUILD_DEBUG)) {
// This checked hal requires a checked kernel
KeBugCheckEx (MISMATCHED_HAL, 2, Prcb->BuildType, PRCB_BUILD_DEBUG, 0);
}
#else
if (Prcb->BuildType & PRCB_BUILD_DEBUG) {
// This free hal requires a free kernel
KeBugCheckEx (MISMATCHED_HAL, 2, Prcb->BuildType, 0, 0);
}
#endif
#ifndef NT_UP
if (Prcb->BuildType & PRCB_BUILD_UNIPROCESSOR) {
// This MP hal requires an MP kernel
KeBugCheckEx (MISMATCHED_HAL, 2, Prcb->BuildType, 0, 0);
}
#endif
if (Prcb->MajorVersion != PRCB_MAJOR_VERSION) {
KeBugCheckEx (MISMATCHED_HAL,
1, Prcb->MajorVersion, PRCB_MAJOR_VERSION, 0);
}
}
VOID
HalpParseLoaderBlock(
IN PLOADER_PARAMETER_BLOCK LoaderBlock
)
{
if (LoaderBlock == NULL) {
return;
}
HalpRecurseLoaderBlock( (PCONFIGURATION_COMPONENT_DATA)
LoaderBlock->ConfigurationRoot);
}
VOID
HalpRecurseLoaderBlock(
IN PCONFIGURATION_COMPONENT_DATA CurrentEntry
)
/*++
Routine Description:
This routine parses the loader parameter block looking for the PCI
node. Once found, used to determine if PCI parity checking should be
enabled or disabled. Set the default to not disable checking.
Arguments:
CurrentEntry - Supplies a pointer to a loader configuration
tree or subtree.
Return Value:
None.
--*/
{
PCONFIGURATION_COMPONENT Component;
PWSTR NameString;
//
// Quick out
//
if (AlreadySet) {
return;
}
if (CurrentEntry) {
Component = &CurrentEntry->ComponentEntry;
if (Component->Class == AdapterClass &&
Component->Type == MultiFunctionAdapter) {
if (strcmp(Component->Identifier, "PCI") == 0) {
HalDisablePCIParityChecking = Component->Flags.ConsoleOut;
AlreadySet = TRUE;
#if HALDBG
DbgPrint("ARC tree sets PCI parity checking to %s\n",
HalDisablePCIParityChecking ? "OFF" : "ON");
#endif
return;
}
}
//
// Process all the Siblings of current entry
//
HalpRecurseLoaderBlock(CurrentEntry->Sibling);
//
// Process all the Childeren of current entry
//
HalpRecurseLoaderBlock(CurrentEntry->Child);
}
}
ULONG
HalpQuerySystemFrequency(
ULONG SampleTime
)
/*++
Routine Description:
This routine returns the speed at which the system is running in hertz.
The system frequency is calculated by counting the number of processor
cycles that occur during 500ms, using the Programmable Interval Timer
(PIT) as the reference time. The PIT is used to generate a square
wave with a 50ms Period. We use the Speaker counter since we can
enable and disable the count from software. The output of the
speaker is obtained from the SIO NmiStatus register.
Arguments:
None.
Return Value:
The system frequency in Hertz.
--*/
{
TIMER_CONTROL TimerControlSetup;
TIMER_CONTROL TimerControlReadStatus;
TIMER_STATUS TimerStatus;
NMI_STATUS NmiStatus;
PEISA_CONTROL controlBase;
ULONGLONG Count1;
ULONGLONG Count2;
ULONG NumberOfIntervals;
ULONG SquareWaveState = 0;
// mdbfix - move this into eisa.h one day
#define SB_READ_STATUS_ONLY 2
controlBase = HalpEisaControlBase;
//
// Disable the speaker counter.
//
*((PUCHAR) &NmiStatus) = READ_PORT_UCHAR(&controlBase->NmiStatus);
NmiStatus.SpeakerGate = 0;
NmiStatus.SpeakerData = 0;
// these are MBZ when writing to NMIMISC
NmiStatus.RefreshToggle = 0;
NmiStatus.SpeakerTimer = 0;
NmiStatus.IochkNmi = 0;
WRITE_PORT_UCHAR(&controlBase->NmiStatus, *((PUCHAR) &NmiStatus));
//
// Number of Square Wave transitions to count.
// at 50ms period, count the number of 25ms
// square wave transitions for a sample reference
// time to against which we measure processor cycle count.
//
NumberOfIntervals = (SampleTime/50) * 2;
//
// Set the timer for counter 0 in binary mode, square wave output
//
TimerControlSetup.BcdMode = 0;
TimerControlSetup.Mode = TM_SQUARE_WAVE;
TimerControlSetup.SelectByte = SB_LSB_THEN_MSB;
TimerControlSetup.SelectCounter = SELECT_COUNTER_2;
//
// Set the counter for a latched read of the status.
// We will poll the PIT for the state of the square
// wave output.
//
TimerControlReadStatus.BcdMode = 0;
TimerControlReadStatus.Mode = (1 << SELECT_COUNTER_2);
TimerControlReadStatus.SelectByte = SB_READ_STATUS_ONLY;
TimerControlReadStatus.SelectCounter = SELECT_READ_BACK;
//
// Write the count value LSB and MSB for a 50ms clock period
//
WRITE_PORT_UCHAR( &controlBase->CommandMode1,
*(PUCHAR)&TimerControlSetup );
WRITE_PORT_UCHAR( &controlBase->SpeakerTone,
TIMER_REF_VALUE & 0xff );
WRITE_PORT_UCHAR( &controlBase->SpeakerTone,
(TIMER_REF_VALUE >> 8) & 0xff );
//
// Enable the speaker counter but disable the SPKR output signal.
//
*((PUCHAR) &NmiStatus) = READ_PORT_UCHAR(&controlBase->NmiStatus);
NmiStatus.SpeakerGate = 1;
NmiStatus.SpeakerData = 0;
// these are MBZ when writing to NMIMISC
NmiStatus.RefreshToggle = 0;
NmiStatus.SpeakerTimer = 0;
NmiStatus.IochkNmi = 0;
WRITE_PORT_UCHAR(&controlBase->NmiStatus, *((PUCHAR) &NmiStatus));
//
// Synchronize with the counter before taking the first
// sample of the Processor Cycle Count (PCC). Since we
// are using the Square Wave Mode, wait until the next
// state change and then observe half a cycle before
// sampling.
//
//
// observe the low transition of the square wave output.
//
do {
*((PUCHAR) &NmiStatus) = READ_PORT_UCHAR(&controlBase->NmiStatus);
} while (NmiStatus.SpeakerTimer != SquareWaveState);
SquareWaveState ^= 1;
//
// observe the next transition of the square wave output and then
// take the first cycle counter sample.
//
do {
*((PUCHAR) &NmiStatus) = READ_PORT_UCHAR(&controlBase->NmiStatus);
} while (NmiStatus.SpeakerTimer != SquareWaveState);
Count1 = __RCC();
//
// Wait for the 500ms time period to pass and then take the
// second sample of the PCC. For a 50ms period, we have to
// observe eight wave transitions (25ms each).
//
do {
SquareWaveState ^= 1;
//
// wait for wave transition
//
do {
*((PUCHAR) &NmiStatus) = READ_PORT_UCHAR(&controlBase->NmiStatus);
} while (NmiStatus.SpeakerTimer != SquareWaveState);
} while (--NumberOfIntervals);
Count2 = __RCC();
//
// Disable the speaker counter.
//
*((PUCHAR) &NmiStatus) = READ_PORT_UCHAR(&controlBase->NmiStatus);
NmiStatus.SpeakerGate = 0;
NmiStatus.SpeakerData = 0;
WRITE_PORT_UCHAR(&controlBase->NmiStatus, *((PUCHAR) &NmiStatus));
//
// Calculate the Hz by the number of processor cycles
// elapsed during 1s.
//
// Hz = PCC/SampleTime * 1000ms/s
// = PCC * (1000/SampleTime)
//
// did the counter wrap? if so add 2^32
if (Count1 > Count2) {
Count2 += (ULONGLONG)(1 << 32);
}
return (ULONG) ((Count2 - Count1)*(((ULONG)1000)/SampleTime));
}
VOID
HalpInitializeProcessorParameters(
VOID
)
/*++
Routine Description:
This routine initalize the processor counter parameters
HalpClockFrequency and HalpClockMegaHertz based on the
estimated CPU speed. A 1s reference time is used for
the estimation.
Arguments:
None.
Return Value:
None.
--*/
{
HalpClockFrequency = HalpQuerySystemFrequency(1000);
HalpClockMegaHertz = (HalpClockFrequency + 500000)/ 1000000;
#if DBG
DbgPrint(
"Frequency = %d\nMegaHertz = %d\n",
HalpClockFrequency,
HalpClockMegaHertz
);
#endif
}
#if 0
VOID
HalpGatherPerformanceParameterStats(
VOID
)
/*++
Routine Description:
This routine gathers statistics on the method for
estimating the system frequency.
Arguments:
None.
Return Value:
None.
--*/
{
ULONG Index;
ULONG Hertz[32];
ULONGLONG Mean = 0;
ULONGLONG Variance = 0;
ULONGLONG TempHertz;
//
// take 32 samples of estimated CPU speed,
// calculating the mean in the process.
//
DbgPrint("Sample\tFrequency\tMegaHertz\n\n");
for (Index = 0; Index < 32; Index++) {
Hertz[Index] = HalpQuerySystemFrequency(500);
Mean += Hertz[Index];
DbgPrint(
"%d\t%d\t%d\n",
Index,
Hertz[Index],
(ULONG)((Hertz[Index] + 500000)/1000000)
);
}
//
// calculate the mean
//
Mean /= 32;
//
// calculate the variance
//
for (Index = 0; Index < 32; Index++) {
TempHertz = (Mean > Hertz[Index])?
(Mean - Hertz[Index]) : (Hertz[Index] - Mean);
TempHertz = TempHertz*TempHertz;
Variance += TempHertz;
}
Variance /= 32;
DbgPrint("\nResults\n\n");
DbgPrint(
"Mean = %d\nVariance = %d\nMegaHertz (derived) = %d\n",
Mean,
Variance,
(Mean + 500000)/ 1000000
);
}
#endif