;
; Copyright (c) 2014, 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:
;
;    Thunk64To32.asm
;
; Abstract:
;
;   This is the assembly code to transition from long mode to compatibility mode to execute 32-bit code and then
;   transit back to long mode.
;
;-------------------------------------------------------------------------------
    .code
;----------------------------------------------------------------------------
; Procedure:    AsmExecute32BitCode
;
; Input:        None
;
; Output:       None
;
; Prototype:    UINT32
;               AsmExecute32BitCode (
;                 IN UINT64           Function,
;                 IN UINT64           Param1,
;                 IN UINT64           Param2,
;                 IN IA32_DESCRIPTOR  *InternalGdtr
;                 );
;
;
; Description:  A thunk function to execute 32-bit code in long mode.
;
;----------------------------------------------------------------------------
AsmExecute32BitCode    PROC
    ;
    ; save IFLAG and disable it
    ;
    pushfq
    cli

    ;
    ; save orignal GDTR and CS
    ;
    mov     rax, ds
    push    rax
    mov     rax, cs
    push    rax
    sub     rsp, 10h
    sgdt    fword ptr [rsp]
    ;
    ; load internal GDT
    ;
    lgdt    fword ptr [r9]
    ;
    ; Save general purpose register and rflag register
    ;
    pushfq
    push    rdi
    push    rsi
    push    rbp
    push    rbx

    ;
    ; save CR3
    ;
    mov     rax, cr3
    mov     rbp, rax

    ;
    ; Prepare the CS and return address for the transition from 32-bit to 64-bit mode
    ;
    mov     rax, 10h              ; load long mode selector
    shl     rax, 32
    mov     r9, OFFSET ReloadCS   ;Assume the ReloadCS is under 4G
    or      rax, r9
    push    rax
    ;
    ; Save parameters for 32-bit function call
    ;
    mov     rax, r8
    shl     rax, 32
    or      rax, rdx
    push    rax
    ;
    ; save the 32-bit function entry and the return address into stack which will be
    ; retrieve in compatibility mode.
    ;
    mov     rax, OFFSET ReturnBack   ;Assume the ReloadCS is under 4G
    shl     rax, 32
    or      rax, rcx
    push    rax

    ;
    ; let rax save DS
    ;
    mov     rax, 018h

    ;
    ; Change to Compatible Segment
    ;
    mov     rcx, 08h               ; load compatible mode selector
    shl     rcx, 32
    mov     rdx, OFFSET Compatible ; assume address < 4G
    or      rcx, rdx
    push    rcx
    retf

Compatible:
    ; reload DS/ES/SS to make sure they are correct referred to current GDT
    mov     ds, ax
    mov     es, ax
    mov     ss, ax

    ;
    ; Disable paging
    ;
    mov     rcx, cr0
    btc     ecx, 31
    mov     cr0, rcx
    ;
    ; Clear EFER.LME
    ;
    mov     ecx, 0C0000080h
    rdmsr
    btc     eax, 8
    wrmsr

; Now we are in protected mode
    ;
    ; Call 32-bit function. Assume the function entry address and parameter value is less than 4G
    ;
    pop    rax                 ; Here is the function entry
    ;
    ; Now the parameter is at the bottom of the stack,  then call in to IA32 function.
    ;
    jmp   rax
ReturnBack:
    mov   ebx, eax             ; save return status
    pop   rcx                  ; drop param1
    pop   rcx                  ; drop param2

    ;
    ; restore CR4
    ;
    mov     rax, cr4
    bts     eax, 5
    mov     cr4, rax

    ;
    ; restore CR3
    ;
    mov     eax, ebp
    mov     cr3, rax

    ;
    ; Set EFER.LME to re-enable ia32-e
    ;
    mov     ecx, 0C0000080h
    rdmsr
    bts     eax, 8
    wrmsr
    ;
    ; Enable paging
    ;
    mov     rax, cr0
    bts     eax, 31
    mov     cr0, rax
; Now we are in compatible mode

    ;
    ; Reload cs register
    ;
    retf
ReloadCS:
    ;
    ; Now we're in Long Mode
    ;
    ;
    ; Restore C register and eax hold the return status from 32-bit function.
    ; Note: Do not touch rax from now which hold the return value from IA32 function
    ;
    mov     eax, ebx ; put return status to EAX
    pop     rbx
    pop     rbp
    pop     rsi
    pop     rdi
    popfq
    ;
    ; Switch to orignal GDT and CS. here rsp is pointer to the orignal GDT descriptor.
    ;
    lgdt    fword ptr[rsp]
    ;
    ; drop GDT descriptor in stack
    ;
    add     rsp, 10h
    ;
    ; switch to orignal CS and GDTR
    ;
    pop     r9                 ; get  CS
    shl     r9,  32            ; rcx[32..47] <- Cs
    mov     rcx, OFFSET @F
    or      rcx, r9
    push    rcx
    retf
@@:
    ;
    ; Reload original DS/ES/SS
    ;
    pop     rcx
    mov     ds, rcx
    mov     es, rcx
    mov     ss, rcx

    ;
    ; Restore IFLAG
    ;
    popfq

    ret
AsmExecute32BitCode   ENDP

    END