123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- /** @file
- SEV-SNP Page Validation functions.
- Copyright (c) 2021 AMD Incorporated. All rights reserved.<BR>
- 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>
- #include <Library/CcExitLib.h>
- #include <Register/Amd/Ghcb.h>
- #include <Register/Amd/Msr.h>
- #include "SnpPageStateChange.h"
- #define IS_ALIGNED(x, y) ((((x) & (y - 1)) == 0))
- #define PAGES_PER_LARGE_ENTRY 512
- 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 ();
- }
- /**
- This function issues the PVALIDATE instruction to validate or invalidate the memory
- range specified. If PVALIDATE returns size mismatch then it retry validating with
- smaller page size.
- */
- STATIC
- VOID
- PvalidateRange (
- IN SNP_PAGE_STATE_CHANGE_INFO *Info,
- IN UINTN StartIndex,
- IN UINTN EndIndex,
- IN BOOLEAN Validate
- )
- {
- UINTN Address, RmpPageSize, Ret, i;
- for ( ; StartIndex <= EndIndex; StartIndex++) {
- //
- // Get the address and the page size from the Info.
- //
- Address = Info->Entry[StartIndex].GuestFrameNumber << EFI_PAGE_SHIFT;
- RmpPageSize = Info->Entry[StartIndex].PageSize;
- 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 (i = 0; i < PAGES_PER_LARGE_ENTRY; i++) {
- 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,
- __FUNCTION__,
- Validate ? "Validate" : "Invalidate",
- Address,
- Ret
- ));
- SnpPageStateFailureTerminate ();
- }
- }
- }
- STATIC
- EFI_PHYSICAL_ADDRESS
- BuildPageStateBuffer (
- IN EFI_PHYSICAL_ADDRESS BaseAddress,
- IN EFI_PHYSICAL_ADDRESS EndAddress,
- IN SEV_SNP_PAGE_STATE State,
- IN BOOLEAN UseLargeEntry,
- IN SNP_PAGE_STATE_CHANGE_INFO *Info
- )
- {
- EFI_PHYSICAL_ADDRESS NextAddress;
- UINTN i, RmpPageSize;
- // Clear the page state structure
- SetMem (Info, sizeof (*Info), 0);
- i = 0;
- NextAddress = EndAddress;
- //
- // Populate the page state entry structure
- //
- while ((BaseAddress < EndAddress) && (i < SNP_PAGE_STATE_MAX_ENTRY)) {
- //
- // Is this a 2MB aligned page? Check if we can use the Large RMP entry.
- //
- if (UseLargeEntry && IS_ALIGNED (BaseAddress, SIZE_2MB) &&
- ((EndAddress - BaseAddress) >= SIZE_2MB))
- {
- RmpPageSize = PvalidatePageSize2MB;
- NextAddress = BaseAddress + SIZE_2MB;
- } else {
- RmpPageSize = PvalidatePageSize4K;
- NextAddress = BaseAddress + EFI_PAGE_SIZE;
- }
- Info->Entry[i].GuestFrameNumber = BaseAddress >> EFI_PAGE_SHIFT;
- Info->Entry[i].PageSize = RmpPageSize;
- Info->Entry[i].Operation = MemoryStateToGhcbOp (State);
- Info->Entry[i].CurrentPage = 0;
- Info->Header.EndEntry = (UINT16)i;
- BaseAddress = NextAddress;
- i++;
- }
- return NextAddress;
- }
- STATIC
- VOID
- PageStateChangeVmgExit (
- IN GHCB *Ghcb,
- IN SNP_PAGE_STATE_CHANGE_INFO *Info
- )
- {
- EFI_STATUS Status;
- //
- // 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.
- //
- while (Info->Header.CurrentEntry <= Info->Header.EndEntry) {
- Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer;
- CcExitVmgSetOffsetValid (Ghcb, GhcbSwScratch);
- Status = CcExitVmgExit (Ghcb, SVM_EXIT_SNP_PAGE_STATE_CHANGE, 0, 0);
- //
- // 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 ();
- }
- }
- }
- /**
- 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,
- IN BOOLEAN UseLargeEntry
- )
- {
- GHCB *Ghcb;
- EFI_PHYSICAL_ADDRESS NextAddress, EndAddress;
- MSR_SEV_ES_GHCB_REGISTER Msr;
- BOOLEAN InterruptState;
- SNP_PAGE_STATE_CHANGE_INFO *Info;
- Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB);
- Ghcb = Msr.Ghcb;
- EndAddress = BaseAddress + EFI_PAGES_TO_SIZE (NumPages);
- DEBUG ((
- DEBUG_VERBOSE,
- "%a:%a Address 0x%Lx - 0x%Lx State = %a LargeEntry = %d\n",
- gEfiCallerBaseName,
- __FUNCTION__,
- BaseAddress,
- EndAddress,
- State == SevSnpPageShared ? "Shared" : "Private",
- UseLargeEntry
- ));
- while (BaseAddress < EndAddress) {
- UINTN CurrentEntry, EndEntry;
- //
- // Initialize the GHCB
- //
- CcExitVmgInit (Ghcb, &InterruptState);
- //
- // Build the page state structure
- //
- Info = (SNP_PAGE_STATE_CHANGE_INFO *)Ghcb->SharedBuffer;
- NextAddress = BuildPageStateBuffer (
- BaseAddress,
- EndAddress,
- State,
- UseLargeEntry,
- Info
- );
- //
- // Save the current and end entry from the page state structure. We need
- // it later.
- //
- CurrentEntry = Info->Header.CurrentEntry;
- EndEntry = Info->Header.EndEntry;
- //
- // 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) {
- PvalidateRange (Info, CurrentEntry, EndEntry, FALSE);
- }
- //
- // Invoke the page state change VMGEXIT.
- //
- PageStateChangeVmgExit (Ghcb, Info);
- //
- // 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) {
- PvalidateRange (Info, CurrentEntry, EndEntry, TRUE);
- }
- CcExitVmgDone (Ghcb, InterruptState);
- BaseAddress = NextAddress;
- }
- }
|