mirror of https://github.com/lianthony/NT4.0
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.
1281 lines
36 KiB
1281 lines
36 KiB
title "Interval Clock Interrupt"
|
|
;++
|
|
;
|
|
; Copyright (c) 1989 Microsoft Corporation
|
|
;
|
|
; Module Name:
|
|
;
|
|
; oliclock.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 LSX5030.
|
|
;
|
|
; John Vert (jvert) 11-Jul-1991
|
|
; Moved from ke\i386 to hal\i386. Removed non-HAL stuff
|
|
;
|
|
; Bruno Sartirana (o-obruno) 3-Mar-92
|
|
; Added support for the Olivetti LSX5030.
|
|
;
|
|
; 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 hal386.inc
|
|
include callconv.inc
|
|
include i386\ix8259.inc
|
|
include i386\ixcmos.inc
|
|
include i386\kimacro.inc
|
|
include mac386.inc
|
|
;LSX5030 start
|
|
include i386\olimp.inc
|
|
;LSX5030 end
|
|
.list
|
|
|
|
EXTRNP _DbgBreakPoint,0,IMPORT
|
|
extrn KiI8259MaskTable:DWORD
|
|
EXTRNP _KeUpdateSystemTime,0
|
|
EXTRNP _KeUpdateRunTime,1,IMPORT
|
|
EXTRNP _KeProfileInterrupt,1,IMPORT
|
|
EXTRNP Kei386EoiHelper,0,IMPORT
|
|
EXTRNP _HalEndSystemInterrupt,2
|
|
EXTRNP _HalBeginSystemInterrupt,3
|
|
EXTRNP _HalRequestIpi,1
|
|
EXTRNP _HalpAcquireCmosSpinLock ,0
|
|
EXTRNP _HalpReleaseCmosSpinLock ,0
|
|
EXTRNP _KeSetTimeIncrement,2,IMPORT
|
|
extrn _HalpProcessorPCR:DWORD
|
|
extrn _HalpSystemHardwareLock:DWORD
|
|
extrn _HalpFindFirstSetRight: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 1193000
|
|
|
|
;
|
|
; Constants used to initialize CMOS/Real Time Clock
|
|
;
|
|
|
|
CMOS_CONTROL_PORT EQU 70h ; command port for cmos
|
|
CMOS_DATA_PORT EQU 71h ; cmos data port
|
|
D_INT032 EQU 8E00h ; access word for 386 ring 0 interrupt gate
|
|
RTCIRQ EQU 8 ; IRQ number for RTC interrupt
|
|
REGISTER_B_ENABLE_PERIODIC_INTERRUPT EQU 01000010B
|
|
; RT/CMOS Register 'B' Init byte
|
|
; Values for byte shown are
|
|
; Bit 7 = Update inhibit
|
|
; Bit 6 = Periodic interrupt enable
|
|
; Bit 5 = Alarm interrupt disable
|
|
; Bit 4 = Update interrupt disable
|
|
; Bit 3 = Square wave disable
|
|
; Bit 2 = BCD data format
|
|
; Bit 1 = 24 hour time mode
|
|
; Bit 0 = Daylight Savings disable
|
|
|
|
REGISTER_B_DISABLE_PERIODIC_INTERRUPT EQU 00000010B
|
|
|
|
;
|
|
; RegisterAInitByte sets 8Hz clock rate, used during init to set up
|
|
; KeStallExecutionProcessor, etc. (See RegASystemClockByte below.)
|
|
;
|
|
|
|
RegisterAInitByte EQU 00101101B ; RT/CMOS Register 'A' init byte
|
|
; 32.768KHz Base divider rate
|
|
; 8Hz int rate, period = 125.0ms
|
|
PeriodInMicroSecond EQU 125000 ;
|
|
|
|
CMOS_STATUS_BUSY EQU 80H ; Time update in progress
|
|
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 8254 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
|
|
;
|
|
;
|
|
; The default Interrupt interval is Interval = 15ms.
|
|
;
|
|
|
|
TIME_INCREMENT EQU 150000 ; 15ms
|
|
ROLLOVER_COUNT EQU 15 * 1193
|
|
|
|
|
|
_DATA SEGMENT DWORD PUBLIC 'DATA'
|
|
|
|
RegisterAProfileValue db 00101000B ; default interval = 3.90625 ms
|
|
|
|
ProfileIntervalTable dd 1221 ; unit = 100 ns
|
|
dd 2441
|
|
dd 4883
|
|
dd 9766
|
|
dd 19531
|
|
dd 39063
|
|
dd 78125
|
|
dd 156250
|
|
dd 312500
|
|
dd 625000
|
|
dd 1250000
|
|
dd 2500000
|
|
dd 5000000
|
|
dd 5000000 OR 80000000H
|
|
|
|
ProfileIntervalInitTable db 00100011B
|
|
db 00100100B
|
|
db 00100101B
|
|
db 00100110B
|
|
db 00100111B
|
|
db 00101000B
|
|
db 00101001B
|
|
db 00101010B
|
|
db 00101011B
|
|
db 00101100B
|
|
db 00101101B
|
|
db 00101110B
|
|
db 00101111B
|
|
db 00101111B
|
|
|
|
;
|
|
; The following array stores the per microsecond loop count for each
|
|
; central processor.
|
|
;
|
|
|
|
ifndef NT_UP
|
|
public _HalpIpiClock
|
|
_HalpIpiClock dd 0 ; Processors to IPI clock pulse to
|
|
endif
|
|
|
|
;
|
|
; Holds the value of the eflags register before a cmos spinlock is
|
|
; acquired (used in HalpAcquire/ReleaseCmosSpinLock().
|
|
;
|
|
_HalpHardwareLockFlags dd 0
|
|
|
|
;
|
|
; 8254 spinlock. This must be acquired before touching the 8254 chip.
|
|
;
|
|
public _Halp8254Lock
|
|
|
|
_Halp8254Lock dd 0
|
|
|
|
;
|
|
; PerfCounter value lock. locks access to the HalpPerfCounterLow/High vars.
|
|
;
|
|
|
|
_HalpPerfCounterLock dd 0
|
|
|
|
|
|
HalpProfileInterval dd -1
|
|
public HalpPerfCounterLow
|
|
public HalpPerfCounterHigh
|
|
HalpPerfCounterLow dd 0
|
|
HalpPerfCounterHigh dd 0
|
|
HalpProfilingStopped dd 1
|
|
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 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
|
|
|
|
pushfd ; save caller's eflag
|
|
cli ; make sure interrupts are disabled
|
|
|
|
;
|
|
; Set clock rate
|
|
;
|
|
|
|
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, ROLLOVER_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
|
|
|
|
popfd ; restore caller's eflag
|
|
|
|
;
|
|
; Fill in PCR value with TIME_INCREMENT
|
|
;
|
|
mov edx, TIME_INCREMENT
|
|
stdCall _KeSetTimeIncrement, <edx, edx>
|
|
|
|
mov HalpPerfCounterInit, 1 ; Indicate performance counter
|
|
; has been initialized.
|
|
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.
|
|
;
|
|
; NOTE:The routine is called for each cpu after the
|
|
; 'KiDispatcherLock' is acquired in ke\kernlini.c!KiInitializeKernel
|
|
; So, global variables used here are safe, but global resources
|
|
; like RTC are not (must be spinlocked).
|
|
;--
|
|
|
|
KiseInterruptCount equ [ebp-12] ; local variable
|
|
|
|
cPublicProc _HalpInitializeStallExecution,1
|
|
|
|
|
|
; LSX5030 start
|
|
;+++
|
|
;
|
|
; WARNING o-obruno Mar-3-92 This routine can't be used for Px, x>0.
|
|
;
|
|
; Since on the LSX5030 only processor 0 can receive RT clock interrupts,
|
|
; the other processors would hang in this routine. In order to calculate
|
|
; the right per microsecond loop count for each processor, the 8254 timer
|
|
; has to be used (To Be Implemented). The processors can have different
|
|
; clock rates, so such a loop count must be calculated for each processor.
|
|
; Here, temporarily, it is assumed that all the processors run at the same
|
|
; speed, so the loop count of the first processor is used by all the others
|
|
; as well.
|
|
|
|
|
|
; LSX5030 end
|
|
|
|
movzx ecx, byte ptr [esp+4] ; Current processor number
|
|
cmp ecx, 0 ; if proc number = 0, ie master
|
|
jz short kise ; if z, it's master, do regular processing
|
|
mov ecx, _HalpProcessorPCR[0] ; PCR for P0
|
|
mov eax, [ecx].PcStallScaleFactor; get P0 scale factor
|
|
mov fs:PcStallScaleFactor, eax ; Px scale factor.
|
|
stdRET _HalpInitializeStallExecution
|
|
kise:
|
|
|
|
;
|
|
; End of Hack
|
|
;
|
|
;---
|
|
|
|
push ebp ; save ebp
|
|
mov ebp, esp ; set up 12 bytes for local use
|
|
sub esp, 12
|
|
|
|
pushfd ; save caller's eflag
|
|
|
|
;
|
|
; Initialize Real Time Clock to interrupt us for every 125ms at
|
|
; IRQ 8.
|
|
;
|
|
|
|
cli ; make sure interrupts are disabled
|
|
|
|
;
|
|
; Get and save current 8259 masks
|
|
;
|
|
|
|
xor eax,eax
|
|
|
|
;
|
|
; Assume there is no third and fourth PICs
|
|
;
|
|
; Get interrupt Mask on PIC2
|
|
;
|
|
|
|
in al,PIC2_PORT1
|
|
shl eax, 8
|
|
|
|
;
|
|
; Get interrupt Mask on PIC1
|
|
;
|
|
|
|
in al,PIC1_PORT1
|
|
push eax ; save the masks
|
|
mov eax, NOT (( 1 SHL PIC_SLAVE_IRQ) + (1 SHL RTCIRQ))
|
|
; Mask all the irqs except irq 2 and 8
|
|
SET_8259_MASK ; Set 8259's int mask register
|
|
|
|
;
|
|
; Since RTC interrupt will come from IRQ 8, we need to
|
|
; Save original irq 8 descriptor and set the descriptor to point to
|
|
; our own handler.
|
|
;
|
|
|
|
sidt fword ptr [ebp-8] ; get IDT address
|
|
mov edx, [ebp-6] ; (edx)->IDT
|
|
|
|
push dword ptr [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)]
|
|
; (TOS) = original desc of IRQ 8
|
|
push dword ptr [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE) + 4]
|
|
; each descriptor has 8 bytes
|
|
push edx ; (TOS) -> IDT
|
|
mov eax, offset FLAT:RealTimeClockHandler
|
|
mov word ptr [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)], ax
|
|
; Lower half of handler addr
|
|
mov word ptr [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)+2], KGDT_R0_CODE
|
|
; set up selector
|
|
mov word ptr [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)+4], D_INT032
|
|
; 386 interrupt gate
|
|
shr eax, 16 ; (ax)=higher half of handler addr
|
|
mov word ptr [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)+6], ax
|
|
mov dword ptr KiseinterruptCount, 0 ; set no interrupt yet
|
|
|
|
stdCall _HalpAcquireCmosSpinLock ; intr disabled
|
|
|
|
mov ax,(RegisterAInitByte SHL 8) OR 0AH ; Register A
|
|
CMOS_WRITE ; Initialize it
|
|
mov ax,(REGISTER_B_ENABLE_PERIODIC_INTERRUPT SHL 8) OR 0BH ; Register B
|
|
CMOS_WRITE ; Initialize it
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0DH ; Register D
|
|
CMOS_READ ; Read to initialize
|
|
mov dword ptr [KiseInterruptCount], 0
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
|
|
;
|
|
; Now enable the interrupt and start the counter
|
|
; (As a matter of fact, only IRQ8 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 real time clock interrupt and compute the
|
|
; permicrosecond loopcount on receiving of the second real time
|
|
; interrupt. This is because the first interrupt is generated
|
|
; based on the previous real time tick interval.
|
|
;
|
|
|
|
RealTimeClockHandler:
|
|
|
|
inc dword ptr KiseInterruptCount ; increment interrupt count
|
|
cmp dword ptr KiseInterruptCount,1 ; Is this the first interrupt?
|
|
jnz short kise25 ; no, its the second go process it
|
|
pop eax ; get rid of original ret addr
|
|
push offset FLAT:kise10 ; set new return addr
|
|
|
|
stdCall _HalpAcquireCmosSpinLock ; intr disabled
|
|
|
|
mov ax,(RegisterAInitByte SHL 8) OR 0AH ; Register A
|
|
CMOS_WRITE ; Initialize it
|
|
mov ax,(REGISTER_B_ENABLE_PERIODIC_INTERRUPT SHL 8) OR 0BH ; Register B
|
|
CMOS_WRITE ; Initialize it
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0DH ; Register D
|
|
CMOS_READ ; Read to initialize
|
|
xor eax, eax ; reset loop counter
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
;
|
|
; Dismiss the interrupt.
|
|
;
|
|
|
|
mov al, OCW2_NON_SPECIFIC_EOI ; send non specific eoi to slave
|
|
out PIC2_PORT0, al
|
|
mov al, PIC2_EOI ; specific eoi to master for pic2 eoi
|
|
out PIC1_PORT0, al ; send irq2 specific eoi to master
|
|
|
|
iretd
|
|
|
|
kise25:
|
|
|
|
;
|
|
; ** temporary - check for incorrect KeStallExecutionProcessorLoopCount
|
|
;
|
|
|
|
ifdef DBG
|
|
cmp eax, 0
|
|
jnz short kise30
|
|
stdCall _DbgBreakPoint
|
|
|
|
endif
|
|
; never return
|
|
;
|
|
; ** End temporay code
|
|
;
|
|
|
|
kise30:
|
|
xor edx, edx ; (edx:eax) = divident
|
|
mov ecx, PeriodInMicroSecond; (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
|
|
;
|
|
; Reset return address to kexit
|
|
;
|
|
|
|
pop eax ; discard original return address
|
|
push offset FLAT:kexit ; return to kexit
|
|
|
|
mov al, OCW2_NON_SPECIFIC_EOI ; send non specific eoi to slave
|
|
out PIC2_PORT0, al
|
|
mov al, PIC2_EOI ; specific eoi to master for pic2 eoi
|
|
out PIC1_PORT0, al ; send irq2 specific eoi to master
|
|
|
|
and word ptr [esp+8], NOT 0200H ; Disable interrupt upon return
|
|
iretd
|
|
|
|
kexit: ; Interrupts are disabled
|
|
pop edx ; (edx)->IDT
|
|
pop [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)+4]
|
|
; restore higher half of NMI desc
|
|
pop [edx+8*(RTCIRQ+PRIMARY_VECTOR_BASE)]
|
|
; restore lower half of NMI desc
|
|
|
|
pop eax ; (eax) = origianl 8259 int masks
|
|
SET_8259_MASK
|
|
|
|
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
|
|
|
|
; DbgWrtP84 57h
|
|
mov eax, fs:PcStallScaleFactor ; get per microsecond
|
|
|
|
mul ecx ; (eax) = desired loop count
|
|
|
|
ifdef DBG
|
|
|
|
;
|
|
; Make sure we the loopcount is less than 4G and is not equal to zero
|
|
;
|
|
|
|
cmp edx, 0
|
|
jz short 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
|
|
|
|
;
|
|
; This function gets called on every processor so the hal can enable
|
|
; a profile interrupt on each processor. The LSX5030 only support
|
|
; profile on P0.
|
|
;
|
|
|
|
cmp fs:PcHal.PcrNumber, 0
|
|
jnz short eip_exit
|
|
|
|
stdCall _HalpAcquireCmosSpinLock ; intr disabled
|
|
|
|
;
|
|
; Mark profiling as active
|
|
;
|
|
|
|
mov dword ptr HalpProfilingStopped, 0
|
|
|
|
;
|
|
; Set the interrupt rate to what is actually needed.
|
|
;
|
|
|
|
|
|
mov al, RegisterAProfileValue
|
|
shl ax, 8
|
|
mov al, 0AH ; Register A
|
|
CMOS_WRITE ; Initialize it
|
|
mov ax,(REGISTER_B_ENABLE_PERIODIC_INTERRUPT SHL 8) OR 0BH
|
|
; Register B
|
|
CMOS_WRITE ; Initialize it
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0DH ; Register D
|
|
CMOS_READ ; Read to initialize
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
eip_exit:
|
|
stdRET _HalStartProfileInterrupt
|
|
|
|
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
|
|
|
|
cmp fs:PcHal.PcrNumber, 0
|
|
jnz short dip_exit
|
|
|
|
|
|
;
|
|
; Turn off profiling hit computation and profile interrupt
|
|
;
|
|
|
|
stdCall _HalpAcquireCmosSpinLock ; intr disabled
|
|
|
|
mov ax,(REGISTER_B_DISABLE_PERIODIC_INTERRUPT SHL 8) OR 0BH
|
|
; Register B
|
|
CMOS_WRITE ; Initialize it
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; dismiss pending profiling interrupt
|
|
mov dword ptr HalpProfilingStopped, 1
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
dip_exit:
|
|
stdRET _HalStopProfileInterrupt
|
|
|
|
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.
|
|
;
|
|
; WARNING - This is HIGHLY machine specific code. Current code
|
|
; works for UP machines and the LSX5030.
|
|
;--
|
|
|
|
cPublicProc _HalSetProfileInterval,1
|
|
|
|
mov edx, [esp+4] ; [edx] = interval in 100ns unit
|
|
and edx, 7FFFFFFFh ; Remove highest bit.
|
|
mov ecx, 0 ; index = 0
|
|
Hspi00:
|
|
mov eax, ProfileIntervalTable[ecx * 4]
|
|
cmp edx, eax ; if request interval < suport interval
|
|
jbe short Hspi10 ; if be, find supported interval
|
|
inc ecx
|
|
jmp short Hspi00
|
|
|
|
Hspi10:
|
|
and eax, 7FFFFFFFh ; remove highest bit from supported interval
|
|
push eax ; save interval value
|
|
mov al, ProfileIntervalInitTable[ecx]
|
|
mov RegisterAProfileValue, al
|
|
test dword ptr HalpProfilingStopped,-1
|
|
jnz Hspi90
|
|
|
|
stdCall _HalStartProfileInterrupt,<0> ; Re-start profile interrupt
|
|
; with the new interval
|
|
Hspi90: pop eax
|
|
stdRET _HalSetProfileInterval ; (eax) = return interval
|
|
|
|
stdENDP _HalSetProfileInterval
|
|
|
|
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
|
|
ifndef NT_UP
|
|
lea eax, _Halp8254Lock
|
|
ACQUIRE_SPINLOCK eax, Kqpc198
|
|
endif
|
|
|
|
|
|
;
|
|
; 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.
|
|
|
|
ifndef NT_UP
|
|
lea eax, _Halp8254Lock
|
|
RELEASE_SPINLOCK eax
|
|
endif
|
|
|
|
;
|
|
; 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, ROLLOVER_COUNT
|
|
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
|
|
|
|
ifndef NT_UP
|
|
Kqpc198: popfd
|
|
SPIN_ON_SPINLOCK eax,<Kqpc11>
|
|
endif
|
|
|
|
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 OLI 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
|
|
;
|
|
|
|
Hci10:
|
|
push CLOCK_VECTOR
|
|
sub esp, 4 ; allocate space to save OldIrql
|
|
|
|
; esp - OldIrql
|
|
stdCall _HalBeginSystemInterrupt,<CLOCK2_LEVEL,CLOCK_VECTOR,esp>
|
|
or al,al ; check for spurious interrupt
|
|
jz Hci100
|
|
|
|
;
|
|
; Update performance counter
|
|
;
|
|
|
|
;add HalpPerfCounterLow, ROLLOVER_COUNT ; update performace counter
|
|
;adc HalpPerfCounterHigh, 0
|
|
|
|
;
|
|
; (esp) = OldIrql
|
|
; (esp+4) = Vector
|
|
; (esp+8) = base of trap frame
|
|
; (ebp) = address of trap frame
|
|
;
|
|
|
|
; LSX5030 start
|
|
;mov eax, _HalpIpiClock ; Emulate clock ticks to any processors?
|
|
;or eax, eax
|
|
;jz Hci90
|
|
|
|
;
|
|
; On the SystemPro we know the processor which needs an emulated clock tick.
|
|
; Just set that processors bit and IPI him
|
|
;
|
|
|
|
;@@:
|
|
;movzx ecx, _HalpFindFirstSetRight[eax] ; lookup first processor
|
|
;btr eax, ecx
|
|
;mov ecx, _HalpProcessorPCR[ecx*4] ; PCR of processor
|
|
;mov [ecx].PcHal.PcrIpiClockTick, 1 ; Set internal IPI event
|
|
;or eax, eax ; any other processors?
|
|
;jnz @b ; yes, loop
|
|
|
|
;push _HalpIpiClock ; Processors to IPI
|
|
;call _HalRequestIpi ; IPI the processor
|
|
;add esp, 4
|
|
|
|
;Hci90:
|
|
;
|
|
; On the LSX5030 every processor receives the timer 1 counter 0 interrupts,
|
|
; since such a timer is present on each CPU. Only processor 0 updates the
|
|
; system time. All the processors execute KeUpdateRunTime (which is called
|
|
; by KeUpdateSystemTime) in order to update the runtime of the current
|
|
; thread, update the runtime of the current thread's process, and
|
|
; decrement the current thread's quantum.
|
|
;
|
|
|
|
movzx eax, fs:PcHal.PcrNumber ; (eax)= Processor number
|
|
or eax, eax
|
|
jnz @f
|
|
;
|
|
; Processor 0 updates the performance counter
|
|
;
|
|
|
|
add HalpPerfCounterLow, ROLLOVER_COUNT ; update performace counter
|
|
adc HalpPerfCounterHigh, 0
|
|
|
|
mov eax, TIME_INCREMENT
|
|
jmp _KeUpdateSystemTime@0 ; if P0, jump to _KeUpdateSystemTime
|
|
|
|
@@:
|
|
stdCall _KeUpdateRunTime,<dword ptr [esp]> ; if Px, x>0, call _KeUpdateRunTime
|
|
|
|
INTERRUPT_EXIT
|
|
; LSX5030 end
|
|
|
|
Hci100:
|
|
add esp, 8 ; spurious, no EndOfInterrupt
|
|
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
|
|
|
|
_HalpClockInterrupt endp
|
|
|
|
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
|
|
|
|
|
|
;
|
|
; This is the RTC interrupt, so we have to clear the
|
|
; interrupt flag on the RTC.
|
|
;
|
|
; clear interrupt flag on RTC by banging on the CMOS. On some systems this
|
|
; doesn't work the first time we do it, so we do it twice. It is rumored that
|
|
; some machines require more than this, but that hasn't been observed with NT.
|
|
;
|
|
; NOTE: THIS IS MACHINE SPECIFIC
|
|
; for MP machines that utilize one global RTC chip, and a subset of
|
|
; the CPUs (here,P0 must be one of them) accept an interrupt from
|
|
; the RTC, only one CPU needs to ACK the RTC (P0 in this scheme).
|
|
;
|
|
|
|
ifndef NT_UP
|
|
cmp fs:PcHal.PcrNumber, 0 ; Only P0 ack RTC.
|
|
jne Hpi10
|
|
endif
|
|
|
|
Hpil01:
|
|
cli
|
|
lea eax, _HalpSystemHardwareLock
|
|
ACQUIRE_SPINLOCK eax, Hpil90
|
|
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
if DBG
|
|
Hpi00: test al, 80h
|
|
jz short Hpi05
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
jmp short Hpi00
|
|
Hpi05:
|
|
endif ; DBG
|
|
|
|
lea eax, _HalpSystemHardwareLock
|
|
RELEASE_SPINLOCK eax
|
|
|
|
Hpi10:
|
|
|
|
|
|
;
|
|
; (esp) - base of trap frame
|
|
;
|
|
|
|
push PROFILE_VECTOR
|
|
sub esp, 4 ; allocate space to save OldIrql
|
|
|
|
; esp - OldIrql
|
|
|
|
stdCall _HalBeginSystemInterrupt,<PROFILE_LEVEL,PROFILE_VECTOR,esp>
|
|
or al,al ; check for spurious interrupt
|
|
jz Hpi100
|
|
;
|
|
; (esp) = OldIrql
|
|
; (esp+4) = H/W vector
|
|
; (esp+8) = base of trap frame
|
|
;
|
|
|
|
;
|
|
; Now check is any profiling stuff to do.
|
|
;
|
|
|
|
cmp HalpProfilingStopped, dword ptr 1 ; Has profiling been stopped?
|
|
jz short Hpi90 ; if z, prof disenabled
|
|
|
|
stdCall _KeProfileInterrupt,<ebp>
|
|
Hpi90: INTERRUPT_EXIT
|
|
|
|
|
|
Hpi100:
|
|
add esp, 8 ; spurious, no EndOfInterrupt
|
|
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
|
|
|
|
Hpil90:
|
|
;
|
|
; Even though intr gets enabled here, we shouldn't get another
|
|
; intr on this channel, because we didn't eoi the interrupt yet.
|
|
;
|
|
sti
|
|
SPIN_ON_SPINLOCK eax, <Hpil01>
|
|
|
|
stdENDP _HalpProfileInterrupt
|
|
|
|
;++
|
|
;
|
|
; 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
|
|
|