/** @file | |
CPU MP Initialize helper function for AMD SEV. | |
Copyright (c) 2021, AMD Inc. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "MpLib.h" | |
#include <Library/CcExitLib.h> | |
/** | |
Get Protected mode code segment with 16-bit default addressing | |
from current GDT table. | |
@return Protected mode 16-bit code segment value. | |
**/ | |
STATIC | |
UINT16 | |
GetProtectedMode16CS ( | |
VOID | |
) | |
{ | |
IA32_DESCRIPTOR GdtrDesc; | |
IA32_SEGMENT_DESCRIPTOR *GdtEntry; | |
UINTN GdtEntryCount; | |
UINT16 Index; | |
Index = (UINT16)-1; | |
AsmReadGdtr (&GdtrDesc); | |
GdtEntryCount = (GdtrDesc.Limit + 1) / sizeof (IA32_SEGMENT_DESCRIPTOR); | |
GdtEntry = (IA32_SEGMENT_DESCRIPTOR *)GdtrDesc.Base; | |
for (Index = 0; Index < GdtEntryCount; Index++) { | |
if ((GdtEntry->Bits.L == 0) && | |
(GdtEntry->Bits.DB == 0) && | |
(GdtEntry->Bits.Type > 8)) | |
{ | |
break; | |
} | |
GdtEntry++; | |
} | |
ASSERT (Index != GdtEntryCount); | |
return Index * 8; | |
} | |
/** | |
Get Protected mode code segment with 32-bit default addressing | |
from current GDT table. | |
@return Protected mode 32-bit code segment value. | |
**/ | |
STATIC | |
UINT16 | |
GetProtectedMode32CS ( | |
VOID | |
) | |
{ | |
IA32_DESCRIPTOR GdtrDesc; | |
IA32_SEGMENT_DESCRIPTOR *GdtEntry; | |
UINTN GdtEntryCount; | |
UINT16 Index; | |
Index = (UINT16)-1; | |
AsmReadGdtr (&GdtrDesc); | |
GdtEntryCount = (GdtrDesc.Limit + 1) / sizeof (IA32_SEGMENT_DESCRIPTOR); | |
GdtEntry = (IA32_SEGMENT_DESCRIPTOR *)GdtrDesc.Base; | |
for (Index = 0; Index < GdtEntryCount; Index++) { | |
if ((GdtEntry->Bits.L == 0) && | |
(GdtEntry->Bits.DB == 1) && | |
(GdtEntry->Bits.Type > 8)) | |
{ | |
break; | |
} | |
GdtEntry++; | |
} | |
ASSERT (Index != GdtEntryCount); | |
return Index * 8; | |
} | |
/** | |
Reset an AP when in SEV-ES mode. | |
If successful, this function never returns. | |
@param[in] Ghcb Pointer to the GHCB | |
@param[in] CpuMpData Pointer to CPU MP Data | |
**/ | |
VOID | |
MpInitLibSevEsAPReset ( | |
IN GHCB *Ghcb, | |
IN CPU_MP_DATA *CpuMpData | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN ProcessorNumber; | |
UINT16 Code16, Code32; | |
AP_RESET *APResetFn; | |
UINTN BufferStart; | |
UINTN StackStart; | |
Status = GetProcessorNumber (CpuMpData, &ProcessorNumber); | |
ASSERT_EFI_ERROR (Status); | |
Code16 = GetProtectedMode16CS (); | |
Code32 = GetProtectedMode32CS (); | |
APResetFn = (AP_RESET *)(CpuMpData->WakeupBufferHigh + CpuMpData->AddressMap.SwitchToRealNoNxOffset); | |
BufferStart = CpuMpData->MpCpuExchangeInfo->BufferStart; | |
StackStart = CpuMpData->SevEsAPResetStackStart - | |
(AP_RESET_STACK_SIZE * ProcessorNumber); | |
// | |
// This call never returns. | |
// | |
APResetFn (BufferStart, Code16, Code32, StackStart); | |
} | |
/** | |
Allocate the SEV-ES AP jump table buffer. | |
@param[in, out] CpuMpData The pointer to CPU MP Data structure. | |
**/ | |
VOID | |
AllocateSevEsAPMemory ( | |
IN OUT CPU_MP_DATA *CpuMpData | |
) | |
{ | |
if (CpuMpData->SevEsAPBuffer == (UINTN)-1) { | |
CpuMpData->SevEsAPBuffer = | |
CpuMpData->SevEsIsEnabled ? GetSevEsAPMemory () : 0; | |
} | |
} | |
/** | |
Program the SEV-ES AP jump table buffer. | |
@param[in] SipiVector The SIPI vector used for the AP Reset | |
**/ | |
VOID | |
SetSevEsJumpTable ( | |
IN UINTN SipiVector | |
) | |
{ | |
SEV_ES_AP_JMP_FAR *JmpFar; | |
UINT32 Offset, InsnByte; | |
UINT8 LoNib, HiNib; | |
JmpFar = (SEV_ES_AP_JMP_FAR *)(UINTN)FixedPcdGet32 (PcdSevEsWorkAreaBase); | |
ASSERT (JmpFar != NULL); | |
// | |
// Obtain the address of the Segment/Rip location in the workarea. | |
// This will be set to a value derived from the SIPI vector and will | |
// be the memory address used for the far jump below. | |
// | |
Offset = FixedPcdGet32 (PcdSevEsWorkAreaBase); | |
Offset += sizeof (JmpFar->InsnBuffer); | |
LoNib = (UINT8)Offset; | |
HiNib = (UINT8)(Offset >> 8); | |
// | |
// Program the workarea (which is the initial AP boot address) with | |
// far jump to the SIPI vector (where XX and YY represent the | |
// address of where the SIPI vector is stored. | |
// | |
// JMP FAR [CS:XXYY] => 2E FF 2E YY XX | |
// | |
InsnByte = 0; | |
JmpFar->InsnBuffer[InsnByte++] = 0x2E; // CS override prefix | |
JmpFar->InsnBuffer[InsnByte++] = 0xFF; // JMP (FAR) | |
JmpFar->InsnBuffer[InsnByte++] = 0x2E; // ModRM (JMP memory location) | |
JmpFar->InsnBuffer[InsnByte++] = LoNib; // YY offset ... | |
JmpFar->InsnBuffer[InsnByte++] = HiNib; // XX offset ... | |
// | |
// Program the Segment/Rip based on the SIPI vector (always at least | |
// 16-byte aligned, so Rip is set to 0). | |
// | |
JmpFar->Rip = 0; | |
JmpFar->Segment = (UINT16)(SipiVector >> 4); | |
} | |
/** | |
The function puts the AP in halt loop. | |
@param[in] CpuMpData The pointer to CPU MP Data structure. | |
**/ | |
VOID | |
SevEsPlaceApHlt ( | |
CPU_MP_DATA *CpuMpData | |
) | |
{ | |
MSR_SEV_ES_GHCB_REGISTER Msr; | |
GHCB *Ghcb; | |
UINT64 Status; | |
BOOLEAN DoDecrement; | |
BOOLEAN InterruptState; | |
DoDecrement = (BOOLEAN)(CpuMpData->InitFlag == ApInitConfig); | |
while (TRUE) { | |
Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB); | |
Ghcb = Msr.Ghcb; | |
CcExitVmgInit (Ghcb, &InterruptState); | |
if (DoDecrement) { | |
DoDecrement = FALSE; | |
// | |
// Perform the delayed decrement just before issuing the first | |
// VMGEXIT with AP_RESET_HOLD. | |
// | |
InterlockedDecrement ((UINT32 *)&CpuMpData->MpCpuExchangeInfo->NumApsExecuting); | |
} | |
Status = CcExitVmgExit (Ghcb, SVM_EXIT_AP_RESET_HOLD, 0, 0); | |
if ((Status == 0) && (Ghcb->SaveArea.SwExitInfo2 != 0)) { | |
CcExitVmgDone (Ghcb, InterruptState); | |
break; | |
} | |
CcExitVmgDone (Ghcb, InterruptState); | |
} | |
// | |
// Awakened in a new phase? Use the new CpuMpData | |
// | |
if (CpuMpData->NewCpuMpData != NULL) { | |
CpuMpData = CpuMpData->NewCpuMpData; | |
} | |
MpInitLibSevEsAPReset (Ghcb, CpuMpData); | |
} | |
/** | |
The function fills the exchange data for the AP. | |
@param[in] ExchangeInfo The pointer to CPU Exchange Data structure | |
**/ | |
VOID | |
FillExchangeInfoDataSevEs ( | |
IN volatile MP_CPU_EXCHANGE_INFO *ExchangeInfo | |
) | |
{ | |
UINT32 StdRangeMax; | |
AsmCpuid (CPUID_SIGNATURE, &StdRangeMax, NULL, NULL, NULL); | |
if (StdRangeMax >= CPUID_EXTENDED_TOPOLOGY) { | |
CPUID_EXTENDED_TOPOLOGY_EBX ExtTopoEbx; | |
AsmCpuidEx ( | |
CPUID_EXTENDED_TOPOLOGY, | |
0, | |
NULL, | |
&ExtTopoEbx.Uint32, | |
NULL, | |
NULL | |
); | |
ExchangeInfo->ExtTopoAvail = !!ExtTopoEbx.Bits.LogicalProcessors; | |
} | |
} | |
/** | |
Get pointer to CPU MP Data structure from GUIDed HOB. | |
@param[in] CpuMpData The pointer to CPU MP Data structure. | |
**/ | |
VOID | |
AmdSevUpdateCpuMpData ( | |
IN CPU_MP_DATA *CpuMpData | |
) | |
{ | |
CPU_MP_DATA *OldCpuMpData; | |
OldCpuMpData = GetCpuMpDataFromGuidedHob (); | |
OldCpuMpData->NewCpuMpData = CpuMpData; | |
} |