mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1091 lines
28 KiB
1091 lines
28 KiB
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
|
|
|