--- /dev/null
+/** @file\r
+\r
+ SEV-SNP Page Validation functions.\r
+\r
+ Copyright (c) 2021 AMD Incorporated. All rights reserved.<BR>\r
+\r
+ SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include <Uefi/UefiBaseType.h>\r
+#include <Library/BaseLib.h>\r
+#include <Library/BaseMemoryLib.h>\r
+#include <Library/MemEncryptSevLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/VmgExitLib.h>\r
+\r
+#include <Register/Amd/Ghcb.h>\r
+#include <Register/Amd/Msr.h>\r
+\r
+#include "SnpPageStateChange.h"\r
+\r
+#define IS_ALIGNED(x, y) ((((x) & (y - 1)) == 0))\r
+#define PAGES_PER_LARGE_ENTRY 512\r
+\r
+STATIC\r
+UINTN\r
+MemoryStateToGhcbOp (\r
+ IN SEV_SNP_PAGE_STATE State\r
+ )\r
+{\r
+ UINTN Cmd;\r
+\r
+ switch (State) {\r
+ case SevSnpPageShared: Cmd = SNP_PAGE_STATE_SHARED;\r
+ break;\r
+ case SevSnpPagePrivate: Cmd = SNP_PAGE_STATE_PRIVATE;\r
+ break;\r
+ default: ASSERT (0);\r
+ }\r
+\r
+ return Cmd;\r
+}\r
+\r
+STATIC\r
+VOID\r
+SnpPageStateFailureTerminate (\r
+ VOID\r
+ )\r
+{\r
+ MSR_SEV_ES_GHCB_REGISTER Msr;\r
+\r
+ //\r
+ // Use the GHCB MSR Protocol to request termination by the hypervisor\r
+ //\r
+ Msr.GhcbPhysicalAddress = 0;\r
+ Msr.GhcbTerminate.Function = GHCB_INFO_TERMINATE_REQUEST;\r
+ Msr.GhcbTerminate.ReasonCodeSet = GHCB_TERMINATE_GHCB;\r
+ Msr.GhcbTerminate.ReasonCode = GHCB_TERMINATE_GHCB_GENERAL;\r
+ AsmWriteMsr64 (MSR_SEV_ES_GHCB, Msr.GhcbPhysicalAddress);\r
+\r
+ AsmVmgExit ();\r
+\r
+ ASSERT (FALSE);\r
+ CpuDeadLoop ();\r
+}\r
+\r
+/**\r
+ This function issues the PVALIDATE instruction to validate or invalidate the memory\r
+ range specified. If PVALIDATE returns size mismatch then it retry validating with\r
+ smaller page size.\r
+\r
+ */\r
+STATIC\r
+VOID\r
+PvalidateRange (\r
+ IN SNP_PAGE_STATE_CHANGE_INFO *Info,\r
+ IN UINTN StartIndex,\r
+ IN UINTN EndIndex,\r
+ IN BOOLEAN Validate\r
+ )\r
+{\r
+ UINTN Address, RmpPageSize, Ret, i;\r
+\r
+ for ( ; StartIndex <= EndIndex; StartIndex++) {\r
+ //\r
+ // Get the address and the page size from the Info.\r
+ //\r
+ Address = Info->Entry[StartIndex].GuestFrameNumber << EFI_PAGE_SHIFT;\r
+ RmpPageSize = Info->Entry[StartIndex].PageSize;\r
+\r
+ Ret = AsmPvalidate (RmpPageSize, Validate, Address);\r
+\r
+ //\r
+ // If we fail to validate due to size mismatch then try with the\r
+ // smaller page size. This senario will occur if the backing page in\r
+ // the RMP entry is 4K and we are validating it as a 2MB.\r
+ //\r
+ if ((Ret == PVALIDATE_RET_SIZE_MISMATCH) && (RmpPageSize == PvalidatePageSize2MB)) {\r
+ for (i = 0; i < PAGES_PER_LARGE_ENTRY; i++) {\r
+ Ret = AsmPvalidate (PvalidatePageSize4K, Validate, Address);\r
+ if (Ret) {\r
+ break;\r
+ }\r
+\r
+ Address = Address + EFI_PAGE_SIZE;\r
+ }\r
+ }\r
+\r
+ //\r
+ // If validation failed then do not continue.\r
+ //\r
+ if (Ret) {\r
+ DEBUG ((\r
+ DEBUG_ERROR,\r
+ "%a:%a: Failed to %a address 0x%Lx Error code %d\n",\r
+ gEfiCallerBaseName,\r
+ __FUNCTION__,\r
+ Validate ? "Validate" : "Invalidate",\r
+ Address,\r
+ Ret\r
+ ));\r
+ SnpPageStateFailureTerminate ();\r
+ }\r
+ }\r
+}\r
+\r
+STATIC\r
+EFI_PHYSICAL_ADDRESS\r
+BuildPageStateBuffer (\r
+ IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
+ IN EFI_PHYSICAL_ADDRESS EndAddress,\r
+ IN SEV_SNP_PAGE_STATE State,\r
+ IN BOOLEAN UseLargeEntry,\r
+ IN SNP_PAGE_STATE_CHANGE_INFO *Info\r
+ )\r
+{\r
+ EFI_PHYSICAL_ADDRESS NextAddress;\r
+ UINTN i, RmpPageSize;\r
+\r
+ // Clear the page state structure\r
+ SetMem (Info, sizeof (*Info), 0);\r
+\r
+ i = 0;\r
+ NextAddress = EndAddress;\r
+\r
+ //\r
+ // Populate the page state entry structure\r
+ //\r
+ while ((BaseAddress < EndAddress) && (i < SNP_PAGE_STATE_MAX_ENTRY)) {\r
+ //\r
+ // Is this a 2MB aligned page? Check if we can use the Large RMP entry.\r
+ //\r
+ if (UseLargeEntry && IS_ALIGNED (BaseAddress, SIZE_2MB) &&\r
+ ((EndAddress - BaseAddress) >= SIZE_2MB))\r
+ {\r
+ RmpPageSize = PvalidatePageSize2MB;\r
+ NextAddress = BaseAddress + SIZE_2MB;\r
+ } else {\r
+ RmpPageSize = PvalidatePageSize4K;\r
+ NextAddress = BaseAddress + EFI_PAGE_SIZE;\r
+ }\r
+\r
+ Info->Entry[i].GuestFrameNumber = BaseAddress >> EFI_PAGE_SHIFT;\r
+ Info->Entry[i].PageSize = RmpPageSize;\r
+ Info->Entry[i].Operation = MemoryStateToGhcbOp (State);\r
+ Info->Entry[i].CurrentPage = 0;\r
+ Info->Header.EndEntry = (UINT16)i;\r
+\r
+ BaseAddress = NextAddress;\r
+ i++;\r
+ }\r
+\r
+ return NextAddress;\r
+}\r
+\r
+STATIC\r
+VOID\r
+PageStateChangeVmgExit (\r
+ IN GHCB *Ghcb,\r
+ IN SNP_PAGE_STATE_CHANGE_INFO *Info\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+\r
+ //\r
+ // As per the GHCB specification, the hypervisor can resume the guest before\r
+ // processing all the entries. Checks whether all the entries are processed.\r
+ //\r
+ // The stragtegy here is to wait for the hypervisor to change the page\r
+ // state in the RMP table before guest access the memory pages. If the\r
+ // page state was not successful, then later memory access will result\r
+ // in the crash.\r
+ //\r
+ while (Info->Header.CurrentEntry <= Info->Header.EndEntry) {\r
+ Ghcb->SaveArea.SwScratch = (UINT64)Ghcb->SharedBuffer;\r
+ VmgSetOffsetValid (Ghcb, GhcbSwScratch);\r
+\r
+ Status = VmgExit (Ghcb, SVM_EXIT_SNP_PAGE_STATE_CHANGE, 0, 0);\r
+\r
+ //\r
+ // The Page State Change VMGEXIT can pass the failure through the\r
+ // ExitInfo2. Lets check both the return value as well as ExitInfo2.\r
+ //\r
+ if ((Status != 0) || (Ghcb->SaveArea.SwExitInfo2)) {\r
+ SnpPageStateFailureTerminate ();\r
+ }\r
+ }\r
+}\r
+\r
+/**\r
+ The function is used to set the page state when SEV-SNP is active. The page state\r
+ transition consist of changing the page ownership in the RMP table, and using the\r
+ PVALIDATE instruction to update the Validated bit in RMP table.\r
+\r
+ When the UseLargeEntry is set to TRUE, then function will try to use the large RMP\r
+ entry (whevever possible).\r
+ */\r
+VOID\r
+InternalSetPageState (\r
+ IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
+ IN UINTN NumPages,\r
+ IN SEV_SNP_PAGE_STATE State,\r
+ IN BOOLEAN UseLargeEntry\r
+ )\r
+{\r
+ GHCB *Ghcb;\r
+ EFI_PHYSICAL_ADDRESS NextAddress, EndAddress;\r
+ MSR_SEV_ES_GHCB_REGISTER Msr;\r
+ BOOLEAN InterruptState;\r
+ SNP_PAGE_STATE_CHANGE_INFO *Info;\r
+\r
+ Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB);\r
+ Ghcb = Msr.Ghcb;\r
+\r
+ EndAddress = BaseAddress + EFI_PAGES_TO_SIZE (NumPages);\r
+\r
+ DEBUG ((\r
+ DEBUG_VERBOSE,\r
+ "%a:%a Address 0x%Lx - 0x%Lx State = %a LargeEntry = %d\n",\r
+ gEfiCallerBaseName,\r
+ __FUNCTION__,\r
+ BaseAddress,\r
+ EndAddress,\r
+ State == SevSnpPageShared ? "Shared" : "Private",\r
+ UseLargeEntry\r
+ ));\r
+\r
+ while (BaseAddress < EndAddress) {\r
+ UINTN CurrentEntry, EndEntry;\r
+\r
+ //\r
+ // Initialize the GHCB\r
+ //\r
+ VmgInit (Ghcb, &InterruptState);\r
+\r
+ //\r
+ // Build the page state structure\r
+ //\r
+ Info = (SNP_PAGE_STATE_CHANGE_INFO *)Ghcb->SharedBuffer;\r
+ NextAddress = BuildPageStateBuffer (\r
+ BaseAddress,\r
+ EndAddress,\r
+ State,\r
+ UseLargeEntry,\r
+ Info\r
+ );\r
+\r
+ //\r
+ // Save the current and end entry from the page state structure. We need\r
+ // it later.\r
+ //\r
+ CurrentEntry = Info->Header.CurrentEntry;\r
+ EndEntry = Info->Header.EndEntry;\r
+\r
+ //\r
+ // If the caller requested to change the page state to shared then\r
+ // invalidate the pages before making the page shared in the RMP table.\r
+ //\r
+ if (State == SevSnpPageShared) {\r
+ PvalidateRange (Info, CurrentEntry, EndEntry, FALSE);\r
+ }\r
+\r
+ //\r
+ // Invoke the page state change VMGEXIT.\r
+ //\r
+ PageStateChangeVmgExit (Ghcb, Info);\r
+\r
+ //\r
+ // If the caller requested to change the page state to private then\r
+ // validate the pages after it has been added in the RMP table.\r
+ //\r
+ if (State == SevSnpPagePrivate) {\r
+ PvalidateRange (Info, CurrentEntry, EndEntry, TRUE);\r
+ }\r
+\r
+ VmgDone (Ghcb, InterruptState);\r
+\r
+ BaseAddress = NextAddress;\r
+ }\r
+}\r