/* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include 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)); } CBMEM_CREATION_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; }