;++
;
; Copyright (c) Microsoft Corporation. All rights reserved. 
;
; You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
; If you do not agree to the terms, do not use the code.
;
;
;   Module Name:
;
;       kimacro.inc
;
;   Abstract:
;
;       This module contains the macros used by kernel assembler code.
;       It includes macros to manipulate interrupts, support system
;       entry and exit for syscalls, faults, and interrupts, and
;       manipulate floating point state.
;
;--

;++
;
;   These constants are used by the fpo directives in this file.
;   This directive causes the assembler to output a .debug$f segment
;   in the obj file.  The segment will contain 1 fpo record for each
;   directive present during assembly.
;
;   Although the assembler will accept all valid values, the value of 7
;   in the FPO_REGS field indicates to the debugger that a trap frame is
;   generated by the function.  The value of 7 can be used because the
;   C/C++ compiler puts a maximum value of 3 in the field.
;
FPO_LOCALS      equ     0         ; 32 bits, size of locals in dwords
FPO_PARAMS      equ     0         ; 32 bits, size of parameters in dwords
FPO_PROLOG      equ     0         ; 12 bits, 0-4095, # of bytes in prolog
FPO_REGS        equ     0         ; 3 bits, 0-7, # regs saved in prolog
FPO_USE_EBP     equ     0         ; 1 bit, 0-1, is ebp used?
FPO_TRAPFRAME   equ     1         ; 2 bits, 0=fpo, 1=trap frame, 2=tss
;
;--


;++
;
;   POLL_DEBUGGER
;
;   Macro Description:
;
;       Call the debugger so it can check for control-c.  If it finds
;       it, it will report our iret address as address of break-in.
;
;       N.B. This macro should be used when all the caller's registers
;            have been restored. (Otherwise, the kernel debugger register
;            dump will not have correct state.)  The only exception is
;            fs.  This is because Kd may need to access PCR or PRCB.
;
;   Arguments:
;
;       There MUST be an iret frame on the stack when this macro
;       is invoked.
;
;   Exit:
;
;       Debugger will iret for us, so we don't usually return from
;       this macro, but remember that it generates nothing for non-DEVL
;       kernels.
;--

POLL_DEBUGGER   macro
local   a, b, c_

if  DEVL
        EXTRNP   _DbgBreakPointWithStatus,1
        stdCall _KdPollBreakIn
        or      al,al
        jz      short c_
        stdCall _DbgBreakPointWithStatus,<DBG_STATUS_CONTROL_C>
c_:
endif   ; DEVL
endm

;++
;
;   ASSERT_FS
;
;   Try to catch funky condition wherein we get FS=r3 value while
;   running in kernel mode.
;
;--

ASSERT_FS   macro
local   a,b

if DBG
        EXTRNP   _KeBugCheck,1

        mov     bx,fs
        cmp     bx,KGDT_R0_PCR
        jnz     short a

        cmp     dword ptr fs:[0], 0
        jne     short b

a:
        stdCall   _KeBugCheck,<-1>
align 4
b:
endif
endm



;++
;
;
;   Copy data from various places into base of TrapFrame, net effect
;   is to allow dbg KB command to trace across trap frame, and to
;   allow user to find arguments to system calls.
;
;   USE ebx and edi.
;--

SET_DEBUG_DATA  macro

ife FPO

;
; This macro is used by ENTER_SYSCALL, ENTER_TRAP and ENTER_INTERRUPT
; and is used at the end of above macros.  It is safe to destroy ebx, edi.
;

        mov     ebx,[ebp]+TsEbp
        mov     edi,[ebp]+TsEip
        mov     [ebp]+TsDbgArgPointer,edx
        mov     [ebp]+TsDbgArgMark,0BADB0D00h
        mov     [ebp]+TsDbgEbp,ebx
        mov     [ebp]+TsDbgEip,edi
endif

endm


