;; @file
;  Provide FSP API entry points.
;
; Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
; SPDX-License-Identifier: BSD-2-Clause-Patent
;;

    SECTION .text

;
; Following are fixed PCDs
;
extern   ASM_PFX(PcdGet32(PcdTemporaryRamBase))
extern   ASM_PFX(PcdGet32(PcdFspTemporaryRamSize))
extern   ASM_PFX(PcdGet8 (PcdFspHeapSizePercentage))
extern   ASM_PFX(FeaturePcdGet (PcdFspSaveRestorePageTableEnable))

struc FSPM_UPD_COMMON
    ; FSP_UPD_HEADER {
    .FspUpdHeader:            resd    8
    ; }
    ; FSPM_ARCH_UPD {
    .Revision:                resb    1
    .Reserved:                resb    3
    .NvsBufferPtr:            resd    1
    .StackBase:               resd    1
    .StackSize:               resd    1
    .BootLoaderTolumSize:     resd    1
    .BootMode:                resd    1
    .Reserved1:               resb    8
    ; }
    .size:
endstruc

struc FSPM_UPD_COMMON_FSP24
    ; FSP_UPD_HEADER {
    .FspUpdHeader:              resd  8
    ; }
    ; FSPM_ARCH2_UPD {
    .Revision:                  resb  1
    .Reserved:                  resb  3
    .Length                     resd  1
    .NvsBufferPtr               resq  1
    .StackBase:                 resq  1
    .StackSize:                 resq  1
    .BootLoaderTolumSize:       resd  1
    .BootMode:                  resd  1
    .FspEventHandler            resq  1
    .Reserved1:                 resb 16
    ; }
    .size:
endstruc

;
; Following functions will be provided in C
;
extern ASM_PFX(SecStartup)
extern ASM_PFX(FspApiCommon)

;
; Following functions will be provided in PlatformSecLib
;
extern ASM_PFX(AsmGetFspBaseAddress)
extern ASM_PFX(AsmGetFspInfoHeader)
extern ASM_PFX(FspMultiPhaseMemInitApiHandler)

STACK_SAVED_EAX_OFFSET       EQU   4 * 7 ; size of a general purpose register * eax index
API_PARAM1_OFFSET            EQU   44h  ; ApiParam1 [ sub esp,8 + push cr0/cr3/cr4/EFER + pushad + pushfd + push eax + call]
FSP_HEADER_IMGBASE_OFFSET    EQU   1Ch
FSP_HEADER_CFGREG_OFFSET     EQU   24h

;----------------------------------------------------------------------------
; FspMemoryInit API
;
; This FSP API is called after TempRamInit and initializes the memory.
;
;----------------------------------------------------------------------------
global ASM_PFX(FspMemoryInitApi)
ASM_PFX(FspMemoryInitApi):
  mov    eax,  3 ; FSP_API_INDEX.FspMemoryInitApiIndex
  jmp    ASM_PFX(FspApiCommon)

;----------------------------------------------------------------------------
; FspMultiPhaseMemoryInitApi API
;
; This FSP API provides multi-phase Memory initialization, which brings greater
; modularity beyond the existing FspMemoryInit() API.
; Increased modularity is achieved by adding an extra API to FSP-M.
; This allows the bootloader to add board specific initialization steps throughout
; the MemoryInit flow as needed.
;
;----------------------------------------------------------------------------
global ASM_PFX(FspMultiPhaseMemoryInitApi)
ASM_PFX(FspMultiPhaseMemoryInitApi):
  mov    eax,  8 ; FSP_API_INDEX.FspMultiPhaseMemInitApiIndex
  jmp    ASM_PFX(FspApiCommon)
;----------------------------------------------------------------------------
; TempRamExitApi API
;
; This API tears down temporary RAM
;
;----------------------------------------------------------------------------
global ASM_PFX(TempRamExitApi)
ASM_PFX(TempRamExitApi):
  mov    eax,  4 ; FSP_API_INDEX.TempRamExitApiIndex
  jmp    ASM_PFX(FspApiCommon)

;----------------------------------------------------------------------------
; FspApiCommonContinue API
;
; This is the FSP API common entry point to resume the FSP execution
;
;----------------------------------------------------------------------------
global ASM_PFX(FspApiCommonContinue)
ASM_PFX(FspApiCommonContinue):
  ;
  ; Handle FspMultiPhaseMemInitApiIndex API
  ;
  cmp    eax, 8   ; FspMultiPhaseMemInitApiIndex
  jnz    NotMultiPhaseMemoryInitApi

  pushad
  push   DWORD [esp + (4 * 8 + 4)]  ; push ApiParam
  push   eax                        ; push ApiIdx
  call   ASM_PFX(FspMultiPhaseMemInitApiHandler)
  add    esp, 8
  mov    dword  [esp + STACK_SAVED_EAX_OFFSET], eax
  popad
  ret

NotMultiPhaseMemoryInitApi:

  ;
  ; FspMemoryInit API setup the initial stack frame
  ;

  ;
  ; Place holder to store the FspInfoHeader pointer
  ;
  push   eax

  ;
  ; Update the FspInfoHeader pointer
  ;
  push   eax
  call   ASM_PFX(AsmGetFspInfoHeader)
  mov    [esp + 4], eax
  pop    eax

  ;
  ; Create a Task Frame in the stack for the Boot Loader
  ;
  pushfd     ; 2 pushf for 4 byte alignment
  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    edx, cr4
  push   edx
  mov    edx, cr3
  push   edx
  mov    edx, cr0
  push   edx
