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