        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
