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, cmp _HalpProfileRunning, 0 ; Profiling? je @f ; if not just exit stdCall _KeProfileInterrupt, ; (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, ; ; 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