;++
;
; Copyright (c) 1991  Microsoft Corporation
;
; Module Name:
;
;     xxbiosa.asm
;
; Abstract:
;
;     This implements the necessary code to put the processor into
;     V86 mode, make a BIOS call, and return safely to protected mode.
;
; Author:
;
;     John Vert (jvert) 29-Oct-1991
;
; Environment:
;
;     Kernel mode
;
; Notes:
;
;     This module is intended for use in panic situations, such as a bugcheck.
;     As a result, we cannot rely on the integrity of the system so we must
;     handle everything ourselves.  Notably, we must map our own memory by
;     adding our own page tables and PTEs.
;
;     We also cannot call KeBugCheck when we notice something has gone wrong.
;
; Revision History:
;
;--
.386p
        .xlist
include hal386.inc
include callconv.inc                    ; calling convention macros
include i386\kimacro.inc
        .list

        extrn   _DbgPrint:proc
        EXTRNP  _DbgBreakPoint,0,IMPORT
        EXTRNP  Kei386EoiHelper,0,IMPORT

        public  _HalpRealModeStart
        public  _HalpRealModeEnd
;
; 32-bit override
;
OVERRIDE        equ     66h

;
; Reginfo structure
;

RegInfo struc
RiSegSs         dd 0
RiEsp           dd 0
RiEFlags        dd 0
RiSegCs         dd 0
RiEip           dd 0
RiTrapFrame     dd 0
RiCsLimit       dd 0
RiCsBase        dd 0
RiCsFlags       dd 0
RiSsLimit       dd 0
RiSsBase        dd 0
RiSsFlags       dd 0
RiPrefixFlags   dd 0
RegInfo ends
REGINFOSIZE     EQU 52

INT_NN_OPCODE   EQU     0CDH

        page ,132
_DATA   SEGMENT  DWORD PUBLIC 'DATA'

;
; In order to return to the calling function after we've trapped out of
; V86 mode, we save our ESP value here.
;
HalpSavedEsp    dd      0

_DATA   ENDS


_TEXT   SEGMENT DWORD PUBLIC 'CODE'
        ASSUME  DS:NOTHING, ES:NOTHING, SS:FLAT, FS:NOTHING, GS:NOTHING

if DBG
        page ,132
        subttl "Processing Exception occurred in ABIOS code"
;++
; VOID
; KiAbiosException (
;    VOID
;    )
;
; Routine Description:
;
;    This routine is called after an exception being detected
;    in ABIOS ROM code.  The system will switch 16 stack to 32 bit
;    stack and bugcheck.
;
;    N.B.  In fact this routine is simply used to resolve a reference
;          to KiAbiosException routine in the Kimacro.inc ENTER_TRAP
;          macro.
;
;
; Arguments:
;
;    None.
;
; Return value:
;
;    system stopped.
;
;--
        public  _KiAbiosException
_KiAbiosException proc
_Ki16BitStackException:
        ret

_KiAbiosException endp

endif


