507 lines
9.2 KiB
C
507 lines
9.2 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 2000 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
mpprofil.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This module implements the code necessary to initialize, field and
|
||
|
process the profile interrupt.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Shie-Lin Tzong (shielint) 12-Jan-1990
|
||
|
|
||
|
Environment:
|
||
|
|
||
|
Kernel mode only.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
bryanwi 20-Sep-90
|
||
|
|
||
|
Forrest Foltz (forrestf) 28-Oct-2000
|
||
|
Ported from ixprofil.asm to ixprofil.c
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "halcmn.h"
|
||
|
|
||
|
#define APIC_TIMER_ENABLED (PERIODIC_TIMER | APIC_PROFILE_VECTOR)
|
||
|
|
||
|
#define APIC_TIMER_DISABLED (INTERRUPT_MASKED | \
|
||
|
PERIODIC_TIMER | \
|
||
|
APIC_PROFILE_VECTOR)
|
||
|
|
||
|
#define TIMER_ROUND(x) ((((x) + 10000 / 2) / 10000) * 10000)
|
||
|
|
||
|
ULONG HalpProfileRunning;
|
||
|
|
||
|
VOID (*HalpPerfInterruptHandler)(PKTRAP_FRAME);
|
||
|
|
||
|
VOID
|
||
|
HalStartProfileInterrupt(
|
||
|
IN ULONG Reserved
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
What we do here is set the interrupt rate to the value that's been set
|
||
|
by the KeSetProfileInterval routine. Then enable the APIC Timer interrupt.
|
||
|
|
||
|
This function gets called on every processor so the hal can enable
|
||
|
a profile interrupt on each processor.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Reserved - Not used, must be zero.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
ULONG initialCount;
|
||
|
|
||
|
ASSERT(Reserved == 0);
|
||
|
|
||
|
initialCount = HalpGetCurrentHalPcr()->ProfileCountDown;
|
||
|
|
||
|
LOCAL_APIC(LU_INITIAL_COUNT) = initialCount;
|
||
|
HalpProfileRunning = TRUE;
|
||
|
|
||
|
//
|
||
|
// Set the Local APIC Timer to interrupt periodically at
|
||
|
// APIC_PROFILE_VECTOR
|
||
|
//
|
||
|
|
||
|
LOCAL_APIC(LU_TIMER_VECTOR) = APIC_TIMER_ENABLED;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
HalStopProfileInterrupt (
|
||
|
IN ULONG Reserved
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Stop the profile interrupt that was started with
|
||
|
HalpStartProfileInterrupt;
|
||
|
|
||
|
This function gets called on every processor so the hal can disable
|
||
|
a profile interrupt on each processor.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Reserved - Not used, must be zero.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
ASSERT(Reserved == 0);
|
||
|
|
||
|
HalpProfileRunning = FALSE;
|
||
|
LOCAL_APIC(LU_TIMER_VECTOR) = APIC_TIMER_DISABLED;
|
||
|
}
|
||
|
|
||
|
|
||
|
ULONG_PTR
|
||
|
HalSetProfileInterval (
|
||
|
ULONG_PTR Interval
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This procedure sets the interrupt rate (and thus the sampling
|
||
|
interval) for the profiling interrupt.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Interval - Supplies the desired profile interrupt interval in 100ns
|
||
|
units (MINIMUM is 1221 or 122.1 uS) see ke\profobj.c
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Interval actually used
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
ULONG64 period;
|
||
|
ULONG apicFreqency;
|
||
|
PHAL_PCR halPcr;
|
||
|
ULONG countDown;
|
||
|
|
||
|
halPcr = HalpGetCurrentHalPcr();
|
||
|
|
||
|
//
|
||
|
// Limit the interrupt period to 1 second
|
||
|
//
|
||
|
|
||
|
if (Interval > TIME_UNITS_PER_SECOND) {
|
||
|
period = TIME_UNITS_PER_SECOND;
|
||
|
} else {
|
||
|
period = Interval;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Compute the countdown value corresponding to the desired period.
|
||
|
// The calculation is done with 64-bit intermediate values.
|
||
|
//
|
||
|
|
||
|
countDown =
|
||
|
(ULONG)(period * halPcr->ApicClockFreqHz) / TIME_UNITS_PER_SECOND;
|
||
|
|
||
|
halPcr->ProfileCountDown = countDown;
|
||
|
LOCAL_APIC(LU_INITIAL_COUNT) = countDown;
|
||
|
|
||
|
return period;
|
||
|
}
|
||
|
|
||
|
|
||
|
VOID
|
||
|
#if defined(MMTIMER)
|
||
|
HalpAcpiTimerCalibratePerfCount (
|
||
|
#else
|
||
|
HalCalibratePerformanceCounter (
|
||
|
#endif
|
||
|
IN LONG volatile *Number,
|
||
|
IN ULONGLONG NewCount
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine calibrates the performance counter value for a
|
||
|
multiprocessor system. The calibration can be done by zeroing
|
||
|
the current performance counter, or by calculating a per-processor
|
||
|
skewing between each processors counter.
|
||
|
|
||
|
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 flags;
|
||
|
PHAL_PCR halPcr;
|
||
|
|
||
|
flags = HalpDisableInterrupts();
|
||
|
|
||
|
//
|
||
|
// Store the count in the PCR
|
||
|
//
|
||
|
|
||
|
halPcr = HalpGetCurrentHalPcr();
|
||
|
halPcr->PerfCounter = NewCount;
|
||
|
|
||
|
//
|
||
|
// Wait here until all processors arrive
|
||
|
//
|
||
|
|
||
|
InterlockedDecrement(Number);
|
||
|
while (*Number > 0) {
|
||
|
;
|
||
|
}
|
||
|
PROCESSOR_FENCE;
|
||
|
|
||
|
//
|
||
|
// Zero the time stamp counter, restore interrupts and return.
|
||
|
//
|
||
|
|
||
|
WriteMSR(MSR_TSC,0);
|
||
|
HalpRestoreInterrupts(flags);
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
HalpWaitForCmosRisingEdge (
|
||
|
VOID
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Waits until the rising edge of the CMOS_STATUS_BUSY bit is detected.
|
||
|
|
||
|
Note - The CMOS spin lock must be acquired before calling this routine.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
UCHAR value;
|
||
|
|
||
|
//
|
||
|
// The simulator takes about a day to go through this, so for right
|
||
|
//
|
||
|
|
||
|
#if !defined(_AMD64_SIMULATOR_)
|
||
|
|
||
|
//
|
||
|
// We're going to be polling the CMOS_STATUS_A register. Program
|
||
|
// that register address here, outside of the loop.
|
||
|
//
|
||
|
|
||
|
WRITE_PORT_UCHAR(CMOS_ADDRESS_PORT,CMOS_STATUS_A);
|
||
|
|
||
|
//
|
||
|
// Wait for the status bit to be clear
|
||
|
//
|
||
|
|
||
|
do {
|
||
|
value = READ_PORT_UCHAR(CMOS_DATA_PORT);
|
||
|
} while ((value & CMOS_STATUS_BUSY) != 0);
|
||
|
|
||
|
//
|
||
|
// Now wait for the rising edge of the status bit
|
||
|
//
|
||
|
|
||
|
do {
|
||
|
value = READ_PORT_UCHAR(CMOS_DATA_PORT);
|
||
|
} while ((value & CMOS_STATUS_BUSY) == 0);
|
||
|
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
ULONG
|
||
|
HalpScaleTimers (
|
||
|
VOID
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Determines the frequency of the APIC timer. This routine is run
|
||
|
during initialization
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
ULONG flags;
|
||
|
ULONG passCount;
|
||
|
ULONG64 cpuFreq;
|
||
|
ULONG apicFreq;
|
||
|
UCHAR value;
|
||
|
PHAL_PCR halPcr;
|
||
|
|
||
|
HalpAcquireCmosSpinLock();
|
||
|
flags = HalpDisableInterrupts();
|
||
|
|
||
|
LOCAL_APIC(LU_TIMER_VECTOR) = APIC_TIMER_DISABLED;
|
||
|
LOCAL_APIC(LU_DIVIDER_CONFIG) = LU_DIVIDE_BY_1;
|
||
|
|
||
|
passCount = 2;
|
||
|
while (passCount > 0) {
|
||
|
|
||
|
//
|
||
|
// Make sure the write has occured
|
||
|
//
|
||
|
|
||
|
LOCAL_APIC(LU_TIMER_VECTOR);
|
||
|
|
||
|
//
|
||
|
// Wait for the rising edge of the UIP bit, this is the start of the
|
||
|
// cycle.
|
||
|
//
|
||
|
|
||
|
HalpWaitForCmosRisingEdge();
|
||
|
|
||
|
//
|
||
|
// At this point the UIP bit has just changed to the set state.
|
||
|
// Clear the time stamp counter and start the APIC counting down
|
||
|
// from it's maximum value.
|
||
|
//
|
||
|
|
||
|
PROCESSOR_FENCE;
|
||
|
|
||
|
LOCAL_APIC(LU_INITIAL_COUNT) = 0xFFFFFFFF;
|
||
|
cpuFreq = ReadTimeStampCounter();
|
||
|
|
||
|
//
|
||
|
// Wait for the next rising edge, this marks the end of the CMOS
|
||
|
// clock update cycle.
|
||
|
//
|
||
|
|
||
|
HalpWaitForCmosRisingEdge();
|
||
|
|
||
|
PROCESSOR_FENCE;
|
||
|
|
||
|
apicFreq = 0xFFFFFFFF - LOCAL_APIC(LU_CURRENT_COUNT);
|
||
|
cpuFreq = ReadTimeStampCounter() - cpuFreq;
|
||
|
|
||
|
passCount -= 1;
|
||
|
}
|
||
|
|
||
|
halPcr = HalpGetCurrentHalPcr();
|
||
|
|
||
|
//
|
||
|
// cpuFreq is elapsed timestamp in one second. Round to nearest
|
||
|
// 10Khz and store.
|
||
|
//
|
||
|
|
||
|
halPcr->TSCHz = TIMER_ROUND(cpuFreq);
|
||
|
|
||
|
//
|
||
|
// Calculate the apic frequency, rounding to the nearest 10Khz
|
||
|
//
|
||
|
|
||
|
apicFreq = TIMER_ROUND(apicFreq);
|
||
|
halPcr->ApicClockFreqHz = apicFreq;
|
||
|
|
||
|
//
|
||
|
// Store microsecond representation of TSC frequency.
|
||
|
//
|
||
|
|
||
|
halPcr->StallScaleFactor = (ULONG)(halPcr->TSCHz / 1000000);
|
||
|
if ((halPcr->TSCHz % 1000000) != 0) {
|
||
|
halPcr->StallScaleFactor += 1;
|
||
|
}
|
||
|
|
||
|
HalpReleaseCmosSpinLock();
|
||
|
halPcr->ProfileCountDown = apicFreq;
|
||
|
|
||
|
//
|
||
|
// Set the interrupt rate in the chip and return the apic frequency
|
||
|
//
|
||
|
|
||
|
LOCAL_APIC(LU_INITIAL_COUNT) = apicFreq;
|
||
|
HalpRestoreInterrupts(flags);
|
||
|
|
||
|
return halPcr->ApicClockFreqHz;
|
||
|
}
|
||
|
|
||
|
BOOLEAN
|
||
|
HalpProfileInterrupt (
|
||
|
IN PKINTERRUPT Interrupt,
|
||
|
IN PVOID ServiceContext
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is entered as the result of a profile interrupt. Its
|
||
|
function is to dismiss the interrupt, raise system Irql to
|
||
|
HAL_PROFILE_LEVEL and transfer control to the standard system routine
|
||
|
to process any active profiles.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Interrupt - Supplies a pointer to the kernel interrupt object
|
||
|
|
||
|
ServiceContext - Supplies the service context
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
UNREFERENCED_PARAMETER(ServiceContext);
|
||
|
|
||
|
if (HalpProfileRunning != FALSE) {
|
||
|
|
||
|
//
|
||
|
// BUGBUG what do we use for source?
|
||
|
//
|
||
|
|
||
|
KeProfileInterruptWithSource(Interrupt->TrapFrame,0);
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
BOOLEAN
|
||
|
HalpPerfInterrupt (
|
||
|
IN PKINTERRUPT Interrupt,
|
||
|
IN PVOID ServiceContext
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine is entered as the result of a perf interrupt. Its
|
||
|
function is to dismiss the interrupt, raise system Irql to
|
||
|
HAL_PROFILE_LEVEL and transfer control to the standard system routine
|
||
|
to process any active profiles.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Interrupt - Supplies a pointer to the kernel interrupt object
|
||
|
|
||
|
ServiceContext - Supplies the service context
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
UNREFERENCED_PARAMETER(Interrupt);
|
||
|
|
||
|
if (HalpPerfInterruptHandler != NULL) {
|
||
|
HalpPerfInterruptHandler(Interrupt->TrapFrame);
|
||
|
} else {
|
||
|
ASSERT(FALSE);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Starting with the Willamette processor, the perf interrupt gets masked
|
||
|
// on interrupting. Need to clear the mask before leaving the interrupt
|
||
|
// handler.
|
||
|
//
|
||
|
|
||
|
LOCAL_APIC(LU_PERF_VECTOR), ~INTERRUPT_MASKED;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|