mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1000 lines
24 KiB
1000 lines
24 KiB
title "Interval Clock Interrupt"
|
|
;++
|
|
;
|
|
; Copyright (c) 1989 Microsoft Corporation
|
|
; Copyright (c) 1992 Intel Corporation
|
|
; All rights reserved
|
|
;
|
|
; INTEL CORPORATION PROPRIETARY INFORMATION
|
|
;
|
|
; This software is supplied to Microsoft under the terms
|
|
; of a license agreement with Intel Corporation and may not be
|
|
; copied nor disclosed except in accordance with the terms
|
|
; of that agreement.
|
|
;
|
|
;
|
|
; Module Name:
|
|
;
|
|
; mpclock.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:
|
|
;
|
|
; Ron Mosgrove (Intel) Aug 1993
|
|
; Modified to support PC+MP Systems
|
|
;--
|
|
|
|
.586p
|
|
.xlist
|
|
include hal386.inc
|
|
include i386\ix8259.inc
|
|
include i386\ixcmos.inc
|
|
include callconv.inc
|
|
include i386\kimacro.inc
|
|
include mac386.inc
|
|
include i386\apic.inc
|
|
include i386\pcmp_nt.inc
|
|
.list
|
|
|
|
EXTRNP _DbgBreakPoint,0,IMPORT
|
|
EXTRNP _KeUpdateSystemTime,0
|
|
EXTRNP _KeUpdateRunTime,1,IMPORT
|
|
EXTRNP Kei386EoiHelper,0,IMPORT
|
|
EXTRNP _HalEndSystemInterrupt,2
|
|
EXTRNP _HalBeginSystemInterrupt,3
|
|
EXTRNP _HalpAcquireCmosSpinLock ,0
|
|
EXTRNP _HalpReleaseCmosSpinLock ,0
|
|
EXTRNP _HalSetProfileInterval ,1
|
|
|
|
EXTRNP _HalpSetInitialClockRate,0
|
|
|
|
EXTRNP _HalpMcaQueueDpc, 0
|
|
|
|
extrn _HalpRtcTimeIncrements:DWORD
|
|
extrn _HalpGlobal8259Mask:WORD
|
|
extrn _HalpMpInfoTable:DWORD
|
|
|
|
;
|
|
; Constants used to initialize CMOS/Real Time Clock
|
|
;
|
|
|
|
CMOS_CONTROL_PORT EQU 70h ; command port for cmos
|
|
CMOS_DATA_PORT EQU 71h ; cmos data port
|
|
CMOS_STATUS_BUSY EQU 80H ; Time update in progress
|
|
|
|
D_INT032 EQU 8E00h ; access word for 386 ring 0 interrupt gate
|
|
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 ;
|
|
|
|
|
|
_DATA SEGMENT DWORD PUBLIC 'DATA'
|
|
|
|
;
|
|
; There is a "C" version of this structure in MPCLOCKC.C
|
|
;
|
|
|
|
TimeStrucSize EQU 20
|
|
|
|
RtcTimeIncStruc struc
|
|
RTCRegisterA dd 0 ;The RTC register A value for this rate
|
|
RateIn100ns dd 0 ;This rate in multiples of 100ns
|
|
RateAdjustmentNs dd 0 ;Error Correction (in ns)
|
|
RateAdjustmentCnt dd 0 ;Error Correction (as a fraction of 256)
|
|
IpiRate dd 0 ;IPI Rate Count (as a fraction of 256)
|
|
RtcTimeIncStruc ends
|
|
|
|
ifdef DBGSSF
|
|
DebugSSFStruc struc
|
|
SSFCount1 dd 0
|
|
SSFCount2 dd 0
|
|
SSFRdtsc1 dd 0
|
|
SSFRdtsc2 dd 0
|
|
SSFRdtsc3 dd 0
|
|
|
|
SSFRna1 dd 0
|
|
SSFRna2 dd 0
|
|
SSFRna3 dd 0
|
|
|
|
DebugSSFStruc ends
|
|
|
|
public HalpDbgSSF
|
|
HalpDbgSSF db (size DebugSSFStruc) * 10 dup (0)
|
|
|
|
endif
|
|
|
|
ALIGN dword
|
|
|
|
public RTCClockFreq
|
|
public RegisterAClockValue
|
|
|
|
RTCClockFreq dd 156250
|
|
RegisterAClockValue dd 00101010B ; default interval = 15.6250 ms
|
|
|
|
MINIMUM_STALL_FACTOR EQU 10H ; Reasonable Minimum
|
|
|
|
public HalpP0StallFactor
|
|
HalpP0StallFactor dd MINIMUM_STALL_FACTOR
|
|
public HalpInitStallComputedCount
|
|
HalpInitStallComputedCount dd 0
|
|
public HalpInitStallLoopCount
|
|
HalpInitStallLoopCount dd 0
|
|
|
|
ALIGN dword
|
|
;
|
|
; Clock Rate Adjustment Counter. This counter is used to keep a tally of
|
|
; adjustments needed to be applied to the RTC rate as passed to the
|
|
; kernel.
|
|
;
|
|
|
|
public _HalpCurrentRTCRegisterA, _HalpCurrentClockRateIn100ns
|
|
public _HalpCurrentClockRateAdjustment, _HalpCurrentIpiRate
|
|
public _HalpIpiRateCounter, _HalpNextRate, _HalpPendingRate
|
|
public _HalpRateAdjustment
|
|
_HalpCurrentRTCRegisterA dd 0
|
|
_HalpCurrentClockRateIn100ns dd 0
|
|
_HalpCurrentClockRateAdjustment dd 0
|
|
_HalpCurrentIpiRate dd 0
|
|
_HalpIpiRateCounter dd 0
|
|
_HalpNextRate dd 0
|
|
_HalpPendingRate dd 0
|
|
_HalpRateAdjustment dd 0
|
|
|
|
|
|
;
|
|
; Inti value for the RTC
|
|
;
|
|
public _HalpRTCApic, _HalpRTCInti
|
|
_HalpRTCApic dd 0
|
|
_HalpRTCInti dd 0
|
|
|
|
;
|
|
; 8254 spinlock. This must be acquired before touching the 8254 chip.
|
|
; This is no longer used here but ixbeep needs it declared.
|
|
;
|
|
public _Halp8254Lock
|
|
|
|
_Halp8254Lock dd 0
|
|
|
|
;
|
|
; Flag to tell clock routine when P0 can Ipi Other processors
|
|
;
|
|
|
|
public _HalpIpiClock
|
|
_HalpIpiClock dd 0
|
|
|
|
public _HalpClockWork, _HalpClockSetMSRate, _HalpClockMcaQueueDpc
|
|
_HalpClockWork label dword
|
|
_HalpClockSetMSRate db 0
|
|
_HalpClockMcaQueueDpc db 0
|
|
_bReserved1 db 0
|
|
_bReserved2 db 0
|
|
|
|
_DATA ends
|
|
|
|
|
|
INIT SEGMENT PARA PUBLIC 'CODE'
|
|
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
|
|
|
|
page ,132
|
|
subttl "Initialize Clock"
|
|
;++
|
|
;
|
|
; VOID
|
|
; HalpInitializeClock (
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This routine initialize system time clock using RTC to generate an
|
|
; interrupt at every 15.6250 ms interval at APIC_CLOCK_VECTOR
|
|
;
|
|
; See the definition of RegisterAClockValue if clock rate needs to be
|
|
; changed.
|
|
;
|
|
; This routine assumes it runs during Phase 0 on P0.
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
cPublicProc _HalpInitializeClock ,0
|
|
|
|
pushfd ; save caller's eflag
|
|
cli ; make sure interrupts are disabled
|
|
|
|
stdCall _HalpSetInitialClockRate
|
|
|
|
;
|
|
; Set the interrupt rate to what is actually needed
|
|
;
|
|
stdCall _HalpAcquireCmosSpinLock ; intr disabled
|
|
|
|
mov eax, _HalpCurrentRTCRegisterA
|
|
shl ax, 8
|
|
mov al, 0AH ; Register A
|
|
CMOS_WRITE ; Initialize it
|
|
;
|
|
; 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,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0DH ; Register D
|
|
CMOS_READ ; Read to initialize
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
|
|
popfd ; restore caller's eflag
|
|
|
|
stdRET _HalpInitializeClock
|
|
|
|
stdENDP _HalpInitializeClock
|
|
|
|
page ,132
|
|
subttl "Scale Apic Timer"
|
|
;++
|
|
;
|
|
; VOID
|
|
; HalpScaleTimers (
|
|
; IN VOID
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; Determines the frequency of the APIC timer. This routine is run
|
|
; during initialization
|
|
;
|
|
;
|
|
;--
|
|
|
|
cPublicProc _HalpScaleTimers ,0
|
|
push ebx
|
|
push esi
|
|
push edi
|
|
|
|
;
|
|
; Don't let anyone in until we've finished here
|
|
;
|
|
stdCall _HalpAcquireCmosSpinLock
|
|
|
|
;
|
|
; Protect us from interrupts
|
|
;
|
|
pushfd
|
|
cli
|
|
|
|
;
|
|
; First set up the Local Apic Counter
|
|
;
|
|
; The following code assumes the CPU clock will never
|
|
; exceed 4Ghz. For the short term this is probably OK
|
|
;
|
|
|
|
;
|
|
; Configure the APIC timer
|
|
;
|
|
|
|
APIC_TIMER_DISABLED equ (INTERRUPT_MASKED OR PERIODIC_TIMER OR APIC_PROFILE_VECTOR)
|
|
TIMER_ROUNDING equ 10000
|
|
|
|
|
|
mov dword ptr APIC[LU_TIMER_VECTOR], APIC_TIMER_DISABLED
|
|
mov dword ptr APIC[LU_DIVIDER_CONFIG], LU_DIVIDE_BY_1
|
|
|
|
;
|
|
; We're going to do this twice & take the second results
|
|
;
|
|
mov esi, 2
|
|
hst10:
|
|
|
|
;
|
|
; Make sure the write has occurred
|
|
;
|
|
mov eax, dword ptr APIC[LU_DIVIDER_CONFIG]
|
|
|
|
;
|
|
; We don't care what the actual time is we are only interested
|
|
; in seeing the UIP transition. We are garenteed a 1 sec interval
|
|
; if we wait for the UIP bit to complete an entire cycle.
|
|
|
|
;
|
|
; We also don't much care which direction the transition we use is
|
|
; as long as we wait for the same transition to read the APIC clock.
|
|
; Just because it is most likely that when we begin the UIP bit will
|
|
; be clear, we'll use the transition from !UIP to UIP.
|
|
;
|
|
|
|
;
|
|
; Wait for the UIP bit to be cleared, this is our starting state
|
|
;
|
|
|
|
@@:
|
|
mov al, 0Ah ; Specify register A
|
|
CMOS_READ ; (al) = CMOS register A
|
|
test al, CMOS_STATUS_BUSY ; Is time update in progress?
|
|
jnz short @b ; if z, no, wait some more
|
|
|
|
;
|
|
; Wait for the UIP bit to get set
|
|
;
|
|
|
|
@@:
|
|
mov al, 0Ah ; Specify register A
|
|
CMOS_READ ; (al) = CMOS register A
|
|
test al, CMOS_STATUS_BUSY ; Is time update in progress?
|
|
jz short @b ; if z, no, wait some more
|
|
|
|
;
|
|
; At this point we found the UIP bit set, now set the initial
|
|
; count. Once we write this register its value is copied to the
|
|
; current count register and countdown starts or continues from
|
|
; there
|
|
;
|
|
|
|
xor eax, eax
|
|
cpuid ; fence
|
|
|
|
|
|
mov ecx, MsrTSC ; MSR of RDTSC
|
|
xor edx, edx
|
|
mov eax, edx
|
|
mov dword ptr APIC[LU_INITIAL_COUNT], 0FFFFFFFFH
|
|
wrmsr ; Clear TSC count
|
|
|
|
;
|
|
; Wait for the UIP bit to be cleared again
|
|
;
|
|
|
|
@@:
|
|
mov al, 0Ah ; Specify register A
|
|
CMOS_READ ; (al) = CMOS register A
|
|
test al, CMOS_STATUS_BUSY ; Is time update in progress?
|
|
jnz short @b ; if z, no, wait some more
|
|
|
|
;
|
|
; Wait for the UIP bit to get set
|
|
;
|
|
|
|
@@:
|
|
mov al, 0Ah ; Specify register A
|
|
CMOS_READ ; (al) = CMOS register A
|
|
test al, CMOS_STATUS_BUSY ; Is time update in progress?
|
|
jz short @b ; if z, no, wait some more
|
|
|
|
;
|
|
; The cycle is complete, we found the UIP bit set. Now read
|
|
; the counters and compute the frequency. The frequency is
|
|
; just the ticks counted which is the initial value minus
|
|
; the current value.
|
|
;
|
|
|
|
xor eax, eax
|
|
cpuid ; fence
|
|
|
|
rdtsc
|
|
mov ecx, dword ptr APIC[LU_CURRENT_COUNT]
|
|
|
|
dec esi ; if this is the first time
|
|
jnz hst10 ; around, go loop
|
|
|
|
mov PCR[PcHal.TSCHz], eax
|
|
|
|
mov eax, 0FFFFFFFFH
|
|
sub eax, ecx
|
|
|
|
;
|
|
; Round the Apic Timer Freq
|
|
;
|
|
|
|
xor edx, edx ; (edx:eax) = dividend
|
|
|
|
mov ecx, TIMER_ROUNDING
|
|
div ecx ; now edx has remainder
|
|
|
|
cmp edx, TIMER_ROUNDING / 2
|
|
jle @f ; if less don't round
|
|
inc eax ; else round up
|
|
@@:
|
|
|
|
;
|
|
; Multiply by the Rounding factor to get the rounded Freq
|
|
;
|
|
mov ecx, TIMER_ROUNDING
|
|
xor edx, edx
|
|
mul ecx
|
|
|
|
mov dword ptr PCR[PcHal.ApicClockFreqHz], eax
|
|
|
|
;
|
|
; Round TSC freq
|
|
;
|
|
|
|
mov eax, PCR[PcHal.TSCHz]
|
|
xor edx, edx
|
|
|
|
mov ecx, TIMER_ROUNDING
|
|
div ecx ; now edx has remainder
|
|
|
|
cmp edx, TIMER_ROUNDING / 2
|
|
jle @f ; if less don't round
|
|
inc eax ; else round up
|
|
@@:
|
|
mov ecx, TIMER_ROUNDING
|
|
xor edx, edx
|
|
mul ecx
|
|
|
|
mov PCR[PcHal.TSCHz], eax
|
|
|
|
;
|
|
; Convert TSC to microseconds
|
|
;
|
|
|
|
xor edx, edx
|
|
mov ecx, 1000000
|
|
div ecx ; Convert to microseconds
|
|
|
|
xor ecx, ecx
|
|
cmp ecx, edx ; any remainder?
|
|
adc eax, ecx ; Yes, add one
|
|
|
|
mov PCR[PcStallScaleFactor], eax
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
|
|
;
|
|
; Return Value is the timer frequency
|
|
;
|
|
|
|
mov eax, dword ptr PCR[PcHal.ApicClockFreqHz]
|
|
mov PCR[PcHal.ProfileCountDown], eax
|
|
|
|
;
|
|
; Set the interrupt rate in the chip.
|
|
;
|
|
|
|
mov dword ptr APIC[LU_INITIAL_COUNT], eax
|
|
|
|
popfd
|
|
|
|
pop edi
|
|
pop esi
|
|
pop ebx
|
|
|
|
stdRET _HalpScaleTimers
|
|
stdENDP _HalpScaleTimers
|
|
|
|
|
|
INIT ends
|
|
|
|
_TEXT SEGMENT PARA PUBLIC 'CODE'
|
|
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
|
|
|
|
|
|
page ,132
|
|
subttl "Stall Execution"
|
|
;++
|
|
;
|
|
; VOID
|
|
; KeStallExecutionProcessor (
|
|
; IN ULONG MicroSeconds
|
|
; )
|
|
;
|
|
; Routine Description:
|
|
;
|
|
; This function stalls execution for the specified number of microseconds.
|
|
; KeStallExecutionProcessor
|
|
;
|
|
; Arguments:
|
|
;
|
|
; MicroSeconds - Supplies the number of microseconds that execution is to be
|
|
; stalled.
|
|
;
|
|
; Return Value:
|
|
;
|
|
; None.
|
|
;
|
|
;--
|
|
|
|
MicroSeconds equ [esp + 12]
|
|
|
|
cPublicProc _KeStallExecutionProcessor ,1
|
|
|
|
push ebx
|
|
push edi
|
|
|
|
;
|
|
; Issue a CPUID to implement a "fence"
|
|
;
|
|
xor eax, eax
|
|
fence1: cpuid
|
|
|
|
|
|
;
|
|
; Get current TSC
|
|
;
|
|
|
|
rdtsc
|
|
|
|
mov ebx, eax
|
|
mov edi, edx
|
|
|
|
;
|
|
; Determine ending TSC
|
|
;
|
|
|
|
mov ecx, MicroSeconds ; (ecx) = Microseconds
|
|
mov eax, PCR[PcStallScaleFactor] ; get per microsecond
|
|
mul ecx
|
|
|
|
add ebx, eax
|
|
adc edi, edx
|
|
|
|
;
|
|
; Wait for ending TSC
|
|
;
|
|
|
|
kese10: rdtsc
|
|
cmp edi, edx
|
|
ja short kese10
|
|
jc short kese20
|
|
cmp ebx, eax
|
|
ja short kese10
|
|
|
|
kese20: pop edi
|
|
pop ebx
|
|
stdRET _KeStallExecutionProcessor
|
|
|
|
stdENDP _KeStallExecutionProcessor
|
|
|
|
|
|
cPublicProc _HalpRemoveFences
|
|
mov word ptr fence1, 0c98bh
|
|
stdRET _HalpRemoveFences
|
|
stdENDP _HalpRemoveFences
|
|
|
|
|
|
|
|
page ,132
|
|
subttl "System Clock Interrupt"
|
|
;++
|
|
;
|
|
; Routine Description:
|
|
;
|
|
;
|
|
; This routine is entered as the result of an interrupt generated by CLOCK2.
|
|
; Its function is to dismiss the interrupt, raise system Irql to
|
|
; CLOCK2_LEVEL, update performance counter and transfer control to the
|
|
; standard system routine to update the system time and the execution
|
|
; time of the current thread
|
|
; and process.
|
|
;
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
; Interrupt is disabled
|
|
;
|
|
; Return Value:
|
|
;
|
|
; Does not return, jumps directly to KeUpdateSystemTime, which returns
|
|
;
|
|
; Sets Irql = CLOCK2_LEVEL and dismisses the interrupt
|
|
;
|
|
;--
|
|
|
|
APIC_ICR_CLOCK equ (DELIVER_FIXED OR ICR_ALL_EXCL_SELF OR APIC_CLOCK_VECTOR)
|
|
|
|
ENTER_DR_ASSIST Hci_a, Hci_t
|
|
|
|
cPublicProc _HalpClockInterrupt ,0
|
|
|
|
;
|
|
; Save machine state in trap frame
|
|
;
|
|
|
|
ENTER_INTERRUPT Hci_a, Hci_t
|
|
|
|
;
|
|
; (esp) - base of trap frame
|
|
;
|
|
; dismiss interrupt and raise Irql
|
|
;
|
|
|
|
push APIC_CLOCK_VECTOR
|
|
sub esp, 4 ; allocate space to save OldIrql
|
|
stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL,APIC_CLOCK_VECTOR,esp>
|
|
|
|
;
|
|
; This is the RTC interrupt, so we have to clear the
|
|
; interrupt flag on the RTC.
|
|
;
|
|
stdCall _HalpAcquireCmosSpinLock
|
|
|
|
;
|
|
; clear interrupt flag on RTC by banging on the CMOS. On some systems this
|
|
; doesn't work the first time we do it, so we do it twice. It is rumored that
|
|
; some machines require more than this, but that hasn't been observed with NT.
|
|
;
|
|
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
|
|
mov eax, _HalpCurrentClockRateIn100ns
|
|
xor ebx, ebx
|
|
|
|
;
|
|
; Adjust the tick count as needed
|
|
;
|
|
|
|
mov ecx, _HalpCurrentClockRateAdjustment
|
|
add byte ptr _HalpRateAdjustment, cl
|
|
adc eax, ebx
|
|
|
|
;
|
|
; (esp) = OldIrql
|
|
; (esp+4) = Vector
|
|
; (esp+8) = base of trap frame
|
|
; eax = time increment of this tick
|
|
; ebx = 0
|
|
;
|
|
|
|
;
|
|
; With an APIC Based System we will force a clock interrupt to all other
|
|
; processors. This is not really an IPI in the NT sense of the word, it
|
|
; uses the Local Apic to generate interrupts to other CPU's.
|
|
;
|
|
|
|
ifdef NT_UP
|
|
|
|
; UP implemention, we don't care about IPI's here
|
|
|
|
else ; ! NT_UP
|
|
|
|
;
|
|
; See if we need to IPI anyone, this happens only at the
|
|
; Lowest supported frequency (ie the value KeSetTimeIncrement
|
|
; is called with. We have a IPI Rate based upon the current
|
|
; clock rate relative to the lowest clock rate.
|
|
;
|
|
|
|
mov ecx, _HalpIpiRateCounter
|
|
add ecx, _HalpCurrentIpiRate
|
|
cmp ch, bl
|
|
mov byte ptr _HalpIpiRateCounter, cl
|
|
jz short HalpDontSendClockIpi ; No, Skip it
|
|
|
|
;
|
|
; Don't send vectors onto the APIC bus until at least one other
|
|
; processor comes on line. Vectors placed on the bus will hang
|
|
; around until someone picks them up.
|
|
;
|
|
|
|
cmp _HalpIpiClock, ebx
|
|
je short HalpDontSendClockIpi
|
|
|
|
;
|
|
; At least one other processor is alive, send clock pulse to all
|
|
; other processors
|
|
;
|
|
|
|
; We use a destination shorthand and therefore only needs to
|
|
; write the lower 32 bits of the ICR.
|
|
|
|
|
|
pushfd
|
|
cli
|
|
|
|
;
|
|
; Now issue the Clock IPI Command by writing to the Memory Mapped Register
|
|
;
|
|
|
|
STALL_WHILE_APIC_BUSY
|
|
mov dword ptr APIC[LU_INT_CMD_LOW], APIC_ICR_CLOCK
|
|
|
|
popfd
|
|
|
|
HalpDontSendClockIpi:
|
|
|
|
endif ; NT_UP
|
|
;
|
|
; Check for any more work
|
|
;
|
|
cmp _HalpClockWork, ebx ; Any clock interrupt work desired?
|
|
jz _KeUpdateSystemTime@0 ; No, process tick
|
|
|
|
cmp _HalpClockMcaQueueDpc, bl
|
|
je short CheckTimerRate
|
|
|
|
mov _HalpClockMcaQueueDpc, bl
|
|
|
|
;
|
|
; Queue MCA Dpc
|
|
;
|
|
|
|
push eax
|
|
stdCall _HalpMcaQueueDpc ; Queue MCA Dpc
|
|
pop eax
|
|
|
|
CheckTimerRate:
|
|
;
|
|
; (esp) = OldIrql
|
|
; (esp+4) = Vector
|
|
; (esp+8) = base of trap frame
|
|
; ebp = trap frame
|
|
; eax = time increment of this tick
|
|
; ebx = 0
|
|
;
|
|
cmp _HalpClockSetMSRate, bl ; New clock rate desired?
|
|
jz _KeUpdateSystemTime@0 ; No, process tick
|
|
|
|
|
|
;
|
|
; Time of clock frequency is being changed. See if we have changed rates
|
|
; since the last tick
|
|
;
|
|
cmp _HalpPendingRate, ebx ; Was a new rate set durning last
|
|
jnz short SetUpForNextTick ; tick? Yes, go update globals
|
|
|
|
ProgramTimerRate:
|
|
|
|
; (eax) = time increment for current tick
|
|
|
|
;
|
|
; 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 and will occur at the same
|
|
; rate as this tick)
|
|
;
|
|
push eax
|
|
|
|
stdCall _HalpAcquireCmosSpinLock
|
|
|
|
mov eax, _HalpNextRate
|
|
mov _HalpPendingRate, eax ; pending rate
|
|
|
|
dec eax
|
|
mov ecx, TimeStrucSize
|
|
xor edx, edx
|
|
mul ecx
|
|
|
|
mov eax, _HalpRtcTimeIncrements[eax].RTCRegisterA
|
|
mov _HalpCurrentRTCRegisterA, eax
|
|
|
|
shl ax, 8 ; (ah) = (al)
|
|
mov al, 0AH ; Register A
|
|
CMOS_WRITE ; Set it
|
|
|
|
stdCall _HalpReleaseCmosSpinLock
|
|
|
|
pop eax
|
|
jmp _KeUpdateSystemTime@0 ; dispatch this tick
|
|
|
|
SetUpForNextTick:
|
|
|
|
;
|
|
; 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.
|
|
;
|
|
; We will get here if there is a request for a rate change. There could
|
|
; been two requests. That is why we are conmparing the Pending with the
|
|
; NextRate.
|
|
;
|
|
; (eax) = time increment for current tick
|
|
;
|
|
push eax
|
|
|
|
mov eax, _HalpPendingRate
|
|
|
|
dec eax
|
|
mov ecx, TimeStrucSize
|
|
xor edx, edx
|
|
mul ecx
|
|
|
|
mov ebx, _HalpRtcTimeIncrements[eax].RateIn100ns
|
|
mov ecx, _HalpRtcTimeIncrements[eax].RateAdjustmentCnt
|
|
mov edx, _HalpRtcTimeIncrements[eax].IpiRate
|
|
mov _HalpCurrentClockRateIn100ns, ebx
|
|
mov _HalpCurrentClockRateAdjustment, ecx
|
|
mov _HalpCurrentIpiRate, edx
|
|
|
|
mov ebx, _HalpPendingRate
|
|
mov _HalpPendingRate, 0 ; no longer pending, clear it
|
|
|
|
pop eax
|
|
|
|
cmp ebx, _HalpNextRate ; new rate == NextRate?
|
|
jne ProgramTimerRate ; no, go set new pending rate
|
|
|
|
mov _HalpClockSetMSRate, 0 ; all done setting new rate
|
|
jmp _KeUpdateSystemTime@0 ; dispatch this tick
|
|
|
|
|
|
stdENDP _HalpClockInterrupt
|
|
|
|
page ,132
|
|
subttl "System Clock Interrupt - Non BSP"
|
|
;++
|
|
;
|
|
; Routine Description:
|
|
;
|
|
;
|
|
; This routine is entered as the result of an interrupt generated by
|
|
; CLOCK2. Its function is to dismiss the interrupt, raise system Irql
|
|
; to CLOCK2_LEVEL, transfer control to the standard system routine to
|
|
; the execution time of the current thread and process.
|
|
;
|
|
; This routine is executed on all processors other than P0
|
|
;
|
|
;
|
|
; 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 HPn_a, HPn_t
|
|
|
|
cPublicProc _HalpClockInterruptPn ,0
|
|
|
|
;
|
|
; Save machine state in trap frame
|
|
;
|
|
|
|
ENTER_INTERRUPT HPn_a, HPn_t
|
|
|
|
;
|
|
; (esp) - base of trap frame
|
|
;
|
|
; dismiss interrupt and raise Irql
|
|
;
|
|
|
|
push APIC_CLOCK_VECTOR
|
|
sub esp, 4 ; allocate space to save OldIrql
|
|
stdCall _HalBeginSystemInterrupt, <CLOCK2_LEVEL,APIC_CLOCK_VECTOR,esp>
|
|
|
|
;
|
|
; All processors will update RunTime for current thread
|
|
;
|
|
|
|
sti
|
|
; TOS const PreviousIrql
|
|
stdCall _KeUpdateRunTime,<dword ptr [esp]>
|
|
|
|
INTERRUPT_EXIT ; lower irql to old value, iret
|
|
|
|
;
|
|
; We don't return here
|
|
;
|
|
|
|
stdENDP _HalpClockInterruptPn
|
|
|
|
|
|
page ,132
|
|
subttl "System Clock Interrupt - Stub"
|
|
;++
|
|
;
|
|
; Routine Description:
|
|
;
|
|
;
|
|
; This routine is entered as the result of an interrupt generated by
|
|
; CLOCK2. Its function is to interrupt and return.
|
|
;
|
|
; This routine is executed on P0 During Phase 0
|
|
;
|
|
;
|
|
; Arguments:
|
|
;
|
|
; None
|
|
; Interrupt is disabled
|
|
;
|
|
; Return Value:
|
|
;
|
|
;--
|
|
|
|
APIC_ICR_CLOCK equ (DELIVER_FIXED OR ICR_ALL_EXCL_SELF OR APIC_CLOCK_VECTOR)
|
|
|
|
ENTER_DR_ASSIST HStub_a, HStub_t
|
|
|
|
cPublicProc _HalpClockInterruptStub ,0
|
|
|
|
;
|
|
; Save machine state in trap frame
|
|
;
|
|
|
|
ENTER_INTERRUPT HStub_a, HStub_t
|
|
|
|
;
|
|
; (esp) - base of trap frame
|
|
;
|
|
|
|
|
|
; clear interrupt flag on RTC by banging on the CMOS. On some systems this
|
|
; doesn't work the first time we do it, so we do it twice. It is rumored that
|
|
; some machines require more than this, but that hasn't been observed with NT.
|
|
;
|
|
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
|
|
Hpi10: test al, 80h
|
|
jz short Hpi15
|
|
mov al,0CH ; Register C
|
|
CMOS_READ ; Read to initialize
|
|
jmp short Hpi10
|
|
Hpi15:
|
|
|
|
mov dword ptr APIC[LU_EOI], 0 ; send EOI to APIC local unit
|
|
|
|
;
|
|
; Do interrupt exit processing without EOI
|
|
;
|
|
|
|
SPURIOUS_INTERRUPT_EXIT
|
|
|
|
;
|
|
; We don't return here
|
|
;
|
|
|
|
stdENDP _HalpClockInterruptStub
|
|
|
|
_TEXT ends
|
|
|
|
end
|