title "Stall Execution Support" ;++ ; ; Copyright (c) 1989 Microsoft Corporation ; Copyright (c) 1993 Sequent Computer Systems, Inc. ; ; Module Name: ; ; w3stall.asm ; ; Abstract: ; ; This module implements the code necessary to stall the processor ; for some specified period of time. ; ; Author: ; ; Phil Hochstetler (phil@sequent.com) ; ; Environment: ; ; Kernel mode only. ; ; Revision History: ; ;-- .386p .xlist include hal386.inc include callconv.inc include i386\kimacro.inc include mac386.inc include i386\apic.inc include i386\ixcmos.inc include i386\w3.inc EXTRNP _DbgBreakPoint,0,IMPORT extrn _HalpLocalUnitBase:DWORD page ,132 subttl "Initialize Stall Execution Counter" ;++ ; ; VOID ; HalpInitializeStallExecution ( ; IN CCHAR ProcessorNumber ; ) ; ; Routine Description: ; ; This routine initialize the per Microsecond counter for ; KeStallExecutionProcessor ; ; Arguments: ; ; ProcessorNumber - Processor Number ; ; Return Value: ; ; None. ; ; Note: ; ; This routine is called from the HalInitSystem routine in w3hal.c. ; It is only called during Phase 0 init on P0 ; ;-- ; ; Local Variables - These are valid even in the Isr because we're the only thing ; running on this processor and no-one else will change the ebp ; register. StallIDTPointer equ [ebp-6] StallIDTArea equ [ebp-8] StallInterruptCount equ [ebp-12] StallLVTentry equ dword ptr [ebp-16] StallDummyentry0 equ dword ptr [ebp-20] StallDummyentry1 equ [ebp-24] StallApicTpr equ dword ptr [ebp-28] _TEXT SEGMENT DWORD PUBLIC 'CODE' ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING cPublicProc _HalpInitializeStallExecution,1 push ebp ; save ebp mov ebp, esp ; set up 28 bytes for local use sub esp, 32 pushfd ; save caller's eflag ; ; --- For an APIC implementaion we use the APIC timer0 and install a ; --- Local Vector Table entry to point to a high priority ; --- vector for the Timer0 Interrupt. Then use the TPR in the local APIC ; --- to mask out every thing below it. To this end we've reserved a vector ; --- in the highest priority group (0F8) to be used here. ; ; ; Since APIC timer interrupt will come from APIC_STALL_VECTOR, we need to ; Save original APIC_STALL_VECTOR descriptor and set the descriptor ; to point to our own handler. ; sidt fword ptr StallIDTArea ; get IDT address mov edx, StallIDTPointer ; (edx)->IDT ; ; --- Save Original Descriptor on Stack ; push dword ptr [edx+8*APIC_STALL_VECTOR]; (TOS) = orig. Vector push dword ptr [edx+8*APIC_STALL_VECTOR + 4] push edx ; (TOS) -> IDT ; ; --- Install our IDT entry ; mov eax, offset FLAT:ApicTimer0Handler mov word ptr [edx+8*APIC_STALL_VECTOR], ax ; Low half handler addr mov word ptr [edx+8*APIC_STALL_VECTOR+2], KGDT_R0_CODE ; set up selector mov word ptr [edx+8*APIC_STALL_VECTOR+4], D_INT032 ; 386 interrupt gate shr eax, 16 ; (ax)=higher half of handler addr mov word ptr [edx+8*APIC_STALL_VECTOR+6], ax ; ; --- Init. interrupt Flag ; mov dword ptr StallinterruptCount, 0 ; set no interrupt yet ; ; Get the Local Vector Table Timer Zero entry and save it ; mov edx, _HalpLocalUnitBase ; get the current TPR mov eax, [edx+LU_TIMER_VECTOR] ; get Timer zero LVT mov StallLVTentry, eax ; Save LVT ; ; --- Set the Inital Timer count ; --- Note: we will use TMBASE with No Divider ; --- which runs at 11 Mhz ; mov eax,(PeriodInUsec*11) ; Set the initial TIMER0 count mov [edx+LU_INITIAL_COUNT], eax ; ; Save then set the Local APIC's TPR to mask all interrupts except the ; highest priority group ; mov eax, [edx+LU_TPR] ; get TPR mov StallApicTpr, eax ; save TPR for later ; ; Set TPR (Priority of CPU) = TPR (VECTOR - 16). So that all interrupts ; in VECTOR's priority group will be allowed in. ; mov eax, APIC_STALL_VECTOR-10H mov [edx+LU_TPR], eax ; Write the new TPR ; ; ; --- Create Timer zero entry and store in Local Vector Table ; --- STARTING the clock ; mov eax,(00040000H OR PERIODIC_TIMER) or eax,(INTERRUPT_MOT_MASKED OR APIC_STALL_VECTOR) @@: test [edx+LU_TIMER_VECTOR], DELIVERY_PENDING jnz @b mov [edx+LU_TIMER_VECTOR], eax ; ; --- Now enable the interrupt and start the counter ; xor eax, eax ; (eax) = 0, initialize loopcount ; ; --- ENABLE TIMER ZERO INTERRUPT ; sti ; ; --- BEGIN SPIN Calibration LOOP ; Stall10: add eax, 1 ; increment the loopcount jnz short Stall10 ; ; Counter overflowed ; stdCall _DbgBreakPoint ;++ ; ; VOID ; ApicTimer0Handler( ; ); ; ;Routine Description: ; ; APIC Timer zero interrupt Handler for Calibrating Spin Loop for ; KeStallExecution(); ; ; Note: we discard first real time clock interrupt and compute the ; permicrosecond loopcount on receiving of the second real time ; interrupt. This is because the first interrupt is generated ; based on the previous real time tick interval. ; ;-- Public ApicTimer0Handler ApicTimer0Handler: inc dword ptr StallInterruptCount ; increment interrupt count cmp dword ptr StallInterruptCount,1 ; Is this the first interrupt? jnz Stall25 ; no, its the second go process it pop eax ; get rid of original ret addr push offset FLAT:Stall10 ; set new ret addr --> top of loop ; ; EOI the Local Apic, the value written is immaterial ; mov eax, _HalpLocalUnitBase ; write to local unit EOI register mov dword ptr [eax+LU_EOI], 0 ; xor eax, eax ; reset loop counter ; iretd ; ; --- Process EAX for Spin Count ; ; Stall25: ifdef DBG cmp eax, 0 jnz short Stall30 stdCall _DbgBreakPoint endif ; Stall30: xor edx, edx ; (edx:eax) = dividend mov ecx, PeriodInUSec; ; (ecx) = time spent in the loop div ecx ; (eax) = loop count per microsecond cmp edx, 0 ; Is remainder =0? jz short Stall40 ; yes, go Stall40 inc eax ; increment loopcount by 1 ; Stall40: movzx ecx, byte ptr [ebp+8] ; Current processor number mov PCR[PcStallScaleFactor], eax ; ; Reset return address to kexit ; pop eax ; discard original return address push offset FLAT:kexit ; return to kexit ; ; EOI the Local Apic, the value written is immaterial ; mov eax, _HalpLocalUnitBase ; write the local unit EOI register mov dword ptr [eax+LU_EOI], 0 and word ptr [esp+8], NOT 0200H ; Disable interrupt upon return ; iretd ; ; ; --- Calibration is Done Restore all values and exit ; --- Interrupts are disabled ; kexit: ; ; Turn OFF TIMER ZERO ; ; mov edx, _HalpLocalUnitBase mov eax, StallLVTentry ; get Saved LVT @@: test [edx+LU_TIMER_VECTOR], DELIVERY_PENDING jnz @b mov [edx+LU_TIMER_VECTOR], eax ; ; --- Restore the interrupt vector we used ; pop edx ; (edx)->IDT pop [edx+8*APIC_STALL_VECTOR+4] ; higher half of desc pop [edx+8*APIC_STALL_VECTOR] ; lower half of desc ; ; --- Restore the Local APIC's TPR ; mov eax, StallApicTpr ; get the saved TPR mov edx, _HalpLocalUnitBase ; write old value to TPR mov [edx+LU_TPR], eax sub esp, 32 popfd ; restore caller's eflags mov esp, ebp pop ebp ; restore ebp stdRET _HalpInitializeStallExecution stdENDP _HalpInitializeStallExecution page ,132 subttl "Stall Execution" ;++ ; ; VOID ; KeStallExecutionProcessor ( ; IN ULONG MicroSeconds ; ) ; ; Routine Description: ; ; This function stalls execution for the specified number of microseconds. ; KeStallExecutionProcessor ; ; Arguments: ; ; MicroSeconds - Supplies the number of microseconds that execution is to be ; stalled. ; ; Return Value: ; ; None. ; ;-- MicroSeconds equ [esp + 4] cPublicProc _KeStallExecutionProcessor ,1 cPublicFpo 1, 0 mov ecx, MicroSeconds ; (ecx) = Microseconds jecxz short kese10 ; return if no loop needed mov eax, PCR[PcStallScaleFactor] ; get per microsecond mul ecx ; (eax) = desired loop count ifdef DBG ; ; Make sure we the loopcount is less than 4G and is not equal to zero ; cmp edx, 0 jz short @f int 3 @@: cmp eax,0 jnz short @f int 3 endif ALIGN 4 @@: sub eax, 1 ; (eax) = (eax) - 1 jnz short @b kese10: stdRET _KeStallExecutionProcessor stdENDP _KeStallExecutionProcessor _TEXT ends end