summaryrefslogtreecommitdiffstats
path: root/src/soc/amd/common/pi/heapmanager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/soc/amd/common/pi/heapmanager.c')
-rw-r--r--src/soc/amd/common/pi/heapmanager.c440
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;
+
+}