xbox-kernel/private/ntos/ke/i386/newsysbg.asm
2020-09-30 17:17:25 +02:00

881 lines
26 KiB
NASM

title "System Startup"
;++
;
; Copyright (c) 1989-2001 Microsoft Corporation
;
; Module Name:
;
; newsysbg.asm
;
; Abstract:
;
; This module implements the code necessary to initially startup the
; NT system.
;
; Environment:
;
; Kernel mode only.
;
;--
.386p
.xlist
include ks386.inc
include i386\kimacro.inc
include callconv.inc
FPOFRAME macro a, b
.FPO ( a, b, 0, 0, 0, 0 )
endm
.list
option segment:flat
EXTRNP _KdInitSystem,1
EXTRNP KfRaiseIrql,1,,FASTCALL
EXTRNP KfLowerIrql,1,,FASTCALL
EXTRNP _KiPreInitializeKernel
EXTRNP _KiInitializeKernel
extrn SwapContext:PROC
EXTRNP _KiSwapGDT
EXTRNP _KiSwapIDT
extrn _KiTrap08:PROC
extrn _KiTrap02:PROC
EXTRNP _KeGetCurrentIrql,0
EXTRNP _KeBugCheckEx, 5
EXTRNP _HalMicrocodeUpdateLoader,0
EXTRNP HalClearSoftwareInterrupt,1,,FASTCALL
EXTRNP _MmPrepareToQuickRebootSystem,0
extrn _KeHasQuickBooted:BYTE
extrn _KiIDT:BYTE
extrn _KiIDTLEN:BYTE ; NOTE - really an ABS, linker problems
ifdef DEVKIT
extrn _KiDpcDispatchNotify:DWORD
endif
if DBG
extrn _KdDebuggerEnabled:BYTE
EXTRNP _DbgBreakPoint,0
extrn _DbgPrint:near
extrn _MsgDpcTrashedEsp:BYTE
extrn _MsgDpcFpuEnabled:BYTE
endif
;
; Constants for various variables
;
_DATA SEGMENT PARA PUBLIC 'DATA'
COMM _KiDoubleFaultStackLimit:byte:DOUBLE_FAULT_STACK_SIZE
COMM _KiIdleThreadStackLimit:byte:KERNEL_STACK_SIZE
align 16
public _KiIdleThread
_KiIdleThread label byte
db ExtendedThreadObjectLength dup(0) ; sizeof (ETHREAD)
align 16
dd 3 dup(0) ; padding to force PbNpxSaveArea to be 16 byte aligned
.errnz ((SIZEOF DWORD * 3 + PcPrcbData + PbNpxSaveArea) AND 15)
public _KiPCR
_KiPCR label dword
db ProcessorControlRegisterLength dup (0)
_DATA ends
STICKY SEGMENT PARA PUBLIC 'DATA'
;
; Allocate space for the task state segments.
;
INITIALIZE_TSS macro HandlerRoutine, HandlerStack
dw 0 ; TssBacklink
dw 0 ; TssReserved0
dd HandlerStack ; TssEsp0
dw KGDT_R0_DATA ; TssSs0
dw 0 ; TssReserved1
dd 0 ; TssEsp1
dw 0 ; TssSs1
dw 0 ; TssReserved2
dd 0 ; TssEsp2
dw 0 ; TssSs2
dw 0 ; TssReserved3
dd 0 ; TssCR3
dd HandlerRoutine ; TssEip
dd 0 ; TssEFlags
dd 0 ; TssEax
dd 0 ; TssEcx
dd 0 ; TssEdx
dd 0 ; TssEbx
dd HandlerStack ; TssEsp
dd 0 ; TssEbp
dd 0 ; TssEsi
dd 0 ; TssEdi
dw KGDT_R0_DATA ; TssEs
dw 0 ; TssReserved4
dw KGDT_R0_CODE ; TssCs
dw 0 ; TssReserved5
dw KGDT_R0_DATA ; TssSs
dw 0 ; TssReserved6
dw KGDT_R0_DATA ; TssDs
dw 0 ; TssReserved7
dw KGDT_R0_PCR ; TssFs
dw 0 ; TssReserved8
dw 0 ; TssGs
dw 0 ; TssReserved9
dw 0 ; TssLDT
dw 0 ; TssReserved10
dw 0 ; TssFlags
dw TssIoMaps ; TssIoMapBase
endm
align 8
public _KiNormalTSS
_KiNormalTSS label byte
INITIALIZE_TSS 0, 0
align 8
public _KiDoubleFaultTSS
_KiDoubleFaultTSS label byte
INITIALIZE_TSS <OFFSET _KiTrap08>, <OFFSET _KiDoubleFaultStackLimit + DOUBLE_FAULT_STACK_SIZE>
align 8
public _KiNMITSS
_KiNMITSS label byte
INITIALIZE_TSS <OFFSET _KiTrap02>, <OFFSET _KiDoubleFaultStackLimit + DOUBLE_FAULT_STACK_SIZE>
;
; Allocate space for the global descriptor table.
;
GDTEntry macro base,limit,flags
dw limit
dd base
dw flags
endm
align 8
public _KiGDT, _KiGDTLEN, _KiGDTEnd
_KiGDT label byte
GDTEntry 0, 0, 0 ; KGDT_NULL
GDTEntry 0, 0FFFFh, 0CB9Bh ; KGDT_R0_CODE
GDTEntry 0, 0FFFFh, 0CF93h ; KGDT_R0_DATA
GDTEntry <OFFSET _KiNormalTSS>, TssIoMaps, 89h ; KGDT_TSS
GDTEntry <OFFSET _KiPCR>, ProcessorControlRegisterLength, 0C093h ; KGDT_R0_PCR
GDTEntry <OFFSET _KiDoubleFaultTSS>, TssIoMaps, 89h ; KGDT_DF_TSS
GDTEntry <OFFSET _KiNMITSS>, TssIoMaps, 89h ; KGDT_NMI_TSS
_KiGDTLEN equ $ - _KiGDT
_KiGDTEnd equ $
STICKY ends
page ,132
subttl "System Startup"
INIT SEGMENT DWORD PUBLIC 'CODE'
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
;++
;
; For processor 0, Routine Description:
;
; This routine is called when the NT system begins execution.
; Its function is to initialize system hardware state, call the
; kernel initialization routine, and then fall into code that
; represents the idle thread for all processors.
;
; Entry state created by the boot loader:
; A short-form IDT (0-1f) exists and is active.
; A complete GDT is set up and loaded.
; A complete TSS is set up and loaded.
; Page map is set up with minimal start pages loaded
; The lower 4Mb of virtual memory are directly mapped into
; physical memory.
;
; The system code (ntoskrnl.exe) is mapped into virtual memory
; as described by its memory descriptor.
; DS=ES=SS = flat
; ESP->a usable boot stack
; Interrupts OFF
;
; For processor > 0, Routine Description:
;
; This routine is called when each additional processor begins execution.
; The entry state for the processor is:
; IDT, GDT, TSS, stack, selectors, PCR = all valid
; Page directory is set to the current running directory
;
; Arguments:
;
; None.
;
; Return Value:
;
; None.
;
;--
cPublicProc _KiSystemStartup,0
;
; Initialize ebp, esp, and argument registers for initializing the kernel.
; Reserve space for idle thread stack NPX_SAVE_AREA, enough padding bytes to
; store the initial context from KiInitializeContextThread and the FWORD for
; lgdt/lidt.
;
xor ebp, ebp ; (ebp) = 0. No more stack frame
mov esp, offset _KiIdleThreadStackLimit + KERNEL_STACK_SIZE - NPX_FRAME_LENGTH - 64
;
; Rearrange the bytes in KiGDT to match the processor's layout.
;
stdCall _KiSwapGDT
;
; Load the global descriptor table register.
;
mov WORD PTR [esp], _KiGDTLEN
mov DWORD PTR [esp+2], OFFSET _KiGDT
lgdt FWORD PTR [esp]
;
; Execute a 16:32 jump to reload the code selector.
;
db 0EAh
dd OFFSET kis10
dw KGDT_R0_CODE
;
; Reload the data selectors.
;
kis10: mov eax, KGDT_R0_DATA
mov ds, eax
mov es, eax
mov ss, eax
;
; Load the various selector registers.
;
push KGDT_R0_PCR
pop fs
mov eax, KGDT_TSS
ltr ax
;
; Initialize TssCr3 of the task state segments to the boot page directory.
;
mov eax, cr3
mov dword ptr [_KiNormalTSS]+TssCr3, eax
mov dword ptr [_KiDoubleFaultTSS]+TssCr3, eax
mov dword ptr [_KiNMITSS]+TssCr3, eax
;
; Update the microcode for the processor.
;
stdCall _HalMicrocodeUpdateLoader
;
; Since the entries of Kernel IDT have their Selector and Extended Offset
; fields set up in the wrong order, we need to swap them back to the order
; which i386 recognizes.
;
stdCall _KiSwapIDT
;
; Load the interrupt descriptor table register.
;
mov ecx, offset FLAT:_KiIDTLEN
mov WORD PTR [esp], cx
mov DWORD PTR [esp+2], OFFSET _KiIDT
lidt FWORD PTR [esp]
;
; Preinitialize the system data structures.
;
stdCall _KiPreInitializeKernel
;
; Enable the kernel debugger.
;
stdCall _KdInitSystem, <1>
if DEVL
;
; Give the debugger an opportunity to gain control.
;
POLL_DEBUGGER
endif ; DEVL
nop ; leave a spot for int-3 patch
;
; Set initial IRQL = HIGH_LEVEL for init
;
mov ecx, HIGH_LEVEL
fstCall KfRaiseIrql
;
; Initialize system data structures and HAL.
;
stdCall _KiInitializeKernel
;
; Set "shadow" priority value for Idle thread. This will keep the Mutex
; priority boost/drop code from dropping priority on the Idle thread, and
; thus avoids leaving a bit set in the ActiveMatrix for the Idle thread when
; there should never be any such bit.
;
mov byte ptr [_KiIdleThread]+ThPriority,LOW_REALTIME_PRIORITY
;
; Control is returned to the idle thread with IRQL at HIGH_LEVEL. Lower IRQL
; to DISPATCH_LEVEL and set wait IRQL of idle thread.
;
sti
mov ecx, DISPATCH_LEVEL
fstCall KfLowerIrql
mov byte ptr [_KiIdleThread]+ThWaitIrql, DISPATCH_LEVEL
;
; The following code represents the idle thread for a processor. The idle
; thread executes at IRQL DISPATCH_LEVEL and continually polls for work to
; do. Control may be given to this loop either as a result of a return from
; the system initialization routine or as the result of starting up another
; processor in a multiprocessor configuration.
;
jmp KiIdleLoop ; enter idle loop
stdENDP _KiSystemStartup
INIT ends
_TEXT SEGMENT DWORD PUBLIC 'CODE' ; Put IdleLoop in text section
ASSUME DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING
;++
;
; DECLSPEC_NORETURN
; VOID
; KeQuickRebootSystem(
; VOID
; )
;
; Routine Description:
;
; This function quick reboots the system. The .data section is refreshed
; from the ROM and system initialization is started again.
;
; Arguments:
;
; None.
;
; Return Value:
;
; None.
;
;--
cPublicProc _KeQuickRebootSystem,0
;
; Disable interrupts.
;
cli
;
; Initialize ebp, esp, and argument registers for initializing the kernel.
; Reserve space for idle thread stack NPX_SAVE_AREA, enough padding bytes to
; store the initial context from KiInitializeContextThread and the FWORD for
; lgdt/lidt.
;
xor ebp, ebp ; (ebp) = 0. No more stack frame
mov esp, offset _KiIdleThreadStackLimit + KERNEL_STACK_SIZE - NPX_FRAME_LENGTH - 64
;
; Inform the memory manager that we're about to quick reboot the system. This
; will cause us to switch back to large pages and to make the ROM visible so
; that we can copy out our .data section again.
;
stdCall _MmPrepareToQuickRebootSystem
;
; Copy the initialized portion of the .data section from ROM. Note that the
; stack that we're running on is part of this data image, so don't use the
; stack here or in the next block.
;
cld
mov ecx, dword ptr ds:[MzXdataSectionHeader]+XdshSizeOfInitializedData
shr ecx, 2
mov esi, dword ptr ds:[MzXdataSectionHeader]+XdshPointerToRawData
mov edi, dword ptr ds:[MzXdataSectionHeader]+XdshVirtualAddress
rep movsd
;
; Zero out the uninitialized portion of the .data section.
;
mov ecx, dword ptr ds:[MzXdataSectionHeader]+XdshSizeOfUninitializedData
xor eax, eax
shr ecx, 2
rep stosd
;
; Indicate to the rest of the system that we're initializing due to a quick
; boot.
;
mov byte ptr [_KeHasQuickBooted], 1
;
; Since the entries of Kernel IDT have their Selector and Extended Offset
; fields set up in the wrong order, we need to swap them back to the order
; which i386 recognizes.
;
stdCall _KiSwapIDT
;
; Load the interrupt descriptor table register.
;
mov ecx, offset FLAT:_KiIDTLEN
mov WORD PTR [esp], cx
mov DWORD PTR [esp+2], OFFSET _KiIDT
lidt FWORD PTR [esp]
;
; Preinitialize the system data structures.
;
stdCall _KiPreInitializeKernel
;
; Enable the kernel debugger.
;
stdCall _KdInitSystem, <1>
;
; Set initial IRQL = HIGH_LEVEL for init
;
mov ecx, HIGH_LEVEL
fstCall KfRaiseIrql
;
; Initialize system data structures and HAL.
;
stdCall _KiInitializeKernel
;
; Set "shadow" priority value for Idle thread. This will keep the Mutex
; priority boost/drop code from dropping priority on the Idle thread, and
; thus avoids leaving a bit set in the ActiveMatrix for the Idle thread when
; there should never be any such bit.
;
mov byte ptr [_KiIdleThread]+ThPriority,LOW_REALTIME_PRIORITY
;
; Control is returned to the idle thread with IRQL at HIGH_LEVEL. Lower IRQL
; to DISPATCH_LEVEL and set wait IRQL of idle thread.
;
sti
mov ecx, DISPATCH_LEVEL
fstCall KfLowerIrql
mov byte ptr [_KiIdleThread]+ThWaitIrql, DISPATCH_LEVEL
;
; The following code represents the idle thread for a processor. The idle
; thread executes at IRQL DISPATCH_LEVEL and continually polls for work to
; do. Control may be given to this loop either as a result of a return from
; the system initialization routine or as the result of starting up another
; processor in a multiprocessor configuration.
;
jmp KiIdleLoop ; enter idle loop
stdENDP _KeQuickRebootSystem
page ,132
subttl "Idle Loop"
;++
;
; Routine Description:
;
; This routine continuously executes the idle loop and never returns.
;
; Arguments:
;
; None.
;
; Return value:
;
; None - routine never returns.
;
;--
public KiIdleLoop
KiIdleLoop proc
lea ebx, _KiPCR ; get address of PCR
lea ebp, [ebx].PcPrcbData.PbDpcListHead ; set DPC listhead address
kid10:
;
; N.B. The following code enables interrupts for a few cycles, then
; disables them again for the subsequent DPC and next thread
; checks.
;
sti ; enable interrupts
nop ;
nop ;
cli ; disable interrupts
;
; Process the deferred procedure call list for the current processor.
;
cmp ebp, [ebp].LsFlink ; check if DPC list is empty
je short CheckNextThread ; if eq, DPC list is empty
mov cl, DISPATCH_LEVEL ; set interrupt level
fstCall HalClearSoftwareInterrupt ; clear software interrupt
call KiRetireDpcList ; process the current DPC list
;
; Check if a thread has been selected to run on the current processor.
;
CheckNextThread: ;
cmp dword ptr [ebx].PcPrcbData.PbNextThread, 0 ; thread selected?
je short kid10 ; if eq, no thread selected
;
; A thread has been selected for execution on this processor. Acquire
; the dispatcher database lock, get the thread address again (it may have
; changed), clear the address of the next thread in the processor block,
; and call swap context to start execution of the selected thread.
;
; N.B. If the dispatcher database lock cannot be obtained immediately,
; then attempt to process another DPC rather than spinning on the
; dispatcher database lock.
;
sti ; enable interrupts
mov esi, [ebx].PcPrcbData.PbNextThread ; get next thread address
mov edi, [ebx].PcPrcbData.PbCurrentThread ; set current thread address
mov dword ptr [ebx].PcPrcbData.PbNextThread, 0 ; clear next thread address
mov [ebx].PcPrcbData.PbCurrentThread, esi ; set current thread address
mov cl, 1 ; set APC interrupt bypass disable
call SwapContext ;
lea ebp, [ebx].PcPrcbData.PbDpcListHead ; set DPC listhead address
jmp kid10
KiIdleLoop endp
page ,132
subttl "Retire DPC List Loop"
;++
;
; Routine Description:
;
; This routine continuously executes a retire DPC loop and never returns.
;
; Arguments:
;
; None.
;
; Return value:
;
; None - routine never returns.
;
;--
cPublicProc _KeRetireDpcListLoop,0
lea ebx, _KiPCR ; get address of PCR
lea ebp, [ebx].PcPrcbData.PbDpcListHead ; set DPC listhead address
;
; This routine is entered at DPC level from a HAL DPC routine. Clear the flag
; that indicates that we're already executing a DPC so that the debug code in
; the clock interrupt won't fire because we're taking too long to execute.
;
mov [ebx].PcPrcbData.PbDpcRoutineActive, 0
krd10:
;
; N.B. The following code enables interrupts for a few cycles, then
; disables them again for the subsequent DPC and next thread
; checks.
;
sti ; enable interrupts
nop ;
nop ;
cli ; disable interrupts
;
; Process the deferred procedure call list for the current processor.
;
cmp ebp, [ebp].LsFlink ; check if DPC list is empty
je short krd10 ; if eq, DPC list is empty
mov cl, DISPATCH_LEVEL ; set interrupt level
fstCall HalClearSoftwareInterrupt ; clear software interrupt
call KiRetireDpcList ; process the current DPC list
jmp short krd10
stdENDP _KeRetireDpcListLoop
page ,132
subttl "Retire Deferred Procedure Call List"
;++
;
; Routine Description:
;
; This routine is called to retire the specified deferred procedure
; call list. DPC routines are called using the idle thread (current)
; stack.
;
; N.B. Interrupts must be disabled and the DPC list lock held on entry
; to this routine. Control is returned to the caller with the same
; conditions true.
;
; N.B. The registers ebx and ebp are preserved across the call.
;
; Arguments:
;
; ebx - Address of the target processor PCR.
; ebp - Address of the target DPC listhead.
;
; Return value:
;
; None.
;
;--
public KiRetireDpcList
KiRetireDpcList proc
?FpoValue = 0
FPOFRAME ?FpoValue,0
;
; Disable the FPU to catch the use of floating point from a DPC without first
; calling KeSaveFloatingPointState and KeRestoreFloatingPointState.
;
if DBG
mov eax, PCR[PcPrcbData+PbCurrentThread]
cmp byte ptr [eax].ThNpxState, NPX_STATE_LOADED
jne rdl5
mov eax, cr0 ; retrieve CR0
or eax, CR0_MP+CR0_TS
lmsw ax ; load new CR0 (low 16-bits)
endif
rdl5: mov PCR[PcPrcbData.PbDpcRoutineActive], esp ; set DPC routine active
;
; Process the DPC List.
;
rdl10: ;
mov edx, [ebp].LsFlink ; get address of next entry
mov ecx, [edx].LsFlink ; get address of next entry
mov [ebp].LsFlink, ecx ; set address of next in header
mov [ecx].LsBlink, ebp ; set address of previous in next
sub edx, DpDpcListEntry ; compute address of DPC object
mov ecx, [edx].DpDeferredRoutine ; get DPC routine address
if DBG
push ecx ; remember DPC routine address
push edi ; save register
mov edi, esp ; save current stack pointer
endif
ifdef DEVKIT
push esi ; save esi register
endif
FPOFRAME ?FpoValue,0
push [edx].DpSystemArgument2 ; second system argument
push [edx].DpSystemArgument1 ; first system argument
push [edx].DpDeferredContext ; get deferred context argument
push edx ; address of DPC object
mov byte ptr [edx]+DpInserted, 0 ; clear DPC inserted state
if DBG
mov PCR[PcPrcbData.PbDebugDpcTime], 0 ; Reset the time in DPC
endif
sti ; enable interrupts
;
; If the profiler is active, call its notification routine.
;
ifdef DEVKIT
cmp _KiDpcDispatchNotify, 0
jne rdl70
call ecx ; call DPC routine
rdl20: pop esi ; restore esi
else
call ecx ; call DPC routine
endif
if DBG
;
; Verify that the DPC returned with the FPU disabled. If the FPU is enabled,
; then the DPC probably forgot to call KeRestoreFloatingPointState.
;
mov eax, cr0 ; retrieve CR0
test al, CR0_TS ; test if FPU is still disabled
je rdl57 ; if e, FPU not disabled
;
; Verify that the DPC returned at DISPATCH_LEVEL.
;
rdl25: movzx eax, byte ptr PCR[PcIrql] ; get current IRQL
cmp al, DISPATCH_LEVEL ; check if still at dispatch level
jne rdl55 ; if ne, not at dispatch level
cmp esp, edi ; check if stack pointer is correct
jne rdl60 ; if ne, stack pointer is not correct
rdl30: pop edi ; restore register
pop ecx ; pop off saved DPC routine address
endif
FPOFRAME ?FpoValue,0
rdl35: cli ; disable interrupts
cmp ebp, [ebp].LsFlink ; check if DPC list is empty
jne rdl10 ; if ne, DPC list not empty
;
; Clear DPC routine active and DPC requested flags.
;
rdl40: mov [ebx].PcPrcbData.PbDpcRoutineActive, 0
mov [ebx].PcPrcbData.PbDpcInterruptRequested, 0
;
; Check one last time that the DPC list is empty. This is required to
; close a race condition with the DPC queuing code where it appears that
; a DPC routine is active (and thus an interrupt is not requested), but
; this code has decided the DPC list is empty and is clearing the DPC
; active flag.
;
cmp ebp, [ebp].LsFlink ; check if DPC list is empty
jne rdl5 ; if ne, DPC list not empty
;
; Restore the original CR0 if the current thread still owns the FPU. Note that
; if a DPC called KeSaveFloatingPointState or hit the debugger, then the FPU
; state was flushed, so we shouldn't enable the FPU.
;
if DBG
mov ecx, PCR[PcPrcbData+PbCurrentThread]
cmp byte ptr [ecx].ThNpxState, NPX_STATE_LOADED
jne rdl50
mov eax, cr0
mov edx, [ebx].PcStackBase
and eax, NOT (CR0_MP+CR0_EM+CR0_TS)
or eax, [edx].FpCr0NpxState
lmsw ax ; load new CR0 (low 16-bits)
endif
rdl50: ret ; return
;
; Unlock DPC list and clear DPC active.
;
if DBG
rdl55: stdCall _KeBugCheckEx, <IRQL_NOT_GREATER_OR_EQUAL, ebx, eax, 0, 0> ;
rdl57: push dword ptr [edi+4]
push offset FLAT:_MsgDpcFpuEnabled ; push message address
call _DbgPrint ; print debug message
add esp, 8 ; remove arguments from stack
int 3 ; break into debugger
mov esp, edi ; reset stack pointer
jmp rdl25 ;
rdl60: push dword ptr [edi+4] ; push address of DPC function
push offset FLAT:_MsgDpcTrashedEsp ; push message address
call _DbgPrint ; print debug message
add esp, 8 ; remove arguments from stack
int 3 ; break into debugger
mov esp, edi ; reset stack pointer
jmp rdl30 ;
endif
;
; Notify the profiler that a DPC routine is called:
;
; VOID (FASTCALL *KiDpcDispatchNotify)(VOID*, ULONG)
;
ifdef DEVKIT
rdl70: mov esi, ecx ; save DPC routine address in esi
xor edx, edx ; KiDpcDispatchNotify(dpcproc, 0)
call [_KiDpcDispatchNotify]
call esi ; call the DPC routine
mov ecx, esi ; KiDpcDispatchNotify(dpcproc, 1)
mov edx, 1
call [_KiDpcDispatchNotify]
jmp rdl20
endif
KiRetireDpcList endp
_TEXT ends
end