BOOLEAN InterruptState = FALSE;\r
EFI_HANDLE mCpuHandle = NULL;\r
BOOLEAN mIsFlushingGCD;\r
+BOOLEAN mIsAllocatingPageTable = FALSE;\r
UINT64 mValidMtrrAddressMask;\r
UINT64 mValidMtrrBitsMask;\r
UINT64 mTimerPeriod = 0;\r
return EFI_SUCCESS;\r
}\r
\r
+ //\r
+ // During memory attributes updating, new pages may be allocated to setup\r
+ // smaller granularity of page table. Page allocation action might then cause\r
+ // another calling of CpuSetMemoryAttributes() recursively, due to memory\r
+ // protection policy configured (such as PcdDxeNxMemoryProtectionPolicy).\r
+ // Since this driver will always protect memory used as page table by itself,\r
+ // there's no need to apply protection policy requested from memory service.\r
+ // So it's safe to just return EFI_SUCCESS if this time of calling is caused\r
+ // by page table memory allocation.\r
+ //\r
+ if (mIsAllocatingPageTable) {\r
+ DEBUG((DEBUG_VERBOSE, " Allocating page table memory\n"));\r
+ return EFI_SUCCESS;\r
+ }\r
\r
CacheAttributes = Attributes & CACHE_ATTRIBUTE_MASK;\r
MemoryAttributes = Attributes & MEMORY_ATTRIBUTE_MASK;\r
//\r
// Set memory attribute by page table\r
//\r
- return AssignMemoryPageAttributes (NULL, BaseAddress, Length, MemoryAttributes, AllocatePages);\r
+ return AssignMemoryPageAttributes (NULL, BaseAddress, Length, MemoryAttributes, NULL);\r
}\r
\r
/**\r
{Page1G, SIZE_1GB, PAGING_1G_ADDRESS_MASK_64},\r
};\r
\r
+PAGE_TABLE_POOL *mPageTablePool = NULL;\r
+\r
/**\r
Enable write protection function for AP.\r
\r
}\r
if ((AsmReadCr0 () & BIT31) != 0) {\r
PagingContext->ContextData.X64.PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);\r
- if ((AsmReadCr0 () & BIT16) == 0) {\r
- AsmWriteCr0 (AsmReadCr0 () | BIT16);\r
- SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);\r
- }\r
} else {\r
PagingContext->ContextData.X64.PageTableBase = 0;\r
}\r
}\r
}\r
\r
+/**\r
+ Check the WP status in CR0 register. This bit is used to lock or unlock write\r
+ access to pages marked as read-only.\r
+\r
+ @retval TRUE Write protection is enabled.\r
+ @retval FALSE Write protection is disabled.\r
+**/\r
+BOOLEAN\r
+IsReadOnlyPageWriteProtected (\r
+ VOID\r
+ )\r
+{\r
+ return ((AsmReadCr0 () & BIT16) != 0);\r
+}\r
+\r
+/**\r
+ Disable write protection function for AP.\r
+\r
+ @param[in,out] Buffer The pointer to private data buffer.\r
+**/\r
+VOID\r
+EFIAPI\r
+SyncCpuDisableWriteProtection (\r
+ IN OUT VOID *Buffer\r
+ )\r
+{\r
+ AsmWriteCr0 (AsmReadCr0() & ~BIT16);\r
+}\r
+\r
+/**\r
+ Disable Write Protect on pages marked as read-only.\r
+**/\r
+VOID\r
+DisableReadOnlyPageWriteProtect (\r
+ VOID\r
+ )\r
+{\r
+ AsmWriteCr0 (AsmReadCr0() & ~BIT16);\r
+ SyncMemoryPageAttributesAp (SyncCpuDisableWriteProtection);\r
+}\r
+\r
+/**\r
+ Enable Write Protect on pages marked as read-only.\r
+**/\r
+VOID\r
+EnableReadOnlyPageWriteProtect (\r
+ VOID\r
+ )\r
+{\r
+ AsmWriteCr0 (AsmReadCr0() | BIT16);\r
+ SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);\r
+}\r
+\r
/**\r
This function modifies the page attributes for the memory region specified by BaseAddress and\r
Length from their current attributes to the attributes specified by Attributes.\r
PAGE_ATTRIBUTE SplitAttribute;\r
RETURN_STATUS Status;\r
BOOLEAN IsEntryModified;\r
+ BOOLEAN IsWpEnabled;\r
\r
if ((BaseAddress & (SIZE_4KB - 1)) != 0) {\r
DEBUG ((DEBUG_ERROR, "BaseAddress(0x%lx) is not aligned!\n", BaseAddress));\r
if (IsModified != NULL) {\r
*IsModified = FALSE;\r
}\r
+ if (AllocatePagesFunc == NULL) {\r
+ AllocatePagesFunc = AllocatePageTableMemory;\r
+ }\r
+\r
+ //\r
+ // Make sure that the page table is changeable.\r
+ //\r
+ IsWpEnabled = IsReadOnlyPageWriteProtected ();\r
+ if (IsWpEnabled) {\r
+ DisableReadOnlyPageWriteProtect ();\r
+ }\r
\r
//\r
// Below logic is to check 2M/4K page to make sure we donot waist memory.\r
//\r
+ Status = EFI_SUCCESS;\r
while (Length != 0) {\r
PageEntry = GetPageTableEntry (&CurrentPagingContext, BaseAddress, &PageAttribute);\r
if (PageEntry == NULL) {\r
- return RETURN_UNSUPPORTED;\r
+ Status = RETURN_UNSUPPORTED;\r
+ goto Done;\r
}\r
PageEntryLength = PageAttributeToLength (PageAttribute);\r
SplitAttribute = NeedSplitPage (BaseAddress, Length, PageEntry, PageAttribute);\r
Length -= PageEntryLength;\r
} else {\r
if (AllocatePagesFunc == NULL) {\r
- return RETURN_UNSUPPORTED;\r
+ Status = RETURN_UNSUPPORTED;\r
+ goto Done;\r
}\r
Status = SplitPage (PageEntry, PageAttribute, SplitAttribute, AllocatePagesFunc);\r
if (RETURN_ERROR (Status)) {\r
- return RETURN_UNSUPPORTED;\r
+ Status = RETURN_UNSUPPORTED;\r
+ goto Done;\r
}\r
if (IsSplitted != NULL) {\r
*IsSplitted = TRUE;\r
}\r
}\r
\r
- return RETURN_SUCCESS;\r
+Done:\r
+ //\r
+ // Restore page table write protection, if any.\r
+ //\r
+ if (IsWpEnabled) {\r
+ EnableReadOnlyPageWriteProtect ();\r
+ }\r
+ return Status;\r
}\r
\r
/**\r
FreePool (MemorySpaceMap);\r
}\r
\r
+/**\r
+ Initialize a buffer pool for page table use only.\r
+\r
+ To reduce the potential split operation on page table, the pages reserved for\r
+ page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and\r
+ at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always\r
+ initialized with number of pages greater than or equal to the given PoolPages.\r
+\r
+ Once the pages in the pool are used up, this method should be called again to\r
+ reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. Usually this won't happen\r
+ often in practice.\r
+\r
+ @param[in] PoolPages The least page number of the pool to be created.\r
+\r
+ @retval TRUE The pool is initialized successfully.\r
+ @retval FALSE The memory is out of resource.\r
+**/\r
+BOOLEAN\r
+InitializePageTablePool (\r
+ IN UINTN PoolPages\r
+ )\r
+{\r
+ VOID *Buffer;\r
+ BOOLEAN IsModified;\r
+\r
+ //\r
+ // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for\r
+ // header.\r
+ //\r
+ PoolPages += 1; // Add one page for header.\r
+ PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) *\r
+ PAGE_TABLE_POOL_UNIT_PAGES;\r
+ Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT);\r
+ if (Buffer == NULL) {\r
+ DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n"));\r
+ return FALSE;\r
+ }\r
+\r
+ //\r
+ // Link all pools into a list for easier track later.\r
+ //\r
+ if (mPageTablePool == NULL) {\r
+ mPageTablePool = Buffer;\r
+ mPageTablePool->NextPool = mPageTablePool;\r
+ } else {\r
+ ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool;\r
+ mPageTablePool->NextPool = Buffer;\r
+ mPageTablePool = Buffer;\r
+ }\r
+\r
+ //\r
+ // Reserve one page for pool header.\r
+ //\r
+ mPageTablePool->FreePages = PoolPages - 1;\r
+ mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1);\r
+\r
+ //\r
+ // Mark the whole pool pages as read-only.\r
+ //\r
+ ConvertMemoryPageAttributes (\r
+ NULL,\r
+ (PHYSICAL_ADDRESS)(UINTN)Buffer,\r
+ EFI_PAGES_TO_SIZE (PoolPages),\r
+ EFI_MEMORY_RO,\r
+ PageActionSet,\r
+ AllocatePageTableMemory,\r
+ NULL,\r
+ &IsModified\r
+ );\r
+ ASSERT (IsModified == TRUE);\r
+\r
+ return TRUE;\r
+}\r
+\r
+/**\r
+ This API provides a way to allocate memory for page table.\r
+\r
+ This API can be called more than once to allocate memory for page tables.\r
+\r
+ Allocates the number of 4KB pages and returns a pointer to the allocated\r
+ buffer. The buffer returned is aligned on a 4KB boundary.\r
+\r
+ If Pages is 0, then NULL is returned.\r
+ If there is not enough memory remaining to satisfy the request, then NULL is\r
+ returned.\r
+\r
+ @param Pages The number of 4 KB pages to allocate.\r
+\r
+ @return A pointer to the allocated buffer or NULL if allocation fails.\r
+\r
+**/\r
+VOID *\r
+EFIAPI\r
+AllocatePageTableMemory (\r
+ IN UINTN Pages\r
+ )\r
+{\r
+ VOID *Buffer;\r
+\r
+ if (Pages == 0) {\r
+ return NULL;\r
+ }\r
+\r
+ //\r
+ // Renew the pool if necessary.\r
+ //\r
+ if (mPageTablePool == NULL ||\r
+ Pages > mPageTablePool->FreePages) {\r
+ if (!InitializePageTablePool (Pages)) {\r
+ return NULL;\r
+ }\r
+ }\r
+\r
+ Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset;\r
+\r
+ mPageTablePool->Offset += EFI_PAGES_TO_SIZE (Pages);\r
+ mPageTablePool->FreePages -= Pages;\r
+\r
+ return Buffer;\r
+}\r
+\r
/**\r
Initialize the Page Table lib.\r
**/\r
PAGE_TABLE_LIB_PAGING_CONTEXT CurrentPagingContext;\r
\r
GetCurrentPagingContext (&CurrentPagingContext);\r
+\r
+ //\r
+ // Reserve memory of page tables for future uses, if paging is enabled.\r
+ //\r
+ if (CurrentPagingContext.ContextData.X64.PageTableBase != 0 &&\r
+ (CurrentPagingContext.ContextData.Ia32.Attributes &\r
+ PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0) {\r
+ DisableReadOnlyPageWriteProtect ();\r
+ InitializePageTablePool (1);\r
+ EnableReadOnlyPageWriteProtect ();\r
+ }\r
+\r
DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n", CurrentPagingContext.MachineType));\r
DEBUG ((DEBUG_INFO, " MachineType - 0x%x\n", CurrentPagingContext.MachineType));\r
DEBUG ((DEBUG_INFO, " PageTableBase - 0x%x\n", CurrentPagingContext.ContextData.X64.PageTableBase));\r