;++
; ULONG
; HalpBorrowTss (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks if the current TSS has IO MAP space.
;    if yes, it simply returns.  Otherwise, it switches to use
;    the regular TSS.
;
; Arguments:
;
;    None.
;
; Return value:
;
;    Return original TSS selector if the regular Tss is borrowed by us.
;
;--
cPublicProc _HalpBorrowTss, 0
cPublicFpo 0, 0

        xor     eax, eax
        str     ax
        mov     edx, PCR[PcGdt]
        add     edx, eax                        ; (edx)->Gdt Entry of current
                                                ;        TSS
        xor     ecx, ecx
        mov     cl, [edx].KgdtLimitHi
        shl     ecx, 16
        mov     cx, [edx].KgdtLimitLow          ; (ecx) = TSS limit
        cmp     ecx, 2000H                      ; Is Io map space available?
        ja      short Hbt99                     ; if a, yes, return

        sub     edx, eax                        ; (edx)->GDT table
        mov     ch, [edx+KGDT_TSS+KgdtBaseHi]
        mov     cl, [edx+KGDT_TSS+KgdtBaseMid]
        shl     ecx, 16
        mov     cx, [edx+KGDT_TSS+KgdtBaseLow]
        mov     PCR[PcTss], ecx
        mov     ecx, KGDT_TSS                   ; switch to use regular TSS
        mov     byte ptr [edx+KGDT_TSS+5], 089h ; 32bit, dpl=0, present, TSS32,
                                                ; not busy.
        ltr     cx
        stdRET  _HalpBorrowTss                  ; (eax) = Original TSS sel

Hbt99:
        xor     eax, eax                        ; No TSS swapped
        stdRET  _HalpBorrowTss

stdENDP _HalpBorrowTss


;++
; VOID
; HalpReturnTss (
;    ULONG TssSelector
;    )
;
; Routine Description:
;
;    This routine switches the current TSS from regular TSS back to
;    the panic TSS (NMI TSS or Double fault TSS).
;
; Arguments:
;
;    TssSelector - the TSS selector to return to.
;
; Return value:
;
;    None.
;
;--
cPublicProc _HalpReturnTss, 1
cPublicFpo 1, 0

        mov     edx, PCR[PcGdt]                 ; (edx)-> Gdt table
        mov     eax, [esp + 4]
        and     eax, 0FFFFh                     ; (eax)= New TSS sel
        add     edx, eax                        ; (edx)->Gdt Entry of new TSS

        mov     ch, [edx+KgdtBaseHi]
        mov     cl, [edx+KgdtBaseMid]
        shl     ecx, 16
        mov     cx, [edx+KgdtBaseLow]
        mov     PCR[PcTss], ecx
        mov     byte ptr [edx+5], 089h          ; 32bit, dpl=0, present, TSS32,
        ltr     ax
        stdRET  _HalpReturnTss                  ; return and clear stack

stdENDP _HalpReturnTss

;++
;
; VOID
; HalpBiosCall
;     VOID
;     )
;
; Routine Description:
;
;     This routine completes the transition to real mode, calls BIOS, and
;     returns to protected mode.
;
; Arguments:
;
;     None.
;
; Return Value:
;
;     None.
;
;--
;;ALIGN 4096
cPublicProc _HalpBiosCall   ,0

        push    ebp
        mov     ebp, esp
        pushfd
        push    edi
        push    esi
        push    ebx
        push    ds
        push    es
        push    fs
        push    gs
        push    offset FLAT:HbcProtMode         ; address where we will start
                                                ; protected mode again once
                                                ; V86 has completed.
        mov     HalpSavedEsp, esp

        mov     eax, cr0                        ; make sure alignment
        and     eax, not CR0_AM                 ; checks are disabled
        mov     cr0, eax

;
; Create space for the V86 trap frame and update the ESP0 value in the TSS
; to use this space.  We will set this up just below our current stack pointer.
; The stuff we push on the stack after we set ESP0 is irrelevant once we
; make it to V86 mode, so it's ok to blast it.
;
        mov     esi, fs:PcTss                   ; (esi) -> TSS
        mov     eax, esp
        sub     eax, NPX_FRAME_LENGTH           ; skip FP save area
        mov     [esi]+TssEsp0, eax

        push    dword ptr 0h                    ; V86 GS
        push    dword ptr 0h                    ; V86 FS
        push    dword ptr 0h                    ; V86 DS
        push    dword ptr 0h                    ; V86 ES
        push    dword ptr 2000h                 ; V86 SS

