From 1a70a690ea534c77639b92c811f6a6378b2bbea0 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Fri, 10 Oct 2014 11:24:11 +0000 Subject: [PATCH] ArmPkg/UncachedMemoryAllocationLib: Track uncached memory allocations Keeping track of uncached memory allocations prevents doing expensive cache operations (eg: clean & invalidate) on newly allocated regions by reusing regions where possible Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Olivier Martin git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@16205 6f19259b-4bc3-4df7-8a09-765794883524 --- ArmPkg/ArmPkg.dec | 3 + .../UncachedMemoryAllocationLib.c | 321 ++++++++++++------ .../UncachedMemoryAllocationLib.inf | 5 + 3 files changed, 217 insertions(+), 112 deletions(-) diff --git a/ArmPkg/ArmPkg.dec b/ArmPkg/ArmPkg.dec index 05bc1dcd6d..2a1947b9ca 100644 --- a/ArmPkg/ArmPkg.dec +++ b/ArmPkg/ArmPkg.dec @@ -77,6 +77,9 @@ gArmTokenSpaceGuid.PcdVFPEnabled|0|UINT32|0x00000024 gArmTokenSpaceGuid.PcdArmUncachedMemoryMask|0x0000000080000000|UINT64|0x00000002 + # This PCD will free the unallocated buffers if their size reach this threshold. + # We set the default value to 512MB. + gArmTokenSpaceGuid.PcdArmFreeUncachedMemorySizeThreshold|0x20000000|UINT64|0x00000043 gArmTokenSpaceGuid.PcdArmCacheOperationThreshold|1024|UINT32|0x00000003 gArmTokenSpaceGuid.PcdCpuVectorBaseAddress|0xffff0000|UINT32|0x00000004 gArmTokenSpaceGuid.PcdCpuResetAddress|0x00000000|UINT32|0x00000005 diff --git a/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.c b/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.c index 1209b926c1..e70d8777d7 100644 --- a/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.c +++ b/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.c @@ -3,6 +3,7 @@ a buffer. Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.
+ Copyright (c) 2014, AMR Ltd. All rights reserved.
This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License @@ -46,59 +47,227 @@ UncachedInternalAllocateAlignedPages ( UINT64 gAttributes; typedef struct { - VOID *Allocation; - UINTN Pages; - LIST_ENTRY Link; + EFI_PHYSICAL_ADDRESS Base; + VOID *Allocation; + UINTN Pages; + EFI_MEMORY_TYPE MemoryType; + BOOLEAN Allocated; + LIST_ENTRY Link; } FREE_PAGE_NODE; -LIST_ENTRY mPageList = INITIALIZE_LIST_HEAD_VARIABLE (mPageList); +STATIC LIST_ENTRY mPageList = INITIALIZE_LIST_HEAD_VARIABLE (mPageList); +// Track the size of the non-allocated buffer in the linked-list +STATIC UINTN mFreedBufferSize = 0; -VOID -AddPagesToList ( - IN VOID *Allocation, - UINTN Pages +/** + * This function firstly checks if the requested allocation can fit into one + * of the previously allocated buffer. + * If the requested allocation does not fit in the existing pool then + * the function makes a new allocation. + * + * @param MemoryType Type of memory requested for the new allocation + * @param Pages Number of requested page + * @param Alignment Required alignment + * @param Allocation Address of the newly allocated buffer + * + * @return EFI_SUCCESS If the function manage to allocate a buffer + * @return !EFI_SUCCESS If the function did not manage to allocate a buffer + */ +STATIC +EFI_STATUS +AllocatePagesFromList ( + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN Pages, + IN UINTN Alignment, + OUT VOID **Allocation ) { + EFI_STATUS Status; + LIST_ENTRY *Link; + FREE_PAGE_NODE *Node; FREE_PAGE_NODE *NewNode; + UINTN AlignmentMask; + EFI_PHYSICAL_ADDRESS Memory; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor; - NewNode = AllocatePool (sizeof (LIST_ENTRY)); - if (NewNode == NULL) { - ASSERT (FALSE); - return; + // Alignment must be a power of two or zero. + ASSERT ((Alignment & (Alignment - 1)) == 0); + + // + // Look in our list for the smallest page that could satisfy the new allocation + // + NewNode = NULL; + for (Link = mPageList.ForwardLink; Link != &mPageList; Link = Link->ForwardLink) { + Node = BASE_CR (Link, FREE_PAGE_NODE, Link); + if ((Node->Allocated == FALSE) && (Node->MemoryType == MemoryType)) { + // We have a node that fits our requirements + if (((UINTN)Node->Base & (Alignment - 1)) == 0) { + // We found a page that matches the page size + if (Node->Pages == Pages) { + Node->Allocated = TRUE; + Node->Allocation = (VOID*)(UINTN)Node->Base; + *Allocation = Node->Allocation; + + // Update the size of the freed buffer + mFreedBufferSize -= Pages * EFI_PAGE_SIZE; + return EFI_SUCCESS; + } else if (Node->Pages > Pages) { + if (NewNode == NULL) { + // It is the first node that could contain our new allocation + NewNode = Node; + } else if (NewNode->Pages > Node->Pages) { + // This node offers a smaller number of page. + NewNode = Node; + } + } + } + } + } + // Check if we have found a node that could contain our new allocation + if (NewNode != NULL) { + NewNode->Allocated = TRUE; + Node->Allocation = (VOID*)(UINTN)Node->Base; + *Allocation = Node->Allocation; + return EFI_SUCCESS; } - NewNode->Allocation = Allocation; + // + // Otherwise, we need to allocate a new buffer + // + + // We do not want to over-allocate in case the alignment requirement does not + // require extra pages + if (Alignment > EFI_PAGE_SIZE) { + AlignmentMask = Alignment - 1; + Pages += EFI_SIZE_TO_PAGES (Alignment); + } else { + AlignmentMask = 0; + } + + Status = gBS->AllocatePages (AllocateAnyPages, MemoryType, Pages, &Memory); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gDS->GetMemorySpaceDescriptor (Memory, &Descriptor); + if (!EFI_ERROR (Status)) { + // We are making an assumption that all of memory has the same default attributes + gAttributes = Descriptor.Attributes; + } else { + gBS->FreePages (Memory, Pages); + return Status; + } + + Status = gDS->SetMemorySpaceAttributes (Memory, EFI_PAGES_TO_SIZE (Pages), EFI_MEMORY_WC); + if (EFI_ERROR (Status)) { + gBS->FreePages (Memory, Pages); + return Status; + } + + NewNode = AllocatePool (sizeof (FREE_PAGE_NODE)); + if (NewNode == NULL) { + ASSERT (FALSE); + gBS->FreePages (Memory, Pages); + return EFI_OUT_OF_RESOURCES; + } + + NewNode->Base = Memory; + NewNode->Allocation = (VOID*)(((UINTN)Memory + AlignmentMask) & ~AlignmentMask); NewNode->Pages = Pages; + NewNode->Allocated = TRUE; + NewNode->MemoryType = MemoryType; InsertTailList (&mPageList, &NewNode->Link); + + *Allocation = NewNode->Allocation; + return EFI_SUCCESS; } +/** + * Free the memory allocation + * + * This function will actually try to find the allocation in the linked list. + * And it will then mark the entry as freed. + * + * @param Allocation Base address of the buffer to free + * + * @return EFI_SUCCESS The allocation has been freed + * @return EFI_NOT_FOUND The allocation was not found in the pool. + * @return EFI_INVALID_PARAMETER If Allocation is NULL + * + */ +STATIC +EFI_STATUS +FreePagesFromList ( + IN VOID *Allocation + ) +{ + LIST_ENTRY *Link; + FREE_PAGE_NODE *Node; -VOID -RemovePagesFromList ( - OUT VOID *Allocation, - OUT UINTN *Pages + if (Allocation == NULL) { + return EFI_INVALID_PARAMETER; + } + + for (Link = mPageList.ForwardLink; Link != &mPageList; Link = Link->ForwardLink) { + Node = BASE_CR (Link, FREE_PAGE_NODE, Link); + if ((UINTN)Node->Allocation == (UINTN)Allocation) { + Node->Allocated = FALSE; + + // Update the size of the freed buffer + mFreedBufferSize += Node->Pages * EFI_PAGE_SIZE; + + // If the size of the non-allocated reaches the threshold we raise a warning. + // It might be an expected behaviour in some cases. + // We might device to free some of these buffers later on. + if (mFreedBufferSize > PcdGet64 (PcdArmFreeUncachedMemorySizeThreshold)) { + DEBUG ((EFI_D_WARN, "Warning: The list of non-allocated buffer has reach the threshold.\n")); + } + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +/** + * This function is automatically invoked when the driver exits + * It frees all the non-allocated memory buffer. + * This function is not responsible to free allocated buffer (eg: case of memory leak, + * runtime allocation). + */ +EFI_STATUS +EFIAPI +UncachedMemoryAllocationLibDestructor ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable ) { LIST_ENTRY *Link; FREE_PAGE_NODE *OldNode; - *Pages = 0; - - for (Link = mPageList.ForwardLink; Link != &mPageList; Link = Link->ForwardLink) { - OldNode = BASE_CR (Link, FREE_PAGE_NODE, Link); - if (OldNode->Allocation == Allocation) { - *Pages = OldNode->Pages; - - RemoveEntryList (&OldNode->Link); - FreePool (OldNode); - return; - } + // Test if the list is empty + Link = mPageList.ForwardLink; + if (Link == &mPageList) { + return EFI_SUCCESS; } - return; -} + // Free all the pages and nodes + do { + OldNode = BASE_CR (Link, FREE_PAGE_NODE, Link); + // Point to the next entry + Link = Link->ForwardLink; + // We only free the non-allocated buffer + if (OldNode->Allocated == FALSE) { + gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)OldNode->Base, OldNode->Pages); + RemoveEntryList (&OldNode->Link); + FreePool (OldNode); + } + } while (Link != &mPageList); + + return EFI_SUCCESS; +} /** Converts a cached or uncached address to a physical address suitable for use in SoC registers. @@ -175,76 +344,21 @@ UncachedInternalAllocateAlignedPages ( IN UINTN Alignment ) { - EFI_STATUS Status; - EFI_PHYSICAL_ADDRESS Memory; - EFI_PHYSICAL_ADDRESS AlignedMemory; - UINTN AlignmentMask; - UINTN UnalignedPages; - UINTN RealPages; - EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor; - - // - // Alignment must be a power of two or zero. - // - ASSERT ((Alignment & (Alignment - 1)) == 0); + EFI_STATUS Status; + VOID *Allocation; if (Pages == 0) { return NULL; } - if (Alignment > EFI_PAGE_SIZE) { - // - // Caculate the total number of pages since alignment is larger than page size. - // - AlignmentMask = Alignment - 1; - RealPages = Pages + EFI_SIZE_TO_PAGES (Alignment); - // - // Make sure that Pages plus EFI_SIZE_TO_PAGES (Alignment) does not overflow. - // - ASSERT (RealPages > Pages); - Status = gBS->AllocatePages (AllocateAnyPages, MemoryType, RealPages, &Memory); - if (EFI_ERROR (Status)) { - return NULL; - } - AlignedMemory = ((UINTN) Memory + AlignmentMask) & ~AlignmentMask; - UnalignedPages = EFI_SIZE_TO_PAGES (AlignedMemory - (UINTN) Memory); - if (UnalignedPages > 0) { - // - // Free first unaligned page(s). - // - Status = gBS->FreePages (Memory, UnalignedPages); - ASSERT_EFI_ERROR (Status); - } - Memory = (EFI_PHYSICAL_ADDRESS) (AlignedMemory + EFI_PAGES_TO_SIZE (Pages)); - UnalignedPages = RealPages - Pages - UnalignedPages; - if (UnalignedPages > 0) { - // - // Free last unaligned page(s). - // - Status = gBS->FreePages (Memory, UnalignedPages); - ASSERT_EFI_ERROR (Status); - } + Allocation = NULL; + Status = AllocatePagesFromList (MemoryType, Pages, Alignment, &Allocation); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return NULL; } else { - // - // Do not over-allocate pages in this case. - // - Status = gBS->AllocatePages (AllocateAnyPages, MemoryType, Pages, &Memory); - if (EFI_ERROR (Status)) { - return NULL; - } - AlignedMemory = (UINTN) Memory; + return Allocation; } - - Status = gDS->GetMemorySpaceDescriptor (Memory, &Descriptor); - if (!EFI_ERROR (Status)) { - // We are making an assumption that all of memory has the same default attributes - gAttributes = Descriptor.Attributes; - } - - Status = gDS->SetMemorySpaceAttributes (Memory, EFI_PAGES_TO_SIZE (Pages), EFI_MEMORY_WC); - ASSERT_EFI_ERROR (Status); - - return (VOID *)(UINTN)Memory; } @@ -255,21 +369,10 @@ UncachedFreeAlignedPages ( IN UINTN Pages ) { - EFI_STATUS Status; - EFI_PHYSICAL_ADDRESS Memory; - - ASSERT (Pages != 0); - - Memory = (EFI_PHYSICAL_ADDRESS) (UINTN) Buffer; - Status = gDS->SetMemorySpaceAttributes (Memory, EFI_PAGES_TO_SIZE (Pages), gAttributes); - - Status = gBS->FreePages (Memory, Pages); - ASSERT_EFI_ERROR (Status); + FreePagesFromList (Buffer); } - - VOID * UncachedInternalAllocateAlignedPool ( IN EFI_MEMORY_TYPE PoolType, @@ -293,8 +396,6 @@ UncachedInternalAllocateAlignedPool ( return NULL; } - AddPagesToList ((VOID *)(UINTN)AlignedAddress, EFI_SIZE_TO_PAGES (AllocationSize)); - return (VOID *) AlignedAddress; } @@ -432,11 +533,7 @@ UncachedFreeAlignedPool ( IN VOID *Allocation ) { - UINTN Pages; - - RemovePagesFromList (Allocation, &Pages); - - UncachedFreePages (Allocation, Pages); + UncachedFreePages (Allocation, 0); } VOID * diff --git a/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.inf b/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.inf index 3fc16699c9..0a0b6cbcc8 100644 --- a/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.inf +++ b/ArmPkg/Library/UncachedMemoryAllocationLib/UncachedMemoryAllocationLib.inf @@ -23,6 +23,8 @@ VERSION_STRING = 1.0 LIBRARY_CLASS = UncachedMemoryAllocationLib + DESTRUCTOR = UncachedMemoryAllocationLibDestructor + [Sources.common] UncachedMemoryAllocationLib.c @@ -34,5 +36,8 @@ BaseLib ArmLib MemoryAllocationLib + PcdLib DxeServicesTableLib +[Pcd] + gArmTokenSpaceGuid.PcdArmFreeUncachedMemorySizeThreshold