|
|
title "Interval Clock Interrupt" ;++ ; ; Copyright (c) 1989 Microsoft Corporation ; ; Module Name: ; ; spclock.asm ; ; Abstract: ; ; This module implements the code necessary to field and process the ; interval clock interrupt. ; ; Author: ; ; Shie-Lin Tzong (shielint) 12-Jan-1990 ; ; Environment: ; ; Kernel mode only. ; ; Revision History: ; ; bryanwi 20-Sep-90 ; ; Add KiSetProfileInterval, KiStartProfileInterrupt, ; KiStopProfileInterrupt procedures. ; KiProfileInterrupt ISR. ; KiProfileList, KiProfileLock are delcared here. ; ; shielint 10-Dec-90 ; Add performance counter support. ; Move system clock to irq8, ie we now use RTC to generate system ; clock. Performance count and Profile use timer 1 counter 0. ; The interval of the irq0 interrupt can be changed by ; KiSetProfileInterval. Performance counter does not care about the ; interval of the interrupt as long as it knows the rollover count. ; Note: Currently I implemented 1 performance counter for the whole ; i386 NT. It works on UP and SystemPro. ; ; John Vert (jvert) 11-Jul-1991 ; Moved from ke\i386 to hal\i386. Removed non-HAL stuff ; ; shie-lin tzong (shielint) 13-March-92 ; Move System clock back to irq0 and use RTC (irq8) to generate ; profile interrupt. Performance counter and system clock use time1 ; counter 0 of 8254. ; ; ;--
.386p .xlist include callconv.inc include hal386.inc include i386\ix8259.inc include i386\ixcmos.inc include i386\kimacro.inc include mac386.inc include i386\spmp.inc .list
EXTRNP _DbgBreakPoint,0,IMPORT extrn KiI8259MaskTable:DWORD EXTRNP _KeUpdateSystemTime,0 EXTRNP _KeUpdateRunTime,1,IMPORT EXTRNP Kei386EoiHelper,0,IMPORT EXTRNP _HalEndSystemInterrupt,2 EXTRNP _HalBeginSystemInterrupt,3 EXTRNP _HalRequestIpi,1 EXTRNP _HalpAcquireCmosSpinLock ,0 EXTRNP _HalpReleaseCmosSpinLock ,0 EXTRNP _KeStallExecutionProcessor, 1 extrn _HalpProcessorPCR:DWORD extrn _HalpSystemHardwareLock:DWORD extrn _HalpFindFirstSetRight:BYTE extrn _Sp8259PerProcessorMode:BYTE EXTRNP _KeSetTimeIncrement,2,IMPORT EXTRNP _HalpMcaQueueDpc, 0 extrn _SpType:BYTE
; ; Constants used to initialize timer 0 ;
TIMER1_DATA_PORT0 EQU 40H ; Timer1, channel 0 data port TIMER1_CONTROL_PORT0 EQU 43H ; Timer1, channel 0 control port TIMER1_IRQ EQU 0 ; Irq 0 for timer1 interrupt
COMMAND_8254_COUNTER0 EQU 00H ; Select count 0 COMMAND_8254_RW_16BIT EQU 30H ; Read/Write LSB firt then MSB COMMAND_8254_MODE2 EQU 4 ; Use mode 2 COMMAND_8254_BCD EQU 0 ; Binary count down COMMAND_8254_LATCH_READ EQU 0 ; Latch read command
PERFORMANCE_FREQUENCY EQU 1193182
; ; ==== Values used for System Clock ==== ;
; ; Convert the interval to rollover count for 8254 Timer1 device. ; Timer1 counts down a 16 bit value at a rate of 1.193181667M counts-per-sec. ; ; ; The best fit value closest to 10ms (but not below) is 10.0144012689ms: ; ROLLOVER_COUNT 11949 ; TIME_INCREMENT 100144 ; Calculated error is -.0109472 s/day ; ; The best fit value closest to 15ms (but not above) is 14.9952019ms: ; ROLLOVER_COUNT 17892 ; TIME_INCREMENT 149952 ; Calculated error is -.0109472 s/day ; ; On 486 class machines or better we use a 10ms tick, on 386 ; class machines we use a 15ms tick ;
ROLLOVER_COUNT_10MS EQU 11949 TIME_INCREMENT_10MS EQU 100144
; ; Value for KeQueryPerf retries. ;
MAX_PERF_RETRY equ 3 ; Odly enough 3 is plenty.
_DATA SEGMENT DWORD PUBLIC 'DATA'
; ; The following array stores the per microsecond loop count for each ; central processor. ;
public _HalpIpiClock _HalpIpiClock dd 0 ; Processors to IPI clock pulse to
public HalpPerfCounterLow public HalpPerfCounterHigh HalpPerfCounterLow dd 0 HalpPerfCounterHigh dd 0 HalpPerfP0Value dd 0 HalpCalibrateFlag db 0 db 0 dw 0
HalpRollOverCount dd 0
public _HalpClockWork, _HalpClockSetMSRate, _HalpClockMcaQueueDpc _HalpClockWork label dword _HalpClockSetMSRate db 0 _HalpClockMcaQueueDpc db 0 _bReserved1 db 0 _bReserved2 db 0
; ; Storage for variable to ensure that queries are always ; greater than the last. ;
HalpLastQueryLowValue dd 0 HalpLastQueryHighValue dd 0 HalpForceDataLock dd 0
; endmod
_DATA ends
_TEXT SEGMENT DWORD PUBLIC 'CODE' ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
page ,132 subttl "Initialize Clock" ;++ ; ; VOID ; HalpInitializeClock ( ; ) ; ; Routine Description: ; ; This routine initialize system time clock using 8254 timer1 counter 0 ; to generate an interrupt at every 15ms interval at 8259 irq0 ; ; See the definition of TIME_INCREMENT and ROLLOVER_COUNT if clock rate ; needs to be changed. ; ; Arguments: ; ; None ; ; Return Value: ; ; None. ; ;-- cPublicProc _HalpInitializeClock ,0
; ; Use 15ms or 10ms clock tick? ;
mov edx, TIME_INCREMENT_10MS ; yes, use 10ms clock mov ecx, ROLLOVER_COUNT_10MS ; ; Fill in PCR value with TIME_INCREMENT ; (edx) = TIME_INCREMENT ; (ecx) = ROLLOVER_COUNT ; cmp byte ptr PCR[PcHal.PcrNumber], 0 jne short icl_10
push ecx stdCall _KeSetTimeIncrement, <edx, edx> pop ecx
pushfd ; save caller's eflag cli ; make sure interrupts are disabled
; ; Set clock rate ; (ecx) = RollOverCount ;
mov al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE2 out TIMER1_CONTROL_PORT0, al ;program count mode of timer 0 IoDelay mov al, cl out TIMER1_DATA_PORT0, al ; program timer 0 LSB count IoDelay mov al,ch out TIMER1_DATA_PORT0, al ; program timer 0 MSB count
popfd ; restore caller's eflag mov HalpRollOverCount, ecx ; Set RollOverCount & initialized stdRET _HalpInitializeClock
icl_10: pushfd ; save caller's eflag cli ; make sure interrupts are disabled ; ; initialize clock, non-p0 ; (ecx) = ROLLOVER_COUNT ;
mov al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE2 out TIMER1_CONTROL_PORT0, al ;program count mode of timer 0 IoDelay mov al, cl out TIMER1_DATA_PORT0, al ; program timer 0 LSB count IoDelay mov al,ch out TIMER1_DATA_PORT0, al ; program timer 0 MSB count
popfd ; restore caller's eflag stdRET _HalpInitializeClock
stdENDP _HalpInitializeClock
;++ ; ; 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. ;-- cPublicProc _HalCalibratePerformanceCounter,3
mov eax, [esp+4] ; ponter to Number pushfd ; save previous interrupt state cli ; disable interrupts (go to high_level)
lock dec dword ptr [eax] ; count down
@@: cmp dword ptr [eax], 0 ; wait for all processors to signal jnz short @b
test _Sp8259PerProcessorMode, SP_SMPCLOCK jz short cal_exit ; 8254 per processor?
xor ecx, ecx mov al, COMMAND_8254_LATCH_READ+COMMAND_8254_COUNTER0 ; Latch PIT Ctr 0 command. out TIMER1_CONTROL_PORT0, al IODelay in al, TIMER1_DATA_PORT0 ; Read PIT Ctr 0, LSByte. IODelay movzx ecx, al in al, TIMER1_DATA_PORT0 ; Read PIT Ctr 0, MSByte. mov ch, al ; (CX) = PIT Ctr 0 count.
cmp byte ptr PCR[PcHal.PcrNumber], 0 ; is this the processor jz short cal_p0 ; which updates HalpPerfCounter?
@@: cmp HalpCalibrateFlag, 0 ; wait for P0 to post it's counter jz short @b
sub ecx, HalpPerfP0Value ; compute difference neg ecx mov PCR[PcHal.PcrPerfSkew], ecx
cal_exit: popfd stdRET _HalCalibratePerformanceCounter
cal_p0: mov HalpPerfP0Value, ecx ; post our timer value mov HalpCalibrateFlag, 1 ; signal we are done jmp short cal_exit
stdENDP _HalCalibratePerformanceCounter
page ,132 subttl "Query Performance Counter" ;++ ; ; LARGE_INTEGER ; KeQueryPerformanceCounter ( ; OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL ; ) ; ; Routine Description: ; ; This routine returns current 64-bit performance counter and, ; optionally, the Performance Frequency. ; ; Note this routine can NOT be called at Profiling interrupt ; service routine. Because this routine depends on IRR0 to determine ; the actual count. ; ; Also note that the performace counter returned by this routine ; is not necessary the value when this routine is just entered. ; The value returned is actually the counter value at any point ; between the routine is entered and is exited. ; ; Arguments: ; ; PerformanceFrequency [TOS+4] - optionally, supplies the address ; of a variable to receive the performance counter frequency. ; ; Return Value: ; ; Current value of the performance counter will be returned. ; ;--
; ; Parameter definitions ;
KqpcFrequency EQU [esp+20] ; User supplied Performance Frequence RetryPerfCount EQU [esp] ; Local retry variable
cPublicProc _KeQueryPerformanceCounter ,1
push ebx push esi push edi push 0 ; make space for RetryPerfCount
; ; First check to see if the performance counter has been initialized yet. ; Since the kernel debugger calls KeQueryPerformanceCounter to support the ; !timer command, we need to return something reasonable before 8254 ; initialization has occured. Reading garbage off the 8254 is not reasonable. ; cmp HalpRollOverCount, 0 jne short Kqpc11 ; ok, perf counter has been initialized
; ; Initialization hasn't occured yet, so just return zeroes. ; mov eax, 0 mov edx, 0 jmp Kqpc50
Kqpc11: pushfd cli
Kqpc20: lea eax, _HalpSystemHardwareLock ACQUIRE_SPINLOCK eax, Kqpc198
; ; Fetch the base value. Note that interrupts are off. ; ; NOTE: ; Need to watch for Px reading the 'CounterLow', P0 updates both ; then Px finishes reading 'CounterHigh' [getting the wrong value]. ; After reading both, make sure that 'CounterLow' didn't change. ; If it did, read it again. This way, we won't have to use a spinlock.
@@: mov ebx, HalpPerfCounterLow mov esi, HalpPerfCounterHigh ; [esi:ebx] = Performance counter
cmp ebx, HalpPerfCounterLow ; jne short @b
; ; Fetch the current counter value from the hardware ;
; ; Background: Belize style systems have an 8254 per Processor. ; ; In short the original implementation kinda assumes that each ; timer on each processor will be in perfect sycnh with each other. ; This is a bad assumption, and the reason why we have attempted ; to use only the timer on P0. ; ; There is an existing window where the return value may not be accurate. ; The window will occur when multiple queries are made back to back ; in an MP environment, and there are a lot of IPIs going on. Intuitive, ; right. The problem is that this routine may return a value with the ; the hardware system timer on P0 that has already generated an interrupt ; and reset its rollover, but the software has yet to process the interrupt ; to update the performance counter value. When this occurs, the second ; querry will seem to have a lower value than the first. ; ; So, why don't I just fix it. Well the cause of the problem is the ; overhead associated with handling the interrupt, and the fact that ; the IPI has a higher IRQL. In addition, a busy system could be ; issueing multiple IPIs back to back, which could extend this window ; even further. ; ; I have managed to close the window most of the way for most normal ; conditions. It takes several minutes on a busy system, with ; multiple applications running with back to back queries to get ; an invalid value. It can happen though. ; ; A retry implementation has been instrumented on top off the ; Indexed IO implementation to finally close the window. ; It seems to work OK. ; ; In reality, I think the fix is sufficient. The performance counter ; is not designed propperly (via only software) to yield very accurate ; values on sub timer tic (10-15msec) ranges on multiprocessor systems. ; ; Problems with this design: ; ; On an idle system threads executing from P0 will always ; use less overhead than threads executing on P1. ; On a ProLiant 2000 with 2 P5-66s the difference in 2 ; consecutive KeQueryPerformanceCounter calls from P0 ; is about 14, while from P1 is about 22. Unfortunately ; on a busy system P0 performs about the same, but P1 ; is much slower due to the overhead involved in performing ; an Indexed_IO. This means the busyier your system gets ; the less accurate your performance values will become. ; ; The solution: ; ; A system wide hardware timer needs to be used. This is about the ; only way to get accurate performance numbers from multiple ; processors without causing unnecessary software overhead. ; ; Supposedly there is a 48 bit counter that we may be able to use ; with SystemPro XL, and ProLiant systems, unfortunately it does ; not appear that any OS is currently using this feature, so ; its dependability may be suspect. ; ; JSL ;
; ; Essentially all we are doing is always using the timer value on P0. ; The indexed_io is a mechanism for one processor to access IOSPACE ; on another processor's IOSPACE. I suspect this will have a greater ; impact on performance than just reading the timer locally. ; By using the indexed_io you are gauranteed of going out on the bus. ; ; But, hey if the user understands anything about performance, they ; know that there will be some amount of overhead each time you make ; this KeQueryPerformanceCounter call. ;
; ; Increment the Retry counter now for convenience ;
inc dword ptr RetryPerfCount+4
; ; This is Belize specific. ;
cmp _SpType, SMP_SYSPRO2 jne timer_p0
; ; Only use Indexed_IO on a nonP0 processor ;
cmp byte ptr PCR[PcHal.PcrNumber], 0 ; is this the processor je timer_p0 ; which updates HalpPerfCounter?
; ; So read the timer of P0. ;
push ebx mov bl, 0 mov al, COMMAND_8254_LATCH_READ+COMMAND_8254_COUNTER0 ; Latch PIT Ctr 0 command. INDEXED_IO_WRITE bl,TIMER1_CONTROL_PORT0,al IODelay INDEXED_IO_READ bl,TIMER1_DATA_PORT0 ; Read PIT Ctr 0, LSByte. movzx ecx, al INDEXED_IO_READ bl,TIMER1_DATA_PORT0 ; Read PIT Ctr 0, MSByte. IODelay mov ch,al ; (CX) = PIT Ctr 0 count. pop ebx
lea eax, _HalpSystemHardwareLock RELEASE_SPINLOCK eax jmp short TimerValDone
timer_p0:
mov al, COMMAND_8254_LATCH_READ+COMMAND_8254_COUNTER0 ;Latch PIT Ctr 0 command. out TIMER1_CONTROL_PORT0, al IODelay in al, TIMER1_DATA_PORT0 ;Read PIT Ctr 0, LSByte. IODelay movzx ecx,al ;Zero upper bytes of (ECX). in al, TIMER1_DATA_PORT0 ;Read PIT Ctr 0, MSByte. mov ch, al ;(CX) = PIT Ctr 0 count.
lea eax, _HalpSystemHardwareLock RELEASE_SPINLOCK eax
TimerValDone:
mov al, PCR[PcHal.PcrNumber] ; get current processor #
; ; This is Belize specific. ;
cmp _SpType, SMP_SYSPRO2 je NoCPU0Update
; ; If not on P0 then make sure P0 isn't in the process of ; of updating its timer. Do this by checking the status ; of the PIC using indexed_io. ; Make sure that only one thread at time reads P0 PIC. ;
cmp al, 0 ; Are we p0 je NoCPU0Update
; ; Check IRQL at PO before going any further ;
push edx mov edx, _HalpProcessorPCR[0] ; PCR of processor 0 cmp byte ptr ds:[edx].PcIrql,CLOCK2_LEVEL pop edx jb short NoCPU0Update push ebx
Kqpc11p: ; ; Check P0 PIC and confirm Timer Interrupt status. ; Perform Spin Lock before reading P0 PIC. ;
pushfd cli lea ebx, _HalpSystemHardwareLock ACQUIRE_SPINLOCK ebx, Kqpc198p ; Spin if another thread is here INDEXED_IO_READ 0,PIC1_PORT1 ; read CPU 0 port 21 for masks RELEASE_SPINLOCK ebx popfd pop ebx test al, 1h ; check for IRQ 0 masked off mov al, PCR[PcHal.PcrNumber] ; get current processor # jz short NoCPU0Update
; ; Try ReadAgain if below retry count. ;
cmp RetryPerfCount+4, MAX_PERF_RETRY ja short NoCPU0Update
ReadAgain: ; ; This readagain is only executed when P0 is ; at CLOCK2_LEVEL or greater. ; AND when Timer IRQ is active (ie interrupt in progress). ; This is done to close the window of an interrupt ; occuring and the irql hasn't been raised yet. ;
popfd jmp Kqpc11 ; go back and read again
NoCPU0Update:
; ; Now enable interrupts such that if timer interrupt is pending, it can ; be serviced and update the PerformanceCounter. Note that there could ; be a long time between the sti and cli because ANY interrupt could come ; in in between. ;
popfd ; don't re-enable interrupts if nop ; the caller had them off!
jmp $+2 ; allow interrupt in case counter ; has wrapped
pushfd cli
; ; In Belize mode we do not care about this since we use the P0 clock. ;
cmp _SpType, SMP_SYSPRO2 je short Kqpc35 ; ; If we moved processors while interrupts were enabled, start over ;
cmp al, PCR[PcHal.PcrNumber] jne Kqpc20 Kqpc35:
; ; Fetch the base value again. ;
@@: mov eax, HalpPerfCounterLow mov edx, HalpPerfCounterHigh ; [edx:eax] = new counter value cmp eax, HalpPerfCounterLow ; did it move? jne short @b ; re-read
; ; Compare the two reads of Performance counter. If they are different, ; start over ;
cmp eax, ebx jne Kqpc20 cmp edx, esi jne Kqpc20
neg ecx ; PIT counts down from 0h add ecx, HalpRollOverCount
; ; In Belize mode we do not care about this since we use the P0 clock. ;
cmp _SpType, SMP_SYSPRO2 je short Kqpc37 add ecx, PCR[PcHal.PcrPerfSkew] Kqpc37:
popfd ; restore interrupt flag
xchg ecx, eax mov ebx, edx cdq
add eax, ecx adc edx, ebx ; [edx:eax] = Final result
; ; We only want to execute this code In Belize mode. ;
cmp _SpType, SMP_SYSPRO2 jne Kqpc50
; ; Ok compare this result with the last result. ; We will force the value to be greater than the last value, ; after we have used up all of our retry counts. ; ; This should slam shut that annoying Window that causes ; applications to recieve a 2nd query less then the first. ; ; This is not an most elegant solution, but fortunately ; this situation is hit only on a rare occasions. ; ; Yeah, I know that this value can roll over ; if someone runs some perf tests, and comes back in a ; few weeks and wants to run some more. In this situation ; the the very first call to this function will yield an ; invalid value. This is the price of the fix. ;
; ; Protect the global data with a spinlock ;
push ebx Kqpc42: pushfd cli lea ebx, HalpForceDataLock ACQUIRE_SPINLOCK ebx, Kqpc199 ; Spin if another thread is here
; ; Compare this value to the last value, if less then ; fix it up. ;
cmp edx, HalpLastQueryHighValue ja short Kqpc44
cmp eax, HalpLastQueryLowValue ja short Kqpc44
; ; Release the spinlock. ;
RELEASE_SPINLOCK ebx popfd pop ebx
; ; Try Again if below count. ;
cmp RetryPerfCount, MAX_PERF_RETRY jbe Kqpc11 ; go back and read again
; ; Exhausted retry count so Fix up the values and leave. ;
mov eax, HalpLastQueryLowValue inc eax mov edx, HalpLastQueryHighValue
jmp short Kqpc50
Kqpc44: ; ; Save off the perf values for next time. ;
mov HalpLastQueryLowValue, eax mov HalpLastQueryHighValue, edx
; ; Release the spinlock. ;
RELEASE_SPINLOCK ebx popfd pop ebx
; ; Return the counter ;
Kqpc50: ; return value is in edx:eax
; ; Return the freq. if caller wants it. ;
or dword ptr KqpcFrequency, 0 ; is it a NULL variable? jz short Kqpc99 ; if z, yes, go exit
mov ecx, KqpcFrequency ; (ecx)-> Frequency variable mov DWORD PTR [ecx], PERFORMANCE_FREQUENCY ; Set frequency mov DWORD PTR [ecx+4], 0
Kqpc99: pop edi ; remove locals pop edi ; restore regs pop esi pop ebx
stdRET _KeQueryPerformanceCounter
Kqpc198: popfd SPIN_ON_SPINLOCK eax,<Kqpc11>
; ; This is just where we are spinning while we are waiting to read the PIC ; Kqpc198p: popfd SPIN_ON_SPINLOCK ebx,<Kqpc11p> ; ; This is just where we are spinning while waiting global last perf data ; Kqpc199: popfd SPIN_ON_SPINLOCK ebx,<Kqpc42>
stdENDP _KeQueryPerformanceCounter ; endmod
page ,132 subttl "System Clock Interrupt" ;++ ; ; Routine Description: ; ; ; This routine is entered as the result of an interrupt generated by CLOCK2. ; Its function is to dismiss the interrupt, raise system Irql to ; CLOCK2_LEVEL, update performance counter and transfer control to the ; standard system routine to update the system time and the execution ; time of the current thread ; and process. ; ; ; Arguments: ; ; None ; Interrupt is disabled ; ; Return Value: ; ; Does not return, jumps directly to KeUpdateSystemTime, which returns ; ; Sets Irql = CLOCK2_LEVEL and dismisses the interrupt ; ;-- ENTER_DR_ASSIST Hci_a, Hci_t
cPublicProc _HalpClockInterrupt ,0
; ; Save machine state in trap frame ;
ENTER_INTERRUPT Hci_a, Hci_t ; ; (esp) - base of trap frame ;
; ; dismiss interrupt and raise Irql ;
Hci10: push CLOCK_VECTOR sub esp, 4 ; allocate space to save OldIrql stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL,CLOCK_VECTOR,esp> or al,al ; check for spurious interrupt jz Hci100
; ; Update performance counter ;
mov eax, HalpRollOverCount xor ebx, ebx add HalpPerfCounterLow, eax ; update performace counter adc HalpPerfCounterHigh, ebx
cmp _HalpClockWork, ebx jz short Hci20
cmp _HalpClockMcaQueueDpc, bl jz short Hci20
mov _HalpClockMcaQueueDpc, bl
; ; Queue MCA Dpc ; stdCall _HalpMcaQueueDpc
Hci20: ; ; (esp) = OldIrql ; (esp+4) = Vector ; (esp+8) = base of trap frame ; (ebp) = address of trap frame ; (eax) = time increment ; mov eax, TIME_INCREMENT_10MS
mov ebx, _HalpIpiClock ; Emulate clock ticks to any processors? or ebx, ebx jz _KeUpdateSystemTime@0
; ; On the SystemPro we know the processor which needs an emulated clock tick. ; Just set that processors bit and IPI him ;
@@: movzx ecx, _HalpFindFirstSetRight[ebx] ; lookup first processor btr ebx, ecx mov ecx, _HalpProcessorPCR[ecx*4] ; PCR of processor mov [ecx].PcHal.PcrIpiClockTick, 1 ; Set internal IPI event or ebx, ebx ; any other processors? jnz short @b ; yes, loop
stdCall _HalRequestIpi, <_HalpIpiClock> ; IPI the processor(s)
mov eax, TIME_INCREMENT_10MS jmp _KeUpdateSystemTime@0
Hci100: add esp, 8 SPURIOUS_INTERRUPT_EXIT
stdENDP _HalpClockInterrupt
page ,132 subttl "NonPrimaryClockTick" ;++ ; ; VOID ; HalpNonPrimaryClockInterrupt ( ; ); ; ; Routine Description: ; ISR for clock interrupts for every processor except one. ; ; Arguments: ; ; None. ; Interrupt is dismissed ; ; Return Value: ; ; None. ; ;-- ENTER_DR_ASSIST Hni_a, Hni_t cPublicProc _HalpNonPrimaryClockInterrupt ,0 ENTER_INTERRUPT Hni_a, Hni_t
; Dismiss interrupt and raise irql
push CLOCK_VECTOR sub esp, 4 ; allocate space to save OldIrql stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL,CLOCK_VECTOR,esp> or al,al ; check for spurious interrupt jz Hni100
; TOS const PreviousIrql stdCall _KeUpdateRunTime,<dword ptr [esp]>
INTERRUPT_EXIT ; will do an iret
Hni100: add esp, 8 SPURIOUS_INTERRUPT_EXIT
stdENDP _HalpNonPrimaryClockInterrupt
page ,132 subttl "Emulate NonPrimaryClockTick" ;++ ; ; VOID ; HalpSWNonPrimaryClockTick ( ; ); ; ; Routine Description: ; On the SystemPro the second processor does not get it's own clock ; ticks. The HAL emulates them by sending an IPI which sets an overloaded ; software interrupt level of SWCLOCK_LEVEL. When the processor attempts ; to lower it's irql level below SWCLOCK_LEVEL the soft interrupt code ; lands us here as if an interrupt occured. ; ; Arguments: ; ; None. ; Interrupt is dismissed ; ; Return Value: ; ; None. ; ENTER_DR_ASSIST Hsi_a, Hsi_t
public _HalpSWNonPrimaryClockTick _HalpSWNonPrimaryClockTick proc ; ; Create IRET frame on stack ; pop eax pushfd push cs push eax ; ; Save machine state in trap frame ;
ENTER_INTERRUPT Hsi_a, Hsi_t
public _HalpSWNonPrimaryClockTick2ndEntry _HalpSWNonPrimaryClockTick2ndEntry:
; Save previous IRQL and set new priority level
push fs:PcIrql ; save previous IRQL mov byte ptr fs:PcIrql, SWCLOCK_LEVEL ; set new irql btr dword ptr fs:PcIRR, SWCLOCK_LEVEL ; clear the pending bit in IRR
sti
; TOS const PreviousIrql stdCall _KeUpdateRunTime,<dword ptr [esp]>
SOFT_INTERRUPT_EXIT ; will do an iret
_HalpSWNonPrimaryClockTick endp
;++ ; ; ULONG ; HalSetTimeIncrement ( ; IN ULONG DesiredIncrement ; ) ; ; /*++ ; ; Routine Description: ; ; This routine initialize system time clock to generate an ; interrupt at every DesiredIncrement interval. ; ; Arguments: ; ; DesiredIncrement - desired interval between every timer tick (in ; 100ns unit.) ; ; Return Value: ; ; The *REAL* time increment set. ;-- cPublicProc _HalSetTimeIncrement,1
mov eax, TIME_INCREMENT_10MS ; yes, use 10ms clock stdRET _HalSetTimeIncrement
stdENDP _HalSetTimeIncrement
_TEXT ends end
|