xref: /aosp_15_r20/external/coreboot/src/soc/amd/common/pi/heapmanager.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <amdblocks/agesawrapper.h>
4 #include <amdblocks/BiosCallOuts.h>
5 #include <cbmem.h>
6 #include <string.h>
7 
agesa_heap_base(void)8 static void *agesa_heap_base(void)
9 {
10 	return cbmem_add(CBMEM_ID_RESUME_SCRATCH, BIOS_HEAP_SIZE);
11 }
12 
EmptyHeap(int unused)13 static void EmptyHeap(int unused)
14 {
15 	void *BiosManagerPtr = agesa_heap_base();
16 	memset(BiosManagerPtr, 0, BIOS_HEAP_SIZE);
17 }
18 
19 /*
20  * Name			agesa_GetTempHeapBase
21  * Brief description	Get the location for TempRam, the target location in
22  *			memory where AmdInitPost copies the heap prior to CAR
23  *			teardown.  AmdInitEnv calls this function after
24  *			teardown for the source address when relocation the
25  *			heap to its final location.
26  * Input parameters
27  *	Func		Unused
28  *	Data		Unused
29  *	ConfigPtr	Pointer to type AGESA_TEMP_HEAP_BASE_PARAMS
30  * Output parameters
31  *	Status		Indicates whether TempHeapAddress was successfully
32  *			set.
33  */
agesa_GetTempHeapBase(uint32_t Func,uintptr_t Data,void * ConfigPtr)34 AGESA_STATUS agesa_GetTempHeapBase(uint32_t Func, uintptr_t Data,
35 							void *ConfigPtr)
36 {
37 	AGESA_TEMP_HEAP_BASE_PARAMS *pTempHeapBase;
38 
39 	pTempHeapBase = (AGESA_TEMP_HEAP_BASE_PARAMS *)ConfigPtr;
40 	pTempHeapBase->TempHeapAddress = CONFIG_PI_AGESA_TEMP_RAM_BASE;
41 
42 	return AGESA_SUCCESS;
43 }
44 
45 /*
46  * Name			agesa_HeapRebase
47  * Brief description	AGESA may use internal hardcoded locations for its
48  *			heap.  Modern implementations allow the base to be
49  *			overridden by calling agesa_HeapRebase.
50  * Input parameters
51  *	Func		Unused
52  *	Data		Unused
53  *	ConfigPtr	Pointer to type AGESA_REBASE_PARAMS
54  * Output parameters
55  *	Status		Indicates whether HeapAddress was successfully
56  *			set.
57  */
agesa_HeapRebase(uint32_t Func,uintptr_t Data,void * ConfigPtr)58 AGESA_STATUS agesa_HeapRebase(uint32_t Func, uintptr_t Data, void *ConfigPtr)
59 {
60 	AGESA_REBASE_PARAMS *Rebase;
61 
62 	Rebase = (AGESA_REBASE_PARAMS *)ConfigPtr;
63 	Rebase->HeapAddress = (uintptr_t)agesa_heap_base();
64 	if (!Rebase->HeapAddress)
65 		Rebase->HeapAddress = CONFIG_PI_AGESA_CAR_HEAP_BASE;
66 
67 	return AGESA_SUCCESS;
68 }
69 
70 /*
71  * Name			FindAllocatedNode
72  * Brief description	Find an allocated node that matches the handle.
73  * Input parameter	The desired handle.
74  * Output parameters
75  *	pointer		Here is returned either the found node or the last
76  *			allocated node if the handle is not found. This is
77  *			intentional, as the field NextNode of this node will
78  *			have to be filled with the offset of the node being
79  *			created in procedure agesa_AllocateBuffer().
80  *	Status		Indicates if the node was or was not found.
81  */
FindAllocatedNode(uint32_t handle,BIOS_BUFFER_NODE ** last_allocd_or_match)82 static AGESA_STATUS FindAllocatedNode(uint32_t handle,
83 				BIOS_BUFFER_NODE **last_allocd_or_match)
84 {
85 	uint32_t            AllocNodeOffset;
86 	uint8_t             *BiosHeapBaseAddr;
87 	BIOS_BUFFER_NODE    *AllocNodePtr;
88 	BIOS_HEAP_MANAGER   *BiosHeapBasePtr;
89 	AGESA_STATUS        Status = AGESA_SUCCESS;
90 
91 	BiosHeapBaseAddr = agesa_heap_base();
92 	BiosHeapBasePtr = (BIOS_HEAP_MANAGER *)BiosHeapBaseAddr;
93 
94 	AllocNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes;
95 	AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset);
96 
97 	while (handle != AllocNodePtr->BufferHandle) {
98 		if (AllocNodePtr->NextNodeOffset == 0) {
99 			Status = AGESA_BOUNDS_CHK;
100 			break;
101 		}
102 		AllocNodeOffset = AllocNodePtr->NextNodeOffset;
103 		AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr +
104 						    AllocNodeOffset);
105 	}
106 	*last_allocd_or_match = AllocNodePtr;
107 	return Status;
108 }
109 
110 /*
111  * Name			ConcatenateNodes
112  * Brief description	Concatenates two adjacent nodes into a single node,
113  *			this procedure is used by agesa_DeallocateBuffer().
114  * Input parameters
115  *	FirstNodePtr	This node is in the front, its header will be
116  *			maintained.
117  *	SecondNodePtr	This node is in the back, its header will be cleared.
118  */
ConcatenateNodes(BIOS_BUFFER_NODE * FirstNodePtr,BIOS_BUFFER_NODE * SecondNodePtr)119 static void ConcatenateNodes(BIOS_BUFFER_NODE *FirstNodePtr,
120 				BIOS_BUFFER_NODE *SecondNodePtr)
121 {
122 	FirstNodePtr->BufferSize += SecondNodePtr->BufferSize +
123 						sizeof(BIOS_BUFFER_NODE);
124 	FirstNodePtr->NextNodeOffset = SecondNodePtr->NextNodeOffset;
125 
126 	/* Zero out the SecondNode header */
127 	memset(SecondNodePtr, 0, sizeof(BIOS_BUFFER_NODE));
128 }
129 
130 CBMEM_CREATION_HOOK(EmptyHeap);
131 
agesa_AllocateBuffer(uint32_t Func,uintptr_t Data,void * ConfigPtr)132 AGESA_STATUS agesa_AllocateBuffer(uint32_t Func, uintptr_t Data,
133 							void *ConfigPtr)
134 {
135 	/*
136 	 * Size variables explanation:
137 	 * FreedNodeSize	- the size of the buffer node being examined,
138 	 *			will be copied to BestFitNodeSize if the node
139 	 *			is selected as a possible best fit.
140 	 * BestFitNodeSize	- the size qf the buffer of the node currently
141 	 *			considered the best fit.
142 	 * MinimumSize		- the requested size + sizeof(BIOS_BUFFER_NODE).
143 	 *			Its the minimum size for the buffer to be broken
144 	 *			down into 2 nodes, once a node is selected as
145 	 *			the best fit.
146 	 */
147 	uint32_t            AvailableHeapSize;
148 	uint8_t             *BiosHeapBaseAddr;
149 	uint32_t            CurrNodeOffset;
150 	uint32_t            PrevNodeOffset;
151 	uint32_t            FreedNodeOffset;
152 	uint32_t            FreedNodeSize;
153 	uint32_t            BestFitNodeOffset;
154 	uint32_t            BestFitNodeSize;
155 	uint32_t            BestFitPrevNodeOffset;
156 	uint32_t            NextFreeOffset;
157 	uint32_t            MinimumSize;
158 	BIOS_BUFFER_NODE   *CurrNodePtr;
159 	BIOS_BUFFER_NODE   *FreedNodePtr;
160 	BIOS_BUFFER_NODE   *BestFitNodePtr;
161 	BIOS_BUFFER_NODE   *BestFitPrevNodePtr;
162 	BIOS_BUFFER_NODE   *NextFreePtr;
163 	BIOS_HEAP_MANAGER  *BiosHeapBasePtr;
164 	AGESA_BUFFER_PARAMS *AllocParams;
165 	AGESA_STATUS        Status;
166 
167 	AllocParams = ((AGESA_BUFFER_PARAMS *)ConfigPtr);
168 	AllocParams->BufferPointer = NULL;
169 	MinimumSize = AllocParams->BufferLength + sizeof(BIOS_BUFFER_NODE);
170 
171 	AvailableHeapSize = BIOS_HEAP_SIZE - sizeof(BIOS_HEAP_MANAGER);
172 	BestFitNodeSize = AvailableHeapSize; /* init with largest possible */
173 	BiosHeapBaseAddr = agesa_heap_base();
174 	BiosHeapBasePtr = (BIOS_HEAP_MANAGER *)BiosHeapBaseAddr;
175 
176 	if (BiosHeapBasePtr->StartOfAllocatedNodes == 0) {
177 		/* First allocation */
178 		CurrNodeOffset = sizeof(BIOS_HEAP_MANAGER);
179 		CurrNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
180 						+ CurrNodeOffset);
181 		CurrNodePtr->BufferHandle = AllocParams->BufferHandle;
182 		CurrNodePtr->BufferSize = AllocParams->BufferLength;
183 		CurrNodePtr->NextNodeOffset = 0;
184 		AllocParams->BufferPointer = (uint8_t *)CurrNodePtr
185 						+ sizeof(BIOS_BUFFER_NODE);
186 
187 		/* Update the remaining free space */
188 		FreedNodeOffset = CurrNodeOffset + CurrNodePtr->BufferSize
189 						+ sizeof(BIOS_BUFFER_NODE);
190 		FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
191 						+ FreedNodeOffset);
192 		FreedNodePtr->BufferSize = AvailableHeapSize
193 					- (FreedNodeOffset - CurrNodeOffset)
194 					- sizeof(BIOS_BUFFER_NODE);
195 		FreedNodePtr->NextNodeOffset = 0;
196 
197 		/* Update the offsets for Allocated and Freed nodes */
198 		BiosHeapBasePtr->StartOfAllocatedNodes = CurrNodeOffset;
199 		BiosHeapBasePtr->StartOfFreedNodes = FreedNodeOffset;
200 	} else {
201 		/*
202 		 * Find out whether BufferHandle has been allocated on the heap.
203 		 * If it has, return AGESA_BOUNDS_CHK.
204 		 */
205 		Status = FindAllocatedNode(AllocParams->BufferHandle,
206 						&CurrNodePtr);
207 		if (Status == AGESA_SUCCESS)
208 			return AGESA_BOUNDS_CHK;
209 
210 		/*
211 		 * If status ditn't returned AGESA_SUCCESS, CurrNodePtr here
212 		 * points to the end of the allocated nodes list.
213 		 */
214 
215 		/* Find the node that best fits the requested buffer size */
216 		FreedNodeOffset = BiosHeapBasePtr->StartOfFreedNodes;
217 		PrevNodeOffset = FreedNodeOffset;
218 		BestFitNodeOffset = 0;
219 		BestFitPrevNodeOffset = 0;
220 		while (FreedNodeOffset != 0) {
221 			FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
222 						+ FreedNodeOffset);
223 			FreedNodeSize = FreedNodePtr->BufferSize;
224 			if (FreedNodeSize >= MinimumSize) {
225 				if (BestFitNodeOffset == 0) {
226 					/*
227 					 * First node that fits the requested
228 					 * buffer size
229 					 */
230 					BestFitNodeOffset = FreedNodeOffset;
231 					BestFitPrevNodeOffset = PrevNodeOffset;
232 					BestFitNodeSize = FreedNodeSize;
233 				} else {
234 					/*
235 					 * Find out whether current node is a
236 					 * betterfit than the previous nodes
237 					 */
238 					if (BestFitNodeSize > FreedNodeSize) {
239 						BestFitNodeOffset =
240 							FreedNodeOffset;
241 						BestFitPrevNodeOffset =
242 							PrevNodeOffset;
243 						BestFitNodeSize = FreedNodeSize;
244 					}
245 				}
246 			}
247 			PrevNodeOffset = FreedNodeOffset;
248 			FreedNodeOffset = FreedNodePtr->NextNodeOffset;
249 		} /* end of while loop */
250 
251 		if (BestFitNodeOffset == 0) {
252 			/*
253 			 * If we could not find a node that fits the requested
254 			 * buffer size, return AGESA_BOUNDS_CHK.
255 			 */
256 			return AGESA_BOUNDS_CHK;
257 		}
258 
259 		BestFitNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
260 					+ BestFitNodeOffset);
261 		BestFitPrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr +
262 							BestFitPrevNodeOffset);
263 
264 		/*
265 		 * If BestFitNode is larger than the requested buffer,
266 		 * fragment the node further
267 		 */
268 		if (BestFitNodePtr->BufferSize > MinimumSize) {
269 			NextFreeOffset = BestFitNodeOffset + MinimumSize;
270 			NextFreePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr +
271 				       NextFreeOffset);
272 			NextFreePtr->BufferSize = BestFitNodeSize - MinimumSize;
273 
274 			/* Remove BestFitNode from list of Freed nodes */
275 			NextFreePtr->NextNodeOffset =
276 					BestFitNodePtr->NextNodeOffset;
277 		} else {
278 			/*
279 			 * Otherwise, next free node is NextNodeOffset of
280 			 * BestFitNode. Remove it from list of Freed nodes.
281 			 */
282 			NextFreeOffset = BestFitNodePtr->NextNodeOffset;
283 		}
284 
285 		/*
286 		 * If BestFitNode is the first buffer in the list, then
287 		 * update StartOfFreedNodes to reflect new free node.
288 		 */
289 		if (BestFitNodeOffset == BiosHeapBasePtr->StartOfFreedNodes)
290 			BiosHeapBasePtr->StartOfFreedNodes = NextFreeOffset;
291 		else
292 			BestFitPrevNodePtr->NextNodeOffset = NextFreeOffset;
293 
294 		/* Add BestFitNode to the list of Allocated nodes */
295 		CurrNodePtr->NextNodeOffset = BestFitNodeOffset;
296 		BestFitNodePtr->BufferSize = AllocParams->BufferLength;
297 		BestFitNodePtr->BufferHandle = AllocParams->BufferHandle;
298 		BestFitNodePtr->NextNodeOffset = 0;
299 
300 		AllocParams->BufferPointer = (uint8_t *)BestFitNodePtr +
301 					     sizeof(BIOS_BUFFER_NODE);
302 	}
303 
304 	return AGESA_SUCCESS;
305 }
306 
agesa_DeallocateBuffer(uint32_t Func,uintptr_t Data,void * ConfigPtr)307 AGESA_STATUS agesa_DeallocateBuffer(uint32_t Func, uintptr_t Data,
308 							void *ConfigPtr)
309 {
310 	uint8_t             *BiosHeapBaseAddr;
311 	uint32_t            AllocNodeOffset;
312 	uint32_t            PrevNodeOffset;
313 	uint32_t            NextNodeOffset;
314 	uint32_t            FreedNodeOffset;
315 	uint32_t            EndNodeOffset;
316 	BIOS_BUFFER_NODE   *AllocNodePtr;
317 	BIOS_BUFFER_NODE   *PrevNodePtr;
318 	BIOS_BUFFER_NODE   *FreedNodePtr;
319 	BIOS_BUFFER_NODE   *NextNodePtr;
320 	BIOS_HEAP_MANAGER  *BiosHeapBasePtr;
321 	AGESA_BUFFER_PARAMS *AllocParams;
322 
323 	AllocParams = (AGESA_BUFFER_PARAMS *)ConfigPtr;
324 
325 	BiosHeapBaseAddr = agesa_heap_base();
326 	BiosHeapBasePtr = (BIOS_HEAP_MANAGER *)BiosHeapBaseAddr;
327 
328 	/* Find target node to deallocate in list of allocated nodes.
329 	 * Return AGESA_BOUNDS_CHK if the BufferHandle is not found.
330 	 */
331 	AllocNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes;
332 	AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset);
333 	PrevNodeOffset = AllocNodeOffset;
334 
335 	while (AllocNodePtr->BufferHandle != AllocParams->BufferHandle) {
336 		if (AllocNodePtr->NextNodeOffset == 0)
337 			return AGESA_BOUNDS_CHK;
338 		PrevNodeOffset = AllocNodeOffset;
339 		AllocNodeOffset = AllocNodePtr->NextNodeOffset;
340 		AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
341 						+ AllocNodeOffset);
342 	}
343 
344 	/* Remove target node from list of allocated nodes */
345 	PrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + PrevNodeOffset);
346 	PrevNodePtr->NextNodeOffset = AllocNodePtr->NextNodeOffset;
347 
348 	/* Zero out the buffer, and clear the BufferHandle */
349 	memset((uint8_t *)AllocNodePtr + sizeof(BIOS_BUFFER_NODE), 0,
350 		AllocNodePtr->BufferSize);
351 	AllocNodePtr->BufferHandle = 0;
352 
353 	/* Add deallocated node in order to the list of freed nodes */
354 	FreedNodeOffset = BiosHeapBasePtr->StartOfFreedNodes;
355 	FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + FreedNodeOffset);
356 
357 	EndNodeOffset = AllocNodeOffset + AllocNodePtr->BufferSize +
358 						sizeof(BIOS_BUFFER_NODE);
359 
360 	if (AllocNodeOffset < FreedNodeOffset) {
361 		/* Add to the start of the freed list */
362 		if (EndNodeOffset == FreedNodeOffset) {
363 			/* If the freed node is adjacent to the first node in
364 			 * the list, concatenate both nodes
365 			 */
366 			ConcatenateNodes(AllocNodePtr, FreedNodePtr);
367 		} else {
368 			/* Otherwise, add freed node to the start of the list
369 			 * Update NextNodeOffset and BufferSize to include the
370 			 * size of BIOS_BUFFER_NODE.
371 			 */
372 			AllocNodePtr->NextNodeOffset = FreedNodeOffset;
373 		}
374 		/* Update StartOfFreedNodes to the new first node */
375 		BiosHeapBasePtr->StartOfFreedNodes = AllocNodeOffset;
376 	} else {
377 		/* Traverse list of freed nodes to find where the deallocated
378 		 * node should be placed.
379 		 */
380 		NextNodeOffset = FreedNodeOffset;
381 		NextNodePtr = FreedNodePtr;
382 		while (AllocNodeOffset > NextNodeOffset) {
383 			PrevNodeOffset = NextNodeOffset;
384 			if (NextNodePtr->NextNodeOffset == 0)
385 				break;
386 			NextNodeOffset = NextNodePtr->NextNodeOffset;
387 			NextNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
388 						+ NextNodeOffset);
389 		}
390 
391 		/* If deallocated node is adjacent to the next node,
392 		 * concatenate both nodes.
393 		 */
394 		if (NextNodeOffset == EndNodeOffset) {
395 			NextNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
396 						+ NextNodeOffset);
397 			ConcatenateNodes(AllocNodePtr, NextNodePtr);
398 		} else {
399 			/*AllocNodePtr->NextNodeOffset =
400 			 *			FreedNodePtr->NextNodeOffset; */
401 			AllocNodePtr->NextNodeOffset = NextNodeOffset;
402 		}
403 		/*
404 		 * If deallocated node is adjacent to the previous node,
405 		 * concatenate both nodes.
406 		 */
407 		PrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr
408 						+ PrevNodeOffset);
409 		EndNodeOffset = PrevNodeOffset + PrevNodePtr->BufferSize +
410 						sizeof(BIOS_BUFFER_NODE);
411 
412 		if (AllocNodeOffset == EndNodeOffset)
413 			ConcatenateNodes(PrevNodePtr, AllocNodePtr);
414 		else
415 			PrevNodePtr->NextNodeOffset = AllocNodeOffset;
416 	}
417 	return AGESA_SUCCESS;
418 }
419 
agesa_LocateBuffer(uint32_t Func,uintptr_t Data,void * ConfigPtr)420 AGESA_STATUS agesa_LocateBuffer(uint32_t Func, uintptr_t Data, void *ConfigPtr)
421 {
422 	BIOS_BUFFER_NODE    *AllocNodePtr;
423 	AGESA_BUFFER_PARAMS *AllocParams;
424 	AGESA_STATUS        Status;
425 
426 	AllocParams = (AGESA_BUFFER_PARAMS *)ConfigPtr;
427 
428 	Status = FindAllocatedNode(AllocParams->BufferHandle, &AllocNodePtr);
429 
430 	if (Status == AGESA_SUCCESS) {
431 		AllocParams->BufferPointer = (uint8_t *)((uint8_t *)AllocNodePtr
432 						+ sizeof(BIOS_BUFFER_NODE));
433 		AllocParams->BufferLength = AllocNodePtr->BufferSize;
434 	}
435 
436 	return Status;
437 }
438