;------------------------------------------------------------------------------
;
; Copyright (c) 2016 - 2019, Intel Corporation. All rights reserved.<BR>
; SPDX-License-Identifier: BSD-2-Clause-Patent
;
; Abstract:
;
;   Switch the stack from temporary memory to permanent memory.
;
;------------------------------------------------------------------------------

    SECTION .text

extern ASM_PFX(SwapStack)
extern ASM_PFX(FeaturePcdGet (PcdFspSaveRestorePageTableEnable))

; Page table related bits in CR0/CR4/EFER
%define CR0_PG_MASK          0x80010000  ; CR0.PG and CR0.WP
%define CR4_PG_MASK          0x10B0      ; CR4.PSE, CR4.PAE, CR4.PGE and CR4.LA57
%define EFER_PG_MASK         0x800       ; EFER.NXE

;------------------------------------------------------------------------------
; UINT32
; EFIAPI
; Pei2LoaderSwitchStack (
;   VOID
;   )
;------------------------------------------------------------------------------
global ASM_PFX(Pei2LoaderSwitchStack)
ASM_PFX(Pei2LoaderSwitchStack):
    xor     eax, eax
    jmp     ASM_PFX(FspSwitchStack)

;------------------------------------------------------------------------------
; UINT32
; EFIAPI
; Loader2PeiSwitchStack (
;   VOID
;   )
;------------------------------------------------------------------------------
global ASM_PFX(Loader2PeiSwitchStack)
ASM_PFX(Loader2PeiSwitchStack):
    jmp     ASM_PFX(FspSwitchStack)

;------------------------------------------------------------------------------
; UINT32
; EFIAPI
; FspSwitchStack (
;   VOID
;   )
;------------------------------------------------------------------------------
global ASM_PFX(FspSwitchStack)
ASM_PFX(FspSwitchStack):
    ; Save current contexts
    push    eax
    pushfd
    cli
    pushad

    ;
    ; Allocate 4x4 bytes on the stack.
    ;
    sub     esp, 16
    cmp     byte [dword ASM_PFX(FeaturePcdGet (PcdFspSaveRestorePageTableEnable))], 0
    jz      SkipPagetableSave

    add     esp, 16
    ; Save EFER MSR lower 32 bits
    push   ecx
    push   eax
    mov    ecx, 0xC0000080
    rdmsr
    mov    edx, eax
    pop    eax
    pop    ecx
    push   edx

    ; Save CR registers
    mov    eax, cr4
    push   eax
    mov    eax, cr3
    push   eax
    mov    eax, cr0
    push   eax
SkipPagetableSave:

    sub     esp, 8
    sidt    [esp]

    ; Load new stack
    push    esp
    call    ASM_PFX(SwapStack)
    mov     esp, eax

    ; Restore previous contexts
    lidt    [esp]
    add     esp, 8

    cmp     byte [dword ASM_PFX(FeaturePcdGet (PcdFspSaveRestorePageTableEnable))], 0
    jz      SkipPagetableRestore
    ; [esp]    stores new cr0
    ; [esp+4]  stores new cr3
    ; [esp+8]  stores new cr4
    ; [esp+12] stores new Efer
    ;
    ; When new EFER.NXE == 1, the restore flow is: EFER --> CRx
    ; Otherwise: CRx --> EFER
    ; When new CR0.PG == 1, the restore flow for CRx is: CR3 --> CR4 --> CR0
    ; Otherwise, the restore flow is: CR0 --> CR3 --> CR4
    ;
    ; If NXE bit is changed to 1, change NXE before CR register
    ; This is because Nx bit in page table entry in new CR3 will be invalid
    ; if updating CR3 before EFER MSR.
    ;
    mov eax, [esp+12]
    bt  eax, 11
    jnc SkipEferLabel1

    ; Restore EFER MSR
    mov    ecx, 0xC0000080
    rdmsr
    and    eax, ~EFER_PG_MASK
    mov    ebx, [esp+12]
    and    ebx, EFER_PG_MASK
    or     eax, ebx
    wrmsr

SkipEferLabel1:

    ;
    ; if new cr0 is to disable page table, change CR0 before CR3/CR4
    ;
    mov     eax, [esp]
    bt      eax, 31
    jc      SkipCr0Label1

    ; Restore CR0
    mov     edx, cr0
    and     edx, ~CR0_PG_MASK
    mov     eax, [esp]
    and     eax, CR0_PG_MASK
    or      edx, eax
    mov     cr0, edx

SkipCr0Label1:

    ; Restore CR3/CR4
    mov     eax, [esp+4]
    mov     cr3, eax

    mov     edx, cr4
    and     edx, ~CR4_PG_MASK
    mov     eax, [esp+8]
    and     eax, CR4_PG_MASK
    or      edx, eax
    mov     cr4, edx

    ;
    ; if new cr0 is to enable page table, change CR0 after CR3/CR4
    ;
    mov     eax, [esp]
    bt      eax, 31
    jnc     SkipCr0Label2

    ; Restore CR0
    mov     edx, cr0
    and     edx, ~CR0_PG_MASK
    mov     eax, [esp]
    and     eax, CR0_PG_MASK
    or      edx, eax
    mov     cr0, edx

SkipCr0Label2:
    ;
    ; If NXE bit is changed to 0, change NXE after than CR regiser
    ;
    mov eax, [esp+12]
    bt  eax, 11
    jc SkipEferLabel2

    ; Restore EFER MSR
    mov    ecx, 0xC0000080
    rdmsr
    and    eax, ~EFER_PG_MASK
    mov    ebx, [esp+12]
    and    ebx, EFER_PG_MASK
    or     eax, ebx
    wrmsr

SkipEferLabel2:
SkipPagetableRestore:

    ; pop page table related registers.
    add     esp, 16

    popad
    popfd
    add     esp, 4
    ret

