933 lines
27 KiB
NASM
933 lines
27 KiB
NASM
title "Initialize Stall Execution for the Corollary MP machines"
|
|
;++
|
|
;
|
|
;Copyright (c) 1992, 1993, 1994 Corollary Inc
|
|
;
|
|
;Module Name:
|
|
;
|
|
; cb1stall.asm
|
|
;
|
|
;Abstract:
|
|
;
|
|
; This module contains various stall initialization, clock and performance
|
|
; counter routines.
|
|
;
|
|
;Author:
|
|
;
|
|
; Landy Wang (landy@corollary.com) 05-Oct-1992
|
|
;
|
|
;Revision History:
|
|
;
|
|
;--
|
|
|
|
|
|
|
|
.386p
|
|
.xlist
|
|
include hal386.inc
|
|
include i386\kimacro.inc
|
|
include callconv.inc ; calling convention macros
|
|
include cbus.inc
|
|
include mac386.inc
|
|
include i386\ix8259.inc
|
|
|
|
EXTRNP _HalBeginSystemInterrupt,3
|
|
EXTRNP _HalEndSystemInterrupt,2
|
|
|
|
.list
|
|
|
|
;
|
|
; the CLKIN pin of the 82489DX is clocking at 32MHz (32000000) on the bridge,
|
|
; and 33-1/3 (333333333) MHz on the additional processor cards.
|
|
;
|
|
; divide this by 1,000,000 to get clocking in a microsecond --> ~33.
|
|
; 33 * 16 microseconds == 528 == 0x210
|
|
;
|
|
|
|
CLKIN_ONE_SECOND EQU 32000000 ; in APIC CLKIN units
|
|
; CLKIN_ONE_SECOND EQU 33333333 ; in APIC CLKIN units
|
|
CLKIN_SIXTEEN_MS EQU 210h ; 16 microseconds (CLKIN)
|
|
CLKIN_SIXTEEN_MS_SHIFT EQU 4 ; shift 16 microseconds to 1ms
|
|
CLKIN_ENABLE_ONESHOT EQU 0h ; enable one-shot CLKIN interrupt
|
|
CLKIN_ENABLE_PERIODIC EQU 20000h ; enable periodic CLKIN interrupts
|
|
CLKIN_DISABLE EQU 10000h ; mask off CLKIN interrupts
|
|
|
|
APIC_TIMER_MILLISECOND EQU 32000 ; timer units to == 1 millisecond
|
|
APIC_TIMER_MICROSECOND EQU 33 ; timer units to == 1 microsecond
|
|
|
|
TIMER_VECTOR_ENTRY EQU 320h ; timer vector table entry 0 address
|
|
INITIAL_COUNT_REG EQU 380h ; poke here to set the initial count
|
|
CURRENT_COUNT_REG EQU 390h ; current counter countdown is here
|
|
|
|
D_INT032 EQU 8E00h ; access word for 386 ring 0 int gate
|
|
|
|
CBUS1_PERF_TASKPRI EQU 0DDh ; vector generated by 8254 timer
|
|
|
|
;
|
|
; The default interval between clock interrupts is
|
|
; 10 milliseconds == 10000 microseconds == 100000 (in 100 ns units).
|
|
; Remember the NT executive expects this value to be in
|
|
; 100 nanosecond units (not microseconds), so multiply
|
|
; accordingly when referencing KeTimeIncrement.
|
|
;
|
|
; the maxclock rates below are not the slowest that the hardware can support -
|
|
; they are the slowest we want NT to restore it whenever it is rolled back.
|
|
;
|
|
MAXCLOCK_RATE_IN_MS EQU 10 ; specify in milliseconds
|
|
|
|
;
|
|
; We will support clock interrupt generation with a minimum of 300
|
|
; nanosecond separations to as much as 10 milliseconds apart.
|
|
;
|
|
MINCLOCK_DELTA_IN_100NS EQU 3 ; specify in 100-nanosecond units
|
|
MAXCLOCK_DELTA_IN_100NS EQU 100000 ; specify in 100-nanosecond units
|
|
|
|
;
|
|
; "Convert" the interval to rollover count for 8254 Timer1 device.
|
|
; timer1 counts down a 16 bit value at a rate of 1.193M counts-per-sec.
|
|
; So that's what we'll use to get the finest granularity. Note that
|
|
; this is solely for the performance counter and has NOTHING to do
|
|
; with the system clock, which is driven directly from the APIC.
|
|
; We'd like for the interrupt rate to be even lower (ie: once per
|
|
; second rather than approximately 17 times per second, but the 8254
|
|
; only gives us two bytes to feed in the interrupt counter.
|
|
;
|
|
|
|
ROLLOVER_COUNT EQU 0ffffH ; must fit in the 8254's two bytes
|
|
PERFORMANCE_FREQUENCY EQU 1193000
|
|
|
|
TIMER1_DATA_PORT0 EQU 40H ; Timer1, channel 0 data port
|
|
TIMER1_CONTROL_PORT0 EQU 43H ; Timer1, channel 0 control port
|
|
|
|
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
|
|
|
|
_DATA SEGMENT DWORD PUBLIC 'DATA'
|
|
;
|
|
; default clock rate is 10 milliseconds
|
|
;
|
|
CbusClockRateInMillis dd 10 ; in milliseconds
|
|
|
|
Cbus1PerfCounterInit dd 0
|
|
Cbus1PerfCounterLow dd 0
|
|
Cbus1PerfCounterHigh dd 0
|
|
Cbus1LastReadPerfLow dd 0
|
|
Cbus1LastReadPerfHigh dd 0
|
|
Cbus18254Late dd 0
|
|
Cbus1CurrentTimeIncrement dd 0
|
|
|
|
if DBG
|
|
Cbus18254LateCount dd 0
|
|
Cbus1Queries dd 0
|
|
endif
|
|
|
|
_DATA ends
|
|
|
|
|
|
INIT SEGMENT PARA PUBLIC 'CODE' ; Start 32 bit code
|
|
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
|
|
|
|
page ,132
|
|
subttl "Initialize Stall Execution Counter"
|
|
;++
|
|
;
|
|
; VOID
|
|
; Cbus1InitializeStall (
|
|
; IN CCHAR ProcessorNumber
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine initializes the per Microsecond counter for
|
|
; KeStallExecutionProcessor.
|
|
;
|
|
; All Corollary processors have their own local APIC and
|
|
; their own local timers. each processor independently
|
|
; calibrates himself here, thus supporting processors of
|
|
; varying speeds.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; ProcessorNumber - Processor Number
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
|
|
cPublicProc _Cbus1InitializeStall ,1
|
|
cPublicFpo 1,2
|
|
|
|
push ebp ; save ebp
|
|
mov ebp, esp
|
|
sub esp, 8 ; save room for idtr
|
|
|
|
pushfd ; save caller's eflag
|
|
|
|
cli ; disable interrupts
|
|
|
|
;
|
|
; save the current CbusClockVector IDT entry, as we are
|
|
; going to temporarily repoint it at a private handler.
|
|
;
|
|
|
|
sidt fword ptr [ebp-8] ; get IDTR (base & limit)
|
|
mov ecx, [ebp-6] ; get IDTR base value
|
|
|
|
mov eax, [_CbusClockVector]
|
|
|
|
shl eax, 3 ; 8 bytes per IDT entry
|
|
add ecx, eax ; now at the correct IDT RTC entry
|
|
|
|
push dword ptr [ecx] ; (TOS) = original desc of IRQ 8
|
|
push dword ptr [ecx + 4] ; each descriptor has 8 bytes
|
|
push ecx ; (TOS) -> &IDT[CbusClockVector]
|
|
|
|
mov eax, offset FLAT:TimerExpired
|
|
|
|
mov word ptr [ecx], ax ; Lower half of handler addr
|
|
mov word ptr [ecx+2], KGDT_R0_CODE ; set up selector
|
|
mov word ptr [ecx+4], D_INT032 ; 386 interrupt gate
|
|
|
|
shr eax, 16 ; (ax)=higher half of handler addr
|
|
mov word ptr [ecx+6], ax
|
|
|
|
mov eax, [_CbusClockVector] ; we expect this vector to...
|
|
or eax, CLKIN_ENABLE_ONESHOT ; use one-shot CLKIN to interrupt us
|
|
|
|
; get the base of APIC space, so we can then access
|
|
; the addr of hardware interrupt command register below
|
|
|
|
mov ecx, [_CbusLocalApic]
|
|
|
|
; the count register appears to decrement approx 0x30 per
|
|
; asm instruction on a 486/33, just fyi. so load it up with
|
|
; a "real high value" so we don't get an interrupt whilst setting
|
|
; up the local time vector entry, etc, and then at the last
|
|
; possible moment, fill it in with the desired starting value.
|
|
|
|
mov dword ptr INITIAL_COUNT_REG[ecx], CLKIN_ONE_SECOND
|
|
|
|
; initialize the local timer vector table entry with
|
|
; appropriate Vector, Timer Base, Timer Mode and Mask.
|
|
|
|
mov TIMER_VECTOR_ENTRY[ecx], eax
|
|
|
|
; poke initial count reg to interrupt us in 16 microseconds
|
|
|
|
mov dword ptr INITIAL_COUNT_REG[ecx], CLKIN_SIXTEEN_MS
|
|
|
|
xor eax, eax ; initialize our register counter
|
|
ALIGN 16
|
|
sti ; enable the interrupt
|
|
jmp kise10
|
|
|
|
ALIGN 16
|
|
kise10:
|
|
sub eax, 1 ; increment the loopcount
|
|
jnz short kise10
|
|
|
|
if DBG
|
|
stdCall _DbgBreakPoint ; Counter overflowed!
|
|
endif
|
|
jmp short kise10
|
|
|
|
TimerExpired:
|
|
|
|
; take the timer expiration interrupt here
|
|
|
|
if DBG
|
|
cmp eax, 0
|
|
jnz short kise30
|
|
stdCall _DbgBreakPoint ; Counter was never bumped!
|
|
; never return
|
|
kise30:
|
|
endif
|
|
|
|
neg eax
|
|
shr eax, CLKIN_SIXTEEN_MS_SHIFT ; convert to microseconds
|
|
|
|
mov dword ptr PCR[PcStallScaleFactor], eax
|
|
|
|
mov eax, [_CbusLocalApic]
|
|
|
|
; mask off local timer vector table entry now that we're done with it.
|
|
|
|
mov dword ptr TIMER_VECTOR_ENTRY[eax], CLKIN_DISABLE
|
|
|
|
;
|
|
; Dismiss the interrupt AFTER disabling the timer entry so
|
|
; we don't get an extra interrupt later that was already pending.
|
|
;
|
|
|
|
mov eax, _CbusClockVector ; mark interrupting vector
|
|
CBUS_EOI eax, ecx ; destroy eax & ecx
|
|
|
|
add esp, 12 ; unload flags, cs, ip
|
|
|
|
pop ecx ; (ecx) -> &IDT[CbusClockVector]
|
|
pop [ecx+4] ; restore higher half of RTC desc
|
|
pop [ecx] ; restore lower half of RTC desc
|
|
|
|
popfd ; restore caller's eflags
|
|
mov esp, ebp
|
|
pop ebp ; restore ebp
|
|
|
|
stdRET _Cbus1InitializeStall
|
|
|
|
stdENDP _Cbus1InitializeStall
|
|
|
|
page ,132
|
|
subttl "Cbus1 Initialize Performance Counter"
|
|
;++
|
|
;
|
|
; VOID
|
|
; Cbus1InitializePerf (
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; Initialize the 8254 to interrupt the minimum number of times
|
|
; per second on the boot processor only to support the performance counter.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
cPublicProc _Cbus1InitializePerf ,0
|
|
|
|
;
|
|
; Since ke\i386\allproc.c no longer boots all the available
|
|
; processors in the machine, the first processor to boot must
|
|
; initialize the 8254 and take the extra 8254 ticks.
|
|
;
|
|
|
|
mov eax, PCR[PcHal.PcrNumber]
|
|
cmp eax, 0
|
|
jne short @f
|
|
|
|
pushfd ; save caller's eflag
|
|
cli ; make sure interrupts are disabled
|
|
|
|
;
|
|
; Initialize the APIC so that 8254 interrupts go only to the boot
|
|
; processor - there is no need for all of them to get this interrupt
|
|
; when all it is doing is updating a global counter.
|
|
;
|
|
|
|
; stdCall _HalEnableSystemInterrupt,<CBUS1_PERF_TASKPRI,CLOCK2_LEVEL,0>
|
|
; no need to enable - it's done by our caller
|
|
|
|
;
|
|
; Set clock rate at the 8254
|
|
;
|
|
|
|
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
|
|
|
|
mov Cbus1PerfCounterInit, 1
|
|
|
|
align 4
|
|
@@:
|
|
|
|
stdRET _Cbus1InitializePerf
|
|
|
|
stdENDP _Cbus1InitializePerf
|
|
|
|
INIT ends
|
|
|
|
_TEXT SEGMENT DWORD PUBLIC 'CODE' ; Start 32 bit code
|
|
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
|
|
|
|
page ,132
|
|
subttl "Cbus1 Initialize Clock"
|
|
;++
|
|
;
|
|
; VOID
|
|
; Cbus1InitializeClock (
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; Initializes the clock and the kernel variable for the amount of
|
|
; time between timer ticks. The kernel needs this information by the end
|
|
; of phase 0.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
|
|
cPublicProc _Cbus1InitializeClock,0
|
|
|
|
;
|
|
; Fill in value with the time between clock ticks,
|
|
; remember the NT executive expects this value to be in
|
|
; 100 nanosecond units, not microseconds, so multiply accordingly.
|
|
;
|
|
|
|
mov eax, CbusClockRateInMillis ; current rate in milliseconds
|
|
|
|
mov ecx, 10000 ; converting back to 100 ns
|
|
mul ecx ; eax == 100ns unit value
|
|
|
|
;
|
|
; The Cbus1CurrentTimeIncrement value is used by the clock
|
|
; interrupt routine to pass to the kernel, so set it now.
|
|
;
|
|
mov Cbus1CurrentTimeIncrement, eax
|
|
|
|
stdCall _Cbus1ProgramClock
|
|
|
|
stdCall _KeSetTimeIncrement, <MAXCLOCK_DELTA_IN_100NS, MINCLOCK_DELTA_IN_100NS>
|
|
|
|
stdRET _Cbus1InitializeClock
|
|
|
|
stdENDP _Cbus1InitializeClock
|
|
|
|
;++
|
|
;
|
|
; VOID
|
|
; Cbus1ProgramClock (
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine initializes the system time clock for each calling
|
|
; processor to generate periodic interrupts at the specified rate using
|
|
; this processor's local APIC timer. Thus, each processor must call
|
|
; this routine to set or change his clock rate. During startup, each
|
|
; processor will call this routine to set his own clock rate. Later,
|
|
; when the system is running, the HalSetTimerResolution API is only
|
|
; supposed to change the clock rate for the boot processor - this is
|
|
; for multimedia apps that want timeouts serviced at a better than
|
|
; 5 millisecond granularity.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
|
|
cPublicProc _Cbus1ProgramClock ,0
|
|
|
|
mov ecx, CbusClockRateInMillis ; new rate in milliseconds
|
|
|
|
mov eax, APIC_TIMER_MILLISECOND ; counter per microsecond
|
|
|
|
mul ecx ; eax == new APIC countdown val
|
|
|
|
mov edx, [_CbusClockVector] ; we expect this vector to...
|
|
or edx, CLKIN_ENABLE_PERIODIC ; use CLKIN to interrupt us
|
|
|
|
mov ecx, [_CbusLocalApic]
|
|
|
|
pushfd
|
|
cli
|
|
|
|
; as a frame of reference, the count register decrements
|
|
; approximately 0x30 per asm instruction on a 486/33.
|
|
|
|
; initialize the local timer vector table entry with
|
|
; appropriate Vector, Timer Base, Timer Mode and Mask.
|
|
|
|
mov TIMER_VECTOR_ENTRY[ecx], edx
|
|
|
|
; poke initial count reg to start the periodic clock timer interrupts.
|
|
; the IDT entry is valid & enabled on entry to this routine.
|
|
|
|
mov dword ptr INITIAL_COUNT_REG[ecx], eax
|
|
|
|
mov eax, CbusClockRateInMillis ; new rate in milliseconds
|
|
mov ecx, 10000 ; converting back to 100 ns
|
|
xor edx, edx
|
|
mul ecx ; eax == 100ns unit value
|
|
|
|
;
|
|
; store it here so the clock ISR can tell it to the kernel
|
|
;
|
|
mov Cbus1CurrentTimeIncrement, eax
|
|
|
|
popfd
|
|
stdRET _Cbus1ProgramClock
|
|
|
|
stdENDP _Cbus1ProgramClock
|
|
|
|
page ,132
|
|
subttl "Query Performance Counter"
|
|
;++
|
|
;
|
|
; LARGE_INTEGER
|
|
; Cbus1QueryPerformanceCounter (
|
|
; OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine returns the current 64-bit performance counter.
|
|
; The Performance Frequency is also returned if asked for.
|
|
;
|
|
; Also note that the performance counter returned by this routine
|
|
; is not necessarily the value exactly when this routine was entered.
|
|
; The value returned is actually the counter value at any point
|
|
; between the routine's entrance and exit times.
|
|
;
|
|
; This routine is not susceptible to the 2 problems that plague most
|
|
; multiprocessor HALs. Their problems are as follows:
|
|
;
|
|
; a) If the boot processor (or whoever updates the global performance
|
|
; counters) misses an interrupt, the counter rolls over undetected,
|
|
; and the performance counter returned can actually roll backwards!
|
|
;
|
|
; b) If you are on an additional processor and for some reason,
|
|
; the boot processor is holding off a pending clock interrupt for
|
|
; the entire time the additional processor is executing in
|
|
; KeQueryPerfomanceCounter. The boot processor hasn't necessarily
|
|
; lost a clock interrupt, it's just that he hasn't dropped IRQL
|
|
; enough to get it yet, and there isn't any good way for the
|
|
; additional processor to force him to. Since the boot processor
|
|
; is the only one maintaining the PerfCounter[High,Low], this can
|
|
; result in processors returning potentially non-uniform snapshots,
|
|
; which can even roll backwards!
|
|
;
|
|
; Both of these problems have been solved in the Corollary HAL by
|
|
; using separate clocks for the system timer and the performance counter.
|
|
; Ie: the APIC timer is used for the system timer and interrupts each
|
|
; processor every 15 milliseconds. The 8254 interrupts ONLY the boot
|
|
; processor once every second. Thus, case a) above cannot happen
|
|
; unless the boot processor disables interrupts for more than one second.
|
|
; If this happens, the entire system will be badly broken. case b)
|
|
; is also bypassed by using a global lock in the performance counter
|
|
; interrupt handler. Since the interrupt only occurs once per second
|
|
; and only on one processor, the global lock will not hinder performance.
|
|
; This allows us to take globally synchronized performance counter
|
|
; snapshots and also detect in software if case b) is happening, and
|
|
; handle it in software. Because the 8254 timer register is only 16 bits
|
|
; wide, the actual 8254 synchronization interrupt will occur approximately
|
|
; seventeen times per second instead of once. This is still at least 4
|
|
; times better than the system timer (approximately 70 times per second).
|
|
;
|
|
; 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+4] ; User supplied Performance Frequency
|
|
|
|
cPublicProc _Cbus1QueryPerformanceCounter ,1
|
|
|
|
;
|
|
; First check to see if the performance counter has been
|
|
; initialized yet. Since the kernel debugger calls
|
|
; KeQueryPerformanceCounter to support the !timer command
|
|
; _VERY_ early on, we need to return something reasonable
|
|
; even though timer initialization hasn't occured yet.
|
|
;
|
|
cmp Cbus1PerfCounterInit, 0
|
|
jne short @f ; ok, perf counter has been initialized
|
|
|
|
;
|
|
; Initialization hasn't occured yet, so just return zeros.
|
|
;
|
|
mov eax, 0
|
|
mov edx, 0
|
|
jmp retfreq ; retfreq too far away for short jmp
|
|
|
|
align 4
|
|
@@:
|
|
if DBG
|
|
inc dword ptr [Cbus1Queries]
|
|
endif
|
|
push ebx
|
|
push esi
|
|
;
|
|
; all interrupts must be disabled prior to lock acquisition to
|
|
; prevent deadlock.
|
|
;
|
|
pushfd
|
|
cli
|
|
|
|
lea esi, _Halp8254Lock
|
|
|
|
align 4
|
|
Kqpc00:
|
|
ACQUIRE_SPINLOCK esi, Kqpc01
|
|
|
|
;
|
|
; get the global timer counters which are incremented
|
|
; one processor only.
|
|
;
|
|
|
|
mov esi, Cbus1PerfCounterLow
|
|
mov ebx, Cbus1PerfCounterHigh ; [ebx:esi] = Performance counter
|
|
|
|
;
|
|
; 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.
|
|
|
|
neg ecx ; PIT counts down to 0h
|
|
add ecx, ROLLOVER_COUNT
|
|
add esi, ecx
|
|
adc ebx, 0 ; [ebx:esi] = Final result
|
|
|
|
;
|
|
; Check whether case b) could be happening right now - this is
|
|
; possible no matter which processor is currently calling us.
|
|
;
|
|
|
|
cmp ebx, dword ptr [Cbus1LastReadPerfHigh]
|
|
jl short caseb
|
|
|
|
cmp esi, dword ptr [Cbus1LastReadPerfLow]
|
|
jl short caseb
|
|
|
|
jmp short notpending
|
|
|
|
align 4
|
|
caseb:
|
|
;
|
|
; Detected case b) happening RIGHT NOW!
|
|
; Update the Cbus1 performance counter NOW, and
|
|
; set a flag so the interrupt handler will NOT.
|
|
; This case actually happens fairly frequently,
|
|
; ie: at least every few seconds on an idle
|
|
; uniprocessor, so this code is quite useful.
|
|
;
|
|
|
|
add Cbus1PerfCounterLow, ROLLOVER_COUNT ; update performance counter
|
|
adc Cbus1PerfCounterHigh, 0
|
|
inc dword ptr [Cbus18254Late]
|
|
|
|
if DBG
|
|
inc dword ptr [Cbus18254LateCount]
|
|
endif
|
|
|
|
align 4
|
|
notpending:
|
|
;
|
|
; save last performance counter read so future callers can compare
|
|
;
|
|
|
|
mov Cbus1LastReadPerfLow, esi
|
|
mov Cbus1LastReadPerfHigh, ebx
|
|
|
|
mov edx, ebx ; save return value
|
|
mov eax, esi ; save return value
|
|
|
|
lea esi, _Halp8254Lock
|
|
RELEASE_SPINLOCK esi
|
|
|
|
popfd
|
|
pop esi
|
|
pop ebx
|
|
|
|
;
|
|
; if asked to, return the frequency in units/second
|
|
;
|
|
|
|
align 4
|
|
retfreq:
|
|
|
|
or dword ptr KqpcFrequency, 0 ; is it NULL?
|
|
jz short @f ; if z, yes, go exit
|
|
|
|
mov ecx, KqpcFrequency ; frequency pointer
|
|
mov dword ptr [ecx], PERFORMANCE_FREQUENCY ; set frequency
|
|
mov dword ptr [ecx+4], 0 ; currently < 4Gig!
|
|
|
|
align 4
|
|
@@:
|
|
stdRET _Cbus1QueryPerformanceCounter
|
|
|
|
align 4
|
|
Kqpc01:
|
|
SPIN_ON_SPINLOCK esi,<Kqpc00>
|
|
|
|
stdENDP _Cbus1QueryPerformanceCounter
|
|
|
|
page ,132
|
|
subttl "Cbus1 Perf Interrupt"
|
|
;++
|
|
;
|
|
; VOID
|
|
; Cbus1PerfInterrupt(
|
|
; VOID
|
|
; );
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine is the interrupt handler for the Cbus1 performance
|
|
; counter interrupt at a priority just below that of normal clocks.
|
|
; Its function is to update the global performance counter so that
|
|
; KeQueryPerformanceCounter can return meaningful values.
|
|
;
|
|
; This routine is executed only by one processor at a rate of seventeen
|
|
; times per second, as there is no need for all processors to update
|
|
; the same global counter. The only reason the rate is so high is
|
|
; because the interrupt interval must fit into a 16 bit register in the
|
|
; 8254. Otherwise, it would have been more like once per second.
|
|
;
|
|
; Since this routine is entered directly via an interrupt gate, interrupt
|
|
; protection via cli is not necessary.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
|
|
ENTER_DR_ASSIST hipi_a, hipi_t
|
|
|
|
cPublicProc _Cbus1PerfInterrupt ,0
|
|
|
|
;
|
|
; Save machine state on trap frame
|
|
;
|
|
|
|
ENTER_INTERRUPT hipi_a, hipi_t
|
|
|
|
; keep it simple, just issue the EOI right now.
|
|
; no changing of taskpri/irql is needed here.
|
|
; Thus, the EOI serves as the HalEndSystemInterrupt.
|
|
|
|
mov eax, CBUS1_PERF_TASKPRI ; mark interrupting vector
|
|
CBUS_EOI eax, ecx ; destroy eax & ecx
|
|
|
|
;
|
|
; (esp) - base of trap frame
|
|
;
|
|
|
|
ifdef MCA
|
|
|
|
;
|
|
; Special hack for MCA machines
|
|
;
|
|
|
|
in al, 61h
|
|
jmp $+2
|
|
or al, 80h
|
|
out 61h, al
|
|
jmp $+2
|
|
|
|
endif ; MCA
|
|
|
|
lea esi, _Halp8254Lock
|
|
|
|
align 4
|
|
Kcpi00:
|
|
ACQUIRE_SPINLOCK esi, Kcpi01
|
|
|
|
;
|
|
; Update Cbus1 performance counter if a performance counter query
|
|
; hasn't done so already.
|
|
;
|
|
|
|
cmp dword ptr [Cbus18254Late], 0
|
|
jne short @f
|
|
|
|
add Cbus1PerfCounterLow, ROLLOVER_COUNT ; update performance counter
|
|
adc Cbus1PerfCounterHigh, 0
|
|
jmp short noroll
|
|
|
|
align 4
|
|
@@:
|
|
dec dword ptr [Cbus18254Late]
|
|
|
|
align 4
|
|
noroll:
|
|
RELEASE_SPINLOCK esi
|
|
|
|
;
|
|
; Call this directly instead of through INTERRUPT_EXIT
|
|
; because the HalEndSystemInterrupt has already been done,
|
|
; and must only be done ONCE per interrupt.
|
|
;
|
|
|
|
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
|
|
|
|
align 4
|
|
Kcpi01:
|
|
SPIN_ON_SPINLOCK esi,<Kcpi00>
|
|
|
|
stdENDP _Cbus1PerfInterrupt
|
|
|
|
;++
|
|
;
|
|
; ULONG
|
|
; Cbus1SetTimeIncrement (
|
|
; IN ULONG DesiredIncrement
|
|
; )
|
|
;
|
|
; /*++
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine initializes the system time clock to generate an
|
|
; interrupt at every DesiredIncrement interval.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; DesiredIncrement - desired interval between every timer tick in
|
|
; 100ns units.
|
|
;
|
|
; Return Value:
|
|
;
|
|
; The *REAL* time increment set - this can be different from what he
|
|
; requested due to hardware limitations - currently to keep the math
|
|
; simple, we limit the interval to between 1 and 32000 milliseconds,
|
|
; on millisecond boundaries (ie 1.3 milliseconds is rounded to 1
|
|
; millisecond).
|
|
;--
|
|
|
|
cPublicProc _Cbus1SetTimeIncrement,1
|
|
|
|
mov eax, [esp+4] ; caller's desired setting
|
|
xor edx, edx
|
|
mov ecx, 10000
|
|
div ecx ; round to milliseconds
|
|
|
|
cmp eax, MAXCLOCK_RATE_IN_MS ; desired > max?
|
|
jc short @f
|
|
mov eax, MAXCLOCK_RATE_IN_MS ; yes, use max
|
|
@@:
|
|
|
|
or eax, eax ; MS < min?
|
|
jnz short @f
|
|
inc eax ; yes, use min
|
|
@@:
|
|
|
|
mov CbusClockRateInMillis, eax ; set new rate in milliseconds
|
|
|
|
;
|
|
; inform this processor's local APIC hardware of the change.
|
|
; and then tell all the other processors to update theirs too...
|
|
;
|
|
stdCall _Cbus1ProgramClock
|
|
|
|
mov eax, Cbus1CurrentTimeIncrement
|
|
|
|
stdRET _Cbus1SetTimeIncrement
|
|
|
|
stdENDP _Cbus1SetTimeIncrement
|
|
|
|
page ,132
|
|
subttl "System Clock Interrupt"
|
|
;++
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine is entered as the result of an interrupt generated by CLOCK.
|
|
; 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.
|
|
;
|
|
; Note spurious interrupts are always sent to the spurious interrupt IDT
|
|
; entry, and hence, no need to check for one here.
|
|
;
|
|
; See also Cbus1ClockInterruptPx() - it's used by the non-boot processors,
|
|
; since additional processors don't need to bump any global counters.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
; Interrupt is disabled
|
|
;
|
|
; Return Value:
|
|
;
|
|
; must set up eax with the increment value, also leave ebp pointing at
|
|
; base of trap frame.
|
|
; Does not return, jumps directly to KeUpdateSystemTime, which returns
|
|
;
|
|
; Sets Irql = CLOCK2_LEVEL and dismisses the interrupt
|
|
;
|
|
;--
|
|
ENTER_DR_ASSIST Hck_a, Hck_t
|
|
|
|
cPublicProc _Cbus1ClockInterrupt ,0
|
|
|
|
;
|
|
; Save machine state in trap frame
|
|
; (esp) - base of trap frame
|
|
;
|
|
|
|
ENTER_INTERRUPT Hck_a, Hck_t
|
|
|
|
;
|
|
; Dismiss interrupt and raise irq level to clock2 level
|
|
;
|
|
|
|
push _CbusClockVector
|
|
sub esp, 4 ; allocate space to save OldIrql
|
|
stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL, _CbusClockVector, esp>
|
|
|
|
POKE_LEDS eax, edx
|
|
|
|
;
|
|
; (esp) = OldIrql
|
|
; (esp+4) = Vector
|
|
; (esp+8) = base of trap frame
|
|
; (ebp) = address of trap frame
|
|
; (eax) = time increment
|
|
;
|
|
mov eax, Cbus1CurrentTimeIncrement
|
|
|
|
jmp _KeUpdateSystemTime@0
|
|
|
|
stdENDP _Cbus1ClockInterrupt
|
|
_TEXT ends
|
|
|
|
end
|