X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=UefiCpuPkg%2FCpuDxe%2FCpuPageTable.c;fp=UefiCpuPkg%2FCpuDxe%2FCpuPageTable.c;h=a9c9bc9d5e9079f54b014ac880be5787c47e686c;hp=9658ed74c557dbfcd574a1995b899d4a4d556f44;hb=147fd35c3e389ecd025dbfd243312bf5b22da7c9;hpb=2ac1730bf2a51d6d0483347a0218e1768d6d7992 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; } @@ -561,6 +559,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; } /** @@ -922,6 +996,127 @@ RefreshGcdMemoryAttributesFromPaging ( FreePool (MemorySpaceMap); } +/** + 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. **/ @@ -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));