;
; We calculate the V86 sp by adding the difference between the linear address
; of the V86 ip (HbcReal) and the linear address of the V86 sp (HbcV86Stack)
; to the offset of the V86 ip (HbcReal & 0xfff).
;

        mov     eax, offset FLAT:HbcV86Stack-4
        sub     eax, offset FLAT:HbcReal
        mov     edx, offset HbcReal        
        and     edx, 0fffh
        add     eax, edx
        push    eax                              ; V86 esp

        pushfd
        or      dword ptr [esp], EFLAGS_V86_MASK; V86 eflags
        or      [esp], 03000h                   ; Give IOPL3
        push    dword ptr 2000h                 ; V86 CS
        mov     eax, offset HbcReal
        and     eax, 0fffh

        push    edx                             ; V86-mode EIP is offset
                                                ; into CS.
        iretd

_HalpRealModeStart      label   byte

HbcReal:
        db      OVERRIDE        ; make mov 32-bits
        mov     eax, 12h        ; 640x480x16 colors
        int     10h

        db      0c4h, 0c4h      ; BOP to indicate V86 mode is done.

;
; V86-mode stack
;
align 4
        db      2048 dup(0)
HbcV86Stack:

_HalpRealModeEnd        label   byte

HbcProtMode:
;
; We are back from V86 mode, so restore everything we saved and we are done.
;
        pop     gs
        pop     fs
        pop     es
        pop     ds
        pop     ebx
        pop     esi
        pop     edi
        popfd
        pop     ebp
        stdRET    _HalpBiosCall

        public  _HalpBiosCallEnd
_HalpBiosCallEnd label byte



_HalpBiosCall   endp


        subttl "HAL General Protection Fault"
;++
;
; Routine Description:
;
;    Handle General protection fault.
;
;    This fault handler is used by the HAL for V86 mode faults only.
;    It should NEVER be used except when running in V86 mode.  The HAL
;    replaces the general-purpose KiTrap0D handler entry in the IDT with
;    this routine.  This allows us to emulate V86-mode instructions which
;    cause a fault.  After we return from V86 mode, we can restore the
;    KiTrap0D handler in the IDT.
;
; Arguments:
;
;    At entry, the saved CS:EIP point to the faulting instruction
;    Error code (whose value depends on detected condition) is provided.
;
; Return value:
;
;    None
;
;--
        ASSUME  DS:FLAT, SS:NOTHING, ES:FLAT

        ENTER_DR_ASSIST Htd_a, Htd_t, NoAbiosAssist
cPublicProc _HalpTrap0D     ,0

        ENTER_TRAP Htd_a, Htd_t

;
;   Did the trap occur in V86 mode?  If not, something is completely messed up.
;
        test    dword ptr [ebp]+TsEFlags,00020000H
        jnz     Ht0d10

;
; The trap was not from V86 mode, so something is very wrong.  We cannot
; BugCheck, since we are probably already in a BugCheck.  So just stop.
;

if DBG
_TEXT segment
MsgBadHalTrap   db 'HAL: Trap0D while not in V86 mode',0ah,0dh,0
_TEXT ends

        push    offset FLAT:MsgBadHalTrap
        call    _DbgPrint
        add     esp,4
        stdCall   _DbgBreakPoint
endif
;
; We can't bugcheck, so just commit suicide.  Maybe we should reboot?
;
        jmp     $

Ht0d10:
        stdCall   HalpDispatchV86Opcode
        SPURIOUS_INTERRUPT_EXIT
stdENDP _HalpTrap0d

        subttl "HAL Invalid Opcode Fault"
;++
;
; Routine Description:
;
;    Handle invalid opcode fault
;
;    This fault handler is used by the HAL to indicate when V86 mode
;    execution is finished.  The V86 code attempts to execute an invalid
;    instruction (BOP) when it is done, and that brings us here.
;    This routine just removes the trap frame from the stack and does
;    a RET.  Note that this assumes that ESP0 in the TSS has been set
;    up to point to the top of the stack that we want to be running on
;    when the V86 call has completed.
;
;    This should NEVER be used except when running in V86 mode.  The HAL
;    replaces the general-purpose KiTrap06 handler entry in the IDT with
;    this routine.  It also sets up ESP0 in the TSS appropriately.  After
;    the V86 call has completed, it restores these to their previous values.
;
; Arguments:
;
;    At entry, the saved CS:EIP point to the faulting instruction
;    Error code (whose value depends on detected condition) is provided.
;
; Return value:
;
;    None
;
;--
        ASSUME  DS:FLAT, SS:NOTHING, ES:FLAT

