UefiCpuPkg/MpInitLib: Use SEV-SNP AP Creation NAE event to launch APs

BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3275

Use the SEV-SNP AP Creation NAE event to create and launch APs under
SEV-SNP. This capability will be advertised in the SEV Hypervisor
Feature Support PCD (PcdSevEsHypervisorFeatures).

Cc: Michael Roth <michael.roth@amd.com>
Cc: Eric Dong <eric.dong@intel.com>
Cc: Ray Ni <ray.ni@intel.com>
Cc: Rahul Kumar <rahul1.kumar@intel.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Min Xu <min.m.xu@intel.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Acked-by: Ray Ni <ray.ni@intel.com>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
This commit is contained in:
Tom Lendacky 2021-12-09 11:28:00 +08:00 committed by mergify[bot]
parent 67484aed69
commit 06544455d0
7 changed files with 433 additions and 22 deletions

View File

@ -22,9 +22,11 @@
#
[Sources.IA32]
Ia32/AmdSev.c
Ia32/MpFuncs.nasm
[Sources.X64]
X64/AmdSev.c
X64/MpFuncs.nasm
[Sources.common]
@ -73,6 +75,7 @@
gUefiCpuPkgTokenSpaceGuid.PcdCpuApLoopMode ## CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdCpuApTargetCstate ## SOMETIMES_CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdCpuApStatusCheckIntervalInMicroSeconds ## CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdGhcbHypervisorFeatures ## CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdSevEsWorkAreaBase ## SOMETIMES_CONSUMES
gEfiMdeModulePkgTokenSpaceGuid.PcdCpuStackGuard ## CONSUMES
gEfiMdeModulePkgTokenSpaceGuid.PcdGhcbBase ## CONSUMES

View File

