+/** @file\r
+\r
+ Virtual Memory Management Services to set or clear the memory encryption bit\r
+\r
+Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.<BR>\r
+Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR>\r
+\r
+This program and the accompanying materials\r
+are licensed and made available under the terms and conditions of the BSD License\r
+which accompanies this distribution. The full text of the license may be found at\r
+http://opensource.org/licenses/bsd-license.php\r
+\r
+THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+\r
+Code is derived from MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c\r
+\r
+**/\r
+\r
+#include <Library/CpuLib.h>\r
+#include <Register/Cpuid.h>\r
+#include <Register/Amd/Cpuid.h>\r
+\r
+#include "VirtualMemory.h"\r
+\r
+STATIC BOOLEAN mAddressEncMaskChecked = FALSE;\r
+STATIC UINT64 mAddressEncMask;\r
+\r
+typedef enum {\r
+ SetCBit,\r
+ ClearCBit\r
+} MAP_RANGE_MODE;\r
+\r
+/**\r
+ Get the memory encryption mask\r
+\r
+ @param[out] EncryptionMask contains the pte mask.\r
+\r
+**/\r
+STATIC\r
+UINT64\r
+GetMemEncryptionAddressMask (\r
+ VOID\r
+ )\r
+{\r
+ UINT64 EncryptionMask;\r
+ CPUID_MEMORY_ENCRYPTION_INFO_EBX Ebx;\r
+\r
+ if (mAddressEncMaskChecked) {\r
+ return mAddressEncMask;\r
+ }\r
+\r
+ //\r
+ // CPUID Fn8000_001F[EBX] Bit 0:5 (memory encryption bit position)\r
+ //\r
+ AsmCpuid (CPUID_MEMORY_ENCRYPTION_INFO, NULL, &Ebx.Uint32, NULL, NULL);\r
+ EncryptionMask = LShiftU64 (1, Ebx.Bits.PtePosBits);\r
+\r
+ mAddressEncMask = EncryptionMask & PAGING_1G_ADDRESS_MASK_64;\r
+ mAddressEncMaskChecked = TRUE;\r
+\r
+ return mAddressEncMask;\r
+}\r
+\r
+/**\r
+ Split 2M page to 4K.\r
+\r
+ @param[in] PhysicalAddress Start physical address the 2M page covered.\r
+ @param[in, out] PageEntry2M Pointer to 2M page entry.\r
+ @param[in] StackBase Stack base address.\r
+ @param[in] StackSize Stack size.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+Split2MPageTo4K (\r
+ IN PHYSICAL_ADDRESS PhysicalAddress,\r
+ IN OUT UINT64 *PageEntry2M,\r
+ IN PHYSICAL_ADDRESS StackBase,\r
+ IN UINTN StackSize\r
+ )\r
+{\r
+ PHYSICAL_ADDRESS PhysicalAddress4K;\r
+ UINTN IndexOfPageTableEntries;\r
+ PAGE_TABLE_4K_ENTRY *PageTableEntry, *PageTableEntry1;\r
+ UINT64 AddressEncMask;\r
+\r
+ PageTableEntry = AllocatePages(1);\r
+\r
+ PageTableEntry1 = PageTableEntry;\r
+\r
+ AddressEncMask = GetMemEncryptionAddressMask ();\r
+\r
+ ASSERT (PageTableEntry != NULL);\r
+ ASSERT (*PageEntry2M & AddressEncMask);\r
+\r
+ PhysicalAddress4K = PhysicalAddress;\r
+ for (IndexOfPageTableEntries = 0; IndexOfPageTableEntries < 512; IndexOfPageTableEntries++, PageTableEntry++, PhysicalAddress4K += SIZE_4KB) {\r
+ //\r
+ // Fill in the Page Table entries\r
+ //\r
+ PageTableEntry->Uint64 = (UINT64) PhysicalAddress4K | AddressEncMask;\r
+ PageTableEntry->Bits.ReadWrite = 1;\r
+ PageTableEntry->Bits.Present = 1;\r
+ if ((PhysicalAddress4K >= StackBase) && (PhysicalAddress4K < StackBase + StackSize)) {\r
+ //\r
+ // Set Nx bit for stack.\r
+ //\r
+ PageTableEntry->Bits.Nx = 1;\r
+ }\r
+ }\r
+\r
+ //\r
+ // Fill in 2M page entry.\r
+ //\r
+ *PageEntry2M = (UINT64) (UINTN) PageTableEntry1 | IA32_PG_P | IA32_PG_RW | AddressEncMask;\r
+}\r
+\r
+/**\r
+ Split 1G page to 2M.\r
+\r
+ @param[in] PhysicalAddress Start physical address the 1G page covered.\r
+ @param[in, out] PageEntry1G Pointer to 1G page entry.\r
+ @param[in] StackBase Stack base address.\r
+ @param[in] StackSize Stack size.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+Split1GPageTo2M (\r
+ IN PHYSICAL_ADDRESS PhysicalAddress,\r
+ IN OUT UINT64 *PageEntry1G,\r
+ IN PHYSICAL_ADDRESS StackBase,\r
+ IN UINTN StackSize\r
+ )\r
+{\r
+ PHYSICAL_ADDRESS PhysicalAddress2M;\r
+ UINTN IndexOfPageDirectoryEntries;\r
+ PAGE_TABLE_ENTRY *PageDirectoryEntry;\r
+ UINT64 AddressEncMask;\r
+\r
+ PageDirectoryEntry = AllocatePages(1);\r
+\r
+ AddressEncMask = GetMemEncryptionAddressMask ();\r
+ ASSERT (PageDirectoryEntry != NULL);\r
+ ASSERT (*PageEntry1G & GetMemEncryptionAddressMask ());\r
+ //\r
+ // Fill in 1G page entry.\r
+ //\r
+ *PageEntry1G = (UINT64) (UINTN) PageDirectoryEntry | IA32_PG_P | IA32_PG_RW | AddressEncMask;\r
+\r
+ PhysicalAddress2M = PhysicalAddress;\r
+ for (IndexOfPageDirectoryEntries = 0; IndexOfPageDirectoryEntries < 512; IndexOfPageDirectoryEntries++, PageDirectoryEntry++, PhysicalAddress2M += SIZE_2MB) {\r
+ if ((PhysicalAddress2M < StackBase + StackSize) && ((PhysicalAddress2M + SIZE_2MB) > StackBase)) {\r
+ //\r
+ // Need to split this 2M page that covers stack range.\r
+ //\r
+ Split2MPageTo4K (PhysicalAddress2M, (UINT64 *) PageDirectoryEntry, StackBase, StackSize);\r
+ } else {\r
+ //\r
+ // Fill in the Page Directory entries\r
+ //\r
+ PageDirectoryEntry->Uint64 = (UINT64) PhysicalAddress2M | AddressEncMask;\r
+ PageDirectoryEntry->Bits.ReadWrite = 1;\r
+ PageDirectoryEntry->Bits.Present = 1;\r
+ PageDirectoryEntry->Bits.MustBe1 = 1;\r
+ }\r
+ }\r
+}\r
+\r
+\r
+/**\r
+ Set or Clear the memory encryption bit\r
+\r
+ @param[in] PagetablePoint Page table entry pointer (PTE).\r
+ @param[in] Mode Set or Clear encryption bit\r
+\r
+**/\r
+STATIC VOID\r
+SetOrClearCBit(\r
+ IN OUT UINT64* PageTablePointer,\r
+ IN MAP_RANGE_MODE Mode\r
+ )\r
+{\r
+ UINT64 AddressEncMask;\r
+\r
+ AddressEncMask = GetMemEncryptionAddressMask ();\r
+\r
+ if (Mode == SetCBit) {\r
+ *PageTablePointer |= AddressEncMask;\r
+ } else {\r
+ *PageTablePointer &= ~AddressEncMask;\r
+ }\r
+\r
+}\r
+\r
+/**\r
+ This function either sets or clears memory encryption bit for the memory region\r
+ specified by PhysicalAddress and length from the current page table context.\r
+\r
+ The function iterates through the physicalAddress one page at a time, and set\r
+ or clears the memory encryption mask in the page table. If it encounters\r
+ that a given physical address range is part of large page then it attempts to\r
+ change the attribute at one go (based on size), otherwise it splits the\r
+ large pages into smaller (e.g 2M page into 4K pages) and then try to set or\r
+ clear the encryption bit on the smallest page size.\r
+\r
+ @param[in] PhysicalAddress The physical address that is the start\r
+ address of a memory region.\r
+ @param[in] Length The length of memory region\r
+ @param[in] Mode Set or Clear mode\r
+ @param[in] Flush Flush the caches before applying the\r
+ encryption mask\r
+\r
+ @retval RETURN_SUCCESS The attributes were cleared for the memory\r
+ region.\r
+ @retval RETURN_INVALID_PARAMETER Number of pages is zero.\r
+ @retval RETURN_UNSUPPORTED Setting the memory encyrption attribute is\r
+ not supported\r
+**/\r
+\r
+STATIC\r
+RETURN_STATUS\r
+EFIAPI\r
+SetMemoryEncDec (\r
+ IN PHYSICAL_ADDRESS Cr3BaseAddress,\r
+ IN PHYSICAL_ADDRESS PhysicalAddress,\r
+ IN UINTN Length,\r
+ IN MAP_RANGE_MODE Mode,\r
+ IN BOOLEAN CacheFlush\r
+ )\r
+{\r
+ PAGE_MAP_AND_DIRECTORY_POINTER *PageMapLevel4Entry;\r
+ PAGE_MAP_AND_DIRECTORY_POINTER *PageUpperDirectoryPointerEntry;\r
+ PAGE_MAP_AND_DIRECTORY_POINTER *PageDirectoryPointerEntry;\r
+ PAGE_TABLE_1G_ENTRY *PageDirectory1GEntry;\r
+ PAGE_TABLE_ENTRY *PageDirectory2MEntry;\r
+ PAGE_TABLE_4K_ENTRY *PageTableEntry;\r
+ UINT64 PgTableMask;\r
+ UINT64 AddressEncMask;\r
+\r
+ //\r
+ // Check if we have a valid memory encryption mask\r
+ //\r
+ AddressEncMask = GetMemEncryptionAddressMask ();\r
+ if (!AddressEncMask) {\r
+ return RETURN_ACCESS_DENIED;\r
+ }\r
+\r
+ PgTableMask = AddressEncMask | EFI_PAGE_MASK;\r
+\r
+ if (Length == 0) {\r
+ return RETURN_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // We are going to change the memory encryption attribute from C=0 -> C=1 or\r
+ // vice versa Flush the caches to ensure that data is written into memory with\r
+ // correct C-bit\r
+ //\r
+ if (CacheFlush) {\r
+ WriteBackInvalidateDataCacheRange((VOID*) (UINTN)PhysicalAddress, Length);\r
+ }\r
+\r
+ while (Length)\r
+ {\r
+ //\r
+ // If Cr3BaseAddress is not specified then read the current CR3\r
+ //\r
+ if (Cr3BaseAddress == 0) {\r
+ Cr3BaseAddress = AsmReadCr3();\r
+ }\r
+\r
+ PageMapLevel4Entry = (VOID*) (Cr3BaseAddress & ~PgTableMask);\r
+ PageMapLevel4Entry += PML4_OFFSET(PhysicalAddress);\r
+ if (!PageMapLevel4Entry->Bits.Present) {\r
+ DEBUG ((DEBUG_WARN,\r
+ "%a:%a ERROR bad PML4 for %lx\n", gEfiCallerBaseName, __FUNCTION__,\r
+ PhysicalAddress));\r
+ return RETURN_NO_MAPPING;\r
+ }\r
+\r
+ PageDirectory1GEntry = (VOID*) ((PageMapLevel4Entry->Bits.PageTableBaseAddress<<12) & ~PgTableMask);\r
+ PageDirectory1GEntry += PDP_OFFSET(PhysicalAddress);\r
+ if (!PageDirectory1GEntry->Bits.Present) {\r
+ DEBUG ((DEBUG_WARN,\r
+ "%a:%a ERROR bad PDPE for %lx\n", gEfiCallerBaseName,\r
+ __FUNCTION__, PhysicalAddress));\r
+ return RETURN_NO_MAPPING;\r
+ }\r
+\r
+ //\r
+ // If the MustBe1 bit is not 1, it's not actually a 1GB entry\r
+ //\r
+ if (PageDirectory1GEntry->Bits.MustBe1) {\r
+ //\r
+ // Valid 1GB page\r
+ // If we have at least 1GB to go, we can just update this entry\r
+ //\r
+ if (!(PhysicalAddress & (BIT30 - 1)) && Length >= BIT30) {\r
+ SetOrClearCBit(&PageDirectory1GEntry->Uint64, Mode);\r
+ DEBUG ((DEBUG_VERBOSE,\r
+ "%a:%a Updated 1GB entry for %lx\n", gEfiCallerBaseName,\r
+ __FUNCTION__, PhysicalAddress));\r
+ PhysicalAddress += BIT30;\r
+ Length -= BIT30;\r
+ } else {\r
+ //\r
+ // We must split the page\r
+ //\r
+ DEBUG ((DEBUG_VERBOSE,\r
+ "%a:%a Spliting 1GB page\n", gEfiCallerBaseName, __FUNCTION__));\r
+ Split1GPageTo2M(((UINT64)PageDirectory1GEntry->Bits.PageTableBaseAddress)<<30, (UINT64*) PageDirectory1GEntry, 0, 0);\r
+ continue;\r
+ }\r
+ } else {\r
+ //\r
+ // Actually a PDP\r
+ //\r
+ PageUpperDirectoryPointerEntry = (PAGE_MAP_AND_DIRECTORY_POINTER*) PageDirectory1GEntry;\r
+ PageDirectory2MEntry = (VOID*) ((PageUpperDirectoryPointerEntry->Bits.PageTableBaseAddress<<12) & ~PgTableMask);\r
+ PageDirectory2MEntry += PDE_OFFSET(PhysicalAddress);\r
+ if (!PageDirectory2MEntry->Bits.Present) {\r
+ DEBUG ((DEBUG_WARN,\r
+ "%a:%a ERROR bad PDE for %lx\n", gEfiCallerBaseName, __FUNCTION__,\r
+ PhysicalAddress));\r
+ return RETURN_NO_MAPPING;\r
+ }\r
+ //\r
+ // If the MustBe1 bit is not a 1, it's not a 2MB entry\r
+ //\r
+ if (PageDirectory2MEntry->Bits.MustBe1) {\r
+ //\r
+ // Valid 2MB page\r
+ // If we have at least 2MB left to go, we can just update this entry\r
+ //\r
+ if (!(PhysicalAddress & (BIT21-1)) && Length >= BIT21) {\r
+ SetOrClearCBit (&PageDirectory2MEntry->Uint64, Mode);\r
+ PhysicalAddress += BIT21;\r
+ Length -= BIT21;\r
+ } else {\r
+ //\r
+ // We must split up this page into 4K pages\r
+ //\r
+ DEBUG ((DEBUG_VERBOSE,\r
+ "%a:%a Spliting 2MB page at %lx\n", gEfiCallerBaseName,__FUNCTION__,\r
+ PhysicalAddress));\r
+ Split2MPageTo4K (((UINT64)PageDirectory2MEntry->Bits.PageTableBaseAddress) << 21, (UINT64*) PageDirectory2MEntry, 0, 0);\r
+ continue;\r
+ }\r
+ } else {\r
+ PageDirectoryPointerEntry = (PAGE_MAP_AND_DIRECTORY_POINTER*) PageDirectory2MEntry;\r
+ PageTableEntry = (VOID*) (PageDirectoryPointerEntry->Bits.PageTableBaseAddress<<12 & ~PgTableMask);\r
+ PageTableEntry += PTE_OFFSET(PhysicalAddress);\r
+ if (!PageTableEntry->Bits.Present) {\r
+ DEBUG ((DEBUG_WARN,\r
+ "%a:%a ERROR bad PTE for %lx\n", gEfiCallerBaseName,\r
+ __FUNCTION__, PhysicalAddress));\r
+ return RETURN_NO_MAPPING;\r
+ }\r
+ SetOrClearCBit (&PageTableEntry->Uint64, Mode);\r
+ PhysicalAddress += EFI_PAGE_SIZE;\r
+ Length -= EFI_PAGE_SIZE;\r
+ }\r
+ }\r
+ }\r
+\r
+ //\r
+ // Flush TLB\r
+ //\r
+ CpuFlushTlb();\r
+\r
+ return RETURN_SUCCESS;\r
+}\r
+\r
+/**\r
+ This function clears memory encryption bit for the memory region specified by\r
+ PhysicalAddress and length from the current page table context.\r
+\r
+ @param[in] PhysicalAddress The physical address that is the start\r
+ address of a memory region.\r
+ @param[in] Length The length of memory region\r
+ @param[in] Flush Flush the caches before applying the\r
+ encryption mask\r
+\r
+ @retval RETURN_SUCCESS The attributes were cleared for the memory\r
+ region.\r
+ @retval RETURN_INVALID_PARAMETER Number of pages is zero.\r
+ @retval RETURN_UNSUPPORTED Setting the memory encyrption attribute is\r
+ not supported\r
+**/\r
+RETURN_STATUS\r
+EFIAPI\r
+InternalMemEncryptSevSetMemoryDecrypted (\r
+ IN PHYSICAL_ADDRESS Cr3BaseAddress,\r
+ IN PHYSICAL_ADDRESS PhysicalAddress,\r
+ IN UINTN Length,\r
+ IN BOOLEAN Flush\r
+ )\r
+{\r
+\r
+ DEBUG ((DEBUG_VERBOSE,\r
+ "%a:%a Clear C-bit Cr3 %Lx Base %Lx Length %Lx flush %d\n",\r
+ gEfiCallerBaseName, __FUNCTION__, Cr3BaseAddress, PhysicalAddress, Length,\r
+ Flush));\r
+ return SetMemoryEncDec (Cr3BaseAddress, PhysicalAddress, Length, ClearCBit, Flush);\r
+}\r
+\r
+/**\r
+ This function sets memory encryption bit for the memory region specified by\r
+ PhysicalAddress and length from the current page table context.\r
+\r
+ @param[in] PhysicalAddress The physical address that is the start address\r
+ of a memory region.\r
+ @param[in] Length The length of memory region\r
+ @param[in] Flush Flush the caches before applying the\r
+ encryption mask\r
+\r
+ @retval RETURN_SUCCESS The attributes were cleared for the memory\r
+ region.\r
+ @retval RETURN_INVALID_PARAMETER Number of pages is zero.\r
+ @retval RETURN_UNSUPPORTED Setting the memory encyrption attribute is\r
+ not supported\r
+**/\r
+RETURN_STATUS\r
+EFIAPI\r
+InternalMemEncryptSevSetMemoryEncrypted (\r
+ IN PHYSICAL_ADDRESS Cr3BaseAddress,\r
+ IN PHYSICAL_ADDRESS PhysicalAddress,\r
+ IN UINTN Length,\r
+ IN BOOLEAN Flush\r
+ )\r
+{\r
+ DEBUG ((DEBUG_VERBOSE,\r
+ "%a:%a Set C-bit Cr3 %Lx Base %Lx Length %Lx flush %d\n",\r
+ gEfiCallerBaseName, __FUNCTION__, Cr3BaseAddress, PhysicalAddress, Length,\r
+ Flush));\r
+ return SetMemoryEncDec (Cr3BaseAddress, PhysicalAddress, Length, SetCBit, Flush);\r
+}\r