SkipPagetableSave:

  ; Reserve 8 bytes for IDT save/restore
  sub     esp, 8
  sidt    [esp]


  ;  Get Stackbase and StackSize from FSPM_UPD Param
  mov    edx, [esp + API_PARAM1_OFFSET]
  cmp    edx, 0
  jnz    FspStackSetup

  ; Get UPD default values if FspmUpdDataPtr (ApiParam1) is null
  push   eax
  call   ASM_PFX(AsmGetFspInfoHeader)
  mov    edx, [eax + FSP_HEADER_IMGBASE_OFFSET]
  add    edx, [eax + FSP_HEADER_CFGREG_OFFSET]
  pop    eax

FspStackSetup:
  mov    ecx, [edx + FSPM_UPD_COMMON.Revision]
  cmp    ecx, 3
  jae    FspmUpdCommon2

  ;
  ; StackBase = temp memory base, StackSize = temp memory size
  ;
  mov    edi, [edx + FSPM_UPD_COMMON.StackBase]
  mov    ecx, [edx + FSPM_UPD_COMMON.StackSize]
  jmp    ChkFspHeapSize

FspmUpdCommon2:
  mov    edi, [edx + FSPM_UPD_COMMON_FSP24.StackBase]
  mov    ecx, [edx + FSPM_UPD_COMMON_FSP24.StackSize]

ChkFspHeapSize:
  ;
  ; Keep using bootloader stack if heap size % is 0
  ;
  mov    bl, BYTE [ASM_PFX(PcdGet8 (PcdFspHeapSizePercentage))]
  cmp    bl, 0
  jz     SkipStackSwitch

  ;
  ; Set up a dedicated temp ram stack for FSP if FSP heap size % doesn't equal 0
  ;
  add    edi, ecx
  ;
  ; Switch to new FSP stack
  ;
  xchg   edi, esp                                ; Exchange edi and esp, edi will be assigned to the current esp pointer and esp will be Stack base + Stack size

SkipStackSwitch:
  ;
  ; If heap size % is 0:
  ;   EDI is FSPM_UPD_COMMON.StackBase and will hold ESP later (boot loader stack pointer)
  ;   ECX is FSPM_UPD_COMMON.StackSize
  ;   ESP is boot loader stack pointer (no stack switch)
  ;   BL  is 0 to indicate no stack switch (EBX will hold FSPM_UPD_COMMON.StackBase later)
  ;
  ; If heap size % is not 0
  ;   EDI is boot loader stack pointer
  ;   ECX is FSPM_UPD_COMMON.StackSize
  ;   ESP is new stack (FSPM_UPD_COMMON.StackBase + FSPM_UPD_COMMON.StackSize)
  ;   BL  is NOT 0 to indicate stack has switched
  ;
  cmp    bl, 0
  jnz    StackHasBeenSwitched

  mov    ebx, edi                                ; Put FSPM_UPD_COMMON.StackBase to ebx as temp memory base
  mov    edi, esp                                ; Put boot loader stack pointer to edi
  jmp    StackSetupDone

StackHasBeenSwitched:
  mov    ebx, esp                                ; Put Stack base + Stack size in ebx
  sub    ebx, ecx                                ; Stack base + Stack size - Stack size as temp memory base

StackSetupDone:

  ;
  ; Pass the API Idx to SecStartup
  ;
  push    eax

  ;
  ; Pass the BootLoader stack to SecStartup
  ;
  push    edi

  ;
  ; Pass entry point of the PEI core
  ;
  call    ASM_PFX(AsmGetFspBaseAddress)
  mov     edi, eax
  call    ASM_PFX(AsmGetPeiCoreOffset)
  add     edi, eax
  push    edi

  ;
  ; Pass BFV into the PEI Core
  ; It uses relative address to calculate the actual boot FV base
  ; For FSP implementation with single FV, PcdFspBootFirmwareVolumeBase and
  ; PcdFspAreaBaseAddress are the same. For FSP with multiple FVs,
  ; they are different. The code below can handle both cases.
  ;
  call    ASM_PFX(AsmGetFspBaseAddress)
  push    eax

  ;
  ; Pass stack base and size into the PEI Core
  ;
  push    ebx
  push    ecx

  ;
  ; Pass Control into the PEI Core
  ;
  call    ASM_PFX(SecStartup)
  add     esp, 4
exit:
  ret

global ASM_PFX(FspPeiCoreEntryOff)
ASM_PFX(FspPeiCoreEntryOff):
   ;
   ; This value will be patched by the build script
   ;
   DD    0x12345678

global ASM_PFX(AsmGetPeiCoreOffset)
ASM_PFX(AsmGetPeiCoreOffset):
   mov   eax, dword [ASM_PFX(FspPeiCoreEntryOff)]
   ret

;----------------------------------------------------------------------------
; TempRamInit API
;
; Empty function for WHOLEARCHIVE build option
;
;----------------------------------------------------------------------------
global ASM_PFX(TempRamInitApi)
ASM_PFX(TempRamInitApi):
  jmp $
  ret

;----------------------------------------------------------------------------
; Module Entrypoint API
;----------------------------------------------------------------------------
global ASM_PFX(_ModuleEntryPoint)
ASM_PFX(_ModuleEntryPoint):
  jmp $
