diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem')
7 files changed, 7136 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/HeapGuard.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/HeapGuard.c new file mode 100644 index 00000000..4f74a512 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/HeapGuard.c @@ -0,0 +1,1746 @@ +/** @file + UEFI Heap Guard functions. + +Copyright (c) 2017-2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "DxeMain.h" +#include "Imem.h" +#include "HeapGuard.h" + +// +// Global to avoid infinite reentrance of memory allocation when updating +// page table attributes, which may need allocate pages for new PDE/PTE. +// +GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN mOnGuarding = FALSE; + +// +// Pointer to table tracking the Guarded memory with bitmap, in which '1' +// is used to indicate memory guarded. '0' might be free memory or Guard +// page itself, depending on status of memory adjacent to it. +// +GLOBAL_REMOVE_IF_UNREFERENCED UINT64 mGuardedMemoryMap = 0; + +// +// Current depth level of map table pointed by mGuardedMemoryMap. +// mMapLevel must be initialized at least by 1. It will be automatically +// updated according to the address of memory just tracked. +// +GLOBAL_REMOVE_IF_UNREFERENCED UINTN mMapLevel = 1; + +// +// Shift and mask for each level of map table +// +GLOBAL_REMOVE_IF_UNREFERENCED UINTN mLevelShift[GUARDED_HEAP_MAP_TABLE_DEPTH] + = GUARDED_HEAP_MAP_TABLE_DEPTH_SHIFTS; +GLOBAL_REMOVE_IF_UNREFERENCED UINTN mLevelMask[GUARDED_HEAP_MAP_TABLE_DEPTH] + = GUARDED_HEAP_MAP_TABLE_DEPTH_MASKS; + +// +// Used for promoting freed but not used pages. +// +GLOBAL_REMOVE_IF_UNREFERENCED EFI_PHYSICAL_ADDRESS mLastPromotedPage = BASE_4GB; + +/** + Set corresponding bits in bitmap table to 1 according to the address. + + @param[in] Address Start address to set for. + @param[in] BitNumber Number of bits to set. + @param[in] BitMap Pointer to bitmap which covers the Address. + + @return VOID. +**/ +STATIC +VOID +SetBits ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINTN BitNumber, + IN UINT64 *BitMap + ) +{ + UINTN Lsbs; + UINTN Qwords; + UINTN Msbs; + UINTN StartBit; + UINTN EndBit; + + StartBit = (UINTN)GUARDED_HEAP_MAP_ENTRY_BIT_INDEX (Address); + EndBit = (StartBit + BitNumber - 1) % GUARDED_HEAP_MAP_ENTRY_BITS; + + if ((StartBit + BitNumber) >= GUARDED_HEAP_MAP_ENTRY_BITS) { + Msbs = (GUARDED_HEAP_MAP_ENTRY_BITS - StartBit) % + GUARDED_HEAP_MAP_ENTRY_BITS; + Lsbs = (EndBit + 1) % GUARDED_HEAP_MAP_ENTRY_BITS; + Qwords = (BitNumber - Msbs) / GUARDED_HEAP_MAP_ENTRY_BITS; + } else { + Msbs = BitNumber; + Lsbs = 0; + Qwords = 0; + } + + if (Msbs > 0) { + *BitMap |= LShiftU64 (LShiftU64 (1, Msbs) - 1, StartBit); + BitMap += 1; + } + + if (Qwords > 0) { + SetMem64 ((VOID *)BitMap, Qwords * GUARDED_HEAP_MAP_ENTRY_BYTES, + (UINT64)-1); + BitMap += Qwords; + } + + if (Lsbs > 0) { + *BitMap |= (LShiftU64 (1, Lsbs) - 1); + } +} + +/** + Set corresponding bits in bitmap table to 0 according to the address. + + @param[in] Address Start address to set for. + @param[in] BitNumber Number of bits to set. + @param[in] BitMap Pointer to bitmap which covers the Address. + + @return VOID. +**/ +STATIC +VOID +ClearBits ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINTN BitNumber, + IN UINT64 *BitMap + ) +{ + UINTN Lsbs; + UINTN Qwords; + UINTN Msbs; + UINTN StartBit; + UINTN EndBit; + + StartBit = (UINTN)GUARDED_HEAP_MAP_ENTRY_BIT_INDEX (Address); + EndBit = (StartBit + BitNumber - 1) % GUARDED_HEAP_MAP_ENTRY_BITS; + + if ((StartBit + BitNumber) >= GUARDED_HEAP_MAP_ENTRY_BITS) { + Msbs = (GUARDED_HEAP_MAP_ENTRY_BITS - StartBit) % + GUARDED_HEAP_MAP_ENTRY_BITS; + Lsbs = (EndBit + 1) % GUARDED_HEAP_MAP_ENTRY_BITS; + Qwords = (BitNumber - Msbs) / GUARDED_HEAP_MAP_ENTRY_BITS; + } else { + Msbs = BitNumber; + Lsbs = 0; + Qwords = 0; + } + + if (Msbs > 0) { + *BitMap &= ~LShiftU64 (LShiftU64 (1, Msbs) - 1, StartBit); + BitMap += 1; + } + + if (Qwords > 0) { + SetMem64 ((VOID *)BitMap, Qwords * GUARDED_HEAP_MAP_ENTRY_BYTES, 0); + BitMap += Qwords; + } + + if (Lsbs > 0) { + *BitMap &= ~(LShiftU64 (1, Lsbs) - 1); + } +} + +/** + Get corresponding bits in bitmap table according to the address. + + The value of bit 0 corresponds to the status of memory at given Address. + No more than 64 bits can be retrieved in one call. + + @param[in] Address Start address to retrieve bits for. + @param[in] BitNumber Number of bits to get. + @param[in] BitMap Pointer to bitmap which covers the Address. + + @return An integer containing the bits information. +**/ +STATIC +UINT64 +GetBits ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINTN BitNumber, + IN UINT64 *BitMap + ) +{ + UINTN StartBit; + UINTN EndBit; + UINTN Lsbs; + UINTN Msbs; + UINT64 Result; + + ASSERT (BitNumber <= GUARDED_HEAP_MAP_ENTRY_BITS); + + StartBit = (UINTN)GUARDED_HEAP_MAP_ENTRY_BIT_INDEX (Address); + EndBit = (StartBit + BitNumber - 1) % GUARDED_HEAP_MAP_ENTRY_BITS; + + if ((StartBit + BitNumber) > GUARDED_HEAP_MAP_ENTRY_BITS) { + Msbs = GUARDED_HEAP_MAP_ENTRY_BITS - StartBit; + Lsbs = (EndBit + 1) % GUARDED_HEAP_MAP_ENTRY_BITS; + } else { + Msbs = BitNumber; + Lsbs = 0; + } + + if (StartBit == 0 && BitNumber == GUARDED_HEAP_MAP_ENTRY_BITS) { + Result = *BitMap; + } else { + Result = RShiftU64((*BitMap), StartBit) & (LShiftU64(1, Msbs) - 1); + if (Lsbs > 0) { + BitMap += 1; + Result |= LShiftU64 ((*BitMap) & (LShiftU64 (1, Lsbs) - 1), Msbs); + } + } + + return Result; +} + +/** + Locate the pointer of bitmap from the guarded memory bitmap tables, which + covers the given Address. + + @param[in] Address Start address to search the bitmap for. + @param[in] AllocMapUnit Flag to indicate memory allocation for the table. + @param[out] BitMap Pointer to bitmap which covers the Address. + + @return The bit number from given Address to the end of current map table. +**/ +UINTN +FindGuardedMemoryMap ( + IN EFI_PHYSICAL_ADDRESS Address, + IN BOOLEAN AllocMapUnit, + OUT UINT64 **BitMap + ) +{ + UINTN Level; + UINT64 *GuardMap; + UINT64 MapMemory; + UINTN Index; + UINTN Size; + UINTN BitsToUnitEnd; + EFI_STATUS Status; + + MapMemory = 0; + + // + // Adjust current map table depth according to the address to access + // + while (AllocMapUnit && + mMapLevel < GUARDED_HEAP_MAP_TABLE_DEPTH && + RShiftU64 ( + Address, + mLevelShift[GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel - 1] + ) != 0) { + + if (mGuardedMemoryMap != 0) { + Size = (mLevelMask[GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel - 1] + 1) + * GUARDED_HEAP_MAP_ENTRY_BYTES; + Status = CoreInternalAllocatePages ( + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES (Size), + &MapMemory, + FALSE + ); + ASSERT_EFI_ERROR (Status); + ASSERT (MapMemory != 0); + + SetMem ((VOID *)(UINTN)MapMemory, Size, 0); + + *(UINT64 *)(UINTN)MapMemory = mGuardedMemoryMap; + mGuardedMemoryMap = MapMemory; + } + + mMapLevel++; + + } + + GuardMap = &mGuardedMemoryMap; + for (Level = GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel; + Level < GUARDED_HEAP_MAP_TABLE_DEPTH; + ++Level) { + + if (*GuardMap == 0) { + if (!AllocMapUnit) { + GuardMap = NULL; + break; + } + + Size = (mLevelMask[Level] + 1) * GUARDED_HEAP_MAP_ENTRY_BYTES; + Status = CoreInternalAllocatePages ( + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES (Size), + &MapMemory, + FALSE + ); + ASSERT_EFI_ERROR (Status); + ASSERT (MapMemory != 0); + + SetMem ((VOID *)(UINTN)MapMemory, Size, 0); + *GuardMap = MapMemory; + } + + Index = (UINTN)RShiftU64 (Address, mLevelShift[Level]); + Index &= mLevelMask[Level]; + GuardMap = (UINT64 *)(UINTN)((*GuardMap) + Index * sizeof (UINT64)); + + } + + BitsToUnitEnd = GUARDED_HEAP_MAP_BITS - GUARDED_HEAP_MAP_BIT_INDEX (Address); + *BitMap = GuardMap; + + return BitsToUnitEnd; +} + +/** + Set corresponding bits in bitmap table to 1 according to given memory range. + + @param[in] Address Memory address to guard from. + @param[in] NumberOfPages Number of pages to guard. + + @return VOID. +**/ +VOID +EFIAPI +SetGuardedMemoryBits ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINTN NumberOfPages + ) +{ + UINT64 *BitMap; + UINTN Bits; + UINTN BitsToUnitEnd; + + while (NumberOfPages > 0) { + BitsToUnitEnd = FindGuardedMemoryMap (Address, TRUE, &BitMap); + ASSERT (BitMap != NULL); + + if (NumberOfPages > BitsToUnitEnd) { + // Cross map unit + Bits = BitsToUnitEnd; + } else { + Bits = NumberOfPages; + } + + SetBits (Address, Bits, BitMap); + + NumberOfPages -= Bits; + Address += EFI_PAGES_TO_SIZE (Bits); + } +} + +/** + Clear corresponding bits in bitmap table according to given memory range. + + @param[in] Address Memory address to unset from. + @param[in] NumberOfPages Number of pages to unset guard. + + @return VOID. +**/ +VOID +EFIAPI +ClearGuardedMemoryBits ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINTN NumberOfPages + ) +{ + UINT64 *BitMap; + UINTN Bits; + UINTN BitsToUnitEnd; + + while (NumberOfPages > 0) { + BitsToUnitEnd = FindGuardedMemoryMap (Address, TRUE, &BitMap); + ASSERT (BitMap != NULL); + + if (NumberOfPages > BitsToUnitEnd) { + // Cross map unit + Bits = BitsToUnitEnd; + } else { + Bits = NumberOfPages; + } + + ClearBits (Address, Bits, BitMap); + + NumberOfPages -= Bits; + Address += EFI_PAGES_TO_SIZE (Bits); + } +} + +/** + Retrieve corresponding bits in bitmap table according to given memory range. + + @param[in] Address Memory address to retrieve from. + @param[in] NumberOfPages Number of pages to retrieve. + + @return An integer containing the guarded memory bitmap. +**/ +UINT64 +GetGuardedMemoryBits ( + IN EFI_PHYSICAL_ADDRESS Address, + IN UINTN NumberOfPages + ) +{ + UINT64 *BitMap; + UINTN Bits; + UINT64 Result; + UINTN Shift; + UINTN BitsToUnitEnd; + + ASSERT (NumberOfPages <= GUARDED_HEAP_MAP_ENTRY_BITS); + + Result = 0; + Shift = 0; + while (NumberOfPages > 0) { + BitsToUnitEnd = FindGuardedMemoryMap (Address, FALSE, &BitMap); + + if (NumberOfPages > BitsToUnitEnd) { + // Cross map unit + Bits = BitsToUnitEnd; + } else { + Bits = NumberOfPages; + } + + if (BitMap != NULL) { + Result |= LShiftU64 (GetBits (Address, Bits, BitMap), Shift); + } + + Shift += Bits; + NumberOfPages -= Bits; + Address += EFI_PAGES_TO_SIZE (Bits); + } + + return Result; +} + +/** + Get bit value in bitmap table for the given address. + + @param[in] Address The address to retrieve for. + + @return 1 or 0. +**/ +UINTN +EFIAPI +GetGuardMapBit ( + IN EFI_PHYSICAL_ADDRESS Address + ) +{ + UINT64 *GuardMap; + + FindGuardedMemoryMap (Address, FALSE, &GuardMap); + if (GuardMap != NULL) { + if (RShiftU64 (*GuardMap, + GUARDED_HEAP_MAP_ENTRY_BIT_INDEX (Address)) & 1) { + return 1; + } + } + + return 0; +} + + +/** + Check to see if the page at the given address is a Guard page or not. + + @param[in] Address The address to check for. + + @return TRUE The page at Address is a Guard page. + @return FALSE The page at Address is not a Guard page. +**/ +BOOLEAN +EFIAPI +IsGuardPage ( + IN EFI_PHYSICAL_ADDRESS Address + ) +{ + UINT64 BitMap; + + // + // There must be at least one guarded page before and/or after given + // address if it's a Guard page. The bitmap pattern should be one of + // 001, 100 and 101 + // + BitMap = GetGuardedMemoryBits (Address - EFI_PAGE_SIZE, 3); + return ((BitMap == BIT0) || (BitMap == BIT2) || (BitMap == (BIT2 | BIT0))); +} + + +/** + Check to see if the page at the given address is guarded or not. + + @param[in] Address The address to check for. + + @return TRUE The page at Address is guarded. + @return FALSE The page at Address is not guarded. +**/ +BOOLEAN +EFIAPI +IsMemoryGuarded ( + IN EFI_PHYSICAL_ADDRESS Address + ) +{ + return (GetGuardMapBit (Address) == 1); +} + +/** + Set the page at the given address to be a Guard page. + + This is done by changing the page table attribute to be NOT PRSENT. + + @param[in] BaseAddress Page address to Guard at + + @return VOID +**/ +VOID +EFIAPI +SetGuardPage ( + IN EFI_PHYSICAL_ADDRESS BaseAddress + ) +{ + EFI_STATUS Status; + + if (gCpu == NULL) { + return; + } + + // + // Set flag to make sure allocating memory without GUARD for page table + // operation; otherwise infinite loops could be caused. + // + mOnGuarding = TRUE; + // + // Note: This might overwrite other attributes needed by other features, + // such as NX memory protection. + // + Status = gCpu->SetMemoryAttributes (gCpu, BaseAddress, EFI_PAGE_SIZE, EFI_MEMORY_RP); + ASSERT_EFI_ERROR (Status); + mOnGuarding = FALSE; +} + +/** + Unset the Guard page at the given address to the normal memory. + + This is done by changing the page table attribute to be PRSENT. + + @param[in] BaseAddress Page address to Guard at. + + @return VOID. +**/ +VOID +EFIAPI +UnsetGuardPage ( + IN EFI_PHYSICAL_ADDRESS BaseAddress + ) +{ + UINT64 Attributes; + EFI_STATUS Status; + + if (gCpu == NULL) { + return; + } + + // + // Once the Guard page is unset, it will be freed back to memory pool. NX + // memory protection must be restored for this page if NX is enabled for free + // memory. + // + Attributes = 0; + if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & (1 << EfiConventionalMemory)) != 0) { + Attributes |= EFI_MEMORY_XP; + } + + // + // Set flag to make sure allocating memory without GUARD for page table + // operation; otherwise infinite loops could be caused. + // + mOnGuarding = TRUE; + // + // Note: This might overwrite other attributes needed by other features, + // such as memory protection (NX). Please make sure they are not enabled + // at the same time. + // + Status = gCpu->SetMemoryAttributes (gCpu, BaseAddress, EFI_PAGE_SIZE, Attributes); + ASSERT_EFI_ERROR (Status); + mOnGuarding = FALSE; +} + +/** + Check to see if the memory at the given address should be guarded or not. + + @param[in] MemoryType Memory type to check. + @param[in] AllocateType Allocation type to check. + @param[in] PageOrPool Indicate a page allocation or pool allocation. + + + @return TRUE The given type of memory should be guarded. + @return FALSE The given type of memory should not be guarded. +**/ +BOOLEAN +IsMemoryTypeToGuard ( + IN EFI_MEMORY_TYPE MemoryType, + IN EFI_ALLOCATE_TYPE AllocateType, + IN UINT8 PageOrPool + ) +{ + UINT64 TestBit; + UINT64 ConfigBit; + + if (AllocateType == AllocateAddress) { + return FALSE; + } + + if ((PcdGet8 (PcdHeapGuardPropertyMask) & PageOrPool) == 0) { + return FALSE; + } + + if (PageOrPool == GUARD_HEAP_TYPE_POOL) { + ConfigBit = PcdGet64 (PcdHeapGuardPoolType); + } else if (PageOrPool == GUARD_HEAP_TYPE_PAGE) { + ConfigBit = PcdGet64 (PcdHeapGuardPageType); + } else { + ConfigBit = (UINT64)-1; + } + + if ((UINT32)MemoryType >= MEMORY_TYPE_OS_RESERVED_MIN) { + TestBit = BIT63; + } else if ((UINT32) MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) { + TestBit = BIT62; + } else if (MemoryType < EfiMaxMemoryType) { + TestBit = LShiftU64 (1, MemoryType); + } else if (MemoryType == EfiMaxMemoryType) { + TestBit = (UINT64)-1; + } else { + TestBit = 0; + } + + return ((ConfigBit & TestBit) != 0); +} + +/** + Check to see if the pool at the given address should be guarded or not. + + @param[in] MemoryType Pool type to check. + + + @return TRUE The given type of pool should be guarded. + @return FALSE The given type of pool should not be guarded. +**/ +BOOLEAN +IsPoolTypeToGuard ( + IN EFI_MEMORY_TYPE MemoryType + ) +{ + return IsMemoryTypeToGuard (MemoryType, AllocateAnyPages, + GUARD_HEAP_TYPE_POOL); +} + +/** + Check to see if the page at the given address should be guarded or not. + + @param[in] MemoryType Page type to check. + @param[in] AllocateType Allocation type to check. + + @return TRUE The given type of page should be guarded. + @return FALSE The given type of page should not be guarded. +**/ +BOOLEAN +IsPageTypeToGuard ( + IN EFI_MEMORY_TYPE MemoryType, + IN EFI_ALLOCATE_TYPE AllocateType + ) +{ + return IsMemoryTypeToGuard (MemoryType, AllocateType, GUARD_HEAP_TYPE_PAGE); +} + +/** + Check to see if the heap guard is enabled for page and/or pool allocation. + + @param[in] GuardType Specify the sub-type(s) of Heap Guard. + + @return TRUE/FALSE. +**/ +BOOLEAN +IsHeapGuardEnabled ( + UINT8 GuardType + ) +{ + return IsMemoryTypeToGuard (EfiMaxMemoryType, AllocateAnyPages, GuardType); +} + +/** + Set head Guard and tail Guard for the given memory range. + + @param[in] Memory Base address of memory to set guard for. + @param[in] NumberOfPages Memory size in pages. + + @return VOID +**/ +VOID +SetGuardForMemory ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ) +{ + EFI_PHYSICAL_ADDRESS GuardPage; + + // + // Set tail Guard + // + GuardPage = Memory + EFI_PAGES_TO_SIZE (NumberOfPages); + if (!IsGuardPage (GuardPage)) { + SetGuardPage (GuardPage); + } + + // Set head Guard + GuardPage = Memory - EFI_PAGES_TO_SIZE (1); + if (!IsGuardPage (GuardPage)) { + SetGuardPage (GuardPage); + } + + // + // Mark the memory range as Guarded + // + SetGuardedMemoryBits (Memory, NumberOfPages); +} + +/** + Unset head Guard and tail Guard for the given memory range. + + @param[in] Memory Base address of memory to unset guard for. + @param[in] NumberOfPages Memory size in pages. + + @return VOID +**/ +VOID +UnsetGuardForMemory ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ) +{ + EFI_PHYSICAL_ADDRESS GuardPage; + UINT64 GuardBitmap; + + if (NumberOfPages == 0) { + return; + } + + // + // Head Guard must be one page before, if any. + // + // MSB-> 1 0 <-LSB + // ------------------- + // Head Guard -> 0 1 -> Don't free Head Guard (shared Guard) + // Head Guard -> 0 0 -> Free Head Guard either (not shared Guard) + // 1 X -> Don't free first page (need a new Guard) + // (it'll be turned into a Guard page later) + // ------------------- + // Start -> -1 -2 + // + GuardPage = Memory - EFI_PAGES_TO_SIZE (1); + GuardBitmap = GetGuardedMemoryBits (Memory - EFI_PAGES_TO_SIZE (2), 2); + if ((GuardBitmap & BIT1) == 0) { + // + // Head Guard exists. + // + if ((GuardBitmap & BIT0) == 0) { + // + // If the head Guard is not a tail Guard of adjacent memory block, + // unset it. + // + UnsetGuardPage (GuardPage); + } + } else { + // + // Pages before memory to free are still in Guard. It's a partial free + // case. Turn first page of memory block to free into a new Guard. + // + SetGuardPage (Memory); + } + + // + // Tail Guard must be the page after this memory block to free, if any. + // + // MSB-> 1 0 <-LSB + // -------------------- + // 1 0 <- Tail Guard -> Don't free Tail Guard (shared Guard) + // 0 0 <- Tail Guard -> Free Tail Guard either (not shared Guard) + // X 1 -> Don't free last page (need a new Guard) + // (it'll be turned into a Guard page later) + // -------------------- + // +1 +0 <- End + // + GuardPage = Memory + EFI_PAGES_TO_SIZE (NumberOfPages); + GuardBitmap = GetGuardedMemoryBits (GuardPage, 2); + if ((GuardBitmap & BIT0) == 0) { + // + // Tail Guard exists. + // + if ((GuardBitmap & BIT1) == 0) { + // + // If the tail Guard is not a head Guard of adjacent memory block, + // free it; otherwise, keep it. + // + UnsetGuardPage (GuardPage); + } + } else { + // + // Pages after memory to free are still in Guard. It's a partial free + // case. We need to keep one page to be a head Guard. + // + SetGuardPage (GuardPage - EFI_PAGES_TO_SIZE (1)); + } + + // + // No matter what, we just clear the mark of the Guarded memory. + // + ClearGuardedMemoryBits(Memory, NumberOfPages); +} + +/** + Adjust address of free memory according to existing and/or required Guard. + + This function will check if there're existing Guard pages of adjacent + memory blocks, and try to use it as the Guard page of the memory to be + allocated. + + @param[in] Start Start address of free memory block. + @param[in] Size Size of free memory block. + @param[in] SizeRequested Size of memory to allocate. + + @return The end address of memory block found. + @return 0 if no enough space for the required size of memory and its Guard. +**/ +UINT64 +AdjustMemoryS ( + IN UINT64 Start, + IN UINT64 Size, + IN UINT64 SizeRequested + ) +{ + UINT64 Target; + + // + // UEFI spec requires that allocated pool must be 8-byte aligned. If it's + // indicated to put the pool near the Tail Guard, we need extra bytes to + // make sure alignment of the returned pool address. + // + if ((PcdGet8 (PcdHeapGuardPropertyMask) & BIT7) == 0) { + SizeRequested = ALIGN_VALUE(SizeRequested, 8); + } + + Target = Start + Size - SizeRequested; + ASSERT (Target >= Start); + if (Target == 0) { + return 0; + } + + if (!IsGuardPage (Start + Size)) { + // No Guard at tail to share. One more page is needed. + Target -= EFI_PAGES_TO_SIZE (1); + } + + // Out of range? + if (Target < Start) { + return 0; + } + + // At the edge? + if (Target == Start) { + if (!IsGuardPage (Target - EFI_PAGES_TO_SIZE (1))) { + // No enough space for a new head Guard if no Guard at head to share. + return 0; + } + } + + // OK, we have enough pages for memory and its Guards. Return the End of the + // free space. + return Target + SizeRequested - 1; +} + +/** + Adjust the start address and number of pages to free according to Guard. + + The purpose of this function is to keep the shared Guard page with adjacent + memory block if it's still in guard, or free it if no more sharing. Another + is to reserve pages as Guard pages in partial page free situation. + + @param[in,out] Memory Base address of memory to free. + @param[in,out] NumberOfPages Size of memory to free. + + @return VOID. +**/ +VOID +AdjustMemoryF ( + IN OUT EFI_PHYSICAL_ADDRESS *Memory, + IN OUT UINTN *NumberOfPages + ) +{ + EFI_PHYSICAL_ADDRESS Start; + EFI_PHYSICAL_ADDRESS MemoryToTest; + UINTN PagesToFree; + UINT64 GuardBitmap; + + if (Memory == NULL || NumberOfPages == NULL || *NumberOfPages == 0) { + return; + } + + Start = *Memory; + PagesToFree = *NumberOfPages; + + // + // Head Guard must be one page before, if any. + // + // MSB-> 1 0 <-LSB + // ------------------- + // Head Guard -> 0 1 -> Don't free Head Guard (shared Guard) + // Head Guard -> 0 0 -> Free Head Guard either (not shared Guard) + // 1 X -> Don't free first page (need a new Guard) + // (it'll be turned into a Guard page later) + // ------------------- + // Start -> -1 -2 + // + MemoryToTest = Start - EFI_PAGES_TO_SIZE (2); + GuardBitmap = GetGuardedMemoryBits (MemoryToTest, 2); + if ((GuardBitmap & BIT1) == 0) { + // + // Head Guard exists. + // + if ((GuardBitmap & BIT0) == 0) { + // + // If the head Guard is not a tail Guard of adjacent memory block, + // free it; otherwise, keep it. + // + Start -= EFI_PAGES_TO_SIZE (1); + PagesToFree += 1; + } + } else { + // + // No Head Guard, and pages before memory to free are still in Guard. It's a + // partial free case. We need to keep one page to be a tail Guard. + // + Start += EFI_PAGES_TO_SIZE (1); + PagesToFree -= 1; + } + + // + // Tail Guard must be the page after this memory block to free, if any. + // + // MSB-> 1 0 <-LSB + // -------------------- + // 1 0 <- Tail Guard -> Don't free Tail Guard (shared Guard) + // 0 0 <- Tail Guard -> Free Tail Guard either (not shared Guard) + // X 1 -> Don't free last page (need a new Guard) + // (it'll be turned into a Guard page later) + // -------------------- + // +1 +0 <- End + // + MemoryToTest = Start + EFI_PAGES_TO_SIZE (PagesToFree); + GuardBitmap = GetGuardedMemoryBits (MemoryToTest, 2); + if ((GuardBitmap & BIT0) == 0) { + // + // Tail Guard exists. + // + if ((GuardBitmap & BIT1) == 0) { + // + // If the tail Guard is not a head Guard of adjacent memory block, + // free it; otherwise, keep it. + // + PagesToFree += 1; + } + } else if (PagesToFree > 0) { + // + // No Tail Guard, and pages after memory to free are still in Guard. It's a + // partial free case. We need to keep one page to be a head Guard. + // + PagesToFree -= 1; + } + + *Memory = Start; + *NumberOfPages = PagesToFree; +} + +/** + Adjust the base and number of pages to really allocate according to Guard. + + @param[in,out] Memory Base address of free memory. + @param[in,out] NumberOfPages Size of memory to allocate. + + @return VOID. +**/ +VOID +AdjustMemoryA ( + IN OUT EFI_PHYSICAL_ADDRESS *Memory, + IN OUT UINTN *NumberOfPages + ) +{ + // + // FindFreePages() has already taken the Guard into account. It's safe to + // adjust the start address and/or number of pages here, to make sure that + // the Guards are also "allocated". + // + if (!IsGuardPage (*Memory + EFI_PAGES_TO_SIZE (*NumberOfPages))) { + // No tail Guard, add one. + *NumberOfPages += 1; + } + + if (!IsGuardPage (*Memory - EFI_PAGE_SIZE)) { + // No head Guard, add one. + *Memory -= EFI_PAGE_SIZE; + *NumberOfPages += 1; + } +} + +/** + Adjust the pool head position to make sure the Guard page is adjavent to + pool tail or pool head. + + @param[in] Memory Base address of memory allocated. + @param[in] NoPages Number of pages actually allocated. + @param[in] Size Size of memory requested. + (plus pool head/tail overhead) + + @return Address of pool head. +**/ +VOID * +AdjustPoolHeadA ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NoPages, + IN UINTN Size + ) +{ + if (Memory == 0 || (PcdGet8 (PcdHeapGuardPropertyMask) & BIT7) != 0) { + // + // Pool head is put near the head Guard + // + return (VOID *)(UINTN)Memory; + } + + // + // Pool head is put near the tail Guard + // + Size = ALIGN_VALUE (Size, 8); + return (VOID *)(UINTN)(Memory + EFI_PAGES_TO_SIZE (NoPages) - Size); +} + +/** + Get the page base address according to pool head address. + + @param[in] Memory Head address of pool to free. + + @return Address of pool head. +**/ +VOID * +AdjustPoolHeadF ( + IN EFI_PHYSICAL_ADDRESS Memory + ) +{ + if (Memory == 0 || (PcdGet8 (PcdHeapGuardPropertyMask) & BIT7) != 0) { + // + // Pool head is put near the head Guard + // + return (VOID *)(UINTN)Memory; + } + + // + // Pool head is put near the tail Guard + // + return (VOID *)(UINTN)(Memory & ~EFI_PAGE_MASK); +} + +/** + Allocate or free guarded memory. + + @param[in] Start Start address of memory to allocate or free. + @param[in] NumberOfPages Memory size in pages. + @param[in] NewType Memory type to convert to. + + @return VOID. +**/ +EFI_STATUS +CoreConvertPagesWithGuard ( + IN UINT64 Start, + IN UINTN NumberOfPages, + IN EFI_MEMORY_TYPE NewType + ) +{ + UINT64 OldStart; + UINTN OldPages; + + if (NewType == EfiConventionalMemory) { + OldStart = Start; + OldPages = NumberOfPages; + + AdjustMemoryF (&Start, &NumberOfPages); + // + // It's safe to unset Guard page inside memory lock because there should + // be no memory allocation occurred in updating memory page attribute at + // this point. And unsetting Guard page before free will prevent Guard + // page just freed back to pool from being allocated right away before + // marking it usable (from non-present to present). + // + UnsetGuardForMemory (OldStart, OldPages); + if (NumberOfPages == 0) { + return EFI_SUCCESS; + } + } else { + AdjustMemoryA (&Start, &NumberOfPages); + } + + return CoreConvertPages (Start, NumberOfPages, NewType); +} + +/** + Set all Guard pages which cannot be set before CPU Arch Protocol installed. +**/ +VOID +SetAllGuardPages ( + VOID + ) +{ + UINTN Entries[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINTN Shifts[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINTN Indices[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 Tables[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 Addresses[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 TableEntry; + UINT64 Address; + UINT64 GuardPage; + INTN Level; + UINTN Index; + BOOLEAN OnGuarding; + + if (mGuardedMemoryMap == 0 || + mMapLevel == 0 || + mMapLevel > GUARDED_HEAP_MAP_TABLE_DEPTH) { + return; + } + + CopyMem (Entries, mLevelMask, sizeof (Entries)); + CopyMem (Shifts, mLevelShift, sizeof (Shifts)); + + SetMem (Tables, sizeof(Tables), 0); + SetMem (Addresses, sizeof(Addresses), 0); + SetMem (Indices, sizeof(Indices), 0); + + Level = GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel; + Tables[Level] = mGuardedMemoryMap; + Address = 0; + OnGuarding = FALSE; + + DEBUG_CODE ( + DumpGuardedMemoryBitmap (); + ); + + while (TRUE) { + if (Indices[Level] > Entries[Level]) { + Tables[Level] = 0; + Level -= 1; + } else { + + TableEntry = ((UINT64 *)(UINTN)(Tables[Level]))[Indices[Level]]; + Address = Addresses[Level]; + + if (TableEntry == 0) { + + OnGuarding = FALSE; + + } else if (Level < GUARDED_HEAP_MAP_TABLE_DEPTH - 1) { + + Level += 1; + Tables[Level] = TableEntry; + Addresses[Level] = Address; + Indices[Level] = 0; + + continue; + + } else { + + Index = 0; + while (Index < GUARDED_HEAP_MAP_ENTRY_BITS) { + if ((TableEntry & 1) == 1) { + if (OnGuarding) { + GuardPage = 0; + } else { + GuardPage = Address - EFI_PAGE_SIZE; + } + OnGuarding = TRUE; + } else { + if (OnGuarding) { + GuardPage = Address; + } else { + GuardPage = 0; + } + OnGuarding = FALSE; + } + + if (GuardPage != 0) { + SetGuardPage (GuardPage); + } + + if (TableEntry == 0) { + break; + } + + TableEntry = RShiftU64 (TableEntry, 1); + Address += EFI_PAGE_SIZE; + Index += 1; + } + } + } + + if (Level < (GUARDED_HEAP_MAP_TABLE_DEPTH - (INTN)mMapLevel)) { + break; + } + + Indices[Level] += 1; + Address = (Level == 0) ? 0 : Addresses[Level - 1]; + Addresses[Level] = Address | LShiftU64(Indices[Level], Shifts[Level]); + + } +} + +/** + Find the address of top-most guarded free page. + + @param[out] Address Start address of top-most guarded free page. + + @return VOID. +**/ +VOID +GetLastGuardedFreePageAddress ( + OUT EFI_PHYSICAL_ADDRESS *Address + ) +{ + EFI_PHYSICAL_ADDRESS AddressGranularity; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINTN Level; + UINT64 Map; + INTN Index; + + ASSERT (mMapLevel >= 1); + + BaseAddress = 0; + Map = mGuardedMemoryMap; + for (Level = GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel; + Level < GUARDED_HEAP_MAP_TABLE_DEPTH; + ++Level) { + AddressGranularity = LShiftU64 (1, mLevelShift[Level]); + + // + // Find the non-NULL entry at largest index. + // + for (Index = (INTN)mLevelMask[Level]; Index >= 0 ; --Index) { + if (((UINT64 *)(UINTN)Map)[Index] != 0) { + BaseAddress += MultU64x32 (AddressGranularity, (UINT32)Index); + Map = ((UINT64 *)(UINTN)Map)[Index]; + break; + } + } + } + + // + // Find the non-zero MSB then get the page address. + // + while (Map != 0) { + Map = RShiftU64 (Map, 1); + BaseAddress += EFI_PAGES_TO_SIZE (1); + } + + *Address = BaseAddress; +} + +/** + Record freed pages. + + @param[in] BaseAddress Base address of just freed pages. + @param[in] Pages Number of freed pages. + + @return VOID. +**/ +VOID +MarkFreedPages ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINTN Pages + ) +{ + SetGuardedMemoryBits (BaseAddress, Pages); +} + +/** + Record freed pages as well as mark them as not-present. + + @param[in] BaseAddress Base address of just freed pages. + @param[in] Pages Number of freed pages. + + @return VOID. +**/ +VOID +EFIAPI +GuardFreedPages ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINTN Pages + ) +{ + EFI_STATUS Status; + + // + // Legacy memory lower than 1MB might be accessed with no allocation. Leave + // them alone. + // + if (BaseAddress < BASE_1MB) { + return; + } + + MarkFreedPages (BaseAddress, Pages); + if (gCpu != NULL) { + // + // Set flag to make sure allocating memory without GUARD for page table + // operation; otherwise infinite loops could be caused. + // + mOnGuarding = TRUE; + // + // Note: This might overwrite other attributes needed by other features, + // such as NX memory protection. + // + Status = gCpu->SetMemoryAttributes ( + gCpu, + BaseAddress, + EFI_PAGES_TO_SIZE (Pages), + EFI_MEMORY_RP + ); + // + // Normally we should ASSERT the returned Status. But there might be memory + // alloc/free involved in SetMemoryAttributes(), which might fail this + // calling. It's rare case so it's OK to let a few tiny holes be not-guarded. + // + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "Failed to guard freed pages: %p (%lu)\n", BaseAddress, (UINT64)Pages)); + } + mOnGuarding = FALSE; + } +} + +/** + Record freed pages as well as mark them as not-present, if enabled. + + @param[in] BaseAddress Base address of just freed pages. + @param[in] Pages Number of freed pages. + + @return VOID. +**/ +VOID +EFIAPI +GuardFreedPagesChecked ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINTN Pages + ) +{ + if (IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED)) { + GuardFreedPages (BaseAddress, Pages); + } +} + +/** + Mark all pages freed before CPU Arch Protocol as not-present. + +**/ +VOID +GuardAllFreedPages ( + VOID + ) +{ + UINTN Entries[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINTN Shifts[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINTN Indices[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 Tables[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 Addresses[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 TableEntry; + UINT64 Address; + UINT64 GuardPage; + INTN Level; + UINT64 BitIndex; + UINTN GuardPageNumber; + + if (mGuardedMemoryMap == 0 || + mMapLevel == 0 || + mMapLevel > GUARDED_HEAP_MAP_TABLE_DEPTH) { + return; + } + + CopyMem (Entries, mLevelMask, sizeof (Entries)); + CopyMem (Shifts, mLevelShift, sizeof (Shifts)); + + SetMem (Tables, sizeof(Tables), 0); + SetMem (Addresses, sizeof(Addresses), 0); + SetMem (Indices, sizeof(Indices), 0); + + Level = GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel; + Tables[Level] = mGuardedMemoryMap; + Address = 0; + GuardPage = (UINT64)-1; + GuardPageNumber = 0; + + while (TRUE) { + if (Indices[Level] > Entries[Level]) { + Tables[Level] = 0; + Level -= 1; + } else { + TableEntry = ((UINT64 *)(UINTN)(Tables[Level]))[Indices[Level]]; + Address = Addresses[Level]; + + if (Level < GUARDED_HEAP_MAP_TABLE_DEPTH - 1) { + Level += 1; + Tables[Level] = TableEntry; + Addresses[Level] = Address; + Indices[Level] = 0; + + continue; + } else { + BitIndex = 1; + while (BitIndex != 0) { + if ((TableEntry & BitIndex) != 0) { + if (GuardPage == (UINT64)-1) { + GuardPage = Address; + } + ++GuardPageNumber; + } else if (GuardPageNumber > 0) { + GuardFreedPages (GuardPage, GuardPageNumber); + GuardPageNumber = 0; + GuardPage = (UINT64)-1; + } + + if (TableEntry == 0) { + break; + } + + Address += EFI_PAGES_TO_SIZE (1); + BitIndex = LShiftU64 (BitIndex, 1); + } + } + } + + if (Level < (GUARDED_HEAP_MAP_TABLE_DEPTH - (INTN)mMapLevel)) { + break; + } + + Indices[Level] += 1; + Address = (Level == 0) ? 0 : Addresses[Level - 1]; + Addresses[Level] = Address | LShiftU64 (Indices[Level], Shifts[Level]); + + } + + // + // Update the maximum address of freed page which can be used for memory + // promotion upon out-of-memory-space. + // + GetLastGuardedFreePageAddress (&Address); + if (Address != 0) { + mLastPromotedPage = Address; + } +} + +/** + This function checks to see if the given memory map descriptor in a memory map + can be merged with any guarded free pages. + + @param MemoryMapEntry A pointer to a descriptor in MemoryMap. + @param MaxAddress Maximum address to stop the merge. + + @return VOID + +**/ +VOID +MergeGuardPages ( + IN EFI_MEMORY_DESCRIPTOR *MemoryMapEntry, + IN EFI_PHYSICAL_ADDRESS MaxAddress + ) +{ + EFI_PHYSICAL_ADDRESS EndAddress; + UINT64 Bitmap; + INTN Pages; + + if (!IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED) || + MemoryMapEntry->Type >= EfiMemoryMappedIO) { + return; + } + + Bitmap = 0; + Pages = EFI_SIZE_TO_PAGES ((UINTN)(MaxAddress - MemoryMapEntry->PhysicalStart)); + Pages -= (INTN)MemoryMapEntry->NumberOfPages; + while (Pages > 0) { + if (Bitmap == 0) { + EndAddress = MemoryMapEntry->PhysicalStart + + EFI_PAGES_TO_SIZE ((UINTN)MemoryMapEntry->NumberOfPages); + Bitmap = GetGuardedMemoryBits (EndAddress, GUARDED_HEAP_MAP_ENTRY_BITS); + } + + if ((Bitmap & 1) == 0) { + break; + } + + Pages--; + MemoryMapEntry->NumberOfPages++; + Bitmap = RShiftU64 (Bitmap, 1); + } +} + +/** + Put part (at most 64 pages a time) guarded free pages back to free page pool. + + Freed memory guard is used to detect Use-After-Free (UAF) memory issue, which + makes use of 'Used then throw away' way to detect any illegal access to freed + memory. The thrown-away memory will be marked as not-present so that any access + to those memory (after free) will be caught by page-fault exception. + + The problem is that this will consume lots of memory space. Once no memory + left in pool to allocate, we have to restore part of the freed pages to their + normal function. Otherwise the whole system will stop functioning. + + @param StartAddress Start address of promoted memory. + @param EndAddress End address of promoted memory. + + @return TRUE Succeeded to promote memory. + @return FALSE No free memory found. + +**/ +BOOLEAN +PromoteGuardedFreePages ( + OUT EFI_PHYSICAL_ADDRESS *StartAddress, + OUT EFI_PHYSICAL_ADDRESS *EndAddress + ) +{ + EFI_STATUS Status; + UINTN AvailablePages; + UINT64 Bitmap; + EFI_PHYSICAL_ADDRESS Start; + + if (!IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED)) { + return FALSE; + } + + // + // Similar to memory allocation service, always search the freed pages in + // descending direction. + // + Start = mLastPromotedPage; + AvailablePages = 0; + while (AvailablePages == 0) { + Start -= EFI_PAGES_TO_SIZE (GUARDED_HEAP_MAP_ENTRY_BITS); + // + // If the address wraps around, try the really freed pages at top. + // + if (Start > mLastPromotedPage) { + GetLastGuardedFreePageAddress (&Start); + ASSERT (Start != 0); + Start -= EFI_PAGES_TO_SIZE (GUARDED_HEAP_MAP_ENTRY_BITS); + } + + Bitmap = GetGuardedMemoryBits (Start, GUARDED_HEAP_MAP_ENTRY_BITS); + while (Bitmap > 0) { + if ((Bitmap & 1) != 0) { + ++AvailablePages; + } else if (AvailablePages == 0) { + Start += EFI_PAGES_TO_SIZE (1); + } else { + break; + } + + Bitmap = RShiftU64 (Bitmap, 1); + } + } + + if (AvailablePages != 0) { + DEBUG ((DEBUG_INFO, "Promoted pages: %lX (%lx)\r\n", Start, (UINT64)AvailablePages)); + ClearGuardedMemoryBits (Start, AvailablePages); + + if (gCpu != NULL) { + // + // Set flag to make sure allocating memory without GUARD for page table + // operation; otherwise infinite loops could be caused. + // + mOnGuarding = TRUE; + Status = gCpu->SetMemoryAttributes (gCpu, Start, EFI_PAGES_TO_SIZE(AvailablePages), 0); + ASSERT_EFI_ERROR (Status); + mOnGuarding = FALSE; + } + + mLastPromotedPage = Start; + *StartAddress = Start; + *EndAddress = Start + EFI_PAGES_TO_SIZE (AvailablePages) - 1; + return TRUE; + } + + return FALSE; +} + +/** + Notify function used to set all Guard pages before CPU Arch Protocol installed. +**/ +VOID +HeapGuardCpuArchProtocolNotify ( + VOID + ) +{ + ASSERT (gCpu != NULL); + + if (IsHeapGuardEnabled (GUARD_HEAP_TYPE_PAGE|GUARD_HEAP_TYPE_POOL) && + IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED)) { + DEBUG ((DEBUG_ERROR, "Heap guard and freed memory guard cannot be enabled at the same time.\n")); + CpuDeadLoop (); + } + + if (IsHeapGuardEnabled (GUARD_HEAP_TYPE_PAGE|GUARD_HEAP_TYPE_POOL)) { + SetAllGuardPages (); + } + + if (IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED)) { + GuardAllFreedPages (); + } +} + +/** + Helper function to convert a UINT64 value in binary to a string. + + @param[in] Value Value of a UINT64 integer. + @param[out] BinString String buffer to contain the conversion result. + + @return VOID. +**/ +VOID +Uint64ToBinString ( + IN UINT64 Value, + OUT CHAR8 *BinString + ) +{ + UINTN Index; + + if (BinString == NULL) { + return; + } + + for (Index = 64; Index > 0; --Index) { + BinString[Index - 1] = '0' + (Value & 1); + Value = RShiftU64 (Value, 1); + } + BinString[64] = '\0'; +} + +/** + Dump the guarded memory bit map. +**/ +VOID +EFIAPI +DumpGuardedMemoryBitmap ( + VOID + ) +{ + UINTN Entries[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINTN Shifts[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINTN Indices[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 Tables[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 Addresses[GUARDED_HEAP_MAP_TABLE_DEPTH]; + UINT64 TableEntry; + UINT64 Address; + INTN Level; + UINTN RepeatZero; + CHAR8 String[GUARDED_HEAP_MAP_ENTRY_BITS + 1]; + CHAR8 *Ruler1; + CHAR8 *Ruler2; + + if (!IsHeapGuardEnabled (GUARD_HEAP_TYPE_ALL)) { + return; + } + + if (mGuardedMemoryMap == 0 || + mMapLevel == 0 || + mMapLevel > GUARDED_HEAP_MAP_TABLE_DEPTH) { + return; + } + + Ruler1 = " 3 2 1 0"; + Ruler2 = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"; + + DEBUG ((HEAP_GUARD_DEBUG_LEVEL, "=============================" + " Guarded Memory Bitmap " + "==============================\r\n")); + DEBUG ((HEAP_GUARD_DEBUG_LEVEL, " %a\r\n", Ruler1)); + DEBUG ((HEAP_GUARD_DEBUG_LEVEL, " %a\r\n", Ruler2)); + + CopyMem (Entries, mLevelMask, sizeof (Entries)); + CopyMem (Shifts, mLevelShift, sizeof (Shifts)); + + SetMem (Indices, sizeof(Indices), 0); + SetMem (Tables, sizeof(Tables), 0); + SetMem (Addresses, sizeof(Addresses), 0); + + Level = GUARDED_HEAP_MAP_TABLE_DEPTH - mMapLevel; + Tables[Level] = mGuardedMemoryMap; + Address = 0; + RepeatZero = 0; + + while (TRUE) { + if (Indices[Level] > Entries[Level]) { + + Tables[Level] = 0; + Level -= 1; + RepeatZero = 0; + + DEBUG (( + HEAP_GUARD_DEBUG_LEVEL, + "=========================================" + "=========================================\r\n" + )); + + } else { + + TableEntry = ((UINT64 *)(UINTN)Tables[Level])[Indices[Level]]; + Address = Addresses[Level]; + + if (TableEntry == 0) { + + if (Level == GUARDED_HEAP_MAP_TABLE_DEPTH - 1) { + if (RepeatZero == 0) { + Uint64ToBinString(TableEntry, String); + DEBUG ((HEAP_GUARD_DEBUG_LEVEL, "%016lx: %a\r\n", Address, String)); + } else if (RepeatZero == 1) { + DEBUG ((HEAP_GUARD_DEBUG_LEVEL, "... : ...\r\n")); + } + RepeatZero += 1; + } + + } else if (Level < GUARDED_HEAP_MAP_TABLE_DEPTH - 1) { + + Level += 1; + Tables[Level] = TableEntry; + Addresses[Level] = Address; + Indices[Level] = 0; + RepeatZero = 0; + + continue; + + } else { + + RepeatZero = 0; + Uint64ToBinString(TableEntry, String); + DEBUG ((HEAP_GUARD_DEBUG_LEVEL, "%016lx: %a\r\n", Address, String)); + + } + } + + if (Level < (GUARDED_HEAP_MAP_TABLE_DEPTH - (INTN)mMapLevel)) { + break; + } + + Indices[Level] += 1; + Address = (Level == 0) ? 0 : Addresses[Level - 1]; + Addresses[Level] = Address | LShiftU64(Indices[Level], Shifts[Level]); + + } +} + diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/HeapGuard.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/HeapGuard.h new file mode 100644 index 00000000..7ac000dc --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/HeapGuard.h @@ -0,0 +1,467 @@ +/** @file + Data type, macros and function prototypes of heap guard feature. + +Copyright (c) 2017-2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _HEAPGUARD_H_ +#define _HEAPGUARD_H_ + +// +// Following macros are used to define and access the guarded memory bitmap +// table. +// +// To simplify the access and reduce the memory used for this table, the +// table is constructed in the similar way as page table structure but in +// reverse direction, i.e. from bottom growing up to top. +// +// - 1-bit tracks 1 page (4KB) +// - 1-UINT64 map entry tracks 256KB memory +// - 1K-UINT64 map table tracks 256MB memory +// - Five levels of tables can track any address of memory of 64-bit +// system, like below. +// +// 512 * 512 * 512 * 512 * 1K * 64b * 4K +// 111111111 111111111 111111111 111111111 1111111111 111111 111111111111 +// 63 54 45 36 27 17 11 0 +// 9b 9b 9b 9b 10b 6b 12b +// L0 -> L1 -> L2 -> L3 -> L4 -> bits -> page +// 1FF 1FF 1FF 1FF 3FF 3F FFF +// +// L4 table has 1K * sizeof(UINT64) = 8K (2-page), which can track 256MB +// memory. Each table of L0-L3 will be allocated when its memory address +// range is to be tracked. Only 1-page will be allocated each time. This +// can save memories used to establish this map table. +// +// For a normal configuration of system with 4G memory, two levels of tables +// can track the whole memory, because two levels (L3+L4) of map tables have +// already coverred 37-bit of memory address. And for a normal UEFI BIOS, +// less than 128M memory would be consumed during boot. That means we just +// need +// +// 1-page (L3) + 2-page (L4) +// +// memory (3 pages) to track the memory allocation works. In this case, +// there's no need to setup L0-L2 tables. +// + +// +// Each entry occupies 8B/64b. 1-page can hold 512 entries, which spans 9 +// bits in address. (512 = 1 << 9) +// +#define BYTE_LENGTH_SHIFT 3 // (8 = 1 << 3) + +#define GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT \ + (EFI_PAGE_SHIFT - BYTE_LENGTH_SHIFT) + +#define GUARDED_HEAP_MAP_TABLE_DEPTH 5 + +// Use UINT64_index + bit_index_of_UINT64 to locate the bit in may +#define GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT 6 // (64 = 1 << 6) + +#define GUARDED_HEAP_MAP_ENTRY_BITS \ + (1 << GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT) + +#define GUARDED_HEAP_MAP_ENTRY_BYTES \ + (GUARDED_HEAP_MAP_ENTRY_BITS / 8) + +// L4 table address width: 64 - 9 * 4 - 6 - 12 = 10b +#define GUARDED_HEAP_MAP_ENTRY_SHIFT \ + (GUARDED_HEAP_MAP_ENTRY_BITS \ + - GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT * 4 \ + - GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT \ + - EFI_PAGE_SHIFT) + +// L4 table address mask: (1 << 10 - 1) = 0x3FF +#define GUARDED_HEAP_MAP_ENTRY_MASK \ + ((1 << GUARDED_HEAP_MAP_ENTRY_SHIFT) - 1) + +// Size of each L4 table: (1 << 10) * 8 = 8KB = 2-page +#define GUARDED_HEAP_MAP_SIZE \ + ((1 << GUARDED_HEAP_MAP_ENTRY_SHIFT) * GUARDED_HEAP_MAP_ENTRY_BYTES) + +// Memory size tracked by one L4 table: 8KB * 8 * 4KB = 256MB +#define GUARDED_HEAP_MAP_UNIT_SIZE \ + (GUARDED_HEAP_MAP_SIZE * 8 * EFI_PAGE_SIZE) + +// L4 table entry number: 8KB / 8 = 1024 +#define GUARDED_HEAP_MAP_ENTRIES_PER_UNIT \ + (GUARDED_HEAP_MAP_SIZE / GUARDED_HEAP_MAP_ENTRY_BYTES) + +// L4 table entry indexing +#define GUARDED_HEAP_MAP_ENTRY_INDEX(Address) \ + (RShiftU64 (Address, EFI_PAGE_SHIFT \ + + GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT) \ + & GUARDED_HEAP_MAP_ENTRY_MASK) + +// L4 table entry bit indexing +#define GUARDED_HEAP_MAP_ENTRY_BIT_INDEX(Address) \ + (RShiftU64 (Address, EFI_PAGE_SHIFT) \ + & ((1 << GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT) - 1)) + +// +// Total bits (pages) tracked by one L4 table (65536-bit) +// +#define GUARDED_HEAP_MAP_BITS \ + (1 << (GUARDED_HEAP_MAP_ENTRY_SHIFT \ + + GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT)) + +// +// Bit indexing inside the whole L4 table (0 - 65535) +// +#define GUARDED_HEAP_MAP_BIT_INDEX(Address) \ + (RShiftU64 (Address, EFI_PAGE_SHIFT) \ + & ((1 << (GUARDED_HEAP_MAP_ENTRY_SHIFT \ + + GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT)) - 1)) + +// +// Memory address bit width tracked by L4 table: 10 + 6 + 12 = 28 +// +#define GUARDED_HEAP_MAP_TABLE_SHIFT \ + (GUARDED_HEAP_MAP_ENTRY_SHIFT + GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT \ + + EFI_PAGE_SHIFT) + +// +// Macro used to initialize the local array variable for map table traversing +// {55, 46, 37, 28, 18} +// +#define GUARDED_HEAP_MAP_TABLE_DEPTH_SHIFTS \ + { \ + GUARDED_HEAP_MAP_TABLE_SHIFT + GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT * 3, \ + GUARDED_HEAP_MAP_TABLE_SHIFT + GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT * 2, \ + GUARDED_HEAP_MAP_TABLE_SHIFT + GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT, \ + GUARDED_HEAP_MAP_TABLE_SHIFT, \ + EFI_PAGE_SHIFT + GUARDED_HEAP_MAP_ENTRY_BIT_SHIFT \ + } + +// +// Masks used to extract address range of each level of table +// {0x1FF, 0x1FF, 0x1FF, 0x1FF, 0x3FF} +// +#define GUARDED_HEAP_MAP_TABLE_DEPTH_MASKS \ + { \ + (1 << GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT) - 1, \ + (1 << GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT) - 1, \ + (1 << GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT) - 1, \ + (1 << GUARDED_HEAP_MAP_TABLE_ENTRY_SHIFT) - 1, \ + (1 << GUARDED_HEAP_MAP_ENTRY_SHIFT) - 1 \ + } + +// +// Memory type to guard (matching the related PCD definition) +// +#define GUARD_HEAP_TYPE_PAGE BIT0 +#define GUARD_HEAP_TYPE_POOL BIT1 +#define GUARD_HEAP_TYPE_FREED BIT4 +#define GUARD_HEAP_TYPE_ALL \ + (GUARD_HEAP_TYPE_PAGE|GUARD_HEAP_TYPE_POOL|GUARD_HEAP_TYPE_FREED) + +// +// Debug message level +// +#define HEAP_GUARD_DEBUG_LEVEL (DEBUG_POOL|DEBUG_PAGE) + +typedef struct { + UINT32 TailMark; + UINT32 HeadMark; + EFI_PHYSICAL_ADDRESS Address; + LIST_ENTRY Link; +} HEAP_GUARD_NODE; + +/** + Internal function. Converts a memory range to the specified type. + The range must exist in the memory map. + + @param Start The first address of the range Must be page + aligned. + @param NumberOfPages The number of pages to convert. + @param NewType The new type for the memory range. + + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Could not find a descriptor cover the specified + range or convertion not allowed. + @retval EFI_SUCCESS Successfully converts the memory range to the + specified type. + +**/ +EFI_STATUS +CoreConvertPages ( + IN UINT64 Start, + IN UINT64 NumberOfPages, + IN EFI_MEMORY_TYPE NewType + ); + +/** + Allocate or free guarded memory. + + @param[in] Start Start address of memory to allocate or free. + @param[in] NumberOfPages Memory size in pages. + @param[in] NewType Memory type to convert to. + + @return VOID. +**/ +EFI_STATUS +CoreConvertPagesWithGuard ( + IN UINT64 Start, + IN UINTN NumberOfPages, + IN EFI_MEMORY_TYPE NewType + ); + +/** + Set head Guard and tail Guard for the given memory range. + + @param[in] Memory Base address of memory to set guard for. + @param[in] NumberOfPages Memory size in pages. + + @return VOID. +**/ +VOID +SetGuardForMemory ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ); + +/** + Unset head Guard and tail Guard for the given memory range. + + @param[in] Memory Base address of memory to unset guard for. + @param[in] NumberOfPages Memory size in pages. + + @return VOID. +**/ +VOID +UnsetGuardForMemory ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ); + +/** + Adjust the base and number of pages to really allocate according to Guard. + + @param[in,out] Memory Base address of free memory. + @param[in,out] NumberOfPages Size of memory to allocate. + + @return VOID. +**/ +VOID +AdjustMemoryA ( + IN OUT EFI_PHYSICAL_ADDRESS *Memory, + IN OUT UINTN *NumberOfPages + ); + +/** + Adjust the start address and number of pages to free according to Guard. + + The purpose of this function is to keep the shared Guard page with adjacent + memory block if it's still in guard, or free it if no more sharing. Another + is to reserve pages as Guard pages in partial page free situation. + + @param[in,out] Memory Base address of memory to free. + @param[in,out] NumberOfPages Size of memory to free. + + @return VOID. +**/ +VOID +AdjustMemoryF ( + IN OUT EFI_PHYSICAL_ADDRESS *Memory, + IN OUT UINTN *NumberOfPages + ); + +/** + Adjust address of free memory according to existing and/or required Guard. + + This function will check if there're existing Guard pages of adjacent + memory blocks, and try to use it as the Guard page of the memory to be + allocated. + + @param[in] Start Start address of free memory block. + @param[in] Size Size of free memory block. + @param[in] SizeRequested Size of memory to allocate. + + @return The end address of memory block found. + @return 0 if no enough space for the required size of memory and its Guard. +**/ +UINT64 +AdjustMemoryS ( + IN UINT64 Start, + IN UINT64 Size, + IN UINT64 SizeRequested + ); + +/** + Check to see if the pool at the given address should be guarded or not. + + @param[in] MemoryType Pool type to check. + + + @return TRUE The given type of pool should be guarded. + @return FALSE The given type of pool should not be guarded. +**/ +BOOLEAN +IsPoolTypeToGuard ( + IN EFI_MEMORY_TYPE MemoryType + ); + +/** + Check to see if the page at the given address should be guarded or not. + + @param[in] MemoryType Page type to check. + @param[in] AllocateType Allocation type to check. + + @return TRUE The given type of page should be guarded. + @return FALSE The given type of page should not be guarded. +**/ +BOOLEAN +IsPageTypeToGuard ( + IN EFI_MEMORY_TYPE MemoryType, + IN EFI_ALLOCATE_TYPE AllocateType + ); + +/** + Check to see if the page at the given address is guarded or not. + + @param[in] Address The address to check for. + + @return TRUE The page at Address is guarded. + @return FALSE The page at Address is not guarded. +**/ +BOOLEAN +EFIAPI +IsMemoryGuarded ( + IN EFI_PHYSICAL_ADDRESS Address + ); + +/** + Check to see if the page at the given address is a Guard page or not. + + @param[in] Address The address to check for. + + @return TRUE The page at Address is a Guard page. + @return FALSE The page at Address is not a Guard page. +**/ +BOOLEAN +EFIAPI +IsGuardPage ( + IN EFI_PHYSICAL_ADDRESS Address + ); + +/** + Dump the guarded memory bit map. +**/ +VOID +EFIAPI +DumpGuardedMemoryBitmap ( + VOID + ); + +/** + Adjust the pool head position to make sure the Guard page is adjavent to + pool tail or pool head. + + @param[in] Memory Base address of memory allocated. + @param[in] NoPages Number of pages actually allocated. + @param[in] Size Size of memory requested. + (plus pool head/tail overhead) + + @return Address of pool head. +**/ +VOID * +AdjustPoolHeadA ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NoPages, + IN UINTN Size + ); + +/** + Get the page base address according to pool head address. + + @param[in] Memory Head address of pool to free. + + @return Address of pool head. +**/ +VOID * +AdjustPoolHeadF ( + IN EFI_PHYSICAL_ADDRESS Memory + ); + +/** + Check to see if the heap guard is enabled for page and/or pool allocation. + + @param[in] GuardType Specify the sub-type(s) of Heap Guard. + + @return TRUE/FALSE. +**/ +BOOLEAN +IsHeapGuardEnabled ( + UINT8 GuardType + ); + +/** + Notify function used to set all Guard pages after CPU Arch Protocol installed. +**/ +VOID +HeapGuardCpuArchProtocolNotify ( + VOID + ); + +/** + This function checks to see if the given memory map descriptor in a memory map + can be merged with any guarded free pages. + + @param MemoryMapEntry A pointer to a descriptor in MemoryMap. + @param MaxAddress Maximum address to stop the merge. + + @return VOID + +**/ +VOID +MergeGuardPages ( + IN EFI_MEMORY_DESCRIPTOR *MemoryMapEntry, + IN EFI_PHYSICAL_ADDRESS MaxAddress + ); + +/** + Record freed pages as well as mark them as not-present, if enabled. + + @param[in] BaseAddress Base address of just freed pages. + @param[in] Pages Number of freed pages. + + @return VOID. +**/ +VOID +EFIAPI +GuardFreedPagesChecked ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINTN Pages + ); + +/** + Put part (at most 64 pages a time) guarded free pages back to free page pool. + + Freed memory guard is used to detect Use-After-Free (UAF) memory issue, which + makes use of 'Used then throw away' way to detect any illegal access to freed + memory. The thrown-away memory will be marked as not-present so that any access + to those memory (after free) will be caught by page-fault exception. + + The problem is that this will consume lots of memory space. Once no memory + left in pool to allocate, we have to restore part of the freed pages to their + normal function. Otherwise the whole system will stop functioning. + + @param StartAddress Start address of promoted memory. + @param EndAddress End address of promoted memory. + + @return TRUE Succeeded to promote memory. + @return FALSE No free memory found. + +**/ +BOOLEAN +PromoteGuardedFreePages ( + OUT EFI_PHYSICAL_ADDRESS *StartAddress, + OUT EFI_PHYSICAL_ADDRESS *EndAddress + ); + +extern BOOLEAN mOnGuarding; + +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Imem.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Imem.h new file mode 100644 index 00000000..357cbfc8 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Imem.h @@ -0,0 +1,182 @@ +/** @file + Data structure and functions to allocate and free memory space. + +Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _IMEM_H_ +#define _IMEM_H_ + +// +// +---------------------------------------------------+ +// | 0..(EfiMaxMemoryType - 1) - Normal memory type | +// +---------------------------------------------------+ +// | EfiMaxMemoryType..0x6FFFFFFF - Invalid | +// +---------------------------------------------------+ +// | 0x70000000..0x7FFFFFFF - OEM reserved | +// +---------------------------------------------------+ +// | 0x80000000..0xFFFFFFFF - OS reserved | +// +---------------------------------------------------+ +// +#define MEMORY_TYPE_OS_RESERVED_MIN 0x80000000 +#define MEMORY_TYPE_OS_RESERVED_MAX 0xFFFFFFFF +#define MEMORY_TYPE_OEM_RESERVED_MIN 0x70000000 +#define MEMORY_TYPE_OEM_RESERVED_MAX 0x7FFFFFFF + +// +// MEMORY_MAP_ENTRY +// + +#define MEMORY_MAP_SIGNATURE SIGNATURE_32('m','m','a','p') +typedef struct { + UINTN Signature; + LIST_ENTRY Link; + BOOLEAN FromPages; + + EFI_MEMORY_TYPE Type; + UINT64 Start; + UINT64 End; + + UINT64 VirtualStart; + UINT64 Attribute; +} MEMORY_MAP; + +// +// Internal prototypes +// + + +/** + Internal function. Used by the pool functions to allocate pages + to back pool allocation requests. + + @param PoolType The type of memory for the new pool pages + @param NumberOfPages No of pages to allocate + @param Alignment Bits to align. + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The allocated memory, or NULL + +**/ +VOID * +CoreAllocatePoolPages ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN NumberOfPages, + IN UINTN Alignment, + IN BOOLEAN NeedGuard + ); + + + +/** + Internal function. Frees pool pages allocated via AllocatePoolPages () + + @param Memory The base address to free + @param NumberOfPages The number of pages to free + +**/ +VOID +CoreFreePoolPages ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ); + + + +/** + Internal function to allocate pool of a particular type. + Caller must have the memory lock held + + @param PoolType Type of pool to allocate + @param Size The amount of pool to allocate + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The allocate pool, or NULL + +**/ +VOID * +CoreAllocatePoolI ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN Size, + IN BOOLEAN NeedGuard + ); + + + +/** + Internal function to free a pool entry. + Caller must have the memory lock held + + @param Buffer The allocated pool entry to free + @param PoolType Pointer to pool type + + @retval EFI_INVALID_PARAMETER Buffer not valid + @retval EFI_SUCCESS Buffer successfully freed. + +**/ +EFI_STATUS +CoreFreePoolI ( + IN VOID *Buffer, + OUT EFI_MEMORY_TYPE *PoolType OPTIONAL + ); + + + +/** + Enter critical section by gaining lock on gMemoryLock. + +**/ +VOID +CoreAcquireMemoryLock ( + VOID + ); + + +/** + Exit critical section by releasing lock on gMemoryLock. + +**/ +VOID +CoreReleaseMemoryLock ( + VOID + ); + +/** + Allocates pages from the memory map. + + @param Type The type of allocation to perform + @param MemoryType The type of memory to turn the allocated pages + into + @param NumberOfPages The number of pages to allocate + @param Memory A pointer to receive the base allocated memory + address + @param NeedGuard Flag to indicate Guard page is needed or not + + @return Status. On success, Memory is filled in with the base address allocated + @retval EFI_INVALID_PARAMETER Parameters violate checking rules defined in + spec. + @retval EFI_NOT_FOUND Could not allocate pages match the requirement. + @retval EFI_OUT_OF_RESOURCES No enough pages to allocate. + @retval EFI_SUCCESS Pages successfully allocated. + +**/ +EFI_STATUS +EFIAPI +CoreInternalAllocatePages ( + IN EFI_ALLOCATE_TYPE Type, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN NumberOfPages, + IN OUT EFI_PHYSICAL_ADDRESS *Memory, + IN BOOLEAN NeedGuard + ); + +// +// Internal Global data +// + +extern EFI_LOCK gMemoryLock; +extern LIST_ENTRY gMemoryMap; +extern LIST_ENTRY mGcdMemorySpaceMap; +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/MemData.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/MemData.c new file mode 100644 index 00000000..aafdb5f5 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/MemData.c @@ -0,0 +1,20 @@ +/** @file + Global data used in memory service + +Copyright (c) 2006 - 2008, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "DxeMain.h" + + +// +// MemoryLock - synchronizes access to the memory map and pool lists +// +EFI_LOCK gMemoryLock = EFI_INITIALIZE_LOCK_VARIABLE (TPL_NOTIFY); + +// +// MemoryMap - the current memory map +// +LIST_ENTRY gMemoryMap = INITIALIZE_LIST_HEAD_VARIABLE (gMemoryMap); diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/MemoryProfileRecord.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/MemoryProfileRecord.c new file mode 100644 index 00000000..ab36a559 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/MemoryProfileRecord.c @@ -0,0 +1,1759 @@ +/** @file + Support routines for UEFI memory profile. + + Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "DxeMain.h" +#include "Imem.h" + +#define IS_UEFI_MEMORY_PROFILE_ENABLED ((PcdGet8 (PcdMemoryProfilePropertyMask) & BIT0) != 0) + +#define GET_OCCUPIED_SIZE(ActualSize, Alignment) \ + ((ActualSize) + (((Alignment) - ((ActualSize) & ((Alignment) - 1))) & ((Alignment) - 1))) + +typedef struct { + UINT32 Signature; + MEMORY_PROFILE_CONTEXT Context; + LIST_ENTRY *DriverInfoList; +} MEMORY_PROFILE_CONTEXT_DATA; + +typedef struct { + UINT32 Signature; + MEMORY_PROFILE_DRIVER_INFO DriverInfo; + LIST_ENTRY *AllocInfoList; + CHAR8 *PdbString; + LIST_ENTRY Link; +} MEMORY_PROFILE_DRIVER_INFO_DATA; + +typedef struct { + UINT32 Signature; + MEMORY_PROFILE_ALLOC_INFO AllocInfo; + CHAR8 *ActionString; + LIST_ENTRY Link; +} MEMORY_PROFILE_ALLOC_INFO_DATA; + + +GLOBAL_REMOVE_IF_UNREFERENCED LIST_ENTRY mImageQueue = INITIALIZE_LIST_HEAD_VARIABLE (mImageQueue); +GLOBAL_REMOVE_IF_UNREFERENCED MEMORY_PROFILE_CONTEXT_DATA mMemoryProfileContext = { + MEMORY_PROFILE_CONTEXT_SIGNATURE, + { + { + MEMORY_PROFILE_CONTEXT_SIGNATURE, + sizeof (MEMORY_PROFILE_CONTEXT), + MEMORY_PROFILE_CONTEXT_REVISION + }, + 0, + 0, + {0}, + {0}, + 0, + 0, + 0 + }, + &mImageQueue, +}; +GLOBAL_REMOVE_IF_UNREFERENCED MEMORY_PROFILE_CONTEXT_DATA *mMemoryProfileContextPtr = NULL; + +GLOBAL_REMOVE_IF_UNREFERENCED EFI_LOCK mMemoryProfileLock = EFI_INITIALIZE_LOCK_VARIABLE (TPL_NOTIFY); +GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN mMemoryProfileGettingStatus = FALSE; +GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN mMemoryProfileRecordingEnable = MEMORY_PROFILE_RECORDING_DISABLE; +GLOBAL_REMOVE_IF_UNREFERENCED EFI_DEVICE_PATH_PROTOCOL *mMemoryProfileDriverPath; +GLOBAL_REMOVE_IF_UNREFERENCED UINTN mMemoryProfileDriverPathSize; + +/** + Get memory profile data. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in, out] ProfileSize On entry, points to the size in bytes of the ProfileBuffer. + On return, points to the size of the data returned in ProfileBuffer. + @param[out] ProfileBuffer Profile buffer. + + @return EFI_SUCCESS Get the memory profile data successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported. + @return EFI_BUFFER_TO_SMALL The ProfileSize is too small for the resulting data. + ProfileSize is updated with the size required. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolGetData ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN OUT UINT64 *ProfileSize, + OUT VOID *ProfileBuffer + ); + +/** + Register image to memory profile. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] FilePath File path of the image. + @param[in] ImageBase Image base address. + @param[in] ImageSize Image size. + @param[in] FileType File type of the image. + + @return EFI_SUCCESS Register successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_OUT_OF_RESOURCE No enough resource for this register. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolRegisterImage ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + IN PHYSICAL_ADDRESS ImageBase, + IN UINT64 ImageSize, + IN EFI_FV_FILETYPE FileType + ); + +/** + Unregister image from memory profile. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] FilePath File path of the image. + @param[in] ImageBase Image base address. + @param[in] ImageSize Image size. + + @return EFI_SUCCESS Unregister successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_NOT_FOUND The image is not found. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolUnregisterImage ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + IN PHYSICAL_ADDRESS ImageBase, + IN UINT64 ImageSize + ); + +/** + Get memory profile recording state. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[out] RecordingState Recording state. + + @return EFI_SUCCESS Memory profile recording state is returned. + @return EFI_UNSUPPORTED Memory profile is unsupported. + @return EFI_INVALID_PARAMETER RecordingState is NULL. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolGetRecordingState ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + OUT BOOLEAN *RecordingState + ); + +/** + Set memory profile recording state. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] RecordingState Recording state. + + @return EFI_SUCCESS Set memory profile recording state successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolSetRecordingState ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN BOOLEAN RecordingState + ); + +/** + Record memory profile of multilevel caller. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] CallerAddress Address of caller. + @param[in] Action Memory profile action. + @param[in] MemoryType Memory type. + EfiMaxMemoryType means the MemoryType is unknown. + @param[in] Buffer Buffer address. + @param[in] Size Buffer size. + @param[in] ActionString String for memory profile action. + Only needed for user defined allocate action. + + @return EFI_SUCCESS Memory profile is updated. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required, + or memory profile for the memory type is not required. + @return EFI_ACCESS_DENIED It is during memory profile data getting. + @return EFI_ABORTED Memory profile recording is not enabled. + @return EFI_OUT_OF_RESOURCES No enough resource to update memory profile for allocate action. + @return EFI_NOT_FOUND No matched allocate info found for free action. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolRecord ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN PHYSICAL_ADDRESS CallerAddress, + IN MEMORY_PROFILE_ACTION Action, + IN EFI_MEMORY_TYPE MemoryType, + IN VOID *Buffer, + IN UINTN Size, + IN CHAR8 *ActionString OPTIONAL + ); + +GLOBAL_REMOVE_IF_UNREFERENCED EDKII_MEMORY_PROFILE_PROTOCOL mProfileProtocol = { + ProfileProtocolGetData, + ProfileProtocolRegisterImage, + ProfileProtocolUnregisterImage, + ProfileProtocolGetRecordingState, + ProfileProtocolSetRecordingState, + ProfileProtocolRecord, +}; + +/** + Acquire lock on mMemoryProfileLock. +**/ +VOID +CoreAcquireMemoryProfileLock ( + VOID + ) +{ + CoreAcquireLock (&mMemoryProfileLock); +} + +/** + Release lock on mMemoryProfileLock. +**/ +VOID +CoreReleaseMemoryProfileLock ( + VOID + ) +{ + CoreReleaseLock (&mMemoryProfileLock); +} + +/** + Return memory profile context. + + @return Memory profile context. + +**/ +MEMORY_PROFILE_CONTEXT_DATA * +GetMemoryProfileContext ( + VOID + ) +{ + return mMemoryProfileContextPtr; +} + +/** + Retrieves and returns the Subsystem of a PE/COFF image that has been loaded into system memory. + If Pe32Data is NULL, then ASSERT(). + + @param Pe32Data The pointer to the PE/COFF image that is loaded in system memory. + + @return The Subsystem of the PE/COFF image. + +**/ +UINT16 +InternalPeCoffGetSubsystem ( + IN VOID *Pe32Data + ) +{ + EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr; + EFI_IMAGE_DOS_HEADER *DosHdr; + UINT16 Magic; + + ASSERT (Pe32Data != NULL); + + DosHdr = (EFI_IMAGE_DOS_HEADER *) Pe32Data; + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { + // + // DOS image header is present, so read the PE header after the DOS image header. + // + Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *) ((UINTN) Pe32Data + (UINTN) ((DosHdr->e_lfanew) & 0x0ffff)); + } else { + // + // DOS image header is not present, so PE header is at the image base. + // + Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *) Pe32Data; + } + + if (Hdr.Te->Signature == EFI_TE_IMAGE_HEADER_SIGNATURE) { + return Hdr.Te->Subsystem; + } else if (Hdr.Pe32->Signature == EFI_IMAGE_NT_SIGNATURE) { + Magic = Hdr.Pe32->OptionalHeader.Magic; + if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + return Hdr.Pe32->OptionalHeader.Subsystem; + } else if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + return Hdr.Pe32Plus->OptionalHeader.Subsystem; + } + } + + return 0x0000; +} + +/** + Retrieves and returns a pointer to the entry point to a PE/COFF image that has been loaded + into system memory with the PE/COFF Loader Library functions. + + Retrieves the entry point to the PE/COFF image specified by Pe32Data and returns this entry + point in EntryPoint. If the entry point could not be retrieved from the PE/COFF image, then + return RETURN_INVALID_PARAMETER. Otherwise return RETURN_SUCCESS. + If Pe32Data is NULL, then ASSERT(). + If EntryPoint is NULL, then ASSERT(). + + @param Pe32Data The pointer to the PE/COFF image that is loaded in system memory. + @param EntryPoint The pointer to entry point to the PE/COFF image to return. + + @retval RETURN_SUCCESS EntryPoint was returned. + @retval RETURN_INVALID_PARAMETER The entry point could not be found in the PE/COFF image. + +**/ +RETURN_STATUS +InternalPeCoffGetEntryPoint ( + IN VOID *Pe32Data, + OUT VOID **EntryPoint + ) +{ + EFI_IMAGE_DOS_HEADER *DosHdr; + EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr; + + ASSERT (Pe32Data != NULL); + ASSERT (EntryPoint != NULL); + + DosHdr = (EFI_IMAGE_DOS_HEADER *) Pe32Data; + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { + // + // DOS image header is present, so read the PE header after the DOS image header. + // + Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *) ((UINTN) Pe32Data + (UINTN) ((DosHdr->e_lfanew) & 0x0ffff)); + } else { + // + // DOS image header is not present, so PE header is at the image base. + // + Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *) Pe32Data; + } + + // + // Calculate the entry point relative to the start of the image. + // AddressOfEntryPoint is common for PE32 & PE32+ + // + if (Hdr.Te->Signature == EFI_TE_IMAGE_HEADER_SIGNATURE) { + *EntryPoint = (VOID *) ((UINTN) Pe32Data + (UINTN) (Hdr.Te->AddressOfEntryPoint & 0x0ffffffff) + sizeof (EFI_TE_IMAGE_HEADER) - Hdr.Te->StrippedSize); + return RETURN_SUCCESS; + } else if (Hdr.Pe32->Signature == EFI_IMAGE_NT_SIGNATURE) { + *EntryPoint = (VOID *) ((UINTN) Pe32Data + (UINTN) (Hdr.Pe32->OptionalHeader.AddressOfEntryPoint & 0x0ffffffff)); + return RETURN_SUCCESS; + } + + return RETURN_UNSUPPORTED; +} + +/** + Build driver info. + + @param ContextData Memory profile context. + @param FileName File name of the image. + @param ImageBase Image base address. + @param ImageSize Image size. + @param EntryPoint Entry point of the image. + @param ImageSubsystem Image subsystem of the image. + @param FileType File type of the image. + + @return Pointer to memory profile driver info. + +**/ +MEMORY_PROFILE_DRIVER_INFO_DATA * +BuildDriverInfo ( + IN MEMORY_PROFILE_CONTEXT_DATA *ContextData, + IN EFI_GUID *FileName, + IN PHYSICAL_ADDRESS ImageBase, + IN UINT64 ImageSize, + IN PHYSICAL_ADDRESS EntryPoint, + IN UINT16 ImageSubsystem, + IN EFI_FV_FILETYPE FileType + ) +{ + EFI_STATUS Status; + MEMORY_PROFILE_DRIVER_INFO *DriverInfo; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + VOID *EntryPointInImage; + CHAR8 *PdbString; + UINTN PdbSize; + UINTN PdbOccupiedSize; + + PdbSize = 0; + PdbOccupiedSize = 0; + PdbString = NULL; + if (ImageBase != 0) { + PdbString = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageBase); + if (PdbString != NULL) { + PdbSize = AsciiStrSize (PdbString); + PdbOccupiedSize = GET_OCCUPIED_SIZE (PdbSize, sizeof (UINT64)); + } + } + + // + // Use CoreInternalAllocatePool() that will not update profile for this AllocatePool action. + // + Status = CoreInternalAllocatePool ( + EfiBootServicesData, + sizeof (*DriverInfoData) + sizeof (LIST_ENTRY) + PdbSize, + (VOID **) &DriverInfoData + ); + if (EFI_ERROR (Status)) { + return NULL; + } + ASSERT (DriverInfoData != NULL); + + ZeroMem (DriverInfoData, sizeof (*DriverInfoData)); + + DriverInfo = &DriverInfoData->DriverInfo; + DriverInfoData->Signature = MEMORY_PROFILE_DRIVER_INFO_SIGNATURE; + DriverInfo->Header.Signature = MEMORY_PROFILE_DRIVER_INFO_SIGNATURE; + DriverInfo->Header.Length = (UINT16) (sizeof (MEMORY_PROFILE_DRIVER_INFO) + PdbOccupiedSize); + DriverInfo->Header.Revision = MEMORY_PROFILE_DRIVER_INFO_REVISION; + if (FileName != NULL) { + CopyMem (&DriverInfo->FileName, FileName, sizeof (EFI_GUID)); + } + DriverInfo->ImageBase = ImageBase; + DriverInfo->ImageSize = ImageSize; + DriverInfo->EntryPoint = EntryPoint; + DriverInfo->ImageSubsystem = ImageSubsystem; + if ((EntryPoint != 0) && ((EntryPoint < ImageBase) || (EntryPoint >= (ImageBase + ImageSize)))) { + // + // If the EntryPoint is not in the range of image buffer, it should come from emulation environment. + // So patch ImageBuffer here to align the EntryPoint. + // + Status = InternalPeCoffGetEntryPoint ((VOID *) (UINTN) ImageBase, &EntryPointInImage); + ASSERT_EFI_ERROR (Status); + DriverInfo->ImageBase = ImageBase + EntryPoint - (PHYSICAL_ADDRESS) (UINTN) EntryPointInImage; + } + DriverInfo->FileType = FileType; + DriverInfoData->AllocInfoList = (LIST_ENTRY *) (DriverInfoData + 1); + InitializeListHead (DriverInfoData->AllocInfoList); + DriverInfo->CurrentUsage = 0; + DriverInfo->PeakUsage = 0; + DriverInfo->AllocRecordCount = 0; + if (PdbSize != 0) { + DriverInfo->PdbStringOffset = (UINT16) sizeof (MEMORY_PROFILE_DRIVER_INFO); + DriverInfoData->PdbString = (CHAR8 *) (DriverInfoData->AllocInfoList + 1); + CopyMem (DriverInfoData->PdbString, PdbString, PdbSize); + } else { + DriverInfo->PdbStringOffset = 0; + DriverInfoData->PdbString = NULL; + } + + InsertTailList (ContextData->DriverInfoList, &DriverInfoData->Link); + ContextData->Context.ImageCount ++; + ContextData->Context.TotalImageSize += DriverInfo->ImageSize; + + return DriverInfoData; +} + +/** + Return if record for this driver is needed.. + + @param DriverFilePath Driver file path. + + @retval TRUE Record for this driver is needed. + @retval FALSE Record for this driver is not needed. + +**/ +BOOLEAN +NeedRecordThisDriver ( + IN EFI_DEVICE_PATH_PROTOCOL *DriverFilePath + ) +{ + EFI_DEVICE_PATH_PROTOCOL *TmpDevicePath; + EFI_DEVICE_PATH_PROTOCOL *DevicePathInstance; + UINTN DevicePathSize; + UINTN FilePathSize; + + if (!IsDevicePathValid (mMemoryProfileDriverPath, mMemoryProfileDriverPathSize)) { + // + // Invalid Device Path means record all. + // + return TRUE; + } + + // + // Record FilePath without END node. + // + FilePathSize = GetDevicePathSize (DriverFilePath) - sizeof(EFI_DEVICE_PATH_PROTOCOL); + + DevicePathInstance = mMemoryProfileDriverPath; + do { + // + // Find END node (it might be END_ENTIRE or END_INSTANCE). + // + TmpDevicePath = DevicePathInstance; + while (!IsDevicePathEndType (TmpDevicePath)) { + TmpDevicePath = NextDevicePathNode (TmpDevicePath); + } + + // + // Do not compare END node. + // + DevicePathSize = (UINTN)TmpDevicePath - (UINTN)DevicePathInstance; + if ((FilePathSize == DevicePathSize) && + (CompareMem (DriverFilePath, DevicePathInstance, DevicePathSize) == 0)) { + return TRUE; + } + + // + // Get next instance. + // + DevicePathInstance = (EFI_DEVICE_PATH_PROTOCOL *)((UINTN)DevicePathInstance + DevicePathSize + DevicePathNodeLength(TmpDevicePath)); + } while (DevicePathSubType (TmpDevicePath) != END_ENTIRE_DEVICE_PATH_SUBTYPE); + + return FALSE; +} + +/** + Register DXE Core to memory profile. + + @param HobStart The start address of the HOB. + @param ContextData Memory profile context. + + @retval TRUE Register success. + @retval FALSE Register fail. + +**/ +BOOLEAN +RegisterDxeCore ( + IN VOID *HobStart, + IN MEMORY_PROFILE_CONTEXT_DATA *ContextData + ) +{ + EFI_PEI_HOB_POINTERS DxeCoreHob; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + PHYSICAL_ADDRESS ImageBase; + UINT8 TempBuffer[sizeof(MEDIA_FW_VOL_FILEPATH_DEVICE_PATH) + sizeof(EFI_DEVICE_PATH_PROTOCOL)]; + MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *FilePath; + + ASSERT (ContextData != NULL); + + // + // Searching for image hob + // + DxeCoreHob.Raw = HobStart; + while ((DxeCoreHob.Raw = GetNextHob (EFI_HOB_TYPE_MEMORY_ALLOCATION, DxeCoreHob.Raw)) != NULL) { + if (CompareGuid (&DxeCoreHob.MemoryAllocationModule->MemoryAllocationHeader.Name, &gEfiHobMemoryAllocModuleGuid)) { + // + // Find Dxe Core HOB + // + break; + } + DxeCoreHob.Raw = GET_NEXT_HOB (DxeCoreHob); + } + ASSERT (DxeCoreHob.Raw != NULL); + + FilePath = (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *) TempBuffer; + EfiInitializeFwVolDevicepathNode (FilePath, &DxeCoreHob.MemoryAllocationModule->ModuleName); + SetDevicePathEndNode (FilePath + 1); + + if (!NeedRecordThisDriver ((EFI_DEVICE_PATH_PROTOCOL *) FilePath)) { + return FALSE; + } + + ImageBase = DxeCoreHob.MemoryAllocationModule->MemoryAllocationHeader.MemoryBaseAddress; + DriverInfoData = BuildDriverInfo ( + ContextData, + &DxeCoreHob.MemoryAllocationModule->ModuleName, + ImageBase, + DxeCoreHob.MemoryAllocationModule->MemoryAllocationHeader.MemoryLength, + DxeCoreHob.MemoryAllocationModule->EntryPoint, + InternalPeCoffGetSubsystem ((VOID *) (UINTN) ImageBase), + EFI_FV_FILETYPE_DXE_CORE + ); + if (DriverInfoData == NULL) { + return FALSE; + } + + return TRUE; +} + +/** + Initialize memory profile. + + @param HobStart The start address of the HOB. + +**/ +VOID +MemoryProfileInit ( + IN VOID *HobStart + ) +{ + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + + if (!IS_UEFI_MEMORY_PROFILE_ENABLED) { + return; + } + + ContextData = GetMemoryProfileContext (); + if (ContextData != NULL) { + return; + } + + mMemoryProfileGettingStatus = FALSE; + if ((PcdGet8 (PcdMemoryProfilePropertyMask) & BIT7) != 0) { + mMemoryProfileRecordingEnable = MEMORY_PROFILE_RECORDING_DISABLE; + } else { + mMemoryProfileRecordingEnable = MEMORY_PROFILE_RECORDING_ENABLE; + } + mMemoryProfileDriverPathSize = PcdGetSize (PcdMemoryProfileDriverPath); + mMemoryProfileDriverPath = AllocateCopyPool (mMemoryProfileDriverPathSize, PcdGetPtr (PcdMemoryProfileDriverPath)); + mMemoryProfileContextPtr = &mMemoryProfileContext; + + RegisterDxeCore (HobStart, &mMemoryProfileContext); + + DEBUG ((EFI_D_INFO, "MemoryProfileInit MemoryProfileContext - 0x%x\n", &mMemoryProfileContext)); +} + +/** + Install memory profile protocol. + +**/ +VOID +MemoryProfileInstallProtocol ( + VOID + ) +{ + EFI_HANDLE Handle; + EFI_STATUS Status; + + if (!IS_UEFI_MEMORY_PROFILE_ENABLED) { + return; + } + + Handle = NULL; + Status = CoreInstallMultipleProtocolInterfaces ( + &Handle, + &gEdkiiMemoryProfileGuid, + &mProfileProtocol, + NULL + ); + ASSERT_EFI_ERROR (Status); +} + +/** + Get the GUID file name from the file path. + + @param FilePath File path. + + @return The GUID file name from the file path. + +**/ +EFI_GUID * +GetFileNameFromFilePath ( + IN EFI_DEVICE_PATH_PROTOCOL *FilePath + ) +{ + MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *ThisFilePath; + EFI_GUID *FileName; + + FileName = NULL; + if (FilePath != NULL) { + ThisFilePath = (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *) FilePath; + while (!IsDevicePathEnd (ThisFilePath)) { + FileName = EfiGetNameGuidFromFwVolDevicePathNode (ThisFilePath); + if (FileName != NULL) { + break; + } + ThisFilePath = (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *) NextDevicePathNode (ThisFilePath); + } + } + + return FileName; +} + +/** + Register image to memory profile. + + @param DriverEntry Image info. + @param FileType Image file type. + + @return EFI_SUCCESS Register successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_OUT_OF_RESOURCES No enough resource for this register. + +**/ +EFI_STATUS +RegisterMemoryProfileImage ( + IN LOADED_IMAGE_PRIVATE_DATA *DriverEntry, + IN EFI_FV_FILETYPE FileType + ) +{ + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + + if (!IS_UEFI_MEMORY_PROFILE_ENABLED) { + return EFI_UNSUPPORTED; + } + + if (!NeedRecordThisDriver (DriverEntry->Info.FilePath)) { + return EFI_UNSUPPORTED; + } + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + DriverInfoData = BuildDriverInfo ( + ContextData, + GetFileNameFromFilePath (DriverEntry->Info.FilePath), + DriverEntry->ImageContext.ImageAddress, + DriverEntry->ImageContext.ImageSize, + DriverEntry->ImageContext.EntryPoint, + DriverEntry->ImageContext.ImageType, + FileType + ); + if (DriverInfoData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + return EFI_SUCCESS; +} + +/** + Search image from memory profile. + + @param ContextData Memory profile context. + @param FileName Image file name. + @param Address Image Address. + + @return Pointer to memory profile driver info. + +**/ +MEMORY_PROFILE_DRIVER_INFO_DATA * +GetMemoryProfileDriverInfoByFileNameAndAddress ( + IN MEMORY_PROFILE_CONTEXT_DATA *ContextData, + IN EFI_GUID *FileName, + IN PHYSICAL_ADDRESS Address + ) +{ + MEMORY_PROFILE_DRIVER_INFO *DriverInfo; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + LIST_ENTRY *DriverLink; + LIST_ENTRY *DriverInfoList; + + DriverInfoList = ContextData->DriverInfoList; + + for (DriverLink = DriverInfoList->ForwardLink; + DriverLink != DriverInfoList; + DriverLink = DriverLink->ForwardLink) { + DriverInfoData = CR ( + DriverLink, + MEMORY_PROFILE_DRIVER_INFO_DATA, + Link, + MEMORY_PROFILE_DRIVER_INFO_SIGNATURE + ); + DriverInfo = &DriverInfoData->DriverInfo; + if ((CompareGuid (&DriverInfo->FileName, FileName)) && + (Address >= DriverInfo->ImageBase) && + (Address < (DriverInfo->ImageBase + DriverInfo->ImageSize))) { + return DriverInfoData; + } + } + + return NULL; +} + +/** + Search image from memory profile. + It will return image, if (Address >= ImageBuffer) AND (Address < ImageBuffer + ImageSize). + + @param ContextData Memory profile context. + @param Address Image or Function address. + + @return Pointer to memory profile driver info. + +**/ +MEMORY_PROFILE_DRIVER_INFO_DATA * +GetMemoryProfileDriverInfoFromAddress ( + IN MEMORY_PROFILE_CONTEXT_DATA *ContextData, + IN PHYSICAL_ADDRESS Address + ) +{ + MEMORY_PROFILE_DRIVER_INFO *DriverInfo; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + LIST_ENTRY *DriverLink; + LIST_ENTRY *DriverInfoList; + + DriverInfoList = ContextData->DriverInfoList; + + for (DriverLink = DriverInfoList->ForwardLink; + DriverLink != DriverInfoList; + DriverLink = DriverLink->ForwardLink) { + DriverInfoData = CR ( + DriverLink, + MEMORY_PROFILE_DRIVER_INFO_DATA, + Link, + MEMORY_PROFILE_DRIVER_INFO_SIGNATURE + ); + DriverInfo = &DriverInfoData->DriverInfo; + if ((Address >= DriverInfo->ImageBase) && + (Address < (DriverInfo->ImageBase + DriverInfo->ImageSize))) { + return DriverInfoData; + } + } + + return NULL; +} + +/** + Unregister image from memory profile. + + @param DriverEntry Image info. + + @return EFI_SUCCESS Unregister successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_NOT_FOUND The image is not found. + +**/ +EFI_STATUS +UnregisterMemoryProfileImage ( + IN LOADED_IMAGE_PRIVATE_DATA *DriverEntry + ) +{ + EFI_STATUS Status; + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + EFI_GUID *FileName; + PHYSICAL_ADDRESS ImageAddress; + VOID *EntryPointInImage; + + if (!IS_UEFI_MEMORY_PROFILE_ENABLED) { + return EFI_UNSUPPORTED; + } + + if (!NeedRecordThisDriver (DriverEntry->Info.FilePath)) { + return EFI_UNSUPPORTED; + } + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + DriverInfoData = NULL; + FileName = GetFileNameFromFilePath (DriverEntry->Info.FilePath); + ImageAddress = DriverEntry->ImageContext.ImageAddress; + if ((DriverEntry->ImageContext.EntryPoint < ImageAddress) || (DriverEntry->ImageContext.EntryPoint >= (ImageAddress + DriverEntry->ImageContext.ImageSize))) { + // + // If the EntryPoint is not in the range of image buffer, it should come from emulation environment. + // So patch ImageAddress here to align the EntryPoint. + // + Status = InternalPeCoffGetEntryPoint ((VOID *) (UINTN) ImageAddress, &EntryPointInImage); + ASSERT_EFI_ERROR (Status); + ImageAddress = ImageAddress + (UINTN) DriverEntry->ImageContext.EntryPoint - (UINTN) EntryPointInImage; + } + if (FileName != NULL) { + DriverInfoData = GetMemoryProfileDriverInfoByFileNameAndAddress (ContextData, FileName, ImageAddress); + } + if (DriverInfoData == NULL) { + DriverInfoData = GetMemoryProfileDriverInfoFromAddress (ContextData, ImageAddress); + } + if (DriverInfoData == NULL) { + return EFI_NOT_FOUND; + } + + ContextData->Context.TotalImageSize -= DriverInfoData->DriverInfo.ImageSize; + + // Keep the ImageBase for RVA calculation in Application. + //DriverInfoData->DriverInfo.ImageBase = 0; + DriverInfoData->DriverInfo.ImageSize = 0; + + if (DriverInfoData->DriverInfo.PeakUsage == 0) { + ContextData->Context.ImageCount --; + RemoveEntryList (&DriverInfoData->Link); + // + // Use CoreInternalFreePool() that will not update profile for this FreePool action. + // + CoreInternalFreePool (DriverInfoData, NULL); + } + + return EFI_SUCCESS; +} + +/** + Return if this memory type needs to be recorded into memory profile. + If BIOS memory type (0 ~ EfiMaxMemoryType - 1), it checks bit (1 << MemoryType). + If OS memory type (0x80000000 ~ 0xFFFFFFFF), it checks bit63 - 0x8000000000000000. + If OEM memory type (0x70000000 ~ 0x7FFFFFFF), it checks bit62 - 0x4000000000000000. + + @param MemoryType Memory type. + + @retval TRUE This memory type need to be recorded. + @retval FALSE This memory type need not to be recorded. + +**/ +BOOLEAN +CoreNeedRecordProfile ( + IN EFI_MEMORY_TYPE MemoryType + ) +{ + UINT64 TestBit; + + if ((UINT32) MemoryType >= MEMORY_TYPE_OS_RESERVED_MIN) { + TestBit = BIT63; + } else if ((UINT32) MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) { + TestBit = BIT62; + } else { + TestBit = LShiftU64 (1, MemoryType); + } + + if ((PcdGet64 (PcdMemoryProfileMemoryType) & TestBit) != 0) { + return TRUE; + } else { + return FALSE; + } +} + +/** + Convert EFI memory type to profile memory index. The rule is: + If BIOS memory type (0 ~ EfiMaxMemoryType - 1), ProfileMemoryIndex = MemoryType. + If OS memory type (0x80000000 ~ 0xFFFFFFFF), ProfileMemoryIndex = EfiMaxMemoryType. + If OEM memory type (0x70000000 ~ 0x7FFFFFFF), ProfileMemoryIndex = EfiMaxMemoryType + 1. + + @param MemoryType Memory type. + + @return Profile memory index. + +**/ +UINTN +GetProfileMemoryIndex ( + IN EFI_MEMORY_TYPE MemoryType + ) +{ + if ((UINT32) MemoryType >= MEMORY_TYPE_OS_RESERVED_MIN) { + return EfiMaxMemoryType; + } else if ((UINT32) MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) { + return EfiMaxMemoryType + 1; + } else { + return MemoryType; + } +} + +/** + Update memory profile Allocate information. + + @param CallerAddress Address of caller who call Allocate. + @param Action This Allocate action. + @param MemoryType Memory type. + @param Size Buffer size. + @param Buffer Buffer address. + @param ActionString String for memory profile action. + + @return EFI_SUCCESS Memory profile is updated. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_OUT_OF_RESOURCES No enough resource to update memory profile for allocate action. + +**/ +EFI_STATUS +CoreUpdateProfileAllocate ( + IN PHYSICAL_ADDRESS CallerAddress, + IN MEMORY_PROFILE_ACTION Action, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN Size, + IN VOID *Buffer, + IN CHAR8 *ActionString OPTIONAL + ) +{ + EFI_STATUS Status; + MEMORY_PROFILE_CONTEXT *Context; + MEMORY_PROFILE_DRIVER_INFO *DriverInfo; + MEMORY_PROFILE_ALLOC_INFO *AllocInfo; + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + MEMORY_PROFILE_ALLOC_INFO_DATA *AllocInfoData; + UINTN ProfileMemoryIndex; + MEMORY_PROFILE_ACTION BasicAction; + UINTN ActionStringSize; + UINTN ActionStringOccupiedSize; + + BasicAction = Action & MEMORY_PROFILE_ACTION_BASIC_MASK; + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + DriverInfoData = GetMemoryProfileDriverInfoFromAddress (ContextData, CallerAddress); + if (DriverInfoData == NULL) { + return EFI_UNSUPPORTED; + } + + ActionStringSize = 0; + ActionStringOccupiedSize = 0; + if (ActionString != NULL) { + ActionStringSize = AsciiStrSize (ActionString); + ActionStringOccupiedSize = GET_OCCUPIED_SIZE (ActionStringSize, sizeof (UINT64)); + } + + // + // Use CoreInternalAllocatePool() that will not update profile for this AllocatePool action. + // + AllocInfoData = NULL; + Status = CoreInternalAllocatePool ( + EfiBootServicesData, + sizeof (*AllocInfoData) + ActionStringSize, + (VOID **) &AllocInfoData + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + ASSERT (AllocInfoData != NULL); + + // + // Only update SequenceCount if and only if it is basic action. + // + if (Action == BasicAction) { + ContextData->Context.SequenceCount ++; + } + + AllocInfo = &AllocInfoData->AllocInfo; + AllocInfoData->Signature = MEMORY_PROFILE_ALLOC_INFO_SIGNATURE; + AllocInfo->Header.Signature = MEMORY_PROFILE_ALLOC_INFO_SIGNATURE; + AllocInfo->Header.Length = (UINT16) (sizeof (MEMORY_PROFILE_ALLOC_INFO) + ActionStringOccupiedSize); + AllocInfo->Header.Revision = MEMORY_PROFILE_ALLOC_INFO_REVISION; + AllocInfo->CallerAddress = CallerAddress; + AllocInfo->SequenceId = ContextData->Context.SequenceCount; + AllocInfo->Action = Action; + AllocInfo->MemoryType = MemoryType; + AllocInfo->Buffer = (PHYSICAL_ADDRESS) (UINTN) Buffer; + AllocInfo->Size = Size; + if (ActionString != NULL) { + AllocInfo->ActionStringOffset = (UINT16) sizeof (MEMORY_PROFILE_ALLOC_INFO); + AllocInfoData->ActionString = (CHAR8 *) (AllocInfoData + 1); + CopyMem (AllocInfoData->ActionString, ActionString, ActionStringSize); + } else { + AllocInfo->ActionStringOffset = 0; + AllocInfoData->ActionString = NULL; + } + + InsertTailList (DriverInfoData->AllocInfoList, &AllocInfoData->Link); + + Context = &ContextData->Context; + DriverInfo = &DriverInfoData->DriverInfo; + DriverInfo->AllocRecordCount ++; + + // + // Update summary if and only if it is basic action. + // + if (Action == BasicAction) { + ProfileMemoryIndex = GetProfileMemoryIndex (MemoryType); + + DriverInfo->CurrentUsage += Size; + if (DriverInfo->PeakUsage < DriverInfo->CurrentUsage) { + DriverInfo->PeakUsage = DriverInfo->CurrentUsage; + } + DriverInfo->CurrentUsageByType[ProfileMemoryIndex] += Size; + if (DriverInfo->PeakUsageByType[ProfileMemoryIndex] < DriverInfo->CurrentUsageByType[ProfileMemoryIndex]) { + DriverInfo->PeakUsageByType[ProfileMemoryIndex] = DriverInfo->CurrentUsageByType[ProfileMemoryIndex]; + } + + Context->CurrentTotalUsage += Size; + if (Context->PeakTotalUsage < Context->CurrentTotalUsage) { + Context->PeakTotalUsage = Context->CurrentTotalUsage; + } + Context->CurrentTotalUsageByType[ProfileMemoryIndex] += Size; + if (Context->PeakTotalUsageByType[ProfileMemoryIndex] < Context->CurrentTotalUsageByType[ProfileMemoryIndex]) { + Context->PeakTotalUsageByType[ProfileMemoryIndex] = Context->CurrentTotalUsageByType[ProfileMemoryIndex]; + } + } + + return EFI_SUCCESS; +} + +/** + Get memory profile alloc info from memory profile. + + @param DriverInfoData Driver info. + @param BasicAction This Free basic action. + @param Size Buffer size. + @param Buffer Buffer address. + + @return Pointer to memory profile alloc info. + +**/ +MEMORY_PROFILE_ALLOC_INFO_DATA * +GetMemoryProfileAllocInfoFromAddress ( + IN MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData, + IN MEMORY_PROFILE_ACTION BasicAction, + IN UINTN Size, + IN VOID *Buffer + ) +{ + LIST_ENTRY *AllocInfoList; + LIST_ENTRY *AllocLink; + MEMORY_PROFILE_ALLOC_INFO *AllocInfo; + MEMORY_PROFILE_ALLOC_INFO_DATA *AllocInfoData; + + AllocInfoList = DriverInfoData->AllocInfoList; + + for (AllocLink = AllocInfoList->ForwardLink; + AllocLink != AllocInfoList; + AllocLink = AllocLink->ForwardLink) { + AllocInfoData = CR ( + AllocLink, + MEMORY_PROFILE_ALLOC_INFO_DATA, + Link, + MEMORY_PROFILE_ALLOC_INFO_SIGNATURE + ); + AllocInfo = &AllocInfoData->AllocInfo; + if ((AllocInfo->Action & MEMORY_PROFILE_ACTION_BASIC_MASK) != BasicAction) { + continue; + } + switch (BasicAction) { + case MemoryProfileActionAllocatePages: + if ((AllocInfo->Buffer <= (PHYSICAL_ADDRESS) (UINTN) Buffer) && + ((AllocInfo->Buffer + AllocInfo->Size) >= ((PHYSICAL_ADDRESS) (UINTN) Buffer + Size))) { + return AllocInfoData; + } + break; + case MemoryProfileActionAllocatePool: + if (AllocInfo->Buffer == (PHYSICAL_ADDRESS) (UINTN) Buffer) { + return AllocInfoData; + } + break; + default: + ASSERT (FALSE); + break; + } + } + + return NULL; +} + +/** + Update memory profile Free information. + + @param CallerAddress Address of caller who call Free. + @param Action This Free action. + @param Size Buffer size. + @param Buffer Buffer address. + + @return EFI_SUCCESS Memory profile is updated. + @return EFI_UNSUPPORTED Memory profile is unsupported. + @return EFI_NOT_FOUND No matched allocate info found for free action. + +**/ +EFI_STATUS +CoreUpdateProfileFree ( + IN PHYSICAL_ADDRESS CallerAddress, + IN MEMORY_PROFILE_ACTION Action, + IN UINTN Size, + IN VOID *Buffer + ) +{ + MEMORY_PROFILE_CONTEXT *Context; + MEMORY_PROFILE_DRIVER_INFO *DriverInfo; + MEMORY_PROFILE_ALLOC_INFO *AllocInfo; + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + LIST_ENTRY *DriverLink; + LIST_ENTRY *DriverInfoList; + MEMORY_PROFILE_DRIVER_INFO_DATA *ThisDriverInfoData; + MEMORY_PROFILE_ALLOC_INFO_DATA *AllocInfoData; + UINTN ProfileMemoryIndex; + MEMORY_PROFILE_ACTION BasicAction; + BOOLEAN Found; + + BasicAction = Action & MEMORY_PROFILE_ACTION_BASIC_MASK; + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + DriverInfoData = GetMemoryProfileDriverInfoFromAddress (ContextData, CallerAddress); + + // + // Do not return if DriverInfoData == NULL here, + // because driver A might free memory allocated by driver B. + // + + // + // Need use do-while loop to find all possible records, + // because one address might be recorded multiple times. + // + Found = FALSE; + AllocInfoData = NULL; + do { + if (DriverInfoData != NULL) { + switch (BasicAction) { + case MemoryProfileActionFreePages: + AllocInfoData = GetMemoryProfileAllocInfoFromAddress (DriverInfoData, MemoryProfileActionAllocatePages, Size, Buffer); + break; + case MemoryProfileActionFreePool: + AllocInfoData = GetMemoryProfileAllocInfoFromAddress (DriverInfoData, MemoryProfileActionAllocatePool, 0, Buffer); + break; + default: + ASSERT (FALSE); + AllocInfoData = NULL; + break; + } + } + if (AllocInfoData == NULL) { + // + // Legal case, because driver A might free memory allocated by driver B, by some protocol. + // + DriverInfoList = ContextData->DriverInfoList; + + for (DriverLink = DriverInfoList->ForwardLink; + DriverLink != DriverInfoList; + DriverLink = DriverLink->ForwardLink) { + ThisDriverInfoData = CR ( + DriverLink, + MEMORY_PROFILE_DRIVER_INFO_DATA, + Link, + MEMORY_PROFILE_DRIVER_INFO_SIGNATURE + ); + switch (BasicAction) { + case MemoryProfileActionFreePages: + AllocInfoData = GetMemoryProfileAllocInfoFromAddress (ThisDriverInfoData, MemoryProfileActionAllocatePages, Size, Buffer); + break; + case MemoryProfileActionFreePool: + AllocInfoData = GetMemoryProfileAllocInfoFromAddress (ThisDriverInfoData, MemoryProfileActionAllocatePool, 0, Buffer); + break; + default: + ASSERT (FALSE); + AllocInfoData = NULL; + break; + } + if (AllocInfoData != NULL) { + DriverInfoData = ThisDriverInfoData; + break; + } + } + + if (AllocInfoData == NULL) { + // + // If (!Found), no matched allocate info is found for this free action. + // It is because the specified memory type allocate actions have been filtered by + // CoreNeedRecordProfile(), but free actions may have no memory type information, + // they can not be filtered by CoreNeedRecordProfile(). Then, they will be + // filtered here. + // + // If (Found), it is normal exit path. + return (Found ? EFI_SUCCESS : EFI_NOT_FOUND); + } + } + + ASSERT (DriverInfoData != NULL); + ASSERT (AllocInfoData != NULL); + + Found = TRUE; + + Context = &ContextData->Context; + DriverInfo = &DriverInfoData->DriverInfo; + AllocInfo = &AllocInfoData->AllocInfo; + + DriverInfo->AllocRecordCount --; + // + // Update summary if and only if it is basic action. + // + if (AllocInfo->Action == (AllocInfo->Action & MEMORY_PROFILE_ACTION_BASIC_MASK)) { + ProfileMemoryIndex = GetProfileMemoryIndex (AllocInfo->MemoryType); + + Context->CurrentTotalUsage -= AllocInfo->Size; + Context->CurrentTotalUsageByType[ProfileMemoryIndex] -= AllocInfo->Size; + + DriverInfo->CurrentUsage -= AllocInfo->Size; + DriverInfo->CurrentUsageByType[ProfileMemoryIndex] -= AllocInfo->Size; + } + + RemoveEntryList (&AllocInfoData->Link); + + if (BasicAction == MemoryProfileActionFreePages) { + if (AllocInfo->Buffer != (PHYSICAL_ADDRESS) (UINTN) Buffer) { + CoreUpdateProfileAllocate ( + AllocInfo->CallerAddress, + AllocInfo->Action, + AllocInfo->MemoryType, + (UINTN) ((PHYSICAL_ADDRESS) (UINTN) Buffer - AllocInfo->Buffer), + (VOID *) (UINTN) AllocInfo->Buffer, + AllocInfoData->ActionString + ); + } + if (AllocInfo->Buffer + AllocInfo->Size != ((PHYSICAL_ADDRESS) (UINTN) Buffer + Size)) { + CoreUpdateProfileAllocate ( + AllocInfo->CallerAddress, + AllocInfo->Action, + AllocInfo->MemoryType, + (UINTN) ((AllocInfo->Buffer + AllocInfo->Size) - ((PHYSICAL_ADDRESS) (UINTN) Buffer + Size)), + (VOID *) ((UINTN) Buffer + Size), + AllocInfoData->ActionString + ); + } + } + + // + // Use CoreInternalFreePool() that will not update profile for this FreePool action. + // + CoreInternalFreePool (AllocInfoData, NULL); + } while (TRUE); +} + +/** + Update memory profile information. + + @param CallerAddress Address of caller who call Allocate or Free. + @param Action This Allocate or Free action. + @param MemoryType Memory type. + EfiMaxMemoryType means the MemoryType is unknown. + @param Size Buffer size. + @param Buffer Buffer address. + @param ActionString String for memory profile action. + Only needed for user defined allocate action. + + @return EFI_SUCCESS Memory profile is updated. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required, + or memory profile for the memory type is not required. + @return EFI_ACCESS_DENIED It is during memory profile data getting. + @return EFI_ABORTED Memory profile recording is not enabled. + @return EFI_OUT_OF_RESOURCES No enough resource to update memory profile for allocate action. + @return EFI_NOT_FOUND No matched allocate info found for free action. + +**/ +EFI_STATUS +EFIAPI +CoreUpdateProfile ( + IN PHYSICAL_ADDRESS CallerAddress, + IN MEMORY_PROFILE_ACTION Action, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN Size, // Valid for AllocatePages/FreePages/AllocatePool + IN VOID *Buffer, + IN CHAR8 *ActionString OPTIONAL + ) +{ + EFI_STATUS Status; + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_ACTION BasicAction; + + if (!IS_UEFI_MEMORY_PROFILE_ENABLED) { + return EFI_UNSUPPORTED; + } + + if (mMemoryProfileGettingStatus) { + return EFI_ACCESS_DENIED; + } + + if (!mMemoryProfileRecordingEnable) { + return EFI_ABORTED; + } + + // + // Get the basic action to know how to process the record + // + BasicAction = Action & MEMORY_PROFILE_ACTION_BASIC_MASK; + + // + // EfiMaxMemoryType means the MemoryType is unknown. + // + if (MemoryType != EfiMaxMemoryType) { + // + // Only record limited MemoryType. + // + if (!CoreNeedRecordProfile (MemoryType)) { + return EFI_UNSUPPORTED; + } + } + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + CoreAcquireMemoryProfileLock (); + switch (BasicAction) { + case MemoryProfileActionAllocatePages: + Status = CoreUpdateProfileAllocate (CallerAddress, Action, MemoryType, Size, Buffer, ActionString); + break; + case MemoryProfileActionFreePages: + Status = CoreUpdateProfileFree (CallerAddress, Action, Size, Buffer); + break; + case MemoryProfileActionAllocatePool: + Status = CoreUpdateProfileAllocate (CallerAddress, Action, MemoryType, Size, Buffer, ActionString); + break; + case MemoryProfileActionFreePool: + Status = CoreUpdateProfileFree (CallerAddress, Action, 0, Buffer); + break; + default: + ASSERT (FALSE); + Status = EFI_UNSUPPORTED; + break; + } + CoreReleaseMemoryProfileLock (); + + return Status; +} + +//////////////////// + +/** + Get memory profile data size. + + @return Memory profile data size. + +**/ +UINTN +MemoryProfileGetDataSize ( + VOID + ) +{ + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + MEMORY_PROFILE_ALLOC_INFO_DATA *AllocInfoData; + LIST_ENTRY *DriverInfoList; + LIST_ENTRY *DriverLink; + LIST_ENTRY *AllocInfoList; + LIST_ENTRY *AllocLink; + UINTN TotalSize; + + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return 0; + } + + TotalSize = sizeof (MEMORY_PROFILE_CONTEXT); + + DriverInfoList = ContextData->DriverInfoList; + for (DriverLink = DriverInfoList->ForwardLink; + DriverLink != DriverInfoList; + DriverLink = DriverLink->ForwardLink) { + DriverInfoData = CR ( + DriverLink, + MEMORY_PROFILE_DRIVER_INFO_DATA, + Link, + MEMORY_PROFILE_DRIVER_INFO_SIGNATURE + ); + TotalSize += DriverInfoData->DriverInfo.Header.Length; + + AllocInfoList = DriverInfoData->AllocInfoList; + for (AllocLink = AllocInfoList->ForwardLink; + AllocLink != AllocInfoList; + AllocLink = AllocLink->ForwardLink) { + AllocInfoData = CR ( + AllocLink, + MEMORY_PROFILE_ALLOC_INFO_DATA, + Link, + MEMORY_PROFILE_ALLOC_INFO_SIGNATURE + ); + TotalSize += AllocInfoData->AllocInfo.Header.Length; + } + } + + return TotalSize; +} + +/** + Copy memory profile data. + + @param ProfileBuffer The buffer to hold memory profile data. + +**/ +VOID +MemoryProfileCopyData ( + IN VOID *ProfileBuffer + ) +{ + MEMORY_PROFILE_CONTEXT *Context; + MEMORY_PROFILE_DRIVER_INFO *DriverInfo; + MEMORY_PROFILE_ALLOC_INFO *AllocInfo; + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + MEMORY_PROFILE_DRIVER_INFO_DATA *DriverInfoData; + MEMORY_PROFILE_ALLOC_INFO_DATA *AllocInfoData; + LIST_ENTRY *DriverInfoList; + LIST_ENTRY *DriverLink; + LIST_ENTRY *AllocInfoList; + LIST_ENTRY *AllocLink; + UINTN PdbSize; + UINTN ActionStringSize; + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return ; + } + + Context = ProfileBuffer; + CopyMem (Context, &ContextData->Context, sizeof (MEMORY_PROFILE_CONTEXT)); + DriverInfo = (MEMORY_PROFILE_DRIVER_INFO *) (Context + 1); + + DriverInfoList = ContextData->DriverInfoList; + for (DriverLink = DriverInfoList->ForwardLink; + DriverLink != DriverInfoList; + DriverLink = DriverLink->ForwardLink) { + DriverInfoData = CR ( + DriverLink, + MEMORY_PROFILE_DRIVER_INFO_DATA, + Link, + MEMORY_PROFILE_DRIVER_INFO_SIGNATURE + ); + CopyMem (DriverInfo, &DriverInfoData->DriverInfo, sizeof (MEMORY_PROFILE_DRIVER_INFO)); + if (DriverInfo->PdbStringOffset != 0) { + PdbSize = AsciiStrSize (DriverInfoData->PdbString); + CopyMem ((VOID *) ((UINTN) DriverInfo + DriverInfo->PdbStringOffset), DriverInfoData->PdbString, PdbSize); + } + AllocInfo = (MEMORY_PROFILE_ALLOC_INFO *) ((UINTN) DriverInfo + DriverInfo->Header.Length); + + AllocInfoList = DriverInfoData->AllocInfoList; + for (AllocLink = AllocInfoList->ForwardLink; + AllocLink != AllocInfoList; + AllocLink = AllocLink->ForwardLink) { + AllocInfoData = CR ( + AllocLink, + MEMORY_PROFILE_ALLOC_INFO_DATA, + Link, + MEMORY_PROFILE_ALLOC_INFO_SIGNATURE + ); + CopyMem (AllocInfo, &AllocInfoData->AllocInfo, sizeof (MEMORY_PROFILE_ALLOC_INFO)); + if (AllocInfo->ActionStringOffset != 0) { + ActionStringSize = AsciiStrSize (AllocInfoData->ActionString); + CopyMem ((VOID *) ((UINTN) AllocInfo + AllocInfo->ActionStringOffset), AllocInfoData->ActionString, ActionStringSize); + } + AllocInfo = (MEMORY_PROFILE_ALLOC_INFO *) ((UINTN) AllocInfo + AllocInfo->Header.Length); + } + + DriverInfo = (MEMORY_PROFILE_DRIVER_INFO *) AllocInfo; + } +} + +/** + Get memory profile data. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in, out] ProfileSize On entry, points to the size in bytes of the ProfileBuffer. + On return, points to the size of the data returned in ProfileBuffer. + @param[out] ProfileBuffer Profile buffer. + + @return EFI_SUCCESS Get the memory profile data successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported. + @return EFI_BUFFER_TO_SMALL The ProfileSize is too small for the resulting data. + ProfileSize is updated with the size required. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolGetData ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN OUT UINT64 *ProfileSize, + OUT VOID *ProfileBuffer + ) +{ + UINTN Size; + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + BOOLEAN MemoryProfileGettingStatus; + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + MemoryProfileGettingStatus = mMemoryProfileGettingStatus; + mMemoryProfileGettingStatus = TRUE; + + Size = MemoryProfileGetDataSize (); + + if (*ProfileSize < Size) { + *ProfileSize = Size; + mMemoryProfileGettingStatus = MemoryProfileGettingStatus; + return EFI_BUFFER_TOO_SMALL; + } + + *ProfileSize = Size; + MemoryProfileCopyData (ProfileBuffer); + + mMemoryProfileGettingStatus = MemoryProfileGettingStatus; + return EFI_SUCCESS; +} + +/** + Register image to memory profile. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] FilePath File path of the image. + @param[in] ImageBase Image base address. + @param[in] ImageSize Image size. + @param[in] FileType File type of the image. + + @return EFI_SUCCESS Register successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_OUT_OF_RESOURCES No enough resource for this register. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolRegisterImage ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + IN PHYSICAL_ADDRESS ImageBase, + IN UINT64 ImageSize, + IN EFI_FV_FILETYPE FileType + ) +{ + EFI_STATUS Status; + LOADED_IMAGE_PRIVATE_DATA DriverEntry; + VOID *EntryPointInImage; + + ZeroMem (&DriverEntry, sizeof (DriverEntry)); + DriverEntry.Info.FilePath = FilePath; + DriverEntry.ImageContext.ImageAddress = ImageBase; + DriverEntry.ImageContext.ImageSize = ImageSize; + Status = InternalPeCoffGetEntryPoint ((VOID *) (UINTN) ImageBase, &EntryPointInImage); + ASSERT_EFI_ERROR (Status); + DriverEntry.ImageContext.EntryPoint = (PHYSICAL_ADDRESS) (UINTN) EntryPointInImage; + DriverEntry.ImageContext.ImageType = InternalPeCoffGetSubsystem ((VOID *) (UINTN) ImageBase); + + return RegisterMemoryProfileImage (&DriverEntry, FileType); +} + +/** + Unregister image from memory profile. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] FilePath File path of the image. + @param[in] ImageBase Image base address. + @param[in] ImageSize Image size. + + @return EFI_SUCCESS Unregister successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required. + @return EFI_NOT_FOUND The image is not found. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolUnregisterImage ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + IN PHYSICAL_ADDRESS ImageBase, + IN UINT64 ImageSize + ) +{ + EFI_STATUS Status; + LOADED_IMAGE_PRIVATE_DATA DriverEntry; + VOID *EntryPointInImage; + + ZeroMem (&DriverEntry, sizeof (DriverEntry)); + DriverEntry.Info.FilePath = FilePath; + DriverEntry.ImageContext.ImageAddress = ImageBase; + DriverEntry.ImageContext.ImageSize = ImageSize; + Status = InternalPeCoffGetEntryPoint ((VOID *) (UINTN) ImageBase, &EntryPointInImage); + ASSERT_EFI_ERROR (Status); + DriverEntry.ImageContext.EntryPoint = (PHYSICAL_ADDRESS) (UINTN) EntryPointInImage; + + return UnregisterMemoryProfileImage (&DriverEntry); +} + +/** + Get memory profile recording state. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[out] RecordingState Recording state. + + @return EFI_SUCCESS Memory profile recording state is returned. + @return EFI_UNSUPPORTED Memory profile is unsupported. + @return EFI_INVALID_PARAMETER RecordingState is NULL. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolGetRecordingState ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + OUT BOOLEAN *RecordingState + ) +{ + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + if (RecordingState == NULL) { + return EFI_INVALID_PARAMETER; + } + *RecordingState = mMemoryProfileRecordingEnable; + return EFI_SUCCESS; +} + +/** + Set memory profile recording state. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] RecordingState Recording state. + + @return EFI_SUCCESS Set memory profile recording state successfully. + @return EFI_UNSUPPORTED Memory profile is unsupported. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolSetRecordingState ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN BOOLEAN RecordingState + ) +{ + MEMORY_PROFILE_CONTEXT_DATA *ContextData; + + ContextData = GetMemoryProfileContext (); + if (ContextData == NULL) { + return EFI_UNSUPPORTED; + } + + mMemoryProfileRecordingEnable = RecordingState; + return EFI_SUCCESS; +} + +/** + Record memory profile of multilevel caller. + + @param[in] This The EDKII_MEMORY_PROFILE_PROTOCOL instance. + @param[in] CallerAddress Address of caller. + @param[in] Action Memory profile action. + @param[in] MemoryType Memory type. + EfiMaxMemoryType means the MemoryType is unknown. + @param[in] Buffer Buffer address. + @param[in] Size Buffer size. + @param[in] ActionString String for memory profile action. + Only needed for user defined allocate action. + + @return EFI_SUCCESS Memory profile is updated. + @return EFI_UNSUPPORTED Memory profile is unsupported, + or memory profile for the image is not required, + or memory profile for the memory type is not required. + @return EFI_ACCESS_DENIED It is during memory profile data getting. + @return EFI_ABORTED Memory profile recording is not enabled. + @return EFI_OUT_OF_RESOURCES No enough resource to update memory profile for allocate action. + @return EFI_NOT_FOUND No matched allocate info found for free action. + +**/ +EFI_STATUS +EFIAPI +ProfileProtocolRecord ( + IN EDKII_MEMORY_PROFILE_PROTOCOL *This, + IN PHYSICAL_ADDRESS CallerAddress, + IN MEMORY_PROFILE_ACTION Action, + IN EFI_MEMORY_TYPE MemoryType, + IN VOID *Buffer, + IN UINTN Size, + IN CHAR8 *ActionString OPTIONAL + ) +{ + return CoreUpdateProfile (CallerAddress, Action, MemoryType, Size, Buffer, ActionString); +} + +//////////////////// diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Page.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Page.c new file mode 100644 index 00000000..d5bbbb16 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Page.c @@ -0,0 +1,2105 @@ +/** @file + UEFI Memory page management functions. + +Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "DxeMain.h" +#include "Imem.h" +#include "HeapGuard.h" + +// +// 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; + +// +// MemoryMap - The current memory map +// +UINTN mMemoryMapKey = 0; + +#define MAX_MAP_DEPTH 6 + +/// +/// mMapDepth - depth of new descriptor stack +/// +UINTN mMapDepth = 0; +/// +/// mMapStack - space to use as temp storage to build new map descriptors +/// +MEMORY_MAP mMapStack[MAX_MAP_DEPTH]; +UINTN mFreeMapStack = 0; +/// +/// This list maintain the free memory map list +/// +LIST_ENTRY mFreeMemoryMapEntryList = INITIALIZE_LIST_HEAD_VARIABLE (mFreeMemoryMapEntryList); +BOOLEAN mMemoryTypeInformationInitialized = FALSE; + +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, 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 }, + { EfiMaxMemoryType, 0 } +}; +// +// Only used when load module at fixed address feature is enabled. True means the memory is alreay successfully allocated +// and ready to load the module in to specified address.or else, the memory is not ready and module will be loaded at a +// address assigned by DXE core. +// +GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN gLoadFixedAddressCodeMemoryReady = FALSE; + +/** + Enter critical section by gaining lock on gMemoryLock. + +**/ +VOID +CoreAcquireMemoryLock ( + VOID + ) +{ + CoreAcquireLock (&gMemoryLock); +} + + + +/** + Exit critical section by releasing lock on gMemoryLock. + +**/ +VOID +CoreReleaseMemoryLock ( + VOID + ) +{ + CoreReleaseLock (&gMemoryLock); +} + + + + +/** + Internal function. Removes a descriptor entry. + + @param Entry The entry to remove + +**/ +VOID +RemoveMemoryMapEntry ( + IN OUT MEMORY_MAP *Entry + ) +{ + RemoveEntryList (&Entry->Link); + Entry->Link.ForwardLink = NULL; + + if (Entry->FromPages) { + // + // Insert the free memory map descriptor to the end of mFreeMemoryMapEntryList + // + InsertTailList (&mFreeMemoryMapEntryList, &Entry->Link); + } +} + +/** + Internal function. Adds a ranges to the memory map. + The range must not already exist in the map. + + @param Type The type of memory range to add + @param Start The starting address in the memory range Must be + paged aligned + @param End The last address in the range Must be the last + byte of a page + @param Attribute The attributes of the memory range to add + +**/ +VOID +CoreAddRange ( + IN EFI_MEMORY_TYPE Type, + IN EFI_PHYSICAL_ADDRESS Start, + IN EFI_PHYSICAL_ADDRESS End, + IN UINT64 Attribute + ) +{ + LIST_ENTRY *Link; + MEMORY_MAP *Entry; + + ASSERT ((Start & EFI_PAGE_MASK) == 0); + ASSERT (End > Start) ; + + ASSERT_LOCKED (&gMemoryLock); + + DEBUG ((DEBUG_PAGE, "AddRange: %lx-%lx to %d\n", Start, End, Type)); + + // + // If memory of type EfiConventionalMemory is being added that includes the page + // starting at address 0, then zero the page starting at address 0. This has + // two benifits. It helps find NULL pointer bugs and it also maximizes + // compatibility with operating systems that may evaluate memory in this page + // for legacy data structures. If memory of any other type is added starting + // at address 0, then do not zero the page at address 0 because the page is being + // used for other purposes. + // + if (Type == EfiConventionalMemory && Start == 0 && (End >= EFI_PAGE_SIZE - 1)) { + if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & BIT0) == 0) { + SetMem ((VOID *)(UINTN)Start, EFI_PAGE_SIZE, 0); + } + } + + // + // Memory map being altered so updated key + // + mMemoryMapKey += 1; + + // + // UEFI 2.0 added an event group for notificaiton on memory map changes. + // So we need to signal this Event Group every time the memory map changes. + // If we are in EFI 1.10 compatability mode no event groups will be + // found and nothing will happen we we call this function. These events + // will get signaled but since a lock is held around the call to this + // function the notificaiton events will only be called after this function + // returns and the lock is released. + // + CoreNotifySignalList (&gEfiEventMemoryMapChangeGuid); + + // + // Look for adjoining memory descriptor + // + + // Two memory descriptors can only be merged if they have the same Type + // and the same Attribute + // + + Link = gMemoryMap.ForwardLink; + while (Link != &gMemoryMap) { + Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + Link = Link->ForwardLink; + + if (Entry->Type != Type) { + continue; + } + + if (Entry->Attribute != Attribute) { + continue; + } + + if (Entry->End + 1 == Start) { + + Start = Entry->Start; + RemoveMemoryMapEntry (Entry); + + } else if (Entry->Start == End + 1) { + + End = Entry->End; + RemoveMemoryMapEntry (Entry); + } + } + + // + // Add descriptor + // + + mMapStack[mMapDepth].Signature = MEMORY_MAP_SIGNATURE; + mMapStack[mMapDepth].FromPages = FALSE; + mMapStack[mMapDepth].Type = Type; + mMapStack[mMapDepth].Start = Start; + mMapStack[mMapDepth].End = End; + mMapStack[mMapDepth].VirtualStart = 0; + mMapStack[mMapDepth].Attribute = Attribute; + InsertTailList (&gMemoryMap, &mMapStack[mMapDepth].Link); + + mMapDepth += 1; + ASSERT (mMapDepth < MAX_MAP_DEPTH); + + return ; +} + +/** + Internal function. Deque a descriptor entry from the mFreeMemoryMapEntryList. + If the list is emtry, then allocate a new page to refuel the list. + Please Note this algorithm to allocate the memory map descriptor has a property + that the memory allocated for memory entries always grows, and will never really be freed + For example, if the current boot uses 2000 memory map entries at the maximum point, but + ends up with only 50 at the time the OS is booted, then the memory associated with the 1950 + memory map entries is still allocated from EfiBootServicesMemory. + + + @return The Memory map descriptor dequed from the mFreeMemoryMapEntryList + +**/ +MEMORY_MAP * +AllocateMemoryMapEntry ( + VOID + ) +{ + MEMORY_MAP* FreeDescriptorEntries; + MEMORY_MAP* Entry; + UINTN Index; + + if (IsListEmpty (&mFreeMemoryMapEntryList)) { + // + // The list is empty, to allocate one page to refuel the list + // + FreeDescriptorEntries = CoreAllocatePoolPages ( + EfiBootServicesData, + EFI_SIZE_TO_PAGES (DEFAULT_PAGE_ALLOCATION_GRANULARITY), + DEFAULT_PAGE_ALLOCATION_GRANULARITY, + FALSE + ); + if (FreeDescriptorEntries != NULL) { + // + // Enque the free memmory map entries into the list + // + for (Index = 0; Index < DEFAULT_PAGE_ALLOCATION_GRANULARITY / sizeof(MEMORY_MAP); Index++) { + FreeDescriptorEntries[Index].Signature = MEMORY_MAP_SIGNATURE; + InsertTailList (&mFreeMemoryMapEntryList, &FreeDescriptorEntries[Index].Link); + } + } else { + return NULL; + } + } + // + // dequeue the first descriptor from the list + // + Entry = CR (mFreeMemoryMapEntryList.ForwardLink, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + RemoveEntryList (&Entry->Link); + + return Entry; +} + + +/** + Internal function. Moves any memory descriptors that are on the + temporary descriptor stack to heap. + +**/ +VOID +CoreFreeMemoryMapStack ( + VOID + ) +{ + MEMORY_MAP *Entry; + MEMORY_MAP *Entry2; + LIST_ENTRY *Link2; + + ASSERT_LOCKED (&gMemoryLock); + + // + // If already freeing the map stack, then return + // + if (mFreeMapStack != 0) { + return ; + } + + // + // Move the temporary memory descriptor stack into pool + // + mFreeMapStack += 1; + + while (mMapDepth != 0) { + // + // Deque an memory map entry from mFreeMemoryMapEntryList + // + Entry = AllocateMemoryMapEntry (); + + ASSERT (Entry); + + // + // Update to proper entry + // + mMapDepth -= 1; + + if (mMapStack[mMapDepth].Link.ForwardLink != NULL) { + + // + // Move this entry to general memory + // + RemoveEntryList (&mMapStack[mMapDepth].Link); + mMapStack[mMapDepth].Link.ForwardLink = NULL; + + CopyMem (Entry , &mMapStack[mMapDepth], sizeof (MEMORY_MAP)); + Entry->FromPages = TRUE; + + // + // Find insertion location + // + for (Link2 = gMemoryMap.ForwardLink; Link2 != &gMemoryMap; Link2 = Link2->ForwardLink) { + Entry2 = CR (Link2, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + if (Entry2->FromPages && Entry2->Start > Entry->Start) { + break; + } + } + + InsertTailList (Link2, &Entry->Link); + + } else { + // + // This item of mMapStack[mMapDepth] has already been dequeued from gMemoryMap list, + // so here no need to move it to memory. + // + InsertTailList (&mFreeMemoryMapEntryList, &Entry->Link); + } + } + + mFreeMapStack -= 1; +} + +/** + Find untested but initialized memory regions in GCD map and convert them to be DXE allocatable. + +**/ +BOOLEAN +PromoteMemoryResource ( + VOID + ) +{ + LIST_ENTRY *Link; + EFI_GCD_MAP_ENTRY *Entry; + BOOLEAN Promoted; + EFI_PHYSICAL_ADDRESS StartAddress; + EFI_PHYSICAL_ADDRESS EndAddress; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor; + + DEBUG ((DEBUG_PAGE, "Promote the memory resource\n")); + + CoreAcquireGcdMemoryLock (); + + Promoted = FALSE; + Link = mGcdMemorySpaceMap.ForwardLink; + while (Link != &mGcdMemorySpaceMap) { + + Entry = CR (Link, EFI_GCD_MAP_ENTRY, Link, EFI_GCD_MAP_SIGNATURE); + + if (Entry->GcdMemoryType == EfiGcdMemoryTypeReserved && + Entry->EndAddress < MAX_ALLOC_ADDRESS && + (Entry->Capabilities & (EFI_MEMORY_PRESENT | EFI_MEMORY_INITIALIZED | EFI_MEMORY_TESTED)) == + (EFI_MEMORY_PRESENT | EFI_MEMORY_INITIALIZED)) { + // + // Update the GCD map + // + if ((Entry->Capabilities & EFI_MEMORY_MORE_RELIABLE) == EFI_MEMORY_MORE_RELIABLE) { + Entry->GcdMemoryType = EfiGcdMemoryTypeMoreReliable; + } else { + Entry->GcdMemoryType = EfiGcdMemoryTypeSystemMemory; + } + Entry->Capabilities |= EFI_MEMORY_TESTED; + Entry->ImageHandle = gDxeCoreImageHandle; + Entry->DeviceHandle = NULL; + + // + // Add to allocable system memory resource + // + + CoreAddRange ( + EfiConventionalMemory, + Entry->BaseAddress, + Entry->EndAddress, + Entry->Capabilities & ~(EFI_MEMORY_PRESENT | EFI_MEMORY_INITIALIZED | EFI_MEMORY_TESTED | EFI_MEMORY_RUNTIME) + ); + CoreFreeMemoryMapStack (); + + Promoted = TRUE; + } + + Link = Link->ForwardLink; + } + + CoreReleaseGcdMemoryLock (); + + if (!Promoted) { + // + // If freed-memory guard is enabled, we could promote pages from + // guarded free pages. + // + Promoted = PromoteGuardedFreePages (&StartAddress, &EndAddress); + if (Promoted) { + CoreGetMemorySpaceDescriptor (StartAddress, &Descriptor); + CoreAddRange ( + EfiConventionalMemory, + StartAddress, + EndAddress, + Descriptor.Capabilities & ~(EFI_MEMORY_PRESENT | EFI_MEMORY_INITIALIZED | + EFI_MEMORY_TESTED | EFI_MEMORY_RUNTIME) + ); + } + } + + return Promoted; +} +/** + This function try to allocate Runtime code & Boot time code memory range. If LMFA enabled, 2 patchable PCD + PcdLoadFixAddressRuntimeCodePageNumber & PcdLoadFixAddressBootTimeCodePageNumber which are set by tools will record the + size of boot time and runtime code. + +**/ +VOID +CoreLoadingFixedAddressHook ( + VOID + ) +{ + UINT32 RuntimeCodePageNumber; + UINT32 BootTimeCodePageNumber; + EFI_PHYSICAL_ADDRESS RuntimeCodeBase; + EFI_PHYSICAL_ADDRESS BootTimeCodeBase; + EFI_STATUS Status; + + // + // Make sure these 2 areas are not initialzied. + // + if (!gLoadFixedAddressCodeMemoryReady) { + RuntimeCodePageNumber = PcdGet32(PcdLoadFixAddressRuntimeCodePageNumber); + BootTimeCodePageNumber= PcdGet32(PcdLoadFixAddressBootTimeCodePageNumber); + RuntimeCodeBase = (EFI_PHYSICAL_ADDRESS)(gLoadModuleAtFixAddressConfigurationTable.DxeCodeTopAddress - EFI_PAGES_TO_SIZE (RuntimeCodePageNumber)); + BootTimeCodeBase = (EFI_PHYSICAL_ADDRESS)(RuntimeCodeBase - EFI_PAGES_TO_SIZE (BootTimeCodePageNumber)); + // + // Try to allocate runtime memory. + // + Status = CoreAllocatePages ( + AllocateAddress, + EfiRuntimeServicesCode, + RuntimeCodePageNumber, + &RuntimeCodeBase + ); + if (EFI_ERROR(Status)) { + // + // Runtime memory allocation failed + // + return; + } + // + // Try to allocate boot memory. + // + Status = CoreAllocatePages ( + AllocateAddress, + EfiBootServicesCode, + BootTimeCodePageNumber, + &BootTimeCodeBase + ); + if (EFI_ERROR(Status)) { + // + // boot memory allocation failed. Free Runtime code range and will try the allocation again when + // new memory range is installed. + // + CoreFreePages ( + RuntimeCodeBase, + RuntimeCodePageNumber + ); + return; + } + gLoadFixedAddressCodeMemoryReady = TRUE; + } + return; +} + +/** + Called to initialize the memory map and add descriptors to + the current descriptor list. + The first descriptor that is added must be general usable + memory as the addition allocates heap. + + @param Type The type of memory to add + @param Start The starting address in the memory range Must be + page aligned + @param NumberOfPages The number of pages in the range + @param Attribute Attributes of the memory to add + + @return None. The range is added to the memory map + +**/ +VOID +CoreAddMemoryDescriptor ( + IN EFI_MEMORY_TYPE Type, + IN EFI_PHYSICAL_ADDRESS Start, + IN UINT64 NumberOfPages, + IN UINT64 Attribute + ) +{ + EFI_PHYSICAL_ADDRESS End; + EFI_STATUS Status; + UINTN Index; + UINTN FreeIndex; + + if ((Start & EFI_PAGE_MASK) != 0) { + return; + } + + if (Type >= EfiMaxMemoryType && Type < MEMORY_TYPE_OEM_RESERVED_MIN) { + return; + } + CoreAcquireMemoryLock (); + End = Start + LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT) - 1; + CoreAddRange (Type, Start, End, Attribute); + CoreFreeMemoryMapStack (); + CoreReleaseMemoryLock (); + + ApplyMemoryProtectionPolicy (EfiMaxMemoryType, Type, Start, + LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT)); + + // + // If Loading Module At Fixed Address feature is enabled. try to allocate memory with Runtime code & Boot time code type + // + if (PcdGet64(PcdLoadModuleAtFixAddressEnable) != 0) { + 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) { + 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; +} + + +/** + Internal function. Converts a memory range to the specified type or attributes. + The range must exist in the memory map. Either ChangingType or + ChangingAttributes must be set, but not both. + + @param Start The first address of the range Must be page + aligned + @param NumberOfPages The number of pages to convert + @param ChangingType Boolean indicating that type value should be changed + @param NewType The new type for the memory range + @param ChangingAttributes Boolean indicating that attributes value should be changed + @param NewAttributes The new attributes for the memory range + + @retval EFI_INVALID_PARAMETER Invalid parameter + @retval EFI_NOT_FOUND Could not find a descriptor cover the specified + range or convertion not allowed. + @retval EFI_SUCCESS Successfully converts the memory range to the + specified type. + +**/ +EFI_STATUS +CoreConvertPagesEx ( + IN UINT64 Start, + IN UINT64 NumberOfPages, + IN BOOLEAN ChangingType, + IN EFI_MEMORY_TYPE NewType, + IN BOOLEAN ChangingAttributes, + IN UINT64 NewAttributes + ) +{ + + UINT64 NumberOfBytes; + UINT64 End; + UINT64 RangeEnd; + UINT64 Attribute; + EFI_MEMORY_TYPE MemType; + LIST_ENTRY *Link; + MEMORY_MAP *Entry; + + Entry = NULL; + NumberOfBytes = LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT); + End = Start + NumberOfBytes - 1; + + ASSERT (NumberOfPages); + ASSERT ((Start & EFI_PAGE_MASK) == 0); + ASSERT (End > Start) ; + ASSERT_LOCKED (&gMemoryLock); + ASSERT ( (ChangingType == FALSE) || (ChangingAttributes == FALSE) ); + + if (NumberOfPages == 0 || ((Start & EFI_PAGE_MASK) != 0) || (Start >= End)) { + return EFI_INVALID_PARAMETER; + } + + // + // Convert the entire range + // + + while (Start < End) { + + // + // Find the entry that the covers the range + // + for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) { + Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + + if (Entry->Start <= Start && Entry->End > Start) { + break; + } + } + + if (Link == &gMemoryMap) { + DEBUG ((DEBUG_ERROR | DEBUG_PAGE, "ConvertPages: failed to find range %lx - %lx\n", Start, End)); + return EFI_NOT_FOUND; + } + + // + // If we are converting the type of the range from EfiConventionalMemory to + // another type, we have to ensure that the entire range is covered by a + // single entry. + // + if (ChangingType && (NewType != EfiConventionalMemory)) { + if (Entry->End < End) { + DEBUG ((DEBUG_ERROR | DEBUG_PAGE, "ConvertPages: range %lx - %lx covers multiple entries\n", Start, End)); + return EFI_NOT_FOUND; + } + } + // + // Convert range to the end, or to the end of the descriptor + // if that's all we've got + // + RangeEnd = End; + + ASSERT (Entry != NULL); + if (Entry->End < End) { + RangeEnd = Entry->End; + } + + if (ChangingType) { + DEBUG ((DEBUG_PAGE, "ConvertRange: %lx-%lx to type %d\n", Start, RangeEnd, NewType)); + } + if (ChangingAttributes) { + DEBUG ((DEBUG_PAGE, "ConvertRange: %lx-%lx to attr %lx\n", Start, RangeEnd, NewAttributes)); + } + + if (ChangingType) { + // + // Debug code - verify conversion is allowed + // + if (!(NewType == EfiConventionalMemory ? 1 : 0) ^ (Entry->Type == EfiConventionalMemory ? 1 : 0)) { + DEBUG ((DEBUG_ERROR | DEBUG_PAGE, "ConvertPages: Incompatible memory types, ")); + if (Entry->Type == EfiConventionalMemory) { + DEBUG ((DEBUG_ERROR | DEBUG_PAGE, "the pages to free have been freed\n")); + } else { + DEBUG ((DEBUG_ERROR | DEBUG_PAGE, "the pages to allocate have been allocated\n")); + } + 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; + } + } + } + } + + // + // Pull range out of descriptor + // + if (Entry->Start == Start) { + + // + // Clip start + // + Entry->Start = RangeEnd + 1; + + } else if (Entry->End == RangeEnd) { + + // + // Clip end + // + Entry->End = Start - 1; + + } else { + + // + // Pull it out of the center, clip current + // + + // + // Add a new one + // + mMapStack[mMapDepth].Signature = MEMORY_MAP_SIGNATURE; + mMapStack[mMapDepth].FromPages = FALSE; + mMapStack[mMapDepth].Type = Entry->Type; + mMapStack[mMapDepth].Start = RangeEnd+1; + mMapStack[mMapDepth].End = Entry->End; + + // + // Inherit Attribute from the Memory Descriptor that is being clipped + // + mMapStack[mMapDepth].Attribute = Entry->Attribute; + + Entry->End = Start - 1; + ASSERT (Entry->Start < Entry->End); + + Entry = &mMapStack[mMapDepth]; + InsertTailList (&gMemoryMap, &Entry->Link); + + mMapDepth += 1; + ASSERT (mMapDepth < MAX_MAP_DEPTH); + } + + // + // The new range inherits the same Attribute as the Entry + // it is being cut out of unless attributes are being changed + // + if (ChangingType) { + Attribute = Entry->Attribute; + MemType = NewType; + } else { + Attribute = NewAttributes; + MemType = Entry->Type; + } + + // + // If the descriptor is empty, then remove it from the map + // + if (Entry->Start == Entry->End + 1) { + RemoveMemoryMapEntry (Entry); + Entry = NULL; + } + + // + // Add our new range in. Don't do this for freed pages if freed-memory + // guard is enabled. + // + if (!IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED) || + !ChangingType || + MemType != EfiConventionalMemory) { + CoreAddRange (MemType, Start, RangeEnd, Attribute); + } + + if (ChangingType && (MemType == EfiConventionalMemory)) { + // + // Avoid calling DEBUG_CLEAR_MEMORY() for an address of 0 because this + // macro will ASSERT() if address is 0. Instead, CoreAddRange() guarantees + // that the page starting at address 0 is always filled with zeros. + // + if (Start == 0) { + if (RangeEnd > EFI_PAGE_SIZE) { + DEBUG_CLEAR_MEMORY ((VOID *)(UINTN) EFI_PAGE_SIZE, (UINTN) (RangeEnd - EFI_PAGE_SIZE + 1)); + } + } else { + DEBUG_CLEAR_MEMORY ((VOID *)(UINTN) Start, (UINTN) (RangeEnd - Start + 1)); + } + } + + // + // Move any map descriptor stack to general pool + // + CoreFreeMemoryMapStack (); + + // + // Bump the starting address, and convert the next range + // + Start = RangeEnd + 1; + } + + // + // Converted the whole range, done + // + + return EFI_SUCCESS; +} + + +/** + Internal function. Converts a memory range to the specified type. + The range must exist in the memory map. + + @param Start The first address of the range Must be page + aligned + @param NumberOfPages The number of pages to convert + @param NewType The new type for the memory range + + @retval EFI_INVALID_PARAMETER Invalid parameter + @retval EFI_NOT_FOUND Could not find a descriptor cover the specified + range or convertion not allowed. + @retval EFI_SUCCESS Successfully converts the memory range to the + specified type. + +**/ +EFI_STATUS +CoreConvertPages ( + IN UINT64 Start, + IN UINT64 NumberOfPages, + IN EFI_MEMORY_TYPE NewType + ) +{ + return CoreConvertPagesEx(Start, NumberOfPages, TRUE, NewType, FALSE, 0); +} + + +/** + Internal function. Converts a memory range to use new attributes. + + @param Start The first address of the range Must be page + aligned + @param NumberOfPages The number of pages to convert + @param NewAttributes The new attributes value for the range. + +**/ +VOID +CoreUpdateMemoryAttributes ( + IN EFI_PHYSICAL_ADDRESS Start, + IN UINT64 NumberOfPages, + IN UINT64 NewAttributes + ) +{ + CoreAcquireMemoryLock (); + + // + // Update the attributes to the new value + // + CoreConvertPagesEx(Start, NumberOfPages, FALSE, (EFI_MEMORY_TYPE)0, TRUE, NewAttributes); + + CoreReleaseMemoryLock (); +} + + +/** + Internal function. Finds a consecutive free page range below + the requested address. + + @param MaxAddress The address that the range must be below + @param MinAddress The address that the range must be above + @param NumberOfPages Number of pages needed + @param NewType The type of memory the range is going to be + turned into + @param Alignment Bits to align with + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The base address of the range, or 0 if the range was not found + +**/ +UINT64 +CoreFindFreePagesI ( + IN UINT64 MaxAddress, + IN UINT64 MinAddress, + IN UINT64 NumberOfPages, + IN EFI_MEMORY_TYPE NewType, + IN UINTN Alignment, + IN BOOLEAN NeedGuard + ) +{ + UINT64 NumberOfBytes; + UINT64 Target; + UINT64 DescStart; + UINT64 DescEnd; + UINT64 DescNumberOfBytes; + LIST_ENTRY *Link; + MEMORY_MAP *Entry; + + if ((MaxAddress < EFI_PAGE_MASK) ||(NumberOfPages == 0)) { + return 0; + } + + if ((MaxAddress & EFI_PAGE_MASK) != EFI_PAGE_MASK) { + + // + // If MaxAddress is not aligned to the end of a page + // + + // + // Change MaxAddress to be 1 page lower + // + MaxAddress -= (EFI_PAGE_MASK + 1); + + // + // Set MaxAddress to a page boundary + // + MaxAddress &= ~(UINT64)EFI_PAGE_MASK; + + // + // Set MaxAddress to end of the page + // + MaxAddress |= EFI_PAGE_MASK; + } + + NumberOfBytes = LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT); + Target = 0; + + for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) { + Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + + // + // If it's not a free entry, don't bother with it + // + if (Entry->Type != EfiConventionalMemory) { + continue; + } + + DescStart = Entry->Start; + DescEnd = Entry->End; + + // + // If desc is past max allowed address or below min allowed address, skip it + // + if ((DescStart >= MaxAddress) || (DescEnd < MinAddress)) { + continue; + } + + // + // If desc ends past max allowed address, clip the end + // + if (DescEnd >= MaxAddress) { + DescEnd = MaxAddress; + } + + DescEnd = ((DescEnd + 1) & (~(Alignment - 1))) - 1; + + // Skip if DescEnd is less than DescStart after alignment clipping + if (DescEnd < DescStart) { + continue; + } + + // + // Compute the number of bytes we can used from this + // descriptor, and see it's enough to satisfy the request + // + DescNumberOfBytes = DescEnd - DescStart + 1; + + if (DescNumberOfBytes >= NumberOfBytes) { + // + // If the start of the allocated range is below the min address allowed, skip it + // + if ((DescEnd - NumberOfBytes + 1) < MinAddress) { + continue; + } + + // + // If this is the best match so far remember it + // + if (DescEnd > Target) { + if (NeedGuard) { + DescEnd = AdjustMemoryS ( + DescEnd + 1 - DescNumberOfBytes, + DescNumberOfBytes, + NumberOfBytes + ); + if (DescEnd == 0) { + continue; + } + } + + Target = DescEnd; + } + } + } + + // + // If this is a grow down, adjust target to be the allocation base + // + Target -= NumberOfBytes - 1; + + // + // If we didn't find a match, return 0 + // + if ((Target & EFI_PAGE_MASK) != 0) { + return 0; + } + + return Target; +} + + +/** + Internal function. Finds a consecutive free page range below + the requested address + + @param MaxAddress The address that the range must be below + @param NoPages Number of pages needed + @param NewType The type of memory the range is going to be + turned into + @param Alignment Bits to align with + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The base address of the range, or 0 if the range was not found. + +**/ +UINT64 +FindFreePages ( + IN UINT64 MaxAddress, + IN UINT64 NoPages, + IN EFI_MEMORY_TYPE NewType, + IN UINTN Alignment, + IN BOOLEAN NeedGuard + ) +{ + UINT64 Start; + + // + // Attempt to find free pages in the preferred bin based on the requested memory type + // + if ((UINT32)NewType < EfiMaxMemoryType && MaxAddress >= mMemoryTypeStatistics[NewType].MaximumAddress) { + Start = CoreFindFreePagesI ( + mMemoryTypeStatistics[NewType].MaximumAddress, + mMemoryTypeStatistics[NewType].BaseAddress, + NoPages, + NewType, + Alignment, + NeedGuard + ); + if (Start != 0) { + return Start; + } + } + + // + // Attempt to find free pages in the default allocation bin + // + if (MaxAddress >= mDefaultMaximumAddress) { + Start = CoreFindFreePagesI (mDefaultMaximumAddress, 0, NoPages, NewType, + Alignment, NeedGuard); + if (Start != 0) { + if (Start < mDefaultBaseAddress) { + mDefaultBaseAddress = Start; + } + return Start; + } + } + + // + // The allocation did not succeed in any of the prefered bins even after + // promoting resources. Attempt to find free pages anywhere is the requested + // address range. If this allocation fails, then there are not enough + // resources anywhere to satisfy the request. + // + Start = CoreFindFreePagesI (MaxAddress, 0, NoPages, NewType, Alignment, + NeedGuard); + if (Start != 0) { + return Start; + } + + // + // If allocations from the preferred bins fail, then attempt to promote memory resources. + // + if (!PromoteMemoryResource ()) { + return 0; + } + + // + // If any memory resources were promoted, then re-attempt the allocation + // + return FindFreePages (MaxAddress, NoPages, NewType, Alignment, NeedGuard); +} + + +/** + Allocates pages from the memory map. + + @param Type The type of allocation to perform + @param MemoryType The type of memory to turn the allocated pages + into + @param NumberOfPages The number of pages to allocate + @param Memory A pointer to receive the base allocated memory + address + @param NeedGuard Flag to indicate Guard page is needed or not + + @return Status. On success, Memory is filled in with the base address allocated + @retval EFI_INVALID_PARAMETER Parameters violate checking rules defined in + spec. + @retval EFI_NOT_FOUND Could not allocate pages match the requirement. + @retval EFI_OUT_OF_RESOURCES No enough pages to allocate. + @retval EFI_SUCCESS Pages successfully allocated. + +**/ +EFI_STATUS +EFIAPI +CoreInternalAllocatePages ( + IN EFI_ALLOCATE_TYPE Type, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN NumberOfPages, + IN OUT EFI_PHYSICAL_ADDRESS *Memory, + IN BOOLEAN NeedGuard + ) +{ + EFI_STATUS Status; + UINT64 Start; + UINT64 NumberOfBytes; + UINT64 End; + UINT64 MaxAddress; + UINTN Alignment; + EFI_MEMORY_TYPE CheckType; + + if ((UINT32)Type >= MaxAllocateType) { + return EFI_INVALID_PARAMETER; + } + + if ((MemoryType >= EfiMaxMemoryType && MemoryType < MEMORY_TYPE_OEM_RESERVED_MIN) || + (MemoryType == EfiConventionalMemory) || (MemoryType == EfiPersistentMemory)) { + return EFI_INVALID_PARAMETER; + } + + if (Memory == NULL) { + return EFI_INVALID_PARAMETER; + } + + Alignment = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + + if (MemoryType == EfiACPIReclaimMemory || + MemoryType == EfiACPIMemoryNVS || + MemoryType == EfiRuntimeServicesCode || + MemoryType == EfiRuntimeServicesData) { + + Alignment = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } + + if (Type == AllocateAddress) { + if ((*Memory & (Alignment - 1)) != 0) { + return EFI_NOT_FOUND; + } + } + + NumberOfPages += EFI_SIZE_TO_PAGES (Alignment) - 1; + NumberOfPages &= ~(EFI_SIZE_TO_PAGES (Alignment) - 1); + + // + // If this is for below a particular address, then + // + Start = *Memory; + + // + // The max address is the max natively addressable address for the processor + // + MaxAddress = MAX_ALLOC_ADDRESS; + + // + // Check for Type AllocateAddress, + // if NumberOfPages is 0 or + // if (NumberOfPages << EFI_PAGE_SHIFT) is above MAX_ALLOC_ADDRESS or + // if (Start + NumberOfBytes) rolls over 0 or + // if Start is above MAX_ALLOC_ADDRESS or + // if End is above MAX_ALLOC_ADDRESS, + // if Start..End overlaps any tracked MemoryTypeStatistics range + // return EFI_NOT_FOUND. + // + if (Type == AllocateAddress) { + if ((NumberOfPages == 0) || + (NumberOfPages > RShiftU64 (MaxAddress, EFI_PAGE_SHIFT))) { + return EFI_NOT_FOUND; + } + NumberOfBytes = LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT); + End = Start + NumberOfBytes - 1; + + if ((Start >= End) || + (Start > MaxAddress) || + (End > MaxAddress)) { + return EFI_NOT_FOUND; + } + + // + // A driver is allowed to call AllocatePages using an AllocateAddress type. This type of + // AllocatePage request the exact physical address if it is not used. The existing code + // will allow this request even in 'special' pages. The problem with this is that the + // reason to have 'special' pages for OS hibernate/resume is defeated as memory is + // fragmented. + // + + for (CheckType = (EFI_MEMORY_TYPE) 0; CheckType < EfiMaxMemoryType; CheckType++) { + if (MemoryType != CheckType && + mMemoryTypeStatistics[CheckType].Special && + mMemoryTypeStatistics[CheckType].NumberOfPages > 0) { + if (Start >= mMemoryTypeStatistics[CheckType].BaseAddress && + Start <= mMemoryTypeStatistics[CheckType].MaximumAddress) { + return EFI_NOT_FOUND; + } + if (End >= mMemoryTypeStatistics[CheckType].BaseAddress && + End <= mMemoryTypeStatistics[CheckType].MaximumAddress) { + return EFI_NOT_FOUND; + } + if (Start < mMemoryTypeStatistics[CheckType].BaseAddress && + End > mMemoryTypeStatistics[CheckType].MaximumAddress) { + return EFI_NOT_FOUND; + } + } + } + } + + if (Type == AllocateMaxAddress) { + MaxAddress = Start; + } + + CoreAcquireMemoryLock (); + + // + // If not a specific address, then find an address to allocate + // + if (Type != AllocateAddress) { + Start = FindFreePages (MaxAddress, NumberOfPages, MemoryType, Alignment, + NeedGuard); + if (Start == 0) { + Status = EFI_OUT_OF_RESOURCES; + goto Done; + } + } + + // + // Convert pages from FreeMemory to the requested type + // + if (NeedGuard) { + Status = CoreConvertPagesWithGuard(Start, NumberOfPages, MemoryType); + } else { + Status = CoreConvertPages(Start, NumberOfPages, MemoryType); + } + +Done: + CoreReleaseMemoryLock (); + + if (!EFI_ERROR (Status)) { + if (NeedGuard) { + SetGuardForMemory (Start, NumberOfPages); + } + *Memory = Start; + } + + return Status; +} + +/** + Allocates pages from the memory map. + + @param Type The type of allocation to perform + @param MemoryType The type of memory to turn the allocated pages + into + @param NumberOfPages The number of pages to allocate + @param Memory A pointer to receive the base allocated memory + address + + @return Status. On success, Memory is filled in with the base address allocated + @retval EFI_INVALID_PARAMETER Parameters violate checking rules defined in + spec. + @retval EFI_NOT_FOUND Could not allocate pages match the requirement. + @retval EFI_OUT_OF_RESOURCES No enough pages to allocate. + @retval EFI_SUCCESS Pages successfully allocated. + +**/ +EFI_STATUS +EFIAPI +CoreAllocatePages ( + IN EFI_ALLOCATE_TYPE Type, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN NumberOfPages, + OUT EFI_PHYSICAL_ADDRESS *Memory + ) +{ + EFI_STATUS Status; + BOOLEAN NeedGuard; + + NeedGuard = IsPageTypeToGuard (MemoryType, Type) && !mOnGuarding; + Status = CoreInternalAllocatePages (Type, MemoryType, NumberOfPages, Memory, + NeedGuard); + if (!EFI_ERROR (Status)) { + CoreUpdateProfile ( + (EFI_PHYSICAL_ADDRESS) (UINTN) RETURN_ADDRESS (0), + MemoryProfileActionAllocatePages, + MemoryType, + EFI_PAGES_TO_SIZE (NumberOfPages), + (VOID *) (UINTN) *Memory, + NULL + ); + InstallMemoryAttributesTableOnMemoryAllocation (MemoryType); + ApplyMemoryProtectionPolicy (EfiConventionalMemory, MemoryType, *Memory, + EFI_PAGES_TO_SIZE (NumberOfPages)); + } + return Status; +} + +/** + Frees previous allocated pages. + + @param Memory Base address of memory being freed + @param NumberOfPages The number of pages to free + @param MemoryType Pointer to memory type + + @retval EFI_NOT_FOUND Could not find the entry that covers the range + @retval EFI_INVALID_PARAMETER Address not aligned + @return EFI_SUCCESS -Pages successfully freed. + +**/ +EFI_STATUS +EFIAPI +CoreInternalFreePages ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages, + OUT EFI_MEMORY_TYPE *MemoryType OPTIONAL + ) +{ + EFI_STATUS Status; + LIST_ENTRY *Link; + MEMORY_MAP *Entry; + UINTN Alignment; + BOOLEAN IsGuarded; + + // + // Free the range + // + CoreAcquireMemoryLock (); + + // + // Find the entry that the covers the range + // + IsGuarded = FALSE; + Entry = NULL; + for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) { + Entry = CR(Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + if (Entry->Start <= Memory && Entry->End > Memory) { + break; + } + } + if (Link == &gMemoryMap) { + Status = EFI_NOT_FOUND; + goto Done; + } + + Alignment = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + + ASSERT (Entry != NULL); + if (Entry->Type == EfiACPIReclaimMemory || + Entry->Type == EfiACPIMemoryNVS || + Entry->Type == EfiRuntimeServicesCode || + Entry->Type == EfiRuntimeServicesData) { + + Alignment = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + + } + + if ((Memory & (Alignment - 1)) != 0) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + NumberOfPages += EFI_SIZE_TO_PAGES (Alignment) - 1; + NumberOfPages &= ~(EFI_SIZE_TO_PAGES (Alignment) - 1); + + if (MemoryType != NULL) { + *MemoryType = Entry->Type; + } + + IsGuarded = IsPageTypeToGuard (Entry->Type, AllocateAnyPages) && + IsMemoryGuarded (Memory); + if (IsGuarded) { + Status = CoreConvertPagesWithGuard (Memory, NumberOfPages, + EfiConventionalMemory); + } else { + Status = CoreConvertPages (Memory, NumberOfPages, EfiConventionalMemory); + } + +Done: + CoreReleaseMemoryLock (); + return Status; +} + +/** + Frees previous allocated pages. + + @param Memory Base address of memory being freed + @param NumberOfPages The number of pages to free + + @retval EFI_NOT_FOUND Could not find the entry that covers the range + @retval EFI_INVALID_PARAMETER Address not aligned + @return EFI_SUCCESS -Pages successfully freed. + +**/ +EFI_STATUS +EFIAPI +CoreFreePages ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ) +{ + EFI_STATUS Status; + EFI_MEMORY_TYPE MemoryType; + + Status = CoreInternalFreePages (Memory, NumberOfPages, &MemoryType); + if (!EFI_ERROR (Status)) { + GuardFreedPagesChecked (Memory, NumberOfPages); + CoreUpdateProfile ( + (EFI_PHYSICAL_ADDRESS) (UINTN) RETURN_ADDRESS (0), + MemoryProfileActionFreePages, + MemoryType, + EFI_PAGES_TO_SIZE (NumberOfPages), + (VOID *) (UINTN) Memory, + NULL + ); + InstallMemoryAttributesTableOnMemoryAllocation (MemoryType); + ApplyMemoryProtectionPolicy (MemoryType, EfiConventionalMemory, Memory, + EFI_PAGES_TO_SIZE (NumberOfPages)); + } + return Status; +} + +/** + This function checks to see if the last memory map descriptor in a memory map + can be merged with any of the other memory map descriptors in a memorymap. + Memory descriptors may be merged if they are adjacent and have the same type + and attributes. + + @param MemoryMap A pointer to the start of the memory map. + @param MemoryMapDescriptor A pointer to the last descriptor in MemoryMap. + @param DescriptorSize The size, in bytes, of an individual + EFI_MEMORY_DESCRIPTOR. + + @return A pointer to the next available descriptor in MemoryMap + +**/ +EFI_MEMORY_DESCRIPTOR * +MergeMemoryMapDescriptor ( + IN EFI_MEMORY_DESCRIPTOR *MemoryMap, + IN EFI_MEMORY_DESCRIPTOR *MemoryMapDescriptor, + IN UINTN DescriptorSize + ) +{ + // + // Traverse the array of descriptors in MemoryMap + // + for (; MemoryMap != MemoryMapDescriptor; MemoryMap = NEXT_MEMORY_DESCRIPTOR (MemoryMap, DescriptorSize)) { + // + // Check to see if the Type fields are identical. + // + if (MemoryMap->Type != MemoryMapDescriptor->Type) { + continue; + } + + // + // Check to see if the Attribute fields are identical. + // + if (MemoryMap->Attribute != MemoryMapDescriptor->Attribute) { + continue; + } + + // + // Check to see if MemoryMapDescriptor is immediately above MemoryMap + // + if (MemoryMap->PhysicalStart + EFI_PAGES_TO_SIZE ((UINTN)MemoryMap->NumberOfPages) == MemoryMapDescriptor->PhysicalStart) { + // + // Merge MemoryMapDescriptor into MemoryMap + // + MemoryMap->NumberOfPages += MemoryMapDescriptor->NumberOfPages; + + // + // Return MemoryMapDescriptor as the next available slot int he MemoryMap array + // + return MemoryMapDescriptor; + } + + // + // Check to see if MemoryMapDescriptor is immediately below MemoryMap + // + if (MemoryMap->PhysicalStart - EFI_PAGES_TO_SIZE ((UINTN)MemoryMapDescriptor->NumberOfPages) == MemoryMapDescriptor->PhysicalStart) { + // + // Merge MemoryMapDescriptor into MemoryMap + // + MemoryMap->PhysicalStart = MemoryMapDescriptor->PhysicalStart; + MemoryMap->VirtualStart = MemoryMapDescriptor->VirtualStart; + MemoryMap->NumberOfPages += MemoryMapDescriptor->NumberOfPages; + + // + // Return MemoryMapDescriptor as the next available slot int he MemoryMap array + // + return MemoryMapDescriptor; + } + } + + // + // MemoryMapDescrtiptor could not be merged with any descriptors in MemoryMap. + // + // Return the slot immediately after MemoryMapDescriptor as the next available + // slot in the MemoryMap array + // + return NEXT_MEMORY_DESCRIPTOR (MemoryMapDescriptor, DescriptorSize); +} + +/** + This function returns a copy of the current memory map. The map is an array of + memory descriptors, each of which describes a contiguous block of memory. + + @param MemoryMapSize A pointer to the size, in bytes, of the + MemoryMap buffer. On input, this is the size of + the buffer allocated by the caller. On output, + it is the size of the buffer returned by the + firmware if the buffer was large enough, or the + size of the buffer needed to contain the map if + the buffer was too small. + @param MemoryMap A pointer to the buffer in which firmware places + the current memory map. + @param MapKey A pointer to the location in which firmware + returns the key for the current memory map. + @param DescriptorSize A pointer to the location in which firmware + returns the size, in bytes, of an individual + EFI_MEMORY_DESCRIPTOR. + @param DescriptorVersion A pointer to the location in which firmware + returns the version number associated with the + EFI_MEMORY_DESCRIPTOR. + + @retval EFI_SUCCESS The memory map was returned in the MemoryMap + buffer. + @retval EFI_BUFFER_TOO_SMALL The MemoryMap buffer was too small. The current + buffer size needed to hold the memory map is + returned in MemoryMapSize. + @retval EFI_INVALID_PARAMETER One of the parameters has an invalid value. + +**/ +EFI_STATUS +EFIAPI +CoreGetMemoryMap ( + IN OUT UINTN *MemoryMapSize, + IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, + OUT UINTN *MapKey, + OUT UINTN *DescriptorSize, + OUT UINT32 *DescriptorVersion + ) +{ + EFI_STATUS Status; + UINTN Size; + UINTN BufferSize; + UINTN NumberOfEntries; + LIST_ENTRY *Link; + MEMORY_MAP *Entry; + EFI_GCD_MAP_ENTRY *GcdMapEntry; + EFI_GCD_MAP_ENTRY MergeGcdMapEntry; + EFI_MEMORY_TYPE Type; + EFI_MEMORY_DESCRIPTOR *MemoryMapStart; + EFI_MEMORY_DESCRIPTOR *MemoryMapEnd; + + // + // Make sure the parameters are valid + // + if (MemoryMapSize == NULL) { + return EFI_INVALID_PARAMETER; + } + + CoreAcquireGcdMemoryLock (); + + // + // Count the number of Reserved and runtime MMIO entries + // And, count the number of Persistent entries. + // + NumberOfEntries = 0; + for (Link = mGcdMemorySpaceMap.ForwardLink; Link != &mGcdMemorySpaceMap; Link = Link->ForwardLink) { + GcdMapEntry = CR (Link, EFI_GCD_MAP_ENTRY, Link, EFI_GCD_MAP_SIGNATURE); + if ((GcdMapEntry->GcdMemoryType == EfiGcdMemoryTypePersistent) || + (GcdMapEntry->GcdMemoryType == EfiGcdMemoryTypeReserved) || + ((GcdMapEntry->GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) && + ((GcdMapEntry->Attributes & EFI_MEMORY_RUNTIME) == EFI_MEMORY_RUNTIME))) { + NumberOfEntries ++; + } + } + + Size = sizeof (EFI_MEMORY_DESCRIPTOR); + + // + // Make sure Size != sizeof(EFI_MEMORY_DESCRIPTOR). This will + // prevent people from having pointer math bugs in their code. + // now you have to use *DescriptorSize to make things work. + // + Size += sizeof(UINT64) - (Size % sizeof (UINT64)); + + if (DescriptorSize != NULL) { + *DescriptorSize = Size; + } + + if (DescriptorVersion != NULL) { + *DescriptorVersion = EFI_MEMORY_DESCRIPTOR_VERSION; + } + + CoreAcquireMemoryLock (); + + // + // Compute the buffer size needed to fit the entire map + // + BufferSize = Size * NumberOfEntries; + for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) { + BufferSize += Size; + } + + if (*MemoryMapSize < BufferSize) { + Status = EFI_BUFFER_TOO_SMALL; + goto Done; + } + + if (MemoryMap == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + // + // Build the map + // + ZeroMem (MemoryMap, BufferSize); + MemoryMapStart = MemoryMap; + for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) { + Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + ASSERT (Entry->VirtualStart == 0); + + // + // Convert internal map into an EFI_MEMORY_DESCRIPTOR + // + MemoryMap->Type = Entry->Type; + MemoryMap->PhysicalStart = Entry->Start; + MemoryMap->VirtualStart = Entry->VirtualStart; + MemoryMap->NumberOfPages = RShiftU64 (Entry->End - Entry->Start + 1, EFI_PAGE_SHIFT); + // + // If the memory type is EfiConventionalMemory, then determine if the range is part of a + // memory type bin and needs to be converted to the same memory type as the rest of the + // memory type bin in order to minimize EFI Memory Map changes across reboots. This + // improves the chances for a successful S4 resume in the presence of minor page allocation + // differences across reboots. + // + if (MemoryMap->Type == EfiConventionalMemory) { + for (Type = (EFI_MEMORY_TYPE) 0; Type < EfiMaxMemoryType; Type++) { + if (mMemoryTypeStatistics[Type].Special && + mMemoryTypeStatistics[Type].NumberOfPages > 0 && + Entry->Start >= mMemoryTypeStatistics[Type].BaseAddress && + Entry->End <= mMemoryTypeStatistics[Type].MaximumAddress) { + MemoryMap->Type = Type; + } + } + } + MemoryMap->Attribute = Entry->Attribute; + if (MemoryMap->Type < EfiMaxMemoryType) { + if (mMemoryTypeStatistics[MemoryMap->Type].Runtime) { + MemoryMap->Attribute |= EFI_MEMORY_RUNTIME; + } + } + + // + // Check to see if the new Memory Map Descriptor can be merged with an + // existing descriptor if they are adjacent and have the same attributes + // + MemoryMap = MergeMemoryMapDescriptor (MemoryMapStart, MemoryMap, Size); + } + + + ZeroMem (&MergeGcdMapEntry, sizeof (MergeGcdMapEntry)); + GcdMapEntry = NULL; + for (Link = mGcdMemorySpaceMap.ForwardLink; ; Link = Link->ForwardLink) { + if (Link != &mGcdMemorySpaceMap) { + // + // Merge adjacent same type and attribute GCD memory range + // + GcdMapEntry = CR (Link, EFI_GCD_MAP_ENTRY, Link, EFI_GCD_MAP_SIGNATURE); + + if ((MergeGcdMapEntry.Capabilities == GcdMapEntry->Capabilities) && + (MergeGcdMapEntry.Attributes == GcdMapEntry->Attributes) && + (MergeGcdMapEntry.GcdMemoryType == GcdMapEntry->GcdMemoryType) && + (MergeGcdMapEntry.GcdIoType == GcdMapEntry->GcdIoType)) { + MergeGcdMapEntry.EndAddress = GcdMapEntry->EndAddress; + continue; + } + } + + if ((MergeGcdMapEntry.GcdMemoryType == EfiGcdMemoryTypeReserved) || + ((MergeGcdMapEntry.GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) && + ((MergeGcdMapEntry.Attributes & EFI_MEMORY_RUNTIME) == EFI_MEMORY_RUNTIME))) { + // + // Page Align GCD range is required. When it is converted to EFI_MEMORY_DESCRIPTOR, + // it will be recorded as page PhysicalStart and NumberOfPages. + // + ASSERT ((MergeGcdMapEntry.BaseAddress & EFI_PAGE_MASK) == 0); + ASSERT (((MergeGcdMapEntry.EndAddress - MergeGcdMapEntry.BaseAddress + 1) & EFI_PAGE_MASK) == 0); + + // + // Create EFI_MEMORY_DESCRIPTOR for every Reserved and runtime MMIO GCD entries + // + MemoryMap->PhysicalStart = MergeGcdMapEntry.BaseAddress; + MemoryMap->VirtualStart = 0; + MemoryMap->NumberOfPages = RShiftU64 ((MergeGcdMapEntry.EndAddress - MergeGcdMapEntry.BaseAddress + 1), EFI_PAGE_SHIFT); + MemoryMap->Attribute = (MergeGcdMapEntry.Attributes & ~EFI_MEMORY_PORT_IO) | + (MergeGcdMapEntry.Capabilities & (EFI_CACHE_ATTRIBUTE_MASK | EFI_MEMORY_ATTRIBUTE_MASK)); + + if (MergeGcdMapEntry.GcdMemoryType == EfiGcdMemoryTypeReserved) { + MemoryMap->Type = EfiReservedMemoryType; + } else if (MergeGcdMapEntry.GcdMemoryType == EfiGcdMemoryTypeMemoryMappedIo) { + if ((MergeGcdMapEntry.Attributes & EFI_MEMORY_PORT_IO) == EFI_MEMORY_PORT_IO) { + MemoryMap->Type = EfiMemoryMappedIOPortSpace; + } else { + MemoryMap->Type = EfiMemoryMappedIO; + } + } + + // + // Check to see if the new Memory Map Descriptor can be merged with an + // existing descriptor if they are adjacent and have the same attributes + // + MemoryMap = MergeMemoryMapDescriptor (MemoryMapStart, MemoryMap, Size); + } + + if (MergeGcdMapEntry.GcdMemoryType == EfiGcdMemoryTypePersistent) { + // + // Page Align GCD range is required. When it is converted to EFI_MEMORY_DESCRIPTOR, + // it will be recorded as page PhysicalStart and NumberOfPages. + // + ASSERT ((MergeGcdMapEntry.BaseAddress & EFI_PAGE_MASK) == 0); + ASSERT (((MergeGcdMapEntry.EndAddress - MergeGcdMapEntry.BaseAddress + 1) & EFI_PAGE_MASK) == 0); + + // + // Create EFI_MEMORY_DESCRIPTOR for every Persistent GCD entries + // + MemoryMap->PhysicalStart = MergeGcdMapEntry.BaseAddress; + MemoryMap->VirtualStart = 0; + MemoryMap->NumberOfPages = RShiftU64 ((MergeGcdMapEntry.EndAddress - MergeGcdMapEntry.BaseAddress + 1), EFI_PAGE_SHIFT); + MemoryMap->Attribute = MergeGcdMapEntry.Attributes | EFI_MEMORY_NV | + (MergeGcdMapEntry.Capabilities & (EFI_CACHE_ATTRIBUTE_MASK | EFI_MEMORY_ATTRIBUTE_MASK)); + MemoryMap->Type = EfiPersistentMemory; + + // + // Check to see if the new Memory Map Descriptor can be merged with an + // existing descriptor if they are adjacent and have the same attributes + // + MemoryMap = MergeMemoryMapDescriptor (MemoryMapStart, MemoryMap, Size); + } + if (Link == &mGcdMemorySpaceMap) { + // + // break loop when arrive at head. + // + break; + } + if (GcdMapEntry != NULL) { + // + // Copy new GCD map entry for the following GCD range merge + // + CopyMem (&MergeGcdMapEntry, GcdMapEntry, sizeof (MergeGcdMapEntry)); + } + } + + // + // Compute the size of the buffer actually used after all memory map descriptor merge operations + // + BufferSize = ((UINT8 *)MemoryMap - (UINT8 *)MemoryMapStart); + + // + // Note: Some OSs will treat EFI_MEMORY_DESCRIPTOR.Attribute as really + // set attributes and change memory paging attribute accordingly. + // But current EFI_MEMORY_DESCRIPTOR.Attribute is assigned by + // value from Capabilities in GCD memory map. This might cause + // boot problems. Clearing all page-access permission related + // capabilities can workaround it. Following code is supposed to + // be removed once the usage of EFI_MEMORY_DESCRIPTOR.Attribute + // is clarified in UEFI spec and adopted by both EDK-II Core and + // all supported OSs. + // + MemoryMapEnd = MemoryMap; + MemoryMap = MemoryMapStart; + while (MemoryMap < MemoryMapEnd) { + MemoryMap->Attribute &= ~(UINT64)EFI_MEMORY_ACCESS_MASK; + MemoryMap = NEXT_MEMORY_DESCRIPTOR (MemoryMap, Size); + } + MergeMemoryMap (MemoryMapStart, &BufferSize, Size); + MemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMapStart + BufferSize); + + Status = EFI_SUCCESS; + +Done: + // + // Update the map key finally + // + if (MapKey != NULL) { + *MapKey = mMemoryMapKey; + } + + CoreReleaseMemoryLock (); + + CoreReleaseGcdMemoryLock (); + + *MemoryMapSize = BufferSize; + + DEBUG_CODE ( + DumpGuardedMemoryBitmap (); + ); + + return Status; +} + + +/** + Internal function. Used by the pool functions to allocate pages + to back pool allocation requests. + + @param PoolType The type of memory for the new pool pages + @param NumberOfPages No of pages to allocate + @param Alignment Bits to align. + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The allocated memory, or NULL + +**/ +VOID * +CoreAllocatePoolPages ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN NumberOfPages, + IN UINTN Alignment, + IN BOOLEAN NeedGuard + ) +{ + UINT64 Start; + + // + // Find the pages to convert + // + Start = FindFreePages (MAX_ALLOC_ADDRESS, NumberOfPages, PoolType, Alignment, + NeedGuard); + + // + // Convert it to boot services data + // + if (Start == 0) { + DEBUG ((DEBUG_ERROR | DEBUG_PAGE, "AllocatePoolPages: failed to allocate %d pages\n", (UINT32)NumberOfPages)); + } else { + if (NeedGuard) { + CoreConvertPagesWithGuard (Start, NumberOfPages, PoolType); + } else { + CoreConvertPages (Start, NumberOfPages, PoolType); + } + } + + return (VOID *)(UINTN) Start; +} + + +/** + Internal function. Frees pool pages allocated via AllocatePoolPages () + + @param Memory The base address to free + @param NumberOfPages The number of pages to free + +**/ +VOID +CoreFreePoolPages ( + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NumberOfPages + ) +{ + CoreConvertPages (Memory, NumberOfPages, EfiConventionalMemory); +} + + + +/** + Make sure the memory map is following all the construction rules, + it is the last time to check memory map error before exit boot services. + + @param MapKey Memory map key + + @retval EFI_INVALID_PARAMETER Memory map not consistent with construction + rules. + @retval EFI_SUCCESS Valid memory map. + +**/ +EFI_STATUS +CoreTerminateMemoryMap ( + IN UINTN MapKey + ) +{ + EFI_STATUS Status; + LIST_ENTRY *Link; + MEMORY_MAP *Entry; + + Status = EFI_SUCCESS; + + CoreAcquireMemoryLock (); + + if (MapKey == mMemoryMapKey) { + + // + // Make sure the memory map is following all the construction rules + // This is the last chance we will be able to display any messages on + // the console devices. + // + + for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) { + Entry = CR(Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE); + if (Entry->Type < EfiMaxMemoryType) { + if (mMemoryTypeStatistics[Entry->Type].Runtime) { + ASSERT (Entry->Type != EfiACPIReclaimMemory); + ASSERT (Entry->Type != EfiACPIMemoryNVS); + if ((Entry->Start & (RUNTIME_PAGE_ALLOCATION_GRANULARITY - 1)) != 0) { + DEBUG((DEBUG_ERROR | DEBUG_PAGE, "ExitBootServices: A RUNTIME memory entry is not on a proper alignment.\n")); + Status = EFI_INVALID_PARAMETER; + goto Done; + } + if (((Entry->End + 1) & (RUNTIME_PAGE_ALLOCATION_GRANULARITY - 1)) != 0) { + DEBUG((DEBUG_ERROR | DEBUG_PAGE, "ExitBootServices: A RUNTIME memory entry is not on a proper alignment.\n")); + Status = EFI_INVALID_PARAMETER; + goto Done; + } + } + } + } + + // + // The map key they gave us matches what we expect. Fall through and + // return success. In an ideal world we would clear out all of + // EfiBootServicesCode and EfiBootServicesData. However this function + // is not the last one called by ExitBootServices(), so we have to + // preserve the memory contents. + // + } else { + Status = EFI_INVALID_PARAMETER; + } + +Done: + CoreReleaseMemoryLock (); + + return Status; +} + + + + + + + + + diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Pool.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Pool.c new file mode 100644 index 00000000..0294ec5f --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Core/Dxe/Mem/Pool.c @@ -0,0 +1,857 @@ +/** @file + UEFI Memory pool management functions. + +Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "DxeMain.h" +#include "Imem.h" +#include "HeapGuard.h" + +STATIC EFI_LOCK mPoolMemoryLock = EFI_INITIALIZE_LOCK_VARIABLE (TPL_NOTIFY); + +#define POOL_FREE_SIGNATURE SIGNATURE_32('p','f','r','0') +typedef struct { + UINT32 Signature; + UINT32 Index; + LIST_ENTRY Link; +} POOL_FREE; + + +#define POOL_HEAD_SIGNATURE SIGNATURE_32('p','h','d','0') +#define POOLPAGE_HEAD_SIGNATURE SIGNATURE_32('p','h','d','1') +typedef struct { + UINT32 Signature; + UINT32 Reserved; + EFI_MEMORY_TYPE Type; + UINTN Size; + CHAR8 Data[1]; +} POOL_HEAD; + +#define SIZE_OF_POOL_HEAD OFFSET_OF(POOL_HEAD,Data) + +#define POOL_TAIL_SIGNATURE SIGNATURE_32('p','t','a','l') +typedef struct { + UINT32 Signature; + UINT32 Reserved; + UINTN Size; +} POOL_TAIL; + +#define POOL_OVERHEAD (SIZE_OF_POOL_HEAD + sizeof(POOL_TAIL)) + +#define HEAD_TO_TAIL(a) \ + ((POOL_TAIL *) (((CHAR8 *) (a)) + (a)->Size - sizeof(POOL_TAIL))); + +// +// Each element is the sum of the 2 previous ones: this allows us to migrate +// blocks between bins by splitting them up, while not wasting too much memory +// as we would in a strict power-of-2 sequence +// +STATIC CONST UINT16 mPoolSizeTable[] = { + 128, 256, 384, 640, 1024, 1664, 2688, 4352, 7040, 11392, 18432, 29824 +}; + +#define SIZE_TO_LIST(a) (GetPoolIndexFromSize (a)) +#define LIST_TO_SIZE(a) (mPoolSizeTable [a]) + +#define MAX_POOL_LIST (ARRAY_SIZE (mPoolSizeTable)) + +#define MAX_POOL_SIZE (MAX_ADDRESS - POOL_OVERHEAD) + +// +// Globals +// + +#define POOL_SIGNATURE SIGNATURE_32('p','l','s','t') +typedef struct { + INTN Signature; + UINTN Used; + EFI_MEMORY_TYPE MemoryType; + LIST_ENTRY FreeList[MAX_POOL_LIST]; + LIST_ENTRY Link; +} POOL; + +// +// Pool header for each memory type. +// +POOL mPoolHead[EfiMaxMemoryType]; + +// +// List of pool header to search for the appropriate memory type. +// +LIST_ENTRY mPoolHeadList = INITIALIZE_LIST_HEAD_VARIABLE (mPoolHeadList); + +/** + Get pool size table index from the specified size. + + @param Size The specified size to get index from pool table. + + @return The index of pool size table. + +**/ +STATIC +UINTN +GetPoolIndexFromSize ( + UINTN Size + ) +{ + UINTN Index; + + for (Index = 0; Index < MAX_POOL_LIST; Index++) { + if (mPoolSizeTable [Index] >= Size) { + return Index; + } + } + return MAX_POOL_LIST; +} + +/** + Called to initialize the pool. + +**/ +VOID +CoreInitializePool ( + VOID + ) +{ + UINTN Type; + UINTN Index; + + for (Type=0; Type < EfiMaxMemoryType; Type++) { + mPoolHead[Type].Signature = 0; + mPoolHead[Type].Used = 0; + mPoolHead[Type].MemoryType = (EFI_MEMORY_TYPE) Type; + for (Index=0; Index < MAX_POOL_LIST; Index++) { + InitializeListHead (&mPoolHead[Type].FreeList[Index]); + } + } +} + + +/** + Look up pool head for specified memory type. + + @param MemoryType Memory type of which pool head is looked for + + @return Pointer of Corresponding pool head. + +**/ +POOL * +LookupPoolHead ( + IN EFI_MEMORY_TYPE MemoryType + ) +{ + LIST_ENTRY *Link; + POOL *Pool; + UINTN Index; + + if ((UINT32)MemoryType < EfiMaxMemoryType) { + return &mPoolHead[MemoryType]; + } + + // + // MemoryType values in the range 0x80000000..0xFFFFFFFF are reserved for use by UEFI + // OS loaders that are provided by operating system vendors. + // MemoryType values in the range 0x70000000..0x7FFFFFFF are reserved for OEM use. + // + if ((UINT32) MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) { + + for (Link = mPoolHeadList.ForwardLink; Link != &mPoolHeadList; Link = Link->ForwardLink) { + Pool = CR(Link, POOL, Link, POOL_SIGNATURE); + if (Pool->MemoryType == MemoryType) { + return Pool; + } + } + + Pool = CoreAllocatePoolI (EfiBootServicesData, sizeof (POOL), FALSE); + if (Pool == NULL) { + return NULL; + } + + Pool->Signature = POOL_SIGNATURE; + Pool->Used = 0; + Pool->MemoryType = MemoryType; + for (Index=0; Index < MAX_POOL_LIST; Index++) { + InitializeListHead (&Pool->FreeList[Index]); + } + + InsertHeadList (&mPoolHeadList, &Pool->Link); + + return Pool; + } + + return NULL; +} + + + +/** + Allocate pool of a particular type. + + @param PoolType Type of pool to allocate + @param Size The amount of pool to allocate + @param Buffer The address to return a pointer to the allocated + pool + + @retval EFI_INVALID_PARAMETER Buffer is NULL. + PoolType is in the range EfiMaxMemoryType..0x6FFFFFFF. + PoolType is EfiPersistentMemory. + @retval EFI_OUT_OF_RESOURCES Size exceeds max pool size or allocation failed. + @retval EFI_SUCCESS Pool successfully allocated. + +**/ +EFI_STATUS +EFIAPI +CoreInternalAllocatePool ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN Size, + OUT VOID **Buffer + ) +{ + EFI_STATUS Status; + BOOLEAN NeedGuard; + + // + // If it's not a valid type, fail it + // + if ((PoolType >= EfiMaxMemoryType && PoolType < MEMORY_TYPE_OEM_RESERVED_MIN) || + (PoolType == EfiConventionalMemory) || (PoolType == EfiPersistentMemory)) { + return EFI_INVALID_PARAMETER; + } + + if (Buffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + *Buffer = NULL; + + // + // If size is too large, fail it + // Base on the EFI spec, return status of EFI_OUT_OF_RESOURCES + // + if (Size > MAX_POOL_SIZE) { + return EFI_OUT_OF_RESOURCES; + } + + NeedGuard = IsPoolTypeToGuard (PoolType) && !mOnGuarding; + + // + // Acquire the memory lock and make the allocation + // + Status = CoreAcquireLockOrFail (&mPoolMemoryLock); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + + *Buffer = CoreAllocatePoolI (PoolType, Size, NeedGuard); + CoreReleaseLock (&mPoolMemoryLock); + return (*Buffer != NULL) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES; +} + +/** + Allocate pool of a particular type. + + @param PoolType Type of pool to allocate + @param Size The amount of pool to allocate + @param Buffer The address to return a pointer to the allocated + pool + + @retval EFI_INVALID_PARAMETER Buffer is NULL. + PoolType is in the range EfiMaxMemoryType..0x6FFFFFFF. + PoolType is EfiPersistentMemory. + @retval EFI_OUT_OF_RESOURCES Size exceeds max pool size or allocation failed. + @retval EFI_SUCCESS Pool successfully allocated. + +**/ +EFI_STATUS +EFIAPI +CoreAllocatePool ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN Size, + OUT VOID **Buffer + ) +{ + EFI_STATUS Status; + + Status = CoreInternalAllocatePool (PoolType, Size, Buffer); + if (!EFI_ERROR (Status)) { + CoreUpdateProfile ( + (EFI_PHYSICAL_ADDRESS) (UINTN) RETURN_ADDRESS (0), + MemoryProfileActionAllocatePool, + PoolType, + Size, + *Buffer, + NULL + ); + InstallMemoryAttributesTableOnMemoryAllocation (PoolType); + } + return Status; +} + +/** + Internal function. Used by the pool functions to allocate pages + to back pool allocation requests. + + @param PoolType The type of memory for the new pool pages + @param NoPages No of pages to allocate + @param Granularity Bits to align. + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The allocated memory, or NULL + +**/ +STATIC +VOID * +CoreAllocatePoolPagesI ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN NoPages, + IN UINTN Granularity, + IN BOOLEAN NeedGuard + ) +{ + VOID *Buffer; + EFI_STATUS Status; + + Status = CoreAcquireLockOrFail (&gMemoryLock); + if (EFI_ERROR (Status)) { + return NULL; + } + + Buffer = CoreAllocatePoolPages (PoolType, NoPages, Granularity, NeedGuard); + CoreReleaseMemoryLock (); + + if (Buffer != NULL) { + if (NeedGuard) { + SetGuardForMemory ((EFI_PHYSICAL_ADDRESS)(UINTN)Buffer, NoPages); + } + ApplyMemoryProtectionPolicy(EfiConventionalMemory, PoolType, + (EFI_PHYSICAL_ADDRESS)(UINTN)Buffer, EFI_PAGES_TO_SIZE (NoPages)); + } + return Buffer; +} + +/** + Internal function to allocate pool of a particular type. + Caller must have the memory lock held + + @param PoolType Type of pool to allocate + @param Size The amount of pool to allocate + @param NeedGuard Flag to indicate Guard page is needed or not + + @return The allocate pool, or NULL + +**/ +VOID * +CoreAllocatePoolI ( + IN EFI_MEMORY_TYPE PoolType, + IN UINTN Size, + IN BOOLEAN NeedGuard + ) +{ + POOL *Pool; + POOL_FREE *Free; + POOL_HEAD *Head; + POOL_TAIL *Tail; + CHAR8 *NewPage; + VOID *Buffer; + UINTN Index; + UINTN FSize; + UINTN Offset, MaxOffset; + UINTN NoPages; + UINTN Granularity; + BOOLEAN HasPoolTail; + BOOLEAN PageAsPool; + + ASSERT_LOCKED (&mPoolMemoryLock); + + if (PoolType == EfiACPIReclaimMemory || + PoolType == EfiACPIMemoryNVS || + PoolType == EfiRuntimeServicesCode || + PoolType == EfiRuntimeServicesData) { + + Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } else { + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + } + + // + // Adjust the size by the pool header & tail overhead + // + + HasPoolTail = !(NeedGuard && + ((PcdGet8 (PcdHeapGuardPropertyMask) & BIT7) == 0)); + PageAsPool = (IsHeapGuardEnabled (GUARD_HEAP_TYPE_FREED) && !mOnGuarding); + + // + // Adjusting the Size to be of proper alignment so that + // we don't get an unaligned access fault later when + // pool_Tail is being initialized + // + Size = ALIGN_VARIABLE (Size); + + Size += POOL_OVERHEAD; + Index = SIZE_TO_LIST(Size); + Pool = LookupPoolHead (PoolType); + if (Pool== NULL) { + return NULL; + } + Head = NULL; + + // + // If allocation is over max size, just allocate pages for the request + // (slow) + // + if (Index >= SIZE_TO_LIST (Granularity) || NeedGuard || PageAsPool) { + if (!HasPoolTail) { + Size -= sizeof (POOL_TAIL); + } + NoPages = EFI_SIZE_TO_PAGES (Size) + EFI_SIZE_TO_PAGES (Granularity) - 1; + NoPages &= ~(UINTN)(EFI_SIZE_TO_PAGES (Granularity) - 1); + Head = CoreAllocatePoolPagesI (PoolType, NoPages, Granularity, NeedGuard); + if (NeedGuard) { + Head = AdjustPoolHeadA ((EFI_PHYSICAL_ADDRESS)(UINTN)Head, NoPages, Size); + } + goto Done; + } + + // + // If there's no free pool in the proper list size, go get some more pages + // + if (IsListEmpty (&Pool->FreeList[Index])) { + + Offset = LIST_TO_SIZE (Index); + MaxOffset = Granularity; + + // + // Check the bins holding larger blocks, and carve one up if needed + // + while (++Index < SIZE_TO_LIST (Granularity)) { + if (!IsListEmpty (&Pool->FreeList[Index])) { + Free = CR (Pool->FreeList[Index].ForwardLink, POOL_FREE, Link, POOL_FREE_SIGNATURE); + RemoveEntryList (&Free->Link); + NewPage = (VOID *) Free; + MaxOffset = LIST_TO_SIZE (Index); + goto Carve; + } + } + + // + // Get another page + // + NewPage = CoreAllocatePoolPagesI (PoolType, EFI_SIZE_TO_PAGES (Granularity), + Granularity, NeedGuard); + if (NewPage == NULL) { + goto Done; + } + + // + // Serve the allocation request from the head of the allocated block + // +Carve: + Head = (POOL_HEAD *) NewPage; + + // + // Carve up remaining space into free pool blocks + // + Index--; + while (Offset < MaxOffset) { + ASSERT (Index < MAX_POOL_LIST); + FSize = LIST_TO_SIZE(Index); + + while (Offset + FSize <= MaxOffset) { + Free = (POOL_FREE *) &NewPage[Offset]; + Free->Signature = POOL_FREE_SIGNATURE; + Free->Index = (UINT32)Index; + InsertHeadList (&Pool->FreeList[Index], &Free->Link); + Offset += FSize; + } + Index -= 1; + } + + ASSERT (Offset == MaxOffset); + goto Done; + } + + // + // Remove entry from free pool list + // + Free = CR (Pool->FreeList[Index].ForwardLink, POOL_FREE, Link, POOL_FREE_SIGNATURE); + RemoveEntryList (&Free->Link); + + Head = (POOL_HEAD *) Free; + +Done: + Buffer = NULL; + + if (Head != NULL) { + + // + // Account the allocation + // + Pool->Used += Size; + + // + // If we have a pool buffer, fill in the header & tail info + // + Head->Signature = (PageAsPool) ? POOLPAGE_HEAD_SIGNATURE : POOL_HEAD_SIGNATURE; + Head->Size = Size; + Head->Type = (EFI_MEMORY_TYPE) PoolType; + Buffer = Head->Data; + + if (HasPoolTail) { + Tail = HEAD_TO_TAIL (Head); + Tail->Signature = POOL_TAIL_SIGNATURE; + Tail->Size = Size; + + Size -= POOL_OVERHEAD; + } else { + Size -= SIZE_OF_POOL_HEAD; + } + + DEBUG_CLEAR_MEMORY (Buffer, Size); + + DEBUG (( + DEBUG_POOL, + "AllocatePoolI: Type %x, Addr %p (len %lx) %,ld\n", PoolType, + Buffer, + (UINT64)Size, + (UINT64) Pool->Used + )); + + + } else { + DEBUG ((DEBUG_ERROR | DEBUG_POOL, "AllocatePool: failed to allocate %ld bytes\n", (UINT64) Size)); + } + + return Buffer; +} + + + +/** + Frees pool. + + @param Buffer The allocated pool entry to free + @param PoolType Pointer to pool type + + @retval EFI_INVALID_PARAMETER Buffer is not a valid value. + @retval EFI_SUCCESS Pool successfully freed. + +**/ +EFI_STATUS +EFIAPI +CoreInternalFreePool ( + IN VOID *Buffer, + OUT EFI_MEMORY_TYPE *PoolType OPTIONAL + ) +{ + EFI_STATUS Status; + + if (Buffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + CoreAcquireLock (&mPoolMemoryLock); + Status = CoreFreePoolI (Buffer, PoolType); + CoreReleaseLock (&mPoolMemoryLock); + return Status; +} + +/** + Frees pool. + + @param Buffer The allocated pool entry to free + + @retval EFI_INVALID_PARAMETER Buffer is not a valid value. + @retval EFI_SUCCESS Pool successfully freed. + +**/ +EFI_STATUS +EFIAPI +CoreFreePool ( + IN VOID *Buffer + ) +{ + EFI_STATUS Status; + EFI_MEMORY_TYPE PoolType; + + Status = CoreInternalFreePool (Buffer, &PoolType); + if (!EFI_ERROR (Status)) { + CoreUpdateProfile ( + (EFI_PHYSICAL_ADDRESS) (UINTN) RETURN_ADDRESS (0), + MemoryProfileActionFreePool, + PoolType, + 0, + Buffer, + NULL + ); + InstallMemoryAttributesTableOnMemoryAllocation (PoolType); + } + return Status; +} + +/** + Internal function. Frees pool pages allocated via CoreAllocatePoolPagesI(). + + @param PoolType The type of memory for the pool pages + @param Memory The base address to free + @param NoPages The number of pages to free + +**/ +STATIC +VOID +CoreFreePoolPagesI ( + IN EFI_MEMORY_TYPE PoolType, + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NoPages + ) +{ + CoreAcquireMemoryLock (); + CoreFreePoolPages (Memory, NoPages); + CoreReleaseMemoryLock (); + + GuardFreedPagesChecked (Memory, NoPages); + ApplyMemoryProtectionPolicy (PoolType, EfiConventionalMemory, + (EFI_PHYSICAL_ADDRESS)(UINTN)Memory, EFI_PAGES_TO_SIZE (NoPages)); +} + +/** + Internal function. Frees guarded pool pages. + + @param PoolType The type of memory for the pool pages + @param Memory The base address to free + @param NoPages The number of pages to free + +**/ +STATIC +VOID +CoreFreePoolPagesWithGuard ( + IN EFI_MEMORY_TYPE PoolType, + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINTN NoPages + ) +{ + EFI_PHYSICAL_ADDRESS MemoryGuarded; + UINTN NoPagesGuarded; + + MemoryGuarded = Memory; + NoPagesGuarded = NoPages; + + AdjustMemoryF (&Memory, &NoPages); + // + // It's safe to unset Guard page inside memory lock because there should + // be no memory allocation occurred in updating memory page attribute at + // this point. And unsetting Guard page before free will prevent Guard + // page just freed back to pool from being allocated right away before + // marking it usable (from non-present to present). + // + UnsetGuardForMemory (MemoryGuarded, NoPagesGuarded); + if (NoPages > 0) { + CoreFreePoolPagesI (PoolType, Memory, NoPages); + } +} + +/** + Internal function to free a pool entry. + Caller must have the memory lock held + + @param Buffer The allocated pool entry to free + @param PoolType Pointer to pool type + + @retval EFI_INVALID_PARAMETER Buffer not valid + @retval EFI_SUCCESS Buffer successfully freed. + +**/ +EFI_STATUS +CoreFreePoolI ( + IN VOID *Buffer, + OUT EFI_MEMORY_TYPE *PoolType OPTIONAL + ) +{ + POOL *Pool; + POOL_HEAD *Head; + POOL_TAIL *Tail; + POOL_FREE *Free; + UINTN Index; + UINTN NoPages; + UINTN Size; + CHAR8 *NewPage; + UINTN Offset; + BOOLEAN AllFree; + UINTN Granularity; + BOOLEAN IsGuarded; + BOOLEAN HasPoolTail; + BOOLEAN PageAsPool; + + ASSERT(Buffer != NULL); + // + // Get the head & tail of the pool entry + // + Head = BASE_CR (Buffer, POOL_HEAD, Data); + ASSERT(Head != NULL); + + if (Head->Signature != POOL_HEAD_SIGNATURE && + Head->Signature != POOLPAGE_HEAD_SIGNATURE) { + ASSERT (Head->Signature == POOL_HEAD_SIGNATURE || + Head->Signature == POOLPAGE_HEAD_SIGNATURE); + return EFI_INVALID_PARAMETER; + } + + IsGuarded = IsPoolTypeToGuard (Head->Type) && + IsMemoryGuarded ((EFI_PHYSICAL_ADDRESS)(UINTN)Head); + HasPoolTail = !(IsGuarded && + ((PcdGet8 (PcdHeapGuardPropertyMask) & BIT7) == 0)); + PageAsPool = (Head->Signature == POOLPAGE_HEAD_SIGNATURE); + + if (HasPoolTail) { + Tail = HEAD_TO_TAIL (Head); + ASSERT (Tail != NULL); + + // + // Debug + // + ASSERT (Tail->Signature == POOL_TAIL_SIGNATURE); + ASSERT (Head->Size == Tail->Size); + + if (Tail->Signature != POOL_TAIL_SIGNATURE) { + return EFI_INVALID_PARAMETER; + } + + if (Head->Size != Tail->Size) { + return EFI_INVALID_PARAMETER; + } + } + + ASSERT_LOCKED (&mPoolMemoryLock); + + // + // Determine the pool type and account for it + // + Size = Head->Size; + Pool = LookupPoolHead (Head->Type); + if (Pool == NULL) { + return EFI_INVALID_PARAMETER; + } + Pool->Used -= Size; + DEBUG ((DEBUG_POOL, "FreePool: %p (len %lx) %,ld\n", Head->Data, (UINT64)(Head->Size - POOL_OVERHEAD), (UINT64) Pool->Used)); + + if (Head->Type == EfiACPIReclaimMemory || + Head->Type == EfiACPIMemoryNVS || + Head->Type == EfiRuntimeServicesCode || + Head->Type == EfiRuntimeServicesData) { + + Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } else { + Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + } + + if (PoolType != NULL) { + *PoolType = Head->Type; + } + + // + // Determine the pool list + // + Index = SIZE_TO_LIST(Size); + DEBUG_CLEAR_MEMORY (Head, Size); + + // + // If it's not on the list, it must be pool pages + // + if (Index >= SIZE_TO_LIST (Granularity) || IsGuarded || PageAsPool) { + + // + // Return the memory pages back to free memory + // + NoPages = EFI_SIZE_TO_PAGES (Size) + EFI_SIZE_TO_PAGES (Granularity) - 1; + NoPages &= ~(UINTN)(EFI_SIZE_TO_PAGES (Granularity) - 1); + if (IsGuarded) { + Head = AdjustPoolHeadF ((EFI_PHYSICAL_ADDRESS)(UINTN)Head); + CoreFreePoolPagesWithGuard ( + Pool->MemoryType, + (EFI_PHYSICAL_ADDRESS)(UINTN)Head, + NoPages + ); + } else { + CoreFreePoolPagesI ( + Pool->MemoryType, + (EFI_PHYSICAL_ADDRESS)(UINTN)Head, + NoPages + ); + } + + } else { + + // + // Put the pool entry onto the free pool list + // + Free = (POOL_FREE *) Head; + ASSERT(Free != NULL); + Free->Signature = POOL_FREE_SIGNATURE; + Free->Index = (UINT32)Index; + InsertHeadList (&Pool->FreeList[Index], &Free->Link); + + // + // See if all the pool entries in the same page as Free are freed pool + // entries + // + NewPage = (CHAR8 *)((UINTN)Free & ~(Granularity - 1)); + Free = (POOL_FREE *) &NewPage[0]; + ASSERT(Free != NULL); + + if (Free->Signature == POOL_FREE_SIGNATURE) { + + AllFree = TRUE; + Offset = 0; + + while ((Offset < Granularity) && (AllFree)) { + Free = (POOL_FREE *) &NewPage[Offset]; + ASSERT(Free != NULL); + if (Free->Signature != POOL_FREE_SIGNATURE) { + AllFree = FALSE; + } + Offset += LIST_TO_SIZE(Free->Index); + } + + if (AllFree) { + + // + // All of the pool entries in the same page as Free are free pool + // entries + // Remove all of these pool entries from the free loop lists. + // + Free = (POOL_FREE *) &NewPage[0]; + ASSERT(Free != NULL); + Offset = 0; + + while (Offset < Granularity) { + Free = (POOL_FREE *) &NewPage[Offset]; + ASSERT(Free != NULL); + RemoveEntryList (&Free->Link); + Offset += LIST_TO_SIZE(Free->Index); + } + + // + // Free the page + // + CoreFreePoolPagesI (Pool->MemoryType, (EFI_PHYSICAL_ADDRESS) (UINTN)NewPage, + EFI_SIZE_TO_PAGES (Granularity)); + } + } + } + + // + // If this is an OS/OEM specific memory type, then check to see if the last + // portion of that memory type has been freed. If it has, then free the + // list entry for that memory type + // + if (((UINT32) Pool->MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) && Pool->Used == 0) { + RemoveEntryList (&Pool->Link); + CoreFreePoolI (Pool, NULL); + } + + return EFI_SUCCESS; +} + |