NT4/private/ntos/nthals/halcbus/i386/cb2stall.asm
2020-09-30 17:12:29 +02:00

1348 lines
38 KiB
NASM

title "Interval Clock Interrupt"
;++
;
; Copyright (c) 1989 Microsoft Corporation
;
; Module Name:
;
; cb2stall.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 declared 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.
;
; 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.
;
; Landy Wang (corollary!landy) 04-Dec-92
; Move much code into separate modules for easy inclusion by various
; HAL builds.
;
;--
.386p
.xlist
include hal386.inc
include callconv.inc ; calling convention macros
include i386\ix8259.inc
include i386\kimacro.inc
include mac386.inc
include i386\ixcmos.inc
include cbus.inc
.list
EXTRNP _HalEndSystemInterrupt,2
EXTRNP _HalBeginSystemInterrupt,3
if DBG
EXTRNP _HalDisplayString,1
endif
ifdef CBC_REV1
EXTRNP _Cbus2RequestSoftwareInterrupt,1
endif
;
; 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_MODE3 EQU 6 ; Use mode 3
COMMAND_8254_BCD EQU 0 ; Binary count down
COMMAND_8254_LATCH_READ EQU 0 ; Latch read command
PERFORMANCE_FREQUENCY EQU 10000000
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_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 int gate
;
; ==== Values used for System Clock ====
;
_DATA SEGMENT DWORD PUBLIC 'DATA'
;
; The following array stores the per microsecond loop count for each
; central processor.
;
public HalpPerfCounterLow, HalpPerfCounterHigh
if DBG
public CbusClockCount
public CbusClockLate
public _CbusCatchClock
endif
Cbus2PerfInit dd 0
HalpPerfCounterLow dd 0
HalpPerfCounterHigh dd 0
if DBG
CbusClockCount dd 0
CbusClockLate dd 0
_CbusCatchClock dd 0
endif
public Cbus2NumberSpuriousClocks
Cbus2NumberSpuriousClocks dd 0
public HalpCurrentRollOver, HalpCurrentTimeIncrement
HalpCurrentRollOver dd 0
HalpCurrentTimeIncrement dd 0
;
; Convert the interval to rollover count for 8254 Timer1 device.
; Timer1 counts down a 16 bit value at a rate of 1.193181667M counts-per-sec.
;
; The best fit value closest to 10ms is 10.0144012689ms:
; ROLLOVER_COUNT 11949
; TIME_INCREMENT 100144
; Calculated error is -.0109472 s/day
;
;
; The following table contains 8254 values timer values to use at
; any given ms setting from 1ms - 15ms. All values work out to the
; same error per day (-.0109472 s/day).
;
public HalpRollOverTable
; RollOver Time
; Count Increment MS
HalpRollOverTable dd 1197, 10032 ; 1 ms
dd 2394, 20064 ; 2 ms
dd 3591, 30096 ; 3 ms
dd 4767, 39952 ; 4 ms
dd 5964, 49984 ; 5 ms
dd 7161, 60016 ; 6 ms
dd 8358, 70048 ; 7 ms
dd 9555, 80080 ; 8 ms
dd 10731, 89936 ; 9 ms
dd 11949, 100144 ; 10 ms
dd 13125, 110000 ; 11 ms
dd 14322, 120032 ; 12 ms
dd 15519, 130064 ; 13 ms
dd 16695, 139920 ; 14 ms
dd 17892, 149952 ; 15 ms
TimeIncr equ 4
RollOver equ 0
public HalpLargestClockMS, HalpNextMSRate, HalpPendingMSRate
HalpLargestClockMS dd 10 ; Table goes to 15MS, but since we
; use only 486 & above, limit to 10ms.
HalpNextMSRate dd 0
HalpPendingMSRate dd 0
;
; This value is used by CPUx (x>0) during the clock interrupt
; routine to re-trigger the call to KeUpdateRunTime. Processors
; calling KeUpdateRunTime call at the maximum supported rate as
; reported by KeSetTimeIncrement.
; (HAL Development Reference Guide 5.12)
;
public HalpMaxTimeIncrement
HalpMaxTimeIncrement dd 0
;
; Holds the next time increment. HalpCurrentTimeIncrement is
; initialized with this value just before calling KeUpdateSystemTime.
;
public HalpNewTimeIncrement
HalpNewTimeIncrement dd 0
if DBG
;
; Cbus2MaxTimeStamp... max value of time stamp ever.
; Cbus2MinTimeStamp... min value of time stamp ever.
; Cbus2CountOverflowTimeStamp...# of overflows of time stamp.
; Cbus2SumTimeStampHi... Sum of all time stamps.
; Cbus2SumTimeStampLow...
; Cbus2CountClockInt... # of clock interrupts.
;
public Cbus2MaxTimeStamp, Cbus2MinTimeStamp,
Cbus2CountOverflowTimeStamp, Cbus2SumTimeStampHi,
Cbus2SumTimeStampLow, Cbus2CountClockInt
Cbus2MaxTimeStamp dd 0
Cbus2MinTimeStamp dd -1
Cbus2CountOverflowTimeStamp dd 0
Cbus2SumTimeStampHi dd 0
Cbus2SumTimeStampLow dd 0
Cbus2CountClockInt dd 0
endif
_DATA ends
INIT SEGMENT PARA PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
if DBG
RTC_toofast db 'RTC IRQ8 HARDWARE ERROR\n', 0
endif
page ,132
subttl "Initialize Clock"
;++
;
; VOID
; Cbus2InitializeClock (
; )
;
; 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 definitions of TIME_INCREMENT and ROLLOVER_COUNT if clock rate
; needs to be changed.
;
; All processors call this routine, but only the first call needs
; to do anything.
;
; Arguments:
;
; None
;
; Return Value:
;
; None.
;
;--
cPublicProc _Cbus2InitializeClock ,0
cmp dword ptr PCR[PcHal.PcrNumber], 0
jz @f
;
; Initialize the 'TickOffset' field for CPUx (x>0).
; It indicates the number of nsec left before calling the
; KeUpdateRunTime routine. This routine is called at the
; maximum supported rate as reported by KeSetTimeIncrement.
;
mov eax, HalpMaxTimeIncrement
mov PCR[PcHal.PcrTickOffset], eax
stdRET _Cbus2InitializeClock
@@:
mov eax, HalpLargestClockMS
mov ecx, HalpRollOverTable.TimeIncr
mov edx, HalpRollOverTable[eax*8-8].TimeIncr
mov eax, HalpRollOverTable[eax*8-8].RollOver
mov HalpCurrentTimeIncrement, edx
;
; (ecx) = Min time_incr
; (edx) = Max time_incr
; (eax) = max roll over count
;
;
; This value is used by CPUx (x>0) during the clock interrupt
; routine to re-trigger the call to KeUpdateRunTime. Processors
; calling KeUpdateRunTime call at the maximum supported rate as
; reported by KeSetTimeIncrement.
; (HAL Development Reference Guide 5.12)
;
mov HalpMaxTimeIncrement, edx
push eax
stdCall _KeSetTimeIncrement, <edx, ecx>
pop ecx
pushfd ; save caller's eflag
cli ; make sure interrupts are disabled
;
; Set clock rate
; (ecx) = RollOverCount
;
;
; use a 50% duty cycle for the system clock
;
mov al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE3
out TIMER1_CONTROL_PORT0, al ;program count mode of timer 0
IoDelay
mov al, cl
out TIMER1_DATA_PORT0, al ; program timer 0 LSB count
IoDelay
mov al,ch
out TIMER1_DATA_PORT0, al ; program timer 0 MSB count
popfd ; restore caller's eflag
mov HalpCurrentRollOver, ecx ; Set RollOverCount & initialized
;
; Set up the performance counter mechanism as well:
; Zero the global system timer for all of the processors.
;
mov eax, dword ptr [_CbusTimeStamp]
mov dword ptr [eax], 0
mov Cbus2PerfInit, 1 ; calibration done
stdRET _Cbus2InitializeClock
stdENDP _Cbus2InitializeClock
INIT ends
_TEXT$03 SEGMENT DWORD PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
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.
;
; 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 _Cbus2ClockInterrupt ,0
;
; Save machine state in trap frame
;
ENTER_INTERRUPT Hci_a, Hci_t
;
; (esp) - base of trap frame
;
;
; Note that during this phase the interrupts are already disabled.
; This is important because we don't want to take an IPI during this phase
; which may force us to discard the next clock interrupt.
; The call to HalBeginSystemInterrupt below will enable the interrupts.
;
ifdef MCA
;
; Special hack for MCA machines
;
in al, 61h
jmp $+2
or al, 80h
out 61h, al
jmp $+2
endif ; MCA
cmp [_Cbus2CheckSpuriousClock], 1 ; Check for spurious clock?
je Hci200
Hci05:
ifdef CBC_REV1
;
; because we can miss an interrupt due to a hardware bug in the
; CBC rev 1 silicon, send ourselves an IPI on every clock.
; since we don't know when we've missed one, this will ensure
; we don't cause lock timeouts if nothing else!
;
stdCall _Cbus2RequestSoftwareInterrupt, <IPI_LEVEL>
endif
;
; Dismiss interrupt and raise irq level to clock2 level
;
Hci10:
push _CbusClockVector
sub esp, 4 ; allocate space to save OldIrql
stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL, _CbusClockVector, esp>
POKE_LEDS eax, edx
if DBG
inc dword ptr [CbusClockCount]
endif
;
; Update our software 64-bit performance counter and zero the
; 32-bit high resolution CBC timestamp counter. we'd like to
; read off the real amount of elapsed time, ie:
;
; mov eax, dword ptr [ecx]
; add HalpPerfCounterLow, eax
;
; but we can't because when the debugger is enabled, NT holds off
; interrupts for long periods of time, ie: like in DbgLoadImageSymbols()
; for over 700 _milliseconds_ !!!. so just fib like all the other
; HALs do, and tell NT only 10ms have gone by.
;
if DBG
;
; we had a problem where clock interrupts are getting
; held off for approximately 700 ms once per second! here is
; the debug code which caught DbgLoadImageSymbols.
;
;mov ecx, dword ptr [_CbusTimeStamp]
;mov eax, dword ptr [ecx]
;cmp _CbusCatchClock, 0 ; debug breakpoint desired?
;je short @f
;cmp eax, 2000000 ; if more than 200 milliseconds since
; the last clockintr, then trigger
; the analyzer and go into debug
;jb short @f
;inc dword ptr [CbusClockLate] ; trigger analyzer
;int 3
@@:
endif
mov eax, HalpCurrentTimeIncrement ; current time increment
mov HalpNewTimeIncrement, eax ; next time increment
Hci30:
;
; (esp) = OldIrql
; (esp+4) = Vector
; (esp+8) = base of trap frame
; ebp = trap frame
; eax = current time increment
;
cmp HalpNextMSRate, 0 ; New clock rate desired?
jnz short Hci60 ; Yes, program timer h/w.
Hci40:
mov ebx, HalpNewTimeIncrement
;
; Update performance counters.
; Do not change without reason the order of the following instractions.
; They are crafted in this way for the HalQueryPerformanceCounter
; routine (it can run at the same time on a different processor).
; The interrupts are disabled because we don't want to take an IPI
; during this process.
;
;
; eax = current time increment
; ebx = next time increment
;
mov ecx, dword ptr [_CbusTimeStamp]
cmp [_Cbus2CheckSpuriousClock], 1 ; Calculate new SystemTimer.
pushfd ; Save and disable flags.
cli
je Hci210
sub edx, edx
Hci50:
;
; Awkward ordering done to place the resetting of the SystemTimer
; as close to the update of the 64-bit performance counter --
;
mov dword ptr [ecx], edx ; New TimeStamp value.
add HalpPerfCounterLow, eax
adc HalpPerfCounterHigh, dword ptr 0
mov HalpCurrentTimeIncrement, ebx ; Next time increment.
popfd ; Restore flags.
;
; (esp) = OldIrql
; (esp+4) = Vector
; (esp+8) = base of trap frame
; ebp = trap frame
; eax = time increment
;
jmp _KeUpdateSystemTime@0 ; dispatch this tick
Hci60:
;
; Time of clock frequency is being changed. See if the 8254 was
; was reprogrammed for a new rate during last tick
;
; (eax) = time increment for current tick
cmp HalpPendingMSRate, 0 ; Was a new rate set durning last
jnz short Hci80 ; tick? Yes, go update globals
Hci70:
;
; A new clock rate needs to be set. Setting the rate here will
; cause the tick after the next tick to be at the new rate.
;
; The next tick is already in progress by the 8254 and will occur
; at the following rate : ~(old rate + new rate)/2 because the new
; count will be loaded at the end of the current half-cycle.
;
; (eax) = time increment for current tick
mov ebx, HalpNextMSRate
mov HalpPendingMSRate, ebx ; pending rate
;
; Next tick increment = ~(current TimeIncr + new TimeIncr)/2
;
mov ecx, HalpNewTimeIncrement
add ecx, HalpRollOverTable[ebx*8-8].TimeIncr
shr ecx, 1
mov HalpNewTimeIncrement, ecx
mov ecx, HalpRollOverTable[ebx*8-8].RollOver
;
; Set clock rate
; (ecx) = RollOverCount
;
push eax ; save current tick's rate
;
; use a 50% duty cycle for the system clock
;
mov al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE3
out TIMER1_CONTROL_PORT0, al ;program count mode of timer 0
IoDelay
mov al, cl
out TIMER1_DATA_PORT0, al ; program timer 0 LSB count
IoDelay
mov al,ch
out TIMER1_DATA_PORT0, al ; program timer 0 MSB count
pop eax
jmp short Hci40 ; dispatch this tick
Hci80:
;
; The next tick will occur at the rate which was programmed during the last
; tick. Update globals for new rate which starts with the next tick.
;
; (eax) = time increment for current tick
;
mov ebx, HalpPendingMSRate
mov ecx, HalpRollOverTable[ebx*8-8].RollOver
mov edx, HalpRollOverTable[ebx*8-8].TimeIncr
mov HalpCurrentRollOver, ecx
mov HalpNewTimeIncrement, edx ; next tick rate
mov HalpPendingMSRate, 0 ; no longer pending, clear it
cmp ebx, HalpNextMSRate ; new rate == NextRate?
jne short Hci70 ; no, go set new pending rate
mov HalpNextMSRate, 0 ; we are at this rate, clear it
jmp Hci30 ; process this tick
Hci100:
add esp, 8 ; spurious, no EndOfInterrupt
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
;
; Check if this is a spurious interrupt.
;
Hci200:
mov ecx, dword ptr [_CbusTimeStamp]
mov edx, dword ptr [ecx] ; Get its value
sub edx, HalpCurrentTimeIncrement ; In range ?
jb short Hci240 ; No, this is spurious.
jmp Hci05
;
; Ensure that the time stamp is no larger than HalpCurrentTimeIncrement.
; We couldn't perform this code earlier when Hci200 was called
; because it would open a window where the System Timer has been
; reset, but the 64-bit performance counter has not been updated.
; An additional CPU, in this case, could get a smaller value than
; previously requested. To minimize this case, the resetting of
; the SystemTimer should be as close to the 64-bit performance counter
; as possible.
;
; in: ecx = time stamp address.
; eax = current time increment.
; ebx = next time increment.
;
; out: ecx = time stamp address.
; eax = current time increment.
; ebx = next time increment.
; edx = new time stamp value to set.
;
Hci210:
mov edx, dword ptr [ecx] ; Get its value
sub edx, eax ; Remove current increment.
if DBG
;
; Collect data for average.
;
cmp edx, 20000000 ; Debugging if above 2 sec.
ja short @f
inc Cbus2CountClockInt
add Cbus2SumTimeStampLow, edx
adc Cbus2SumTimeStampHi, 0
@@:
;
; Compute minimum.
;
cmp Cbus2MinTimeStamp, edx
jbe short @f
mov Cbus2MinTimeStamp, edx
@@:
endif
cmp edx, eax ; Time stamp must be <= unit.
jbe Hci50 ; Yes, process the int.
if DBG
;
; Compute maximum.
;
cmp edx, 20000000 ; Debugging if above 2 sec.
ja short @f
inc Cbus2CountOverflowTimeStamp
cmp Cbus2MaxTimeStamp, edx
ja short @f
mov Cbus2MaxTimeStamp, edx
@@:
endif
;
; Use the whole time increment.
;
mov edx, eax
jmp Hci50
;
; This is a spurious interrupt.
;
Hci240:
inc [Cbus2NumberSpuriousClocks]
mov eax, [_Cbus2ClockVector] ; get the interrupting vector
CBUS_EOI eax, edx ; ack the interrupt controller
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without eoi
stdENDP _Cbus2ClockInterrupt
page ,132
subttl "System Clock Interrupt for Additional Processors"
;++
;
; Routine Description:
;
; This routine is entered as the result of an interrupt generated by CLOCK.
; This routine is entered only by additional (non-boot) processors, so it
; must NOT update the performance counter or update the system time.
;
; instead, it just dismisses the interrupt, raises system Irql to
; CLOCK2_LEVEL and transfers control to the standard system routine
; to update the execution time of the current thread and process.
;
; Arguments:
;
; None
; Interrupt is disabled
;
; Return Value:
;
; Does not return, jumps directly to KeUpdateRunTime, which returns
;
; Sets Irql = CLOCK2_LEVEL and dismisses the interrupt
;
;--
ENTER_DR_ASSIST Hcix2_a, Hcix2_t
cPublicProc _Cbus2ClockInterruptPx ,0
;
; Save machine state in trap frame
; (esp) - base of trap frame
;
ENTER_INTERRUPT Hcix2_a, Hcix2_t
mov eax, HalpCurrentTimeIncrement ; Get the clock period.
mov ecx, dword ptr [_CbusTimeStamp] ; Get the time stamp address.
;
; Note that during this phase the interrupts are already disabled.
; This is important because we don't want to take an IPI during this
; phase which may force us to discard the next clock interrupt.
; The call to HalBeginSystemInterrupt below will enable the interrupts.
;
cmp [_Cbus2CheckSpuriousClock], 1 ; Check for spurious clock?
je short Hcix70
sub edx, edx ;
Hcix50:
mov dword ptr [ecx], edx ; New SystemTimer value.
;
; We call the KeUpdateRunTime routine only at the maximum
; rate reported by KeSetTimeIncrement.
;
sub PCR[PcHal.PcrTickOffset], eax ; subtract time increment.
jg short Hcix100 ; if greater, not complete tick.
mov eax, HalpMaxTimeIncrement ; Re-trigger the call.
add PCR[PcHal.PcrTickOffset], eax ;
ifdef CBC_REV1
;
; because we can miss an interrupt due to a hardware bug in the
; CBC rev 1 silicon, send ourselves an IPI on every clock.
; since we don't know when we've missed one, this will ensure
; we don't cause lock timeouts if nothing else!
;
stdCall _Cbus2RequestSoftwareInterrupt, <IPI_LEVEL>
endif
;
; 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>
; Spurious interrupts on Corollary hardware are
; directed to a different interrupt gate, so no need
; to check return value above.
POKE_LEDS eax, edx
;
; (esp) = OldIrql
; (esp+4) = Vector
; (esp+8) = base of trap frame
;
; (ebp) = base of trap frame for KeUpdateRunTime, this was set
; up by the ENTER_INTERRUPT macro above
stdCall _KeUpdateRunTime,<dword ptr [esp]>
INTERRUPT_EXIT
;
; Check if this is a spurious interrupt.
;
; in: ecx = time stamp address.
; eax = current time increment.
;
; out: ecx = time stamp address.
; eax = current time increment.
; edx = new time stamp value to set.
;
Hcix70:
mov edx, dword ptr [ecx] ; Get its value
sub edx, eax ; In range ?
jb short Hcix100 ; No, this is spurious.
cmp edx, eax ; Time stamp must be <= unit.
jbe short Hcix50 ; Yes, process the int.
;
; Use the whole time-stamp unit.
;
mov edx, eax
jmp short Hcix50
;
; This is a spurious interrupt.
;
Hcix100:
SPURIOUS_INTERRUPT_EXIT ; exit interrupt without EOI
stdENDP _Cbus2ClockInterruptPx
;++
;
; ULONG
; Cbus2SetTimeIncrement (
; IN ULONG DesiredIncrement
; )
;
; /*++
;
; Routine Description:
;
; This routine initializes the system time clock to generate an
; interrupt at every DesiredIncrement interval. No lock synchronization
; is needed here, since it is done by the executive prior to calling us.
;
; Arguments:
;
; DesiredIncrement - desired interval between every timer tick (in
; 100ns unit.)
;
; Return Value:
;
; The *REAL* time increment set.
;--
cPublicProc _Cbus2SetTimeIncrement,1
mov eax, [esp+4] ; desired setting
xor edx, edx
mov ecx, 10000
div ecx ; round to MS
cmp eax, HalpLargestClockMS ; MS > max?
jc short @f
mov eax, HalpLargestClockMS ; yes, use max
@@:
or eax, eax ; MS < min?
jnz short @f
inc eax ; yes, use min
@@:
mov HalpNextMSRate, eax
mov eax, HalpRollOverTable[eax*8-8].TimeIncr
stdRET _Cbus2SetTimeIncrement
stdENDP _Cbus2SetTimeIncrement
page ,132
subttl "Query Performance Counter"
;++
;
; LARGE_INTEGER
; Cbus2QueryPerformanceCounter (
; 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+4] ; User supplied Performance Frequency
cPublicProc _Cbus2QueryPerformanceCounter ,1
;
; 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 performance counter calibration has occured.
;
cmp Cbus2PerfInit, 0 ; calibration finished yet?
jne @f ; yes, we can read the perf counter
;
; Initialization hasn't occurred yet, so just return zeroes.
;
mov eax, 0
mov edx, 0
jmp ret_freq
@@:
;
; Merge our software 64-bit performance counter and the
; 32-bit high resolution CBC timestamp counter into edx:eax
;
push ebx ; ebx will be destroyed
@@:
mov ebx, HalpCurrentTimeIncrement
mov edx, HalpPerfCounterHigh
mov eax, HalpPerfCounterLow
mov ecx, dword ptr [_Cbus2TimeStamp0]
mov ecx, dword ptr [ecx]
;
; re-read the global counters until we see we didn't get a torn read.
;
cmp ebx, HalpCurrentTimeIncrement
jne short @b
cmp edx, HalpPerfCounterHigh
jne short @b
cmp eax, HalpPerfCounterLow
jne short @b
;
; This code can run on any CPU while the 'perf counters'
; are updated only by CPU 0. If the first CPU is unable
; to receive the clock interrupt because it is 'cli-ed'
; or 'ipi-ed' for a long time, we need to return a value
; greater than the provious returned value but less or equal
; to any future value.
;
cmp ecx, ebx ; Time stamp must be <= unit
jbe short @f ; Yes, it is
mov ecx, ebx ; else use unit
@@:
pop ebx ; Restore ebx
;
; since the time stamp register and our 64-bit software register
; are already both in 100ns units, there's no need for conversion here.
;
add eax, ecx
adc edx, dword ptr 0
ret_freq:
;
; return value is in edx:eax, return the performance counter
; frequency if the caller wants it.
;
or dword ptr KqpcFrequency, 0 ; is it a NULL variable?
jz short @f ; it's NULL, so bail
mov ecx, KqpcFrequency ; (ecx)-> Frequency variable
;
; Set frequency to a hardcoded clock speed of 66Mhz for now.
;
mov DWORD PTR [ecx], PERFORMANCE_FREQUENCY
mov DWORD PTR [ecx+4], 0
@@:
stdRET _Cbus2QueryPerformanceCounter
stdENDP _Cbus2QueryPerformanceCounter
_TEXT$03 ends
; CMOS_READ
;
; Description: This macro read a byte from the CMOS register specified
; in (AL).
;
; Parameter: (AL) = address/register to read
; Return: (AL) = data
;
CMOS_READ MACRO
OUT CMOS_CONTROL_PORT,al ; ADDRESS LOCATION AND DISABLE NMI
IODelay ; I/O DELAY
IN AL,CMOS_DATA_PORT ; READ IN REQUESTED CMOS DATA
IODelay ; I/O DELAY
ENDM
;
; CMOS_WRITE
;
; Description: This macro read a byte from the CMOS register specified
; in (AL).
;
; Parameter: (AL) = address/register to read
; (AH) = data to be written
;
; Return: None
;
CMOS_WRITE MACRO
OUT CMOS_CONTROL_PORT,al ; ADDRESS LOCATION AND DISABLE NMI
IODelay ; I/O DELAY
MOV AL,AH ; (AL) = DATA
OUT CMOS_DATA_PORT,AL ; PLACE IN REQUESTED CMOS LOCATION
IODelay ; I/O DELAY
ENDM
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
; Cbus2InitializeStall (
; IN CCHAR ProcessorNumber
; )
;
; Routine Description:
;
; This routine initialize the per Microsecond counter for
; KeStallExecutionProcessor. Note that the additional processors
; execute this loop in Phase1, so they are already getting clock
; interrupts as well as the RTC interrupts they expect from this
; routine. We should disable clock interrupts during this period
; to ensure a really accurate result, but it should be "good enough"
; for now.
;
; Arguments:
;
; ProcessorNumber - Processor Number
;
; Return Value:
;
; None.
;
;--
KiseInterruptCount equ [ebp-12] ; local variable
cPublicProc _Cbus2InitializeStall ,1
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 every 125ms at
; IRQ 8.
;
cli ; make sure interrupts are disabled
;
; Since RTC interrupt will come from IRQ 8, we need to save
; the original irq 8 descriptor and set the descriptor to point to
; our own handler.
;
sidt fword ptr [ebp-8] ; get IDT address
mov ecx, [ebp-6] ; (edx)->IDT
;
; the profile vector varies for each platform
;
mov eax, dword ptr [_ProfileVector]
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
;
; Pushing the appropriate entry address now (instead of
; the IDT start address later) to make the pop at the end simpler.
; we actually will retrieve this value twice, but only pop it once.
;
push ecx ; (TOS) -> &IDT[HalProfileVector]
;
; No need to get and save current interrupt masks - only IPI
; and software interrupts are enabled at this point in the kernel
; startup. since other processors are still in reset, the profile
; interrupt is the only one we will see. we really don't need to save
; the old IDT[PROFILE_VECTOR] entry either, by the way - it's just
; going to be overwritten in HalInitSystem Phase 1 anyway.
;
; note we are not saving edx before calling this function
stdCall _HalEnableSystemInterrupt,<dword ptr [_ProfileVector],PROFILE_LEVEL,0>
mov ecx, dword ptr [esp] ; restore IDT pointer
mov eax, offset FLAT:RealTimeClockHandler
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 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
;
; register C _MUST_ be read before register B is initialized,
; otherwise an interrupt which was already pending will happen
; immediately.
;
mov al,0CH ; Register C
CMOS_READ ; Read to initialize
;
; Don't clobber the Daylight Savings Time bit in register B, because we
; stash the LastKnownGood "environment variable" there.
;
mov ax, 0bh
CMOS_READ
and al, 1
mov ah, al
or ah, REGISTER_B_ENABLE_PERIODIC_INTERRUPT
mov al, 0bh
CMOS_WRITE ; Initialize it
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
ALIGN 16
sti
jmp kise10
ALIGN 16
kise10:
sub eax, 1 ; increment the loopcount
jnz short kise10
if DBG
;
; Counter overflowed
;
stdCall _DbgBreakPoint
endif
jmp short kise10
;
; Our RealTimeClock interrupt handler. The control comes here through
; irq 8.
; Note: we discard the 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 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
;
; register C _MUST_ be read before register B is initialized,
; otherwise an interrupt which was already pending will happen
; immediately.
;
mov al,0CH ; Register C
CMOS_READ ; Read to initialize
;
; Don't clobber the Daylight Savings Time bit in register B, because we
; stash the LastKnownGood "environment variable" there.
;
mov ax, 0bh
CMOS_READ
and al, 1
mov ah, al
or ah, REGISTER_B_ENABLE_PERIODIC_INTERRUPT
mov al, 0bh
CMOS_WRITE ; Initialize it
mov al,0DH ; Register D
CMOS_READ ; Read to initialize
mov eax, _ProfileVector ; mark interrupting vec
CBUS_EOI eax, ecx ; destroy eax & ecx
xor eax, eax ; reset loop counter
stdCall _HalpReleaseCmosSpinLock
iretd
align 4
kise25:
if DBG
;
; ** temporary - check for incorrect KeStallExecutionProcessorLoopCount
;
cmp eax, 0
jnz short kise30
stdCall _DbgBreakPoint
; never return
;
; ** End temporary code
;
kise30:
endif
neg eax
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
align 4
kise40:
mov PCR[PcStallScaleFactor], eax
;
; Reset return address to kexit
;
pop eax ; discard original return address
push offset FLAT:kexit ; return to kexit
;
; Shutdown the periodic RTC interrupt
;
stdCall _HalpAcquireCmosSpinLock
mov ax,(RegisterAInitByte SHL 8) OR 0AH ; Register A
CMOS_WRITE ; Initialize it
mov ax, 0bh
CMOS_READ
and al, 1
mov ah, al
or ah, REGISTER_B_DISABLE_PERIODIC_INTERRUPT
mov al, 0bh
CMOS_WRITE ; Initialize it
mov al,0CH ; Register C
CMOS_READ ; dismiss pending interrupt
stdCall _HalpReleaseCmosSpinLock
stdCall _HalDisableSystemInterrupt,<dword ptr [_ProfileVector],PROFILE_LEVEL>
mov eax, _ProfileVector ; mark interrupting vec
CBUS_EOI eax, ecx ; destroy eax & ecx
and word ptr [esp+8], NOT 0200H ; Disable interrupt upon return
iretd
align 4
kexit: ; Interrupts are disabled
pop ecx ; (ecx) -> &IDT[HalProfileVector]
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 _Cbus2InitializeStall
stdENDP _Cbus2InitializeStall
INIT ends
end