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