cPublicProc _HalpTrap06     ,0
        mov     eax,KGDT_R3_DATA OR RPL_MASK
        mov     ds,ax
        mov     es,ax
        mov     esp, HalpSavedEsp
        ret

stdENDP _HalpTrap06

        subttl "Instruction Emulation Dispatcher"
;++
;
;   Routine Description:
;
;       This routine dispatches to the opcode specific emulation routine,
;       based on the first byte of the opcode.  Two byte opcodes, and prefixes
;       result in another level of dispatching, from the handling routine.
;
;       This code blatantly stolen from ke\i386\instemul.asm
;
;   Arguments:
;
;       ebp = pointer to trap frame
;
;   Returns:
;
;       Nothing
;

cPublicProc HalpDispatchV86Opcode ,0

RI      equ     [ebp - REGINFOSIZE]
        push    ebp
        mov     ebp,esp
        sub     esp,REGINFOSIZE
        push    esi
        push    edi

        ; Initialize RegInfo

        mov     esi,[ebp]
        mov     RI.RiTrapFrame,esi
        movzx   eax,word ptr [esi].TsHardwareSegSs
        mov     RI.RiSegSs,eax
        mov     eax,[esi].TsHardwareEsp
        mov     RI.RiEsp,eax
        mov     eax,[esi].TsEFlags
        mov     RI.RiEFlags,eax
        movzx   eax,word ptr [esi].TsSegCs
        mov     RI.RiSegCs,eax
        mov     eax,[esi].TsEip
        mov     RI.RiEip,eax

        xor     eax,eax
        mov     RI.RiPrefixFlags,eax
        lea     esi,RI

;
; Convert CS to a linear address
;

        mov     eax,[esi].RiSegCs
        shl     eax,4
        mov     [esi].RiCsBase,eax
        mov     [esi].RiCsLimit,0FFFFh
        mov     [esi].RiCsFlags,0

        mov     edi,RI.RiEip
        cmp     edi,RI.RiCsLimit
        ja      doerr

        add     edi,RI.RiCsBase
        mov     dl, [edi]                               ; get faulting opcode
        cmp     dl, INT_NN_OPCODE
        je      short @f

        stdCall HalpOpcodeInvalid
        jmp     short doerr

@@:
        stdCall HalpOpcodeINTnn
        test    eax,0FFFFh
        jz      do20

        mov     edi,RI.RiTrapFrame
        mov     eax,RI.RiEip                            ; advance eip
        mov     [edi].TsEip,eax
        mov     eax,1
do20:   pop     edi
        pop     esi
        mov     esp,ebp
        pop     ebp
        ret

doerr:  xor     eax,eax
        jmp     do20
stdENDP HalpDispatchV86Opcode

        page   ,132
        subttl "Invalid Opcode Handler"
;++
;
;   Routine Description:
;
;       This routine handles invalid opcodes.  It prints the invalid
;       opcode message, and breaks into the kernel debugger.
;
;   Arguments:
;
;       esi = address of reg info
;       edx = opcode
;
;   Returns:
;
;       nothing
;

_TEXT segment
HalpMsgInvalidOpcode db 'HAL: An invalid V86 opcode was encountered at '
                     db 'address %x:%x',0ah, 0dh, 0
_TEXT ends

