diff --git a/MdeModulePkg/Core/Pei/Memory/MemoryServices.c b/MdeModulePkg/Core/Pei/Memory/MemoryServices.c index 5b35657da5a9..3343da500ccc 100644 --- a/MdeModulePkg/Core/Pei/Memory/MemoryServices.c +++ b/MdeModulePkg/Core/Pei/Memory/MemoryServices.c @@ -809,6 +809,20 @@ PeiAllocatePages ( } } + if (!PrivateData->PeiMemoryInstalled && (PrivateData->FreeMemorySearchHintCount != 0)) { + // Reuse temporary-memory pages before consuming the limited free-memory top. + Status = FindFreeMemoryFromMemoryAllocationHob ( + (CONST EFI_PEI_SERVICES **)PeiServices, + MemoryType, + ALIGN_VALUE (Pages, EFI_SIZE_TO_PAGES (Granularity)), + Granularity, + Memory + ); + if (!EFI_ERROR (Status)) { + return Status; + } + } + // // We will attempt up to two times here. // If we don't have memory bins, we will only attempt to allocate from the PHIT. @@ -849,6 +863,9 @@ PeiAllocatePages ( Padding & ~(UINTN)EFI_PAGE_MASK, EfiConventionalMemory ); + if (!PrivateData->PeiMemoryInstalled && (PrivateData->FreeMemorySearchHintCount < MAX_UINTN)) { + PrivateData->FreeMemorySearchHintCount++; + } } // @@ -1051,6 +1068,10 @@ PeiFreePages ( if (MemoryAllocationHob != NULL) { UpdateOrSplitMemoryAllocationHob (PeiServices, MemoryAllocationHob, Memory, Bytes, EfiConventionalMemory); FreeMemoryAllocationHob (PrivateData, MemoryAllocationHob); + if (!PrivateData->PeiMemoryInstalled && (PrivateData->FreeMemorySearchHintCount < MAX_UINTN)) { + PrivateData->FreeMemorySearchHintCount++; + } + return EFI_SUCCESS; } else { return EFI_NOT_FOUND; diff --git a/MdeModulePkg/Core/Pei/PeiMain.h b/MdeModulePkg/Core/Pei/PeiMain.h index 611998a20cc6..cb7e7d72387c 100644 --- a/MdeModulePkg/Core/Pei/PeiMain.h +++ b/MdeModulePkg/Core/Pei/PeiMain.h @@ -353,6 +353,11 @@ struct _PEI_CORE_INSTANCE { // This is used for the memory bin feature, if enabled, to track bin locations. // EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics; + + /// + /// Count pre-memory events that may produce reusable conventional-memory ranges. + /// + UINTN FreeMemorySearchHintCount; }; /// diff --git a/MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTest.cpp b/MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTest.cpp new file mode 100644 index 000000000000..cfbdee0cbcc9 --- /dev/null +++ b/MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTest.cpp @@ -0,0 +1,666 @@ +/** @file + Unit tests for PEI Core page allocation. + + Test cases: + - ReusesFreedRangeAfterFreePagesInPreMemory: + reuses a freed range before consuming temporary-memory top-down pages. + - SkipsPreMemorySearchWithoutFreeMemoryEvents: + skips the early HOB scan when no free-memory event has occurred. + - FallsBackToFreeMemoryTopWhenFreedRangesAreTooSmall: + top-down allocation when free HOBs are too small. + - KeepsTopDownAllocationFirstAfterPermanentMemoryInstall: + leaves post-install allocation order unchanged. + - RejectsInvalidMemoryTypeWithoutChangingFreeMemoryTop: + rejects invalid types without changing the PHIT. + - UsesConfiguredMemoryBinBeforeFreedRangesOutsideTheBin: + prefers the configured bin to ranges outside it. + - MergesFreeRangesAcrossConfiguredMemoryBinsAsFinalFallback: + unrestricted fallback can reuse adjacent free ranges from separate bins. + - ReusesFreedRangeFromDifferentMemoryBinAsFinalFallback: + final fallback reuses a range from another type bin. + - ReusesFreedRangeOutsidePhitAsFinalFallback: + final fallback reuses an out-of-PHIT range without bins. + + Copyright (c) 2026, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include + +extern "C" { + #include "PeiMain.h" + #include "MemoryBin.h" + #include +} + +using namespace testing; + +/** Maximum number of synthetic allocation HOBs used by the fixture. **/ +static constexpr UINTN MAX_MEMORY_ALLOCATION_HOBS = 4; +/** Top of the PHIT free-memory range used by allocation tests. **/ +static constexpr EFI_PHYSICAL_ADDRESS FREE_MEMORY_TOP = 0x10000000; + +/** + Fixture for exercising PEI Core page allocation with controlled HOB state. + + The fixture provides mock HOB lookup over synthetic allocation HOBs and a + valid PEI core instance/PHIT for calls to PeiAllocatePages(). +**/ +class PeiMemoryServicesTest : public ::testing::Test { +protected: + // Intercepts production HOB lookups and maps them to the fixture HOBs. + StrictMock HobLib; + // PEI core state consumed by PeiAllocatePages(). + PEI_CORE_INSTANCE PrivateData; + // Handoff information table containing the default free-memory range. + EFI_HOB_HANDOFF_INFO_TABLE HobList; + // Per-type address ranges used by the configured memory-bin test. + EFI_MEMORY_TYPE_STATISTICS MemoryTypeStatistics[EfiMaxMemoryType + 1]; + // Synthetic memory allocation HOBs returned by the mocked HOB library. + EFI_HOB_MEMORY_ALLOCATION MemoryAllocationHobs[MAX_MEMORY_ALLOCATION_HOBS]; + // Reusable HOB consumed when the allocator creates a memory allocation HOB. + EFI_HOB_MEMORY_ALLOCATION UnusedHob; + // Number of valid entries in MemoryAllocationHobs. + UINTN MemoryAllocationHobCount; + + /** + Initialize the fixture's PEI state and mock HOB lookup behavior. + **/ + VOID + SetUp ( + ) override + { + ZeroMem (&PrivateData, sizeof (PrivateData)); + ZeroMem (&HobList, sizeof (HobList)); + ZeroMem (MemoryTypeStatistics, sizeof (MemoryTypeStatistics)); + ZeroMem (MemoryAllocationHobs, sizeof (MemoryAllocationHobs)); + ZeroMem (&UnusedHob, sizeof (UnusedHob)); + + PrivateData.Signature = PEI_CORE_HANDLE_SIGNATURE; + PrivateData.HobList.Raw = (UINT8 *)&HobList; + PrivateData.PeiMemoryInstalled = TRUE; + HobList.EfiFreeMemoryTop = FREE_MEMORY_TOP; + HobList.EfiFreeMemoryBottom = FREE_MEMORY_TOP - (8 * DEFAULT_PAGE_ALLOCATION_GRANULARITY); + UnusedHob.Header.HobType = EFI_HOB_TYPE_UNUSED; + UnusedHob.Header.HobLength = sizeof (UnusedHob); + MemoryAllocationHobCount = 0; + + EXPECT_CALL (HobLib, GetFirstHob (_)) + .Times (AnyNumber ()) + .WillRepeatedly ( + [this](UINT16 Type) { + return FindFirstHob (Type); + } + ); + EXPECT_CALL (HobLib, GetNextHob (_, _)) + .Times (AnyNumber ()) + .WillRepeatedly ( + [this](UINT16 Type, CONST VOID *HobStart) { + return FindNextHob (Type, HobStart); + } + ); + } + + /** + Return the PEI services pointer associated with the fixture instance. + + @return The PEI services pointer expected by PeiAllocatePages(). + **/ + CONST EFI_PEI_SERVICES ** + PeiServices ( + ) + { + return (CONST EFI_PEI_SERVICES **)&PrivateData.Ps; + } + + /** + Add a synthetic conventional-memory allocation HOB to the fixture. + + @param[in] BaseAddress Start address of the free range. + @param[in] Length Length of the free range in bytes. + + @return The newly initialized memory allocation HOB. + **/ + EFI_HOB_MEMORY_ALLOCATION * + AddFreeRange ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length + ) + { + EFI_HOB_MEMORY_ALLOCATION *Hob; + + Hob = &MemoryAllocationHobs[MemoryAllocationHobCount++]; + ZeroMem (Hob, sizeof (*Hob)); + Hob->Header.HobType = EFI_HOB_TYPE_MEMORY_ALLOCATION; + Hob->Header.HobLength = sizeof (*Hob); + Hob->AllocDescriptor.MemoryBaseAddress = BaseAddress; + Hob->AllocDescriptor.MemoryLength = Length; + Hob->AllocDescriptor.MemoryType = EfiConventionalMemory; + return Hob; + } + +private: + + /** + Find the first fixture HOB matching the requested type. + + @param[in] Type HOB type to find. + + @return The first matching HOB, or NULL if none is available. + **/ + VOID * + FindFirstHob ( + IN UINT16 Type + ) + { + if ((Type == EFI_HOB_TYPE_MEMORY_ALLOCATION) && (MemoryAllocationHobCount != 0)) { + return &MemoryAllocationHobs[0]; + } + + if (Type == EFI_HOB_TYPE_UNUSED) { + return &UnusedHob; + } + + return NULL; + } + + /** + Find the fixture HOB beginning at HobStart if it matches Type. + + @param[in] Type HOB type to find. + @param[in] HobStart Starting HOB address supplied by HobLib. + + @return The matching HOB, or NULL if none is available. + **/ + VOID * + FindNextHob ( + IN UINT16 Type, + IN CONST VOID *HobStart + ) + { + UINTN Index; + + if (Type == EFI_HOB_TYPE_MEMORY_ALLOCATION) { + for (Index = 0; Index < MemoryAllocationHobCount; Index++) { + if (HobStart == &MemoryAllocationHobs[Index]) { + return &MemoryAllocationHobs[Index]; + } + } + } + + return NULL; + } +}; + +/** + Verify that allocation reuses the smallest suitable freed range first. + + The PHIT free-memory top remains unchanged when a freed range can satisfy + the request. +**/ +TEST_F (PeiMemoryServicesTest, ReusesFreedRangeAfterFreePagesInPreMemory) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreedRange; + EFI_STATUS Status; + UINT64 Granularity; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + PrivateData.PeiMemoryInstalled = FALSE; + FreedRange = AddFreeRange (HobList.EfiFreeMemoryBottom, 2 * Granularity); + FreedRange->AllocDescriptor.MemoryType = EfiLoaderData; + + Status = PeiFreePages (PeiServices (), FreedRange->AllocDescriptor.MemoryBaseAddress, EFI_SIZE_TO_PAGES (2 * Granularity)); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (PrivateData.FreeMemorySearchHintCount, (UINTN)1); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, 1, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, HobList.EfiFreeMemoryBottom + Granularity); + EXPECT_EQ (HobList.EfiFreeMemoryTop, FREE_MEMORY_TOP); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryBaseAddress, Memory); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryLength, Granularity); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryType, EfiLoaderData); + EXPECT_EQ (UnusedHob.Header.HobType, EFI_HOB_TYPE_MEMORY_ALLOCATION); + EXPECT_EQ (UnusedHob.AllocDescriptor.MemoryBaseAddress, HobList.EfiFreeMemoryBottom); + EXPECT_EQ (UnusedHob.AllocDescriptor.MemoryLength, Granularity); +} + +/** + Verify that a pre-memory allocation skips the early HOB search without a hint. + + The existing top-down path is used while the free range remains untouched. +**/ +TEST_F (PeiMemoryServicesTest, SkipsPreMemorySearchWithoutFreeMemoryEvents) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreeRange; + EFI_STATUS Status; + UINT64 Granularity; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + PrivateData.PeiMemoryInstalled = FALSE; + FreeRange = AddFreeRange (HobList.EfiFreeMemoryBottom, 2 * Granularity); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, 1, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (PrivateData.FreeMemorySearchHintCount, (UINTN)0); + EXPECT_EQ (Memory, FREE_MEMORY_TOP - Granularity); + EXPECT_EQ (HobList.EfiFreeMemoryTop, Memory); + EXPECT_EQ (FreeRange->AllocDescriptor.MemoryBaseAddress, HobList.EfiFreeMemoryBottom); + EXPECT_EQ (FreeRange->AllocDescriptor.MemoryLength, 2 * Granularity); + EXPECT_EQ (FreeRange->AllocDescriptor.MemoryType, EfiConventionalMemory); +} + +/** + Verify post-install allocation remains top-down-first. + + A suitable free HOB is left untouched while the PHIT can satisfy the request. +**/ +TEST_F (PeiMemoryServicesTest, KeepsTopDownAllocationFirstAfterPermanentMemoryInstall) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreedRange; + EFI_STATUS Status; + UINT64 Granularity; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + FreedRange = AddFreeRange (HobList.EfiFreeMemoryBottom, 2 * Granularity); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, 1, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, FREE_MEMORY_TOP - Granularity); + EXPECT_EQ (HobList.EfiFreeMemoryTop, Memory); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryBaseAddress, HobList.EfiFreeMemoryBottom); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryLength, 2 * Granularity); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryType, EfiConventionalMemory); +} + +/** + Verify that undersized freed ranges leave allocation to the PHIT free-memory top. + + The request is rounded to the configured page-allocation granularity before + the PHIT range is updated. +**/ +TEST_F (PeiMemoryServicesTest, FallsBackToFreeMemoryTopWhenFreedRangesAreTooSmall) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreedRange; + EFI_STATUS Status; + UINTN GranularityPages; + UINTN RequestedPages; + UINTN AllocatedPages; + + GranularityPages = EFI_SIZE_TO_PAGES (DEFAULT_PAGE_ALLOCATION_GRANULARITY); + RequestedPages = GranularityPages + 1; + AllocatedPages = ALIGN_VALUE (RequestedPages, GranularityPages); + FreedRange = AddFreeRange (0x40000000, DEFAULT_PAGE_ALLOCATION_GRANULARITY); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, RequestedPages, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, FREE_MEMORY_TOP - (AllocatedPages * EFI_PAGE_SIZE)); + EXPECT_EQ (HobList.EfiFreeMemoryTop, Memory); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryType, EfiConventionalMemory); + EXPECT_EQ (UnusedHob.Header.HobType, EFI_HOB_TYPE_MEMORY_ALLOCATION); + EXPECT_EQ (UnusedHob.AllocDescriptor.MemoryBaseAddress, Memory); + EXPECT_EQ (UnusedHob.AllocDescriptor.MemoryLength, AllocatedPages * EFI_PAGE_SIZE); +} + +/** + Verify that an invalid memory type is rejected without changing free memory. +**/ +TEST_F (PeiMemoryServicesTest, RejectsInvalidMemoryTypeWithoutChangingFreeMemoryTop) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_STATUS Status; + + Memory = 0; + Status = PeiAllocatePages (PeiServices (), EfiConventionalMemory, 1, &Memory); + + EXPECT_EQ (Status, EFI_INVALID_PARAMETER); + EXPECT_EQ (HobList.EfiFreeMemoryTop, FREE_MEMORY_TOP); + EXPECT_EQ (Memory, (EFI_PHYSICAL_ADDRESS)0); +} + +/** + Verify that a configured memory bin retains priority over freed ranges elsewhere. + + A suitable freed range outside the bin must remain unused while the request + can be satisfied from the configured bin. +**/ +TEST_F (PeiMemoryServicesTest, UsesConfiguredMemoryBinBeforeFreedRangesOutsideTheBin) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreedRange; + EFI_STATUS Status; + UINT64 Granularity; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + PrivateData.MemoryTypeInformationInitialized = TRUE; + PrivateData.MemoryTypeStatistics = MemoryTypeStatistics; + MemoryTypeStatistics[EfiLoaderData].BaseAddress = 0x60000000 - (2 * Granularity); + MemoryTypeStatistics[EfiLoaderData].MaximumAddress = 0x60000000; + FreedRange = AddFreeRange (0x40000000, 2 * Granularity); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, 1, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, (EFI_PHYSICAL_ADDRESS)(0x60000000 - Granularity)); + EXPECT_EQ (MemoryTypeStatistics[EfiLoaderData].MaximumAddress, Memory); + EXPECT_EQ (HobList.EfiFreeMemoryTop, FREE_MEMORY_TOP); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryType, EfiConventionalMemory); + EXPECT_EQ (UnusedHob.Header.HobType, EFI_HOB_TYPE_MEMORY_ALLOCATION); + EXPECT_EQ (UnusedHob.AllocDescriptor.MemoryBaseAddress, Memory); +} + +/** + Verify the final fallback can reuse adjacent free HOBs across memory bins. + + Bounded searches stay within their configured ranges, while the final + unrestricted search can satisfy the request using both adjacent ranges. +**/ +TEST_F (PeiMemoryServicesTest, MergesFreeRangesAcrossConfiguredMemoryBinsAsFinalFallback) { + EFI_HOB_MEMORY_ALLOCATION *FirstRange; + EFI_HOB_MEMORY_ALLOCATION *SecondRange; + EFI_STATUS Status; + UINT64 Granularity; + EFI_PHYSICAL_ADDRESS BinBase; + EFI_PHYSICAL_ADDRESS BinBoundary; + EFI_PHYSICAL_ADDRESS BinTop; + EFI_PHYSICAL_ADDRESS Memory; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + BinBase = 0x60000000; + BinBoundary = BinBase + Granularity; + BinTop = BinBoundary + Granularity; + PrivateData.MemoryTypeInformationInitialized = TRUE; + PrivateData.MemoryTypeStatistics = MemoryTypeStatistics; + MemoryTypeStatistics[EfiLoaderData].BaseAddress = BinBase; + MemoryTypeStatistics[EfiLoaderData].MaximumAddress = BinBoundary - 1; + MemoryTypeStatistics[EfiLoaderData].NumberOfPages = EFI_SIZE_TO_PAGES (Granularity); + MemoryTypeStatistics[EfiBootServicesData].BaseAddress = BinBoundary; + MemoryTypeStatistics[EfiBootServicesData].MaximumAddress = BinTop - 1; + MemoryTypeStatistics[EfiBootServicesData].NumberOfPages = EFI_SIZE_TO_PAGES (Granularity); + HobList.EfiFreeMemoryBottom = FREE_MEMORY_TOP; + HobList.EfiFreeMemoryTop = FREE_MEMORY_TOP; + FirstRange = AddFreeRange (BinBase, Granularity); + SecondRange = AddFreeRange (BinBoundary, Granularity); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, EFI_SIZE_TO_PAGES (2 * Granularity), &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, BinBase); + EXPECT_EQ (FirstRange->Header.HobType, EFI_HOB_TYPE_UNUSED); + EXPECT_EQ (SecondRange->AllocDescriptor.MemoryBaseAddress, Memory); + EXPECT_EQ (SecondRange->AllocDescriptor.MemoryLength, 2 * Granularity); + EXPECT_EQ (SecondRange->AllocDescriptor.MemoryType, EfiLoaderData); +} + +/** + Verify the final unrestricted search can reuse a range from another type bin. + + The requested type bin and PHIT are exhausted, while a suitable free range + remains in the EfiBootServicesData bin. +**/ +TEST_F (PeiMemoryServicesTest, ReusesFreedRangeFromDifferentMemoryBinAsFinalFallback) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreedRange; + EFI_STATUS Status; + UINT64 Granularity; + EFI_PHYSICAL_ADDRESS OtherBinBase; + EFI_PHYSICAL_ADDRESS OtherBinTop; + EFI_PHYSICAL_ADDRESS RequestedBinTop; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + OtherBinBase = 0x70000000; + OtherBinTop = OtherBinBase + (2 * Granularity); + RequestedBinTop = 0x60000000; + PrivateData.MemoryTypeInformationInitialized = TRUE; + PrivateData.MemoryTypeStatistics = MemoryTypeStatistics; + MemoryTypeStatistics[EfiLoaderData].BaseAddress = RequestedBinTop; + MemoryTypeStatistics[EfiLoaderData].MaximumAddress = RequestedBinTop; + MemoryTypeStatistics[EfiBootServicesData].BaseAddress = OtherBinBase; + MemoryTypeStatistics[EfiBootServicesData].MaximumAddress = OtherBinTop; + HobList.EfiFreeMemoryBottom = FREE_MEMORY_TOP; + FreedRange = AddFreeRange (OtherBinBase, 2 * Granularity); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, 1, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, OtherBinBase + Granularity); + EXPECT_EQ (MemoryTypeStatistics[EfiLoaderData].MaximumAddress, RequestedBinTop); + EXPECT_EQ (HobList.EfiFreeMemoryTop, FREE_MEMORY_TOP); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryBaseAddress, Memory); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryLength, Granularity); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryType, EfiLoaderData); +} + +/** + Verify the final unrestricted search can reuse a free range outside the PHIT. + + The PHIT has no available top-down memory, so allocation succeeds only if + the final free-HOB search is not restricted to the PHIT range. +**/ +TEST_F (PeiMemoryServicesTest, ReusesFreedRangeOutsidePhitAsFinalFallback) { + EFI_PHYSICAL_ADDRESS Memory; + EFI_HOB_MEMORY_ALLOCATION *FreedRange; + EFI_STATUS Status; + UINT64 Granularity; + EFI_PHYSICAL_ADDRESS RangeBase; + + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + RangeBase = FREE_MEMORY_TOP + Granularity; + HobList.EfiFreeMemoryBottom = FREE_MEMORY_TOP; + FreedRange = AddFreeRange (RangeBase, 2 * Granularity); + + Status = PeiAllocatePages (PeiServices (), EfiLoaderData, 1, &Memory); + + ASSERT_EQ (Status, EFI_SUCCESS); + EXPECT_EQ (Memory, RangeBase + Granularity); + EXPECT_EQ (HobList.EfiFreeMemoryTop, FREE_MEMORY_TOP); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryBaseAddress, Memory); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryLength, Granularity); + EXPECT_EQ (FreedRange->AllocDescriptor.MemoryType, EfiLoaderData); +} + +/** + Provide a test stub for PEI Core HOB creation. + + This entry point is unrelated to the AllocatePages() paths under test. + + @param[in] PeiServices PEI services table pointer. + @param[in] Type Type of HOB to create. + @param[in] Length Requested HOB length. + @param[out] Hob Receives the created HOB pointer. + + @retval EFI_OUT_OF_RESOURCES HOB creation is not supported by this stub. +**/ +extern "C" EFI_STATUS +EFIAPI +PeiCreateHob ( + IN CONST EFI_PEI_SERVICES **PeiServices, + IN UINT16 Type, + IN UINT16 Length, + IN OUT VOID **Hob + ) +{ + (VOID)PeiServices; + (VOID)Type; + (VOID)Length; + (VOID)Hob; + return EFI_OUT_OF_RESOURCES; +} + +/** + Provide a test stub for the PEI service-table HOB creation service. + + This entry point is referenced by unrelated pool-allocation code. + + @param[in] Type Type of HOB to create. + @param[in] Length Requested HOB length. + @param[out] Hob Receives the created HOB pointer. + + @retval EFI_OUT_OF_RESOURCES HOB creation is not supported by this stub. +**/ +extern "C" EFI_STATUS +EFIAPI +PeiServicesCreateHob ( + IN UINT16 Type, + IN UINT16 Length, + OUT VOID **Hob + ) +{ + (VOID)Type; + (VOID)Length; + (VOID)Hob; + return EFI_OUT_OF_RESOURCES; +} + +/** + Provide a test stub for memory-type information discovery. + + @param[out] MemoryTypeInformation Receives the discovered memory types. + + @retval EFI_NOT_FOUND Memory-type discovery is outside this test's scope. +**/ +extern "C" EFI_STATUS +EFIAPI +PopulateMemoryTypeInformation ( + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + (VOID)MemoryTypeInformation; + return EFI_NOT_FOUND; +} + +/** + Provide a test stub for memory-type information resource-HOB lookup. + + @param[in] HobStart Start of the HOB list. + @param[in] MemoryTypeInformation Memory-type information to locate. + + @return NULL because resource-HOB lookup is outside this test's scope. +**/ +extern "C" EFI_HOB_RESOURCE_DESCRIPTOR * +EFIAPI +GetMemoryTypeInformationResourceHob ( + IN VOID **HobStart, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + (VOID)HobStart; + (VOID)MemoryTypeInformation; + return NULL; +} + +/** + Provide a test stub for memory-bin range initialization. + + @param[in] Start Start of the memory-bin range. + @param[in] Length Length of the memory-bin range. + @param[in] MemoryTypeInformation Memory-type allocation counts. + @param[in, out] MemoryTypeInformationInitialized Memory-bin initialization state. + @param[out] MemoryTypeStatistics Memory-bin address statistics. + @param[in, out] DefaultMaximumAddress Default bin maximum address. +**/ +extern "C" VOID +EFIAPI +CoreSetMemoryTypeInformationRange ( + IN EFI_PHYSICAL_ADDRESS Start, + IN UINT64 Length, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN BOOLEAN *MemoryTypeInformationInitialized, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_PHYSICAL_ADDRESS *DefaultMaximumAddress + ) +{ + (VOID)Start; + (VOID)Length; + (VOID)MemoryTypeInformation; + (VOID)MemoryTypeInformationInitialized; + (VOID)MemoryTypeStatistics; + (VOID)DefaultMaximumAddress; +} + +/** + Provide a test stub for memory-bin allocation. + + @param[in, out] MemoryTypeInformationInitialized Memory-bin initialization state. + @param[in] MemoryTypeInformation Memory-type allocation counts. + @param[out] MemoryTypeStatistics Memory-bin address statistics. + @param[in, out] DefaultMaximumAddress Default bin maximum address. + @param[in] CreateHob Whether to create the resource HOB. +**/ +extern "C" VOID +EFIAPI +AllocateMemoryTypeInformationBins ( + IN BOOLEAN *MemoryTypeInformationInitialized, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_PHYSICAL_ADDRESS *DefaultMaximumAddress, + IN BOOLEAN CreateHob + ) +{ + (VOID)MemoryTypeInformationInitialized; + (VOID)MemoryTypeInformation; + (VOID)MemoryTypeStatistics; + (VOID)DefaultMaximumAddress; + (VOID)CreateHob; +} + +/** + Provide a test stub for PEI HOB-list retrieval. + + @param[in] PeiServices PEI services table pointer. + @param[out] HobList Receives the HOB list pointer. + + @retval EFI_NOT_AVAILABLE_YET HOB-list retrieval is outside this test's scope. +**/ +extern "C" EFI_STATUS +EFIAPI +PeiGetHobList ( + IN CONST EFI_PEI_SERVICES **PeiServices, + IN OUT VOID **HobList + ) +{ + (VOID)PeiServices; + (VOID)HobList; + return EFI_NOT_AVAILABLE_YET; +} + +/** + Provide a test stub for PHIT construction. + + @param[in] BootMode Current boot mode. + @param[in] MemoryBegin Start address of the memory region. + @param[in] MemoryLength Length of the memory region in bytes. +**/ +extern "C" VOID +PeiCoreBuildHobHandoffInfoTable ( + IN EFI_BOOT_MODE BootMode, + IN EFI_PHYSICAL_ADDRESS MemoryBegin, + IN UINT64 MemoryLength + ) +{ + (VOID)BootMode; + (VOID)MemoryBegin; + (VOID)MemoryLength; +} + +/** + Initialize GoogleTest and execute the PEI memory-service test cases. + + @param[in] argc Number of command-line arguments. + @param[in] argv Command-line argument array. + + @return GoogleTest's result code: zero if all tests pass, nonzero otherwise. +**/ +int +main ( + int argc, + char *argv[] + ) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTestHost.inf b/MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTestHost.inf new file mode 100644 index 000000000000..c88541aa32fc --- /dev/null +++ b/MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTestHost.inf @@ -0,0 +1,39 @@ +## @file +# Unit tests for PEI Core page allocation. +# +# Builds the host-based test application that exercises AllocatePages() against +# synthetic HOB lists and verifies freed-range reuse and fallback behavior. +# +# Copyright (c) 2026, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = PeiMemoryServicesGoogleTest + FILE_GUID = 28D287A7-50AC-4A49-AB33-76FC0A392784 + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +[Sources] + PeiMemoryServicesGoogleTest.cpp + Memory/MemoryServices.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + +[LibraryClasses] + GoogleTestLib + BaseLib + BaseMemoryLib + DebugLib + HobLib + PcdLib + +[Guids] + gEfiMemoryTypeInformationGuid + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdPeiMemoryBinsEnable diff --git a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc index aeecc1a2f7ec..da24657efd03 100644 --- a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc +++ b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc @@ -77,6 +77,11 @@ HobLib|MdePkg/Test/Mock/Library/GoogleTest/MockHobLib/MockHobLib.inf } + MdeModulePkg/Core/Pei/PeiMemoryServicesGoogleTestHost.inf { + + HobLib|MdePkg/Test/Mock/Library/GoogleTest/MockHobLib/MockHobLib.inf + } + MdeModulePkg/Library/GptLib/UnitTest/GptLibUnitTestHost.inf { GptLib|MdeModulePkg/Library/GptLib/GptLib.inf