/** @file | |
SVSM Support Library. | |
Copyright (C) 2024, Advanced Micro Devices, Inc. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Base.h> | |
#include <Uefi.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/AmdSvsmLib.h> | |
#include <Register/Amd/Msr.h> | |
#include <Register/Amd/Svsm.h> | |
#define PAGES_PER_2MB_ENTRY 512 | |
/** | |
Issue a GHCB termination request for termination. | |
Request termination using the GHCB MSR protocol. | |
**/ | |
STATIC | |
VOID | |
SnpTerminate ( | |
VOID | |
) | |
{ | |
MSR_SEV_ES_GHCB_REGISTER Msr; | |
// | |
// Use the GHCB MSR Protocol to request termination by the hypervisor | |
// | |
Msr.Uint64 = 0; | |
Msr.GhcbTerminate.Function = GHCB_INFO_TERMINATE_REQUEST; | |
Msr.GhcbTerminate.ReasonCodeSet = GHCB_TERMINATE_GHCB; | |
Msr.GhcbTerminate.ReasonCode = GHCB_TERMINATE_GHCB_GENERAL; | |
AsmWriteMsr64 (MSR_SEV_ES_GHCB, Msr.Uint64); | |
AsmVmgExit (); | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
/** | |
Issue an SVSM request. | |
Invokes the SVSM to process a request on behalf of the guest. | |
@param[in,out] SvsmCallData Pointer to the SVSM call data | |
@return Contents of RAX upon return from VMGEXIT | |
**/ | |
STATIC | |
UINTN | |
SvsmMsrProtocol ( | |
IN OUT SVSM_CALL_DATA *SvsmCallData | |
) | |
{ | |
MSR_SEV_ES_GHCB_REGISTER Msr; | |
UINT64 CurrentMsr; | |
UINT8 Pending; | |
BOOLEAN InterruptState; | |
UINTN Ret; | |
do { | |
// | |
// Be sure that an interrupt can't cause a #VC while the GHCB MSR protocol | |
// is being used (#VC handler will ASSERT if lower 12-bits are not zero). | |
// | |
InterruptState = GetInterruptState (); | |
if (InterruptState) { | |
DisableInterrupts (); | |
} | |
Pending = 0; | |
SvsmCallData->CallPending = &Pending; | |
CurrentMsr = AsmReadMsr64 (MSR_SEV_ES_GHCB); | |
Msr.Uint64 = 0; | |
Msr.SnpVmplRequest.Function = GHCB_INFO_SNP_VMPL_REQUEST; | |
Msr.SnpVmplRequest.Vmpl = 0; | |
AsmWriteMsr64 (MSR_SEV_ES_GHCB, Msr.Uint64); | |
// | |
// Guest memory is used for the guest-SVSM communication, so fence the | |
// invocation of the VMGEXIT instruction to ensure VMSA accesses are | |
// synchronized properly. | |
// | |
MemoryFence (); | |
Ret = AsmVmgExitSvsm (SvsmCallData); | |
MemoryFence (); | |
Msr.Uint64 = AsmReadMsr64 (MSR_SEV_ES_GHCB); | |
AsmWriteMsr64 (MSR_SEV_ES_GHCB, CurrentMsr); | |
if (InterruptState) { | |
EnableInterrupts (); | |
} | |
if (Pending != 0) { | |
SnpTerminate (); | |
} | |
if ((Msr.SnpVmplResponse.Function != GHCB_INFO_SNP_VMPL_RESPONSE) || | |
(Msr.SnpVmplResponse.ErrorCode != 0)) | |
{ | |
SnpTerminate (); | |
} | |
} while (Ret == SVSM_ERR_INCOMPLETE || Ret == SVSM_ERR_BUSY); | |
return Ret; | |
} | |
/** | |
Report the presence of an Secure Virtual Services Module (SVSM). | |
Determines the presence of an SVSM. | |
@retval TRUE An SVSM is present | |
@retval FALSE An SVSM is not present | |
**/ | |
BOOLEAN | |
EFIAPI | |
AmdSvsmIsSvsmPresent ( | |
VOID | |
) | |
{ | |
SVSM_INFORMATION *SvsmInfo; | |
SvsmInfo = (SVSM_INFORMATION *)(UINTN)PcdGet32 (PcdOvmfSnpSecretsBase); | |
return (SvsmInfo != NULL && SvsmInfo->SvsmSize != 0); | |
} | |
/** | |
Report the VMPL level at which the SEV-SNP guest is running. | |
Determines the VMPL level at which the guest is running. If an SVSM is | |
not present, then it must be VMPL0, otherwise return what is reported | |
by the SVSM. | |
@return The VMPL level | |
**/ | |
UINT8 | |
EFIAPI | |
AmdSvsmSnpGetVmpl ( | |
VOID | |
) | |
{ | |
SVSM_INFORMATION *SvsmInfo; | |
SvsmInfo = (SVSM_INFORMATION *)(UINTN)PcdGet32 (PcdOvmfSnpSecretsBase); | |
return AmdSvsmIsSvsmPresent () ? SvsmInfo->SvsmGuestVmpl : 0; | |
} | |
/** | |
Report the Calling Area address (CAA) for the BSP of the SEV-SNP guest. | |
If an SVSM is present, the CAA for the BSP is returned. | |
@return The CAA | |
**/ | |
UINT64 | |
EFIAPI | |
AmdSvsmSnpGetCaa ( | |
VOID | |
) | |
{ | |
SVSM_INFORMATION *SvsmInfo; | |
SvsmInfo = (SVSM_INFORMATION *)(UINTN)PcdGet32 (PcdOvmfSnpSecretsBase); | |
return AmdSvsmIsSvsmPresent () ? SvsmInfo->SvsmCaa : 0; | |
} | |
/** | |
Issue an SVSM request to perform the PVALIDATE instruction. | |
Invokes the SVSM to process the PVALIDATE instruction on behalf of the | |
guest to validate or invalidate the memory range specified. | |
@param[in] Info Pointer to a page state change structure | |
**/ | |
STATIC | |
VOID | |
SvsmPvalidate ( | |
IN SNP_PAGE_STATE_CHANGE_INFO *Info | |
) | |
{ | |
SVSM_CALL_DATA SvsmCallData; | |
SVSM_CAA *Caa; | |
SVSM_PVALIDATE_REQUEST *Request; | |
SVSM_FUNCTION Function; | |
BOOLEAN Validate; | |
UINTN Entry; | |
UINTN EntryLimit; | |
UINTN Index; | |
UINTN EndIndex; | |
UINT64 Gfn; | |
UINT64 GfnEnd; | |
UINTN Ret; | |
Caa = (SVSM_CAA *)AmdSvsmSnpGetCaa (); | |
ZeroMem (Caa->SvsmBuffer, sizeof (Caa->SvsmBuffer)); | |
Function.Id.Protocol = 0; | |
Function.Id.CallId = 1; | |
Request = (SVSM_PVALIDATE_REQUEST *)Caa->SvsmBuffer; | |
EntryLimit = ((sizeof (Caa->SvsmBuffer) - sizeof (*Request)) / | |
sizeof (Request->Entry[0])) - 1; | |
SvsmCallData.Caa = Caa; | |
SvsmCallData.RaxIn = Function.Uint64; | |
SvsmCallData.RcxIn = (UINT64)(UINTN)Request; | |
Entry = 0; | |
Index = Info->Header.CurrentEntry; | |
EndIndex = Info->Header.EndEntry; | |
while (Index <= EndIndex) { | |
Validate = Info->Entry[Index].Operation == SNP_PAGE_STATE_PRIVATE; | |
Request->Header.Entries++; | |
Request->Entry[Entry].Bits.PageSize = Info->Entry[Index].PageSize; | |
Request->Entry[Entry].Bits.Action = (Validate == TRUE) ? 1 : 0; | |
Request->Entry[Entry].Bits.IgnoreCf = 0; | |
Request->Entry[Entry].Bits.Address = Info->Entry[Index].GuestFrameNumber; | |
Entry++; | |
if ((Entry > EntryLimit) || (Index == EndIndex)) { | |
Ret = SvsmMsrProtocol (&SvsmCallData); | |
if ((Ret == SVSM_ERR_PVALIDATE_FAIL_SIZE_MISMATCH) && | |
(Request->Entry[Request->Header.Next].Bits.PageSize != 0)) | |
{ | |
// Calculate the Index of the entry after the entry that failed | |
// before clearing the buffer so that processing can continue | |
// from that point | |
Index = Index - (Entry - Request->Header.Next) + 2; | |
// Obtain the failing GFN before clearing the buffer | |
Gfn = Request->Entry[Request->Header.Next].Bits.Address; | |
// Clear the buffer in prep for creating all new entries | |
ZeroMem (Caa->SvsmBuffer, sizeof (Caa->SvsmBuffer)); | |
Entry = 0; | |
GfnEnd = Gfn + PAGES_PER_2MB_ENTRY - 1; | |
for ( ; Gfn <= GfnEnd; Gfn++) { | |
Request->Header.Entries++; | |
Request->Entry[Entry].Bits.PageSize = 0; | |
Request->Entry[Entry].Bits.Action = (Validate == TRUE) ? 1 : 0; | |
Request->Entry[Entry].Bits.IgnoreCf = 0; | |
Request->Entry[Entry].Bits.Address = Gfn; | |
Entry++; | |
if ((Entry > EntryLimit) || (Gfn == GfnEnd)) { | |
Ret = SvsmMsrProtocol (&SvsmCallData); | |
if (Ret != 0) { | |
SnpTerminate (); | |
} | |
ZeroMem (Caa->SvsmBuffer, sizeof (Caa->SvsmBuffer)); | |
Entry = 0; | |
} | |
} | |
continue; | |
} | |
if (Ret != 0) { | |
SnpTerminate (); | |
} | |
ZeroMem (Caa->SvsmBuffer, sizeof (Caa->SvsmBuffer)); | |
Entry = 0; | |
} | |
Index++; | |
} | |
} | |
/** | |
Perform a native PVALIDATE operation for the page ranges specified. | |
Validate or rescind the validation of the specified pages. | |
@param[in] Info Pointer to a page state change structure | |
**/ | |
STATIC | |
VOID | |
BasePvalidate ( | |
IN SNP_PAGE_STATE_CHANGE_INFO *Info | |
) | |
{ | |
UINTN RmpPageSize; | |
UINTN StartIndex; | |
UINTN EndIndex; | |
UINTN Index; | |
UINTN Ret; | |
EFI_PHYSICAL_ADDRESS Address; | |
BOOLEAN Validate; | |
StartIndex = Info->Header.CurrentEntry; | |
EndIndex = Info->Header.EndEntry; | |
for ( ; StartIndex <= EndIndex; StartIndex++) { | |
// | |
// Get the address and the page size from the Info. | |
// | |
Address = ((EFI_PHYSICAL_ADDRESS)Info->Entry[StartIndex].GuestFrameNumber) << EFI_PAGE_SHIFT; | |
RmpPageSize = Info->Entry[StartIndex].PageSize; | |
Validate = Info->Entry[StartIndex].Operation == SNP_PAGE_STATE_PRIVATE; | |
Ret = AsmPvalidate (RmpPageSize, Validate, Address); | |
// | |
// If we fail to validate due to size mismatch then try with the | |
// smaller page size. This senario will occur if the backing page in | |
// the RMP entry is 4K and we are validating it as a 2MB. | |
// | |
if ((Ret == PVALIDATE_RET_SIZE_MISMATCH) && (RmpPageSize == PvalidatePageSize2MB)) { | |
for (Index = 0; Index < PAGES_PER_2MB_ENTRY; Index++) { | |
Ret = AsmPvalidate (PvalidatePageSize4K, Validate, Address); | |
if (Ret) { | |
break; | |
} | |
Address = Address + EFI_PAGE_SIZE; | |
} | |
} | |
// | |
// If validation failed then do not continue. | |
// | |
if (Ret) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a:%a: Failed to %a address 0x%Lx Error code %d\n", | |
gEfiCallerBaseName, | |
__func__, | |
Validate ? "Validate" : "Invalidate", | |
Address, | |
Ret | |
)); | |
SnpTerminate (); | |
} | |
} | |
} | |
/** | |
Perform a PVALIDATE operation for the page ranges specified. | |
Validate or rescind the validation of the specified pages. | |
@param[in] Info Pointer to a page state change structure | |
**/ | |
VOID | |
EFIAPI | |
AmdSvsmSnpPvalidate ( | |
IN SNP_PAGE_STATE_CHANGE_INFO *Info | |
) | |
{ | |
AmdSvsmIsSvsmPresent () ? SvsmPvalidate (Info) : BasePvalidate (Info); | |
} | |
/** | |
Perform an RMPADJUST operation to alter the VMSA setting of a page. | |
Add or remove the VMSA attribute for a page. | |
@param[in] Vmsa Pointer to an SEV-ES save area page | |
@param[in] ApicId APIC ID associated with the VMSA | |
@param[in] SetVmsa Boolean indicator as to whether to set or | |
or clear the VMSA setting for the page | |
@retval EFI_SUCCESS RMPADJUST operation successful | |
@retval EFI_UNSUPPORTED Operation is not supported | |
@retval EFI_INVALID_PARAMETER RMPADJUST operation failed, an invalid | |
parameter was supplied | |
**/ | |
STATIC | |
EFI_STATUS | |
SvsmVmsaRmpAdjust ( | |
IN SEV_ES_SAVE_AREA *Vmsa, | |
IN UINT32 ApicId, | |
IN BOOLEAN SetVmsa | |
) | |
{ | |
SVSM_CALL_DATA SvsmCallData; | |
SVSM_FUNCTION Function; | |
UINTN Ret; | |
SvsmCallData.Caa = (SVSM_CAA *)AmdSvsmSnpGetCaa (); | |
Function.Id.Protocol = 0; | |
if (SetVmsa) { | |
Function.Id.CallId = 2; | |
SvsmCallData.RaxIn = Function.Uint64; | |
SvsmCallData.RcxIn = (UINT64)(UINTN)Vmsa; | |
SvsmCallData.RdxIn = (UINT64)(UINTN)Vmsa + SIZE_4KB; | |
SvsmCallData.R8In = ApicId; | |
} else { | |
Function.Id.CallId = 3; | |
SvsmCallData.RaxIn = Function.Uint64; | |
SvsmCallData.RcxIn = (UINT64)(UINTN)Vmsa; | |
} | |
Ret = SvsmMsrProtocol (&SvsmCallData); | |
return (Ret == 0) ? EFI_SUCCESS : EFI_INVALID_PARAMETER; | |
} | |
/** | |
Perform a native RMPADJUST operation to alter the VMSA setting of a page. | |
Add or remove the VMSA attribute for a page. | |
@param[in] Vmsa Pointer to an SEV-ES save area page | |
@param[in] SetVmsa Boolean indicator as to whether to set or | |
or clear the VMSA setting for the page | |
@retval EFI_SUCCESS RMPADJUST operation successful | |
@retval EFI_INVALID_PARAMETER RMPADJUST operation failed, an invalid | |
parameter was supplied | |
**/ | |
STATIC | |
EFI_STATUS | |
BaseVmsaRmpAdjust ( | |
IN SEV_ES_SAVE_AREA *Vmsa, | |
IN BOOLEAN SetVmsa | |
) | |
{ | |
UINT64 Rdx; | |
UINT32 Ret; | |
// | |
// 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 (SetVmsa) { | |
Rdx |= RMPADJUST_VMSA_PAGE_BIT; | |
} | |
Ret = AsmRmpAdjust ((UINT64)(UINTN)Vmsa, 0, Rdx); | |
return (Ret == 0) ? EFI_SUCCESS : EFI_INVALID_PARAMETER; | |
} | |
/** | |
Perform an RMPADJUST operation to alter the VMSA setting of a page. | |
Add or remove the VMSA attribute for a page. | |
@param[in] Vmsa Pointer to an SEV-ES save area page | |
@param[in] ApicId APIC ID associated with the VMSA | |
@param[in] SetVmsa Boolean indicator as to whether to set or | |
or clear the VMSA setting for the page | |
@retval EFI_SUCCESS RMPADJUST operation successful | |
@retval EFI_UNSUPPORTED Operation is not supported | |
@retval EFI_INVALID_PARAMETER RMPADJUST operation failed, an invalid | |
parameter was supplied | |
**/ | |
EFI_STATUS | |
EFIAPI | |
AmdSvsmSnpVmsaRmpAdjust ( | |
IN SEV_ES_SAVE_AREA *Vmsa, | |
IN UINT32 ApicId, | |
IN BOOLEAN SetVmsa | |
) | |
{ | |
return AmdSvsmIsSvsmPresent () ? SvsmVmsaRmpAdjust (Vmsa, ApicId, SetVmsa) | |
: BaseVmsaRmpAdjust (Vmsa, SetVmsa); | |
} |