windows-nt/Source/XPSP1/NT/base/hals/halsp/i386/spclock.asm
2020-09-26 16:20:57 +08:00

1092 lines
28 KiB
NASM
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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