diff --git a/MdeModulePkg/Core/Dxe/DxeMain.h b/MdeModulePkg/Core/Dxe/DxeMain.h index 328da465b2c..9b325dbda94 100644 --- a/MdeModulePkg/Core/Dxe/DxeMain.h +++ b/MdeModulePkg/Core/Dxe/DxeMain.h @@ -263,6 +263,12 @@ extern EFI_GUID *gDxeCoreFileName; extern EFI_LOADED_IMAGE_PROTOCOL *gDxeCoreLoadedImage; extern EFI_MEMORY_TYPE_INFORMATION gMemoryTypeInformation[EfiMaxMemoryType + 1]; +// MU_CHANGE BEGIN: PEI Bins +extern BOOLEAN mMemoryTypeInformationInitialized; +extern EFI_MEMORY_TYPE_STATISTICS mMemoryTypeStatistics[EfiMaxMemoryType + 1]; +extern EFI_PHYSICAL_ADDRESS mDefaultMaximumAddress; +extern EFI_PHYSICAL_ADDRESS mDefaultBaseAddress; +// MU_CHANGE END: PEI Bins extern BOOLEAN gDispatcherRunning; extern EFI_RUNTIME_ARCH_PROTOCOL gRuntimeTemplate; @@ -284,11 +290,13 @@ CoreInitializePool ( VOID ); -VOID -CoreSetMemoryTypeInformationRange ( - IN EFI_PHYSICAL_ADDRESS Start, - IN UINT64 Length - ); +// MU_CHANGE BEGIN: PEI Bins +// VOID +// CoreSetMemoryTypeInformationRange ( +// IN EFI_PHYSICAL_ADDRESS Start, +// IN UINT64 Length +// ); +// MU_CHANGE END: PEI Bins /** Called to initialize the memory map and add descriptors to diff --git a/MdeModulePkg/Core/Dxe/DxeMain.inf b/MdeModulePkg/Core/Dxe/DxeMain.inf index d95ef8840e8..1a44efb2785 100644 --- a/MdeModulePkg/Core/Dxe/DxeMain.inf +++ b/MdeModulePkg/Core/Dxe/DxeMain.inf @@ -50,6 +50,7 @@ Mem/Page.c Mem/MemData.c Mem/Imem.h + Mem/MemoryBin.c # MU_CHANGE: PEI Bins Mem/MemoryProfileRecord.c Mem/HeapGuard.c Mem/HeapGuard.h diff --git a/MdeModulePkg/Core/Dxe/Gcd/Gcd.c b/MdeModulePkg/Core/Dxe/Gcd/Gcd.c index 3c2484d12de..770671de4f7 100644 --- a/MdeModulePkg/Core/Dxe/Gcd/Gcd.c +++ b/MdeModulePkg/Core/Dxe/Gcd/Gcd.c @@ -13,6 +13,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include "DxeMain.h" #include "Gcd.h" #include "Mem/HeapGuard.h" +#include // MU_CHANGE: PEI Bins #define MINIMUM_INITIAL_MEMORY_SIZE 0x10000 @@ -2164,30 +2165,32 @@ CoreConvertResourceDescriptorHobAttributesToCapabilities ( return Capabilities; } -/** - Calculate total memory bin size neeeded. - - @return The total memory bin size neeeded. - -**/ -UINT64 -CalculateTotalMemoryBinSizeNeeded ( - VOID - ) -{ - UINTN Index; - UINT64 TotalSize; - - // - // Loop through each memory type in the order specified by the gMemoryTypeInformation[] array - // - TotalSize = 0; - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - TotalSize += LShiftU64 (gMemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); - } - - return TotalSize; -} +// MU_CHANGE BEGIN: PEI Bins - Moved to MemoryBin.c +// /** +// Calculate total memory bin size neeeded. +// +// @return The total memory bin size neeeded. +// +// **/ +// UINT64 +// CalculateTotalMemoryBinSizeNeeded ( +// VOID +// ) +// { +// UINTN Index; +// UINT64 TotalSize; +// +// // +// // Loop through each memory type in the order specified by the gMemoryTypeInformation[] array +// // +// TotalSize = 0; +// for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { +// TotalSize += LShiftU64 (gMemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); +// } +// +// return TotalSize; +// } +// MU_CHANGE END: PEI Bins /** Find the largest region in the specified region that is not covered by an existing memory allocation @@ -2270,14 +2273,16 @@ CoreInitializeMemoryServices ( ) { EFI_PEI_HOB_POINTERS Hob; - EFI_MEMORY_TYPE_INFORMATION *EfiMemoryTypeInformation; - UINTN DataSize; + // MU_CHANGE BEGIN: PEI Bins + // EFI_MEMORY_TYPE_INFORMATION *EfiMemoryTypeInformation; + // UINTN DataSize; + // MU_CHANGE END: PEI Bins BOOLEAN Found; EFI_HOB_HANDOFF_INFO_TABLE *PhitHob; EFI_HOB_RESOURCE_DESCRIPTOR *ResourceHob; EFI_HOB_RESOURCE_DESCRIPTOR *PhitResourceHob; EFI_HOB_RESOURCE_DESCRIPTOR *MemoryTypeInformationResourceHob; - UINTN Count; + // UINTN Count; // MU_CHANGE: PEI Bins EFI_PHYSICAL_ADDRESS BaseAddress; UINT64 Length; UINT64 Attributes; @@ -2285,10 +2290,11 @@ CoreInitializeMemoryServices ( EFI_PHYSICAL_ADDRESS TestedMemoryBaseAddress; UINT64 TestedMemoryLength; EFI_PHYSICAL_ADDRESS HighAddress; - EFI_HOB_GUID_TYPE *GuidHob; + // EFI_HOB_GUID_TYPE *GuidHob; // MU_CHANGE: PEI Bins UINT32 ReservedCodePageNumber; UINT64 MinimalMemorySizeNeeded; - EFI_PHYSICAL_ADDRESS ResourceHobMemoryTop; // MU_CHANGE + EFI_PHYSICAL_ADDRESS ResourceHobMemoryTop; + EFI_STATUS Status; // MU_CHANGE: PEI Bins // // Point at the first HOB. This must be the PHIT HOB. @@ -2327,57 +2333,68 @@ CoreInitializeMemoryServices ( + EFI_PAGES_TO_SIZE (ReservedCodePageNumber); } - // - // See if a Memory Type Information HOB is available - // - MemoryTypeInformationResourceHob = NULL; - GuidHob = GetFirstGuidHob (&gEfiMemoryTypeInformationGuid); - if (GuidHob != NULL) { - EfiMemoryTypeInformation = GET_GUID_HOB_DATA (GuidHob); - DataSize = GET_GUID_HOB_DATA_SIZE (GuidHob); - if ((EfiMemoryTypeInformation != NULL) && (DataSize > 0) && (DataSize <= (EfiMaxMemoryType + 1) * sizeof (EFI_MEMORY_TYPE_INFORMATION))) { - CopyMem (&gMemoryTypeInformation, EfiMemoryTypeInformation, DataSize); - - // - // Look for Resource Descriptor HOB with a ResourceType of System Memory - // and an Owner GUID of gEfiMemoryTypeInformationGuid. If more than 1 is - // found, then set MemoryTypeInformationResourceHob to NULL. - // - Count = 0; - for (Hob.Raw = *HobStart; !END_OF_HOB_LIST (Hob); Hob.Raw = GET_NEXT_HOB (Hob)) { - if ((GET_HOB_TYPE (Hob) != EFI_HOB_TYPE_RESOURCE_DESCRIPTOR) && (GET_HOB_TYPE (Hob) != EFI_HOB_TYPE_RESOURCE_DESCRIPTOR2)) { - continue; - } - - ResourceHob = Hob.ResourceDescriptor; - if (!CompareGuid (&ResourceHob->Owner, &gEfiMemoryTypeInformationGuid)) { - continue; - } - - Count++; - if (ResourceHob->ResourceType != EFI_RESOURCE_SYSTEM_MEMORY) { - continue; - } - - if ((ResourceHob->ResourceAttribute & MEMORY_ATTRIBUTE_MASK) != TESTED_MEMORY_ATTRIBUTES) { - continue; - } - - if (ResourceHob->ResourceLength >= CalculateTotalMemoryBinSizeNeeded ()) { - MemoryTypeInformationResourceHob = ResourceHob; - } - } - - if (Count > 1) { - MemoryTypeInformationResourceHob = NULL; - } - } + // MU_CHANGE BEGIN: PEI Bins - Replaced inline HOB search with shared MemoryBin functions + // // + // // See if a Memory Type Information HOB is available + // // + // MemoryTypeInformationResourceHob = NULL; + // GuidHob = GetFirstGuidHob (&gEfiMemoryTypeInformationGuid); + // if (GuidHob != NULL) { + // EfiMemoryTypeInformation = GET_GUID_HOB_DATA (GuidHob); + // DataSize = GET_GUID_HOB_DATA_SIZE (GuidHob); + // if ((EfiMemoryTypeInformation != NULL) && (DataSize > 0) && (DataSize <= (EfiMaxMemoryType + 1) * sizeof (EFI_MEMORY_TYPE_INFORMATION))) { + // CopyMem (&gMemoryTypeInformation, EfiMemoryTypeInformation, DataSize); + // + // // + // // Look for Resource Descriptor HOB with a ResourceType of System Memory + // // and an Owner GUID of gEfiMemoryTypeInformationGuid. If more than 1 is + // // found, then set MemoryTypeInformationResourceHob to NULL. + // // + // Count = 0; + // for (Hob.Raw = *HobStart; !END_OF_HOB_LIST (Hob); Hob.Raw = GET_NEXT_HOB (Hob)) { + // if ((GET_HOB_TYPE (Hob) != EFI_HOB_TYPE_RESOURCE_DESCRIPTOR) && (GET_HOB_TYPE (Hob) != EFI_HOB_TYPE_RESOURCE_DESCRIPTOR2)) { + // continue; + // } + // + // ResourceHob = Hob.ResourceDescriptor; + // if (!CompareGuid (&ResourceHob->Owner, &gEfiMemoryTypeInformationGuid)) { + // continue; + // } + // + // Count++; + // if (ResourceHob->ResourceType != EFI_RESOURCE_SYSTEM_MEMORY) { + // continue; + // } + // + // if ((ResourceHob->ResourceAttribute & MEMORY_ATTRIBUTE_MASK) != TESTED_MEMORY_ATTRIBUTES) { + // continue; + // } + // + // if (ResourceHob->ResourceLength >= CalculateTotalMemoryBinSizeNeeded ()) { + // MemoryTypeInformationResourceHob = ResourceHob; + // } + // } + // + // if (Count > 1) { + // MemoryTypeInformationResourceHob = NULL; + // } + // } + // } + Status = PopulateMemoryTypeInformation (gMemoryTypeInformation); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "No Memory Type Information HOB found, S4 resume will likely fail\n")); } + MemoryTypeInformationResourceHob = GetMemoryTypeInformationResourceHob ( + HobStart, + gMemoryTypeInformation + ); + // // Include the total memory bin size needed to make sure memory bin could be allocated successfully. // - MinimalMemorySizeNeeded = MINIMUM_INITIAL_MEMORY_SIZE + CalculateTotalMemoryBinSizeNeeded (); + MinimalMemorySizeNeeded = MINIMUM_INITIAL_MEMORY_SIZE + CalculateTotalMemoryBinSizeNeeded (0, gMemoryTypeInformation); + // MU_CHANGE END: PEI Bins // // Find the Resource Descriptor HOB that contains PHIT range EfiFreeMemoryBottom..EfiFreeMemoryTop @@ -2586,16 +2603,29 @@ CoreInitializeMemoryServices ( Capabilities = CoreConvertResourceDescriptorHobAttributesToCapabilities (EfiGcdMemoryTypeSystemMemory, Attributes); } - if (MemoryTypeInformationResourceHob != NULL) { + // MU_CHANGE BEGIN: PEI Bins + // if (MemoryTypeInformationResourceHob != NULL) { + if ((Status == EFI_SUCCESS) && (MemoryTypeInformationResourceHob != NULL)) { + // MU_CHANGE END: PEI Bins // // If a Memory Type Information Resource HOB was found, then use the address // range of the Memory Type Information Resource HOB as the preferred // address range for the Memory Type Information bins. // + // MU_CHANGE BEGIN: PEI Bins + // CoreSetMemoryTypeInformationRange ( + // MemoryTypeInformationResourceHob->PhysicalStart, + // MemoryTypeInformationResourceHob->ResourceLength + // ); CoreSetMemoryTypeInformationRange ( MemoryTypeInformationResourceHob->PhysicalStart, - MemoryTypeInformationResourceHob->ResourceLength + MemoryTypeInformationResourceHob->ResourceLength, + gMemoryTypeInformation, + &mMemoryTypeInformationInitialized, + mMemoryTypeStatistics, + &mDefaultMaximumAddress ); + // MU_CHANGE END: PEI Bins } // @@ -2843,6 +2873,23 @@ CoreInitializeGcdServices ( RShiftU64 (MemoryHob->AllocDescriptor.MemoryLength, EFI_PAGE_SHIFT), Descriptor.Capabilities & (~EFI_MEMORY_RUNTIME) ); + + // MU_CHANGE BEGIN: PEI Bins - Update memory bin statistics from PEI allocation HOBs + // if this Memory Allocation HOB came from PEI, update the memory bin statistics + if (CompareGuid (&MemoryHob->AllocDescriptor.Name, &gEfiMemoryTypeInformationGuid)) { + UpdateMemoryStatistics ( + EfiConventionalMemory, + MemoryHob->AllocDescriptor.MemoryType, + MemoryHob->AllocDescriptor.MemoryBaseAddress, + EFI_SIZE_TO_PAGES ((UINT32)MemoryHob->AllocDescriptor.MemoryLength), + &mMemoryTypeInformationInitialized, + mMemoryTypeStatistics, + gMemoryTypeInformation, + mDefaultBaseAddress, + mDefaultMaximumAddress + ); + } + // MU_CHANGE END: PEI Bins } } } diff --git a/MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTest.cpp b/MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTest.cpp new file mode 100644 index 00000000000..9011f5ac7b3 --- /dev/null +++ b/MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTest.cpp @@ -0,0 +1,611 @@ +// MU_CHANGE: PEI Bins - Whole File +/** @file + Unit tests for BaseMemoryBinLib library. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +extern "C" { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + BOOLEAN mMemoryTypeInformationInitialized = FALSE; + + EFI_MEMORY_TYPE_STATISTICS mMemoryTypeStatistics[EfiMaxMemoryType + 1] = { + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiReservedMemoryType + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiLoaderCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiLoaderData + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiBootServicesCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiBootServicesData + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE, FALSE }, // EfiRuntimeServicesCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE, FALSE }, // EfiRuntimeServicesData + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiConventionalMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiUnusableMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiACPIReclaimMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiACPIMemoryNVS + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiMemoryMappedIO + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiMemoryMappedIOPortSpace + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE, FALSE }, // EfiPalCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiPersistentMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiUnacceptedMemoryType + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE } // EfiMaxMemoryType + }; + + EFI_PHYSICAL_ADDRESS mDefaultMaximumAddress = MAX_ALLOC_ADDRESS; + EFI_PHYSICAL_ADDRESS mDefaultBaseAddress = MAX_ALLOC_ADDRESS; + + EFI_MEMORY_TYPE_INFORMATION gMemoryTypeInformation[EfiMaxMemoryType + 1] = { + { EfiReservedMemoryType, 0 }, + { EfiLoaderCode, 0 }, + { EfiLoaderData, 0 }, + { EfiBootServicesCode, 0 }, + { EfiBootServicesData, 0 }, + { EfiRuntimeServicesCode, 0 }, + { EfiRuntimeServicesData, 0 }, + { EfiConventionalMemory, 0 }, + { EfiUnusableMemory, 0 }, + { EfiACPIReclaimMemory, 0 }, + { EfiACPIMemoryNVS, 0 }, + { EfiMemoryMappedIO, 0 }, + { EfiMemoryMappedIOPortSpace, 0 }, + { EfiPalCode, 0 }, + { EfiPersistentMemory, 0 }, + { EfiGcdMemoryTypeUnaccepted, 0 }, + { EfiMaxMemoryType, 0 } + }; +} + +using namespace testing; + +class BaseMemoryBinLibTest : public ::testing::Test { +protected: + StrictMock HobLib; + void + SetUp ( + ) override + { + // + // Reset global state before each test + // + mMemoryTypeInformationInitialized = FALSE; + mDefaultMaximumAddress = MAX_ALLOC_ADDRESS; + mDefaultBaseAddress = MAX_ALLOC_ADDRESS; + + for (UINTN i = 0; i < EfiMaxMemoryType + 1; i++) { + gMemoryTypeInformation[i].NumberOfPages = 0; + } + + for (UINTN i = 0; i < EfiMaxMemoryType + 1; i++) { + mMemoryTypeStatistics[i].BaseAddress = 0; + mMemoryTypeStatistics[i].MaximumAddress = MAX_ALLOC_ADDRESS; + mMemoryTypeStatistics[i].CurrentNumberOfPages = 0; + mMemoryTypeStatistics[i].NumberOfPages = 0; + mMemoryTypeStatistics[i].InformationIndex = EfiMaxMemoryType; + mMemoryTypeStatistics[i].DefaultBin = FALSE; + } + + mMemoryTypeStatistics[EfiReservedMemoryType].Special = TRUE; + mMemoryTypeStatistics[EfiRuntimeServicesCode].Special = TRUE; + mMemoryTypeStatistics[EfiRuntimeServicesCode].Runtime = TRUE; + mMemoryTypeStatistics[EfiRuntimeServicesData].Special = TRUE; + mMemoryTypeStatistics[EfiRuntimeServicesData].Runtime = TRUE; + mMemoryTypeStatistics[EfiACPIReclaimMemory].Special = TRUE; + mMemoryTypeStatistics[EfiACPIMemoryNVS].Special = TRUE; + mMemoryTypeStatistics[EfiPalCode].Special = TRUE; + mMemoryTypeStatistics[EfiPalCode].Runtime = TRUE; + mMemoryTypeStatistics[EfiUnacceptedMemoryType].Special = TRUE; + } +}; + +// +// Test: CalculateTotalMemoryBinSizeNeeded with no pages allocated +// +TEST_F (BaseMemoryBinLibTest, CalculateTotalMemoryBinSizeNeededReturnsZeroWhenNoPagesAllocated) { + UINT64 TotalSize; + + TotalSize = CalculateTotalMemoryBinSizeNeeded (0, gMemoryTypeInformation); + + ASSERT_EQ (TotalSize, (UINT64)0); +} + +// +// Test: CalculateTotalMemoryBinSizeNeeded with pages allocated +// +TEST_F (BaseMemoryBinLibTest, CalculateTotalMemoryBinSizeNeededCalculatesCorrectSize) { + UINT64 TotalSize; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 10; + gMemoryTypeInformation[1].Type = EfiRuntimeServicesData; + gMemoryTypeInformation[1].NumberOfPages = 20; + gMemoryTypeInformation[2].Type = EfiMaxMemoryType; + + TotalSize = CalculateTotalMemoryBinSizeNeeded (0, gMemoryTypeInformation); + + ASSERT_EQ (TotalSize, (UINT64)(30 * EFI_PAGE_SIZE)); +} + +// +// Test: CoreSetMemoryTypeInformationRange initializes bins correctly +// +TEST_F (BaseMemoryBinLibTest, CoreSetMemoryTypeInformationRangeSetsUpBinsCorrectly) { + EFI_PHYSICAL_ADDRESS Start; + UINT64 Length; + + Start = 0x100000; + Length = 0x100000; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 20; + gMemoryTypeInformation[1].Type = EfiRuntimeServicesData; + gMemoryTypeInformation[1].NumberOfPages = 10; + gMemoryTypeInformation[2].Type = EfiReservedMemoryType; + gMemoryTypeInformation[2].NumberOfPages = 5; + gMemoryTypeInformation[3].Type = EfiACPIReclaimMemory; + gMemoryTypeInformation[3].NumberOfPages = 15; + gMemoryTypeInformation[4].Type = EfiACPIMemoryNVS; + gMemoryTypeInformation[4].NumberOfPages = 25; + gMemoryTypeInformation[5].Type = EfiMaxMemoryType; + + CoreSetMemoryTypeInformationRange (Start, Length, gMemoryTypeInformation, &mMemoryTypeInformationInitialized, mMemoryTypeStatistics, &mDefaultMaximumAddress); + + ASSERT_TRUE (mMemoryTypeInformationInitialized); + + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].NumberOfPages, (UINT32)20); + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesData].NumberOfPages, (UINT32)10); + ASSERT_EQ (mMemoryTypeStatistics[EfiReservedMemoryType].NumberOfPages, (UINT32)5); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIReclaimMemory].NumberOfPages, (UINT32)15); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIMemoryNVS].NumberOfPages, (UINT32)25); + + // Confirm contiguous bins + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesData].MaximumAddress + 1, mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress); + ASSERT_EQ (mMemoryTypeStatistics[EfiReservedMemoryType].MaximumAddress + 1, mMemoryTypeStatistics[EfiRuntimeServicesData].BaseAddress); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIReclaimMemory].MaximumAddress + 1, mMemoryTypeStatistics[EfiReservedMemoryType].BaseAddress); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIMemoryNVS].MaximumAddress + 1, mMemoryTypeStatistics[EfiACPIReclaimMemory].BaseAddress); +} + +// +// Test: CoreSetMemoryTypeInformationRange rejects insufficient space +// +TEST_F (BaseMemoryBinLibTest, CoreSetMemoryTypeInformationRangeRejectsInsufficientSpace) { + EFI_PHYSICAL_ADDRESS Start; + UINT64 Length; + + Start = 0x100000; + Length = 0x1000; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 100; // Requires more than 4KB + gMemoryTypeInformation[1].Type = EfiMaxMemoryType; + + CoreSetMemoryTypeInformationRange (Start, Length, gMemoryTypeInformation, &mMemoryTypeInformationInitialized, mMemoryTypeStatistics, &mDefaultMaximumAddress); + + ASSERT_FALSE (mMemoryTypeInformationInitialized); +} + +// +// Test: CoreSetMemoryTypeInformationRange respects already initialized state +// +TEST_F (BaseMemoryBinLibTest, CoreSetMemoryTypeInformationRangeRespectsInitializedState) { + EFI_PHYSICAL_ADDRESS Start; + UINT64 Length; + + mMemoryTypeInformationInitialized = TRUE; + + Start = 0x100000; + Length = 0x100000; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 10; + mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress = 0x1000; + gMemoryTypeInformation[1].Type = EfiMaxMemoryType; + + CoreSetMemoryTypeInformationRange (Start, Length, gMemoryTypeInformation, &mMemoryTypeInformationInitialized, mMemoryTypeStatistics, &mDefaultMaximumAddress); + + ASSERT_TRUE (mMemoryTypeInformationInitialized); + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress, (EFI_PHYSICAL_ADDRESS)0x1000); +} + +// +// Test: UpdateMemoryStatistics updates in-bin statistics correctly +// +TEST_F (BaseMemoryBinLibTest, UpdateMemoryStatisticsUpdatesInBinCorrectly) { + mMemoryTypeInformationInitialized = TRUE; + + mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress = 0x100000; + mMemoryTypeStatistics[EfiRuntimeServicesCode].MaximumAddress = 0x200000; + mMemoryTypeStatistics[EfiRuntimeServicesCode].InformationIndex = 0; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 0; + + UpdateMemoryStatistics ( + EfiConventionalMemory, + EfiRuntimeServicesCode, + 0x150000, // Within bin range + 10, + &mMemoryTypeInformationInitialized, + mMemoryTypeStatistics, + gMemoryTypeInformation, + 0x10000, // Just set these outside of the range + 0x20000 + ); + + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].CurrentNumberOfPages, (UINT32)10); +} + +// +// Test: UpdateMemoryStatistics updates out-of-bin statistics correctly +// +TEST_F (BaseMemoryBinLibTest, UpdateMemoryStatisticsUpdatesOutOfBinCorrectly) { + mMemoryTypeInformationInitialized = TRUE; + + mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress = 0x100000; + mMemoryTypeStatistics[EfiRuntimeServicesCode].MaximumAddress = 0x200000; + mMemoryTypeStatistics[EfiRuntimeServicesCode].InformationIndex = 0; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 0; + + UpdateMemoryStatistics ( + EfiConventionalMemory, + EfiRuntimeServicesCode, + 0x300000, // Outside bin range + 10, + &mMemoryTypeInformationInitialized, + mMemoryTypeStatistics, + gMemoryTypeInformation, + 0x10000, // Just set these outside of the range + 0x20000 + ); + + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].CurrentNumberOfPages, (UINT32)0); +} + +// +// Test: UpdateMemoryStatistics handles freeing memory +// +TEST_F (BaseMemoryBinLibTest, UpdateMemoryStatisticsHandlesFreeingMemory) { + mMemoryTypeInformationInitialized = TRUE; + + mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress = 0x100000; + mMemoryTypeStatistics[EfiRuntimeServicesCode].MaximumAddress = 0x200000; + mMemoryTypeStatistics[EfiRuntimeServicesCode].CurrentNumberOfPages = 20; + mMemoryTypeStatistics[EfiRuntimeServicesCode].InformationIndex = 0; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 20; + + UpdateMemoryStatistics ( + EfiRuntimeServicesCode, + EfiConventionalMemory, + 0x150000, // Within bin range + 10, + &mMemoryTypeInformationInitialized, + mMemoryTypeStatistics, + gMemoryTypeInformation, + 0x10000, // Just set these outside of the range + 0x20000 + ); + + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].CurrentNumberOfPages, (UINT32)10); +} + +// +// Tests for AllocateMemoryTypeInformationBins +// + +// +// Test: AllocateMemoryTypeInformationBins does nothing when already initialized +// +TEST_F (BaseMemoryBinLibTest, DoesNothingWhenAlreadyInitialized) { + mMemoryTypeInformationInitialized = TRUE; + mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress = 0x1000; + + AllocateMemoryTypeInformationBins (&mMemoryTypeInformationInitialized, gMemoryTypeInformation, mMemoryTypeStatistics, &mDefaultMaximumAddress, FALSE); + + // Should remain unchanged since already initialized + ASSERT_TRUE (mMemoryTypeInformationInitialized); + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress, (EFI_PHYSICAL_ADDRESS)0x1000); +} + +// +// Test: AllocateMemoryTypeInformationBins does nothing when no pages needed +// +TEST_F (BaseMemoryBinLibTest, DoesNothingWhenNoPagesNeeded) { + AllocateMemoryTypeInformationBins (&mMemoryTypeInformationInitialized, gMemoryTypeInformation, mMemoryTypeStatistics, &mDefaultMaximumAddress, FALSE); + + ASSERT_TRUE (mMemoryTypeInformationInitialized); + ASSERT_EQ (mMemoryTypeStatistics[EfiReservedMemoryType].NumberOfPages, (UINT32)0); + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].NumberOfPages, (UINT32)0); + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesData].NumberOfPages, (UINT32)0); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIReclaimMemory].NumberOfPages, (UINT32)0); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIMemoryNVS].NumberOfPages, (UINT32)0); +} + +// +// Test: AllocateMemoryTypeInformationBins allocates pages and initializes bins +// +TEST_F (BaseMemoryBinLibTest, AllocatesPagesAndInitializesBins) { + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 20; + gMemoryTypeInformation[1].Type = EfiRuntimeServicesData; + gMemoryTypeInformation[1].NumberOfPages = 10; + gMemoryTypeInformation[2].Type = EfiReservedMemoryType; + gMemoryTypeInformation[2].NumberOfPages = 5; + gMemoryTypeInformation[3].Type = EfiACPIReclaimMemory; + gMemoryTypeInformation[3].NumberOfPages = 15; + gMemoryTypeInformation[4].Type = EfiACPIMemoryNVS; + gMemoryTypeInformation[4].NumberOfPages = 25; + gMemoryTypeInformation[5].Type = EfiMaxMemoryType; + + // HOB should not be built in this case + EXPECT_CALL (HobLib, BuildResourceDescriptorWithOwnerHob (_, _, _, _, _)) + .Times (0); + + AllocateMemoryTypeInformationBins (&mMemoryTypeInformationInitialized, gMemoryTypeInformation, mMemoryTypeStatistics, &mDefaultMaximumAddress, FALSE); + + ASSERT_TRUE (mMemoryTypeInformationInitialized); + + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesCode].NumberOfPages, (UINT32)20); + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesData].NumberOfPages, (UINT32)10); + ASSERT_EQ (mMemoryTypeStatistics[EfiReservedMemoryType].NumberOfPages, (UINT32)5); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIReclaimMemory].NumberOfPages, (UINT32)15); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIMemoryNVS].NumberOfPages, (UINT32)25); + + // Confirm contiguous bins + ASSERT_EQ (mMemoryTypeStatistics[EfiRuntimeServicesData].MaximumAddress + 1, mMemoryTypeStatistics[EfiRuntimeServicesCode].BaseAddress); + ASSERT_EQ (mMemoryTypeStatistics[EfiReservedMemoryType].MaximumAddress + 1, mMemoryTypeStatistics[EfiRuntimeServicesData].BaseAddress); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIReclaimMemory].MaximumAddress + 1, mMemoryTypeStatistics[EfiReservedMemoryType].BaseAddress); + ASSERT_EQ (mMemoryTypeStatistics[EfiACPIMemoryNVS].MaximumAddress + 1, mMemoryTypeStatistics[EfiACPIReclaimMemory].BaseAddress); +} + +// +// Test: AllocateMemoryTypeInformationBins creates HOB when requested +// +TEST_F (BaseMemoryBinLibTest, CreatesHobWhenRequested) { + UINT64 TotalPages = 30; + + gMemoryTypeInformation[0].Type = EfiRuntimeServicesCode; + gMemoryTypeInformation[0].NumberOfPages = 10; + gMemoryTypeInformation[1].Type = EfiRuntimeServicesData; + gMemoryTypeInformation[1].NumberOfPages = 20; + gMemoryTypeInformation[2].Type = EfiMaxMemoryType; + + EXPECT_CALL ( + HobLib, + BuildResourceDescriptorWithOwnerHob ( + EFI_RESOURCE_SYSTEM_MEMORY, + EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED, + _, + TotalPages * EFI_PAGE_SIZE, + &gEfiMemoryTypeInformationGuid + ) + ) + .Times (1); + + AllocateMemoryTypeInformationBins (&mMemoryTypeInformationInitialized, gMemoryTypeInformation, mMemoryTypeStatistics, &mDefaultMaximumAddress, TRUE); + + ASSERT_TRUE (mMemoryTypeInformationInitialized); +} + +// +// Test: PopulateMemoryTypeInformation returns NOT_FOUND when Memory Type Information HOB is missing +// +TEST_F (BaseMemoryBinLibTest, ReturnsNotFoundWhenMemoryTypeInformationHobMissing) { + EFI_STATUS Status; + + EXPECT_CALL (HobLib, GetFirstGuidHob (&gEfiMemoryTypeInformationGuid)) + .WillOnce (Return ((VOID *)NULL)); + + Status = PopulateMemoryTypeInformation (gMemoryTypeInformation); + + ASSERT_EQ (Status, EFI_NOT_FOUND); +} + +// +// Test: PopulateMemoryTypeInformation populates from valid HOB +// +TEST_F (BaseMemoryBinLibTest, PopulatesFromValidHob) { + EFI_STATUS Status; + UINT8 GuidHobBuffer[sizeof (EFI_HOB_GUID_TYPE) + sizeof (EFI_MEMORY_TYPE_INFORMATION) * 7]; + EFI_HOB_GUID_TYPE *GuidHob; + EFI_MEMORY_TYPE_INFORMATION *MemTypeInfo; + + ZeroMem (GuidHobBuffer, sizeof (GuidHobBuffer)); + GuidHob = (EFI_HOB_GUID_TYPE *)GuidHobBuffer; + GuidHob->Header.HobType = EFI_HOB_TYPE_GUID_EXTENSION; + GuidHob->Header.HobLength = sizeof (GuidHobBuffer); + CopyGuid (&GuidHob->Name, &gEfiMemoryTypeInformationGuid); + + MemTypeInfo = (EFI_MEMORY_TYPE_INFORMATION *)(GuidHob + 1); + MemTypeInfo[0].Type = EfiReservedMemoryType; + MemTypeInfo[0].NumberOfPages = 5; + MemTypeInfo[1].Type = EfiRuntimeServicesCode; + MemTypeInfo[1].NumberOfPages = 10; + MemTypeInfo[2].Type = EfiRuntimeServicesData; + MemTypeInfo[2].NumberOfPages = 15; + MemTypeInfo[3].Type = EfiACPIReclaimMemory; + MemTypeInfo[3].NumberOfPages = 20; + MemTypeInfo[4].Type = EfiACPIMemoryNVS; + MemTypeInfo[4].NumberOfPages = 25; + MemTypeInfo[5].Type = EfiPalCode; + MemTypeInfo[5].NumberOfPages = 8; + MemTypeInfo[6].Type = EfiMaxMemoryType; + MemTypeInfo[6].NumberOfPages = 0; + + EXPECT_CALL (HobLib, GetFirstGuidHob (&gEfiMemoryTypeInformationGuid)) + .WillOnce (Return ((VOID *)GuidHob)); + + Status = PopulateMemoryTypeInformation (gMemoryTypeInformation); + + ASSERT_EQ (Status, EFI_SUCCESS); + ASSERT_EQ (gMemoryTypeInformation[0].Type, (UINT32)EfiReservedMemoryType); + ASSERT_EQ (gMemoryTypeInformation[0].NumberOfPages, (UINT32)5); + ASSERT_EQ (gMemoryTypeInformation[1].Type, (UINT32)EfiRuntimeServicesCode); + ASSERT_EQ (gMemoryTypeInformation[1].NumberOfPages, (UINT32)10); + ASSERT_EQ (gMemoryTypeInformation[2].Type, (UINT32)EfiRuntimeServicesData); + ASSERT_EQ (gMemoryTypeInformation[2].NumberOfPages, (UINT32)15); + ASSERT_EQ (gMemoryTypeInformation[3].Type, (UINT32)EfiACPIReclaimMemory); + ASSERT_EQ (gMemoryTypeInformation[3].NumberOfPages, (UINT32)20); + ASSERT_EQ (gMemoryTypeInformation[4].Type, (UINT32)EfiACPIMemoryNVS); + ASSERT_EQ (gMemoryTypeInformation[4].NumberOfPages, (UINT32)25); + ASSERT_EQ (gMemoryTypeInformation[5].Type, (UINT32)EfiPalCode); + ASSERT_EQ (gMemoryTypeInformation[5].NumberOfPages, (UINT32)8); +} + +// +// Test: GetMemoryTypeInformationResourceHob returns NOT_FOUND when no resource HOB exists +// +TEST_F (BaseMemoryBinLibTest, GetMemoryTypeInformationResourceHobReturnsNotFoundWhenNoResourceHob) { + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceHob; + UINT8 HobListBuffer[sizeof (EFI_HOB_HANDOFF_INFO_TABLE) + sizeof (EFI_HOB_GENERIC_HEADER)]; + EFI_HOB_HANDOFF_INFO_TABLE *HandoffHob; + EFI_HOB_GENERIC_HEADER *EndOfHobList; + VOID *HobStart; + + ZeroMem (HobListBuffer, sizeof (HobListBuffer)); + + HandoffHob = (EFI_HOB_HANDOFF_INFO_TABLE *)HobListBuffer; + HandoffHob->Header.HobType = EFI_HOB_TYPE_HANDOFF; + HandoffHob->Header.HobLength = sizeof (EFI_HOB_HANDOFF_INFO_TABLE); + + EndOfHobList = (EFI_HOB_GENERIC_HEADER *)(HandoffHob + 1); + EndOfHobList->HobType = EFI_HOB_TYPE_END_OF_HOB_LIST; + EndOfHobList->HobLength = sizeof (EFI_HOB_GENERIC_HEADER); + + HobStart = (VOID *)HobListBuffer; + + ResourceHob = GetMemoryTypeInformationResourceHob (&HobStart, gMemoryTypeInformation); + + ASSERT_EQ (ResourceHob, NULL); +} + +// +// Test: GetMemoryTypeInformationResourceHob finds matching resource HOB +// +TEST_F (BaseMemoryBinLibTest, GetMemoryTypeInformationResourceHobFindsMatchingHob) { + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceHob; + UINT8 HobListBuffer[sizeof (EFI_HOB_HANDOFF_INFO_TABLE) + 3 *sizeof (EFI_HOB_RESOURCE_DESCRIPTOR) + sizeof (EFI_HOB_GENERIC_HEADER)]; + EFI_HOB_HANDOFF_INFO_TABLE *HandoffHob; + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceDescriptor; + EFI_HOB_GENERIC_HEADER *EndOfHobList; + VOID *HobStart; + EFI_GUID OtherGuid = { 0x12345678, 0x1234, 0x1234, { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 } + }; + + ZeroMem (HobListBuffer, sizeof (HobListBuffer)); + + HandoffHob = (EFI_HOB_HANDOFF_INFO_TABLE *)HobListBuffer; + HandoffHob->Header.HobType = EFI_HOB_TYPE_HANDOFF; + HandoffHob->Header.HobLength = sizeof (EFI_HOB_HANDOFF_INFO_TABLE); + + ResourceDescriptor = (EFI_HOB_RESOURCE_DESCRIPTOR *)(HandoffHob + 1); + ResourceDescriptor->Header.HobType = EFI_HOB_TYPE_RESOURCE_DESCRIPTOR; + ResourceDescriptor->Header.HobLength = sizeof (EFI_HOB_RESOURCE_DESCRIPTOR); + ResourceDescriptor->ResourceType = EFI_RESOURCE_SYSTEM_MEMORY; + CopyGuid (&ResourceDescriptor->Owner, &OtherGuid); + ResourceDescriptor->PhysicalStart = 0x100000; + ResourceDescriptor->ResourceLength = 0x50000; + + ResourceDescriptor = (EFI_HOB_RESOURCE_DESCRIPTOR *)(ResourceDescriptor + 1); + ResourceDescriptor->Header.HobType = EFI_HOB_TYPE_RESOURCE_DESCRIPTOR; + ResourceDescriptor->Header.HobLength = sizeof (EFI_HOB_RESOURCE_DESCRIPTOR); + ResourceDescriptor->ResourceType = EFI_RESOURCE_SYSTEM_MEMORY; + CopyGuid (&ResourceDescriptor->Owner, &gEfiMemoryTypeInformationGuid); + ResourceDescriptor->PhysicalStart = 0x200000; + ResourceDescriptor->ResourceLength = 0x75000; + ResourceDescriptor->ResourceAttribute = EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED; + + ResourceDescriptor = (EFI_HOB_RESOURCE_DESCRIPTOR *)(ResourceDescriptor + 1); + ResourceDescriptor->Header.HobType = EFI_HOB_TYPE_RESOURCE_DESCRIPTOR; + ResourceDescriptor->Header.HobLength = sizeof (EFI_HOB_RESOURCE_DESCRIPTOR); + ResourceDescriptor->ResourceType = EFI_RESOURCE_SYSTEM_MEMORY; + CopyGuid (&ResourceDescriptor->Owner, &OtherGuid); + ResourceDescriptor->PhysicalStart = 0x300000; + ResourceDescriptor->ResourceLength = 0x50000; + + EndOfHobList = (EFI_HOB_GENERIC_HEADER *)(ResourceDescriptor + 1); + EndOfHobList->HobType = EFI_HOB_TYPE_END_OF_HOB_LIST; + EndOfHobList->HobLength = sizeof (EFI_HOB_GENERIC_HEADER); + + HobStart = (VOID *)HobListBuffer; + + ResourceHob = GetMemoryTypeInformationResourceHob (&HobStart, gMemoryTypeInformation); + + ASSERT_EQ (ResourceHob->PhysicalStart, (EFI_PHYSICAL_ADDRESS)0x200000); + ASSERT_EQ (ResourceHob->ResourceLength, (UINT64)0x75000); +} + +// +// Test: GetMemoryTypeInformationResourceHob Fails When Multiple Matching HOBs Exist +// +TEST_F (BaseMemoryBinLibTest, GetMemoryTypeInformationResourceHobFailsWithMultipleMatchingHobs) { + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceHob; + UINT8 HobListBuffer[sizeof (EFI_HOB_HANDOFF_INFO_TABLE) + 2 * sizeof (EFI_HOB_RESOURCE_DESCRIPTOR) + sizeof (EFI_HOB_GENERIC_HEADER)]; + EFI_HOB_HANDOFF_INFO_TABLE *HandoffHob; + EFI_HOB_RESOURCE_DESCRIPTOR *FirstMatchingResource; + EFI_HOB_RESOURCE_DESCRIPTOR *SecondMatchingResource; + EFI_HOB_GENERIC_HEADER *EndOfHobList; + VOID *HobStart; + + ZeroMem (HobListBuffer, sizeof (HobListBuffer)); + + HandoffHob = (EFI_HOB_HANDOFF_INFO_TABLE *)HobListBuffer; + HandoffHob->Header.HobType = EFI_HOB_TYPE_HANDOFF; + HandoffHob->Header.HobLength = sizeof (EFI_HOB_HANDOFF_INFO_TABLE); + + // First matching resource HOB + FirstMatchingResource = (EFI_HOB_RESOURCE_DESCRIPTOR *)(HandoffHob + 1); + FirstMatchingResource->Header.HobType = EFI_HOB_TYPE_RESOURCE_DESCRIPTOR; + FirstMatchingResource->Header.HobLength = sizeof (EFI_HOB_RESOURCE_DESCRIPTOR); + FirstMatchingResource->ResourceType = EFI_RESOURCE_SYSTEM_MEMORY; + CopyGuid (&FirstMatchingResource->Owner, &gEfiMemoryTypeInformationGuid); + FirstMatchingResource->PhysicalStart = 0x100000; + FirstMatchingResource->ResourceLength = 0x50000; + FirstMatchingResource->ResourceAttribute = EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED; + + // Second matching resource HOB + SecondMatchingResource = (EFI_HOB_RESOURCE_DESCRIPTOR *)(FirstMatchingResource + 1); + SecondMatchingResource->Header.HobType = EFI_HOB_TYPE_RESOURCE_DESCRIPTOR; + SecondMatchingResource->Header.HobLength = sizeof (EFI_HOB_RESOURCE_DESCRIPTOR); + SecondMatchingResource->ResourceType = EFI_RESOURCE_SYSTEM_MEMORY; + CopyGuid (&SecondMatchingResource->Owner, &gEfiMemoryTypeInformationGuid); + SecondMatchingResource->PhysicalStart = 0x400000; + SecondMatchingResource->ResourceLength = 0x80000; + SecondMatchingResource->ResourceAttribute = EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED; + + EndOfHobList = (EFI_HOB_GENERIC_HEADER *)(SecondMatchingResource + 1); + EndOfHobList->HobType = EFI_HOB_TYPE_END_OF_HOB_LIST; + EndOfHobList->HobLength = sizeof (EFI_HOB_GENERIC_HEADER); + + HobStart = (VOID *)HobListBuffer; + + ResourceHob = GetMemoryTypeInformationResourceHob (&HobStart, gMemoryTypeInformation); + + ASSERT_EQ (ResourceHob, NULL); +} + +int +main ( + int argc, + char *argv[] + ) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTestHost.inf b/MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTestHost.inf new file mode 100644 index 00000000000..4edc3d5a68a --- /dev/null +++ b/MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTestHost.inf @@ -0,0 +1,34 @@ +## MU_CHANGE: PEI Bins - Whole File +## @file +# Unit tests for the Memory Bin feature +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = MemoryBinGoogleTest + FILE_GUID = 8A3C5C7B-2D4E-4F9A-B1C6-9E8F7D6C5B4A + MODULE_TYPE = HOST_APPLICATION + VERSION_STRING = 1.0 + +[Sources] + MemoryBinGoogleTest.cpp + ../MemoryBin.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + +[LibraryClasses] + GoogleTestLib + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + HobLib + +[Guids] + gEfiMemoryTypeInformationGuid diff --git a/MdeModulePkg/Core/Dxe/Mem/Imem.h b/MdeModulePkg/Core/Dxe/Mem/Imem.h index cf66fcb7197..84027d628bc 100644 --- a/MdeModulePkg/Core/Dxe/Mem/Imem.h +++ b/MdeModulePkg/Core/Dxe/Mem/Imem.h @@ -146,25 +146,6 @@ CoreInternalAllocatePages ( IN BOOLEAN NeedGuard ); -// MU_CHANGE START: Add function to get the bucket memory type for a given memory region - -/** - Get the memory type for a given bucket. - - @param PhysicalStart The starting address of the memory region. - @param PhysicalEnd The ending address of the memory region. - - @return The memory type for the bucket that contains the given physical address range. - If the address range does not match any special bucket, it returns EfiMaxMemoryType. -**/ -EFI_MEMORY_TYPE -GetBucketMemoryType ( - IN EFI_PHYSICAL_ADDRESS PhysicalStart, - IN EFI_PHYSICAL_ADDRESS PhysicalEnd - ); - -// MU_CHANGE ENDS - // // Internal Global data // diff --git a/MdeModulePkg/Core/Dxe/Mem/MemoryBin.c b/MdeModulePkg/Core/Dxe/Mem/MemoryBin.c new file mode 100644 index 00000000000..a7a13d7c42d --- /dev/null +++ b/MdeModulePkg/Core/Dxe/Mem/MemoryBin.c @@ -0,0 +1,587 @@ +// MU_CHANGE: PEI Bins - Whole File +/** @file + + Shared logic between cores to work with memory bins for S4 resume stability. This file is duplicated in PEI Core and + DXE Core until a BaseTools feature comes online to support recommended library instances. Any changes to this file + must also be made in MdeModulePkg/Core/Pei/Memory/MemoryBin.c. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#define MEMORY_ATTRIBUTE_MASK (EFI_RESOURCE_ATTRIBUTE_PRESENT | \ + EFI_RESOURCE_ATTRIBUTE_INITIALIZED | \ + EFI_RESOURCE_ATTRIBUTE_TESTED | \ + EFI_RESOURCE_ATTRIBUTE_READ_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_WRITE_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_EXECUTION_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_READ_ONLY_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_16_BIT_IO | \ + EFI_RESOURCE_ATTRIBUTE_32_BIT_IO | \ + EFI_RESOURCE_ATTRIBUTE_64_BIT_IO | \ + EFI_RESOURCE_ATTRIBUTE_PERSISTENT | \ + EFI_RESOURCE_ATTRIBUTE_SPECIAL_PURPOSE ) + +#define TESTED_MEMORY_ATTRIBUTES (EFI_RESOURCE_ATTRIBUTE_PRESENT | \ + EFI_RESOURCE_ATTRIBUTE_INITIALIZED | \ + EFI_RESOURCE_ATTRIBUTE_TESTED ) + +/** + Calculate total memory bin size needed. + + @param BinTop The top address of the memory bins. This is an optional parameter. + If non-zero, alignment requirements will be considered in the calculation. + @param MemoryTypeInformation The memory type information array. + + @return The total memory bin size needed. + +**/ +UINT64 +CalculateTotalMemoryBinSizeNeeded ( + IN UINTN BinTop, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + UINTN Index; + UINT64 TotalSize; + UINTN Granularity; + + if (MemoryTypeInformation == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return 0; + } + + // + // Loop through each memory type in the order specified by the MemoryTypeInformation[] array + // + TotalSize = 0; + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + if ((MemoryTypeInformation[Index].Type == EfiReservedMemoryType) || + (MemoryTypeInformation[Index].Type == EfiACPIMemoryNVS) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesCode) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesData)) + { + Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } + + // MemoryTypeInformation[Index].NumberOfPages is already aligned to the allocation granularity + TotalSize += LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + + // BinTop is optional + if (BinTop == 0) { + continue; + } + + BinTop -= (UINTN)LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + TotalSize += (BinTop & (Granularity - 1)); + BinTop &= ~(Granularity - 1); + } + + return TotalSize; +} + +/** + Get the Memory Type Information HOB if it exists and populate gMemoryTypeInformation. + + @param MemoryTypeInformation The pointer to the memory type information array to be populated. + + @return EFI_STATUS On EFI_SUCCESS, gMemoryTypeInformation points to the + Memory Type Information. + @return EFI_NOT_FOUND No valid Memory Type Information HOB found. +**/ +EFI_STATUS +EFIAPI +PopulateMemoryTypeInformation ( + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + UINTN DataSize; + EFI_MEMORY_TYPE_INFORMATION *EfiMemoryTypeInformation; + EFI_HOB_GUID_TYPE *GuidHob; + UINTN Index; + UINT32 Granularity; + + if (MemoryTypeInformation == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + GuidHob = GetFirstGuidHob (&gEfiMemoryTypeInformationGuid); + if (GuidHob != NULL) { + EfiMemoryTypeInformation = GET_GUID_HOB_DATA (GuidHob); + DataSize = GET_GUID_HOB_DATA_SIZE (GuidHob); + if ((EfiMemoryTypeInformation != NULL) && (DataSize > 0) && (DataSize <= (EfiMaxMemoryType + 1) * sizeof (EFI_MEMORY_TYPE_INFORMATION))) { + CopyMem (MemoryTypeInformation, EfiMemoryTypeInformation, DataSize); + + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the MemoryTypeInformation[] array is valid + // + if (MemoryTypeInformation[Index].Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + if ((MemoryTypeInformation[Index].Type == EfiReservedMemoryType) || + (MemoryTypeInformation[Index].Type == EfiACPIMemoryNVS) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesCode) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesData)) + { + Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } else { + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + } + + // Align the number of pages to the allocation granularity + MemoryTypeInformation[Index].NumberOfPages = (UINT32)RShiftU64 (ALIGN_VALUE (LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT), Granularity), EFI_PAGE_SHIFT); + } + } + + return EFI_SUCCESS; + } + + DEBUG ((DEBUG_ERROR, "%a: Invalid Memory Type Information HOB data\n", __func__)); + ASSERT (FALSE); + } + + DEBUG ((DEBUG_ERROR, "%a: No Memory Type Information HOB found\n", __func__)); + + return EFI_NOT_FOUND; +} + +/** + Look for Resource Descriptor HOB with a ResourceType of System Memory + and an Owner GUID of gEfiMemoryTypeInformationGuid. If more than 1 is + found, then return NULL. + + @param HobStart Pointer to the start of the HOB list. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + + @return Non-NULL The pointer to the singular MemoryTypeInformation Resource Descriptor HOB. + @return NULL No valid MemoryTypeInformation Resource Descriptor HOB found. +**/ +EFI_HOB_RESOURCE_DESCRIPTOR * +EFIAPI +GetMemoryTypeInformationResourceHob ( + IN VOID **HobStart, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + UINTN Count; + EFI_PEI_HOB_POINTERS Hob; + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceHob; + EFI_HOB_RESOURCE_DESCRIPTOR *MemoryTypeInformationResourceHob; + + if ((HobStart == NULL) || (MemoryTypeInformation == NULL)) { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return NULL; + } + + // + // See if a Memory Type Information HOB is available + // + MemoryTypeInformationResourceHob = NULL; + Count = 0; + for (Hob.Raw = *HobStart; !END_OF_HOB_LIST (Hob); Hob.Raw = GET_NEXT_HOB (Hob)) { + if (GET_HOB_TYPE (Hob) != EFI_HOB_TYPE_RESOURCE_DESCRIPTOR) { + continue; + } + + ResourceHob = Hob.ResourceDescriptor; + if (!CompareGuid (&ResourceHob->Owner, &gEfiMemoryTypeInformationGuid)) { + continue; + } + + Count++; + if (ResourceHob->ResourceType != EFI_RESOURCE_SYSTEM_MEMORY) { + continue; + } + + if ((ResourceHob->ResourceAttribute & MEMORY_ATTRIBUTE_MASK) != TESTED_MEMORY_ATTRIBUTES) { + continue; + } + + if (ResourceHob->ResourceLength >= CalculateTotalMemoryBinSizeNeeded ((UINTN)(ResourceHob->PhysicalStart + ResourceHob->ResourceLength), MemoryTypeInformation)) { + MemoryTypeInformationResourceHob = ResourceHob; + } + } + + if (Count > 1) { + return NULL; + } + + return MemoryTypeInformationResourceHob; +} + +/** + Helper function to set up the bin statistics with the provided bin range + + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeStatistics The memory type statistics to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. +**/ +STATIC +VOID +InitializeBinStatisticsFromRange ( + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_PHYSICAL_ADDRESS *DefaultMaximumAddress + ) +{ + EFI_MEMORY_TYPE Type; + UINTN Index; + + if ((MemoryTypeInformation == NULL) || (MemoryTypeStatistics == NULL) || (DefaultMaximumAddress == NULL)) { + ASSERT (FALSE); + return; + } + + // + // If the number of pages reserved for a memory type is 0, then all + // allocations for that type should be in the default range. + // + for (Type = (EFI_MEMORY_TYPE)0; Type < EfiMaxMemoryType; Type++) { + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + if (Type == (EFI_MEMORY_TYPE)MemoryTypeInformation[Index].Type) { + MemoryTypeStatistics[Type].InformationIndex = Index; + } + } + + MemoryTypeStatistics[Type].CurrentNumberOfPages = 0; + if (MemoryTypeStatistics[Type].MaximumAddress == MAX_ALLOC_ADDRESS) { + MemoryTypeStatistics[Type].MaximumAddress = *DefaultMaximumAddress; + MemoryTypeStatistics[Type].DefaultBin = TRUE; + } + } +} + +/** + Sets the preferred memory range to use for the Memory Type Information bins. + This service must be called before fist call to CoreAddMemoryDescriptor(). + + If the location of the Memory Type Information bins has already been + established or the size of the range provides is smaller than all the + Memory Type Information bins, then the range provides is not used. + + @param Start The start address of the Memory Type Information range. + @param Length The size, in bytes, of the Memory Type Information range. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeStatistics The memory type statistics array to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. +**/ +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 + ) +{ + EFI_PHYSICAL_ADDRESS Top; + EFI_MEMORY_TYPE Type; + UINTN Index; + UINT64 Size; + + if ((MemoryTypeInformation == NULL) || + (MemoryTypeInformationInitialized == NULL) || + (MemoryTypeStatistics == NULL) || + (DefaultMaximumAddress == NULL)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return; + } + + // + // Return if Memory Type Information bin locations have already been set + // + if (*MemoryTypeInformationInitialized) { + DEBUG ((DEBUG_ERROR, "%a: Ignored. Bins already set.\n", __func__)); + return; + } + + // + // Return if size of the Memory Type Information bins is greater than Length + // + Size = CalculateTotalMemoryBinSizeNeeded ((UINTN)(Start + Length), MemoryTypeInformation); + + if (Size > Length) { + return; + } + + // + // Loop through each memory type in the order specified by the + // gMemoryTypeInformation[] array + // + Top = Start + Length; + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the MemoryTypeInformation[] array is valid + // + Type = (EFI_MEMORY_TYPE)(MemoryTypeInformation[Index].Type); + if ((UINT32)Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + MemoryTypeStatistics[Type].MaximumAddress = Top - 1; + Top -= LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + MemoryTypeStatistics[Type].BaseAddress = Top; + + // + // If the current base address is the lowest address so far, then update + // the default maximum address + // + if (MemoryTypeStatistics[Type].BaseAddress < *DefaultMaximumAddress) { + *DefaultMaximumAddress = MemoryTypeStatistics[Type].BaseAddress - 1; + } + + MemoryTypeStatistics[Type].NumberOfPages = MemoryTypeInformation[Index].NumberOfPages; + MemoryTypeInformation[Index].NumberOfPages = 0; + } + } + + InitializeBinStatisticsFromRange (MemoryTypeInformation, MemoryTypeStatistics, DefaultMaximumAddress); + + *MemoryTypeInformationInitialized = TRUE; +} + +/** + Allocate memory bins for each memory type as specified in gMemoryTypeInformation. + + If all the memory types cannot be allocated, then all previously allocated + memory types are freed and the function returns. If this function fails, it will log and expect to be called + again when more memory is added to the system. + + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeStatistics The memory type statistics array to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. + @param CreateHob TRUE to create Memory Type Information Resource HOB after successful + allocation. This is used for PEI Core to report the bins to DXE Core. + FALSE if HOB creation is not needed. +**/ +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 + ) +{ + UINTN Index; + EFI_MEMORY_TYPE Type; + EFI_PHYSICAL_ADDRESS BaseAddress; + EFI_PHYSICAL_ADDRESS LastBinAddress; + UINT64 RequiredSize; + + if ((MemoryTypeInformationInitialized == NULL) || + (MemoryTypeInformation == NULL) || + (MemoryTypeStatistics == NULL) || + (DefaultMaximumAddress == NULL)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return; + } + + // + // Check to see if the statistics for the different memory types have already been established + // + if (*MemoryTypeInformationInitialized) { + return; + } + + BaseAddress = 0; + RequiredSize = CalculateTotalMemoryBinSizeNeeded (0, MemoryTypeInformation); + if (RequiredSize == 0) { + *MemoryTypeInformationInitialized = TRUE; + return; + } + + DEBUG ((DEBUG_INFO, "%a: Attempting to allocate 0x%llx bytes for all memory bins\n", __func__, RequiredSize)); + + // To ensure we get a contiguous range of memory for our bins, we will attempt to allocate + // all of the memory needed in one go. If that works, we can then carve it up into the individual bins. We allocate + // reserved pages to ensure runtime page allocation granularity is taken into account. + BaseAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)AllocateReservedPages ( + (UINTN)RShiftU64 (RequiredSize, EFI_PAGE_SHIFT) + ); + + if (BaseAddress == 0) { + DEBUG ((DEBUG_ERROR, "%a: Could not allocate contiguous pages for all memory bins\n", __func__)); + ASSERT (FALSE); + return; + } + + LastBinAddress = BaseAddress + RequiredSize; + *DefaultMaximumAddress = BaseAddress - 1; + + // + // Loop through each memory type in the order specified by the gMemoryTypeInformation[] array + // + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the gMemoryTypeInformation[] array is valid + // + Type = (EFI_MEMORY_TYPE)(MemoryTypeInformation[Index].Type); + if ((UINT32)Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + MemoryTypeStatistics[Type].BaseAddress = LastBinAddress - LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + MemoryTypeStatistics[Type].MaximumAddress = LastBinAddress - 1; + LastBinAddress = MemoryTypeStatistics[Type].BaseAddress; + } + } + + // + // There was enough system memory for all the the memory types were allocated. So, + // those memory areas can be freed for future allocations, and all future memory + // allocations can occur within their respective bins + // + FreePages ( + (VOID *)(UINTN)BaseAddress, + (UINTN)RShiftU64 (RequiredSize, EFI_PAGE_SHIFT) + ); + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the MemoryTypeInformation[] array is valid + // + Type = (EFI_MEMORY_TYPE)(MemoryTypeInformation[Index].Type); + if ((UINT32)Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + MemoryTypeStatistics[Type].NumberOfPages = MemoryTypeInformation[Index].NumberOfPages; + MemoryTypeInformation[Index].NumberOfPages = 0; + } + } + + InitializeBinStatisticsFromRange (MemoryTypeInformation, MemoryTypeStatistics, DefaultMaximumAddress); + + if (CreateHob) { + // + // Create a Resource Descriptor HOB to report the Memory Type Information bins to DXE Core + // + BuildResourceDescriptorWithOwnerHob ( + EFI_RESOURCE_SYSTEM_MEMORY, + TESTED_MEMORY_ATTRIBUTES, + BaseAddress, + RequiredSize, + &gEfiMemoryTypeInformationGuid + ); + } + + *MemoryTypeInformationInitialized = TRUE; +} + +/** + Update memory type statistics upon memory allocation and free. + + @param OldType The original memory type of the memory region. + @param NewType The new memory type of the memory region. + @param Start The starting physical address of the memory region. + @param NumberOfPages The number of pages in the memory region. + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeStatistics The memory type statistics array to be updated. + @param MemoryTypeInformation The memory type information array to be updated. + @param DefaultBaseAddress Default bin base address. + @param DefaultMaximumAddress Default bin maximum address. +**/ +VOID +EFIAPI +UpdateMemoryStatistics ( + IN EFI_MEMORY_TYPE OldType, + IN EFI_MEMORY_TYPE NewType, + IN EFI_PHYSICAL_ADDRESS Start, + IN UINTN NumberOfPages, + IN BOOLEAN *MemoryTypeInformationInitialized, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN EFI_PHYSICAL_ADDRESS DefaultBaseAddress, + IN EFI_PHYSICAL_ADDRESS DefaultMaximumAddress + ) +{ + if ((MemoryTypeInformationInitialized == NULL) || + (MemoryTypeStatistics == NULL) || + (MemoryTypeInformation == NULL)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return; + } + + if (!*MemoryTypeInformationInitialized) { + return; + } + + // + // Update counters for the number of pages allocated to each memory type + // + if ((UINT32)OldType < EfiMaxMemoryType) { + if (((Start >= MemoryTypeStatistics[OldType].BaseAddress) && (Start <= MemoryTypeStatistics[OldType].MaximumAddress)) || + ((Start >= DefaultBaseAddress) && (Start <= DefaultMaximumAddress))) + { + if (NumberOfPages > MemoryTypeStatistics[OldType].CurrentNumberOfPages) { + MemoryTypeStatistics[OldType].CurrentNumberOfPages = 0; + } else { + MemoryTypeStatistics[OldType].CurrentNumberOfPages -= NumberOfPages; + } + } + } + + if ((UINT32)NewType < EfiMaxMemoryType) { + if (((Start >= MemoryTypeStatistics[NewType].BaseAddress) && (Start <= MemoryTypeStatistics[NewType].MaximumAddress)) || + ((Start >= DefaultBaseAddress) && (Start <= DefaultMaximumAddress))) + { + MemoryTypeStatistics[NewType].CurrentNumberOfPages += NumberOfPages; + if ((MemoryTypeStatistics[NewType].InformationIndex < (UINTN)EfiMaxMemoryType) && + (MemoryTypeStatistics[NewType].CurrentNumberOfPages > MemoryTypeInformation[MemoryTypeStatistics[NewType].InformationIndex].NumberOfPages)) + { + MemoryTypeInformation[MemoryTypeStatistics[NewType].InformationIndex].NumberOfPages = (UINT32)MemoryTypeStatistics[NewType].CurrentNumberOfPages; + } + } + } +} diff --git a/MdeModulePkg/Core/Dxe/Mem/Page.c b/MdeModulePkg/Core/Dxe/Mem/Page.c index cba06b3bd27..446c568e7e0 100644 --- a/MdeModulePkg/Core/Dxe/Mem/Page.c +++ b/MdeModulePkg/Core/Dxe/Mem/Page.c @@ -10,19 +10,21 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include "Imem.h" #include "HeapGuard.h" #include - -// -// Entry for tracking the memory regions for each memory type to coalesce similar memory types -// -typedef struct { - EFI_PHYSICAL_ADDRESS BaseAddress; - EFI_PHYSICAL_ADDRESS MaximumAddress; - UINT64 CurrentNumberOfPages; - UINT64 NumberOfPages; - UINTN InformationIndex; - BOOLEAN Special; - BOOLEAN Runtime; -} EFI_MEMORY_TYPE_STATISTICS; +// MU_CHANGE BEGIN: PEI Bins - Replaced EFI_MEMORY_TYPE_STATISTICS struct with shared header +// // +// // Entry for tracking the memory regions for each memory type to coalesce similar memory types +// // +// typedef struct { +// EFI_PHYSICAL_ADDRESS BaseAddress; +// EFI_PHYSICAL_ADDRESS MaximumAddress; +// UINT64 CurrentNumberOfPages; +// UINT64 NumberOfPages; +// UINTN InformationIndex; +// BOOLEAN Special; +// BOOLEAN Runtime; +// } EFI_MEMORY_TYPE_STATISTICS; +#include +// MU_CHANGE END: PEI Bins // // MemoryMap - The current memory map @@ -46,25 +48,27 @@ UINTN mFreeMapStack = 0; LIST_ENTRY mFreeMemoryMapEntryList = INITIALIZE_LIST_HEAD_VARIABLE (mFreeMemoryMapEntryList); BOOLEAN mMemoryTypeInformationInitialized = FALSE; +// MU_CHANGE BEGIN: PEI Bins - Added DefaultBin field to struct initializers EFI_MEMORY_TYPE_STATISTICS mMemoryTypeStatistics[EfiMaxMemoryType + 1] = { - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE }, // EfiReservedMemoryType - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiLoaderCode - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiLoaderData - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiBootServicesCode - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiBootServicesData - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE }, // EfiRuntimeServicesCode - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE }, // EfiRuntimeServicesData - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiConventionalMemory - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiUnusableMemory - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE }, // EfiACPIReclaimMemory - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE }, // EfiACPIMemoryNVS - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiMemoryMappedIO - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiMemoryMappedIOPortSpace - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE }, // EfiPalCode - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE }, // EfiPersistentMemory - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE }, // EfiUnacceptedMemoryType - { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE } // EfiMaxMemoryType + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiReservedMemoryType + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiLoaderCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiLoaderData + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiBootServicesCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiBootServicesData + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE, FALSE }, // EfiRuntimeServicesCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE, FALSE }, // EfiRuntimeServicesData + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiConventionalMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiUnusableMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiACPIReclaimMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiACPIMemoryNVS + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiMemoryMappedIO + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiMemoryMappedIOPortSpace + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, TRUE, FALSE }, // EfiPalCode + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE }, // EfiPersistentMemory + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, TRUE, FALSE, FALSE }, // EfiUnacceptedMemoryType + { 0, MAX_ALLOC_ADDRESS, 0, 0, EfiMaxMemoryType, FALSE, FALSE, FALSE } // EfiMaxMemoryType }; +// MU_CHANGE END: PEI Bins EFI_PHYSICAL_ADDRESS mDefaultMaximumAddress = MAX_ALLOC_ADDRESS; EFI_PHYSICAL_ADDRESS mDefaultBaseAddress = MAX_ALLOC_ADDRESS; @@ -95,20 +99,6 @@ EFI_MEMORY_TYPE_INFORMATION gMemoryTypeInformation[EfiMaxMemoryType + 1] = { // GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN gLoadFixedAddressCodeMemoryReady = FALSE; -// MU_CHANGE START: Add function prototype to be used in CoreAddRange - -/** - Internal function. Moves any memory descriptors that are on the - temporary descriptor stack to heap. - -**/ -VOID -CoreFreeMemoryMapStack ( - VOID - ); - -// MU_CHANGE END - /** Enter critical section by gaining lock on gMemoryLock. @@ -155,102 +145,6 @@ RemoveMemoryMapEntry ( } } -// MU_CHANGE START: Add function to get the bucket memory type for a given memory region - -/** - Helper function to evaluate if memory regions intersect. - - @param Start1 The address of the first byte in the first memory region. - @param End1 The address of the last byte in the first memory region. - @param Start2 The address of the first byte in the second memory region. - @param End2 The address of the last byte in the second memory region. - - @return TRUE if the memory regions intersect, FALSE otherwise. -**/ -STATIC -BOOLEAN -MemoryRegionsIntersect ( - IN EFI_PHYSICAL_ADDRESS Start1, - IN EFI_PHYSICAL_ADDRESS End1, - IN EFI_PHYSICAL_ADDRESS Start2, - IN EFI_PHYSICAL_ADDRESS End2 - ) -{ - return (((Start1 <= End2) && (Start2 <= Start1)) || - ((Start2 <= End1) && (Start1 <= Start2))); -} - -/** - Get the memory type for a given bucket. - - @param PhysicalStart The starting address of the memory region. - @param PhysicalEnd The ending address of the memory region. - - @return The memory type for the bucket that contains the given physical address range. - If the address range does not match any special bucket, it returns EfiMaxMemoryType. -**/ -EFI_MEMORY_TYPE -GetBucketMemoryType ( - IN EFI_PHYSICAL_ADDRESS PhysicalStart, - IN EFI_PHYSICAL_ADDRESS PhysicalEnd - ) -{ - EFI_MEMORY_TYPE BucketType; - - // Find the bucket type for the incoming memory region. - for (BucketType = (EFI_MEMORY_TYPE)0; BucketType < EfiMaxMemoryType; BucketType++) { - // - // If the number of pages for this memory type is not zero, the input region - // better be within the same bucket. We only care about the special memory type - // here because we need these buckets to remain consistent so that the OS resume - // logic can work properly. The same applies to the memory allocation logic. - // - if (mMemoryTypeStatistics[BucketType].Special && (mMemoryTypeStatistics[BucketType].NumberOfPages != 0)) { - if ((PhysicalStart >= mMemoryTypeStatistics[BucketType].BaseAddress) && - (PhysicalEnd <= mMemoryTypeStatistics[BucketType].MaximumAddress)) - { - break; - } else if (MemoryRegionsIntersect ( - PhysicalStart, - PhysicalEnd, - mMemoryTypeStatistics[BucketType].BaseAddress, - mMemoryTypeStatistics[BucketType].MaximumAddress - )) - { - // The start and end overlap the bucket, but not fully inclusive. We should not allow this. - DEBUG (( - DEBUG_ERROR, - "%a: %lx-%lx intersects bucket type %d (%lx-%lx)\n", - __func__, - PhysicalStart, - PhysicalEnd, - BucketType, - mMemoryTypeStatistics[BucketType].BaseAddress, - mMemoryTypeStatistics[BucketType].MaximumAddress - )); - - ASSERT (FALSE); - } - } - } - - // If we can find the bucket type, use it to guide the merging logic below. - // Otherwise, we will not care about the bucket type. - if (BucketType >= EfiMaxMemoryType) { - DEBUG (( - DEBUG_PAGE, - "%a: defaulting to max for %lx -%lx\n", - __func__, - PhysicalStart, - PhysicalEnd - )); - } - - return BucketType; -} - -// MU_CHANGE ENDS - /** Internal function. Adds a ranges to the memory map. The range must not already exist in the map. @@ -274,79 +168,11 @@ CoreAddRange ( LIST_ENTRY *Link; MEMORY_MAP *Entry; - // MU_CHANGE STARTS: Add check to merge memory regions of the bucket type - EFI_MEMORY_TYPE BucketType; - EFI_MEMORY_TYPE MergeType; - BOOLEAN Break; - - // MU_CHANGE ENDs - ASSERT ((Start & EFI_PAGE_MASK) == 0); ASSERT (End > Start); ASSERT_LOCKED (&gMemoryLock); - // MU_CHANGE STARTS: Add check to merge memory regions of the bucket type - // Find the bucket type for the incoming memory region. - Break = FALSE; - for (BucketType = (EFI_MEMORY_TYPE)0; BucketType < EfiMaxMemoryType; BucketType++) { - // - // If the number of pages for this memory type is not zero, the input region better - // be within the same bucket. Otherwise, we will handle the ones we care about, - // the special memory types, in chunks. - // - if (mMemoryTypeStatistics[BucketType].Special && (mMemoryTypeStatistics[BucketType].NumberOfPages != 0)) { - if ((Start <= mMemoryTypeStatistics[BucketType].MaximumAddress) && - (End > mMemoryTypeStatistics[BucketType].MaximumAddress)) - { - // - // The start overlaps the bucket, so we let self-recursion handle the tail, and we - // handle the head. - // - // |----------|---Special Memory Bucket---|----------| - // |--------------^---------------------------^------| - // |------------Start------------------------End-----| - // - CoreAddRange ( - Type, - mMemoryTypeStatistics[BucketType].MaximumAddress + 1, - End, - Attribute - ); - CoreFreeMemoryMapStack (); - End = mMemoryTypeStatistics[BucketType].MaximumAddress; - Break = TRUE; - } - - if ((Start < mMemoryTypeStatistics[BucketType].BaseAddress) && - (End >= mMemoryTypeStatistics[BucketType].BaseAddress)) - { - // The end overlaps the bucket, so we let self-recursion handle the head, and we - // handle the tail. - // - // |----------|---Special Memory Bucket---|----------| - // |------^-------------------^----------------------| - // |----Start----------------End---------------------| - // - CoreAddRange ( - Type, - Start, - mMemoryTypeStatistics[BucketType].BaseAddress - 1, - Attribute - ); - CoreFreeMemoryMapStack (); - Start = mMemoryTypeStatistics[BucketType].BaseAddress; - Break = TRUE; - } - - if (Break) { - break; - } - } - } - - // MU_CHANGE ENDS - DEBUG ((DEBUG_PAGE, "AddRange: %lx-%lx to %d\n", Start, End, Type)); // @@ -391,8 +217,7 @@ CoreAddRange ( // and the same Attribute // - MergeType = GetBucketMemoryType (Start, End); // MU_CHANGE: Add check to merge memory regions of the bucket type - Link = gMemoryMap.ForwardLink; + Link = gMemoryMap.ForwardLink; while (Link != &gMemoryMap) { Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); Link = Link->ForwardLink; @@ -405,14 +230,6 @@ CoreAddRange ( continue; } - // MU_CHANGE STARTS: Add check to merge memory regions of the bucket type - // We need to make sure we can only merge with the same type as the merge type - if (MergeType != GetBucketMemoryType (Entry->Start, Entry->End)) { - continue; - } - - // MU_CHANGE ENDS - if (Entry->End + 1 == Start) { Start = Entry->Start; RemoveMemoryMapEntry (Entry); @@ -727,107 +544,109 @@ CoreLoadingFixedAddressHook ( return; } -/** - Sets the preferred memory range to use for the Memory Type Information bins. - This service must be called before fist call to CoreAddMemoryDescriptor(). - - If the location of the Memory Type Information bins has already been - established or the size of the range provides is smaller than all the - Memory Type Information bins, then the range provides is not used. - - @param Start The start address of the Memory Type Information range. - @param Length The size, in bytes, of the Memory Type Information range. -**/ -VOID -CoreSetMemoryTypeInformationRange ( - IN EFI_PHYSICAL_ADDRESS Start, - IN UINT64 Length - ) -{ - EFI_PHYSICAL_ADDRESS Top; - EFI_MEMORY_TYPE Type; - UINTN Index; - UINTN Size; - - // - // Return if Memory Type Information bin locations have already been set - // - if (mMemoryTypeInformationInitialized) { - DEBUG ((DEBUG_ERROR, "%a: Ignored. Bins already set.\n", __func__)); - return; - } - - // - // Return if size of the Memory Type Information bins is greater than Length - // - Size = 0; - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - // - // Make sure the memory type in the gMemoryTypeInformation[] array is valid - // - Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); - if ((UINT32)Type > EfiMaxMemoryType) { - continue; - } - - Size += EFI_PAGES_TO_SIZE (gMemoryTypeInformation[Index].NumberOfPages); - } - - if (Size > Length) { - return; - } - - // - // Loop through each memory type in the order specified by the - // gMemoryTypeInformation[] array - // - Top = Start + Length; - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - // - // Make sure the memory type in the gMemoryTypeInformation[] array is valid - // - Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); - if ((UINT32)Type > EfiMaxMemoryType) { - continue; - } - - if (gMemoryTypeInformation[Index].NumberOfPages != 0) { - mMemoryTypeStatistics[Type].MaximumAddress = Top - 1; - Top -= EFI_PAGES_TO_SIZE (gMemoryTypeInformation[Index].NumberOfPages); - mMemoryTypeStatistics[Type].BaseAddress = Top; - - // - // If the current base address is the lowest address so far, then update - // the default maximum address - // - if (mMemoryTypeStatistics[Type].BaseAddress < mDefaultMaximumAddress) { - mDefaultMaximumAddress = mMemoryTypeStatistics[Type].BaseAddress - 1; - } - - mMemoryTypeStatistics[Type].NumberOfPages = gMemoryTypeInformation[Index].NumberOfPages; - gMemoryTypeInformation[Index].NumberOfPages = 0; - } - } - - // - // If the number of pages reserved for a memory type is 0, then all - // allocations for that type should be in the default range. - // - for (Type = (EFI_MEMORY_TYPE)0; Type < EfiMaxMemoryType; Type++) { - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - if (Type == (EFI_MEMORY_TYPE)gMemoryTypeInformation[Index].Type) { - mMemoryTypeStatistics[Type].InformationIndex = Index; - } - } - - mMemoryTypeStatistics[Type].CurrentNumberOfPages = 0; - if (mMemoryTypeStatistics[Type].MaximumAddress == MAX_ALLOC_ADDRESS) { - mMemoryTypeStatistics[Type].MaximumAddress = mDefaultMaximumAddress; - } - } - - mMemoryTypeInformationInitialized = TRUE; -} +// MU_CHANGE BEGIN: PEI Bins - Moved CoreSetMemoryTypeInformationRange to MemoryBin.c +// /** +// Sets the preferred memory range to use for the Memory Type Information bins. +// This service must be called before fist call to CoreAddMemoryDescriptor(). +// +// If the location of the Memory Type Information bins has already been +// established or the size of the range provides is smaller than all the +// Memory Type Information bins, then the range provides is not used. +// +// @param Start The start address of the Memory Type Information range. +// @param Length The size, in bytes, of the Memory Type Information range. +// **/ +// VOID +// CoreSetMemoryTypeInformationRange ( +// IN EFI_PHYSICAL_ADDRESS Start, +// IN UINT64 Length +// ) +// { +// EFI_PHYSICAL_ADDRESS Top; +// EFI_MEMORY_TYPE Type; +// UINTN Index; +// UINTN Size; +// +// // +// // Return if Memory Type Information bin locations have already been set +// // +// if (mMemoryTypeInformationInitialized) { +// DEBUG ((DEBUG_ERROR, "%a: Ignored. Bins already set.\n", __func__)); +// return; +// } +// +// // +// // Return if size of the Memory Type Information bins is greater than Length +// // +// Size = 0; +// for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { +// // +// // Make sure the memory type in the gMemoryTypeInformation[] array is valid +// // +// Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); +// if ((UINT32)Type > EfiMaxMemoryType) { +// continue; +// } +// +// Size += EFI_PAGES_TO_SIZE (gMemoryTypeInformation[Index].NumberOfPages); +// } +// +// if (Size > Length) { +// return; +// } +// +// // +// // Loop through each memory type in the order specified by the +// // gMemoryTypeInformation[] array +// // +// Top = Start + Length; +// for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { +// // +// // Make sure the memory type in the gMemoryTypeInformation[] array is valid +// // +// Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); +// if ((UINT32)Type > EfiMaxMemoryType) { +// continue; +// } +// +// if (gMemoryTypeInformation[Index].NumberOfPages != 0) { +// mMemoryTypeStatistics[Type].MaximumAddress = Top - 1; +// Top -= EFI_PAGES_TO_SIZE (gMemoryTypeInformation[Index].NumberOfPages); +// mMemoryTypeStatistics[Type].BaseAddress = Top; +// +// // +// // If the current base address is the lowest address so far, then update +// // the default maximum address +// // +// if (mMemoryTypeStatistics[Type].BaseAddress < mDefaultMaximumAddress) { +// mDefaultMaximumAddress = mMemoryTypeStatistics[Type].BaseAddress - 1; +// } +// +// mMemoryTypeStatistics[Type].NumberOfPages = gMemoryTypeInformation[Index].NumberOfPages; +// gMemoryTypeInformation[Index].NumberOfPages = 0; +// } +// } +// +// // +// // If the number of pages reserved for a memory type is 0, then all +// // allocations for that type should be in the default range. +// // +// for (Type = (EFI_MEMORY_TYPE)0; Type < EfiMaxMemoryType; Type++) { +// for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { +// if (Type == (EFI_MEMORY_TYPE)gMemoryTypeInformation[Index].Type) { +// mMemoryTypeStatistics[Type].InformationIndex = Index; +// } +// } +// +// mMemoryTypeStatistics[Type].CurrentNumberOfPages = 0; +// if (mMemoryTypeStatistics[Type].MaximumAddress == MAX_ALLOC_ADDRESS) { +// mMemoryTypeStatistics[Type].MaximumAddress = mDefaultMaximumAddress; +// } +// } +// +// mMemoryTypeInformationInitialized = TRUE; +// } +// MU_CHANGE END: PEI Bins /** Called to initialize the memory map and add descriptors to @@ -853,9 +672,11 @@ CoreAddMemoryDescriptor ( ) { EFI_PHYSICAL_ADDRESS End; - EFI_STATUS Status; - UINTN Index; - UINTN FreeIndex; + // MU_CHANGE BEGIN: PEI Bins + // EFI_STATUS Status; + // UINTN Index; + // UINTN FreeIndex; + // MU_CHANGE END: PEI Bins if ((Start & EFI_PAGE_MASK) != 0) { return; @@ -885,128 +706,133 @@ CoreAddMemoryDescriptor ( CoreLoadingFixedAddressHook (); } - // - // Check to see if the statistics for the different memory types have already been established - // - if (mMemoryTypeInformationInitialized) { - return; - } - - // - // Loop through each memory type in the order specified by the gMemoryTypeInformation[] array - // - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - // - // Make sure the memory type in the gMemoryTypeInformation[] array is valid - // - Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); - if ((UINT32)Type > EfiMaxMemoryType) { - continue; - } - - if (gMemoryTypeInformation[Index].NumberOfPages != 0) { - // - // Allocate pages for the current memory type from the top of available memory - // - Status = CoreAllocatePages ( - AllocateAnyPages, - Type, - gMemoryTypeInformation[Index].NumberOfPages, - &mMemoryTypeStatistics[Type].BaseAddress - ); - if (EFI_ERROR (Status)) { - // - // If an error occurs allocating the pages for the current memory type, then - // free all the pages allocates for the previous memory types and return. This - // operation with be retied when/if more memory is added to the system - // - for (FreeIndex = 0; FreeIndex < Index; FreeIndex++) { - // - // Make sure the memory type in the gMemoryTypeInformation[] array is valid - // - Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[FreeIndex].Type); - if ((UINT32)Type > EfiMaxMemoryType) { - continue; - } - - if (gMemoryTypeInformation[FreeIndex].NumberOfPages != 0) { - CoreFreePages ( - mMemoryTypeStatistics[Type].BaseAddress, - gMemoryTypeInformation[FreeIndex].NumberOfPages - ); - mMemoryTypeStatistics[Type].BaseAddress = 0; - mMemoryTypeStatistics[Type].MaximumAddress = MAX_ALLOC_ADDRESS; - } - } - - return; - } - - // - // Compute the address at the top of the current statistics - // - mMemoryTypeStatistics[Type].MaximumAddress = - mMemoryTypeStatistics[Type].BaseAddress + - LShiftU64 (gMemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT) - 1; - - // - // If the current base address is the lowest address so far, then update the default - // maximum address - // - if (mMemoryTypeStatistics[Type].BaseAddress < mDefaultMaximumAddress) { - mDefaultMaximumAddress = mMemoryTypeStatistics[Type].BaseAddress - 1; - } - } - } - - // - // There was enough system memory for all the the memory types were allocated. So, - // those memory areas can be freed for future allocations, and all future memory - // allocations can occur within their respective bins - // - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - // - // Make sure the memory type in the gMemoryTypeInformation[] array is valid - // - Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); - if ((UINT32)Type > EfiMaxMemoryType) { - continue; - } - - if (gMemoryTypeInformation[Index].NumberOfPages != 0) { - // MU_CHANGE Starts - // Activate the statistics so that the free page operation can be performed - // with valid bucket information. - mMemoryTypeStatistics[Type].NumberOfPages = gMemoryTypeInformation[Index].NumberOfPages; - gMemoryTypeInformation[Index].NumberOfPages = 0; - CoreFreePages ( - mMemoryTypeStatistics[Type].BaseAddress, - (UINTN)mMemoryTypeStatistics[Type].NumberOfPages - ); - // mMemoryTypeStatistics[Type].NumberOfPages = gMemoryTypeInformation[Index].NumberOfPages; - // gMemoryTypeInformation[Index].NumberOfPages = 0; - // MU_CHANGE Ends - } - } - - // - // If the number of pages reserved for a memory type is 0, then all allocations for that type - // should be in the default range. - // - for (Type = (EFI_MEMORY_TYPE)0; Type < EfiMaxMemoryType; Type++) { - for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { - if (Type == (EFI_MEMORY_TYPE)gMemoryTypeInformation[Index].Type) { - mMemoryTypeStatistics[Type].InformationIndex = Index; - } - } - - mMemoryTypeStatistics[Type].CurrentNumberOfPages = 0; - if (mMemoryTypeStatistics[Type].MaximumAddress == MAX_ALLOC_ADDRESS) { - mMemoryTypeStatistics[Type].MaximumAddress = mDefaultMaximumAddress; - } - } - - mMemoryTypeInformationInitialized = TRUE; + // MU_CHANGE BEGIN: PEI Bins - Replaced inline bin allocation with shared function + // // + // // Check to see if the statistics for the different memory types have already been established + // // + // if (mMemoryTypeInformationInitialized) { + // return; + // } + // + // // + // // Loop through each memory type in the order specified by the gMemoryTypeInformation[] array + // // + // for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // // + // // Make sure the memory type in the gMemoryTypeInformation[] array is valid + // // + // Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); + // if ((UINT32)Type > EfiMaxMemoryType) { + // continue; + // } + // + // if (gMemoryTypeInformation[Index].NumberOfPages != 0) { + // // + // // Allocate pages for the current memory type from the top of available memory + // // + // Status = CoreAllocatePages ( + // AllocateAnyPages, + // Type, + // gMemoryTypeInformation[Index].NumberOfPages, + // &mMemoryTypeStatistics[Type].BaseAddress + // ); + // if (EFI_ERROR (Status)) { + // // + // // If an error occurs allocating the pages for the current memory type, then + // // free all the pages allocates for the previous memory types and return. This + // // operation with be retied when/if more memory is added to the system + // // + // for (FreeIndex = 0; FreeIndex < Index; FreeIndex++) { + // // + // // Make sure the memory type in the gMemoryTypeInformation[] array is valid + // // + // Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[FreeIndex].Type); + // if ((UINT32)Type > EfiMaxMemoryType) { + // continue; + // } + // + // if (gMemoryTypeInformation[FreeIndex].NumberOfPages != 0) { + // CoreFreePages ( + // mMemoryTypeStatistics[Type].BaseAddress, + // gMemoryTypeInformation[FreeIndex].NumberOfPages + // ); + // mMemoryTypeStatistics[Type].BaseAddress = 0; + // mMemoryTypeStatistics[Type].MaximumAddress = MAX_ALLOC_ADDRESS; + // } + // } + // + // return; + // } + // + // // + // // Compute the address at the top of the current statistics + // // + // mMemoryTypeStatistics[Type].MaximumAddress = + // mMemoryTypeStatistics[Type].BaseAddress + + // LShiftU64 (gMemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT) - 1; + // + // // + // // If the current base address is the lowest address so far, then update the default + // // maximum address + // // + // if (mMemoryTypeStatistics[Type].BaseAddress < mDefaultMaximumAddress) { + // mDefaultMaximumAddress = mMemoryTypeStatistics[Type].BaseAddress - 1; + // } + // } + // } + // + // // + // // There was enough system memory for all the the memory types were allocated. So, + // // those memory areas can be freed for future allocations, and all future memory + // // allocations can occur within their respective bins + // // + // for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // // + // // Make sure the memory type in the gMemoryTypeInformation[] array is valid + // // + // Type = (EFI_MEMORY_TYPE)(gMemoryTypeInformation[Index].Type); + // if ((UINT32)Type > EfiMaxMemoryType) { + // continue; + // } + // + // if (gMemoryTypeInformation[Index].NumberOfPages != 0) { + // CoreFreePages ( + // mMemoryTypeStatistics[Type].BaseAddress, + // gMemoryTypeInformation[Index].NumberOfPages + // ); + // mMemoryTypeStatistics[Type].NumberOfPages = gMemoryTypeInformation[Index].NumberOfPages; + // gMemoryTypeInformation[Index].NumberOfPages = 0; + // } + // } + // + // // + // // If the number of pages reserved for a memory type is 0, then all allocations for that type + // // should be in the default range. + // // + // for (Type = (EFI_MEMORY_TYPE)0; Type < EfiMaxMemoryType; Type++) { + // for (Index = 0; gMemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // if (Type == (EFI_MEMORY_TYPE)gMemoryTypeInformation[Index].Type) { + // mMemoryTypeStatistics[Type].InformationIndex = Index; + // } + // } + // + // mMemoryTypeStatistics[Type].CurrentNumberOfPages = 0; + // if (mMemoryTypeStatistics[Type].MaximumAddress == MAX_ALLOC_ADDRESS) { + // mMemoryTypeStatistics[Type].MaximumAddress = mDefaultMaximumAddress; + // } + // } + // + // mMemoryTypeInformationInitialized = TRUE; + // Check if we need to allocate the memory bins. This function will immediately return if we have already done so. + // Pass FALSE to indicate we don't need to publish the HOB. + AllocateMemoryTypeInformationBins ( + &mMemoryTypeInformationInitialized, + gMemoryTypeInformation, + mMemoryTypeStatistics, + &mDefaultMaximumAddress, + FALSE + ); + // MU_CHANGE END: PEI Bins } /** @@ -1131,31 +957,44 @@ CoreConvertPagesEx ( return EFI_NOT_FOUND; } - // - // Update counters for the number of pages allocated to each memory type - // - if ((UINT32)Entry->Type < EfiMaxMemoryType) { - if (((Start >= mMemoryTypeStatistics[Entry->Type].BaseAddress) && (Start <= mMemoryTypeStatistics[Entry->Type].MaximumAddress)) || - ((Start >= mDefaultBaseAddress) && (Start <= mDefaultMaximumAddress))) - { - if (NumberOfPages > mMemoryTypeStatistics[Entry->Type].CurrentNumberOfPages) { - mMemoryTypeStatistics[Entry->Type].CurrentNumberOfPages = 0; - } else { - mMemoryTypeStatistics[Entry->Type].CurrentNumberOfPages -= NumberOfPages; - } - } - } - - if ((UINT32)NewType < EfiMaxMemoryType) { - if (((Start >= mMemoryTypeStatistics[NewType].BaseAddress) && (Start <= mMemoryTypeStatistics[NewType].MaximumAddress)) || - ((Start >= mDefaultBaseAddress) && (Start <= mDefaultMaximumAddress))) - { - mMemoryTypeStatistics[NewType].CurrentNumberOfPages += NumberOfPages; - if (mMemoryTypeStatistics[NewType].CurrentNumberOfPages > gMemoryTypeInformation[mMemoryTypeStatistics[NewType].InformationIndex].NumberOfPages) { - gMemoryTypeInformation[mMemoryTypeStatistics[NewType].InformationIndex].NumberOfPages = (UINT32)mMemoryTypeStatistics[NewType].CurrentNumberOfPages; - } - } - } + // MU_CHANGE BEGIN: PEI Bins - Replaced inline memory statistics update with shared function + // // + // // Update counters for the number of pages allocated to each memory type + // // + // if ((UINT32)Entry->Type < EfiMaxMemoryType) { + // if (((Start >= mMemoryTypeStatistics[Entry->Type].BaseAddress) && (Start <= mMemoryTypeStatistics[Entry->Type].MaximumAddress)) || + // ((Start >= mDefaultBaseAddress) && (Start <= mDefaultMaximumAddress))) + // { + // if (NumberOfPages > mMemoryTypeStatistics[Entry->Type].CurrentNumberOfPages) { + // mMemoryTypeStatistics[Entry->Type].CurrentNumberOfPages = 0; + // } else { + // mMemoryTypeStatistics[Entry->Type].CurrentNumberOfPages -= NumberOfPages; + // } + // } + // } + // + // if ((UINT32)NewType < EfiMaxMemoryType) { + // if (((Start >= mMemoryTypeStatistics[NewType].BaseAddress) && (Start <= mMemoryTypeStatistics[NewType].MaximumAddress)) || + // ((Start >= mDefaultBaseAddress) && (Start <= mDefaultMaximumAddress))) + // { + // mMemoryTypeStatistics[NewType].CurrentNumberOfPages += NumberOfPages; + // if (mMemoryTypeStatistics[NewType].CurrentNumberOfPages > gMemoryTypeInformation[mMemoryTypeStatistics[NewType].InformationIndex].NumberOfPages) { + // gMemoryTypeInformation[mMemoryTypeStatistics[NewType].InformationIndex].NumberOfPages = (UINT32)mMemoryTypeStatistics[NewType].CurrentNumberOfPages; + // } + // } + // } + UpdateMemoryStatistics ( + Entry->Type, + NewType, + Start, + (UINTN)NumberOfPages, + &mMemoryTypeInformationInitialized, + mMemoryTypeStatistics, + gMemoryTypeInformation, + mDefaultBaseAddress, + mDefaultMaximumAddress + ); + // MU_CHANGE END: PEI Bins } // @@ -2233,30 +2072,6 @@ CoreGetMemoryMap ( (Entry->End <= mMemoryTypeStatistics[Type].MaximumAddress)) { MemoryMap->Type = Type; - // MU_CHANGE STARTS: Add check to merge memory regions of the bucket type - } else if (mMemoryTypeStatistics[Type].Special && - (mMemoryTypeStatistics[Type].NumberOfPages > 0) && - MemoryRegionsIntersect ( - Entry->Start, - Entry->End, - mMemoryTypeStatistics[Type].BaseAddress, - mMemoryTypeStatistics[Type].MaximumAddress - )) - { - // There is partial overlap with a special memory type bin. - // This is not allowed, so we will not change the type. - DEBUG (( - DEBUG_ERROR, - "%a: Memory Map entry partially overlaps with a special memory type bin. Bucket Type %d, Type %d, Start 0x%lx, End 0x%lx\n", - __func__, - Type, - Entry->Type, - Entry->Start, - Entry->End - )); - - ASSERT (FALSE); - // MU_CHANGE ENDS } } } diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.c index 356c929daac..60e8cc89373 100644 --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.c +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.c @@ -1106,10 +1106,6 @@ MergeMemoryMapByAttribute ( MemoryBlockLength = (UINT64)(EfiPagesToSize (NewMemoryMapEntry->NumberOfPages)); if (((UINTN)NextMemoryMapEntry < (UINTN)MemoryMapEnd) && (NewMemoryMapEntry->Attribute == NextMemoryMapEntry->Attribute) && - // MU_CHANGE STARTS: Add check to merge memory regions of the bucket type - (GetBucketMemoryType (NewMemoryMapEntry->PhysicalStart, NewMemoryMapEntry->PhysicalStart + EFI_PAGES_TO_SIZE (NewMemoryMapEntry->NumberOfPages) - 1) == - GetBucketMemoryType (NextMemoryMapEntry->PhysicalStart, NextMemoryMapEntry->PhysicalStart + EFI_PAGES_TO_SIZE (NextMemoryMapEntry->NumberOfPages) - 1)) && - // MU_CHANGE ENDS ((NewMemoryMapEntry->PhysicalStart + MemoryBlockLength) == NextMemoryMapEntry->PhysicalStart)) { NewMemoryMapEntry->NumberOfPages += NextMemoryMapEntry->NumberOfPages; diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.h b/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.h index ee021b676e4..4c4ba7726b2 100644 --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.h +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtectionSupport.h @@ -11,7 +11,6 @@ #include "DxeMain.h" #include "Mem/HeapGuard.h" -#include "Mem/Imem.h" // MU_CHANGE: Include Imem.h for bucket memory type functions #include #include diff --git a/MdeModulePkg/Core/MemoryBins.md b/MdeModulePkg/Core/MemoryBins.md new file mode 100644 index 00000000000..460a422c36e --- /dev/null +++ b/MdeModulePkg/Core/MemoryBins.md @@ -0,0 +1,277 @@ + +# Memory Bin Feature + +## Table of Contents + +## Background + +The S4 sleep state is the lowest power state defined by the ACPI spec that supports system state restoration. In this +sleep state, the OS or platform FW is responsible for saving the running context in a persistent location. Upon +resumption from S4, this running context needs to be restored to memory and whatever device state is required. Platform +FW is responsible for bringing the system out of S4 and returning to the OS, which generally is the one to restore +system context. Because the OS needs to return the system memory to exactly the state it was before, it must use the +same memory regions as it did the previous boot. In order to do this, UEFI must have the same runtime memory footprint +it did on the previous boot. + +The edk2 memory bin feature is designed to support the S4 sleep state's requirements to keep UEFI's runtime memory +footprint identical between boots of the same FW. + +## Implementation + +### Overview + +There are 5 types of UEFI memory that persist into the OS timeframe: EfiReservedMemoryType, EfiRuntimeServicesCode, +EfiRuntimeServicesData, EfiACPIMemoryNVS, and generally EfiACPIReclaimMemory (the OS is allowed to reclaim this, but +typically does not). edk2 allows a platform to define a memory bin size for each of these memory types, as desired. +Technically, any type can be requested to use a memory bin, but the real use case is for the runtime types. + +The DXE and PEI (if configured) allocators will attempt to allocate all runtime types in the memory bin defined for +them. If the allocation size is too large, it will allocate it elsewhere. Runtime memory may also be allocated by +address outside the bin or reported as a Resource Descriptor HOB; neither of these will land in the memory bin. + +When the EFI_BOOT_SERVICES.GetMemoryMap() API is called, edk2 will examine the memory bin sizes defined by the +platform and create an EFI_MEMORY_MAP descriptor that is the bin size (assuming allocations are lower than the defined +size). This way, any fluctuations in runtime memory usage are covered by the defined bin size, i.e. the bin size is an +overallocation of runtime memory usage which allows for stability during S4 resumes. + +### MemoryBinLib + +MemoryBinLib is the phase agnostic memory bin logic. This logic is shared between DXE core and PEI core to have +standardized memory bin operation. It handles HOB processing and production (for PEI), memory bin setup in the various +forms, and memory statistics. + +### Boot Flow + +A platform opts into either PEI + DXE memory bins or DXE only (the original implementation). A platform may also define +where the memory bin region is. All of these paths are defined below. + +#### Platform Configuration + +A platform opts into the memory bin feature by producing the +[Memory Type Information HOB](https://github.com/tianocore/edk2/blob/HEAD/MdeModulePkg/Include/Guid/MemoryTypeInformation.h). +This HOB defines the type and size of each bin. If this HOB is not produced, no version of memory bins will be used and +runtime allocations will fall wherever the allocator finds space, likely breaking S4 resume. + +A platform can also define where the memory bins should be allocated by creating a resource descriptor HOB with the +owner set to gEfiMemoryTypeInformationGuid. This is recommended as it will create even more stability in the bins by +ensuring they live at a fixed address. + +With the Memory Type Information HOB produced and optionally the Resource Descriptor HOB produced, a platform will opt +into DXE only memory bins. However, it is recommended a platform opt into PEI memory bins as well for greater stability +as post-mem PEI often makes runtime memory allocations; without memory bins in PEI, these either have a high chance of +breaking S4 resume or must be allocated as non-runtime and relocated in DXE to a runtime bin. + +To opt into PEI memory bins a platform must, in any order: + +- Set `gEfiMdeModulePkgTokenSpaceGuid.PcdPeiMemoryBinsEnable` to `TRUE` in their DSC. +- Produce the Memory Type Information HOB in SEC or pre-mem PEI. +- Optionally produce the Resource Descriptor HOB owned by gEfiMemoryTypeInformationGuid in SEC or pre-mem PEI. This must + not be produced post-mem or DXE will ignore the PEI memory bins because of conflicting Resource Descriptor HOBs. + +#### PEI Memory Bins + +##### PEI Setup + +PEI memory bins are enabled in post-mem PEI in order to have memory available for the allocation of the bins. If a +platform allocates any runtime memory before memory is installed, it will not land in the bin and will jeopardize S4 +resume stability. The PEI memory bin feature is only applicable to the PEI_SERVICES.AllocatePages() and +PEI_SERVICES.FreePages() APIs. PEI_SERVICES.AllocatePool() does not have memory types associated with it and per the +PI spec DXE can ignore the EFI_HOB_MEMORY_POOL HOBs this API produces. I.e., PEI pool memory does not persist into DXE +and so therefore cannot persist into the OS runtime and affect S4 stability. + +When permanent memory is discovered, PEI core is relaunched to begin the post-mem PEI phase. When memory services +are initialized post-mem, PEI core will check for `PcdPeiMemoryBinsEnable`, which is the opt in +mechanism to PEI memory bins. If the PCD is not `FALSE`, the memory bin flow will try again in DXE. + +PEI core will next look for the Memory Type Information HOB to get bin types and sizes. If this is found, PEI memory +bins will be enabled. + +The final piece of configuration PEI core looks for is a Resource Descriptor HOB owned by gEfiMemoryTypeInformationGuid. +If one and only one of these HOBs is discovered, PEI core will use this range for the memory bins (if it is large +enough). + +If the Resource Descriptor HOB is not present, PEI core will allocate the memory for the memory bins as one +large contiguous chunk and then split it up amongst the bins. It will then create a Resource Descriptor HOB owned by +gEfiMemoryTypeInformationGuid to tell DXE core where the PEI memory bins are located. + +Finally, PEI core will update the PHIT free memory region to exclude the memory bin region. + +> **Note:** PEI must be very careful in its usage of global writeable variables. Usage of these pre-mem or if PEI Core +> has not been shadowed to memory will generally result in page faults, as flash is write protected. As a result, this +> design does not rely on any writeable global variables for PEI (but retains them in DXE). Instead, the stack based +> PEI_CORE_INSTANCE is given pointers to the relevant structures which are allocated in post-mem PEI in permanent +> memory. This is done to avoid a HOB lookup for every PEI memory allocation while still avoiding global variables. + +##### PEI Operation + +When PEI_SERVICES.AllocatePages() is called, PEI core will attempt to allocate the request in the memory bin, if +defined. If the request fails, PEI core will fall back to its standard allocation mechanism: attempt to allocate from +the PHIT free memory, if there is not enough space, look for a free memory memory allocation HOB. + +When PEI_SERVICES.FreePages() is called, PEI core will free the pages as normal. + +As with allocating from the PHIT HOB, PEI core will only ever walk down the memory range defined in a given bin, that is +new allocations are given the requested size down from the top of the bin and freeing pages does not increase the +available range in the bin. This is done to keep the PEI allocator simple and because most runtime allocations are +expected in DXE. + +PEI core will mark all of the Memory Allocation HOBs for bin types its allocator produces with +gEfiMemoryTypeInformationGuid. This is done so that DXE can use the Memory Allocation HOBs to build bin statistics, +e.g. which allocations landed within a bin and which landed outside of a bin. Updating the Name field of the Memory +Allocation HOB allows DXE Core to distinguish between allocations that PEI Core made and are subject to bin rules and +those that were before the PEI bins were setup; platforms may produce Memory Allocation HOBs to specify static runtime +regions. These should not be included in the bin logic. If PEI Core is not configured to use memory bins, it will not +mark its allocator created Memory Allocation HOBs with the PEI Core GUID. DXE will then ignore all pre-DXE memory +allocations and how they fall into the bins or not. + +#### DXE Memory Bins + +##### DXE Setup + +DXE core is agnostic to whether PEI core set up memory bins or not, it will simply find the relevant HOBs and operate +on them if present, regardless of producer. + +DXE memory bins apply to the EFI_BOOT_SERVICES.AllocatePages() and EFI_BOOT_SERVICES.FreePages() APIs. The Pool APIs +will respect the memory bins, but only because they use the Page APIs under the hood. + +When DXE core initializes memory services, it will look for the Memory Type Information HOB. If this is not present, +bins will not be initialized. It will then look for a Resource Descriptor HOB with owner gEfiMemoryTypeInformationGuid. +If this HOB is present, the DXE memory bins will be at this location. If it is not present, DXE will allocate memory +bins. + +If the Resource Descriptor HOB was present, DXE core will process through the Memory Allocation HOBs that have the +gEfiMemoryTypeInformationGuid to discover if any bin types have been allocated pre-DXE and whether they fall in or out +of the defined bin range. DXE core will seed its memory statistics with this information. See #BDS-Setup for information +on how the memory statistics are used. + +##### DXE Operation + +When EFI_BOOT_SERVICES.AllocatePages() is called, DXE core will attempt to allocate the memory in the defined bin +region. If the allocation succeeds, DXE core will update the memory statistics to indicate the requested number of pages +landed in the bin. If the allocation failed, DXE core will fall back to its standard pattern, attempting to allocate +outside of the bin. It will also update the memory statistics to indicate that the requested number of pages landed +outside of the bin. + +When EFI_BOOT_SERVICES.FreePages() is called, DXE core will free the pages and update the memory statistics to indicate +that the number of pages either in or out of the bin are not being used. + +The DXE allocator is much smarter than the PEI allocator and does account for bin pages freed back and allows them to +be allocated again. + +DXE core will publish the +[gMemoryTypeInformationGuid](https://github.com/tianocore/edk2/blob/HEAD/MdeModulePkg/Include/Guid/MemoryTypeInformation.h#L26) +config table to publish the memory statistics for BDS to consume to +advise a platform on the correct memory bin sizes. See [BDS Advertisement](#bds-advertisement) for more details. + +When EFI_BOOT_SERVICES.GetMemoryMap() is called, DXE core will create a single EFI_MEMORY_DESCRIPTOR for each memory +bin. + +#### BDS Advertisement + +[UefiBootManagerLib](https://github.com/tianocore/edk2/blob/HEAD/MdeModulePkg/Library/UefiBootManagerLib) has a +mechanism to advise a platform on the ideal bin size and location for S4 stability based on the current boot memory +statistics that DXE has collected. + +Just before launching a given boot option, UefiBootManagerLib (generally called BDS for brevity below) consumes the +`gMemoryTypeInformationGuid` config table that DXE core has produced. It reads the currently used bin pages and +compares them against the last used bin pages, using a heuristic to calculate the ideal number of pages for each memory +bin. It then writes the gEfiMemoryTypeInformation.MemoryTypeInformation variable to give the platform each recommended +memory bin size for S4 stability. BDS will optionally reboot the system if the bin size has changed, depending on +[PcdResetOnMemoryTypeInformationChange](https://github.com/tianocore/edk2/blob/HEAD/MdeModulePkg/MdeModulePkg.dec#L1930). + +##### Platform Adjustments + +Prior to post-mem PEI, it is recommended that the platform consume the gEfiMemoryTypeInformation.MemoryTypeInformation +variable if present. Then, the platform can use the BDS recommended values to create the Memory Type Information HOB. + +>**Note:** The recommended path has a dependency on variable reads being available in pre-mem PEI or SEC. Without +> this, the BDS advertisement cannot be acted upon. PEI memory bins may still be used without variable reads in +> pre-mem PEI/SEC, but the greatest chance of S4 resume success will be following the recommended path. + +#### End-to-End Diagrams + +```mermaid +flowchart TD + Start([Boot Start]) --> SecPreMemPEI[SEC/Pre-Memory PEI Phase] + + SecPreMemPEI --> CheckPlatform{Platform Configuration} + + CheckPlatform -->|Produces Memory Type Info HOB| MemTypeHOB[Memory Type Information HOB Created] + + MemTypeHOB --> CheckResourceHOB{Optional: Resource Descriptor HOB?} + + CheckResourceHOB -->|Yes| ResourceHOB[Resource Descriptor HOB
gEfiMemoryTypeInformationGuid] + CheckResourceHOB -->|No| NoResourceHOB[No Resource Descriptor HOB] + + ResourceHOB --> CheckPEIPath{PEI?} + NoResourceHOB --> CheckPEIPath + + CheckPEIPath -->|Yes| MemDiscovered + CheckPEIPath -->|No PEI| DXEOnly[**DXE Only Path**] + + %% PEI + DXE Path + MemDiscovered --> PEICoreRelaunch[PEI Core Relaunched Post-Memory] + PEICoreRelaunch --> CheckPcd{PcdPeiMemoryBinsEnable TRUE?} + + CheckPcd -->|No| DXEOnly[DXE only bins] + CheckPcd -->|Yes| InitPEIBins[Initialize PEI Memory Bins] + + InitPEIBins --> CheckPEIResourceHOB{Resource Descriptor HOB Present?} + + CheckPEIResourceHOB -->|Yes| UsePEIResource[Use Resource HOB Range for Bins] + CheckPEIResourceHOB -->|No| AllocatePEI[Allocate Contiguous Memory for Bins] + + AllocatePEI --> CreatePEIResourceHOB[Create Resource Descriptor HOB
for DXE] + UsePEIResource --> UpdatePHIT1[Update PHIT to Exclude Bin Region] + CreatePEIResourceHOB --> UpdatePHIT1 + + UpdatePHIT1 --> PEIOperation[PEI Operation:
AllocatePages attempts bin first,
falls back to PHIT/free HOBs] + + PEIOperation --> TransitionToDXE[Transition to DXE] + DXEOnly --> TransitionToDXE + + %% DXE Phase Common + TransitionToDXE --> DXEInit[DXE Core Memory Services Init] + + DXEInit --> CheckDXEMemTypeHOB{Memory Type Info HOB Present?} + + CheckDXEMemTypeHOB -->|No| NoBins[No Memory Bins Initialized] + CheckDXEMemTypeHOB -->|Yes| CheckDXEResourceHOB{Resource Descriptor HOB Present?} + + CheckDXEResourceHOB -->|Yes| ProcessMemAlloc[Process Memory Allocation HOBs
Seed Statistics] + CheckDXEResourceHOB -->|No| AllocateDXEBins[Allocate DXE Memory Bins] + + ProcessMemAlloc --> UseDXEResource[Use Resource HOB Range for DXE Bins] + AllocateDXEBins --> DXEOperation + UseDXEResource --> DXEOperation + + DXEOperation[DXE Operation:
AllocatePages/FreePages use bins
Update statistics] + + DXEOperation --> PublishConfig[Publish Config Table] + NoBins --> BDSPhase + + PublishConfig --> BDSPhase[BDS Phase] + + BDSPhase --> ConsumeStats[BDS Consumes Config Table] + ConsumeStats --> CalculateIdeal[Calculate Ideal Bin Size] + CalculateIdeal --> WriteVariable[Write MemoryTypeInformation Variable] + + WriteVariable --> CheckBinChange{Bin Size Changed?} + + CheckBinChange -->|Yes| CheckRebootPCD{PcdResetOnMemoryTypeInformationChange?} + CheckBinChange -->|No| LaunchBootOption + + CheckRebootPCD -->|TRUE| Reboot[System Reboot] + CheckRebootPCD -->|FALSE| LaunchBootOption[Launch Boot Option] + + Reboot --> Start + LaunchBootOption --> GetMemoryMap[GetMemoryMap Called] + GetMemoryMap --> CreateDescriptor[Create EFI_MEMORY_DESCRIPTOR
for Each Bin] + CreateDescriptor --> OSRuntime[OS Runtime] + + style ResourceHOB fill:#d4edda + style NoResourceHOB fill:#f8d7da + style DXEOnly fill:#5c4a1f,color:#fff + style ResourceHOB fill:#1f4a2f,color:#fff + style NoResourceHOB fill:#5a2a2a,color:#fff +``` diff --git a/MdeModulePkg/Core/Pei/Memory/MemoryBin.c b/MdeModulePkg/Core/Pei/Memory/MemoryBin.c new file mode 100644 index 00000000000..a7a13d7c42d --- /dev/null +++ b/MdeModulePkg/Core/Pei/Memory/MemoryBin.c @@ -0,0 +1,587 @@ +// MU_CHANGE: PEI Bins - Whole File +/** @file + + Shared logic between cores to work with memory bins for S4 resume stability. This file is duplicated in PEI Core and + DXE Core until a BaseTools feature comes online to support recommended library instances. Any changes to this file + must also be made in MdeModulePkg/Core/Pei/Memory/MemoryBin.c. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#define MEMORY_ATTRIBUTE_MASK (EFI_RESOURCE_ATTRIBUTE_PRESENT | \ + EFI_RESOURCE_ATTRIBUTE_INITIALIZED | \ + EFI_RESOURCE_ATTRIBUTE_TESTED | \ + EFI_RESOURCE_ATTRIBUTE_READ_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_WRITE_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_EXECUTION_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_READ_ONLY_PROTECTED | \ + EFI_RESOURCE_ATTRIBUTE_16_BIT_IO | \ + EFI_RESOURCE_ATTRIBUTE_32_BIT_IO | \ + EFI_RESOURCE_ATTRIBUTE_64_BIT_IO | \ + EFI_RESOURCE_ATTRIBUTE_PERSISTENT | \ + EFI_RESOURCE_ATTRIBUTE_SPECIAL_PURPOSE ) + +#define TESTED_MEMORY_ATTRIBUTES (EFI_RESOURCE_ATTRIBUTE_PRESENT | \ + EFI_RESOURCE_ATTRIBUTE_INITIALIZED | \ + EFI_RESOURCE_ATTRIBUTE_TESTED ) + +/** + Calculate total memory bin size needed. + + @param BinTop The top address of the memory bins. This is an optional parameter. + If non-zero, alignment requirements will be considered in the calculation. + @param MemoryTypeInformation The memory type information array. + + @return The total memory bin size needed. + +**/ +UINT64 +CalculateTotalMemoryBinSizeNeeded ( + IN UINTN BinTop, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + UINTN Index; + UINT64 TotalSize; + UINTN Granularity; + + if (MemoryTypeInformation == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return 0; + } + + // + // Loop through each memory type in the order specified by the MemoryTypeInformation[] array + // + TotalSize = 0; + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + if ((MemoryTypeInformation[Index].Type == EfiReservedMemoryType) || + (MemoryTypeInformation[Index].Type == EfiACPIMemoryNVS) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesCode) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesData)) + { + Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } + + // MemoryTypeInformation[Index].NumberOfPages is already aligned to the allocation granularity + TotalSize += LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + + // BinTop is optional + if (BinTop == 0) { + continue; + } + + BinTop -= (UINTN)LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + TotalSize += (BinTop & (Granularity - 1)); + BinTop &= ~(Granularity - 1); + } + + return TotalSize; +} + +/** + Get the Memory Type Information HOB if it exists and populate gMemoryTypeInformation. + + @param MemoryTypeInformation The pointer to the memory type information array to be populated. + + @return EFI_STATUS On EFI_SUCCESS, gMemoryTypeInformation points to the + Memory Type Information. + @return EFI_NOT_FOUND No valid Memory Type Information HOB found. +**/ +EFI_STATUS +EFIAPI +PopulateMemoryTypeInformation ( + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + UINTN DataSize; + EFI_MEMORY_TYPE_INFORMATION *EfiMemoryTypeInformation; + EFI_HOB_GUID_TYPE *GuidHob; + UINTN Index; + UINT32 Granularity; + + if (MemoryTypeInformation == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + GuidHob = GetFirstGuidHob (&gEfiMemoryTypeInformationGuid); + if (GuidHob != NULL) { + EfiMemoryTypeInformation = GET_GUID_HOB_DATA (GuidHob); + DataSize = GET_GUID_HOB_DATA_SIZE (GuidHob); + if ((EfiMemoryTypeInformation != NULL) && (DataSize > 0) && (DataSize <= (EfiMaxMemoryType + 1) * sizeof (EFI_MEMORY_TYPE_INFORMATION))) { + CopyMem (MemoryTypeInformation, EfiMemoryTypeInformation, DataSize); + + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the MemoryTypeInformation[] array is valid + // + if (MemoryTypeInformation[Index].Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + if ((MemoryTypeInformation[Index].Type == EfiReservedMemoryType) || + (MemoryTypeInformation[Index].Type == EfiACPIMemoryNVS) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesCode) || + (MemoryTypeInformation[Index].Type == EfiRuntimeServicesData)) + { + Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } else { + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + } + + // Align the number of pages to the allocation granularity + MemoryTypeInformation[Index].NumberOfPages = (UINT32)RShiftU64 (ALIGN_VALUE (LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT), Granularity), EFI_PAGE_SHIFT); + } + } + + return EFI_SUCCESS; + } + + DEBUG ((DEBUG_ERROR, "%a: Invalid Memory Type Information HOB data\n", __func__)); + ASSERT (FALSE); + } + + DEBUG ((DEBUG_ERROR, "%a: No Memory Type Information HOB found\n", __func__)); + + return EFI_NOT_FOUND; +} + +/** + Look for Resource Descriptor HOB with a ResourceType of System Memory + and an Owner GUID of gEfiMemoryTypeInformationGuid. If more than 1 is + found, then return NULL. + + @param HobStart Pointer to the start of the HOB list. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + + @return Non-NULL The pointer to the singular MemoryTypeInformation Resource Descriptor HOB. + @return NULL No valid MemoryTypeInformation Resource Descriptor HOB found. +**/ +EFI_HOB_RESOURCE_DESCRIPTOR * +EFIAPI +GetMemoryTypeInformationResourceHob ( + IN VOID **HobStart, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ) +{ + UINTN Count; + EFI_PEI_HOB_POINTERS Hob; + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceHob; + EFI_HOB_RESOURCE_DESCRIPTOR *MemoryTypeInformationResourceHob; + + if ((HobStart == NULL) || (MemoryTypeInformation == NULL)) { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return NULL; + } + + // + // See if a Memory Type Information HOB is available + // + MemoryTypeInformationResourceHob = NULL; + Count = 0; + for (Hob.Raw = *HobStart; !END_OF_HOB_LIST (Hob); Hob.Raw = GET_NEXT_HOB (Hob)) { + if (GET_HOB_TYPE (Hob) != EFI_HOB_TYPE_RESOURCE_DESCRIPTOR) { + continue; + } + + ResourceHob = Hob.ResourceDescriptor; + if (!CompareGuid (&ResourceHob->Owner, &gEfiMemoryTypeInformationGuid)) { + continue; + } + + Count++; + if (ResourceHob->ResourceType != EFI_RESOURCE_SYSTEM_MEMORY) { + continue; + } + + if ((ResourceHob->ResourceAttribute & MEMORY_ATTRIBUTE_MASK) != TESTED_MEMORY_ATTRIBUTES) { + continue; + } + + if (ResourceHob->ResourceLength >= CalculateTotalMemoryBinSizeNeeded ((UINTN)(ResourceHob->PhysicalStart + ResourceHob->ResourceLength), MemoryTypeInformation)) { + MemoryTypeInformationResourceHob = ResourceHob; + } + } + + if (Count > 1) { + return NULL; + } + + return MemoryTypeInformationResourceHob; +} + +/** + Helper function to set up the bin statistics with the provided bin range + + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeStatistics The memory type statistics to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. +**/ +STATIC +VOID +InitializeBinStatisticsFromRange ( + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_PHYSICAL_ADDRESS *DefaultMaximumAddress + ) +{ + EFI_MEMORY_TYPE Type; + UINTN Index; + + if ((MemoryTypeInformation == NULL) || (MemoryTypeStatistics == NULL) || (DefaultMaximumAddress == NULL)) { + ASSERT (FALSE); + return; + } + + // + // If the number of pages reserved for a memory type is 0, then all + // allocations for that type should be in the default range. + // + for (Type = (EFI_MEMORY_TYPE)0; Type < EfiMaxMemoryType; Type++) { + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + if (Type == (EFI_MEMORY_TYPE)MemoryTypeInformation[Index].Type) { + MemoryTypeStatistics[Type].InformationIndex = Index; + } + } + + MemoryTypeStatistics[Type].CurrentNumberOfPages = 0; + if (MemoryTypeStatistics[Type].MaximumAddress == MAX_ALLOC_ADDRESS) { + MemoryTypeStatistics[Type].MaximumAddress = *DefaultMaximumAddress; + MemoryTypeStatistics[Type].DefaultBin = TRUE; + } + } +} + +/** + Sets the preferred memory range to use for the Memory Type Information bins. + This service must be called before fist call to CoreAddMemoryDescriptor(). + + If the location of the Memory Type Information bins has already been + established or the size of the range provides is smaller than all the + Memory Type Information bins, then the range provides is not used. + + @param Start The start address of the Memory Type Information range. + @param Length The size, in bytes, of the Memory Type Information range. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeStatistics The memory type statistics array to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. +**/ +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 + ) +{ + EFI_PHYSICAL_ADDRESS Top; + EFI_MEMORY_TYPE Type; + UINTN Index; + UINT64 Size; + + if ((MemoryTypeInformation == NULL) || + (MemoryTypeInformationInitialized == NULL) || + (MemoryTypeStatistics == NULL) || + (DefaultMaximumAddress == NULL)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return; + } + + // + // Return if Memory Type Information bin locations have already been set + // + if (*MemoryTypeInformationInitialized) { + DEBUG ((DEBUG_ERROR, "%a: Ignored. Bins already set.\n", __func__)); + return; + } + + // + // Return if size of the Memory Type Information bins is greater than Length + // + Size = CalculateTotalMemoryBinSizeNeeded ((UINTN)(Start + Length), MemoryTypeInformation); + + if (Size > Length) { + return; + } + + // + // Loop through each memory type in the order specified by the + // gMemoryTypeInformation[] array + // + Top = Start + Length; + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the MemoryTypeInformation[] array is valid + // + Type = (EFI_MEMORY_TYPE)(MemoryTypeInformation[Index].Type); + if ((UINT32)Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + MemoryTypeStatistics[Type].MaximumAddress = Top - 1; + Top -= LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + MemoryTypeStatistics[Type].BaseAddress = Top; + + // + // If the current base address is the lowest address so far, then update + // the default maximum address + // + if (MemoryTypeStatistics[Type].BaseAddress < *DefaultMaximumAddress) { + *DefaultMaximumAddress = MemoryTypeStatistics[Type].BaseAddress - 1; + } + + MemoryTypeStatistics[Type].NumberOfPages = MemoryTypeInformation[Index].NumberOfPages; + MemoryTypeInformation[Index].NumberOfPages = 0; + } + } + + InitializeBinStatisticsFromRange (MemoryTypeInformation, MemoryTypeStatistics, DefaultMaximumAddress); + + *MemoryTypeInformationInitialized = TRUE; +} + +/** + Allocate memory bins for each memory type as specified in gMemoryTypeInformation. + + If all the memory types cannot be allocated, then all previously allocated + memory types are freed and the function returns. If this function fails, it will log and expect to be called + again when more memory is added to the system. + + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeStatistics The memory type statistics array to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. + @param CreateHob TRUE to create Memory Type Information Resource HOB after successful + allocation. This is used for PEI Core to report the bins to DXE Core. + FALSE if HOB creation is not needed. +**/ +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 + ) +{ + UINTN Index; + EFI_MEMORY_TYPE Type; + EFI_PHYSICAL_ADDRESS BaseAddress; + EFI_PHYSICAL_ADDRESS LastBinAddress; + UINT64 RequiredSize; + + if ((MemoryTypeInformationInitialized == NULL) || + (MemoryTypeInformation == NULL) || + (MemoryTypeStatistics == NULL) || + (DefaultMaximumAddress == NULL)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return; + } + + // + // Check to see if the statistics for the different memory types have already been established + // + if (*MemoryTypeInformationInitialized) { + return; + } + + BaseAddress = 0; + RequiredSize = CalculateTotalMemoryBinSizeNeeded (0, MemoryTypeInformation); + if (RequiredSize == 0) { + *MemoryTypeInformationInitialized = TRUE; + return; + } + + DEBUG ((DEBUG_INFO, "%a: Attempting to allocate 0x%llx bytes for all memory bins\n", __func__, RequiredSize)); + + // To ensure we get a contiguous range of memory for our bins, we will attempt to allocate + // all of the memory needed in one go. If that works, we can then carve it up into the individual bins. We allocate + // reserved pages to ensure runtime page allocation granularity is taken into account. + BaseAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)AllocateReservedPages ( + (UINTN)RShiftU64 (RequiredSize, EFI_PAGE_SHIFT) + ); + + if (BaseAddress == 0) { + DEBUG ((DEBUG_ERROR, "%a: Could not allocate contiguous pages for all memory bins\n", __func__)); + ASSERT (FALSE); + return; + } + + LastBinAddress = BaseAddress + RequiredSize; + *DefaultMaximumAddress = BaseAddress - 1; + + // + // Loop through each memory type in the order specified by the gMemoryTypeInformation[] array + // + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the gMemoryTypeInformation[] array is valid + // + Type = (EFI_MEMORY_TYPE)(MemoryTypeInformation[Index].Type); + if ((UINT32)Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + MemoryTypeStatistics[Type].BaseAddress = LastBinAddress - LShiftU64 (MemoryTypeInformation[Index].NumberOfPages, EFI_PAGE_SHIFT); + MemoryTypeStatistics[Type].MaximumAddress = LastBinAddress - 1; + LastBinAddress = MemoryTypeStatistics[Type].BaseAddress; + } + } + + // + // There was enough system memory for all the the memory types were allocated. So, + // those memory areas can be freed for future allocations, and all future memory + // allocations can occur within their respective bins + // + FreePages ( + (VOID *)(UINTN)BaseAddress, + (UINTN)RShiftU64 (RequiredSize, EFI_PAGE_SHIFT) + ); + for (Index = 0; MemoryTypeInformation[Index].Type != EfiMaxMemoryType; Index++) { + // + // Make sure the memory type in the MemoryTypeInformation[] array is valid + // + Type = (EFI_MEMORY_TYPE)(MemoryTypeInformation[Index].Type); + if ((UINT32)Type > EfiMaxMemoryType) { + continue; + } + + if (MemoryTypeInformation[Index].NumberOfPages != 0) { + MemoryTypeStatistics[Type].NumberOfPages = MemoryTypeInformation[Index].NumberOfPages; + MemoryTypeInformation[Index].NumberOfPages = 0; + } + } + + InitializeBinStatisticsFromRange (MemoryTypeInformation, MemoryTypeStatistics, DefaultMaximumAddress); + + if (CreateHob) { + // + // Create a Resource Descriptor HOB to report the Memory Type Information bins to DXE Core + // + BuildResourceDescriptorWithOwnerHob ( + EFI_RESOURCE_SYSTEM_MEMORY, + TESTED_MEMORY_ATTRIBUTES, + BaseAddress, + RequiredSize, + &gEfiMemoryTypeInformationGuid + ); + } + + *MemoryTypeInformationInitialized = TRUE; +} + +/** + Update memory type statistics upon memory allocation and free. + + @param OldType The original memory type of the memory region. + @param NewType The new memory type of the memory region. + @param Start The starting physical address of the memory region. + @param NumberOfPages The number of pages in the memory region. + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeStatistics The memory type statistics array to be updated. + @param MemoryTypeInformation The memory type information array to be updated. + @param DefaultBaseAddress Default bin base address. + @param DefaultMaximumAddress Default bin maximum address. +**/ +VOID +EFIAPI +UpdateMemoryStatistics ( + IN EFI_MEMORY_TYPE OldType, + IN EFI_MEMORY_TYPE NewType, + IN EFI_PHYSICAL_ADDRESS Start, + IN UINTN NumberOfPages, + IN BOOLEAN *MemoryTypeInformationInitialized, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN EFI_PHYSICAL_ADDRESS DefaultBaseAddress, + IN EFI_PHYSICAL_ADDRESS DefaultMaximumAddress + ) +{ + if ((MemoryTypeInformationInitialized == NULL) || + (MemoryTypeStatistics == NULL) || + (MemoryTypeInformation == NULL)) + { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter(s)\n", __func__)); + ASSERT (FALSE); + return; + } + + if (!*MemoryTypeInformationInitialized) { + return; + } + + // + // Update counters for the number of pages allocated to each memory type + // + if ((UINT32)OldType < EfiMaxMemoryType) { + if (((Start >= MemoryTypeStatistics[OldType].BaseAddress) && (Start <= MemoryTypeStatistics[OldType].MaximumAddress)) || + ((Start >= DefaultBaseAddress) && (Start <= DefaultMaximumAddress))) + { + if (NumberOfPages > MemoryTypeStatistics[OldType].CurrentNumberOfPages) { + MemoryTypeStatistics[OldType].CurrentNumberOfPages = 0; + } else { + MemoryTypeStatistics[OldType].CurrentNumberOfPages -= NumberOfPages; + } + } + } + + if ((UINT32)NewType < EfiMaxMemoryType) { + if (((Start >= MemoryTypeStatistics[NewType].BaseAddress) && (Start <= MemoryTypeStatistics[NewType].MaximumAddress)) || + ((Start >= DefaultBaseAddress) && (Start <= DefaultMaximumAddress))) + { + MemoryTypeStatistics[NewType].CurrentNumberOfPages += NumberOfPages; + if ((MemoryTypeStatistics[NewType].InformationIndex < (UINTN)EfiMaxMemoryType) && + (MemoryTypeStatistics[NewType].CurrentNumberOfPages > MemoryTypeInformation[MemoryTypeStatistics[NewType].InformationIndex].NumberOfPages)) + { + MemoryTypeInformation[MemoryTypeStatistics[NewType].InformationIndex].NumberOfPages = (UINT32)MemoryTypeStatistics[NewType].CurrentNumberOfPages; + } + } + } +} diff --git a/MdeModulePkg/Core/Pei/Memory/MemoryServices.c b/MdeModulePkg/Core/Pei/Memory/MemoryServices.c index 80b2f898b18..746d63f63b8 100644 --- a/MdeModulePkg/Core/Pei/Memory/MemoryServices.c +++ b/MdeModulePkg/Core/Pei/Memory/MemoryServices.c @@ -7,6 +7,105 @@ SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "PeiMain.h" +// MU_CHANGE BEGIN: PEI Bins +#include +#include +// MU_CHANGE END: PEI Bins + +// MU_CHANGE BEGIN: PEI Bins +/** + Initialize memory type information bins based on the memory type information HOB. + + @param[in] PrivateData Pointer to PEI core's private instance data. + @param[in] HobList Pointer to the memory type information HOB. +**/ +STATIC +VOID +InitializeMemoryTypeInformationBins ( + PEI_CORE_INSTANCE *PrivateData, + IN VOID **HobList + ) +{ + EFI_PHYSICAL_ADDRESS BaseBinAddress; + EFI_PHYSICAL_ADDRESS EndBinAddress; + EFI_PEI_HOB_POINTERS Hob; + UINTN Index; + EFI_HOB_RESOURCE_DESCRIPTOR *MemoryTypeInformationResourceHob; + + BaseBinAddress = 0; + EndBinAddress = 0; + + MemoryTypeInformationResourceHob = GetMemoryTypeInformationResourceHob (HobList, PrivateData->MemoryTypeInformation); + + if (MemoryTypeInformationResourceHob != NULL) { + // + // If a Memory Type Information Resource HOB was found, then use the address + // range of the Memory Type Information Resource HOB as the preferred + // address range for the Memory Type Information bins. + // PEI doesn't use the last param here, so we ignore it, but + // the API needs something passed in. + // + CoreSetMemoryTypeInformationRange ( + MemoryTypeInformationResourceHob->PhysicalStart, + MemoryTypeInformationResourceHob->ResourceLength, + PrivateData->MemoryTypeInformation, + &PrivateData->MemoryTypeInformationInitialized, + PrivateData->MemoryTypeStatistics, + &BaseBinAddress + ); + + goto Fixup_PHIT; + } + + // We don't have a Memory Type Information Resource HOB, so we will allocate memory to use for the bins + // and create the HOB ourselves to inform DXE this is where the bins live. + // PEI doesn't use the last param here, so we ignore it, but + // the API needs something passed in. + AllocateMemoryTypeInformationBins ( + &PrivateData->MemoryTypeInformationInitialized, + PrivateData->MemoryTypeInformation, + PrivateData->MemoryTypeStatistics, + &BaseBinAddress, + TRUE + ); + +Fixup_PHIT: + // Set these to 0, ignoring whatever was set here by the memory bin code, PEI doesn't use it + BaseBinAddress = 0; + + // At this point we have set up our memory bins, so we need to fix up the PHIT to reflect the + // new memory allocations. + Hob.Raw = ((EFI_PEI_HOB_POINTERS *)HobList)->Raw; + + if (Hob.Raw == NULL) { + // + // We shouldn't get here, bins won't work without a valid HOB list + // + DEBUG ((DEBUG_ERROR, "%a: No valid HOB list found, can't init memory bins\n", __func__)); + ASSERT (FALSE); + return; + } + + // Find max/min addresses of the bins + for (Index = 0; (EFI_MEMORY_TYPE)Index < EfiMaxMemoryType; Index++) { + if ((PrivateData->MemoryTypeStatistics[Index].NumberOfPages != 0) && !PrivateData->MemoryTypeStatistics[Index].DefaultBin) { + if ((PrivateData->MemoryTypeStatistics[Index].BaseAddress < BaseBinAddress) || (BaseBinAddress == 0)) { + BaseBinAddress = PrivateData->MemoryTypeStatistics[Index].BaseAddress; + } + + if (PrivateData->MemoryTypeStatistics[Index].MaximumAddress > EndBinAddress) { + EndBinAddress = PrivateData->MemoryTypeStatistics[Index].MaximumAddress; + } + } + } + + DEBUG ((DEBUG_INFO, "%a: Memory bins address range 0x%llx - 0x%llx\n", __func__, BaseBinAddress, EndBinAddress)); + // The bins may or may not intersect the PHIT + if ((Hob.HandoffInformationTable->EfiFreeMemoryTop > BaseBinAddress) && (Hob.HandoffInformationTable->EfiFreeMemoryBottom < EndBinAddress)) { + Hob.HandoffInformationTable->EfiFreeMemoryTop = BaseBinAddress; + } +} +// MU_CHANGE END: PEI Bins /** @@ -27,6 +126,12 @@ InitializeMemoryServices ( IN PEI_CORE_INSTANCE *OldCoreData ) { + // MU_CHANGE BEGIN: PEI Bins + VOID *HobList; + EFI_STATUS Status; + EFI_MEMORY_TYPE Type; + // MU_CHANGE END: PEI Bins + PrivateData->SwitchStackSignal = FALSE; // @@ -47,6 +152,69 @@ InitializeMemoryServices ( // Set Ps to point to ServiceTableShadow in Cache // PrivateData->Ps = &(PrivateData->ServiceTableShadow); + // MU_CHANGE BEGIN: PEI Bins + } else { + // We have permanent memory now, so we should set up the memory bins if we can. No matter what, initialize the + // structures + PrivateData->MemoryTypeInformationInitialized = FALSE; + PrivateData->MemoryTypeInformation = NULL; + PrivateData->MemoryTypeStatistics = NULL; + + // Check if the platform has opted in to the PEI memory bins feature + if (FeaturePcdGet (PcdPeiMemoryBinsEnable)) { + Status = PeiGetHobList ((CONST EFI_PEI_SERVICES **)&PrivateData->Ps, &HobList); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return; + } + + PrivateData->MemoryTypeInformation = AllocateZeroPool (sizeof (EFI_MEMORY_TYPE_INFORMATION) * (EfiMaxMemoryType + 1)); + PrivateData->MemoryTypeStatistics = AllocateZeroPool (sizeof (EFI_MEMORY_TYPE_STATISTICS) * (EfiMaxMemoryType + 1)); + + if ((PrivateData->MemoryTypeInformation == NULL) || (PrivateData->MemoryTypeStatistics == NULL)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to allocate memory for PEI memory bins\n", __func__)); + ASSERT (FALSE); + return; + } + + for (Type = 0; Type < EfiMaxMemoryType; Type++) { + PrivateData->MemoryTypeInformation[Type].Type = Type; + + PrivateData->MemoryTypeStatistics[Type].InformationIndex = EfiMaxMemoryType; + + switch (Type) { + case EfiRuntimeServicesCode: + case EfiRuntimeServicesData: + case EfiPalCode: + PrivateData->MemoryTypeStatistics[Type].Runtime = TRUE; + // fall through + case EfiReservedMemoryType: + case EfiACPIReclaimMemory: + case EfiACPIMemoryNVS: + case EfiUnacceptedMemoryType: + PrivateData->MemoryTypeStatistics[Type].Special = TRUE; + break; + default: + break; + } + } + + Status = PopulateMemoryTypeInformation (PrivateData->MemoryTypeInformation); + if (EFI_ERROR (Status)) { + // + // Couldn't find the memory type information HOB, so we can't set up bins + // + DEBUG (( + DEBUG_ERROR, + "Memory Type Information HOB not found during memory services initialization but PPI was produced\n" + )); + ASSERT_EFI_ERROR (Status); + return; + } + + InitializeMemoryTypeInformationBins (PrivateData, &HobList); + } + // MU_CHANGE END: PEI Bins } return; @@ -57,9 +225,9 @@ InitializeMemoryServices ( This function registers the found memory configuration with the PEI Foundation. The usage model is that the PEIM that discovers the permanent memory shall invoke this service. - This routine will hold discoveried memory information into PeiCore's private data, - and set SwitchStackSignal flag. After PEIM who discovery memory is dispatched, - PeiDispatcher will migrate temporary memory to permanent memory. + This routine will store discovered memory information into PeiCore's private data, + and set the SwitchStackSignal flag. After the PEIM who discovered memory is dispatched, + the PeiDispatcher will migrate temporary memory to permanent memory. @param PeiServices An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation. @param MemoryBegin Start of memory address. @@ -266,15 +434,26 @@ ConvertMemoryAllocationHobs ( @param[in] MemoryType The type of memory allocated by this HOB. **/ +// MU_CHANGE BEGIN: PEI Bins - Added PeiServices parameter and reworked HOB creation logic VOID InternalBuildMemoryAllocationHob ( - IN EFI_PHYSICAL_ADDRESS BaseAddress, - IN UINT64 Length, - IN EFI_MEMORY_TYPE MemoryType + IN CONST EFI_PEI_SERVICES **PeiServices, + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN EFI_MEMORY_TYPE MemoryType ) { EFI_PEI_HOB_POINTERS Hob; EFI_HOB_MEMORY_ALLOCATION *MemoryAllocationHob; + EFI_STATUS Status; + PEI_CORE_INSTANCE *PrivateData; + + if (PeiServices == NULL) { + ASSERT (PeiServices != NULL); + return; + } + + PrivateData = PEI_CORE_INSTANCE_FROM_PS_THIS (PeiServices); // // Search unused(freed) memory allocation HOB. @@ -291,31 +470,47 @@ InternalBuildMemoryAllocationHob ( Hob.Raw = GetNextHob (EFI_HOB_TYPE_UNUSED, Hob.Raw); } - if (MemoryAllocationHob != NULL) { - // - // Reuse the unused(freed) memory allocation HOB. - // - MemoryAllocationHob->Header.HobType = EFI_HOB_TYPE_MEMORY_ALLOCATION; - ZeroMem (&(MemoryAllocationHob->AllocDescriptor.Name), sizeof (EFI_GUID)); - MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress = BaseAddress; - MemoryAllocationHob->AllocDescriptor.MemoryLength = Length; - MemoryAllocationHob->AllocDescriptor.MemoryType = MemoryType; - // - // Zero the reserved space to match HOB spec - // - ZeroMem (MemoryAllocationHob->AllocDescriptor.Reserved, sizeof (MemoryAllocationHob->AllocDescriptor.Reserved)); + // + // If we didn't find a HOB to reuse, then create a new one. + // + if (MemoryAllocationHob == NULL) { + Status = PeiCreateHob (PeiServices, EFI_HOB_TYPE_MEMORY_ALLOCATION, (UINT16)sizeof (EFI_HOB_MEMORY_ALLOCATION), (VOID **)&MemoryAllocationHob); + if (EFI_ERROR (Status) || (MemoryAllocationHob == NULL)) { + DEBUG (( + DEBUG_ERROR, + "%a: Failed to create memory allocation HOB Status = %r, MemoryAllocationHob = 0x%llx\n", + __func__, + Status, + MemoryAllocationHob + )); + ASSERT (FALSE); + return; + } + } + + MemoryAllocationHob->Header.HobType = EFI_HOB_TYPE_MEMORY_ALLOCATION; + + // + // If we are using PEI memory bins, track this allocation with the Memory Type Information GUID for DXE to consume. + // Otherwise, just produce a HOB with a zeroed GUID. + // + if (PrivateData->MemoryTypeInformationInitialized && (MemoryType < EfiMaxMemoryType) && + (PrivateData->MemoryTypeStatistics[MemoryType].NumberOfPages != 0)) + { + CopyGuid (&(MemoryAllocationHob->AllocDescriptor.Name), &gEfiMemoryTypeInformationGuid); } else { - // - // No unused(freed) memory allocation HOB found. - // Build memory allocation HOB normally. - // - BuildMemoryAllocationHob ( - BaseAddress, - Length, - MemoryType - ); + ZeroMem (&(MemoryAllocationHob->AllocDescriptor.Name), sizeof (EFI_GUID)); } + + MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress = BaseAddress; + MemoryAllocationHob->AllocDescriptor.MemoryLength = Length; + MemoryAllocationHob->AllocDescriptor.MemoryType = MemoryType; + // + // Zero the reserved space to match HOB spec + // + ZeroMem (MemoryAllocationHob->AllocDescriptor.Reserved, sizeof (MemoryAllocationHob->AllocDescriptor.Reserved)); } +// MU_CHANGE END: PEI Bins /** Update or split memory allocation HOB for memory pages allocate and free. @@ -330,8 +525,10 @@ InternalBuildMemoryAllocationHob ( others for pages allocate. **/ +// MU_CHANGE BEGIN: PEI Bins - Added PeiServices parameter VOID UpdateOrSplitMemoryAllocationHob ( + IN CONST EFI_PEI_SERVICES **PeiServices, IN OUT EFI_HOB_MEMORY_ALLOCATION *MemoryAllocationHob, IN EFI_PHYSICAL_ADDRESS Memory, IN UINT64 Bytes, @@ -345,6 +542,7 @@ UpdateOrSplitMemoryAllocationHob ( // Last pages need to be split out. // InternalBuildMemoryAllocationHob ( + PeiServices, Memory + Bytes, (MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress + MemoryAllocationHob->AllocDescriptor.MemoryLength) - (Memory + Bytes), MemoryAllocationHob->AllocDescriptor.MemoryType @@ -356,6 +554,7 @@ UpdateOrSplitMemoryAllocationHob ( // First pages need to be split out. // InternalBuildMemoryAllocationHob ( + PeiServices, MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress, Memory - MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress, MemoryAllocationHob->AllocDescriptor.MemoryType @@ -369,6 +568,7 @@ UpdateOrSplitMemoryAllocationHob ( MemoryAllocationHob->AllocDescriptor.MemoryLength = Bytes; MemoryAllocationHob->AllocDescriptor.MemoryType = MemoryType; } +// MU_CHANGE END: PEI Bins /** Merge adjacent free memory ranges in memory allocation HOBs. @@ -456,11 +656,13 @@ MergeFreeMemoryInMemoryAllocationHob ( **/ EFI_STATUS +// MU_CHANGE BEGIN: PEI Bins - Added PeiServices parameter FindFreeMemoryFromMemoryAllocationHob ( - IN EFI_MEMORY_TYPE MemoryType, - IN UINTN Pages, - IN UINTN Granularity, - OUT EFI_PHYSICAL_ADDRESS *Memory + IN CONST EFI_PEI_SERVICES **PeiServices, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN Pages, + IN UINTN Granularity, + OUT EFI_PHYSICAL_ADDRESS *Memory ) { EFI_PEI_HOB_POINTERS Hob; @@ -503,7 +705,7 @@ FindFreeMemoryFromMemoryAllocationHob ( } if (MemoryAllocationHob != NULL) { - UpdateOrSplitMemoryAllocationHob (MemoryAllocationHob, BaseAddress, Bytes, MemoryType); + UpdateOrSplitMemoryAllocationHob (PeiServices, MemoryAllocationHob, BaseAddress, Bytes, MemoryType); *Memory = BaseAddress; return EFI_SUCCESS; } else { @@ -511,12 +713,13 @@ FindFreeMemoryFromMemoryAllocationHob ( // // Retry if there are free memory ranges merged. // - return FindFreeMemoryFromMemoryAllocationHob (MemoryType, Pages, Granularity, Memory); + return FindFreeMemoryFromMemoryAllocationHob (PeiServices, MemoryType, Pages, Granularity, Memory); } return EFI_NOT_FOUND; } } +// MU_CHANGE END: PEI Bins /** The purpose of the service is to publish an interface that allows @@ -558,6 +761,7 @@ PeiAllocatePages ( UINTN RemainingMemory; UINTN Granularity; UINTN Padding; + UINTN Index; // MU_CHANGE: PEI Bins if ((MemoryType != EfiLoaderCode) && (MemoryType != EfiLoaderData) && @@ -607,82 +811,126 @@ PeiAllocatePages ( FreeMemoryTop = &(PrivateData->FreePhysicalMemoryTop); FreeMemoryBottom = &(PrivateData->PhysicalMemoryBegin); } else { - FreeMemoryTop = &(Hob.HandoffInformationTable->EfiFreeMemoryTop); - FreeMemoryBottom = &(Hob.HandoffInformationTable->EfiFreeMemoryBottom); - } - - // - // Check to see if on correct boundary for the memory type. - // If not aligned, make the allocation aligned and that we are not trying to allocate page 0, which is used for - // null detection. - // - Padding = *(FreeMemoryTop) & (Granularity - 1); - if (((UINTN)(*FreeMemoryTop - *FreeMemoryBottom) < Padding) || (*(FreeMemoryTop) - Padding == 0)) { - DEBUG ((DEBUG_ERROR, "AllocatePages failed: Out of space after padding.\n")); - return EFI_OUT_OF_RESOURCES; - } - - *(FreeMemoryTop) -= Padding; - if (Padding >= EFI_PAGE_SIZE) { - // - // Create a memory allocation HOB to cover - // the pages that we will lose to rounding - // - InternalBuildMemoryAllocationHob ( - *(FreeMemoryTop), - Padding & ~(UINTN)EFI_PAGE_MASK, - EfiConventionalMemory - ); + // MU_CHANGE BEGIN: PEI Bins - Bin-aware memory allocation + // if we are in permanent memory and have memory bins, we need to respect them + if (PrivateData->MemoryTypeInformationInitialized && !PrivateData->MemoryTypeStatistics[MemoryType].DefaultBin) { + FreeMemoryTop = &(PrivateData->MemoryTypeStatistics[MemoryType].MaximumAddress); + FreeMemoryBottom = &(PrivateData->MemoryTypeStatistics[MemoryType].BaseAddress); + } else { + FreeMemoryTop = &(Hob.HandoffInformationTable->EfiFreeMemoryTop); + FreeMemoryBottom = &(Hob.HandoffInformationTable->EfiFreeMemoryBottom); + } + // MU_CHANGE END: PEI Bins } + // MU_CHANGE BEGIN: PEI Bins - Two-pass allocation: try bin first, then PHIT, then HOBs // - // Verify that there is sufficient memory to satisfy the allocation. + // We will attempt up to two times here. + // If we don't have memory bins, we will only attempt to allocate from the PHIT. + // If we have memory bins, we'll attempt to allocate from the appropriate bin first. If that fails we'll try + // allocating from the PHIT. // - RemainingMemory = (UINTN)(*FreeMemoryTop - *FreeMemoryBottom); - RemainingPages = (UINTN)(RShiftU64 (RemainingMemory, EFI_PAGE_SHIFT)); + // If allocating from the PHIT fails in either case, we'll then search memory allocation HOBs for free memory. // - // The number of remaining pages needs to be greater than or equal to that of - // the request pages. In addition, there should be enough space left to hold a - // Memory Allocation HOB. - // - Pages = ALIGN_VALUE (Pages, EFI_SIZE_TO_PAGES (Granularity)); - if ((RemainingPages > Pages) || - ((RemainingPages == Pages) && - ((RemainingMemory & EFI_PAGE_MASK) >= sizeof (EFI_HOB_MEMORY_ALLOCATION)))) - { + for (Index = 0; Index < 2; Index++) { // - // Update the PHIT to reflect the memory usage + // Check to see if on correct boundary for the memory type. + // If not aligned, make the allocation aligned and that we are not trying to allocate page 0, which is used for + // null detection. // - *(FreeMemoryTop) -= Pages * EFI_PAGE_SIZE; + Padding = *(FreeMemoryTop) & (Granularity - 1); + if (((UINTN)(*FreeMemoryTop - *FreeMemoryBottom) < Padding) || (*(FreeMemoryTop) - Padding == 0)) { + if ((Index == 0) && PrivateData->MemoryTypeInformationInitialized && !PrivateData->MemoryTypeStatistics[MemoryType].DefaultBin) { + // + // Try the default bin before searching memory allocation HOBs + // + FreeMemoryTop = &(Hob.HandoffInformationTable->EfiFreeMemoryTop); + FreeMemoryBottom = &(Hob.HandoffInformationTable->EfiFreeMemoryBottom); + continue; + } - // - // Update the value for the caller - // - *Memory = *(FreeMemoryTop); + goto NotEnoughFreeMemory; + } + + *(FreeMemoryTop) -= Padding; + if (Padding >= EFI_PAGE_SIZE) { + // + // Create a memory allocation HOB to cover + // the pages that we will lose to rounding + // + InternalBuildMemoryAllocationHob ( + PeiServices, + *(FreeMemoryTop), + Padding & ~(UINTN)EFI_PAGE_MASK, + EfiConventionalMemory + ); + } // - // Create a memory allocation HOB. + // Verify that there is sufficient memory to satisfy the allocation. // - InternalBuildMemoryAllocationHob ( - *(FreeMemoryTop), - Pages * EFI_PAGE_SIZE, - MemoryType - ); - - return EFI_SUCCESS; - } else { + RemainingMemory = (UINTN)(*FreeMemoryTop - *FreeMemoryBottom); + RemainingPages = (UINTN)(RShiftU64 (RemainingMemory, EFI_PAGE_SHIFT)); // - // Try to find free memory by searching memory allocation HOBs. + // The number of remaining pages needs to be greater than or equal to that of + // the request pages. In addition, there should be enough space left to hold a + // Memory Allocation HOB. // - Status = FindFreeMemoryFromMemoryAllocationHob (MemoryType, Pages, Granularity, Memory); - if (!EFI_ERROR (Status)) { - return Status; + Pages = ALIGN_VALUE (Pages, EFI_SIZE_TO_PAGES (Granularity)); + if ((RemainingPages > Pages) || + ((RemainingPages == Pages) && + ((RemainingMemory & EFI_PAGE_MASK) >= sizeof (EFI_HOB_MEMORY_ALLOCATION)))) + { + // + // Update the PHIT to reflect the memory usage + // + *(FreeMemoryTop) -= Pages * EFI_PAGE_SIZE; + + // + // Update the value for the caller + // + *Memory = *(FreeMemoryTop); + + // + // Create a memory allocation HOB. + // + InternalBuildMemoryAllocationHob ( + PeiServices, + *(FreeMemoryTop), + Pages * EFI_PAGE_SIZE, + MemoryType + ); + + return EFI_SUCCESS; } - DEBUG ((DEBUG_ERROR, "AllocatePages failed: No 0x%lx Pages is available.\n", (UINT64)Pages)); - DEBUG ((DEBUG_ERROR, "There is only left 0x%lx pages memory resource to be allocated.\n", (UINT64)RemainingPages)); - return EFI_OUT_OF_RESOURCES; + // If this was the first allocation attempt, we are using memory bins, and this memory type lands in a bin, + // retry allocation but target the PHIT free region. + if ((Index == 0) && PrivateData->MemoryTypeInformationInitialized && !PrivateData->MemoryTypeStatistics[MemoryType].DefaultBin) { + // + // Try to the default bin before searching memory allocation HOBs + // + FreeMemoryTop = &(Hob.HandoffInformationTable->EfiFreeMemoryTop); + FreeMemoryBottom = &(Hob.HandoffInformationTable->EfiFreeMemoryBottom); + continue; + } else { + goto NotEnoughFreeMemory; + } + } + +NotEnoughFreeMemory: + // + // Try to find free memory by searching memory allocation HOBs. + // + Status = FindFreeMemoryFromMemoryAllocationHob ((CONST EFI_PEI_SERVICES **)PeiServices, MemoryType, Pages, Granularity, Memory); + if (!EFI_ERROR (Status)) { + return Status; } + + DEBUG ((DEBUG_ERROR, "AllocatePages failed: No 0x%lx Pages is available.\n", (UINT64)Pages)); + DEBUG ((DEBUG_ERROR, "There is only left 0x%lx pages memory resource to be allocated.\n", (UINT64)RemainingPages)); + return EFI_OUT_OF_RESOURCES; + // MU_CHANGE END: PEI Bins } /** @@ -817,7 +1065,7 @@ PeiFreePages ( } if (MemoryAllocationHob != NULL) { - UpdateOrSplitMemoryAllocationHob (MemoryAllocationHob, Memory, Bytes, EfiConventionalMemory); + UpdateOrSplitMemoryAllocationHob (PeiServices, MemoryAllocationHob, Memory, Bytes, EfiConventionalMemory); // MU_CHANGE: PEI Bins FreeMemoryAllocationHob (PrivateData, MemoryAllocationHob); return EFI_SUCCESS; } else { diff --git a/MdeModulePkg/Core/Pei/PeiMain.h b/MdeModulePkg/Core/Pei/PeiMain.h index 6fdd8db453d..a59195092d9 100644 --- a/MdeModulePkg/Core/Pei/PeiMain.h +++ b/MdeModulePkg/Core/Pei/PeiMain.h @@ -51,6 +51,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include #include #include +#include // MU_CHANGE: PEI Bins /// /// It is an FFS type extension used for PeiFindFileEx. It indicates current @@ -337,6 +338,25 @@ struct _PEI_CORE_INSTANCE { DELAYED_DISPATCH_TABLE *DelayedDispatchTable; EFI_PHYSICAL_ADDRESS PlatformBlob; // MU_CHANGE Used by AdvancedLogger + + // MU_CHANGE BEGIN: PEI Bins + // + // Whether memory bins are initialized and being used in PEI + // + BOOLEAN MemoryTypeInformationInitialized; + + // + // Memory type information for all memory types. The array index is the memory type. + // This is used for the memory bin feature, if enabled, to track bin sizes. + // + EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation; + + // + // Memory type statistics for all memory types. The array index is the memory type. + // This is used for the memory bin feature, if enabled, to track bin locations. + // + EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics; + // MU_CHANGE END: PEI Bins }; /// diff --git a/MdeModulePkg/Core/Pei/PeiMain.inf b/MdeModulePkg/Core/Pei/PeiMain.inf index dea1b20d085..8547d025f7e 100644 --- a/MdeModulePkg/Core/Pei/PeiMain.inf +++ b/MdeModulePkg/Core/Pei/PeiMain.inf @@ -34,6 +34,7 @@ Reset/Reset.c Ppi/Ppi.c PeiMain/PeiMain.c + Memory/MemoryBin.c # MU_CHANGE: PEI Bins Memory/MemoryServices.c Image/Image.c Hob/Hob.c @@ -83,6 +84,7 @@ gEdkiiMigrationInfoGuid ## SOMETIMES_CONSUMES ## HOB gEfiDelayedDispatchTableGuid ## SOMETIMES_PRODUCES ## HOB gEfiFirmwarePerformanceGuid # MU_CHANGE_161871 - needed to build SEC perf HOB + gEfiMemoryTypeInformationGuid ## SOMETIMES_CONSUMES ## HOB # MU_CHANGE: PEI Bins [Ppis] gEfiPeiStatusCodePpiGuid ## SOMETIMES_CONSUMES # PeiReportStatusService is not ready if this PPI doesn't exist @@ -124,6 +126,7 @@ gEfiMdeModulePkgTokenSpaceGuid.PcdDelayedDispatchMaxDelayUs ## CONSUMES gEfiMdeModulePkgTokenSpaceGuid.PcdDelayedDispatchCompletionTimeoutUs ## CONSUMES gEfiMdeModulePkgTokenSpaceGuid.PcdDelayedDispatchMaxEntries ## CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdPeiMemoryBinsEnable ## CONSUMES # MU_CHANGE: PEI Bins # [BootMode] # S3_RESUME ## SOMETIMES_CONSUMES diff --git a/MdeModulePkg/Core/PrivateInclude/MemoryBin.h b/MdeModulePkg/Core/PrivateInclude/MemoryBin.h new file mode 100644 index 00000000000..6c0cf77343b --- /dev/null +++ b/MdeModulePkg/Core/PrivateInclude/MemoryBin.h @@ -0,0 +1,149 @@ +// MU_CHANGE: PEI Bins - Whole File +/** @file + Shared logic between cores to work with memory bins for S4 resume stability. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#pragma once + +#include + +/** + Calculate total memory bin size needed. + + @param BinTop The top address of the memory bins. This is an optional parameter. + If non-zero, alignment requirements will be considered in the calculation. + @param MemoryTypeInformation The memory type information array. + + @return The total memory bin size needed. + +**/ +UINT64 +CalculateTotalMemoryBinSizeNeeded ( + IN UINTN BinTop, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ); + +/** + Get the Memory Type Information HOB if it exists and populate gMemoryTypeInformation. + + @param MemoryTypeInformation The pointer to the memory type information array to be populated. + + @return EFI_STATUS On EFI_SUCCESS, gMemoryTypeInformation points to the + Memory Type Information. + @return EFI_NOT_FOUND No valid Memory Type Information HOB found. +**/ +EFI_STATUS +EFIAPI +PopulateMemoryTypeInformation ( + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ); + +/** + Look for Resource Descriptor HOB with a ResourceType of System Memory + and an Owner GUID of gEfiMemoryTypeInformationGuid. If more than 1 is + found, then return NULL. + + @param HobStart Pointer to the start of the HOB list. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + + @return Non-NULL The pointer to the singular MemoryTypeInformation Resource Descriptor HOB. + @return NULL No valid MemoryTypeInformation Resource Descriptor HOB found. +**/ +EFI_HOB_RESOURCE_DESCRIPTOR * +EFIAPI +GetMemoryTypeInformationResourceHob ( + IN VOID **HobStart, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation + ); + +/** + Sets the preferred memory range to use for the Memory Type Information bins. + This service must be called before fist call to CoreAddMemoryDescriptor(). + + If the location of the Memory Type Information bins has already been + established or the size of the range provides is smaller than all the + Memory Type Information bins, then the range provides is not used. + + @param Start The start address of the Memory Type Information range. + @param Length The size, in bytes, of the Memory Type Information range. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeStatistics The memory type statistics array to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. +**/ +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 + ); + +/** + Allocate memory bins for each memory type as specified in gMemoryTypeInformation. + + If all the memory types cannot be allocated, then all previously allocated + memory types are freed and the function returns. If this function fails, it will log and expect to be called + again when more memory is added to the system. + + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeInformation The memory type information array to be used to determine + the size of the memory bins. + @param MemoryTypeStatistics The memory type statistics array to be updated with the memory bin + information if the provided range is used. + @param DefaultMaximumAddress A pointer to the default maximum address to be updated if the + provided range is used. + @param CreateHob TRUE to create Memory Type Information Resource HOB after successful + allocation. This is used for PEI Core to report the bins to DXE Core. + FALSE if HOB creation is not needed. +**/ +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 + ); + +/** + Update memory type statistics upon memory allocation and free. + + @param OldType The original memory type of the memory region. + @param NewType The new memory type of the memory region. + @param Start The starting physical address of the memory region. + @param NumberOfPages The number of pages in the memory region. + @param MemoryTypeInformationInitialized A pointer to a boolean that indicates whether the memory type + information bins have been initialized. + @param MemoryTypeStatistics The memory type statistics array to be updated. + @param MemoryTypeInformation The memory type information array to be updated. + @param DefaultBaseAddress Default bin base address. + @param DefaultMaximumAddress Default bin maximum address. +**/ +VOID +EFIAPI +UpdateMemoryStatistics ( + IN EFI_MEMORY_TYPE OldType, + IN EFI_MEMORY_TYPE NewType, + IN EFI_PHYSICAL_ADDRESS Start, + IN UINTN NumberOfPages, + IN BOOLEAN *MemoryTypeInformationInitialized, + IN EFI_MEMORY_TYPE_STATISTICS *MemoryTypeStatistics, + IN EFI_MEMORY_TYPE_INFORMATION *MemoryTypeInformation, + IN EFI_PHYSICAL_ADDRESS DefaultBaseAddress, + IN EFI_PHYSICAL_ADDRESS DefaultMaximumAddress + ); diff --git a/MdeModulePkg/Include/Guid/MemoryTypeInformation.h b/MdeModulePkg/Include/Guid/MemoryTypeInformation.h index d1bb7d985d4..b1348f78030 100644 --- a/MdeModulePkg/Include/Guid/MemoryTypeInformation.h +++ b/MdeModulePkg/Include/Guid/MemoryTypeInformation.h @@ -38,4 +38,21 @@ typedef struct { UINT32 NumberOfPages; ///< The pages of this type memory. } EFI_MEMORY_TYPE_INFORMATION; +// MU_CHANGE BEGIN: PEI Bins +// +// Entry in an array that keeps track of memory type statistics per memory bin +// +typedef struct { + EFI_PHYSICAL_ADDRESS BaseAddress; ///< The base address of the memory bin. + EFI_PHYSICAL_ADDRESS MaximumAddress; ///< The maximum address of the memory bin. + UINT64 CurrentNumberOfPages; ///< The current number of pages allocated. + UINT64 NumberOfPages; ///< The total number of pages in the bin. + UINTN InformationIndex; ///< The index into the EFI_MEMORY_TYPE_INFORMATION array. + BOOLEAN Special; ///< Indicates if this memory type persists into the OS runtime. + BOOLEAN Runtime; ///< Indicates if this memory type should have the EFI_MEMORY_RUNTIME + ///< attribute applied in the EFI_MEMORY_MAP. + BOOLEAN DefaultBin; ///< Indicates if this memory type is the default bin. +} EFI_MEMORY_TYPE_STATISTICS; +// MU_CHANGE END: PEI Bins + #endif diff --git a/MdeModulePkg/MdeModulePkg.dec b/MdeModulePkg/MdeModulePkg.dec index c29e03fa94d..a366d67175f 100644 --- a/MdeModulePkg/MdeModulePkg.dec +++ b/MdeModulePkg/MdeModulePkg.dec @@ -29,6 +29,7 @@ [Includes.Common.Private] Library/BrotliCustomDecompressLib/brotli/c/include + Core/PrivateInclude # MU_CHANGE: PEI Bins [LibraryClasses] # MU_CHANGE [BEGIN] - Isolate the VariablePolicy locking event into its own code. @@ -1134,6 +1135,20 @@ # @Prompt Install Internal Event Services Protocol gEfiMdeModulePkgTokenSpaceGuid.PcdInternalEventServicesEnabled|FALSE|BOOLEAN|0x30003003 + # MU_CHANGE BEGIN: PEI Bins + ## Indicates if the PEI memory bins feature is allowed to be enabled. + # TRUE - The PEI memory bins feature is allowed to be enabled. The platform must also produce the + # gEfiMemoryTypeInformationGuid HOB prior to permanent memory installation in order to enroll in the + # feature. + # FALSE - The PEI memory bins feature is not allowed to be enabled. If the gEfiMemoryTypeInformationGuid HOB + # is produced by the platform in PEI, DXE will set up the memory bins. If not, no memory bins will be + # used. + # + # This PCD defaults to FALSE to follow the same behavior prior to the PEI memory bins feature being introduced. + # See MdeModulePkg/Core/MemoryBins.md for more details. + gEfiMdeModulePkgTokenSpaceGuid.PcdPeiMemoryBinsEnable|FALSE|BOOLEAN|0x00010080 + # MU_CHANGE END: PEI Bins + [PcdsFeatureFlag.IA32, PcdsFeatureFlag.ARM, PcdsFeatureFlag.AARCH64] gEfiMdeModulePkgTokenSpaceGuid.PcdPciDegradeResourceForOptionRom|FALSE|BOOLEAN|0x0001003a diff --git a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc index 56ea05c9288..baaac72d896 100644 --- a/MdeModulePkg/Test/MdeModulePkgHostTest.dsc +++ b/MdeModulePkg/Test/MdeModulePkgHostTest.dsc @@ -120,6 +120,13 @@ UefiBootServicesTableLib|MdePkg/Test/Mock/Library/GoogleTest/MockUefiBootServicesTableLib/MockUefiBootServicesTableLib.inf } + # MU_CHANGE BEGIN: PEI Bins + MdeModulePkg/Core/Dxe/Mem/GoogleTest/MemoryBinGoogleTestHost.inf { + + HobLib|MdePkg/Test/Mock/Library/GoogleTest/MockHobLib/MockHobLib.inf + } + # MU_CHANGE END: PEI Bins + # # Build HOST_APPLICATION Libraries #