@ -93,7 +93,14 @@ GetWakeupBuffer (
EFI_PHYSICAL_ADDRESS StartAddress;
EFI_MEMORY_TYPE MemoryType;
if (ConfidentialComputingGuestHas (CCAttrAmdSevEs)) {
if (ConfidentialComputingGuestHas (CCAttrAmdSevEs) &&
!ConfidentialComputingGuestHas (CCAttrAmdSevSnp))
{
//
// An SEV-ES-only guest requires the memory to be reserved. SEV-SNP, which
// is also considered SEV-ES, uses a different AP startup method, though,
// which does not have the same requirement.
//
MemoryType = EfiReservedMemoryType;
} else {
MemoryType = EfiBootServicesData;
@ -380,7 +387,7 @@ RelocateApLoop (
MpInitLibWhoAmI (&ProcessorNumber);
CpuMpData = GetCpuMpData ();
MwaitSupport = IsMwaitSupport ();
if (CpuMpData->SevEsIsEnabled) {
if (CpuMpData->UseSevEsAPMethod) {
StackStart = CpuMpData->SevEsAPResetStackStart;
} else {
StackStart = mReservedTopOfApStack;
@ -430,7 +437,7 @@ MpInitChangeApLoopCallback (
CpuPause ();
}
if (CpuMpData->SevEsIsEnabled && (CpuMpData->WakeupBuffer != (UINTN)-1)) {
if (CpuMpData->UseSevEsAPMethod && (CpuMpData->WakeupBuffer != (UINTN)-1)) {
//
// There are APs present. Re-use reserved memory area below 1MB from
// WakeupBuffer as the area to be used for transitioning to 16-bit mode

View File

@ -0,0 +1,70 @@
/** @file
AMD SEV helper function.
Copyright (c) 2021, AMD Incorporated. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "MpLib.h"
/**
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
)
{
//
// SEV-SNP is not support on 32-bit build.
//
ASSERT (FALSE);
}
/**
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
)
{
//
// SEV-SNP is not support on 32-bit build.
//
ASSERT (FALSE);
}
/**
Issue RMPADJUST to adjust the VMSA attribute of an SEV-SNP page.
@param[in] PageAddress
@param[in] VmsaPage
@return RMPADJUST return value
**/
UINT32
SevSnpRmpAdjust (
IN EFI_PHYSICAL_ADDRESS PageAddress,
IN BOOLEAN VmsaPage
)
{
//
// RMPADJUST is not supported in 32-bit mode
//
return RETURN_UNSUPPORTED;
}

View File

@ -295,10 +295,12 @@ GetApLoopMode (
ApLoopMode = ApInHltLoop;
}
if (ConfidentialComputingGuestHas (CCAttrAmdSevEs)) {
if (ConfidentialComputingGuestHas (CCAttrAmdSevEs) &&
!ConfidentialComputingGuestHas (CCAttrAmdSevSnp))
{
//
// For SEV-ES, force AP in Hlt-loop mode in order to use the GHCB
// protocol for starting APs
// For SEV-ES (SEV-SNP is also considered SEV-ES), force AP in Hlt-loop
// mode in order to use the GHCB protocol for starting APs
//
ApLoopMode = ApInHltLoop;
}
@ -763,7 +765,7 @@ ApWakeupFunction (
// to allow the APs to issue an AP_RESET_HOLD before the BSP possibly
// performs another INIT-SIPI-SIPI sequence.
//
if (!CpuMpData->SevEsIsEnabled) {
if (!CpuMpData->UseSevEsAPMethod) {
InterlockedDecrement ((UINT32 *)&CpuMpData->MpCpuExchangeInfo->NumApsExecuting);
}
}
@ -777,7 +779,7 @@ ApWakeupFunction (
//
while (TRUE) {
DisableInterrupts ();
if (CpuMpData->SevEsIsEnabled) {
if (CpuMpData->UseSevEsAPMethod) {
SevEsPlaceApHlt (CpuMpData);
} else {
CpuSleep ();
@ -1061,9 +1063,13 @@ AllocateResetVector (
);
//
// The AP reset stack is only used by SEV-ES guests. Do not allocate it
// if SEV-ES is not enabled.
// if SEV-ES is not enabled. An SEV-SNP guest is also considered
// an SEV-ES guest, but uses a different method of AP startup, eliminating
// the need for the allocation.
//
if (ConfidentialComputingGuestHas (CCAttrAmdSevEs)) {
if (ConfidentialComputingGuestHas (CCAttrAmdSevEs) &&
!ConfidentialComputingGuestHas (CCAttrAmdSevSnp))
{
//
// Stack location is based on ProcessorNumber, so use the total number
// of processors for calculating the total stack area.
@ -1114,7 +1120,7 @@ FreeResetVector (
// perform the restore as this will overwrite memory which has data
// needed by SEV-ES.
//
if (!CpuMpData->SevEsIsEnabled) {
if (!CpuMpData->UseSevEsAPMethod) {
RestoreWakeupBuffer (CpuMpData);
}
}
@ -1193,7 +1199,7 @@ WakeUpAP (
if (ResetVectorRequired) {
//
// For SEV-ES, the initial AP boot address will be defined by
// For SEV-ES and SEV-SNP, the initial AP boot address will be defined by
// PcdSevEsWorkAreaBase. The Segment/Rip must be the jump address
// from the original INIT-SIPI-SIPI.
//
@ -1203,8 +1209,14 @@ WakeUpAP (
//
// Wakeup all APs
// Must use the INIT-SIPI-SIPI method for initial configuration in
// order to obtain the APIC ID.
//
SendInitSipiSipiAllExcludingSelf ((UINT32)ExchangeInfo->BufferStart);
if (CpuMpData->SevSnpIsEnabled && (CpuMpData->InitFlag != ApInitConfig)) {
SevSnpCreateAP (CpuMpData, -1);
} else {
SendInitSipiSipiAllExcludingSelf ((UINT32)ExchangeInfo->BufferStart);
}
}
if (CpuMpData->InitFlag == ApInitConfig) {
@ -1295,7 +1307,7 @@ WakeUpAP (
CpuInfoInHob = (CPU_INFO_IN_HOB *)(UINTN)CpuMpData->CpuInfoInHob;
//
// For SEV-ES, the initial AP boot address will be defined by
// For SEV-ES and SEV-SNP, the initial AP boot address will be defined by
// PcdSevEsWorkAreaBase. The Segment/Rip must be the jump address
// from the original INIT-SIPI-SIPI.
//
@ -1303,10 +1315,14 @@ WakeUpAP (
SetSevEsJumpTable (ExchangeInfo->BufferStart);
}
SendInitSipiSipi (
CpuInfoInHob[ProcessorNumber].ApicId,
(UINT32)ExchangeInfo->BufferStart
);
if (CpuMpData->SevSnpIsEnabled && (CpuMpData->InitFlag != ApInitConfig)) {
SevSnpCreateAP (CpuMpData, (INTN)ProcessorNumber);
} else {
SendInitSipiSipi (
CpuInfoInHob[ProcessorNumber].ApicId,
(UINT32)ExchangeInfo->BufferStart
);
}
}
//
@ -1855,10 +1871,15 @@ MpInitLibInitialize (
CpuMpData->CpuData = (CPU_AP_DATA *)(CpuMpData + 1);
CpuMpData->CpuInfoInHob = (UINT64)(UINTN)(CpuMpData->CpuData + MaxLogicalProcessorNumber);
InitializeSpinLock (&CpuMpData->MpLock);
CpuMpData->SevEsIsEnabled = ConfidentialComputingGuestHas (CCAttrAmdSevEs);
CpuMpData->SevSnpIsEnabled = ConfidentialComputingGuestHas (CCAttrAmdSevSnp);
CpuMpData->SevEsAPBuffer = (UINTN)-1;
CpuMpData->GhcbBase = PcdGet64 (PcdGhcbBase);
CpuMpData->SevEsIsEnabled = ConfidentialComputingGuestHas (CCAttrAmdSevEs);
CpuMpData->SevSnpIsEnabled = ConfidentialComputingGuestHas (CCAttrAmdSevSnp);
CpuMpData->SevEsAPBuffer = (UINTN)-1;
CpuMpData->GhcbBase = PcdGet64 (PcdGhcbBase);
CpuMpData->UseSevEsAPMethod = CpuMpData->SevEsIsEnabled && !CpuMpData->SevSnpIsEnabled;
if (CpuMpData->SevSnpIsEnabled) {
ASSERT ((PcdGet64 (PcdGhcbHypervisorFeatures) & GHCB_HV_FEATURES_SNP_AP_CREATE) == GHCB_HV_FEATURES_SNP_AP_CREATE);
}
//
// Make sure no memory usage outside of the allocated buffer.

View File

@ -15,6 +15,7 @@
#include <Register/Intel/Cpuid.h>
#include <Register/Amd/Cpuid.h>
#include <Register/Amd/Ghcb.h>
#include <Register/Intel/Msr.h>
#include <Register/Intel/LocalApic.h>
#include <Register/Intel/Microcode.h>
@ -150,6 +151,7 @@ typedef struct {
UINT8 PlatformId;
UINT64 MicrocodeEntryAddr;
UINT32 MicrocodeRevision;
SEV_ES_SAVE_AREA *SevEsSaveArea;
} CPU_AP_DATA;
//
@ -294,6 +296,7 @@ struct _CPU_MP_DATA {
BOOLEAN SevEsIsEnabled;
BOOLEAN SevSnpIsEnabled;
BOOLEAN UseSevEsAPMethod;
UINTN SevEsAPBuffer;
UINTN SevEsAPResetStackStart;
CPU_MP_DATA *NewCpuMpData;
@ -799,4 +802,45 @@ FillExchangeInfoDataSevEs (
IN volatile MP_CPU_EXCHANGE_INFO *ExchangeInfo
);
/**
Issue RMPADJUST to adjust the VMSA attribute of an SEV-SNP page.
@param[in] PageAddress
@param[in] VmsaPage
@return RMPADJUST return value
**/
UINT32
SevSnpRmpAdjust (
IN EFI_PHYSICAL_ADDRESS PageAddress,
IN BOOLEAN VmsaPage
);
/**
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
);
/**
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
);
#endif

View File

@ -22,9 +22,11 @@
#
[Sources.IA32]
Ia32/AmdSev.c
Ia32/MpFuncs.nasm
[Sources.X64]
X64/AmdSev.c
X64/MpFuncs.nasm
[Sources.common]
@ -64,6 +66,7 @@
gUefiCpuPkgTokenSpaceGuid.PcdCpuApLoopMode ## CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdCpuApTargetCstate ## SOMETIMES_CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdSevEsWorkAreaBase ## SOMETIMES_CONSUMES
gUefiCpuPkgTokenSpaceGuid.PcdGhcbHypervisorFeatures ## CONSUMES
gEfiMdeModulePkgTokenSpaceGuid.PcdGhcbBase ## CONSUMES
gEfiMdePkgTokenSpaceGuid.PcdConfidentialComputingGuestAttr ## CONSUMES

View File

@ -0,0 +1,263 @@
/** @file
AMD SEV helper function.
Copyright (c) 2021, AMD Incorporated. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "MpLib.h"
#include <Library/VmgExitLib.h>
#include <Register/Amd/Fam17Msr.h>
#include <Register/Amd/Ghcb.h>
/**
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
)
{
SEV_ES_SAVE_AREA *SaveArea;
IA32_CR0 ApCr0;
IA32_CR0 ResetCr0;
IA32_CR4 ApCr4;
IA32_CR4 ResetCr4;
UINTN StartIp;
UINT8 SipiVector;
UINT32 RmpAdjustStatus;
UINT64 VmgExitStatus;
MSR_SEV_ES_GHCB_REGISTER Msr;
GHCB *Ghcb;
BOOLEAN InterruptState;
UINT64 ExitInfo1;
UINT64 ExitInfo2;
//
// Allocate a single page for the SEV-ES Save Area and initialize it.
//
SaveArea = AllocateReservedPages (1);
if (!SaveArea) {
return;
}
ZeroMem (SaveArea, EFI_PAGE_SIZE);
//
// 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 - always VMPL0
// SEV_FEATURES - equivalent to the SEV_STATUS MSR right shifted 2 bits
//
SaveArea->Vmpl = 0;
SaveArea->SevFeatures = AsmReadMsr64 (MSR_SEV_STATUS) >> 2;
//
// To turn the page into a recognized VMSA page, issue RMPADJUST:
// Target VMPL but numerically higher than current VMPL
// Target PermissionMask is not used
//
RmpAdjustStatus = SevSnpRmpAdjust (
(EFI_PHYSICAL_ADDRESS)(UINTN)SaveArea,
TRUE
);
ASSERT (RmpAdjustStatus == 0);
ExitInfo1 = (UINT64)ApicId << 32;
ExitInfo1 |= SVM_VMGEXIT_SNP_AP_CREATE;
ExitInfo2 = (UINT64)(UINTN)SaveArea;
Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB);
Ghcb = Msr.Ghcb;
VmgInit (Ghcb, &InterruptState);
Ghcb->SaveArea.Rax = SaveArea->SevFeatures;
VmgSetOffsetValid (Ghcb, GhcbRax);
VmgExitStatus = VmgExit (
Ghcb,
SVM_EXIT_SNP_AP_CREATION,
ExitInfo1,
ExitInfo2
);
VmgDone (Ghcb, InterruptState);
ASSERT (VmgExitStatus == 0);
if (VmgExitStatus != 0) {
RmpAdjustStatus = SevSnpRmpAdjust (
(EFI_PHYSICAL_ADDRESS)(UINTN)SaveArea,
FALSE
);
if (RmpAdjustStatus == 0) {
FreePages (SaveArea, 1);
} else {
DEBUG ((DEBUG_INFO, "SEV-SNP: RMPADJUST failed, leaking VMSA page\n"));
}
SaveArea = NULL;
}
if (CpuData->SevEsSaveArea) {
RmpAdjustStatus = SevSnpRmpAdjust (
(EFI_PHYSICAL_ADDRESS)(UINTN)CpuData->SevEsSaveArea,
FALSE
);
if (RmpAdjustStatus == 0) {
FreePages (CpuData->SevEsSaveArea, 1);
} else {
DEBUG ((DEBUG_INFO, "SEV-SNP: RMPADJUST failed, leaking VMSA page\n"));
}
}
CpuData->SevEsSaveArea = SaveArea;
}
/**
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;
UINT32 ApicId;
ASSERT (CpuMpData->MpCpuExchangeInfo->BufferStart < 0x100000);
CpuInfoInHob = (CPU_INFO_IN_HOB *)(UINTN)CpuMpData->CpuInfoInHob;
if (ProcessorNumber < 0) {
for (Index = 0; Index < CpuMpData->CpuCount; Index++) {
if (Index != CpuMpData->BspNumber) {
CpuData = &CpuMpData->CpuData[Index];
ApicId = CpuInfoInHob[Index].ApicId,
SevSnpCreateSaveArea (CpuMpData, CpuData, ApicId);
}
}
} else {
Index = (UINTN)ProcessorNumber;
CpuData = &CpuMpData->CpuData[Index];
ApicId = CpuInfoInHob[ProcessorNumber].ApicId,
SevSnpCreateSaveArea (CpuMpData, CpuData, ApicId);
}
}
/**
Issue RMPADJUST to adjust the VMSA attribute of an SEV-SNP page.
@param[in] PageAddress
@param[in] VmsaPage
@return RMPADJUST return value
**/
UINT32
SevSnpRmpAdjust (
IN EFI_PHYSICAL_ADDRESS PageAddress,
IN BOOLEAN VmsaPage
)
{
UINT64 Rdx;
//
// The RMPADJUST instruction is used to set or clear the VMSA bit for a
// page. The VMSA change is only made when running at VMPL0 and is ignored
// otherwise. If too low a target VMPL is specified, the instruction can
// succeed without changing the VMSA bit when not running at VMPL0. Using a
// target VMPL level of 1, RMPADJUST will return a FAIL_PERMISSION error if
// not running at VMPL0, thus ensuring that the VMSA bit is set appropriately
// when no error is returned.
//
Rdx = 1;
if (VmsaPage) {
Rdx |= RMPADJUST_VMSA_PAGE_BIT;
}
return AsmRmpAdjust ((UINT64)PageAddress, 0, Rdx);
}