summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--UefiCpuPkg/CpuDxe/CpuDxe.c17
-rw-r--r--UefiCpuPkg/CpuDxe/CpuDxe.h2
-rw-r--r--UefiCpuPkg/CpuDxe/CpuPageTable.c223
-rw-r--r--UefiCpuPkg/CpuDxe/CpuPageTable.h34
4 files changed, 267 insertions, 9 deletions
diff --git a/UefiCpuPkg/CpuDxe/CpuDxe.c b/UefiCpuPkg/CpuDxe/CpuDxe.c
index 8ddebabd02..6ae2dcd1c7 100644
--- a/UefiCpuPkg/CpuDxe/CpuDxe.c
+++ b/UefiCpuPkg/CpuDxe/CpuDxe.c
@@ -25,6 +25,7 @@
BOOLEAN InterruptState = FALSE;
EFI_HANDLE mCpuHandle = NULL;
BOOLEAN mIsFlushingGCD;
+BOOLEAN mIsAllocatingPageTable = FALSE;
UINT64 mValidMtrrAddressMask;
UINT64 mValidMtrrBitsMask;
UINT64 mTimerPeriod = 0;
@@ -407,6 +408,20 @@ CpuSetMemoryAttributes (
return EFI_SUCCESS;
}
+ //
+ // During memory attributes updating, new pages may be allocated to setup
+ // smaller granularity of page table. Page allocation action might then cause
+ // another calling of CpuSetMemoryAttributes() recursively, due to memory
+ // protection policy configured (such as PcdDxeNxMemoryProtectionPolicy).
+ // Since this driver will always protect memory used as page table by itself,
+ // there's no need to apply protection policy requested from memory service.
+ // So it's safe to just return EFI_SUCCESS if this time of calling is caused
+ // by page table memory allocation.
+ //
+ if (mIsAllocatingPageTable) {
+ DEBUG((DEBUG_VERBOSE, " Allocating page table memory\n"));
+ return EFI_SUCCESS;
+ }
CacheAttributes = Attributes & CACHE_ATTRIBUTE_MASK;
MemoryAttributes = Attributes & MEMORY_ATTRIBUTE_MASK;
@@ -487,7 +502,7 @@ CpuSetMemoryAttributes (
//
// Set memory attribute by page table
//
- return AssignMemoryPageAttributes (NULL, BaseAddress, Length, MemoryAttributes, AllocatePages);
+ return AssignMemoryPageAttributes (NULL, BaseAddress, Length, MemoryAttributes, NULL);
}
/**
diff --git a/UefiCpuPkg/CpuDxe/CpuDxe.h b/UefiCpuPkg/CpuDxe/CpuDxe.h
index 9c0d22359d..540f5f2dbf 100644
--- a/UefiCpuPkg/CpuDxe/CpuDxe.h
+++ b/UefiCpuPkg/CpuDxe/CpuDxe.h
@@ -273,5 +273,7 @@ RefreshGcdMemoryAttributesFromPaging (
VOID
);
+extern BOOLEAN mIsAllocatingPageTable;
+
#endif
diff --git a/UefiCpuPkg/CpuDxe/CpuPageTable.c b/UefiCpuPkg/CpuDxe/CpuPageTable.c
index 9658ed74c5..a9c9bc9d5e 100644
--- a/UefiCpuPkg/CpuDxe/CpuPageTable.c
+++ b/UefiCpuPkg/CpuDxe/CpuPageTable.c
@@ -87,6 +87,8 @@ PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = {
{Page1G, SIZE_1GB, PAGING_1G_ADDRESS_MASK_64},
};
+PAGE_TABLE_POOL *mPageTablePool = NULL;
+
/**
Enable write protection function for AP.
@@ -172,10 +174,6 @@ GetCurrentPagingContext (
}
if ((AsmReadCr0 () & BIT31) != 0) {
PagingContext->ContextData.X64.PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);
- if ((AsmReadCr0 () & BIT16) == 0) {
- AsmWriteCr0 (AsmReadCr0 () | BIT16);
- SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);
- }
} else {
PagingContext->ContextData.X64.PageTableBase = 0;
}
@@ -562,6 +560,59 @@ SplitPage (
}
/**
+ Check the WP status in CR0 register. This bit is used to lock or unlock write
+ access to pages marked as read-only.
+
+ @retval TRUE Write protection is enabled.
+ @retval FALSE Write protection is disabled.
+**/
+BOOLEAN
+IsReadOnlyPageWriteProtected (
+ VOID
+ )
+{
+ return ((AsmReadCr0 () & BIT16) != 0);
+}
+
+/**
+ Disable write protection function for AP.
+
+ @param[in,out] Buffer The pointer to private data buffer.
+**/
+VOID
+EFIAPI
+SyncCpuDisableWriteProtection (
+ IN OUT VOID *Buffer
+ )
+{
+ AsmWriteCr0 (AsmReadCr0() & ~BIT16);
+}
+
+/**
+ Disable Write Protect on pages marked as read-only.
+**/
+VOID
+DisableReadOnlyPageWriteProtect (
+ VOID
+ )
+{
+ AsmWriteCr0 (AsmReadCr0() & ~BIT16);
+ SyncMemoryPageAttributesAp (SyncCpuDisableWriteProtection);
+}
+
+/**
+ Enable Write Protect on pages marked as read-only.
+**/
+VOID
+EnableReadOnlyPageWriteProtect (
+ VOID
+ )
+{
+ AsmWriteCr0 (AsmReadCr0() | BIT16);
+ SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);
+}
+
+/**
This function modifies the page attributes for the memory region specified by BaseAddress and
Length from their current attributes to the attributes specified by Attributes.
@@ -609,6 +660,7 @@ ConvertMemoryPageAttributes (
PAGE_ATTRIBUTE SplitAttribute;
RETURN_STATUS Status;
BOOLEAN IsEntryModified;
+ BOOLEAN IsWpEnabled;
if ((BaseAddress & (SIZE_4KB - 1)) != 0) {
DEBUG ((DEBUG_ERROR, "BaseAddress(0x%lx) is not aligned!\n", BaseAddress));
@@ -665,14 +717,27 @@ ConvertMemoryPageAttributes (
if (IsModified != NULL) {
*IsModified = FALSE;
}
+ if (AllocatePagesFunc == NULL) {
+ AllocatePagesFunc = AllocatePageTableMemory;
+ }
+
+ //
+ // Make sure that the page table is changeable.
+ //
+ IsWpEnabled = IsReadOnlyPageWriteProtected ();
+ if (IsWpEnabled) {
+ DisableReadOnlyPageWriteProtect ();
+ }
//
// Below logic is to check 2M/4K page to make sure we donot waist memory.
//
+ Status = EFI_SUCCESS;
while (Length != 0) {
PageEntry = GetPageTableEntry (&CurrentPagingContext, BaseAddress, &PageAttribute);
if (PageEntry == NULL) {
- return RETURN_UNSUPPORTED;
+ Status = RETURN_UNSUPPORTED;
+ goto Done;
}
PageEntryLength = PageAttributeToLength (PageAttribute);
SplitAttribute = NeedSplitPage (BaseAddress, Length, PageEntry, PageAttribute);
@@ -690,11 +755,13 @@ ConvertMemoryPageAttributes (
Length -= PageEntryLength;
} else {
if (AllocatePagesFunc == NULL) {
- return RETURN_UNSUPPORTED;
+ Status = RETURN_UNSUPPORTED;
+ goto Done;
}
Status = SplitPage (PageEntry, PageAttribute, SplitAttribute, AllocatePagesFunc);
if (RETURN_ERROR (Status)) {
- return RETURN_UNSUPPORTED;
+ Status = RETURN_UNSUPPORTED;
+ goto Done;
}
if (IsSplitted != NULL) {
*IsSplitted = TRUE;
@@ -709,7 +776,14 @@ ConvertMemoryPageAttributes (
}
}
- return RETURN_SUCCESS;
+Done:
+ //
+ // Restore page table write protection, if any.
+ //
+ if (IsWpEnabled) {
+ EnableReadOnlyPageWriteProtect ();
+ }
+ return Status;
}
/**
@@ -923,6 +997,127 @@ RefreshGcdMemoryAttributesFromPaging (
}
/**
+ Initialize a buffer pool for page table use only.
+
+ To reduce the potential split operation on page table, the pages reserved for
+ page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and
+ at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always
+ initialized with number of pages greater than or equal to the given PoolPages.
+
+ Once the pages in the pool are used up, this method should be called again to
+ reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. Usually this won't happen
+ often in practice.
+
+ @param[in] PoolPages The least page number of the pool to be created.
+
+ @retval TRUE The pool is initialized successfully.
+ @retval FALSE The memory is out of resource.
+**/
+BOOLEAN
+InitializePageTablePool (
+ IN UINTN PoolPages
+ )
+{
+ VOID *Buffer;
+ BOOLEAN IsModified;
+
+ //
+ // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for
+ // header.
+ //
+ PoolPages += 1; // Add one page for header.
+ PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) *
+ PAGE_TABLE_POOL_UNIT_PAGES;
+ Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT);
+ if (Buffer == NULL) {
+ DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n"));
+ return FALSE;
+ }
+
+ //
+ // Link all pools into a list for easier track later.
+ //
+ if (mPageTablePool == NULL) {
+ mPageTablePool = Buffer;
+ mPageTablePool->NextPool = mPageTablePool;
+ } else {
+ ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool;
+ mPageTablePool->NextPool = Buffer;
+ mPageTablePool = Buffer;
+ }
+
+ //
+ // Reserve one page for pool header.
+ //
+ mPageTablePool->FreePages = PoolPages - 1;
+ mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1);
+
+ //
+ // Mark the whole pool pages as read-only.
+ //
+ ConvertMemoryPageAttributes (
+ NULL,
+ (PHYSICAL_ADDRESS)(UINTN)Buffer,
+ EFI_PAGES_TO_SIZE (PoolPages),
+ EFI_MEMORY_RO,
+ PageActionSet,
+ AllocatePageTableMemory,
+ NULL,
+ &IsModified
+ );
+ ASSERT (IsModified == TRUE);
+
+ return TRUE;
+}
+
+/**
+ This API provides a way to allocate memory for page table.
+
+ This API can be called more than once to allocate memory for page tables.
+
+ Allocates the number of 4KB pages and returns a pointer to the allocated
+ buffer. The buffer returned is aligned on a 4KB boundary.
+
+ If Pages is 0, then NULL is returned.
+ If there is not enough memory remaining to satisfy the request, then NULL is
+ returned.
+
+ @param Pages The number of 4 KB pages to allocate.
+
+ @return A pointer to the allocated buffer or NULL if allocation fails.
+
+**/
+VOID *
+EFIAPI
+AllocatePageTableMemory (
+ IN UINTN Pages
+ )
+{
+ VOID *Buffer;
+
+ if (Pages == 0) {
+ return NULL;
+ }
+
+ //
+ // Renew the pool if necessary.
+ //
+ if (mPageTablePool == NULL ||
+ Pages > mPageTablePool->FreePages) {
+ if (!InitializePageTablePool (Pages)) {
+ return NULL;
+ }
+ }
+
+ Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset;
+
+ mPageTablePool->Offset += EFI_PAGES_TO_SIZE (Pages);
+ mPageTablePool->FreePages -= Pages;
+
+ return Buffer;
+}
+
+/**
Initialize the Page Table lib.
**/
VOID
@@ -933,6 +1128,18 @@ InitializePageTableLib (
PAGE_TABLE_LIB_PAGING_CONTEXT CurrentPagingContext;
GetCurrentPagingContext (&CurrentPagingContext);
+
+ //
+ // Reserve memory of page tables for future uses, if paging is enabled.
+ //
+ if (CurrentPagingContext.ContextData.X64.PageTableBase != 0 &&
+ (CurrentPagingContext.ContextData.Ia32.Attributes &
+ PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0) {
+ DisableReadOnlyPageWriteProtect ();
+ InitializePageTablePool (1);
+ EnableReadOnlyPageWriteProtect ();
+ }
+
DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n", CurrentPagingContext.MachineType));
DEBUG ((DEBUG_INFO, " MachineType - 0x%x\n", CurrentPagingContext.MachineType));
DEBUG ((DEBUG_INFO, " PageTableBase - 0x%x\n", CurrentPagingContext.ContextData.X64.PageTableBase));
diff --git a/UefiCpuPkg/CpuDxe/CpuPageTable.h b/UefiCpuPkg/CpuDxe/CpuPageTable.h
index eaff595b4c..48da9cdde7 100644
--- a/UefiCpuPkg/CpuDxe/CpuPageTable.h
+++ b/UefiCpuPkg/CpuDxe/CpuPageTable.h
@@ -50,6 +50,19 @@ typedef struct {
PAGE_TABLE_LIB_PAGING_CONTEXT_DATA ContextData;
} PAGE_TABLE_LIB_PAGING_CONTEXT;
+#define PAGE_TABLE_POOL_ALIGNMENT BASE_2MB
+#define PAGE_TABLE_POOL_UNIT_SIZE SIZE_2MB
+#define PAGE_TABLE_POOL_UNIT_PAGES EFI_SIZE_TO_PAGES (PAGE_TABLE_POOL_UNIT_SIZE)
+#define PAGE_TABLE_POOL_ALIGN_MASK \
+ (~(EFI_PHYSICAL_ADDRESS)(PAGE_TABLE_POOL_ALIGNMENT - 1))
+
+typedef struct {
+ VOID *NextPool;
+ UINTN Offset;
+ UINTN FreePages;
+} PAGE_TABLE_POOL;
+
+
/**
Allocates one or more 4KB pages for page table.
@@ -110,4 +123,25 @@ InitializePageTableLib (
VOID
);
+/**
+ This API provides a way to allocate memory for page table.
+
+ This API can be called more once to allocate memory for page tables.
+
+ Allocates the number of 4KB pages of type EfiRuntimeServicesData and returns a pointer to the
+ allocated buffer. The buffer returned is aligned on a 4KB boundary. If Pages is 0, then NULL
+ is returned. If there is not enough memory remaining to satisfy the request, then NULL is
+ returned.
+
+ @param Pages The number of 4 KB pages to allocate.
+
+ @return A pointer to the allocated buffer or NULL if allocation fails.
+
+**/
+VOID *
+EFIAPI
+AllocatePageTableMemory (
+ IN UINTN Pages
+ );
+
#endif