2021-12-09 04:27:42 +01:00
|
|
|
/** @file
|
|
|
|
|
|
|
|
SEV-SNP Page Validation functions.
|
|
|
|
|
2024-03-08 16:30:44 +01:00
|
|
|
Copyright (c) 2021 - 2024, AMD Incorporated. All rights reserved.<BR>
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
#include <Uefi/UefiBaseType.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
|
|
#include <Library/BaseMemoryLib.h>
|
|
|
|
#include <Library/MemEncryptSevLib.h>
|
|
|
|
#include <Library/DebugLib.h>
|
2022-11-07 07:30:26 +01:00
|
|
|
#include <Library/CcExitLib.h>
|
2024-03-08 16:32:10 +01:00
|
|
|
#include <Library/AmdSvsmLib.h>
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
#include <Register/Amd/Ghcb.h>
|
|
|
|
#include <Register/Amd/Msr.h>
|
|
|
|
|
|
|
|
#include "SnpPageStateChange.h"
|
|
|
|
|
|
|
|
STATIC
|
|
|
|
UINTN
|
|
|
|
MemoryStateToGhcbOp (
|
|
|
|
IN SEV_SNP_PAGE_STATE State
|
|
|
|
)
|
|
|
|
{
|
|
|
|
UINTN Cmd;
|
|
|
|
|
|
|
|
switch (State) {
|
|
|
|
case SevSnpPageShared: Cmd = SNP_PAGE_STATE_SHARED;
|
|
|
|
break;
|
|
|
|
case SevSnpPagePrivate: Cmd = SNP_PAGE_STATE_PRIVATE;
|
|
|
|
break;
|
|
|
|
default: ASSERT (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Cmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
|
|
SnpPageStateFailureTerminate (
|
|
|
|
VOID
|
|
|
|
)
|
|
|
|
{
|
|
|
|
MSR_SEV_ES_GHCB_REGISTER Msr;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Use the GHCB MSR Protocol to request termination by the hypervisor
|
|
|
|
//
|
|
|
|
Msr.GhcbPhysicalAddress = 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.GhcbPhysicalAddress);
|
|
|
|
|
|
|
|
AsmVmgExit ();
|
|
|
|
|
|
|
|
ASSERT (FALSE);
|
|
|
|
CpuDeadLoop ();
|
|
|
|
}
|
|
|
|
|
|
|
|
STATIC
|
|
|
|
EFI_PHYSICAL_ADDRESS
|
|
|
|
BuildPageStateBuffer (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS BaseAddress,
|
|
|
|
IN EFI_PHYSICAL_ADDRESS EndAddress,
|
|
|
|
IN SEV_SNP_PAGE_STATE State,
|
|
|
|
IN BOOLEAN UseLargeEntry,
|
2024-03-08 16:30:49 +01:00
|
|
|
IN SNP_PAGE_STATE_CHANGE_INFO *Info,
|
|
|
|
IN UINTN InfoSize
|
2021-12-09 04:27:42 +01:00
|
|
|
)
|
|
|
|
{
|
|
|
|
EFI_PHYSICAL_ADDRESS NextAddress;
|
2024-03-08 16:30:44 +01:00
|
|
|
UINTN RmpPageSize;
|
|
|
|
UINTN Index;
|
2024-03-08 16:30:49 +01:00
|
|
|
UINTN IndexMax;
|
2024-03-08 16:31:11 +01:00
|
|
|
UINTN PscIndexMax;
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
// Clear the page state structure
|
2024-03-08 16:30:49 +01:00
|
|
|
SetMem (Info, InfoSize, 0);
|
2021-12-09 04:27:42 +01:00
|
|
|
|
2024-03-08 16:30:44 +01:00
|
|
|
Index = 0;
|
2024-03-08 16:30:49 +01:00
|
|
|
IndexMax = (InfoSize - sizeof (Info->Header)) / sizeof (Info->Entry[0]);
|
2021-12-09 04:27:42 +01:00
|
|
|
NextAddress = EndAddress;
|
|
|
|
|
2024-03-08 16:31:11 +01:00
|
|
|
//
|
|
|
|
// Make the use of the work area as efficient as possible relative to
|
|
|
|
// exiting from the guest to the hypervisor. Maximize the number of entries
|
|
|
|
// that can be processed per exit.
|
|
|
|
//
|
|
|
|
PscIndexMax = (IndexMax / SNP_PAGE_STATE_MAX_ENTRY) * SNP_PAGE_STATE_MAX_ENTRY;
|
|
|
|
if (PscIndexMax > 0) {
|
|
|
|
IndexMax = MIN (IndexMax, PscIndexMax);
|
|
|
|
}
|
|
|
|
|
2021-12-09 04:27:42 +01:00
|
|
|
//
|
|
|
|
// Populate the page state entry structure
|
|
|
|
//
|
2024-03-08 16:30:49 +01:00
|
|
|
while ((BaseAddress < EndAddress) && (Index < IndexMax)) {
|
2021-12-09 04:27:42 +01:00
|
|
|
//
|
|
|
|
// Is this a 2MB aligned page? Check if we can use the Large RMP entry.
|
|
|
|
//
|
2023-03-22 08:02:42 +01:00
|
|
|
if (UseLargeEntry && IS_ALIGNED (BaseAddress, SIZE_2MB) &&
|
2021-12-09 04:27:42 +01:00
|
|
|
((EndAddress - BaseAddress) >= SIZE_2MB))
|
|
|
|
{
|
|
|
|
RmpPageSize = PvalidatePageSize2MB;
|
|
|
|
NextAddress = BaseAddress + SIZE_2MB;
|
|
|
|
} else {
|
|
|
|
RmpPageSize = PvalidatePageSize4K;
|
|
|
|
NextAddress = BaseAddress + EFI_PAGE_SIZE;
|
|
|
|
}
|
|
|
|
|
2024-03-08 16:30:44 +01:00
|
|
|
Info->Entry[Index].GuestFrameNumber = BaseAddress >> EFI_PAGE_SHIFT;
|
|
|
|
Info->Entry[Index].PageSize = RmpPageSize;
|
|
|
|
Info->Entry[Index].Operation = MemoryStateToGhcbOp (State);
|
|
|
|
Info->Entry[Index].CurrentPage = 0;
|
|
|
|
Info->Header.EndEntry = (UINT16)Index;
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
BaseAddress = NextAddress;
|
2024-03-08 16:30:44 +01:00
|
|
|
Index++;
|
2021-12-09 04:27:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return NextAddress;
|
|
|
|
}
|
|
|
|
|
|
|
|
STATIC
|
|
|
|
VOID
|
|
|
|
PageStateChangeVmgExit (
|
2024-03-08 16:31:11 +01:00
|
|
|
IN GHCB *Ghcb,
|
|
|
|
IN SNP_PAGE_STATE_ENTRY *Start,
|
|
|
|
IN UINT16 Count
|
2021-12-09 04:27:42 +01:00
|
|
|
)
|
|
|
|
{
|
2024-03-08 16:31:11 +01:00
|
|
|
SNP_PAGE_STATE_CHANGE_INFO *GhcbInfo;
|
|
|
|
EFI_STATUS Status;
|
|
|
|
BOOLEAN InterruptState;
|
|
|
|
|
|
|
|
ASSERT (Count <= SNP_PAGE_STATE_MAX_ENTRY);
|
|
|
|
if (Count > SNP_PAGE_STATE_MAX_ENTRY) {
|
|
|
|
SnpPageStateFailureTerminate ();
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Initialize the GHCB
|
|
|
|
//
|
|
|
|
CcExitVmgInit (Ghcb, &InterruptState);
|
|
|
|
|
|
|
|
GhcbInfo = (SNP_PAGE_STATE_CHANGE_INFO *)Ghcb->SharedBuffer;
|
|
|
|
GhcbInfo->Header.CurrentEntry = 0;
|
|
|
|
GhcbInfo->Header.EndEntry = Count - 1;
|
|
|
|
CopyMem (GhcbInfo->Entry, Start, sizeof (*Start) * Count);
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// As per the GHCB specification, the hypervisor can resume the guest before
|
|
|
|
// processing all the entries. Checks whether all the entries are processed.
|
|
|
|
//
|
|
|
|
// The stragtegy here is to wait for the hypervisor to change the page
|
|
|
|
// state in the RMP table before guest access the memory pages. If the
|
|
|
|
// page state was not successful, then later memory access will result
|
|
|
|
// in the crash.
|
|
|
|
//
|
2024-03-08 16:31:11 +01:00
|
|
|
while (GhcbInfo->Header.CurrentEntry <= GhcbInfo->Header.EndEntry) {
|
2021-12-09 04:27:42 +01:00
|
|
|
Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer;
|
2022-11-07 08:50:11 +01:00
|
|
|
CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch);
|
2021-12-09 04:27:42 +01:00
|
|
|
|
2022-11-07 08:50:11 +01:00
|
|
|
Status = CcExitVmgExit (Ghcb, SVM_EXIT_SNP_PAGE_STATE_CHANGE, 0, 0);
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// The Page State Change VMGEXIT can pass the failure through the
|
|
|
|
// ExitInfo2. Lets check both the return value as well as ExitInfo2.
|
|
|
|
//
|
|
|
|
if ((Status != 0) || (Ghcb->SaveArea.SwExitInfo2)) {
|
|
|
|
SnpPageStateFailureTerminate ();
|
|
|
|
}
|
|
|
|
}
|
2024-03-08 16:31:11 +01:00
|
|
|
|
|
|
|
CcExitVmgDone (Ghcb, InterruptState);
|
|
|
|
}
|
|
|
|
|
|
|
|
STATIC
|
|
|
|
VOID
|
|
|
|
PageStateChange (
|
|
|
|
IN SNP_PAGE_STATE_CHANGE_INFO *Info
|
|
|
|
)
|
|
|
|
{
|
|
|
|
GHCB *Ghcb;
|
|
|
|
MSR_SEV_ES_GHCB_REGISTER Msr;
|
|
|
|
SNP_PAGE_STATE_HEADER *Header;
|
|
|
|
UINT16 Index;
|
|
|
|
UINT16 Count;
|
|
|
|
|
|
|
|
Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB);
|
|
|
|
Ghcb = Msr.Ghcb;
|
|
|
|
|
|
|
|
Header = &Info->Header;
|
|
|
|
|
|
|
|
for (Index = Header->CurrentEntry; Index <= Header->EndEntry;) {
|
|
|
|
Count = MIN (Header->EndEntry - Index + 1, SNP_PAGE_STATE_MAX_ENTRY);
|
|
|
|
|
|
|
|
PageStateChangeVmgExit (Ghcb, &Info->Entry[Index], Count);
|
|
|
|
|
|
|
|
Index += Count;
|
|
|
|
}
|
2021-12-09 04:27:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
The function is used to set the page state when SEV-SNP is active. The page state
|
|
|
|
transition consist of changing the page ownership in the RMP table, and using the
|
|
|
|
PVALIDATE instruction to update the Validated bit in RMP table.
|
|
|
|
|
|
|
|
When the UseLargeEntry is set to TRUE, then function will try to use the large RMP
|
|
|
|
entry (whevever possible).
|
|
|
|
*/
|
|
|
|
VOID
|
|
|
|
InternalSetPageState (
|
|
|
|
IN EFI_PHYSICAL_ADDRESS BaseAddress,
|
|
|
|
IN UINTN NumPages,
|
|
|
|
IN SEV_SNP_PAGE_STATE State,
|
2024-03-08 16:31:11 +01:00
|
|
|
IN BOOLEAN UseLargeEntry,
|
|
|
|
IN VOID *PscBuffer,
|
|
|
|
IN UINTN PscBufferSize
|
2021-12-09 04:27:42 +01:00
|
|
|
)
|
|
|
|
{
|
|
|
|
EFI_PHYSICAL_ADDRESS NextAddress, EndAddress;
|
|
|
|
SNP_PAGE_STATE_CHANGE_INFO *Info;
|
|
|
|
|
|
|
|
EndAddress = BaseAddress + EFI_PAGES_TO_SIZE (NumPages);
|
|
|
|
|
|
|
|
DEBUG ((
|
|
|
|
DEBUG_VERBOSE,
|
|
|
|
"%a:%a Address 0x%Lx - 0x%Lx State = %a LargeEntry = %d\n",
|
|
|
|
gEfiCallerBaseName,
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2021-12-09 04:27:42 +01:00
|
|
|
BaseAddress,
|
|
|
|
EndAddress,
|
|
|
|
State == SevSnpPageShared ? "Shared" : "Private",
|
|
|
|
UseLargeEntry
|
|
|
|
));
|
|
|
|
|
2024-03-08 16:31:11 +01:00
|
|
|
Info = (SNP_PAGE_STATE_CHANGE_INFO *)PscBuffer;
|
2021-12-09 04:27:42 +01:00
|
|
|
|
2024-03-08 16:31:11 +01:00
|
|
|
for (NextAddress = BaseAddress; NextAddress < EndAddress;) {
|
2021-12-09 04:27:42 +01:00
|
|
|
//
|
|
|
|
// Build the page state structure
|
|
|
|
//
|
|
|
|
NextAddress = BuildPageStateBuffer (
|
2024-03-08 16:31:11 +01:00
|
|
|
NextAddress,
|
2021-12-09 04:27:42 +01:00
|
|
|
EndAddress,
|
|
|
|
State,
|
|
|
|
UseLargeEntry,
|
2024-03-08 16:31:11 +01:00
|
|
|
PscBuffer,
|
|
|
|
PscBufferSize
|
2021-12-09 04:27:42 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
//
|
|
|
|
// If the caller requested to change the page state to shared then
|
|
|
|
// invalidate the pages before making the page shared in the RMP table.
|
|
|
|
//
|
|
|
|
if (State == SevSnpPageShared) {
|
2024-03-08 16:32:10 +01:00
|
|
|
AmdSvsmSnpPvalidate (Info);
|
2021-12-09 04:27:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Invoke the page state change VMGEXIT.
|
|
|
|
//
|
2024-03-08 16:31:11 +01:00
|
|
|
PageStateChange (Info);
|
2021-12-09 04:27:42 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// If the caller requested to change the page state to private then
|
|
|
|
// validate the pages after it has been added in the RMP table.
|
|
|
|
//
|
|
|
|
if (State == SevSnpPagePrivate) {
|
2024-03-08 16:32:10 +01:00
|
|
|
AmdSvsmSnpPvalidate (Info);
|
2021-12-09 04:27:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|