mirror of https://github.com/acidanthera/audk.git
362 lines
11 KiB
C
362 lines
11 KiB
C
/** @file
|
|
|
|
AMD SEV helper function.
|
|
|
|
Copyright (c) 2021 - 2024, AMD Incorporated. All rights reserved.<BR>
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
**/
|
|
|
|
#include "MpLib.h"
|
|
#include <Library/CcExitLib.h>
|
|
#include <Library/AmdSvsmLib.h>
|
|
#include <Register/Amd/Fam17Msr.h>
|
|
#include <Register/Amd/Ghcb.h>
|
|
|
|
#define _IS_ALIGNED(x, y) (ALIGN_POINTER((x), (y)) == (x))
|
|
|
|
/**
|
|
Perform the requested AP Creation action.
|
|
|
|
@param[in] SaveArea Pointer to VM save area (VMSA)
|
|
@param[in] ApicId APIC ID of the vCPU
|
|
@param[in] Action AP action to perform
|
|
|
|
@retval TRUE Action completed successfully
|
|
@retval FALSE Action did not complete successfully
|
|
**/
|
|
STATIC
|
|
BOOLEAN
|
|
SevSnpPerformApAction (
|
|
IN SEV_ES_SAVE_AREA *SaveArea,
|
|
IN UINT32 ApicId,
|
|
IN UINTN Action
|
|
)
|
|
{
|
|
MSR_SEV_ES_GHCB_REGISTER Msr;
|
|
GHCB *Ghcb;
|
|
BOOLEAN InterruptState;
|
|
UINT64 ExitInfo1;
|
|
UINT64 ExitInfo2;
|
|
UINT64 VmgExitStatus;
|
|
EFI_STATUS VmsaStatus;
|
|
|
|
if (Action == SVM_VMGEXIT_SNP_AP_CREATE) {
|
|
//
|
|
// Turn the page into a recognized VMSA page. When an SVSM is present
|
|
// the page following the VMSA is the Calling Area page.
|
|
//
|
|
VmsaStatus = AmdSvsmSnpVmsaRmpAdjust (SaveArea, ApicId, TRUE);
|
|
if (EFI_ERROR (VmsaStatus)) {
|
|
DEBUG ((DEBUG_INFO, "SEV-SNP: RMPADJUST failed for VMSA creation\n"));
|
|
ASSERT (FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
ExitInfo1 = (UINT64)ApicId << 32;
|
|
ExitInfo1 |= (UINT64)SaveArea->Vmpl << 16;
|
|
ExitInfo1 |= Action;
|
|
ExitInfo2 = (UINT64)(UINTN)SaveArea;
|
|
|
|
Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB);
|
|
Ghcb = Msr.Ghcb;
|
|
|
|
CcExitVmgInit (Ghcb, &InterruptState);
|
|
|
|
if (Action == SVM_VMGEXIT_SNP_AP_CREATE) {
|
|
Ghcb->SaveArea.Rax = SaveArea->SevFeatures;
|
|
CcExitVmgSetOffsetValid (Ghcb, GhcbRax);
|
|
}
|
|
|
|
VmgExitStatus = CcExitVmgExit (
|
|
Ghcb,
|
|
SVM_EXIT_SNP_AP_CREATION,
|
|
ExitInfo1,
|
|
ExitInfo2
|
|
);
|
|
|
|
CcExitVmgDone (Ghcb, InterruptState);
|
|
|
|
if (VmgExitStatus != 0) {
|
|
DEBUG ((DEBUG_INFO, "SEV-SNP: AP Destroy failed\n"));
|
|
ASSERT (FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (Action == SVM_VMGEXIT_SNP_AP_DESTROY) {
|
|
//
|
|
// Make the current VMSA not runnable and accessible to be reprogrammed.
|
|
// When an SVSM is present the page following the VMSA is the Calling Area
|
|
// page.
|
|
//
|
|
VmsaStatus = AmdSvsmSnpVmsaRmpAdjust (SaveArea, ApicId, FALSE);
|
|
if (EFI_ERROR (VmsaStatus)) {
|
|
DEBUG ((DEBUG_INFO, "SEV-SNP: RMPADJUST failed for VMSA reset\n"));
|
|
ASSERT (FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
Create an SEV-SNP AP save area (VMSA) for use in running the vCPU.
|
|
|
|
@param[in] CpuMpData Pointer to CPU MP Data
|
|
@param[in] CpuData Pointer to CPU AP Data
|
|
@param[in] ApicId APIC ID of the vCPU
|
|
**/
|
|
VOID
|
|
SevSnpCreateSaveArea (
|
|
IN CPU_MP_DATA *CpuMpData,
|
|
IN CPU_AP_DATA *CpuData,
|
|
UINT32 ApicId
|
|
)
|
|
{
|
|
UINTN PageCount;
|
|
UINT8 *Pages;
|
|
SEV_ES_SAVE_AREA *SaveArea;
|
|
IA32_CR0 ApCr0;
|
|
IA32_CR0 ResetCr0;
|
|
IA32_CR4 ApCr4;
|
|
IA32_CR4 ResetCr4;
|
|
UINTN StartIp;
|
|
UINT8 SipiVector;
|
|
|
|
//
|
|
// When running under an SVSM, a Calling Area page is also needed and is
|
|
// always the page following the VMSA.
|
|
//
|
|
PageCount = AmdSvsmIsSvsmPresent () ? 2 : 1;
|
|
|
|
if (CpuData->SevEsSaveArea == NULL) {
|
|
//
|
|
// Allocate a page for the SEV-ES Save Area and initialize it. Due to AMD
|
|
// erratum #1467 (VMSA cannot be on a 2MB boundary), allocate an extra page
|
|
// to choose from to work around the issue.
|
|
//
|
|
Pages = AllocateReservedPages (PageCount + 1);
|
|
if (!Pages) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Since page allocation works by allocating downward in the address space,
|
|
// try to always free the first (lower address) page to limit possible holes
|
|
// in the memory map. So, if the address of the second page is 2MB aligned,
|
|
// then use the first page and free the last page. Otherwise, free the
|
|
// first page and use the second page.
|
|
//
|
|
if (_IS_ALIGNED (Pages + EFI_PAGE_SIZE, SIZE_2MB)) {
|
|
SaveArea = (SEV_ES_SAVE_AREA *)Pages;
|
|
FreePages (Pages + (EFI_PAGE_SIZE * PageCount), 1);
|
|
} else {
|
|
SaveArea = (SEV_ES_SAVE_AREA *)(Pages + EFI_PAGE_SIZE);
|
|
FreePages (Pages, 1);
|
|
}
|
|
|
|
CpuData->SevEsSaveArea = SaveArea;
|
|
} else {
|
|
SaveArea = CpuData->SevEsSaveArea;
|
|
|
|
//
|
|
// Tell the hypervisor to not use the current VMSA
|
|
//
|
|
if (!SevSnpPerformApAction (SaveArea, ApicId, SVM_VMGEXIT_SNP_AP_DESTROY)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ZeroMem (SaveArea, EFI_PAGE_SIZE * PageCount);
|
|
|
|
//
|
|
// Propogate the CR0.NW and CR0.CD setting to the AP
|
|
//
|
|
ResetCr0.UintN = 0x00000010;
|
|
ApCr0.UintN = CpuData->VolatileRegisters.Cr0;
|
|
if (ApCr0.Bits.NW) {
|
|
ResetCr0.Bits.NW = 1;
|
|
}
|
|
|
|
if (ApCr0.Bits.CD) {
|
|
ResetCr0.Bits.CD = 1;
|
|
}
|
|
|
|
//
|
|
// Propagate the CR4.MCE setting to the AP
|
|
//
|
|
ResetCr4.UintN = 0;
|
|
ApCr4.UintN = CpuData->VolatileRegisters.Cr4;
|
|
if (ApCr4.Bits.MCE) {
|
|
ResetCr4.Bits.MCE = 1;
|
|
}
|
|
|
|
//
|
|
// Convert the start IP into a SIPI Vector
|
|
//
|
|
StartIp = CpuMpData->MpCpuExchangeInfo->BufferStart;
|
|
SipiVector = (UINT8)(StartIp >> 12);
|
|
|
|
//
|
|
// Set the CS:RIP value based on the start IP
|
|
//
|
|
SaveArea->Cs.Base = SipiVector << 12;
|
|
SaveArea->Cs.Selector = SipiVector << 8;
|
|
SaveArea->Cs.Limit = 0xFFFF;
|
|
SaveArea->Cs.Attributes.Bits.Present = 1;
|
|
SaveArea->Cs.Attributes.Bits.Sbit = 1;
|
|
SaveArea->Cs.Attributes.Bits.Type = SEV_ES_RESET_CODE_SEGMENT_TYPE;
|
|
SaveArea->Rip = StartIp & 0xFFF;
|
|
|
|
//
|
|
// Set the remaining values as defined in APM for INIT
|
|
//
|
|
SaveArea->Ds.Limit = 0xFFFF;
|
|
SaveArea->Ds.Attributes.Bits.Present = 1;
|
|
SaveArea->Ds.Attributes.Bits.Sbit = 1;
|
|
SaveArea->Ds.Attributes.Bits.Type = SEV_ES_RESET_DATA_SEGMENT_TYPE;
|
|
SaveArea->Es = SaveArea->Ds;
|
|
SaveArea->Fs = SaveArea->Ds;
|
|
SaveArea->Gs = SaveArea->Ds;
|
|
SaveArea->Ss = SaveArea->Ds;
|
|
|
|
SaveArea->Gdtr.Limit = 0xFFFF;
|
|
SaveArea->Ldtr.Limit = 0xFFFF;
|
|
SaveArea->Ldtr.Attributes.Bits.Present = 1;
|
|
SaveArea->Ldtr.Attributes.Bits.Type = SEV_ES_RESET_LDT_TYPE;
|
|
SaveArea->Idtr.Limit = 0xFFFF;
|
|
SaveArea->Tr.Limit = 0xFFFF;
|
|
SaveArea->Ldtr.Attributes.Bits.Present = 1;
|
|
SaveArea->Ldtr.Attributes.Bits.Type = SEV_ES_RESET_TSS_TYPE;
|
|
|
|
SaveArea->Efer = 0x1000;
|
|
SaveArea->Cr4 = ResetCr4.UintN;
|
|
SaveArea->Cr0 = ResetCr0.UintN;
|
|
SaveArea->Dr7 = 0x0400;
|
|
SaveArea->Dr6 = 0xFFFF0FF0;
|
|
SaveArea->Rflags = 0x0002;
|
|
SaveArea->GPat = 0x0007040600070406ULL;
|
|
SaveArea->XCr0 = 0x0001;
|
|
SaveArea->Mxcsr = 0x1F80;
|
|
SaveArea->X87Ftw = 0x5555;
|
|
SaveArea->X87Fcw = 0x0040;
|
|
|
|
//
|
|
// Set the SEV-SNP specific fields for the save area:
|
|
// VMPL - based on current mode
|
|
// SEV_FEATURES - equivalent to the SEV_STATUS MSR right shifted 2 bits
|
|
//
|
|
SaveArea->Vmpl = AmdSvsmSnpGetVmpl ();
|
|
SaveArea->SevFeatures = AsmReadMsr64 (MSR_SEV_STATUS) >> 2;
|
|
|
|
SevSnpPerformApAction (SaveArea, ApicId, SVM_VMGEXIT_SNP_AP_CREATE);
|
|
}
|
|
|
|
/**
|
|
Create SEV-SNP APs.
|
|
|
|
@param[in] CpuMpData Pointer to CPU MP Data
|
|
@param[in] ProcessorNumber The handle number of specified processor
|
|
(-1 for all APs)
|
|
**/
|
|
VOID
|
|
SevSnpCreateAP (
|
|
IN CPU_MP_DATA *CpuMpData,
|
|
IN INTN ProcessorNumber
|
|
)
|
|
{
|
|
CPU_INFO_IN_HOB *CpuInfoInHob;
|
|
CPU_AP_DATA *CpuData;
|
|
UINTN Index;
|
|
UINTN MaxIndex;
|
|
UINT32 ApicId;
|
|
EFI_HOB_GUID_TYPE *GuidHob;
|
|
GHCB_APIC_IDS *GhcbApicIds;
|
|
|
|
ASSERT (CpuMpData->MpCpuExchangeInfo->BufferStart < 0x100000);
|
|
|
|
CpuInfoInHob = (CPU_INFO_IN_HOB *)(UINTN)CpuMpData->CpuInfoInHob;
|
|
|
|
if (ProcessorNumber < 0) {
|
|
if (CpuMpData->InitFlag == ApInitConfig) {
|
|
//
|
|
// APs have not been started, so CpuCount is not "known" yet. Use the
|
|
// retrieved APIC IDs to start the APs and fill out the MpLib CPU
|
|
// information properly. CanUseSevSnpCreateAP() guarantees we have a
|
|
// HOB when InitFlag is ApInitConfig.
|
|
//
|
|
GuidHob = GetFirstGuidHob (&gGhcbApicIdsGuid);
|
|
GhcbApicIds = (GHCB_APIC_IDS *)(*(UINTN *)GET_GUID_HOB_DATA (GuidHob));
|
|
MaxIndex = MIN (GhcbApicIds->NumEntries, PcdGet32 (PcdCpuMaxLogicalProcessorNumber));
|
|
} else {
|
|
//
|
|
// APs have been previously started.
|
|
//
|
|
MaxIndex = CpuMpData->CpuCount;
|
|
}
|
|
|
|
for (Index = 0; Index < MaxIndex; Index++) {
|
|
if (Index != CpuMpData->BspNumber) {
|
|
CpuData = &CpuMpData->CpuData[Index];
|
|
|
|
if (CpuMpData->InitFlag == ApInitConfig) {
|
|
ApicId = GhcbApicIds->ApicIds[Index];
|
|
|
|
//
|
|
// For the first boot, use the BSP register information.
|
|
//
|
|
CopyMem (
|
|
&CpuData->VolatileRegisters,
|
|
&CpuMpData->CpuData[0].VolatileRegisters,
|
|
sizeof (CpuData->VolatileRegisters)
|
|
);
|
|
} else {
|
|
ApicId = CpuInfoInHob[Index].ApicId;
|
|
}
|
|
|
|
SevSnpCreateSaveArea (CpuMpData, CpuData, ApicId);
|
|
}
|
|
}
|
|
} else {
|
|
Index = (UINTN)ProcessorNumber;
|
|
CpuData = &CpuMpData->CpuData[Index];
|
|
ApicId = CpuInfoInHob[ProcessorNumber].ApicId,
|
|
SevSnpCreateSaveArea (CpuMpData, CpuData, ApicId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Determine if the SEV-SNP AP Create protocol should be used.
|
|
|
|
@param[in] CpuMpData Pointer to CPU MP Data
|
|
|
|
@retval TRUE Use SEV-SNP AP Create protocol
|
|
@retval FALSE Do not use SEV-SNP AP Create protocol
|
|
**/
|
|
BOOLEAN
|
|
CanUseSevSnpCreateAP (
|
|
IN CPU_MP_DATA *CpuMpData
|
|
)
|
|
{
|
|
//
|
|
// The AP Create protocol is used for an SEV-SNP guest if
|
|
// - The initial configuration has been performed already or
|
|
// - The APIC IDs GUIDed HOB is non-zero.
|
|
//
|
|
if (!CpuMpData->SevSnpIsEnabled) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ((CpuMpData->InitFlag == ApInitConfig) && (GetFirstGuidHob (&gGhcbApicIdsGuid) == NULL)) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|