#include "BaseLibInternals.h"

;------------------------------------------------------------------------------
;
; Copyright (c) 2006 - 2013, Intel Corporation. All rights reserved.<BR>
; This program and the accompanying materials
; are licensed and made available under the terms and conditions of the BSD License
; which accompanies this distribution.  The full text of the license may be found at
; http://opensource.org/licenses/bsd-license.php.
;
; THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
; WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
;
; Module Name:
;
;   Thunk.asm
;
; Abstract:
;
;   Real mode thunk
;
;------------------------------------------------------------------------------

    .686p
    .model  flat,C

EXTERNDEF   C   m16Start:BYTE
EXTERNDEF   C   m16Size:WORD
EXTERNDEF   C   mThunk16Attr:WORD
EXTERNDEF   C   m16Gdt:WORD
EXTERNDEF   C   m16GdtrBase:WORD
EXTERNDEF   C   mTransition:WORD

;
; Here is the layout of the real mode stack. _ToUserCode() is responsible for
; loading all these registers from real mode stack.
;
IA32_REGS   STRUC   4t
_EDI        DD      ?
_ESI        DD      ?
_EBP        DD      ?
_ESP        DD      ?
_EBX        DD      ?
_EDX        DD      ?
_ECX        DD      ?
_EAX        DD      ?
_DS         DW      ?
_ES         DW      ?
_FS         DW      ?
_GS         DW      ?
_EFLAGS     DD      ?
_EIP        DD      ?
_CS         DW      ?
_SS         DW      ?
IA32_REGS   ENDS

    .const

;
; These are global constant to convey information to C code.
;
m16Size         DW      InternalAsmThunk16 - m16Start
mThunk16Attr    DW      _ThunkAttr - m16Start
m16Gdt          DW      _NullSegDesc - m16Start
m16GdtrBase     DW      _16GdtrBase - m16Start
mTransition     DW      _EntryPoint - m16Start

    .code

m16Start    LABEL   BYTE

SavedGdt    LABEL   FWORD
            DW      ?
            DD      ?
;------------------------------------------------------------------------------
; _BackFromUserCode() takes control in real mode after 'retf' has been executed
; by user code. It will be shadowed to somewhere in memory below 1MB.
;------------------------------------------------------------------------------
_BackFromUserCode   PROC
    ;
    ; The order of saved registers on the stack matches the order they appears
    ; in IA32_REGS structure. This facilitates wrapper function to extract them
    ; into that structure.
    ;
    push    ss
    push    cs
    DB      66h
    call    @Base                       ; push eip
@Base:
    pushf                               ; pushfd actually
    cli                                 ; disable interrupts
    push    gs
    push    fs
    push    es
    push    ds
    pushaw                              ; pushad actually
    DB      66h, 0bah                   ; mov edx, imm32
_ThunkAttr  DD      ?
    test    dl, THUNK_ATTRIBUTE_DISABLE_A20_MASK_INT_15
    jz      @1
    mov     eax, 15cd2401h              ; mov ax, 2401h & int 15h
    cli                                 ; disable interrupts
    jnc     @2
@1:
    test    dl, THUNK_ATTRIBUTE_DISABLE_A20_MASK_KBD_CTRL
    jz      @2
    in      al, 92h
    or      al, 2
    out     92h, al                     ; deactivate A20M#
@2:
    xor     ax, ax                      ; xor eax, eax
    mov     eax, ss                     ; mov ax, ss
    DB      67h
    lea     bp, [esp + sizeof (IA32_REGS)]
    ;
    ; esi's in the following 2 instructions are indeed bp in 16-bit code. Fact
    ; is "esi" in 32-bit addressing mode has the same encoding of "bp" in 16-
    ; bit addressing mode.
    ;
    mov     word ptr (IA32_REGS ptr [esi - sizeof (IA32_REGS)])._ESP, bp
    mov     ebx, (IA32_REGS ptr [esi - sizeof (IA32_REGS)])._EIP
    shl     ax, 4                       ; shl eax, 4
    add     bp, ax                      ; add ebp, eax
    DB      66h, 0b8h                   ; mov eax, imm32
SavedCr4    DD      ?
    mov     cr4, eax
    DB      66h
    lgdt    fword ptr cs:[edi + (SavedGdt - @Base)]
    DB      66h, 0b8h                   ; mov eax, imm32
SavedCr0    DD      ?
    mov     cr0, eax
    DB      0b8h                        ; mov ax, imm16
SavedSs     DW      ?
    mov     ss, eax
    DB      66h, 0bch                   ; mov esp, imm32
SavedEsp    DD      ?
    DB      66h
    retf                                ; return to protected mode
_BackFromUserCode   ENDP

_EntryPoint DD      _ToUserCode - m16Start
            DW      8h
_16Idtr     FWORD   (1 SHL 10) - 1
_16Gdtr     LABEL   FWORD
            DW      GdtEnd - _NullSegDesc - 1
