diff options
Diffstat (limited to 'src/soc/amd/common/pi/heapmanager.c')
-rw-r--r-- | src/soc/amd/common/pi/heapmanager.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/soc/amd/common/pi/heapmanager.c b/src/soc/amd/common/pi/heapmanager.c new file mode 100644 index 000000000000..699bb534310b --- /dev/null +++ b/src/soc/amd/common/pi/heapmanager.c @@ -0,0 +1,440 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <amdblocks/agesawrapper.h> +#include <amdblocks/BiosCallOuts.h> +#include <cbmem.h> +#include <string.h> + +static void *agesa_heap_base(void) +{ + return cbmem_add(CBMEM_ID_RESUME_SCRATCH, BIOS_HEAP_SIZE); +} + +static void EmptyHeap(int unused) +{ + void *BiosManagerPtr = agesa_heap_base(); + memset(BiosManagerPtr, 0, BIOS_HEAP_SIZE); +} + +/* + * Name agesa_GetTempHeapBase + * Brief description Get the location for TempRam, the target location in + * memory where AmdInitPost copies the heap prior to CAR + * teardown. AmdInitEnv calls this function after + * teardown for the source address when relocation the + * heap to its final location. + * Input parameters + * Func Unused + * Data Unused + * ConfigPtr Pointer to type AGESA_TEMP_HEAP_BASE_PARAMS + * Output parameters + * Status Indicates whether TempHeapAddress was successfully + * set. + */ +AGESA_STATUS agesa_GetTempHeapBase(uint32_t Func, uintptr_t Data, + void *ConfigPtr) +{ + AGESA_TEMP_HEAP_BASE_PARAMS *pTempHeapBase; + + pTempHeapBase = (AGESA_TEMP_HEAP_BASE_PARAMS *)ConfigPtr; + pTempHeapBase->TempHeapAddress = CONFIG_PI_AGESA_TEMP_RAM_BASE; + + return AGESA_SUCCESS; +} + +/* + * Name agesa_HeapRebase + * Brief description AGESA may use internal hardcoded locations for its + * heap. Modern implementations allow the base to be + * overridden by calling agesa_HeapRebase. + * Input parameters + * Func Unused + * Data Unused + * ConfigPtr Pointer to type AGESA_REBASE_PARAMS + * Output parameters + * Status Indicates whether HeapAddress was successfully + * set. + */ +AGESA_STATUS agesa_HeapRebase(uint32_t Func, uintptr_t Data, void *ConfigPtr) +{ + AGESA_REBASE_PARAMS *Rebase; + + Rebase = (AGESA_REBASE_PARAMS *)ConfigPtr; + Rebase->HeapAddress = (uintptr_t)agesa_heap_base(); + if (!Rebase->HeapAddress) + Rebase->HeapAddress = CONFIG_PI_AGESA_CAR_HEAP_BASE; + + return AGESA_SUCCESS; +} + +/* + * Name FindAllocatedNode + * Brief description Find an allocated node that matches the handle. + * Input parameter The desired handle. + * Output parameters + * pointer Here is returned either the found node or the last + * allocated node if the handle is not found. This is + * intentional, as the field NextNode of this node will + * have to be filled with the offset of the node being + * created in procedure agesa_AllocateBuffer(). + * Status Indicates if the node was or was not found. + */ +static AGESA_STATUS FindAllocatedNode(uint32_t handle, + BIOS_BUFFER_NODE **last_allocd_or_match) +{ + uint32_t AllocNodeOffset; + uint8_t *BiosHeapBaseAddr; + BIOS_BUFFER_NODE *AllocNodePtr; + BIOS_HEAP_MANAGER *BiosHeapBasePtr; + AGESA_STATUS Status = AGESA_SUCCESS; + + BiosHeapBaseAddr = agesa_heap_base(); + BiosHeapBasePtr = (BIOS_HEAP_MANAGER *)BiosHeapBaseAddr; + + AllocNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes; + AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset); + + while (handle != AllocNodePtr->BufferHandle) { + if (AllocNodePtr->NextNodeOffset == 0) { + Status = AGESA_BOUNDS_CHK; + break; + } + AllocNodeOffset = AllocNodePtr->NextNodeOffset; + AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + AllocNodeOffset); + } + *last_allocd_or_match = AllocNodePtr; + return Status; +} + +/* + * Name ConcatenateNodes + * Brief description Concatenates two adjacent nodes into a single node, + * this procedure is used by agesa_DeallocateBuffer(). + * Input parameters + * FirstNodePtr This node is in the front, its header will be + * maintained. + * SecondNodePtr This node is in the back, its header will be cleared. + */ +static void ConcatenateNodes(BIOS_BUFFER_NODE *FirstNodePtr, + BIOS_BUFFER_NODE *SecondNodePtr) +{ + FirstNodePtr->BufferSize += SecondNodePtr->BufferSize + + sizeof(BIOS_BUFFER_NODE); + FirstNodePtr->NextNodeOffset = SecondNodePtr->NextNodeOffset; + + /* Zero out the SecondNode header */ + memset(SecondNodePtr, 0, sizeof(BIOS_BUFFER_NODE)); +} + +ROMSTAGE_CBMEM_INIT_HOOK(EmptyHeap) + +AGESA_STATUS agesa_AllocateBuffer(uint32_t Func, uintptr_t Data, + void *ConfigPtr) +{ + /* + * Size variables explanation: + * FreedNodeSize - the size of the buffer node being examined, + * will be copied to BestFitNodeSize if the node + * is selected as a possible best fit. + * BestFitNodeSize - the size qf the buffer of the node currently + * considered the best fit. + * MinimumSize - the requested size + sizeof(BIOS_BUFFER_NODE). + * Its the minimum size for the buffer to be broken + * down into 2 nodes, once a node is selected as + * the best fit. + */ + uint32_t AvailableHeapSize; + uint8_t *BiosHeapBaseAddr; + uint32_t CurrNodeOffset; + uint32_t PrevNodeOffset; + uint32_t FreedNodeOffset; + uint32_t FreedNodeSize; + uint32_t BestFitNodeOffset; + uint32_t BestFitNodeSize; + uint32_t BestFitPrevNodeOffset; + uint32_t NextFreeOffset; + uint32_t MinimumSize; + BIOS_BUFFER_NODE *CurrNodePtr; + BIOS_BUFFER_NODE *FreedNodePtr; + BIOS_BUFFER_NODE *BestFitNodePtr; + BIOS_BUFFER_NODE *BestFitPrevNodePtr; + BIOS_BUFFER_NODE *NextFreePtr; + BIOS_HEAP_MANAGER *BiosHeapBasePtr; + AGESA_BUFFER_PARAMS *AllocParams; + AGESA_STATUS Status; + + AllocParams = ((AGESA_BUFFER_PARAMS *)ConfigPtr); + AllocParams->BufferPointer = NULL; + MinimumSize = AllocParams->BufferLength + sizeof(BIOS_BUFFER_NODE); + + AvailableHeapSize = BIOS_HEAP_SIZE - sizeof(BIOS_HEAP_MANAGER); + BestFitNodeSize = AvailableHeapSize; /* init with largest possible */ + BiosHeapBaseAddr = agesa_heap_base(); + BiosHeapBasePtr = (BIOS_HEAP_MANAGER *)BiosHeapBaseAddr; + + if (BiosHeapBasePtr->StartOfAllocatedNodes == 0) { + /* First allocation */ + CurrNodeOffset = sizeof(BIOS_HEAP_MANAGER); + CurrNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + CurrNodeOffset); + CurrNodePtr->BufferHandle = AllocParams->BufferHandle; + CurrNodePtr->BufferSize = AllocParams->BufferLength; + CurrNodePtr->NextNodeOffset = 0; + AllocParams->BufferPointer = (uint8_t *)CurrNodePtr + + sizeof(BIOS_BUFFER_NODE); + + /* Update the remaining free space */ + FreedNodeOffset = CurrNodeOffset + CurrNodePtr->BufferSize + + sizeof(BIOS_BUFFER_NODE); + FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + FreedNodeOffset); + FreedNodePtr->BufferSize = AvailableHeapSize + - (FreedNodeOffset - CurrNodeOffset) + - sizeof(BIOS_BUFFER_NODE); + FreedNodePtr->NextNodeOffset = 0; + + /* Update the offsets for Allocated and Freed nodes */ + BiosHeapBasePtr->StartOfAllocatedNodes = CurrNodeOffset; + BiosHeapBasePtr->StartOfFreedNodes = FreedNodeOffset; + } else { + /* + * Find out whether BufferHandle has been allocated on the heap. + * If it has, return AGESA_BOUNDS_CHK. + */ + Status = FindAllocatedNode(AllocParams->BufferHandle, + &CurrNodePtr); + if (Status == AGESA_SUCCESS) + return AGESA_BOUNDS_CHK; + + /* + * If status ditn't returned AGESA_SUCCESS, CurrNodePtr here + * points to the end of the allocated nodes list. + */ + + /* Find the node that best fits the requested buffer size */ + FreedNodeOffset = BiosHeapBasePtr->StartOfFreedNodes; + PrevNodeOffset = FreedNodeOffset; + BestFitNodeOffset = 0; + BestFitPrevNodeOffset = 0; + while (FreedNodeOffset != 0) { + FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + FreedNodeOffset); + FreedNodeSize = FreedNodePtr->BufferSize; + if (FreedNodeSize >= MinimumSize) { + if (BestFitNodeOffset == 0) { + /* + * First node that fits the requested + * buffer size + */ + BestFitNodeOffset = FreedNodeOffset; + BestFitPrevNodeOffset = PrevNodeOffset; + BestFitNodeSize = FreedNodeSize; + } else { + /* + * Find out whether current node is a + * betterfit than the previous nodes + */ + if (BestFitNodeSize > FreedNodeSize) { + + BestFitNodeOffset = + FreedNodeOffset; + BestFitPrevNodeOffset = + PrevNodeOffset; + BestFitNodeSize = FreedNodeSize; + } + } + } + PrevNodeOffset = FreedNodeOffset; + FreedNodeOffset = FreedNodePtr->NextNodeOffset; + } /* end of while loop */ + + if (BestFitNodeOffset == 0) { + /* + * If we could not find a node that fits the requested + * buffer size, return AGESA_BOUNDS_CHK. + */ + return AGESA_BOUNDS_CHK; + } + + BestFitNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + BestFitNodeOffset); + BestFitPrevNodePtr = (BIOS_BUFFER_NODE *) (BiosHeapBaseAddr + + BestFitPrevNodeOffset); + + /* + * If BestFitNode is larger than the requested buffer, + * fragment the node further + */ + if (BestFitNodePtr->BufferSize > MinimumSize) { + NextFreeOffset = BestFitNodeOffset + MinimumSize; + NextFreePtr = (BIOS_BUFFER_NODE *) (BiosHeapBaseAddr + + NextFreeOffset); + NextFreePtr->BufferSize = BestFitNodeSize - MinimumSize; + + /* Remove BestFitNode from list of Freed nodes */ + NextFreePtr->NextNodeOffset = + BestFitNodePtr->NextNodeOffset; + } else { + /* + * Otherwise, next free node is NextNodeOffset of + * BestFitNode. Remove it from list of Freed nodes. + */ + NextFreeOffset = BestFitNodePtr->NextNodeOffset; + } + + /* + * If BestFitNode is the first buffer in the list, then + * update StartOfFreedNodes to reflect new free node. + */ + if (BestFitNodeOffset == BiosHeapBasePtr->StartOfFreedNodes) + BiosHeapBasePtr->StartOfFreedNodes = NextFreeOffset; + else + BestFitPrevNodePtr->NextNodeOffset = NextFreeOffset; + + /* Add BestFitNode to the list of Allocated nodes */ + CurrNodePtr->NextNodeOffset = BestFitNodeOffset; + BestFitNodePtr->BufferSize = AllocParams->BufferLength; + BestFitNodePtr->BufferHandle = AllocParams->BufferHandle; + BestFitNodePtr->NextNodeOffset = 0; + + AllocParams->BufferPointer = (uint8_t *)BestFitNodePtr + + sizeof(BIOS_BUFFER_NODE); + } + + return AGESA_SUCCESS; +} + +AGESA_STATUS agesa_DeallocateBuffer(uint32_t Func, uintptr_t Data, + void *ConfigPtr) +{ + + uint8_t *BiosHeapBaseAddr; + uint32_t AllocNodeOffset; + uint32_t PrevNodeOffset; + uint32_t NextNodeOffset; + uint32_t FreedNodeOffset; + uint32_t EndNodeOffset; + BIOS_BUFFER_NODE *AllocNodePtr; + BIOS_BUFFER_NODE *PrevNodePtr; + BIOS_BUFFER_NODE *FreedNodePtr; + BIOS_BUFFER_NODE *NextNodePtr; + BIOS_HEAP_MANAGER *BiosHeapBasePtr; + AGESA_BUFFER_PARAMS *AllocParams; + + AllocParams = (AGESA_BUFFER_PARAMS *)ConfigPtr; + + BiosHeapBaseAddr = agesa_heap_base(); + BiosHeapBasePtr = (BIOS_HEAP_MANAGER *)BiosHeapBaseAddr; + + /* Find target node to deallocate in list of allocated nodes. + * Return AGESA_BOUNDS_CHK if the BufferHandle is not found. + */ + AllocNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes; + AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset); + PrevNodeOffset = AllocNodeOffset; + + while (AllocNodePtr->BufferHandle != AllocParams->BufferHandle) { + if (AllocNodePtr->NextNodeOffset == 0) + return AGESA_BOUNDS_CHK; + PrevNodeOffset = AllocNodeOffset; + AllocNodeOffset = AllocNodePtr->NextNodeOffset; + AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + AllocNodeOffset); + } + + /* Remove target node from list of allocated nodes */ + PrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + PrevNodeOffset); + PrevNodePtr->NextNodeOffset = AllocNodePtr->NextNodeOffset; + + /* Zero out the buffer, and clear the BufferHandle */ + memset((uint8_t *)AllocNodePtr + sizeof(BIOS_BUFFER_NODE), 0, + AllocNodePtr->BufferSize); + AllocNodePtr->BufferHandle = 0; + + /* Add deallocated node in order to the list of freed nodes */ + FreedNodeOffset = BiosHeapBasePtr->StartOfFreedNodes; + FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + FreedNodeOffset); + + EndNodeOffset = AllocNodeOffset + AllocNodePtr->BufferSize + + sizeof(BIOS_BUFFER_NODE); + + if (AllocNodeOffset < FreedNodeOffset) { + /* Add to the start of the freed list */ + if (EndNodeOffset == FreedNodeOffset) { + /* If the freed node is adjacent to the first node in + * the list, concatenate both nodes + */ + ConcatenateNodes(AllocNodePtr, FreedNodePtr); + } else { + /* Otherwise, add freed node to the start of the list + * Update NextNodeOffset and BufferSize to include the + * size of BIOS_BUFFER_NODE. + */ + AllocNodePtr->NextNodeOffset = FreedNodeOffset; + } + /* Update StartOfFreedNodes to the new first node */ + BiosHeapBasePtr->StartOfFreedNodes = AllocNodeOffset; + } else { + /* Traverse list of freed nodes to find where the deallocated + * node should be placed. + */ + NextNodeOffset = FreedNodeOffset; + NextNodePtr = FreedNodePtr; + while (AllocNodeOffset > NextNodeOffset) { + PrevNodeOffset = NextNodeOffset; + if (NextNodePtr->NextNodeOffset == 0) + break; + NextNodeOffset = NextNodePtr->NextNodeOffset; + NextNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + NextNodeOffset); + } + + /* If deallocated node is adjacent to the next node, + * concatenate both nodes. + */ + if (NextNodeOffset == EndNodeOffset) { + NextNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + NextNodeOffset); + ConcatenateNodes(AllocNodePtr, NextNodePtr); + } else { + /*AllocNodePtr->NextNodeOffset = + * FreedNodePtr->NextNodeOffset; */ + AllocNodePtr->NextNodeOffset = NextNodeOffset; + } + /* + * If deallocated node is adjacent to the previous node, + * concatenate both nodes. + */ + PrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + + PrevNodeOffset); + EndNodeOffset = PrevNodeOffset + PrevNodePtr->BufferSize + + sizeof(BIOS_BUFFER_NODE); + + if (AllocNodeOffset == EndNodeOffset) + ConcatenateNodes(PrevNodePtr, AllocNodePtr); + else + PrevNodePtr->NextNodeOffset = AllocNodeOffset; + } + return AGESA_SUCCESS; +} + +AGESA_STATUS agesa_LocateBuffer(uint32_t Func, uintptr_t Data, void *ConfigPtr) +{ + BIOS_BUFFER_NODE *AllocNodePtr; + AGESA_BUFFER_PARAMS *AllocParams; + AGESA_STATUS Status; + + AllocParams = (AGESA_BUFFER_PARAMS *)ConfigPtr; + + Status = FindAllocatedNode(AllocParams->BufferHandle, &AllocNodePtr); + + if (Status == AGESA_SUCCESS) { + AllocParams->BufferPointer = (uint8_t *)((uint8_t *)AllocNodePtr + + sizeof(BIOS_BUFFER_NODE)); + AllocParams->BufferLength = AllocNodePtr->BufferSize; + } + + return Status; + +} |