summaryrefslogtreecommitdiffstats
path: root/private/ntos/nthals/halcbus/i386/cb1stall.asm
diff options
context:
space:
mode:
Diffstat (limited to 'private/ntos/nthals/halcbus/i386/cb1stall.asm')
-rw-r--r--private/ntos/nthals/halcbus/i386/cb1stall.asm932
1 files changed, 932 insertions, 0 deletions
diff --git a/private/ntos/nthals/halcbus/i386/cb1stall.asm b/private/ntos/nthals/halcbus/i386/cb1stall.asm
new file mode 100644
index 000000000..719627aef
--- /dev/null
+++ b/private/ntos/nthals/halcbus/i386/cb1stall.asm
@@ -0,0 +1,932 @@
+ 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