|
|
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 ;
; ; 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 dword ptr PCR[PcHal][TSCHz], eax ; Frequency LowPart mov dword ptr PCR[PcHal][TSCHz+4], edx ; Frequency HighPart
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, dword ptr PCR[PcHal][TSCHz] ; Frequency LowPart mov edx, dword ptr PCR[PcHal][TSCHz+4] ; Frequency HighPart
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 dword ptr PCR[PcHal][TSCHz], eax ; Frequency LowPart mov dword ptr PCR[PcHal][TSCHz+4], edx ; Frequency HighPart
; ; Convert TSC to microseconds ;
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
|