xref: /aosp_15_r20/external/coreboot/src/drivers/amd/agesa/heapmanager.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #pragma pack(push)
4 #include <AGESA.h>
5 #pragma pack(pop)
6 
7 #include <amdlib.h>
8 
9 #include <cbmem.h>
10 #include <northbridge/amd/agesa/agesa_helper.h>
11 #include <northbridge/amd/agesa/BiosCallOuts.h>
12 
13 #include <acpi/acpi.h>
14 #include <console/console.h>
15 #include <string.h>
16 
17 /* BIOS_HEAP_START_ADDRESS is only for cold boots. */
18 #define BIOS_HEAP_SIZE		0x30000
19 #define BIOS_HEAP_START_ADDRESS	0x010000000
20 
21 #if CONFIG(HAVE_ACPI_RESUME) && (HIGH_MEMORY_SCRATCH < BIOS_HEAP_SIZE)
22 #error Increase HIGH_MEMORY_SCRATCH allocation
23 #endif
24 
GetHeapBase(void)25 void *GetHeapBase(void)
26 {
27 	void *heap = (void *)BIOS_HEAP_START_ADDRESS;
28 
29 	if (acpi_is_wakeup_s3()) {
30 		/* FIXME: For S3 resume path, buffer is in CBMEM
31 		 * with some arbitrary header. */
32 		heap = cbmem_find(CBMEM_ID_RESUME_SCRATCH);
33 		heap += 0x10;
34 	}
35 
36 	return heap;
37 }
38 
EmptyHeap(void)39 void EmptyHeap(void)
40 {
41 	void *base = GetHeapBase();
42 	memset(base, 0, BIOS_HEAP_SIZE);
43 
44 	printk(BIOS_DEBUG, "Wiped HEAP at [%08x - %08x]\n",
45 		(unsigned int)(uintptr_t)base, (unsigned int)(uintptr_t)base + BIOS_HEAP_SIZE - 1);
46 }
47 
48 #if defined(HEAP_CALLOUT_RUNTIME) && ENV_RAMSTAGE
49 
50 #define AGESA_RUNTIME_SIZE 4096
alloc_cbmem(AGESA_BUFFER_PARAMS * AllocParams)51 static AGESA_STATUS alloc_cbmem(AGESA_BUFFER_PARAMS *AllocParams)
52 {
53 	static unsigned int used = 0;
54 	void *p = cbmem_find(CBMEM_ID_AGESA_RUNTIME);
55 
56 	if ((AGESA_RUNTIME_SIZE - used) < AllocParams->BufferLength) {
57 		return AGESA_BOUNDS_CHK;
58 	}
59 
60 	/* first time allocation */
61 	if (!p) {
62 		p = cbmem_add(CBMEM_ID_AGESA_RUNTIME, AGESA_RUNTIME_SIZE);
63 		if (!p)
64 			return AGESA_BOUNDS_CHK;
65 	}
66 
67 	AllocParams->BufferPointer = p + used;
68 	used += AllocParams->BufferLength;
69 	return AGESA_SUCCESS;
70 }
71 #endif
72 
73 typedef struct _BIOS_HEAP_MANAGER {
74 	UINT32 StartOfAllocatedNodes;
75 	UINT32 StartOfFreedNodes;
76 } BIOS_HEAP_MANAGER;
77 
78 typedef struct _BIOS_BUFFER_NODE {
79 	UINT32 BufferHandle;
80 	UINT32 BufferSize;
81 	UINT32 NextNodeOffset;
82 } BIOS_BUFFER_NODE;
83 
agesa_AllocateBuffer(BIOS_HEAP_MANAGER * BiosHeapBasePtr,AGESA_BUFFER_PARAMS * AllocParams)84 static AGESA_STATUS agesa_AllocateBuffer(BIOS_HEAP_MANAGER *BiosHeapBasePtr,
85 	AGESA_BUFFER_PARAMS *AllocParams)
86 {
87 	UINT32              AvailableHeapSize;
88 	UINT8               *BiosHeapBaseAddr = (void *)BiosHeapBasePtr;
89 	UINT32              CurrNodeOffset;
90 	UINT32              PrevNodeOffset;
91 	UINT32              FreedNodeOffset;
92 	UINT32              BestFitNodeOffset;
93 	UINT32              BestFitPrevNodeOffset;
94 	UINT32              NextFreeOffset;
95 	BIOS_BUFFER_NODE   *CurrNodePtr;
96 	BIOS_BUFFER_NODE   *FreedNodePtr;
97 	BIOS_BUFFER_NODE   *BestFitNodePtr;
98 	BIOS_BUFFER_NODE   *BestFitPrevNodePtr;
99 	BIOS_BUFFER_NODE   *NextFreePtr;
100 
101 	AllocParams->BufferPointer = NULL;
102 	AvailableHeapSize = BIOS_HEAP_SIZE - sizeof(BIOS_HEAP_MANAGER);
103 
104 	if (BiosHeapBasePtr->StartOfAllocatedNodes == 0) {
105 		/* First allocation */
106 		CurrNodeOffset = sizeof(BIOS_HEAP_MANAGER);
107 		CurrNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + CurrNodeOffset);
108 		CurrNodePtr->BufferHandle = AllocParams->BufferHandle;
109 		CurrNodePtr->BufferSize = AllocParams->BufferLength;
110 		CurrNodePtr->NextNodeOffset = 0;
111 		AllocParams->BufferPointer = (UINT8 *)CurrNodePtr + sizeof(BIOS_BUFFER_NODE);
112 
113 		/* Update the remaining free space */
114 		FreedNodeOffset = CurrNodeOffset + CurrNodePtr->BufferSize + sizeof(BIOS_BUFFER_NODE);
115 		FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + FreedNodeOffset);
116 		FreedNodePtr->BufferSize = AvailableHeapSize
117 					- (FreedNodeOffset - CurrNodeOffset)
118 					- sizeof(BIOS_BUFFER_NODE);
119 		FreedNodePtr->NextNodeOffset = 0;
120 
121 		/* Update the offsets for Allocated and Freed nodes */
122 		BiosHeapBasePtr->StartOfAllocatedNodes = CurrNodeOffset;
123 		BiosHeapBasePtr->StartOfFreedNodes = FreedNodeOffset;
124 	} else {
125 		/* Find out whether BufferHandle has been allocated on the heap.
126 		 * If it has, return AGESA_BOUNDS_CHK.
127 		 */
128 		CurrNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes;
129 		CurrNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + CurrNodeOffset);
130 
131 		while (CurrNodeOffset != 0) {
132 			CurrNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + CurrNodeOffset);
133 			if (CurrNodePtr->BufferHandle == AllocParams->BufferHandle) {
134 				return AGESA_BOUNDS_CHK;
135 			}
136 			CurrNodeOffset = CurrNodePtr->NextNodeOffset;
137 			/* If BufferHandle has not been allocated on the heap, CurrNodePtr here points
138 			 * to the end of the allocated nodes list.
139 			 */
140 		}
141 		/* Find the node that best fits the requested buffer size */
142 		FreedNodeOffset = BiosHeapBasePtr->StartOfFreedNodes;
143 		PrevNodeOffset = FreedNodeOffset;
144 		BestFitNodeOffset = 0;
145 		BestFitPrevNodeOffset = 0;
146 		while (FreedNodeOffset != 0) {
147 			FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + FreedNodeOffset);
148 			if (FreedNodePtr->BufferSize >= (AllocParams->BufferLength + sizeof(BIOS_BUFFER_NODE))) {
149 				if (BestFitNodeOffset == 0) {
150 					/* First node that fits the requested buffer size */
151 					BestFitNodeOffset = FreedNodeOffset;
152 					BestFitPrevNodeOffset = PrevNodeOffset;
153 				} else {
154 					/* Find out whether current node is a better fit than the previous nodes */
155 					BestFitNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + BestFitNodeOffset);
156 					if (BestFitNodePtr->BufferSize > FreedNodePtr->BufferSize) {
157 						BestFitNodeOffset = FreedNodeOffset;
158 						BestFitPrevNodeOffset = PrevNodeOffset;
159 					}
160 				}
161 			}
162 			PrevNodeOffset = FreedNodeOffset;
163 			FreedNodeOffset = FreedNodePtr->NextNodeOffset;
164 		} /* end of while loop */
165 
166 		if (BestFitNodeOffset == 0) {
167 			/* If we could not find a node that fits the requested buffer
168 			 * size, return AGESA_BOUNDS_CHK.
169 			 */
170 			return AGESA_BOUNDS_CHK;
171 		} else {
172 			BestFitNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + BestFitNodeOffset);
173 			BestFitPrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + BestFitPrevNodeOffset);
174 
175 			/* If BestFitNode is larger than the requested buffer, fragment the node further */
176 			if (BestFitNodePtr->BufferSize > (AllocParams->BufferLength + sizeof(BIOS_BUFFER_NODE))) {
177 				NextFreeOffset = BestFitNodeOffset + AllocParams->BufferLength + sizeof(BIOS_BUFFER_NODE);
178 
179 				NextFreePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + NextFreeOffset);
180 				NextFreePtr->BufferSize = BestFitNodePtr->BufferSize - (AllocParams->BufferLength + sizeof(BIOS_BUFFER_NODE));
181 				NextFreePtr->NextNodeOffset = BestFitNodePtr->NextNodeOffset;
182 			} else {
183 				/* Otherwise, next free node is NextNodeOffset of BestFitNode */
184 				NextFreeOffset = BestFitNodePtr->NextNodeOffset;
185 			}
186 
187 			/* If BestFitNode is the first buffer in the list, then update
188 			 * StartOfFreedNodes to reflect the new free node.
189 			 */
190 			if (BestFitNodeOffset == BiosHeapBasePtr->StartOfFreedNodes) {
191 				BiosHeapBasePtr->StartOfFreedNodes = NextFreeOffset;
192 			} else {
193 				BestFitPrevNodePtr->NextNodeOffset = NextFreeOffset;
194 			}
195 
196 			/* Add BestFitNode to the list of Allocated nodes */
197 			CurrNodePtr->NextNodeOffset = BestFitNodeOffset;
198 			BestFitNodePtr->BufferSize = AllocParams->BufferLength;
199 			BestFitNodePtr->BufferHandle = AllocParams->BufferHandle;
200 			BestFitNodePtr->NextNodeOffset = 0;
201 
202 			/* Remove BestFitNode from list of Freed nodes */
203 			AllocParams->BufferPointer = (UINT8 *)BestFitNodePtr + sizeof(BIOS_BUFFER_NODE);
204 		}
205 	}
206 
207 	return AGESA_SUCCESS;
208 }
209 
agesa_DeallocateBuffer(BIOS_HEAP_MANAGER * BiosHeapBasePtr,AGESA_BUFFER_PARAMS * AllocParams)210 static AGESA_STATUS agesa_DeallocateBuffer(BIOS_HEAP_MANAGER *BiosHeapBasePtr,
211 	AGESA_BUFFER_PARAMS *AllocParams)
212 {
213 	UINT8               *BiosHeapBaseAddr = (void *)BiosHeapBasePtr;
214 	UINT32              AllocNodeOffset;
215 	UINT32              PrevNodeOffset;
216 	UINT32              NextNodeOffset;
217 	UINT32              FreedNodeOffset;
218 	UINT32              EndNodeOffset;
219 	BIOS_BUFFER_NODE   *AllocNodePtr;
220 	BIOS_BUFFER_NODE   *PrevNodePtr;
221 	BIOS_BUFFER_NODE   *FreedNodePtr;
222 	BIOS_BUFFER_NODE   *NextNodePtr;
223 
224 	/* Find target node to deallocate in list of allocated nodes.
225 	 * Return AGESA_BOUNDS_CHK if the BufferHandle is not found.
226 	 */
227 	AllocNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes;
228 	AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset);
229 	PrevNodeOffset = AllocNodeOffset;
230 
231 	while (AllocNodePtr->BufferHandle !=  AllocParams->BufferHandle) {
232 		if (AllocNodePtr->NextNodeOffset == 0) {
233 			return AGESA_BOUNDS_CHK;
234 		}
235 		PrevNodeOffset = AllocNodeOffset;
236 		AllocNodeOffset = AllocNodePtr->NextNodeOffset;
237 		AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset);
238 	}
239 
240 	/* Remove target node from list of allocated nodes */
241 	PrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + PrevNodeOffset);
242 	PrevNodePtr->NextNodeOffset = AllocNodePtr->NextNodeOffset;
243 
244 	/* Zero out the buffer, and clear the BufferHandle */
245 	LibAmdMemFill((UINT8 *)AllocNodePtr + sizeof(BIOS_BUFFER_NODE), 0, AllocNodePtr->BufferSize, &(AllocParams->StdHeader));
246 	AllocNodePtr->BufferHandle = 0;
247 
248 	/* Add deallocated node in order to the list of freed nodes */
249 	FreedNodeOffset = BiosHeapBasePtr->StartOfFreedNodes;
250 	FreedNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + FreedNodeOffset);
251 
252 	EndNodeOffset = AllocNodeOffset + AllocNodePtr->BufferSize +
253 						sizeof(BIOS_BUFFER_NODE);
254 
255 	if (AllocNodeOffset < FreedNodeOffset) {
256 		/* Add to the start of the freed list */
257 		if (EndNodeOffset == FreedNodeOffset) {
258 			/* If the freed node is adjacent to the first node in the list, concatenate both nodes */
259 			AllocNodePtr->BufferSize += FreedNodePtr->BufferSize +
260 						sizeof(BIOS_BUFFER_NODE);
261 			AllocNodePtr->NextNodeOffset = FreedNodePtr->NextNodeOffset;
262 
263 			/* Zero out the FreedNode header */
264 			memset((UINT8 *)FreedNodePtr, 0,
265 						sizeof(BIOS_BUFFER_NODE));
266 		} else {
267 			/* Otherwise, add freed node to the start of the list
268 			 * Update NextNodeOffset and BufferSize to include the
269 			 * size of BIOS_BUFFER_NODE.
270 			 */
271 			AllocNodePtr->NextNodeOffset = FreedNodeOffset;
272 		}
273 		/* Update StartOfFreedNodes to the new first node */
274 		BiosHeapBasePtr->StartOfFreedNodes = AllocNodeOffset;
275 	} else {
276 		/* Traverse list of freed nodes to find where the deallocated node
277 		 * should be placed.
278 		 */
279 		NextNodeOffset = FreedNodeOffset;
280 		NextNodePtr = FreedNodePtr;
281 		while (AllocNodeOffset > NextNodeOffset) {
282 			PrevNodeOffset = NextNodeOffset;
283 			if (NextNodePtr->NextNodeOffset == 0) {
284 				break;
285 			}
286 			NextNodeOffset = NextNodePtr->NextNodeOffset;
287 			NextNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + NextNodeOffset);
288 		}
289 
290 		/* If deallocated node is adjacent to the next node,
291 		 * concatenate both nodes.
292 		 */
293 		if (NextNodeOffset == EndNodeOffset) {
294 			NextNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + NextNodeOffset);
295 			AllocNodePtr->BufferSize += NextNodePtr->BufferSize +
296 						sizeof(BIOS_BUFFER_NODE);
297 			AllocNodePtr->NextNodeOffset = NextNodePtr->NextNodeOffset;
298 
299 			/* Zero out the NextNode header */
300 			memset((UINT8 *)NextNodePtr, 0,
301 						sizeof(BIOS_BUFFER_NODE));
302 		} else {
303 			/*AllocNodePtr->NextNodeOffset = FreedNodePtr->NextNodeOffset; */
304 			AllocNodePtr->NextNodeOffset = NextNodeOffset;
305 		}
306 		/* If deallocated node is adjacent to the previous node,
307 		 * concatenate both nodes.
308 		 */
309 		PrevNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + PrevNodeOffset);
310 		EndNodeOffset = PrevNodeOffset + PrevNodePtr->BufferSize +
311 						sizeof(BIOS_BUFFER_NODE);
312 		if (AllocNodeOffset == EndNodeOffset) {
313 			PrevNodePtr->NextNodeOffset = AllocNodePtr->NextNodeOffset;
314 			PrevNodePtr->BufferSize += AllocNodePtr->BufferSize +
315 						sizeof(BIOS_BUFFER_NODE);
316 
317 			/* Zero out the AllocNode header */
318 			memset((UINT8 *)AllocNodePtr, 0,
319 						sizeof(BIOS_BUFFER_NODE));
320 		} else {
321 			PrevNodePtr->NextNodeOffset = AllocNodeOffset;
322 		}
323 	}
324 	return AGESA_SUCCESS;
325 }
326 
agesa_LocateBuffer(BIOS_HEAP_MANAGER * BiosHeapBasePtr,AGESA_BUFFER_PARAMS * AllocParams)327 static AGESA_STATUS agesa_LocateBuffer(BIOS_HEAP_MANAGER *BiosHeapBasePtr,
328 	AGESA_BUFFER_PARAMS *AllocParams)
329 {
330 	UINT32              AllocNodeOffset;
331 	UINT8               *BiosHeapBaseAddr = (void *)BiosHeapBasePtr;
332 	BIOS_BUFFER_NODE   *AllocNodePtr;
333 
334 	AllocNodeOffset = BiosHeapBasePtr->StartOfAllocatedNodes;
335 	AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset);
336 
337 	while (AllocParams->BufferHandle != AllocNodePtr->BufferHandle) {
338 		if (AllocNodePtr->NextNodeOffset == 0) {
339 			AllocParams->BufferPointer = NULL;
340 			AllocParams->BufferLength = 0;
341 			return AGESA_BOUNDS_CHK;
342 		} else {
343 			AllocNodeOffset = AllocNodePtr->NextNodeOffset;
344 			AllocNodePtr = (BIOS_BUFFER_NODE *)(BiosHeapBaseAddr + AllocNodeOffset);
345 		}
346 	}
347 
348 	AllocParams->BufferPointer = (UINT8 *)((UINT8 *)AllocNodePtr + sizeof(BIOS_BUFFER_NODE));
349 	AllocParams->BufferLength = AllocNodePtr->BufferSize;
350 
351 	return AGESA_SUCCESS;
352 }
353 
HeapManagerCallout(UINT32 Func,UINTN Data,VOID * ConfigPtr)354 AGESA_STATUS HeapManagerCallout(UINT32 Func, UINTN Data, VOID *ConfigPtr)
355 {
356 	AGESA_BUFFER_PARAMS *AllocParams = ConfigPtr;
357 
358 #if defined(HEAP_CALLOUT_RUNTIME) && ENV_RAMSTAGE
359 	if (Func == AGESA_ALLOCATE_BUFFER && Data == HEAP_CALLOUT_RUNTIME)
360 		return alloc_cbmem(AllocParams);
361 #endif
362 
363 	/* Must not call GetHeapBase() in AGESA_UNSUPPORTED path. */
364 	if (Func == AGESA_LOCATE_BUFFER)
365 		return agesa_LocateBuffer(GetHeapBase(), AllocParams);
366 	else if (Func == AGESA_ALLOCATE_BUFFER)
367 		return agesa_AllocateBuffer(GetHeapBase(), AllocParams);
368 	else if (Func == AGESA_DEALLOCATE_BUFFER)
369 		return agesa_DeallocateBuffer(GetHeapBase(), AllocParams);
370 
371 	return AGESA_UNSUPPORTED;
372 }
373