windows-nt/Source/XPSP1/NT/base/hals/halmps/i386/mpprofil.asm

687 lines
16 KiB
NASM
Raw Normal View History

2020-09-26 03:20:57 -05:00
title "Interval Clock Interrupt"
;++
;
; Copyright (c) 1989 Microsoft Corporation
;
; Module Name:
;
; mpprofile.asm
;
; 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
;
;--
.586p
.xlist
include hal386.inc
include i386\ix8259.inc
include i386\ixcmos.inc
include callconv.inc ; calling convention macros
include i386\kimacro.inc
include mac386.inc
include apic.inc
include ntapic.inc
include i386\mp8254.inc
.list
EXTRNP _DbgBreakPoint,0,IMPORT
EXTRNP _KeProfileInterrupt,1,IMPORT
EXTRNP Kei386EoiHelper,0,IMPORT
EXTRNP _HalEndSystemInterrupt,2
EXTRNP _HalBeginSystemInterrupt,3
EXTRNP _HalpAcquireSystemHardwareSpinLock,0
EXTRNP _HalpReleaseSystemHardwareSpinLock,0
EXTRNP _HalpAcquireCmosSpinLock ,0
EXTRNP _HalpReleaseCmosSpinLock ,0
extrn _HalpUse8254:BYTE
;
; APIC Timer Constants
;
APIC_TIMER_DISABLED equ (INTERRUPT_MASKED OR PERIODIC_TIMER OR APIC_PROFILE_VECTOR)
APIC_TIMER_ENABLED equ (PERIODIC_TIMER OR APIC_PROFILE_VECTOR)
;
; number of 100ns intervals in one second
;
Num100nsIntervalsPerSec equ 10000000
_DATA SEGMENT DWORD PUBLIC 'DATA'
ALIGN dword
public _HalpProfileRunning, _HalpPerfInterruptHandler
_HalpProfileRunning dd 0
_HalpPerfInterruptHandler dd 0
_DATA ends
_TEXT SEGMENT DWORD PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
;++
;
; 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.
;
;--
cPublicProc _HalStartProfileInterrupt ,1
;
; Set the interrupt rate to what is actually needed.
;
mov eax, PCR[PcHal.ProfileCountDown]
mov dword ptr APIC[LU_INITIAL_COUNT], eax
mov _HalpProfileRunning, 1 ; Indicate profiling
;
; Set the Local APIC Timer to interrupt Periodically at APIC_PROFILE_VECTOR
;
mov dword ptr APIC[LU_TIMER_VECTOR], APIC_TIMER_ENABLED
stdRET _HalStartProfileInterrupt
stdENDP _HalStartProfileInterrupt
;++
;
; HalStopProfileInterrupt(
; IN ULONG Reserved
; );
;
; Routine Description:
;
;--
cPublicProc _HalStopProfileInterrupt ,1
;
; Turn off profiling
;
mov _HalpProfileRunning, 0 ; Indicate profiling is off
mov dword ptr APIC[LU_TIMER_VECTOR], APIC_TIMER_DISABLED
stdRET _HalStopProfileInterrupt
stdENDP _HalStopProfileInterrupt
;++
; ULONG
; HalSetProfileInterval (
; ULONG Interval
; );
;
; Routine Description:
;
; This procedure sets the interrupt rate (and thus the sampling
; interval) for the profiling interrupt.
;
; Arguments:
;
; (TOS+4) - Interval in 100ns unit.
; (MINIMUM is 1221 or 122.1 uS) see ke\profobj.c
;
; Return Value:
;
; Interval actually used
;
;--
cPublicProc _HalSetProfileInterval ,1
mov ecx, [esp+4] ; ecx = interval in 100ns unit
and ecx, 7FFFFFFFh ; Remove sign bit.
;
; The only possible error is if we will cause a divide overflow
; this can happen only if the (frequency * request count) is
; greater than 2^32* Num100nsIntervalsPerSec.
;
; To protect against that we just ensure that the request count
; is less than (or equal to) Num100nsIntervalsPerSec
;
cmp ecx, Num100nsIntervalsPerSec
jle @f
mov ecx, Num100nsIntervalsPerSec
@@:
;
; Save the interval we're using to return
;
push ecx
;
; Compute the countdown value
;
; let
; R == caller's requested 100ns interval count
; F == APIC Counter Freguency (hz)
; N == Number of 100ns Intervals per sec
;
; then
; count = (R*F)/N
;
; Get the previously computed APIC counter Freq
; for this processor
;
mov eax, PCR[PcHal.ApicClockFreqHz]
;
; eax <= F and ecx <= R
;
;
; Compute (request count) * (ApicClockFreqHz) == (R*F)
;
xor edx, edx
mul ecx
;
; edx:eax contains 64Bits of (R*F)
;
mov ecx, Num100nsIntervalsPerSec
div ecx
;
; Compute (R*F) / Num100nsIntervalsPerSec == (R*F)/N
;
mov PCR[PcHal.ProfileCountDown], eax ; Save the Computed Count Down
mov edx, dword ptr APIC[LU_CURRENT_COUNT]
;
; Set the interrupt rate in the chip.
;
mov dword ptr APIC[LU_INITIAL_COUNT], eax
pop eax ; Return Actual Interval Used
stdRET _HalSetProfileInterval
stdENDP _HalSetProfileInterval
page ,132
subttl "System Profile Interrupt"
;++
;
; 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:
;
; None
; Interrupt is disabled
;
; Return Value:
;
; Does not return, jumps directly to KeProfileInterrupt, which returns
;
; Sets Irql = HAL_PROFILE_LEVEL and dismisses the interrupt
;
;--
ENTER_DR_ASSIST Hpi_a, Hpi_t
cPublicProc _HalpProfileInterrupt ,0
;
; Save machine state in trap frame
;
ENTER_INTERRUPT Hpi_a, Hpi_t
;
; (esp) - base of trap frame
;
push APIC_PROFILE_VECTOR
sub esp, 4 ; allocate space to save OldIrql
stdCall _HalBeginSystemInterrupt, <HAL_PROFILE_LEVEL,APIC_PROFILE_VECTOR,esp>
cmp _HalpProfileRunning, 0 ; Profiling?
je @f ; if not just exit
stdCall _KeProfileInterrupt,<ebp> ; (ebp) = TrapFrame address
@@:
INTERRUPT_EXIT
stdENDP _HalpProfileInterrupt
subttl "System Perf Interrupt"
;++
;
; 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:
;
; None
; Interrupt is disabled
;
; Return Value:
;
; Does not return, jumps directly to KeProfileInterrupt, which returns
;
; Sets Irql = HAL_PROFILE_LEVEL and dismisses the interrupt
;
;--
ENTER_DR_ASSIST Hpf_a, Hpf_t
cPublicProc _HalpPerfInterrupt ,0
;
; Save machine state in trap frame
;
ENTER_INTERRUPT Hpf_a, Hpf_t
;
; (esp) - base of trap frame
;
push APIC_PERF_VECTOR
sub esp, 4 ; allocate space to save OldIrql
stdCall _HalBeginSystemInterrupt, <HAL_PROFILE_LEVEL,APIC_PERF_VECTOR,esp>
;
; Invoke perf interrupt handler
;
mov ecx, ebp ; param1 = trap frame
mov eax, _HalpPerfInterruptHandler
or eax, eax
jz short hpf20
call eax
hpf10:
;
; Starting with the Willamette processor, the perf interrupt gets masked on
; interrupting. Needs to clear the mask before leaving the interrupt handler.
; Do this regardless of whether a valid interrupt handler exists or not.
;
and dword ptr APIC[LU_PERF_VECTOR], (NOT INTERRUPT_MASKED)
INTERRUPT_EXIT
hpf20:
if DBG
int 3
endif
jmp short hpf10
stdENDP _HalpPerfInterrupt
_TEXT ends
PAGELK SEGMENT PARA PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
;++
;
; VOID
; HalCalibratePerformanceCounter (
; 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.
;--
NewCountLow equ [esp + 24]
NewCountHigh equ [esp + 28]
ifdef MMTIMER
cPublicProc _HalpAcpiTimerCalibratePerfCount,3
else
cPublicProc _HalCalibratePerformanceCounter,3
endif
cPublicFpo 3,0
push esi
push edi
push ebx
mov esi, [esp+16] ; pointer to Number
pushfd ; save previous interrupt state
cli ; disable interrupts
cPublicFpo 3,4
xor eax, eax
lock dec dword ptr [esi] ; count down
@@: YIELD
cmp dword ptr [esi], 0 ; wait for all processors to signal
jnz short @b
cpuid ; fence
mov ecx, MsrTSC ; MSR of time stamp counter
mov eax, NewCountLow
mov edx, NewCountHigh
mov PCR[PcHal.PerfCounterLow], eax
mov PCR[PcHal.PerfCounterHigh], edx
xor eax,eax
xor edx,edx
wrmsr ; zero the time stamp counter
popfd ; restore interrupt flag
pop ebx
pop edi
pop esi
ifdef MMTIMER
stdRET _HalpAcpiTimerCalibratePerfCount
stdENDP _HalpAcpiTimerCalibratePerfCount
else
stdRET _HalCalibratePerformanceCounter
stdENDP _HalCalibratePerformanceCounter
endif
PAGELK ends
INIT SEGMENT PARA PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
page ,132
subttl "Scale Apic Timer"
;++
;
; VOID
; HalpScaleTimers (
; IN VOID
; )
;
; Routine Description:
;
; Determines the frequency of the APIC timer. This routine is run
; during initialization
;
;
;--
cPublicProc _HalpScaleTimers ,0
push ebx
push esi
push edi
;
; Don't let anyone in until we've finished here
;
stdCall _HalpAcquireCmosSpinLock
;
; Protect us from interrupts
;
pushfd
cli
;
; First set up the Local Apic Counter
;
; The following code assumes the CPU clock will never
; exceed 4Ghz. For the short term this is probably OK
;
;
; Configure the APIC timer
;
APIC_TIMER_DISABLED equ (INTERRUPT_MASKED OR PERIODIC_TIMER OR APIC_PROFILE_VECTOR)
TIMER_ROUNDING equ 10000
mov dword ptr APIC[LU_TIMER_VECTOR], APIC_TIMER_DISABLED
mov dword ptr APIC[LU_DIVIDER_CONFIG], LU_DIVIDE_BY_1
;
; We're going to do this twice & take the second results
;
mov esi, 2
hst10:
;
; Make sure the write has occurred
;
mov eax, dword ptr APIC[LU_DIVIDER_CONFIG]
;
; We don't care what the actual time is we are only interested
; in seeing the UIP transition. We are garenteed a 1 sec interval
; if we wait for the UIP bit to complete an entire cycle.
;
; We also don't much care which direction the transition we use is
; as long as we wait for the same transition to read the APIC clock.
; Just because it is most likely that when we begin the UIP bit will
; be clear, we'll use the transition from !UIP to UIP.
;
;
; Wait for the UIP bit to be cleared, this is our starting state
;
@@:
mov al, 0Ah ; Specify register A
CMOS_READ ; (al) = CMOS register A
test al, CMOS_STATUS_BUSY ; Is time update in progress?
jnz short @b ; if z, no, wait some more
;
; Wait for the UIP bit to get set
;
@@:
mov al, 0Ah ; Specify register A
CMOS_READ ; (al) = CMOS register A
test al, CMOS_STATUS_BUSY ; Is time update in progress?
jz short @b ; if z, no, wait some more
;
; At this point we found the UIP bit set, now set the initial
; count. Once we write this register its value is copied to the
; current count register and countdown starts or continues from
; there
;
xor eax, eax
mov PCR[PcHal.PerfCounterLow], eax
mov PCR[PcHal.PerfCounterHigh], eax
cpuid ; fence
mov ecx, MsrTSC ; MSR of RDTSC
xor edx, edx
mov eax, edx
mov dword ptr APIC[LU_INITIAL_COUNT], 0FFFFFFFFH
wrmsr ; Clear TSC count
;
; Wait for the UIP bit to be cleared again
;
@@:
mov al, 0Ah ; Specify register A
CMOS_READ ; (al) = CMOS register A
test al, CMOS_STATUS_BUSY ; Is time update in progress?
jnz short @b ; if z, no, wait some more
;
; Wait for the UIP bit to get set
;
@@:
mov al, 0Ah ; Specify register A
CMOS_READ ; (al) = CMOS register A
test al, CMOS_STATUS_BUSY ; Is time update in progress?
jz short @b ; if z, no, wait some more
;
; The cycle is complete, we found the UIP bit set. Now read
; the counters and compute the frequency. The frequency is
; just the ticks counted which is the initial value minus
; the current value.
;
xor eax, eax
cpuid ; fence
rdtsc
mov ecx, dword ptr APIC[LU_CURRENT_COUNT]
dec esi ; if this is the first time
jnz hst10 ; around, go loop
mov PCR[PcHal.TSCHz], eax
mov eax, 0FFFFFFFFH
sub eax, ecx
;
; Round the Apic Timer Freq
;
xor edx, edx ; (edx:eax) = dividend
mov ecx, TIMER_ROUNDING
div ecx ; now edx has remainder
cmp edx, TIMER_ROUNDING / 2
jle @f ; if less don't round
inc eax ; else round up
@@:
;
; Multiply by the Rounding factor to get the rounded Freq
;
mov ecx, TIMER_ROUNDING
xor edx, edx
mul ecx
mov dword ptr PCR[PcHal.ApicClockFreqHz], eax
;
; Round TSC freq
;
mov eax, PCR[PcHal.TSCHz]
xor edx, edx
mov ecx, TIMER_ROUNDING
div ecx ; now edx has remainder
cmp edx, TIMER_ROUNDING / 2
jle @f ; if less don't round
inc eax ; else round up
@@:
mov ecx, TIMER_ROUNDING
xor edx, edx
mul ecx
mov PCR[PcHal.TSCHz], eax
;
; Convert TSC to microseconds
;
xor edx, edx
mov ecx, 1000000
div ecx ; Convert to microseconds
xor ecx, ecx
cmp ecx, edx ; any remainder?
adc eax, ecx ; Yes, add one
mov PCR[PcStallScaleFactor], eax
stdCall _HalpReleaseCmosSpinLock
;
; Return Value is the timer frequency
;
mov eax, dword ptr PCR[PcHal.ApicClockFreqHz]
mov PCR[PcHal.ProfileCountDown], eax
;
; Set the interrupt rate in the chip.
;
mov dword ptr APIC[LU_INITIAL_COUNT], eax
popfd
pop edi
pop esi
pop ebx
stdRET _HalpScaleTimers
stdENDP _HalpScaleTimers
INIT ends
end