;++
;
;   ENTER_DR_ASSIST     EnterLabel, ExitLabel, NoAbiosAssist, NoV86Assist
;
;   Macro Description:
;
;       Jumped to by ENTER_ macros to deal with DR register work,
;       abios work and v86 work.  The main purpose of this macro is
;       that interrupt/trap/systemCall EnterMacros can jump here to
;       deal with some special cases such that most of the times the
;       main ENTER_ execution flow can proceed without being branched.
;
;       If (previousmode == usermode) {
;           save DR* in trapframe
;           load DR* from Prcb
;       }
;
;   Arguments:
;       EnterLabel - label to emit
;       ExitLabel - label to branch to when done
;
;   Entry-conditions:
;     Dr work:
;       DebugActive == TRUE
;       (esi)->Thread object
;       (esp)->base of trap frame
;       (ebp)->base of trap frame
;
;     Abios work:
;     v86 work:
;
;   Exit-conditions:
;     Dr work:
;       Interrupts match input state (this routine doesn't change IEF)
;       (esp)->base of trap frame
;       (ebp)->base of trap frame
;       Preserves entry eax, edx
;     Abios work:
;     v86 work:
;
;--

ENTER_DR_ASSIST macro   EnterLabel, ExitLabel, NoAbiosAssist, NoV86Assist, V86R
        local   a,b

        public  Dr_&EnterLabel
align 4
Dr_&EnterLabel:

;
; Test if we came from user-mode.  If not, do nothing.
;
        test    dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
        jnz     short a

.errnz (MODE_MASK AND 0FFFFFF00h)
        test    byte ptr [ebp]+TsSegCs,MODE_MASK
        jz      Dr_&ExitLabel          ; called from kmode, go continue


;
; Save user-mode Dr* regs in TrapFrame
;
; We are safe to destroy ebx, ecx, edi because in ENTER_INTERRUPT and
; ENTER_TRAP these registers are saved already.  In ENTER_SYSCALL
; ebx, edi is saved and ecx is saved as needed.
;

a:      mov     ebx,dr0
        mov     ecx,dr1
        mov     edi,dr2
        mov     [ebp]+TsDr0,ebx
        mov     [ebp]+TsDr1,ecx
        mov     [ebp]+TsDr2,edi
        mov     ebx,dr3
        mov     ecx,dr6
        mov     edi,dr7
        mov     [ebp]+TsDr3,ebx
        mov     [ebp]+TsDr6,ecx
        xor     ebx,ebx
        mov     [ebp]+TsDr7,edi

;
; Make Dr7 safe before loading junk from save area
;
        mov     dr7,ebx

;
; Load KernelDr* into processor
;

        mov     edi,dword ptr fs:[PcPrcb]
        mov     ebx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr0
        mov     ecx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr1
        mov     dr0,ebx
        mov     dr1,ecx
        mov     ebx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr2
        mov     ecx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr3
        mov     dr2,ebx
        mov     dr3,ecx
        mov     ebx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr6
        mov     ecx,[edi].PbProcessorState.PsSpecialRegisters.SrKernelDr7
        mov     dr6,ebx
        mov     dr7,ecx

ifnb <V86R>
        test    dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
        jz      short b
        jmp     Dr_&V86R
endif
b:
        jmp     Dr_&ExitLabel


ifb <NoAbiosAssist>

        public  Abios_&EnterLabel
align 4
Abios_&EnterLabel:

;
;   INTERRUPT_STACK16_TO_STACK32
;
;       This macro remaps current 32bit stack to 16bit stack at interrupt
;       time.
;
;   Arguments:
;
;       (esp)->trap frame.
;       (eax)->Entry Esp.
;

        mov     eax, [esp].TsErrCode    ; (eax) = Entry Esp
        mov     ecx, KGDT_R0_DATA
        mov     edx, fs:[PcPrcb]        ; get current PRCB address
        mov     edx, [edx]+PbCurrentThread ; get current thread
        mov     edx, [edx]+ThStackLimit ; get thread stack base
        shl     eax, 16
        add     edx, esp
        mov     [esp].TsErrCode, eax
        mov     ss, cx
        mov     esp, edx                ; Interrupts are off
        mov     ebp, edx
        jmp     Abios_&ExitLabel

endif   ; NoAbiosAssist

ifb <NoV86Assist>

        public  V86_&EnterLabel
align 4
V86_&EnterLabel:

;
;   Move the V86 segment registers to the correct place in the frame
;
        mov     eax,dword ptr [ebp].TsV86Fs
        mov     ebx,dword ptr [ebp].TsV86Gs
        mov     ecx,dword ptr [ebp].TsV86Es
        mov     edx,dword ptr [ebp].TsV86Ds
        mov     [ebp].TsSegFs,ax
        mov     [ebp].TsSegGs,bx
        mov     [ebp].TsSegEs,cx
        mov     [ebp].TsSegDs,dx
        jmp     V86_&ExitLabel

endif   ; NoV86Assist

        endm

;++
;
;   ENTER_SYSCALL       AssistLabel, TagetLabel, NoFSLoad, SaveEcx
;
;   Macro Description:
;
;       Build the frame and set registers needed by a system call.
;
;       Save:
;           Errorpad,
;           Non-volatile regs,
;           FS,
;           ExceptionList,
;           PreviousMode
;
;       Don't Save:
;           Volatile regs
;           Seg regs
;           Floating point state
;
;       Set:
;           FS,
;           ExceptionList,
;           PreviousMode,
;           Direction
;
;   Arguments:
;       AssistLabel - label ENTER_ASSIST macro is at
;       TargetLabel - label to emit for ENTER_ASSIST to jump to
;       NoFSLoad    - Don't set FS(it is already set to KGDT_R0_PCR at entry).
;	SaveEcx     - Save ecx off in the trap frame.
;
;   Exit-conditions:
;       Interrupts match input state (this routine doesn't change IEF)
;       (esp)->base of trap frame
;       (ebp)->base of trap frame
;       Preserves entry eax, edx
;
;   Note:
;       The DS: reference to PreviousMode is *required* for correct
;       functioning of lazy selector loads.  If you remove this use
;       of DS:, put a DS: override on something.
;
;--

ENTER_SYSCALL macro     AssistLabel, TargetLabel, NoFSLoad, RejectVdmLabel, SaveEcx


.FPO ( FPO_LOCALS, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )

ifdef KERNELONLY

;
; Construct trap frame.
;
; N.B. The initial part of the trap frame is constructed by pushing values
;      on the stack. If the format of the trap frame is changed, then the
;      following code must alos be changed.
;

        push    0                       ; put pad dword for error on stack
        push    ebp                     ; save the non-volatile registers
        push    ebx                     ;
        push    esi                     ;
        push    edi                     ;
ifb <NoFSLoad>
        push    fs                      ; save and set FS to PCR.
        mov     ebx,KGDT_R0_PCR         ; set PCR segment number
        mov     fs,bx                   ;
else
        ; FS already contains KGDT_R0_PCR(entry via PentiumPro fast system call)
        push    KGDT_R3_TEB OR RPL_MASK
endif  ; NoFSLoad

        mov     esi,PCR[PcPrcbData+PbCurrentThread] ; get current thread address
;
; Save the old exception list in trap frame and initialize a new empty
; exception list.
;

        push    PCR[PcExceptionList]    ; save old exception list
        mov     PCR[PcExceptionList],EXCEPTION_CHAIN_END ; set new empty list

;
; Save the old previous mode in trap frame, allocate remainder of trap frame,
; and set the new previous mode.
;

        push    [esi]+ThPreviousMode    ; save old previous mode
        sub     esp,TsPreviousPreviousMode ; allocate remainder of trap frame
        mov     ebx,[esp+TsSegCS]       ; compute new previous mode
        and     ebx,MODE_MASK           ;
        mov     [esi]+ThPreviousMode,bl ; set new previous mode
;
; Save the old trap frame address and set the new trap frame address.
;

        mov     ebp,esp                 ; set trap frame address
        mov     ebx,[esi].ThTrapFrame   ; save current trap frame address
        mov     [ebp].TsEdx,ebx         ;
ifnb <SaveEcx>
        mov     [ebp].TsEcx,ecx         ; Save ecx if requested
endif
        and     dword ptr [ebp].TsDr7, 0
        test    byte ptr [esi].ThDebugActive, 0ffh ; See if we need to save debug registers
        mov     [esi].ThTrapFrame,ebp   ; set new trap frame address
        cld                             ; make sure direction is forward
        
        jnz     Dr_&AssistLabel         ; if nz, debugging is active on thread

Dr_&TargetLabel:                        ;

        SET_DEBUG_DATA                  ; Note this destroys edi
        sti                             ; enable interrupts

else
        %out    ENTER_SYSCAL outside of kernel
        .err
endif
        endm

;++
;
;   ENTER_INTERRUPT     AssistLabel, TargetLabel
;
;   Macro Description:
;
;       Build the frame and set registers needed by an interrupt.
;
;       Save:
;           Errorpad,
;           Non-volatile regs,
;           FS,
;           ExceptionList,
;           PreviousMode
;           Volatile regs
;           Seg regs from V86 mode
;           DS, ES, GS
;
;       Don't Save:
;           Floating point state
;
;       Set:
;           FS,
;           ExceptionList,
;           Direction,
;           DS, ES
;
;       Don't Set:
;           PreviousMode
;
;   Arguments:
;       AssistLabel - label ENTER_ASSIST macro is at
;       TargetLabel - label to emit for ENTER_ASSIST to jump to
;
;   Exit-conditions:
;       Interrupts match input state (this routine doesn't change IEF)
;       (esp)->base of trap frame
;       (ebp)->base of trap frame
;       Preserves entry eax, ecx, edx
;
;--

ENTER_INTERRUPT macro   AssistLabel, TargetLabel, PassParm
        local b

.FPO ( FPO_LOCALS+2, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )

;
;   Fill in parts of frame we care about
;

ifb <PassParm>
        push    esp                 ; Use Error code field to save 16bit esp
endif
        push    ebp                 ; Save the non-volatile registers
        push    ebx
        push    esi
        push    edi

        sub     esp, TsEdi
        mov     ebp,esp

        mov     [esp]+TsEax, eax    ; Save volatile registers
        mov     [esp]+TsEcx, ecx
        mov     [esp]+TsEdx, edx
if DBG
        mov     dword ptr [esp]+TsPreviousPreviousMode, -1 ; ThPreviousMode not pushed on interrupt
endif

        test    dword ptr [esp].TsEFlags,EFLAGS_V86_MASK

        jnz     V86_&AssistLabel

        cmp     word ptr [esp]+TsSegCs, KGDT_R0_CODE
        jz      short @f

        mov     [esp]+TsSegFs, fs  ; Save and set FS to PCR.
        mov     [esp]+TsSegDs, ds
        mov     [esp]+TsSegEs, es
        mov     [esp]+TsSegGs, gs

V86_&TargetLabel:
        mov     ebx,KGDT_R0_PCR
        mov     eax,KGDT_R3_DATA OR RPL_MASK
        mov     fs, bx
        mov     ds, ax
        mov     es, ax
@@:
        mov     ebx, fs:[PcExceptionList] ;Save, set ExceptionList
        mov     fs:[PcExceptionList],EXCEPTION_CHAIN_END
        mov     [esp]+TsExceptionList, ebx

ifnb <PassParm>
        lea     eax, [esp].TsErrCode
        lea     ecx, [esp].TsEip    ; Move eax to EIP field
        mov     ebx, ss:[eax]       ; (ebx) = parameter to pass
        mov     ss:[eax], ecx       ; save 16bit esp
endif

;
; Remap ABIOS 16 bit stack to 32 bit stack, if necessary.
;

        cmp     esp, 10000h
        jb      Abios_&AssistLabel

        mov     dword ptr [esp].TsErrCode, 0 ; Indicate no remapping.
Abios_&TargetLabel:

;
; end of Abios stack checking
;

ifdef PcPrcbData
        mov     ecx,PCR[PcPrcbData+PbCurrentThread] ; get current thread address
else
        mov     ecx,PCR[PcPrcb]
        mov     ecx,[ecx].PbCurrentThread ; get current thread address
endif

        cld

        and     dword ptr [ebp].TsDr7, 0
        test    byte ptr [ecx].ThDebugActive, 0ffh ; See if debug registers need saving
ifnb <PassParm>
        push    ebx                 ; push parameter as argument
endif
        jnz     Dr_&AssistLabel

Dr_&TargetLabel:

        SET_DEBUG_DATA

        endm

;++
;
;   ENTER_INTERRUPT_FORCE_STATE   AssistLabel, TargetLabel
;
;   Macro Description:
;
;       Build the frame and set registers needed by an interrupt.
;
;       This macro is the same as ENTER_INTERRUPT except that it forces the
;       needed state and does not save previous state.
;
;       This macro is currently only used by HalpApicRebootService which does not
;       return;
;
;       Save:
;           Errorpad,
;           Non-volatile regs,
;           ExceptionList,
;           PreviousMode
;           Volatile regs
;           Seg regs from V86 mode
;
;       Don't Save:
;           FS,
;           DS, ES, GS
;           Floating point state
;
;       Set:
;           FS,
;           ExceptionList,
;           Direction,
;           DS, ES
;
;       Don't Set:
;           PreviousMode
;
;   Arguments:
;       AssistLabel - label ENTER_ASSIST macro is at
;       TargetLabel - label to emit for ENTER_ASSIST to jump to
;
;   Exit-conditions:
;       Interrupts match input state (this routine doesn't change IEF)
;       (esp)->base of trap frame
;       (ebp)->base of trap frame
;       Preserves entry eax, ecx, edx
;
;--

ENTER_INTERRUPT_FORCE_STATE macro   AssistLabel, TargetLabel, PassParm
        local b

.FPO ( FPO_LOCALS+2, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )

;
;   Fill in parts of frame we care about
;

ifb <PassParm>
        push    esp                 ; Use Error code field to save 16bit esp
endif
        push    ebp                 ; Save the non-volatile registers
        push    ebx
        push    esi
        push    edi

        sub     esp, TsEdi
        mov     ebp,esp

        mov     [esp]+TsEax, eax    ; Save volatile registers
        mov     [esp]+TsEcx, ecx
        mov     [esp]+TsEdx, edx
if DBG
        mov     dword ptr [esp]+TsPreviousPreviousMode, -1 ; ThPreviousMode not pushed on interrupt
endif

        test    dword ptr [esp].TsEflags,EFLAGS_V86_MASK

        jnz     V86_&AssistLabel

V86_&TargetLabel:
        mov     ebx,KGDT_R0_PCR
        mov     eax,KGDT_R3_DATA OR RPL_MASK
        mov     fs, bx
        mov     ds, ax
        mov     es, ax
@@:
        mov     ebx, fs:[PcExceptionList] ;Save, set ExceptionList
        mov     fs:[PcExceptionList],EXCEPTION_CHAIN_END
        mov     [esp]+TsExceptionList, ebx

ifnb <PassParm>
        lea     eax, [esp].TsErrCode
        lea     ecx, [esp].TsEip    ; Move eax to EIP field
        mov     ebx, ss:[eax]       ; (ebx) = parameter to pass
        mov     ss:[eax], ecx       ; save 16bit esp
endif

;
; Remap ABIOS 16 bit stack to 32 bit stack, if necessary.
;

        cmp     esp, 10000h
        jb      Abios_&AssistLabel

        mov     dword ptr [esp].TsErrCode, 0 ; Indicate no remapping.
Abios_&TargetLabel:

;
; end of Abios stack checking
;

ifdef PcPrcbData
        mov     ecx,PCR[PcPrcbData+PbCurrentThread] ; get current thread address
else
        mov     ecx,PCR[PcPrcb]
        mov     ecx,[ecx].PbCurrentThread ; get current thread address
endif
        cld

        and     dword ptr [ebp].TsDr7, 0
        test    byte ptr [ecx].ThDebugActive, 0ffh ; See if debug registers need saving
ifnb <PassParm>
        push    ebx                 ; push parameter as argument
endif
        jnz     Dr_&AssistLabel

Dr_&TargetLabel:

        SET_DEBUG_DATA


        endm

;++
;
;   ENTER_TRAP      AssistLabel, TargetLabel
;
;   Macro Description:
;
;       Build the frame and set registers needed by a trap or exception.
;
;       Save:
;           Non-volatile regs,
;           FS,
;           ExceptionList,
;           PreviousMode,
;           Volatile regs
;           Seg Regs from V86 mode
;           DS, ES, GS
;
;       Don't Save:
;           Floating point state
;
;       Set:
;           FS,
;           Direction,
;           DS, ES
;
;       Don't Set:
;           PreviousMode,
;           ExceptionList
;
;   Arguments:
;       AssistLabel - label ENTER_ASSIST macro is at
;       TargetLabel - label to emit for ENTER_ASSIST to jump to
;
;   Exit-conditions:
;       Interrupts match input state (this routine doesn't change IEF)
;       (esp)->base of trap frame
;       (ebp)->base of trap frame
;       Preserves entry eax
;
;--

ENTER_TRAP macro    AssistLabel, TargetLabel
        local b

.FPO ( FPO_LOCALS, FPO_PARAMS, FPO_PROLOG, FPO_REGS, FPO_USE_EBP, FPO_TRAPFRAME )

;
;   Fill in parts of frame we care about
;

if DBG
ifndef  _Ki16BitStackException
    EXTRNP   _Ki16BitStackException
endif
endif ; DBG

        mov     word ptr [esp+2], 0 ; Clear upper word of ErrorCode

        push    ebp                 ; Save the non-volatile registers
        push    ebx
        push    esi
        push    edi

        push    fs                  ; Save and set FS to PCR.
        mov     ebx,KGDT_R0_PCR
        mov     fs,bx
        mov     ebx, fs:[PcExceptionList] ;Save ExceptionList
        push    ebx
if DBG
        push    -1                  ; Don't need to save ThPreviousMode from trap
else
        sub     esp, 4              ; pad dword
endif
        push    eax                 ; Save the volatile registers
        push    ecx
        push    edx

        push    ds                  ; Save segments
        push    es
        push    gs

;
;   Skip allocate reset of trap frame and Set up DS/ES, they may be trash
;

        mov     ax,KGDT_R3_DATA OR RPL_MASK
        sub     esp,TsSegGs
        mov     ds,ax
        mov     es,ax

if DBG
;
; The code here check if the exception occurred in ring 0
; ABIOS code. If yes, this is a fatal condition.  We will
; put out message and bugcheck.
;

        cmp     esp, 10000h             ; Is the trap in abios?
        jb      _Ki16BitStackException       ; if b, yes, switch stack and bugcheck.

endif ; DBG

        mov     ebp,esp

        test    dword ptr [esp].TsEflags,EFLAGS_V86_MASK

        jnz     V86_&AssistLabel

V86_&TargetLabel:

ifdef PcPrcbData
        mov     ecx,PCR[PcPrcbData+PbCurrentThread] ; get current thread address
else
        mov     ecx,PCR[PcPrcb]
        mov     ecx,[ecx].PbCurrentThread ; get current thread address
endif
        cld

        and     dword ptr [ebp].TsDr7, 0
        test    byte ptr [ecx].ThDebugActive, 0ffh ; See if debug registers need saving

        jnz     Dr_&AssistLabel

Dr_&TargetLabel:

        SET_DEBUG_DATA


        endm
;++
;
;   EXIT_ALL    NoRestoreSegs, NoRestoreVolatiles, NoPreviousMode
;
;   Macro Description:
;
;       Load a syscall frame back into the machine.
;
;       Restore:
;           Volatile regs, IF NoRestoreVolatiles blank
;           NoPreviousMode,
;           ExceptionList,
;           FS,
;           Non-volatile regs
;
;       If the frame is a kernel mode frame, AND esp has been edited,
;       then TsSegCs will have a special value.  Test for that value
;       and execute special code for that case.
;
;       N.B. This macro generates an IRET!  (i.e. It exits!)
;
;   Arguments:
;
;       NoRestoreSegs - non-blank if DS, ES, GS are NOT to be restored
;
;       NoRestoreVolatiles - non-blank if Volatile regs are NOT to be restored
;
;       NoPreviousMode - if nb pop ThPreviousMode
;
;   Entry-conditions:
;
;       (esp)->base of trap frame
;       (ebp)->Base of trap frame
;
;   Exit-conditions:
;
;       Does not exit, returns.
;       Preserves eax, ecx, edx, IFF NoRestoreVolatiles is set
;
;--

?adjesp = 0
?RestoreAll = 1

EXIT_ALL macro  NoRestoreSegs, NoRestoreVolatiles, NoPreviousMode
local   a, b, f, x
local   Dr_ExitHelp, Dr_ExitHelp_Target
local   Db_NotATrapFrame, Db_A, Db_NotValidEntry, NonFlatPm_Target
;
; Sanity check some values and setup globals for macro
;

?adjesp = TsSegGs
?RestoreAll = 1

ifnb <NoRestoreSegs>
    ?RestoreAll = 0
    ?adjesp = ?adjesp + 12
endif

ifnb <NoRestoreVolatiles>
    if ?RestoreAll eq 1
        %out "EXIT_ALL NoRestoreVolatiles requires NoRestoreSegs"
        .err
    endif
    ?adjesp = ?adjesp + 12
endif

ifb <NoPreviousMode>
ifndef KERNELONLY
        %out    EXIT_ALL can not restore previousmode outside kernel
        .err
endif
endif

; All callers are responsible for getting here with interrupts disabled.

if DBG
        pushfd
        pop     edx

        test    edx, EFLAGS_INTERRUPT_MASK
        jnz     Db_NotValidEntry

        cmp     esp, ebp                    ; make sure esp = ebp
        jne     Db_NotValidEntry

; Make sure BADB0D00 sig is present.  If not this isn't a trap frame!
Db_A:   sub     [esp]+TsDbgArgMark,0BADB0D00h
        jne     Db_NotATrapFrame

endif

        ASSERT_FS

        mov     edx, [esp]+TsExceptionList
if DBG
        or      edx, edx
        jnz     short @f
    int 3
@@:
endif
        mov     PCR[PcExceptionList], edx   ; Restore ExceptionList

ifb <NoPreviousMode>
        mov     ecx, [esp]+TsPreviousPreviousMode ; Restore PreviousMode
if DBG
        cmp     ecx, -1     ; temporary debugging code
        jne     @f          ; to make sure no one tries to pop ThPreviousMode
    int 3                   ; when it wasn't saved
@@:
endif
        mov     esi,PCR[PcPrcbData+PbCurrentThread]
        mov     [esi]+ThPreviousMode,cl
else
if DBG
        mov     ecx, [esp]+TsPreviousPreviousMode
        cmp     ecx, -1     ; temporary debugging code
        je     @f           ; to make sure no one pushed ThPreviousMode and
    int 3                   ; is now exiting without restoreing it
@@:
endif
endif

        test    dword ptr [esp].TsDr7, (NOT DR7_RESERVED_MASK)
        jnz     Dr_ExitHelp

Dr_ExitHelp_Target:

        test    dword ptr [esp].TsEflags,EFLAGS_V86_MASK
        jnz     V86ExitHelp

        test    word ptr [esp]+TsSegCs,FRAME_EDITED
        jz      b                           ; Edited frame pop out.


if ?RestoreAll eq 0
.errnz MODE_MASK-1
        cmp     word ptr [esp]+TsSegCs,KGDT_R3_CODE OR RPL_MASK ; set/clear ZF
        bt      word ptr [esp]+TsSegCs,0    ; test MODE_MASK      set/clear CF
        cmc                                 ;       (CF=1 and ZF=0)
        ja      f                           ; jmp if CF=0 and ZF=0
endif
ifb <NoRestoreVolatiles>
        mov     edx, [esp]+TsEdx            ; Restore volitales
        mov     ecx, [esp]+TsEcx
                                            ; must restore eax before any
        mov     eax, [esp].TsEax            ; selectors! (see trap0e handler)
endif

        cmp     word ptr [ebp]+TsSegCs, KGDT_R0_CODE
        jz      short @f

ifb <NoRestoreSegs>
        lea     esp, [ebp]+TsSegGs
        pop     gs                          ; Restore Segs
        pop     es
        pop     ds
endif
NonFlatPm_Target:
        lea     esp, [ebp]+TsSegFs
        pop     fs
@@:
        lea     esp, [ebp]+TsEdi            ; Skip PreMode, ExceptList and fs

        pop     edi                         ; restore non-volatiles
        pop     esi
        pop     ebx
        pop     ebp

;
; Esp MUST point to the Error Code on the stack.  Because we use it to
; store the entering esp.
;

        cmp     word ptr [esp+8], 80h ; check for abios code segment?
        ja      AbiosExitHelp

        add     esp, 4              ; remove error code from trap frame

ifnb <NoRestoreVolatiles>

        public  _KiSystemCallExitBranch
        public  _KiSystemCallExit
        public  _KiSystemCallExit2
        public  _KiSystemCallExit3

; NoRestoreVolatiles is only used for return from System Service.
; If returning to Kernel mode, the processor state does not need
; to be altered (CS, CPL stays the same etc), so simply unwind the
; kernel frame and branch to the saved EIP.

        test    dword ptr [esp+4], MODE_MASK

; If the following branch is taken, we are returning to usermode.
; If this processor supports the SYSEXIT instruction, the branch
; will be adjusted at boot time to use the appropriate code sequence.

_KiSystemCallExitBranch:
        jnz     short _KiSystemCallExit

        ; Exit to kernel mode from system call, faster than IRETD,
        ; unwind the frame and branch to return address.

        pop     edx                 ; get eip
        pop     ecx                 ; remove CS from stack
        popfd                       ; restore eflags
        jmp     edx


_KiSystemCallExit:

        iretd                       ; return

_KiSystemCallExit2:

        test    dword ptr [esp+8], EFLAGS_TF
        jne     short _KiSystemCallExit

        pop     edx                 ; pop EIP
        add     esp, 4              ; Remove CS
        and     dword ptr [esp], NOT EFLAGS_INTERRUPT_MASK ; Disable interrupts in the flags
        popfd
        pop     ecx                 ; pop ESP

        sti                         ; sysexit does not reload flags

        iSYSEXIT

_KiSystemCallExit3:

        ; AMD

        pop     ecx                 ; pop EIP
        add     esp, 8
        pop     esp
;        mov     esp, [esp+8]        ; remove CS & Eflags, get ESP

        iSYSRET

endif  ;; <NoRestoreVolatiles>

        iretd                       ; return

if DBG
Db_NotATrapFrame:
        add     [esp]+TsDbgArgMark,0BADB0D00h   ; put back the orig value
Db_NotValidEntry:
        int 3
        jmp     Db_A
endif

;
;   EXIT_HELPER
;
;       if (PreviousMode == UserMode) {
;           DR* regs = TF.Dr* regs
;       }
;
;   Entry-Conditions:
;
;       DebugActive == TRUE
;       (ebp)->TrapFrame
;
;--

align dword
Dr_ExitHelp:

        test    dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
        jnz     short x

        test    dword ptr [ebp]+TsSegCs,MODE_MASK
        jz      Dr_ExitHelp_Target

x:      xor     ebx,ebx
        mov     esi,[ebp]+TsDr0
        mov     edi,[ebp]+TsDr1
        mov     dr7,ebx
        mov     dr0,esi
        mov     ebx,[ebp]+TsDr2
        mov     dr1,edi
        mov     dr2,ebx
        mov     esi,[ebp]+TsDr3
        mov     edi,[ebp]+TsDr6
        mov     ebx,[ebp]+TsDr7
        mov     dr3,esi
        mov     dr6,edi
        mov     dr7,ebx

        jmp     Dr_ExitHelp_Target

;
if ?RestoreAll eq 0
;
;   Restore segs and volatiles for non-flat R3 PM (VDM in PM)
;

f:      mov     eax,[esp].TsEax     ; restore eax before any selectors
                                        ; (see trap0e handler)
        add     esp,TsSegGs

        pop     gs
        pop     es
        pop     ds

        pop     edx
        pop     ecx
        jmp     NonFlatPm_Target

endif   ; not ?RestoreAll


;
;   TsSegCs contains the special value that means the frame was edited
;   in a way that affected esp, AND it's a kernel mode frame.
;   (Special value is null selector except for RPL.)
;
;   Put back the real CS.
;   push eflags, eip onto target stack
;   restore
;   switch to target stack
;   iret
;

b:      mov     ebx,[esp]+TsTempSegCs
        mov     [esp]+TsSegCs,ebx

;
;   There is no instruction that will load esp with an arbitrary value
;   (i.e. one out of a frame) and do a return, if no privledge transition
;   is occuring.  Therefore, if we are returning to kernel mode, and
;   esp has been edited, we must "emulate" a kind of iretd.
;
;   We do this by logically pushing the eip,cs,eflags onto the new
;   logical stack, loading that stack, and doing an iretd.  This
;   requires that the new logical stack is at least 1 dword higher
;   than the unedited esp would have been.  (i.e.  It is not legal
;   to edit esp to have a new value < the old value.)
;
;   KeContextToKframes enforces this rule.
;

;
;   Compute new logical stack address
;

        mov     ebx,[esp]+TsTempEsp
        sub     ebx,12
        mov     [esp]+TsErrCode,ebx

;
;   Copy eip,cs,eflags to new stack.  note we do this high to low
;

        mov     esi,[esp]+TsEflags
        mov     [ebx+8],esi
        mov     esi,[esp]+TsSegCs
        mov     [ebx+4],esi
        mov     esi,[esp]+TsEip
        mov     [ebx],esi

;
;   Do a standard restore sequence.
;
;   Observe that RestoreVolatiles is honored.  Editing a volatile
;   register has no effect when returning from a system call.
;
ifb     <NoRestoreVolatiles>
        mov     eax,[esp].TsEax
endif
;        add     esp,TsSegGs
;
;ifb     <NoRestoreSegs>
;        pop     gs
;        pop     es
;        pop     ds
;else
;        add     esp,12
;endif

ifb     <NoRestoreVolatiles>
        mov     edx, [esp]+TsEdx
        mov     ecx, [esp]+TsEcx
endif

;ifnb <NoPreviousMode>
;        add     esp, 4              ; Skip previous mode
;else
;        pop     ebx                 ; Restore PreviousMode
;        mov     esi,fs:[PcPrcbData+PbCurrentThread]
;        mov     ss:[esi]+ThPreviousMode,bl
;endif
;
;       pop     ebx
;
;       mov     fs:[PcExceptionList], ebx ;Restore ExceptionList
;       pop     fs

        add     esp, TsEdi
        pop     edi                 ; restore non-volatiles
        pop     esi
        pop     ebx
        pop     ebp

;
;   (esp)->TsErrCode, where we saved the new esp
;

        mov     esp,[esp]           ; Do move not push to avoid increment
        iretd

        endm


;++
;
;   INTERRUPT_EXIT
;
;   Macro Description:
;
;       This macro is executed on return from an interrupt vector service
;       service routine.  Its function is to restore privileged processor
;       state, and continue thread execution. If control is returning to
;       user mode and there is a user  APC pending, then APC level interupt
;       will be requested and control is transfered to the user APC delivery
;       routine, if no higher level interrupt pending.
;
;   Arguments:
;
;       (TOS)   = previous irql
;       (TOS+4) = irq vector to eoi
;       (TOS+8 ...) = machine_state frame
;       (ebp)-> machine state frame (trap frame)
;
;--

INTERRUPT_EXIT     macro    DebugCheck
local   a

ifnb <DebugCheck>
        POLL_DEBUGGER
endif
if DBG                                          ; save current eip for
a:      mov     esi, offset a                   ; debugging bad trap frames
endif

;
; N.B. End system interrupt MUST be called with a trap frame at 8[esp].
;

ifdef __imp_Kei386EoiHelper@0
        cli

        call    _HalEndSystemInterrupt@8
        jmp     dword ptr [__imp_Kei386EoiHelper@0]

else
        cli
        call    dword ptr [__imp__HalEndSystemInterrupt@8]
        jmp     Kei386EoiHelper@0
endif
endm


;++
;
;   SPURIOUS_INTERRUPT_EXIT
;
;   Macro Description:
;
;       To exit an interrupt without performing the EOI.
;
;   Arguments:
;
;       (TOS) = machine_state frame
;       (ebp)-> machine state frame (trap frame)
;
;--

SPURIOUS_INTERRUPT_EXIT  macro
local   a
if DBG                                          ; save current eip for
a:      mov     esi, offset a                   ; debugging bad trap frames
endif


ifdef __imp_Kei386EoiHelper@0
        jmp     dword ptr [__imp_Kei386EoiHelper@0]
else
        jmp     Kei386EoiHelper@0
endif
endm

;++
;
;   ENTER_TRAPV86
;
;   Macro Description:
;
;       Construct trap frame for v86 mode traps.
;
;--

ENTER_TRAPV86 macro DRENTER,V86ENTER
        sub     esp, TsErrCode
        mov     word ptr [esp].TsErrCode + 2, 0
        mov     [esp].TsEbx, ebx
        mov     [esp].TsEax, eax
        mov     [esp].TsEbp, ebp
        mov     [esp].TsEsi, esi
        mov     [esp].TsEdi, edi
        mov     ebx, KGDT_R0_PCR
        mov     eax, KGDT_R3_DATA OR RPL_MASK
        mov     [esp].TsEcx, ecx
        mov     [esp].TsEdx, edx
if DBG
        mov     [esp].TsPreviousPreviousMode, -1
        mov     [esp].TsDbgArgMark, 0BADB0D00h
endif
        mov     fs, bx
        mov     ds, ax
        mov     es, ax
        mov     ebp, esp
        mov     eax, PCR[PcExceptionList]
        mov     [esp]+TsExceptionList, eax
        mov     eax, dr7
        cld                             ; do we really need it?
        test    eax, (NOT DR7_RESERVED_MASK)
        mov     [esp].TsDr7, eax
        jnz     Dr_&DRENTER

Dr_&V86ENTER:
endm


;
; Taken from ntos\vdm\i386\vdmtb.inc
;

FIXED_NTVDMSTATE_LINEAR_PC_AT equ 0714H
FIXED_NTVDMSTATE_LINEAR_PC_98 equ 0614H
MACHINE_TYPE_MASK equ 0ff00H
VDM_VIRTUAL_INTERRUPTS  equ 0200H

;++
;
;   EXIT_TRAPV86
;
;   Macro Description:
;
;       if UserApc is pending deliver it
;       if User Context is v86 mode
;          Exit from kernel (does not return)
;       else
;          return (expected to execute EXIT_ALL)
;--

EXIT_TRAPV86 macro
        local w, x, y, z

z:      mov     ebx, PCR[PcPrcbData+PbCurrentThread]
        mov     byte ptr [ebx]+ThAlerted, 0
        cmp     byte ptr [ebx]+ThApcState.AsUserApcPending, 0
        jne     short w

        ;
        ; Kernel exit to V86 mode
        ;

        add     esp,TsEdx
        pop     edx
        pop     ecx
        pop     eax
        test    dword ptr [ebp].TsDr7, (NOT DR7_RESERVED_MASK)
        jnz     short x
y:
        add     esp,12              ; unused fields
        pop     edi
        pop     esi
        pop     ebx
        pop     ebp
        add     esp,4               ; clear error code
        iretd

x:      xor     ebx, ebx
        mov     esi,[ebp]+TsDr0
        mov     edi,[ebp]+TsDr1
        mov     dr7, ebx            ; Turn off debug exceptions while reloading
        mov     ebx,[ebp]+TsDr2
        mov     dr0,esi
        mov     dr1,edi
        mov     dr2,ebx
        mov     esi,[ebp]+TsDr3
        mov     edi,[ebp]+TsDr6
        mov     ebx,[ebp]+TsDr7
        mov     dr3,esi
        mov     dr6,edi
        mov     dr7,ebx
        jmp     short y

w:
        ;
        ; Dispatch user mode APC
        ; The APC routine runs with interrupts on and at APC level
        ;

        RaiseIrql APC_LEVEL
        push    eax                              ; Save OldIrql
        sti

        stdCall _KiDeliverApc, <1, 0, ebp>       ; ebp - Trap frame
                                                 ; 0 - Null exception frame
                                                 ; 1 - Previous mode

        pop     ecx                              ; (TOS) = OldIrql
        LowerIrql ecx

        cli

        ;
        ; UserApc may have changed to vdm Monitor context (user flat 32)
        ; If it has cannot use the v86 only kernel exit
        ;

        test    dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK
        jnz     z

        ; Exit to do EXIT_ALL
endm


;++
;
;   PERF_GET_TIMESTAMP
;
;   Macro Description:
;
;
;   Return a time stamp that for event tracing in EDX:EAX
;
;   NOTE: This may trash ECX
; 
;   In retail, get the clock value from WmiGetCpuClock.  Else if using
;   reserved memory for logging, get cycle counter.  
; 
;--
PERF_GET_TIMESTAMP macro
        extrn   _WmiGetCpuClock:DWORD

        call    [_WmiGetCpuClock]

endm

