/** @file UEFI Memory pool management functions. Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
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) || (PoolType == EfiUnacceptedMemoryType)) { 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 == EfiReservedMemoryType) || (PoolType == EfiACPIMemoryNVS) || (PoolType == EfiRuntimeServicesCode) || (PoolType == EfiRuntimeServicesData)) { Granularity = RUNTIME_PAGE_ALLOCATION_GRANULARITY; } else { Granularity = DEFAULT_PAGE_ALLOCATION_GRANULARITY; } // // The heap guard system does not support non-EFI_PAGE_SIZE alignments. // Architectures that require larger RUNTIME_PAGE_ALLOCATION_GRANULARITY // will have the runtime memory regions unguarded. OSes do not // map guard pages anyway, so this is a minimal loss. Not guarding prevents // alignment mismatches // if (Granularity != EFI_PAGE_SIZE) { NeedGuard = FALSE; } // // 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 == EfiReservedMemoryType) || (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, NoPages, Size); 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; }