1293 lines
35 KiB
NASM
Raw Normal View History

2001-01-01 00:00:00 +01:00
title "Interval Clock Interrupt"
;++
;
; Copyright (c) 1989-1993 Microsoft Corporation
; Copyright (c) 1992, 1993 Wyse Technology
;
; Module Name:
;
; wyclock.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.
;
; John Fuller (o-johnf) 1-Apr-92
; convert to Wyse 7000i MP system, clock goes to cpu's local timer,
; profile interrupt and performance counter use 8254 timer1 counter 0,
; use cpu's local timer to initialize stall execution.
;--
.386p
.xlist
include hal386.inc
include callconv.inc ; calling convention macros
include i386\ix8259.inc
include i386\kimacro.inc
include mac386.inc
include i386\wy7000mp.inc
.list
extrn ReadMyCpuReg:NEAR
extrn WriteMyCpuReg:NEAR
EXTRNP _DbgBreakPoint,0,IMPORT
EXTRNP _KeUpdateSystemTime,0
EXTRNP _KeUpdateRunTime,1,IMPORT
EXTRNP _KeProfileInterrupt,1,IMPORT
EXTRNP _KeSetTimeIncrement,2,IMPORT
EXTRNP Kei386EoiHelper,0,IMPORT
EXTRNP _HalEndSystemInterrupt,2
EXTRNP _HalBeginSystemInterrupt,3
EXTRNP _HalpAcquireCmosSpinLock ,0
EXTRNP _HalpReleaseCmosSpinLock ,0
extrn _HalpIRQLtoCPL:BYTE
extrn _HalpIRQLtoVector: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
TIMER2_DATA_PORT0 EQU 48H ; Timer1, channel 0 data port
TIMER2_CONTROL_PORT0 EQU 4BH ; 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 1193000
;
; Constants used to initialize CMOS/Real Time Clock
;
D_INT032 EQU 8E00h ; access word for 386 ring 0 interrupt gate
RTC_OFFSET_SECOND EQU 0 ; second field of RTC memory
RTC_OFFSET_MINUTE EQU 2 ; minute field of RTC memory
RTC_OFFSET_HOUR EQU 4 ; hour field of RTC memory
RTC_OFFSET_DAY_OF_WEEK EQU 6 ; day-of-week field of RTC memory
RTC_OFFSET_DATE_OF_MONTH EQU 7 ; date-of-month field of RTC memory
RTC_OFFSET_MONTH EQU 8 ; month field of RTC memory
RTC_OFFSET_YEAR EQU 9 ; year field of RTC memory
RTC_OFFSET_CENTURY EQU 32h ; Century field of RTC memory
;
; ==== Values used for System Clock ====
;
; Convert the interval to rollover count for ICU local timer (ltimer) device.
; Since ltimer counts down a 16 bit value at a one count every 240ns and the
; interval is in units of 100ns, the computation is:
; RolloverCount = (Interval*10)/24 = (Interval*5)/12
; Therefore, for an integral RolloverCount, Interval must be a multiple of
; 1.2us. Since RolloverCount is a 16-bit count the maximum Interval is
; (65535*12)/5 or 15.7284ms.
;
; For the convience of HalpInitializeStallExecution the TIME_INCREMENT
; should be chosen to be an integral number of microseconds. Hence
; TIME_INCREMENT should be a multiple of 60 (6us).
;
; The default Interrupt interval is 10.002ms (8335 * 1.2us)
;
TIME_INCREMENT EQU 100020 ; 10.002ms
ROLLOVER_COUNT EQU (TIME_INCREMENT*5)/12
;
; ==== Values used for performance counter
;
; Maximum counter value for timer1 (8254) and its corresponding interrupt
; interval. These values are for profiling and performance counter support.
;
; This value is equivalent to a value of zero (counter is 16-bit).
; So, the rollover rate is approximately 18.2 Hz.
; Note this value will be used if no profiling support.
;
MAXIMUM_ROLLOVER_COUNT Equ 0FFFFh
MAXIMUM_ROLLOVER_INTERVAL Equ 861D2h ; in 100ns units
_DATA SEGMENT DWORD PUBLIC 'DATA'
;
; The following array stores the per microsecond loop count for each
; central processor.
;
;
; 8254 spinlock. This must be acquired before touching the 8254 chip.
;
public _Halp8254Lock
_Halp8254Lock dd 0
;
; Init Stall count must have a spin lock. This lock is held a long time
; but only processors tring to initialize the stall count will wait.
;
iscSpinLock dd 0
HalpProfileRolloverCnt dd MAXIMUM_ROLLOVER_COUNT
public HalpPerfCounterLow
public HalpPerfCounterHigh
HalpPerfCounterLow dd 0
HalpPerfCounterHigh dd 0
HalpRollOverCount dd 0
HalpNextRolloverCount dd MAXIMUM_ROLLOVER_COUNT
HalpProfilingStopped dd -1 ;stopped when this is negative
HalpPerfCounterInit dd 0
public HalpHackEsp
HalpHackEsp dd 0
_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 the local timer
; to generate an interrupt at every 15ms interval
;
; See the definition of TIME_INCREMENT and ROLLOVER_COUNT if clock rate
; needs to be changed.
;
; Arguments:
;
; None
;
; Return Value:
;
; None.
;
;--
cPublicProc _HalpInitializeClock ,0
enproc 4
;
; Fill in PCR value with TIME_INCREMENT
;
mov eax, TIME_INCREMENT
stdCall _KeSetTimeIncrement, <eax, eax>
pushfd ; save caller's eflag
cli ; make sure interrupts are disabled
;
; Set clock rate for Wyse local timer
;
mov al, ICU_CNT_REG
mov dx, My+CpuPtrReg
out dx, al
mov dx, My+CpuDataReg
mov ax, ROLLOVER_COUNT
out dx, ax
;
; Initialize rollover count for performance counter and profiling
;
cmp fs:PcHal.pchPrNum, 0 ;master cpu?
jne short HICexit ;jump if not
;
; Since this happens only on master cpu before other cpu's are started
; we do not need to aquire Halp8254Lock.
;
stdCall _HalpSetRolloverCount, <MAXIMUM_ROLLOVER_COUNT>
HICexit:
exproc 4
popfd ; restore caller's eflag
stdRET _HalpInitializeClock
stdENDP _HalpInitializeClock
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:
;
; Current implementation assumes that all the processors share
; the same Real Time Clock. So, the dispatcher database lock is
; acquired before entering this routine to guarantee only one
; processor can access the routine.
;
;--
isc00: sti
SPIN_ON_SPINLOCK eax, <isc01>
KiseInterruptCount equ [ebp-12] ; local variable
cPublicProc _HalpInitializeStallExecution ,1
enproc 7
push ebp ; save ebp
mov ebp, esp ; set up 12 bytes for local use
sub esp, 12
pushfd ; save caller's eflag
;
; Initialize Cpu Local Timer to interrupt us for every 15ms at
; PROFILE_LEVEL-19
;
;
; acquire spin lock to prevent two processors from doing this routine at
; the same time
;
isc01: cli
lea eax, iscSpinLock
ACQUIRE_SPINLOCK eax, isc00
;
; Get and save current local interrupt pointer register
;
push ICU_LIPTR
call ReadMyCpuReg
push eax ; save for later
push lipTimer ; set to only timer at PROFILE_LEVEL-19
;(must use an otherwise unused level
; or at least one that won't generate
; an interrupt until all processors
; have been started)
push ICU_LIPTR
call WriteMyCpuReg
;
; Get and save current ICU interrupt masks
;
push ICU_IMR0
call ReadMyCpuReg ; get local interrupt masks (low)
shl eax, 16
in ax, dx ; get local interrupt masks (high)
push eax ; save the masks
movzx ecx, _HalpIRQLtoCPL[PROFILE_LEVEL-19]
mov eax, IMR_MASK ; all ints masked out
btr eax, ecx ; clear bit for local timer
out dx, ax ; set new mask (low)
rol eax, 16
out dx, ax ; set new mask (high)
;
; Since RTC interrupt will come from PROFILE_LEVEL-19, we need to
; Save original IDT descriptor and set the descriptor to point to
; our own handler.
;
movzx ecx, _HalpIRQLtoVector[PROFILE_LEVEL-19]
sidt fword ptr [ebp-8] ; get IDT address
mov edx, [ebp-6] ; (edx)->IDT
push dword ptr [edx+8*ecx]
; (TOS) = original desc of IRQ 8
push dword ptr [edx+8*ecx + 4]
; each descriptor has 8 bytes
push edx ; (TOS) -> IDT
mov eax, offset FLAT:RealTimeClockHandler
mov word ptr [edx+8*ecx], ax
; Lower half of handler addr
mov word ptr [edx+8*ecx+2], KGDT_R0_CODE
; set up selector
mov word ptr [edx+8*ecx+4], D_INT032
; 386 interrupt gate
shr eax, 16 ; (ax)=higher half of handler addr
mov word ptr [edx+8*ecx+6], ax
mov dword ptr KiseinterruptCount, 0 ; set no interrupt yet
mov dx, My+CpuPriortyLevel
in ax, dx ;get old CPL
shl eax, 16
push ICU_CNT_REG
call ReadMyCpuReg ;read old tick count
push eax ;save for later
push ROLLOVER_COUNT
push ICU_CNT_REG
call WriteMyCpuReg
movzx eax, _HalpIRQLtoCPL[PROFILE_LEVEL-19]
inc eax ;allow this level interrupt
mov dx, My+CpuPriortyLevel
out dx, ax
;
; Now enable the interrupt and start the counter
; (As a matter of fact, only local timer can come through.)
;
xor eax, eax ; (eax) = 0, initialize loopcount
sti
kise10:
add eax, 1 ; increment the loopcount
jnz short kise10
;
; Counter overflowed
;
stdCall _DbgBreakPoint
;
; Our RealTimeClock interrupt handler. The control comes here through
; irq 8.
; Note: we discard first two real time clock interrupts and compute the
; permicrosecond loopcount on receiving of the third real time
; interrupt. This is because the first interrupt may be already
; pending in which case the second is generated based on the previous
; real time tick interval.
;
RealTimeClockHandler:
inc dword ptr KiseInterruptCount ; increment interrupt count
cmp dword ptr KiseInterruptCount, 2 ; Is this 1st or 2nd interrupt?
ja short kise25 ; no, its the third go process it
pop eax ; get rid of original ret addr
push offset FLAT:kise10 ; set new return addr
;
; dismiss interrupt at ICU
;
mov dx, My+CpuIntCmd
@@: in ax, dx
test eax, ICU_CMD_BUSY
jnz @B
mov al, ICU_CLR_INSERV1 ; clear interrupt in service bit
out dx, ax
xor eax, eax ; reset loop counter
cPublicProc _HalpICUSpurious ,0
iretd
stdENDP _HalpICUSpurious
kise25:
;
; ** temporary - check for incorrect KeStallExecutionProcessorLoopCount
;
if DBG
cmp eax, 0
jnz short kise30
stdCall _DbgBreakPoint
endif
; never return
;
; ** End temporay code
;
kise30:
xor edx, edx ; (edx:eax) = divident
mov ecx, TIME_INCREMENT / 10; (ecx) = time spent in the loop
div ecx ; (eax) = loop count per microsecond
cmp edx, 0 ; Is remainder =0?
jz short kise40 ; yes, go kise40
inc eax ; increment loopcount by 1
kise40:
movzx ecx, byte ptr [ebp+8] ; Current processor number
mov fs:PcStallScaleFactor, eax ; save in per processor data
;
; Reset return address to kexit
;
pop eax ; discard original return address
push offset FLAT:kexit ; return to kexit
and word ptr [esp+8], NOT 0200H ; Disable interrupt upon return
iretd
kexit: ; Interrupts are disabled
; push original tick count (already on stack)
push ICU_CNT_REG
call WriteMyCpuReg ; restore original tick count
shr eax, 16 ; original CPL to AX
mov dx, My+CpuPriortyLevel
out dx, ax ; restore original CPL
pop edx ; (edx)->IDT
movzx ecx, _HalpIRQLtoVector[PROFILE_LEVEL-19]
pop [edx+8*Ecx+4]
; restore higher half of NMI desc
pop [edx+8*Ecx]
; restore lower half of NMI desc
; push original interrupt masks (already on stack)
push ICU_IMR1
call WriteMyCpuReg ; restore original mask (high)
rol eax, 16
out dx, ax ; restore original mask (low)
; push original local interrupt pointer (already on stack)
push ICU_LIPTR
call WriteMyCpuReg ; restore original LIPTR
;
; dismiss interrupt at ICU
;
mov dx, My+CpuIntCmd
@@: in ax, dx
test eax, ICU_CMD_BUSY
jnz @B
mov al, ICU_CLR_INSERV1 ; clear interrupt in service bit
out dx, ax
lea eax, iscSpinLock
RELEASE_SPINLOCK eax
exproc 7
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
mov ecx, MicroSeconds ; (ecx) = Microseconds
jecxz short kese10 ; return if no loop needed
mov eax, fs:PcStallScaleFactor ; get per microsecond
; loop count for the processor
mul ecx ; (eax) = desired loop count
if DBG
;
; Make sure we the loopcount is less than 4G and is not equal to zero
;
cmp edx, 0
jz short kese
stdCall _DbgBreakPoint ; stop ...
;; align 4
kese: cmp eax,0
jnz short kese0
stdCall _DbgBreakPoint ; stop ...
endif
kese0:
sub eax, 1 ; (eax) = (eax) - 1
jnz short kese0
kese10:
stdRET _KeStallExecutionProcessor
stdENDP _KeStallExecutionProcessor
;++
;
; PROFILING AND PERFORMANCE COUNTER SUPPORT
;
;--
;++
;
; HalStartProfileInterrupt(
; IN ULONG Reserved
; );
;
; Routine Description:
;
; What we do here is change the interrupt
; rate from the slowest thing we can get away with to the value
; that's been KeSetProfileInterval
;
;--
cPublicProc _HalStartProfileInterrupt ,1
;
; Prevent races by aquiring Halp8254Lock
;
pushfd
HStartPI01:
cli
test fs:PcHal.pchCurLiptr, lipGlobal
jnz short HStartPI08 ;jump if already started
lea eax, _Halp8254Lock
ACQUIRE_SPINLOCK eax, HStartPI10
;
; Mark profiling as active
;
inc HalpProfilingStopped ;protected by spinlock
jnz short HStartPI06 ;jump if RollerCount already set
;
; Set the interrupt rate to what is actually needed
;
stdCall _HalpSetRolloverCount, <HalpProfileRolloverCnt>
HStartPI06:
lea eax, _Halp8254Lock
RELEASE_SPINLOCK eax
push lipDefault+lipGlobalVal
push ICU_LIPTR
call WriteMyCpuReg
mov fs:PcHal.pchCurLiptr, eax
HStartPI08:
popfd
stdRET _HalStartProfileInterrupt
HStartPI10:
sti
SPIN_ON_SPINLOCK eax, HStartPI01
stdENDP _HalStartProfileInterrupt
;++
;
; HalStopProfileInterrupt(
; IN ULONG Reserved
; );
;
; Routine Description:
;
; What we do here is change the interrupt
; rate from the high profiling rate to the slowest thing we
; can get away with for PerformanceCounter rollover notification.
;
;--
cPublicProc _HalStopProfileInterrupt ,1
;
; Prevent races
;
pushfd
HStopPI01:
cli
test fs:PcHal.pchCurLiptr, lipGlobal
jz short HStopPI08 ;jump if already stopped
lea eax, _Halp8254Lock
ACQUIRE_SPINLOCK eax, HStopPI10
dec HalpProfilingStopped ;protected by spinlock
jns short HStopPI06 ;jump if still someone doing it
;
; Set the interrupt rate to "idle"
;
stdCall _HalpSetRolloverCount, <MAXIMUM_ROLLOVER_COUNT>
;
; Turn off profiling hit computation
;
HStopPI06:
lea eax, _Halp8254Lock
RELEASE_SPINLOCK eax
push lipDefault
push ICU_LIPTR
call WriteMyCpuReg
mov fs:PcHal.pchCurLiptr, eax
HStopPI08:
popfd
stdRET _HalStopProfileInterrupt
HStopPI10:
sti
SPIN_ON_SPINLOCK eax, HStopPI01
stdENDP _HalStopProfileInterrupt
;++
; ULONG
; HalSetProfileInterval (
; ULONG Interval
; );
;
; Routine Description:
;
; This procedure sets the interrupt rate (and thus the sampling
; interval) for the profiling interrupt.
;
; If profiling is active (KiProfilingStopped == 0) the actual
; hardware interrupt rate will be set. Otherwise, a simple
; rate validation computation is done.
;
; Arguments:
;
; (TOS+4) - Interval in 100ns unit.
;
; Return Value:
;
; Interval actually used by system.
;
;--
cPublicProc _HalSetProfileInterval ,1
mov edx, [esp+4] ; [edx] = interval in 100ns unit
pushfd
HSetPI01:
cli
lea eax, _Halp8254Lock
ACQUIRE_SPINLOCK eax, HSetPI10
push edx ; [TOS] = interval
;
; Convert the interval to rollover count for Timer1 device.
; Since timer1 counts down a 16 bit value at a rate of 1.193M counts-per-
; sec, the computation is:
; RolloverCount = (Interval * 0.0000001) * (1.193 * 1000000)
; = Interval * 0.1193
; = Interval * 1193 / 10000
;
mov eax, 1193
mul edx ; [edx:eax] = interval * 1193
mov ecx, 10000
div ecx ; [eax] = rollover count
test eax, 0FFFF0000H ; Is high word set?
jz short Kspi80 ; if z, no , go initialize 8254
pop eax ; else discard original interval
push MAXIMUM_ROLLOVER_INTERVAL ; set real interval
mov eax, MAXIMUM_ROLLOVER_COUNT ; use max. rollover count
Kspi80:
mov HalpProfileRolloverCnt, eax
test dword ptr HalpProfilingStopped,-1
js short Kspi90
stdCall _HalpSetRolloverCount, <eax>
Kspi90:
lea eax, _Halp8254Lock
RELEASE_SPINLOCK eax
pop eax ; (eax) = returned Interval
Popfd
stdRET _HalSetProfileInterval ; (eax) = cReturn interval
HSetPI10:
sti
SPIN_ON_SPINLOCK eax, HSetPI01
stdENDP _HalSetProfileInterval
page ,132
subttl "Set Performance Counter Rollover count"
;++
;
; VOID
; HalpSetRolloverCount (
; IN ULONG RolloverCount
; )
;
; Routine Description:
;
; This function initialize 8254 timer chip counter 0 to generate
; timer interrupt at the rate specified by caller. The timer 1 counter0
; will be initialized to use binary count down, 16-bit counter, and mode
; 2.
;
; Note that 8254 mode 2 will not use the new count immediately. The
; new count will be loaded at the end of current counting cycle.
;
; NOTE: Interrupts must already be disabled and Halp8254Lock must
; already be aquired!
;
; Arguments:
;
; RolloverCount [TOS+4] - Value used to set timer 1 counter 0's
; rollover counter.
;
; Return Value:
;
; None.
;
;--
;
; Parameter definitions
;
KsrcRolloverCount equ [esp+4]
cPublicProc _HalpSetRolloverCount ,1
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 ecx, KsrcRolloverCount
mov HalpNextRolloverCount, ecx ; set new count
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
mov HalpPerfCounterInit, 1 ; indicate performance counter has
; been initialized
stdRET _HalpSetRolloverCount
stdENDP _HalpSetRollOverCount
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+12] ; User supplied Performance Frequence
cPublicProc _KeQueryPerformanceCounter ,1
push ebx
push esi
;
; 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 HalpPerfCounterInit, 0
jne Kqpc01 ; ok, perf counter has been initialized
;
; Initialization hasn't occured yet, so just return zeroes.
;
mov eax, 0
mov edx, 0
jmp Kqpc20
Kqpc01:
Kqpc11: pushfd
cli
lea eax, _Halp8254Lock
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 @b
;
; Fetch the current counter value from the hardware
;
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, _Halp8254Lock
RELEASE_SPINLOCK eax
;
; 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
;
; Fetch the base value again.
;
@@:
mov eax, HalpPerfCounterLow
mov edx, HalpPerfCounterHigh ; [edx:eax] = new counter value
cmp eax, HalpPerfCounterLow
jne @b
;
; Compare the two reads of Performance counter. If they are different,
; simply returns the new Performance counter. Otherwise, we add the hardware
; count to the performance counter to form the final result.
;
cmp eax, ebx
jne short Kqpc20
cmp edx, esi
jne short Kqpc20
neg ecx ; PIT counts down from 0h
add ecx, HalpRolloverCount
add eax, ecx
adc edx, 0 ; [edx:eax] = Final result
;
; Return the counter
;
Kqpc20:
; 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 esi ; restore esi and ebx
pop ebx
stdRET _KeQueryPerformanceCounter
Kqpc198: popfd
SPIN_ON_SPINLOCK eax,<Kqpc11>
stdENDP _KeQueryPerformanceCounter
;++
;
; VOID
; HalCalibratePerformanceCounter (
; IN volatile PLONG Number
; )
;
; /*++
;
; 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.
;
; Return Value:
;
; None.
;--
cPublicProc _HalCalibratePerformanceCounter,1
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
;
; Nothing to calibrate on a Wyse MP machine. There is only a single
; 8254 device
;
popfd ; restore interrupt flag
stdRET _HalCalibratePerformanceCounter
stdENDP _HalCalibratePerformanceCounter
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
;
movzx eax, _HalpIRQLtoVector[CLOCK2_LEVEL]
push eax ; save our interrupt vector number
sub esp, 4 ; allocate space to save OldIrql
stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL,eax,esp>
or al,al ; check for spurious interrupt
jz Hci100
;
; (esp) = OldIrql
; (esp+4) = Vector
; (esp+8) = base of trap frame
; (ebp) = address of trap frame
;
mov eax, TIME_INCREMENT
cmp fs:PcHal.pchPrNum, 0 ; is this the master cpu?
je _KeUpdateSystemTime@0 ; if it is, update system time
sti
stdCall _KeUpdateRunTime,<dword ptr [esp]> ; othewise update runtime
INTERRUPT_EXIT ; lower irql to old value, iret
Hci100:
add esp, 8 ; spurious, no EndOfInterrupt
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
stdENDP _HalpClockInterrupt
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
; 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 = 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
;
movzx eax, _HalpIRQLtoVector[PROFILE_LEVEL]
push eax
sub esp, 4 ; allocate space to save OldIrql
stdCall _HalBeginSystemInterrupt, <PROFILE_LEVEL,eax,esp>
or al,al ; check for spurious interrupt
jz Hpi100
;
; (esp) = OldIrql
; (esp+4) = H/W vector
; (esp+8) = base of trap frame
;
test fs:PcHal.pchCurLiptr, lipGlobal
je Hpi90 ; if prof disable don't call kernel
stdCall _KeProfileInterrupt,<ebp> ; (ebp) = trap frame
Hpi90: INTERRUPT_EXIT
Hpi100:
add esp, 8 ; spurious, no EndOfInterrupt
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
stdENDP _HalpProfileInterrupt
;++
;
; Routine Description:
;
; This routine is entered as the result of a performance counter interrupt.
; Its function is to dismiss the interrupt, raise system Irql to
; PROFILE_LEVEL-1, update the performance counter, and generate the
; profile interrupts if any there are any active profiles.
;
; Arguments:
;
; None
; Interrupt is disabled
;
; Return Value:
;
; none
;
; Sets Irql = PROFILE_LEVEL-1 and dismisses the interrupt
;
;--
ENTER_DR_ASSIST Hpci_a, Hpci_t
cPublicProc _HalpPerfCtrInterrupt ,0
;
; Save machine state in trap frame
;
ENTER_INTERRUPT Hpci_a, Hpci_t
;
; (esp) - base of trap frame
;
movzx eax, _HalpIRQLtoVector[PROFILE_LEVEL-1]
push eax
sub esp, 4 ; allocate space to save OldIrql
stdCall _HalBeginSystemInterrupt, <PROFILE_LEVEL-1,eax,esp>
or al,al ; check for spurious interrupt
jz Hpci100
;
; (esp) = OldIrql
; (esp+4) = H/W vector
; (esp+8) = base of trap frame
;
;
; Update performance counter
;
mov eax, HalpNextRolloverCount
mov ecx, HalpRollOverCount
mov HalpRollOverCount, eax ; next rollover count
add HalpPerfCounterLow, ecx ; update performace counter
adc HalpPerfCounterHigh, 0
;
; Now check is any profiling stuff to do.
;
cmp HalpProfilingStopped, 0 ; Has profiling been stopped?
; je _KeProfileInterrupt@0 ; if prof enabled, jump to kernel
js short Hpci99 ; jump if all stopped
cli
mov dx, My+CpuIntCmd
@@: in ax, dx ;wait til ICU not busy
test eax, ICU_CMD_BUSY
jnz @B
mov ax, ICU_XMT_GLB_INT
out dx, ax ;set profile interrupt to all interested
Hpci99:
INTERRUPT_EXIT
Hpci100:
add esp, 8 ; spurious, no EndOfInterrupt
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
stdENDP _HalpPerfCtrInterrupt
;++
;
; 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
stdRET _HalSetTimeIncrement
stdENDP _HalSetTimeIncrement
_TEXT ends
end