Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

776 lines
22 KiB

title "Irql Processing"
;++
;
; Copyright (c) 1989 Microsoft Corporation
; Copyright (c) 1993 Sequent Computer Systems, Inc.
;
; Module Name:
;
; w3irql.asm
;
; Abstract:
;
; WinServer 3000 IRQL implementation.
;
; This module implements the code necessary to raise and lower i386
; Irql and dispatch software interrupts with the WinServer APIC/PIC system.
;
; Author:
;
; Phil Hochstetler ([email protected]) 3-30-93
;
; Environment:
;
; Kernel mode only.
;
; Revision History:
;
.386p
.xlist
include ks386.inc
include callconv.inc ; calling convention macros
include i386\apic.inc
include i386\kimacro.inc
include i386\w3.inc
.list
EXTRNP _KeBugCheck,1
EXTRNP _Halptpicinit,0
EXTRNP _HalpDispatchInterrupt,0
EXTRNP _HalpApcInterrupt,0
extrn _HalpIrql2TPR:byte
extrn _HalpK2Rdir2Irq:byte
extrn _HalpELCRImage:word
extrn _HalpW3BaseIOunitRedirectionTable:dword
extrn _HalpK2EbsIOunitRedirectionTable:dword
extrn _HalpK2EISAIrq2Irql:byte
extrn _HalpK2Irql2Eisa:byte
extrn _HalpActiveProcessors:DWORD
extrn _HalpIrql2IRRMask:dword
extrn ApicSpuriousService@0:near
_DATA SEGMENT DWORD PUBLIC 'DATA'
;
; Virtual addresses of the APIC Local and IO units.
; The virtual mapping is done in HalInitializeProcessor.
;
align dword
public _HalpLocalUnitBase, _HalpIOunitBase, _HalpIOunitTwoBase
_HalpLocalUnitBase dd 0
_HalpIOunitBase dd 0
_HalpIOunitTwoBase dd 0
SyncIdCommand equ DELIVER_INIT + \
LOGICAL_DESTINATION + \
LEVEL_TRIGGERED
ResetAllExclSelf equ DELIVER_INIT + \
LEVEL_TRIGGERED + \
ICR_LEVEL_ASSERTED + \
ICR_ALL_EXCL_SELF
UnResetLogical equ DELIVER_INIT + \
LOGICAL_DESTINATION + \
LEVEL_TRIGGERED
;
; This table is used to mask all pending interrupts below a given Irql
; out of the IRR
;
align 4
public FindHigherIrqlMask
FindHigherIrqlMask label dword
dd 11111111111111111111111111111110B ; irql 0
dd 11111111111111111111111111111100B ; irql 1
dd 11111111111111111111111111111000B ; irql 2
dd 11111111111111111111111111110000B ; irql 3
dd 11111111111111111111111111100000B ; irql 4
dd 11111111111111111111111111000000B ; irql 5
dd 11111111111111111111111110000000B ; irql 6
dd 11111111111111111111111100000000B ; irql 7
dd 11111111111111111111111000000000B ; irql 8
dd 11111111111111111111110000000000B ; irql 9
dd 11111111111111111111100000000000B ; irql 10
dd 11111111111111111111000000000000B ; irql 11
dd 11111111111111111110000000000000B ; irql 12
dd 11111111111111111100000000000000B ; irql 13
dd 11111111111111111000000000000000B ; irql 14
dd 11111111111111110000000000000000B ; irql 15
dd 11111111111111100000000000000000B ; irql 16
dd 11111111111111000000000000000000B ; irql 17
dd 11111111111110000000000000000000B ; irql 18
dd 11111111111100000000000000000000B ; irql 19
dd 11111111111000000000000000000000B ; irql 20
dd 11111111110000000000000000000000B ; irql 21
dd 11111111100000000000000000000000B ; irql 22
dd 11111111000000000000000000000000B ; irql 23
dd 11111110000000000000000000000000B ; irql 24
dd 11111100000000000000000000000000B ; irql 25
dd 11111000000000000000000000000000B ; irql 26
dd 11110000000000000000000000000000B ; irql 27
dd 11100000000000000000000000000000B ; irql 28
dd 11000000000000000000000000000000B ; irql 29
dd 10000000000000000000000000000000B ; irql 30
dd 00000000000000000000000000000000B ; irql 31
_DATA ENDS
page ,132
subttl "RaiseIrql"
_TEXT$01 SEGMENT PARA PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:FLAT, FS:NOTHING, GS:NOTHING
;++
;
; KIRQL
; KfRaiseIrql (
; IN KIRQL NewIrql,
; )
;
; Routine Description:
;
; This routine is used to raise IRQL to the specified value.
;
; *** IMPORTANT IMPLEMENTATION NOTE ***
; Be sure to not write the OldIrql return value until after the
; IRQL has taken effect (due to poor coding practices).
;
; Arguments:
;
; (cl) = NewIrql - the new irql to be raised to
;
; Return Value:
;
; OldIrql - old irql
;
;--
cPublicFastCall KfRaiseIrql ,1
cPublicFpo 0,0
mov al, PCR[PcHal.ProcIrql] ; (al) = old irql
if DBG
cmp al, cl ; old > new?
ja short KriErr1
endif
cmp cl, DISPATCH_LEVEL ; a software level?
ja short @f ; if yes, set the hardware
mov PCR[PcHal.ProcIrql], cl ; Save new irql
fstRET KfRaiseIrql
@@:
mov edx, _HalpLocalUnitBase ; Get address of Local APIC
and ecx, 0ffh ; clear upper 3 bytes
pushfd ; enter critical region
cli ;
mov PCR[PcHal.ProcIrql], cl ; Save new irql
mov cl, _HalpIrql2TPR[ecx] ; convert irql to TPR
mov [edx+LU_TPR], ecx ; write new irql
mov ecx, [edx+LU_TPR] ; Flush CPU write buffer
popfd ; leave critical region
fstRET KfRaiseIrql
if DBG
cPublicFpo 0, 2
KriErr1:
movzx eax, al
movzx ecx, cl
push ecx ; put new irql where we can find it
push eax ; put old irql where we can find it
mov byte ptr PCR[PcHal.ProcIrql], 0 ; avoid recursive error
stdCall _KeBugCheck, <IRQL_NOT_GREATER_OR_EQUAL>
endif
fstENDP KfRaiseIrql
page ,132
subttl "LowerIrql"
;++
;
; VOID
; KfLowerIrql (
; IN KIRQL NewIrql
; )
;
; Routine Description:
;
; This routine is used to lower IRQL to the specified value.
;
; Arguments:
;
; (cl) = NewIrql - the new irql to be set.
;
; Return Value:
;
; None.
;
;--
cPublicFastCall KfLowerIrql ,1
cPublicFpo 0,0
movzx ecx, cl ; get new irql value
if DBG
cmp cl, PCR[PcHal.ProcIrql] ; new > old ?
ja KliErr ; panic if so
endif
cmp byte ptr PCR[PcHal.ProcIrql], DISPATCH_LEVEL
ja short KliHW ; if old irql <= 2, software only
mov PCR[PcHal.ProcIrql], cl ; Save new irql
;
; Check for any pending DISPATCH interrupts
;
KliChkSW:
mov edx, PCR[PcIRR] ; get current IRR
and edx, FindHigherIrqlMask[ecx*4]
jnz KliSW
KliEnd:
fstRET KfLowerIrql ; return
KliSW:
pushfd
cli
test dword ptr PCR[PcIRR], (1 SHL DISPATCH_LEVEL)
jz short @f
stdCall _HalpDispatchInterrupt
popfd
xor ecx, ecx
mov cl, PCR[PcHal.ProcIrql] ; restore current irql value
jmp short KliChkSW
@@:
cmp cl, APC_LEVEL
jae short KliPop
test dword ptr PCR[PcIRR], (1 SHL APC_LEVEL)
jz short KliPop
stdCall _HalpApcInterrupt
popfd
xor ecx, ecx
mov cl, PCR[PcHal.ProcIrql] ; restore current irql value
jmp short KliChkSW
KliPop:
popfd
jmp short KliEnd
;
; Lower APIC Task Priority Register to reflect change in IRQL
; and then check for software interrupts (if below DISPATCH)
;
KliHW:
mov edx, _HalpLocalUnitBase ; get address of Local APIC
pushfd
cli
mov PCR[PcHal.ProcIrql], cl ; Save new irql
mov cl, _HalpIrql2TPR[ecx] ; convert irql to TPR
mov [edx+LU_TPR], ecx ; write new TPR value
mov ecx, [edx+LU_TPR] ; Flush CPU write buffer
popfd
mov cl, byte ptr PCR[PcHal.ProcIrql] ; restore current irql value
cmp cl, DISPATCH_LEVEL
jb KliChkSW
fstRET KfLowerIrql ; return
if DBG
cPublicFpo 1, 2
KliErr:
push ecx ; new irql for debugging
mov cl, PCR[PcHal.ProcIrql] ; get old irql
push ecx ; old irql for debugging
mov byte ptr PCR[PcHal.ProcIrql], HIGH_LEVEL ; avoid recursive error
stdCall _KeBugCheck, <IRQL_NOT_LESS_OR_EQUAL>
endif
fstENDP KfLowerIrql
;++
;
; VOID
; KIRQL
; KeRaiseIrqlToDpcLevel (
; )
;
; Routine Description:
;
; This routine is used to raise IRQL to DPC level.
; The APIC TPR is used to block all lower-priority HW interrupts.
;
; Arguments:
;
; Return Value:
;
; OldIrql - the addr of a variable which old irql should be stored
;
;--
cPublicProc _KeRaiseIrqlToDpcLevel,0
cPublicFpo 0, 0
mov ecx, DISPATCH_LEVEL
jmp @KfRaiseIrql
stdENDP _KeRaiseIrqlToDpcLevel
;++
;
; VOID
; KIRQL
; KeRaiseIrqlToSynchLevel (
; )
;
; Routine Description:
;
; This routine is used to raise IRQL to SYNC level.
; The APIC TPR is used to block all lower-priority HW interrupts.
;
; Arguments:
;
; Return Value:
;
; OldIrql - the addr of a variable which old irql should be stored
;
;--
cPublicProc _KeRaiseIrqlToSynchLevel,0
mov ecx, SYNCH_LEVEL
jmp @KfRaiseIrql
stdENDP _KeRaiseIrqlToSynchLevel
;++
;
; KIRQL
; KeGetCurrentIrql (VOID)
;
; Routine Description:
;
; This routine returns to current IRQL.
;
; Arguments:
;
; None.
;
; Return Value:
;
; The current IRQL.
;
;--
cPublicProc _KeGetCurrentIrql ,0
xor eax, eax
mov al, PCR[PcHal.ProcIrql] ; return 32 bits to cover mistakes
stdRET _KeGetCurrentIrql
stdENDP _KeGetCurrentIrql
;++
;
; VOID
; _HalpDisableAllInterrupts (VOID)
;
; Routine Description:
;
; This routine is called during a system crash. The hal needs all
; interrupts disabled.
;
; Arguments:
;
; None.
;
; Return Value:
;
; None - all interrupts are masked off
;
;--
cPublicProc _HalpDisableAllInterrupts,0
;
; Raising to HIGH_LEVEL disables all interrupts
;
mov ax, 0FFFFh
SET_8259_MASK
mov ecx, HIGH_LEVEL
fstCall KfRaiseIrql
stdRET _HalpDisableAllInterrupts
stdENDP _HalpDisableAllInterrupts
_TEXT$01 ends
page ,132
subttl "Interrupt Controller Initialization"
_TEXT SEGMENT DWORD PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:FLAT, FS:NOTHING, GS:NOTHING
;++
;
; VOID
; _HalpInitializePICs (
; )
;
; Routine Description:
;
; Call the _Halptpicinit C routine to initialize 8259 and APIC
;
;Arguments:
;
; None
;
; Return Value:
;
; None.
;
;--
cPublicProc _HalpInitializePICs ,0
DISABLE_INTERRUPTS_AT_CPU
stdCall _HalpInitializeEbsIOunit ; Initialize EBS I/O APIC
stdCall _HalpInitializeBaseIOunit ; Initialize P0 I/O APIC
stdCall _Halptpicinit ; Initialize PICs
RESTORE_INTERRUPTS_AT_CPU
stdRET _HalpInitializePICs
stdENDP _HalpInitializePICs
page ,132
subttl "APIC EBS IO Unit Initialization"
;++
;
; VOID
; HalpInitializeEbsIOUnit (
; )
;
; Routine Description:
;
; This routine initializes the interrupt structures for the IO unit of
; the 82489DX APIC. It masks all interrupt inputs in the redirection
; table - these will be unmasked when the interrupts are enabled by
; HalpEnableSystemInterrupt.
;
; HalpInitializeIOunit is called by HalpInitializePics during Phase 0
; initialization. It is executed by P0 only, and executes AFTER the
; local unit is initialized. This procedure assumes that the APIC virtual
; address space has been mapped.
;
; The I/O unit for external WinServer 3000 interrupts resides on the
; EBS module.
;
; Arguments:
;
; None
;
; Return Value:
;
; None.
;
;--
cPublicProc _HalpInitializeEbsIOUnit ,0
push esi ; save regs
push ebx ;
;
; When using the Intel 82350 ISP Chipset the interrupt edge/level definitions
; are readable from the ISP's edge/level control register (ELCR).
;
; Read the ELCR so the input polarity control XOR gates on the APIC interrupt
; inputs can be programmed. This whole thing works because EISA defines edge
; -triggered interrupts as active-high and level interrupts as active-low.
;
; We will use the ELCR later to set the ACTIVE_LOW bit as required in the
; IOunit redirection table.
;
mov dx, PIC2_ELCR_PORT ; read the edge/level control reg
in al, dx
shl ax, 8
mov dx, PIC1_ELCR_PORT
in al, dx
and ax, ELCR_MASK ; clear reserved IRQs
mov _HalpELCRImage, ax ; save the image for later
;
; load addresses of the register-select register and register-window register
;
mov ecx, _HalpIOunitBase
lea edx, [ecx+IO_REGISTER_SELECT]
lea ecx, [ecx+IO_REGISTER_WINDOW]
;
; write the I/O unit APIC-ID - Since we are using the Processor
; Numbers for the local unit ID's we need to set the IO unit
; to a high (out of Processor Number range) value.
;
mov dword ptr [edx], IO_ID_REGISTER
mov dword ptr [ecx], (IOUNIT_APIC_ID SHL APIC_ID_SHIFT)
;
; program the redirection table
;
mov ebx, IO_REDIR_00_LOW ; [EBX] has register select
lea esi, _HalpK2EbsIOunitRedirectionTable ; [ESI] has address of image
RedirLoop1:
lodsd ; load low dword
or eax, eax ; end of table?
jz RedirLoopExit1 ; yup - we're done
push ebx
push ecx
push eax
sub ebx, IO_REDIR_00_LOW
shr ebx, 1 ; Form RDIR #
xor ecx,ecx
mov cl, _HalpK2Rdir2Irq[ebx] ; Form IRQ #
xor eax, eax ; clear reg.
mov ax, _HalpELCRImage
bt eax, ecx
pop eax
pop ecx
; Note: 0 will result for all K2 IRQs, this is what we want....
jnc @f ; bit is 0 => active high (default)
; eax is val
; ebx in RDIR entry #
or eax, LEVEL_TRIGGERED ; This is a level triggered interrupt
;
; We must tell the hardware to invert the polarity for level triggered
; interrupts because APIC is "active HIGH", EISA is active low for level
; triggered interrupts...so tickle the K2 EISA to APIC Polarity Register..
;
push eax
push edx
mov dx, EISA_2_MPIC_POLARITY_REG
in al, dx
bts eax, ebx ;
out dx, al
pop edx
pop eax
@@:
pop ebx
mov dword ptr [edx], ebx ; write to select register
mov dword ptr [ecx], eax ; write redirection table entry
inc ebx ; increment to next entry
lodsd ; load high dword
mov dword ptr [edx], ebx ; write to select register
mov dword ptr [ecx], eax ; write redirection table entry
inc ebx ; increment to next entry
jmp RedirLoop1 ; continue...
RedirLoopExit1:
pop ebx ; restore registers and return
pop esi ;
stdRET _HalpInitializeEbsIOUnit
stdENDP _HalpInitializeEbsIOUnit
page ,132
subttl "APIC Base IO Unit Initialization"
;++
;
; VOID
; HalpInitializeBaseIOUnit (
; )
;
; Routine Description:
;
; This routine initializes the interrupt structures for the IO unit of
; the 82489DX APIC. It masks all interrupt inputs in the redirection
; table - these will be unmasked when the interrupts are enabled by
; HalpEnableSystemInterrupt.
;
; HalpInitializeIOunit is called by HalpInitializePics during Phase 0
; initialization. It is executed by CPU0 only, and executes AFTER the
; local unit is initialized. This procedure assumes that the APIC virtual
; address space has been mapped.
;
; The I/O unit for external K2 interrupts resides on the EBS module.
;
; Arguments:
;
; None
;
; Return Value:
;
; None.
;
;--
cPublicProc _HalpInitializeBaseIOUnit ,0
push esi ; save regs
push ebx ;
;
; load addresses of the register-select register and register-window register
;
mov ecx, _HalpIOunitTwoBase
lea edx, [ecx+IO_REGISTER_SELECT]
lea ecx, [ecx+IO_REGISTER_WINDOW]
;
; write the I/O unit APIC-ID - Since we are using the Processor
; Numbers for the local unit ID's we need to set the 2nd IO unit
; to a high (out of Processor Number range) value (but != other IO unit).
;
mov dword ptr [edx], IO_ID_REGISTER
mov dword ptr [ecx], (IOUNIT2_APIC_ID SHL APIC_ID_SHIFT)
;
; re-program the redirection table
;
mov ebx, IO_REDIR_00_LOW ; [EBX] has register select
lea esi, _HalpW3BaseIOunitRedirectionTable ; [ESI] has address of image
RedirLoop:
lodsd ; load low dword
or eax, eax ; end of table?
jz RedirLoopExit ; yup - we're done
mov dword ptr [edx], ebx ; write to select register
mov dword ptr [ecx], eax ; write redirection table entry
inc ebx ; increment to next entry
lodsd ; load high dword
mov dword ptr [edx], ebx ; write to select register
mov dword ptr [ecx], eax ; write redirection table entry
inc ebx ; increment to next entry
jmp RedirLoop ; continue...
RedirLoopExit:
pop ebx ; restore registers and return
pop esi ;
stdRET _HalpInitializeBaseIOUnit
stdENDP _HalpInitializeBaseIOUnit
cPublicProc _HalpUnResetLocalUnit ,1
movzx ecx, byte ptr [esp+4] ; get CPU logical number
xor eax, eax
bts eax, ecx ; convert to bit mask of 1 bit
mov ecx, _HalpLocalUnitBase ; pointer to local unit
DISABLE_INTERRUPTS_AT_CPU
@@: test dword ptr [ecx+LU_INT_CMD_LOW], DELIVERY_PENDING
jnz short @b
mov dword ptr [ecx+LU_INT_CMD_HIGH], eax ; destination bit mask
mov dword ptr [ecx+LU_INT_CMD_LOW], UnResetLogical
RESTORE_INTERRUPTS_AT_CPU
stdRET _HalpUnResetLocalUnit
stdENDP _HalpUnResetLocalUnit
cPublicProc _HalpResetLocalUnits ,0
mov ecx, _HalpLocalUnitBase ; pointer to local unit
DISABLE_INTERRUPTS_AT_CPU
@@: test dword ptr [ecx+LU_INT_CMD_LOW], DELIVERY_PENDING
jnz short @b
mov dword ptr [ecx+LU_INT_CMD_LOW], ResetAllExclSelf
RESTORE_INTERRUPTS_AT_CPU
stdRET _HalpResetLocalUnits
stdENDP _HalpResetLocalUnits
page ,132
subttl "APIC Local Unit Initialization"
;++
;
; VOID
; HalpInitializeLocalUnit (
; )
;
; Routine Description:
;
; This routine initializes the interrupt structures for the local unit
; of the 82489DX APIC. This procedure is called by HalInitializeProcessor
; as it is executed by each CPU.
;
; Arguments:
;
; None
;
; Return Value:
;
; None.
;
;--
APIC_ENABLE equ (APIC_SPURIOUS_VECTOR OR LU_UNIT_ENABLED)
cPublicProc _HalpInitializeLocalUnit ,0
cPublicFpo 0, 1
pushfd
cli
mov edx, _HalpLocalUnitBase ; base address of local unit
mov dword ptr [edx+LU_TPR], 0FFh ; Disable all interrupts
movzx eax, byte ptr PCR[PcHal.PcrNumber] ; use CPU number for APIC-id
mov dword ptr [edx+LU_DEST_FORMAT], LU_DEST_FORMAT_FLAT
xor ecx, ecx ; zero bitmask
bts ecx, eax ; create logical dest bitmask
mov [edx+LU_LOGICAL_DEST], ecx ; and set it
shl eax, APIC_ID_SHIFT ; ID_REGISTER has ID in MSB
mov dword ptr [edx+LU_ID_REGISTER], eax ; set local unit ID
;
; APIC does not seem to see a hardware reset across a reboot thus an interrupt
; could have been taken but the EOI is never written. The BIOS does not
; recover from this condition so we must do it here. Many days with a logic
; analyzer finally found this one. If there are any ISR bits set, clear
; one by a write to the EOI register and look again.
;
@@:
mov eax, [edx+LU_ISR_0+000h] ; read ISR 0
or eax, [edx+LU_ISR_0+010h] ; read ISR 1
or eax, [edx+LU_ISR_0+020h] ; read ISR 2
or eax, [edx+LU_ISR_0+030h] ; read ISR 3
or eax, [edx+LU_ISR_0+040h] ; read ISR 4
or eax, [edx+LU_ISR_0+050h] ; read ISR 5
or eax, [edx+LU_ISR_0+060h] ; read ISR 6
or eax, [edx+LU_ISR_0+070h] ; read ISR 7
jz short @f
mov dword ptr [edx+LU_EOI], eax ; clear highest ISR bit
jmp short @b
@@:
mov dword ptr [edx+LU_SPURIOUS_VECTOR], APIC_ENABLE
;
; Sync all APIC IDs by using Data Sheet recommended procedure
;
xor eax, eax
mov dword ptr [edx+LU_INT_CMD_HIGH], eax
mov dword ptr [edx+LU_INT_CMD_LOW], SyncIdCommand
;
; we're done - set TPR back to zero and return
;
mov PCR[PcHal.ProcIrql], al ; Set CurrentIrql=0
mov [edx+LU_TPR], eax
;
; Program in the spurious interrupt vector into the IDT
;
IDTEntry APIC_SPURIOUS_VECTOR, ApicSpuriousService@0
popfd
stdRET _HalpInitializeLocalUnit
stdENDP _HalpInitializeLocalUnit
_TEXT ends
end