_16GdtrBase DD      _NullSegDesc

;------------------------------------------------------------------------------
; _ToUserCode() takes control in real mode before passing control to user code.
; It will be shadowed to somewhere in memory below 1MB.
;------------------------------------------------------------------------------
_ToUserCode PROC
    mov     edx, ss
    mov     ss, ecx                     ; set new segment selectors
    mov     ds, ecx
    mov     es, ecx
    mov     fs, ecx
    mov     gs, ecx
    mov     cr0, eax                    ; real mode starts at next instruction
                                        ;  which (per SDM) *must* be a far JMP.
    DB      0eah
_RealAddr DW 0,0                       ; filled in by InternalAsmThunk16

    mov     cr4, ebp
    mov     ss, esi                     ; set up 16-bit stack segment
    xchg    sp, bx                      ; set up 16-bit stack pointer

;   mov     bp, [esp + sizeof(IA32_REGS)
    DB      67h
    mov     ebp, [esp + sizeof(IA32_REGS)] ; BackFromUserCode address from stack

;   mov     cs:[bp + (SavedSs - _BackFromUserCode)], dx
    mov     cs:[esi + (SavedSs - _BackFromUserCode)], edx

;   mov     cs:[bp + (SavedEsp - _BackFromUserCode)], ebx
    DB      2eh, 66h, 89h, 9eh
    DW      SavedEsp - _BackFromUserCode

;   lidt    cs:[bp + (_16Idtr - _BackFromUserCode)]
    DB      2eh, 66h, 0fh, 01h, 9eh
    DW      _16Idtr - _BackFromUserCode

    popaw                               ; popad actually
    pop     ds
    pop     es
    pop     fs
    pop     gs
    popf                                ; popfd
    DB      66h                         ; Use 32-bit addressing for "retf" below
    retf                                ; transfer control to user code
_ToUserCode ENDP

_NullSegDesc    DQ      0
_16CsDesc       LABEL   QWORD
                DW      -1
                DW      0
                DB      0
                DB      9bh
                DB      8fh             ; 16-bit segment, 4GB limit
                DB      0
_16DsDesc       LABEL   QWORD
                DW      -1
                DW      0
                DB      0
                DB      93h
                DB      8fh             ; 16-bit segment, 4GB limit
                DB      0
GdtEnd          LABEL   QWORD

;------------------------------------------------------------------------------
; IA32_REGISTER_SET *
; EFIAPI
; InternalAsmThunk16 (
;   IN      IA32_REGISTER_SET         *RegisterSet,
;   IN OUT  VOID                      *Transition
;   );
;------------------------------------------------------------------------------
InternalAsmThunk16  PROC    USES    ebp ebx esi edi ds  es  fs  gs
    mov     esi, [esp + 36]             ; esi <- RegSet, the 1st parameter
    movzx   edx, (IA32_REGS ptr [esi])._SS
    mov     edi, (IA32_REGS ptr [esi])._ESP
    add     edi, - (sizeof (IA32_REGS) + 4) ; reserve stack space
    mov     ebx, edi                    ; ebx <- stack offset
    imul    eax, edx, 16                ; eax <- edx * 16
    push    sizeof (IA32_REGS) / 4
    add     edi, eax                    ; edi <- linear address of 16-bit stack
    pop     ecx
    rep     movsd                       ; copy RegSet
    mov     eax, [esp + 40]             ; eax <- address of transition code
    mov     esi, edx                    ; esi <- 16-bit stack segment
    lea     edx, [eax + (SavedCr0 - m16Start)]
    mov     ecx, eax
    and     ecx, 0fh
    shl     eax, 12
    lea     ecx, [ecx + (_BackFromUserCode - m16Start)]
    mov     ax, cx
    stosd                               ; [edi] <- return address of user code
    add     eax, _RealAddr + 4 - _BackFromUserCode
    mov     dword ptr [edx + (_RealAddr - SavedCr0)], eax
    sgdt    fword ptr [edx + (SavedGdt - SavedCr0)]
    sidt    fword ptr [esp + 36]        ; save IDT stack in argument space
    mov     eax, cr0
    mov     [edx], eax                  ; save CR0 in SavedCr0
    and     eax, 7ffffffeh              ; clear PE, PG bits
    mov     ebp, cr4
    mov     [edx + (SavedCr4 - SavedCr0)], ebp
    and     ebp, NOT 30h                ; clear PAE, PSE bits
    push    10h
    pop     ecx                         ; ecx <- selector for data segments
    lgdt    fword ptr [edx + (_16Gdtr - SavedCr0)]
    pushfd                              ; Save df/if indeed
    call    fword ptr [edx + (_EntryPoint - SavedCr0)]
    popfd
    lidt    fword ptr [esp + 36]        ; restore protected mode IDTR
    lea     eax, [ebp - sizeof (IA32_REGS)] ; eax <- the address of IA32_REGS
    ret
InternalAsmThunk16  ENDP

    END