cPublicProc HalpOpcodeInvalid ,0

        push    [esi].RiEip
        push    [esi].RiSegCs
        push    offset FLAT:HalpMsgInvalidOpcode
        call    _DbgPrint               ; display invalid opcode message
        add     esp,12
        int     3
        xor     eax,eax
        stdRET    HalpOpcodeInvalid

stdENDP HalpOpcodeInvalid

        subttl "INTnn Opcode Handler"
;++
;
;   Routine Description:
;
;       This routine emulates an INTnn opcode.  It retrieves the handler
;       from the IVT, pushes the current cs:ip and flags on the stack,
;       and dispatches to the handler.
;
;   Arguments:
;
;       esi = address of reg info
;       edx = opcode
;
;   Returns:
;
;       Current CS:IP on user stack
;       RiCs:RiEip -> handler from IVT
;

cPublicProc HalpOpcodeINTnn ,0

        push    ebp
        push    edi
        push    ebx

;
; Convert SS to linear address
;
        mov     eax,[esi].RiSegSs
        shl     eax,4
        mov     [esi].RiSsBase,eax
        mov     [esi].RiSsLimit,0FFFFh
        mov     [esi].RiSsFlags,0

        inc     [esi].RiEip                     ; point to int #
        mov     edi,[esi].RiEip
        cmp     edi,[esi].RiCsLimit
        ja      oinerr

        add     edi,[esi].RiCsBase
        movzx   ecx,byte ptr [edi]              ; get int #
        inc     [esi].RiEip                     ; inc past end of instruction
        stdCall   HalpPushInt
        test    eax,0FFFFh
        jz      oin20                           ; error!
;
;  Note:  Some sort of check for BOP should go here, or in push int.
;

        mov     ebp,[esi].RiTrapFrame
        mov     eax,[esi].RiSegSs
        mov     [ebp].TsHardwareSegSs,eax
        mov     eax,[esi].RiEsp
        mov     [ebp].TsHardwareEsp,eax
        mov     eax,[esi].RiSegCs
        mov     [ebp].TsSegCs,eax
        mov     eax,[esi].RiEFlags
        mov     [ebp].TsEFlags,eax
        mov     eax,1
oin20:  pop     ebx
        pop     edi
        pop     ebp
        stdRET    HalpOpcodeINTnn

oinerr: xor     eax,eax
        jmp     oin20

stdENDP HalpOpcodeINTnn

        page   ,132
        subttl "Push Interrupt frame on user stack"
;++
;
;   Routine Description:
;
;       This routine pushes an interrupt frame on the user stack
;
;   Arguments:
;
;       ecx = interrupt #
;       esi = address of reg info
;   Returns:
;
;       interrupt frame pushed on stack
;       reg info updated
;
cPublicProc HalpPushInt ,0
        push    ebx

        mov     edx,[esi].RiEsp
        mov     ebx,[esi].RiSsBase
        and     edx,0FFFFh              ; only use a 16 bit sp
        sub     dx,2
        mov     ax,word ptr [esi].RiEFlags
        mov     [ebx+edx],ax            ; push flags
        sub     dx,2
        mov     ax,word ptr [esi].RiSegCs
        mov     [ebx+edx],ax            ; push cs
        sub     dx,2
        mov     ax,word ptr [esi].RiEip
        mov     [ebx+edx],ax            ; push ip
        mov     eax,[ecx*4]             ; get new cs:ip value
        push    eax
        movzx   eax,ax
        mov     [esi].RiEip,eax
        pop     eax
        shr     eax,16
        mov     [esi].RiSegCs,eax
        mov     word ptr [esi].RiEsp,dx

;
; Convert CS to a linear address
;

        mov     eax,[esi].RiSegCs
        shl     eax,4
        mov     [esi].RiCsBase,eax
        mov     [esi].RiCsLimit,0FFFFh
        mov     [esi].RiCsFlags,0

        mov     eax,1                   ; success
pi80:   pop     ebx
        stdRET    HalpPushInt
stdENDP HalpPushInt


_TEXT   ends
        end