1 /* SPDX-License-Identifier: GPL-2.0-only */
10 #include <northbridge/amd/agesa/agesa_helper.h>
11 #include <northbridge/amd/agesa/BiosCallOuts.h>
13 #include <acpi/acpi.h>
14 #include <console/console.h>
17 /* BIOS_HEAP_START_ADDRESS is only for cold boots. */
18 #define BIOS_HEAP_SIZE 0x30000
19 #define BIOS_HEAP_START_ADDRESS 0x010000000
21 #if CONFIG(HAVE_ACPI_RESUME) && (HIGH_MEMORY_SCRATCH < BIOS_HEAP_SIZE)
22 #error Increase HIGH_MEMORY_SCRATCH allocation
25 void *GetHeapBase(void)
27 void *heap
= (void *)BIOS_HEAP_START_ADDRESS
;
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
);
41 void *base
= GetHeapBase();
42 memset(base
, 0, BIOS_HEAP_SIZE
);
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);
48 #if defined(HEAP_CALLOUT_RUNTIME) && ENV_RAMSTAGE
50 #define AGESA_RUNTIME_SIZE 4096
51 static AGESA_STATUS
alloc_cbmem(AGESA_BUFFER_PARAMS
*AllocParams
)
53 static unsigned int used
= 0;
54 void *p
= cbmem_find(CBMEM_ID_AGESA_RUNTIME
);
56 if ((AGESA_RUNTIME_SIZE
- used
) < AllocParams
->BufferLength
) {
57 return AGESA_BOUNDS_CHK
;
60 /* first time allocation */
62 p
= cbmem_add(CBMEM_ID_AGESA_RUNTIME
, AGESA_RUNTIME_SIZE
);
64 return AGESA_BOUNDS_CHK
;
67 AllocParams
->BufferPointer
= p
+ used
;
68 used
+= AllocParams
->BufferLength
;
73 typedef struct _BIOS_HEAP_MANAGER
{
74 UINT32 StartOfAllocatedNodes
;
75 UINT32 StartOfFreedNodes
;
78 typedef struct _BIOS_BUFFER_NODE
{
81 UINT32 NextNodeOffset
;
84 static AGESA_STATUS
agesa_AllocateBuffer(BIOS_HEAP_MANAGER
*BiosHeapBasePtr
,
85 AGESA_BUFFER_PARAMS
*AllocParams
)
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
;
101 AllocParams
->BufferPointer
= NULL
;
102 AvailableHeapSize
= BIOS_HEAP_SIZE
- sizeof(BIOS_HEAP_MANAGER
);
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
);
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;
121 /* Update the offsets for Allocated and Freed nodes */
122 BiosHeapBasePtr
->StartOfAllocatedNodes
= CurrNodeOffset
;
123 BiosHeapBasePtr
->StartOfFreedNodes
= FreedNodeOffset
;
125 /* Find out whether BufferHandle has been allocated on the heap.
126 * If it has, return AGESA_BOUNDS_CHK.
128 CurrNodeOffset
= BiosHeapBasePtr
->StartOfAllocatedNodes
;
129 CurrNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ CurrNodeOffset
);
131 while (CurrNodeOffset
!= 0) {
132 CurrNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ CurrNodeOffset
);
133 if (CurrNodePtr
->BufferHandle
== AllocParams
->BufferHandle
) {
134 return AGESA_BOUNDS_CHK
;
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.
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
;
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
;
162 PrevNodeOffset
= FreedNodeOffset
;
163 FreedNodeOffset
= FreedNodePtr
->NextNodeOffset
;
164 } /* end of while loop */
166 if (BestFitNodeOffset
== 0) {
167 /* If we could not find a node that fits the requested buffer
168 * size, return AGESA_BOUNDS_CHK.
170 return AGESA_BOUNDS_CHK
;
172 BestFitNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ BestFitNodeOffset
);
173 BestFitPrevNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ BestFitPrevNodeOffset
);
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
);
179 NextFreePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ NextFreeOffset
);
180 NextFreePtr
->BufferSize
= BestFitNodePtr
->BufferSize
- (AllocParams
->BufferLength
+ sizeof(BIOS_BUFFER_NODE
));
181 NextFreePtr
->NextNodeOffset
= BestFitNodePtr
->NextNodeOffset
;
183 /* Otherwise, next free node is NextNodeOffset of BestFitNode */
184 NextFreeOffset
= BestFitNodePtr
->NextNodeOffset
;
187 /* If BestFitNode is the first buffer in the list, then update
188 * StartOfFreedNodes to reflect the new free node.
190 if (BestFitNodeOffset
== BiosHeapBasePtr
->StartOfFreedNodes
) {
191 BiosHeapBasePtr
->StartOfFreedNodes
= NextFreeOffset
;
193 BestFitPrevNodePtr
->NextNodeOffset
= NextFreeOffset
;
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;
202 /* Remove BestFitNode from list of Freed nodes */
203 AllocParams
->BufferPointer
= (UINT8
*)BestFitNodePtr
+ sizeof(BIOS_BUFFER_NODE
);
207 return AGESA_SUCCESS
;
210 static AGESA_STATUS
agesa_DeallocateBuffer(BIOS_HEAP_MANAGER
*BiosHeapBasePtr
,
211 AGESA_BUFFER_PARAMS
*AllocParams
)
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
;
224 /* Find target node to deallocate in list of allocated nodes.
225 * Return AGESA_BOUNDS_CHK if the BufferHandle is not found.
227 AllocNodeOffset
= BiosHeapBasePtr
->StartOfAllocatedNodes
;
228 AllocNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ AllocNodeOffset
);
229 PrevNodeOffset
= AllocNodeOffset
;
231 while (AllocNodePtr
->BufferHandle
!= AllocParams
->BufferHandle
) {
232 if (AllocNodePtr
->NextNodeOffset
== 0) {
233 return AGESA_BOUNDS_CHK
;
235 PrevNodeOffset
= AllocNodeOffset
;
236 AllocNodeOffset
= AllocNodePtr
->NextNodeOffset
;
237 AllocNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ AllocNodeOffset
);
240 /* Remove target node from list of allocated nodes */
241 PrevNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ PrevNodeOffset
);
242 PrevNodePtr
->NextNodeOffset
= AllocNodePtr
->NextNodeOffset
;
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;
248 /* Add deallocated node in order to the list of freed nodes */
249 FreedNodeOffset
= BiosHeapBasePtr
->StartOfFreedNodes
;
250 FreedNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ FreedNodeOffset
);
252 EndNodeOffset
= AllocNodeOffset
+ AllocNodePtr
->BufferSize
+
253 sizeof(BIOS_BUFFER_NODE
);
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
;
263 /* Zero out the FreedNode header */
264 memset((UINT8
*)FreedNodePtr
, 0,
265 sizeof(BIOS_BUFFER_NODE
));
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.
271 AllocNodePtr
->NextNodeOffset
= FreedNodeOffset
;
273 /* Update StartOfFreedNodes to the new first node */
274 BiosHeapBasePtr
->StartOfFreedNodes
= AllocNodeOffset
;
276 /* Traverse list of freed nodes to find where the deallocated node
279 NextNodeOffset
= FreedNodeOffset
;
280 NextNodePtr
= FreedNodePtr
;
281 while (AllocNodeOffset
> NextNodeOffset
) {
282 PrevNodeOffset
= NextNodeOffset
;
283 if (NextNodePtr
->NextNodeOffset
== 0) {
286 NextNodeOffset
= NextNodePtr
->NextNodeOffset
;
287 NextNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ NextNodeOffset
);
290 /* If deallocated node is adjacent to the next node,
291 * concatenate both nodes.
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
;
299 /* Zero out the NextNode header */
300 memset((UINT8
*)NextNodePtr
, 0,
301 sizeof(BIOS_BUFFER_NODE
));
303 /*AllocNodePtr->NextNodeOffset = FreedNodePtr->NextNodeOffset; */
304 AllocNodePtr
->NextNodeOffset
= NextNodeOffset
;
306 /* If deallocated node is adjacent to the previous node,
307 * concatenate both nodes.
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
);
317 /* Zero out the AllocNode header */
318 memset((UINT8
*)AllocNodePtr
, 0,
319 sizeof(BIOS_BUFFER_NODE
));
321 PrevNodePtr
->NextNodeOffset
= AllocNodeOffset
;
324 return AGESA_SUCCESS
;
327 static AGESA_STATUS
agesa_LocateBuffer(BIOS_HEAP_MANAGER
*BiosHeapBasePtr
,
328 AGESA_BUFFER_PARAMS
*AllocParams
)
330 UINT32 AllocNodeOffset
;
331 UINT8
*BiosHeapBaseAddr
= (void *)BiosHeapBasePtr
;
332 BIOS_BUFFER_NODE
*AllocNodePtr
;
334 AllocNodeOffset
= BiosHeapBasePtr
->StartOfAllocatedNodes
;
335 AllocNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ AllocNodeOffset
);
337 while (AllocParams
->BufferHandle
!= AllocNodePtr
->BufferHandle
) {
338 if (AllocNodePtr
->NextNodeOffset
== 0) {
339 AllocParams
->BufferPointer
= NULL
;
340 AllocParams
->BufferLength
= 0;
341 return AGESA_BOUNDS_CHK
;
343 AllocNodeOffset
= AllocNodePtr
->NextNodeOffset
;
344 AllocNodePtr
= (BIOS_BUFFER_NODE
*)(BiosHeapBaseAddr
+ AllocNodeOffset
);
348 AllocParams
->BufferPointer
= (UINT8
*)((UINT8
*)AllocNodePtr
+ sizeof(BIOS_BUFFER_NODE
));
349 AllocParams
->BufferLength
= AllocNodePtr
->BufferSize
;
351 return AGESA_SUCCESS
;
354 AGESA_STATUS
HeapManagerCallout(UINT32 Func
, UINTN Data
, VOID
*ConfigPtr
)
356 AGESA_BUFFER_PARAMS
*AllocParams
= ConfigPtr
;
358 #if defined(HEAP_CALLOUT_RUNTIME) && ENV_RAMSTAGE
359 if (Func
== AGESA_ALLOCATE_BUFFER
&& Data
== HEAP_CALLOUT_RUNTIME
)
360 return alloc_cbmem(AllocParams
);
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
);
371 return AGESA_UNSUPPORTED
;