]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPkg/Library/BdsLib/BdsLinuxFdt.c
ArmPlatformPkg: Add the LinuxLoader.efi EFI application
[mirror_edk2.git] / ArmPkg / Library / BdsLib / BdsLinuxFdt.c
1 /** @file
2 *
3 * Copyright (c) 2011-2014, ARM Limited. All rights reserved.
4 *
5 * This program and the accompanying materials
6 * are licensed and made available under the terms and conditions of the BSD License
7 * which accompanies this distribution. The full text of the license may be found at
8 * http://opensource.org/licenses/bsd-license.php
9 *
10 * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12 *
13 **/
14
15 #include <Library/ArmSmcLib.h>
16 #include <Library/PcdLib.h>
17 #include <libfdt.h>
18
19 #include "BdsInternal.h"
20 #include "BdsLinuxLoader.h"
21
22 #define ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1))
23 #define PALIGN(p, a) ((void *)(ALIGN((unsigned long)(p), (a))))
24 #define GET_CELL(p) (p += 4, *((const UINT32 *)(p-4)))
25
26 STATIC
27 UINTN
28 cpu_to_fdtn (UINTN x) {
29 if (sizeof (UINTN) == sizeof (UINT32)) {
30 return cpu_to_fdt32 (x);
31 } else {
32 return cpu_to_fdt64 (x);
33 }
34 }
35
36 typedef struct {
37 UINTN Base;
38 UINTN Size;
39 } FdtRegion;
40
41
42 STATIC
43 UINTN
44 IsPrintableString (
45 IN CONST VOID* data,
46 IN UINTN len
47 )
48 {
49 CONST CHAR8 *s = data;
50 CONST CHAR8 *ss;
51
52 // Zero length is not
53 if (len == 0) {
54 return 0;
55 }
56
57 // Must terminate with zero
58 if (s[len - 1] != '\0') {
59 return 0;
60 }
61
62 ss = s;
63 while (*s/* && isprint(*s)*/) {
64 s++;
65 }
66
67 // Not zero, or not done yet
68 if (*s != '\0' || (s + 1 - ss) < len) {
69 return 0;
70 }
71
72 return 1;
73 }
74
75 STATIC
76 VOID
77 PrintData (
78 IN CONST CHAR8* data,
79 IN UINTN len
80 )
81 {
82 UINTN i;
83 CONST CHAR8 *p = data;
84
85 // No data, don't print
86 if (len == 0)
87 return;
88
89 if (IsPrintableString (data, len)) {
90 Print(L" = \"%a\"", (const char *)data);
91 } else if ((len % 4) == 0) {
92 Print(L" = <");
93 for (i = 0; i < len; i += 4) {
94 Print(L"0x%08x%a", fdt32_to_cpu(GET_CELL(p)),i < (len - 4) ? " " : "");
95 }
96 Print(L">");
97 } else {
98 Print(L" = [");
99 for (i = 0; i < len; i++)
100 Print(L"%02x%a", *p++, i < len - 1 ? " " : "");
101 Print(L"]");
102 }
103 }
104
105 VOID
106 DebugDumpFdt (
107 IN VOID* FdtBlob
108 )
109 {
110 struct fdt_header *bph;
111 UINT32 off_dt;
112 UINT32 off_str;
113 CONST CHAR8* p_struct;
114 CONST CHAR8* p_strings;
115 CONST CHAR8* p;
116 CONST CHAR8* s;
117 CONST CHAR8* t;
118 UINT32 tag;
119 UINTN sz;
120 UINTN depth;
121 UINTN shift;
122 UINT32 version;
123
124 {
125 // Can 'memreserve' be printed by below code?
126 INTN num = fdt_num_mem_rsv(FdtBlob);
127 INTN i, err;
128 UINT64 addr = 0,size = 0;
129
130 for (i = 0; i < num; i++) {
131 err = fdt_get_mem_rsv(FdtBlob, i, &addr, &size);
132 if (err) {
133 DEBUG((EFI_D_ERROR, "Error (%d) : Cannot get memreserve section (%d)\n", err, i));
134 }
135 else {
136 Print(L"/memreserve/ \t0x%lx \t0x%lx;\n",addr,size);
137 }
138 }
139 }
140
141 depth = 0;
142 shift = 4;
143
144 bph = FdtBlob;
145 off_dt = fdt32_to_cpu(bph->off_dt_struct);
146 off_str = fdt32_to_cpu(bph->off_dt_strings);
147 p_struct = (CONST CHAR8*)FdtBlob + off_dt;
148 p_strings = (CONST CHAR8*)FdtBlob + off_str;
149 version = fdt32_to_cpu(bph->version);
150
151 p = p_struct;
152 while ((tag = fdt32_to_cpu(GET_CELL(p))) != FDT_END) {
153 if (tag == FDT_BEGIN_NODE) {
154 s = p;
155 p = PALIGN(p + AsciiStrLen (s) + 1, 4);
156
157 if (*s == '\0')
158 s = "/";
159
160 Print(L"%*s%a {\n", depth * shift, L" ", s);
161
162 depth++;
163 continue;
164 }
165
166 if (tag == FDT_END_NODE) {
167 depth--;
168
169 Print(L"%*s};\n", depth * shift, L" ");
170 continue;
171 }
172
173 if (tag == FDT_NOP) {
174 Print(L"%*s// [NOP]\n", depth * shift, L" ");
175 continue;
176 }
177
178 if (tag != FDT_PROP) {
179 Print(L"%*s ** Unknown tag 0x%08x\n", depth * shift, L" ", tag);
180 break;
181 }
182 sz = fdt32_to_cpu(GET_CELL(p));
183 s = p_strings + fdt32_to_cpu(GET_CELL(p));
184 if (version < 16 && sz >= 8)
185 p = PALIGN(p, 8);
186 t = p;
187
188 p = PALIGN(p + sz, 4);
189
190 Print(L"%*s%a", depth * shift, L" ", s);
191 PrintData(t, sz);
192 Print(L";\n");
193 }
194 }
195
196 STATIC
197 BOOLEAN
198 IsLinuxReservedRegion (
199 IN EFI_MEMORY_TYPE MemoryType
200 )
201 {
202 switch(MemoryType) {
203 case EfiRuntimeServicesCode:
204 case EfiRuntimeServicesData:
205 case EfiUnusableMemory:
206 case EfiACPIReclaimMemory:
207 case EfiACPIMemoryNVS:
208 case EfiReservedMemoryType:
209 return TRUE;
210 default:
211 return FALSE;
212 }
213 }
214
215 /**
216 ** Relocate the FDT blob to a more appropriate location for the Linux kernel.
217 ** This function will allocate memory for the relocated FDT blob.
218 **
219 ** @retval EFI_SUCCESS on success.
220 ** @retval EFI_OUT_OF_RESOURCES or EFI_INVALID_PARAMETER on failure.
221 */
222 STATIC
223 EFI_STATUS
224 RelocateFdt (
225 EFI_PHYSICAL_ADDRESS OriginalFdt,
226 UINTN OriginalFdtSize,
227 EFI_PHYSICAL_ADDRESS *RelocatedFdt,
228 UINTN *RelocatedFdtSize,
229 EFI_PHYSICAL_ADDRESS *RelocatedFdtAlloc
230 )
231 {
232 EFI_STATUS Status;
233 INTN Error;
234 UINT64 FdtAlignment;
235
236 *RelocatedFdtSize = OriginalFdtSize + FDT_ADDITIONAL_ENTRIES_SIZE;
237
238 // If FDT load address needs to be aligned, allocate more space.
239 FdtAlignment = PcdGet32 (PcdArmLinuxFdtAlignment);
240 if (FdtAlignment != 0) {
241 *RelocatedFdtSize += FdtAlignment;
242 }
243
244 // Try below a watermark address.
245 Status = EFI_NOT_FOUND;
246 if (PcdGet32 (PcdArmLinuxFdtMaxOffset) != 0) {
247 *RelocatedFdt = LINUX_FDT_MAX_OFFSET;
248 Status = gBS->AllocatePages (AllocateMaxAddress, EfiBootServicesData,
249 EFI_SIZE_TO_PAGES (*RelocatedFdtSize), RelocatedFdt);
250 if (EFI_ERROR (Status)) {
251 DEBUG ((EFI_D_WARN, "Warning: Failed to load FDT below address 0x%lX (%r). Will try again at a random address anywhere.\n", *RelocatedFdt, Status));
252 }
253 }
254
255 // Try anywhere there is available space.
256 if (EFI_ERROR (Status)) {
257 Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData,
258 EFI_SIZE_TO_PAGES (*RelocatedFdtSize), RelocatedFdt);
259 if (EFI_ERROR (Status)) {
260 ASSERT_EFI_ERROR (Status);
261 return EFI_OUT_OF_RESOURCES;
262 } else {
263 DEBUG ((EFI_D_WARN, "WARNING: Loaded FDT at random address 0x%lX.\nWARNING: There is a risk of accidental overwriting by other code/data.\n", *RelocatedFdt));
264 }
265 }
266
267 *RelocatedFdtAlloc = *RelocatedFdt;
268 if (FdtAlignment != 0) {
269 *RelocatedFdt = ALIGN (*RelocatedFdt, FdtAlignment);
270 }
271
272 // Load the Original FDT tree into the new region
273 Error = fdt_open_into ((VOID*)(UINTN) OriginalFdt,
274 (VOID*)(UINTN)(*RelocatedFdt), *RelocatedFdtSize);
275 if (Error) {
276 DEBUG ((EFI_D_ERROR, "fdt_open_into(): %a\n", fdt_strerror (Error)));
277 gBS->FreePages (*RelocatedFdtAlloc, EFI_SIZE_TO_PAGES (*RelocatedFdtSize));
278 return EFI_INVALID_PARAMETER;
279 }
280
281 DEBUG_CODE_BEGIN();
282 //DebugDumpFdt (fdt);
283 DEBUG_CODE_END();
284
285 return EFI_SUCCESS;
286 }
287
288
289 EFI_STATUS
290 PrepareFdt (
291 IN CONST CHAR8* CommandLineArguments,
292 IN EFI_PHYSICAL_ADDRESS InitrdImage,
293 IN UINTN InitrdImageSize,
294 IN OUT EFI_PHYSICAL_ADDRESS *FdtBlobBase,
295 IN OUT UINTN *FdtBlobSize
296 )
297 {
298 EFI_STATUS Status;
299 EFI_PHYSICAL_ADDRESS NewFdtBlobBase;
300 EFI_PHYSICAL_ADDRESS NewFdtBlobAllocation;
301 UINTN NewFdtBlobSize;
302 VOID* fdt;
303 INTN err;
304 INTN node;
305 INTN cpu_node;
306 INT32 lenp;
307 CONST VOID* BootArg;
308 CONST VOID* Method;
309 EFI_PHYSICAL_ADDRESS InitrdImageStart;
310 EFI_PHYSICAL_ADDRESS InitrdImageEnd;
311 FdtRegion Region;
312 UINTN Index;
313 CHAR8 Name[10];
314 LIST_ENTRY ResourceList;
315 BDS_SYSTEM_MEMORY_RESOURCE *Resource;
316 ARM_PROCESSOR_TABLE *ArmProcessorTable;
317 ARM_CORE_INFO *ArmCoreInfoTable;
318 UINT32 MpId;
319 UINT32 ClusterId;
320 UINT32 CoreId;
321 UINT64 CpuReleaseAddr;
322 UINTN MemoryMapSize;
323 EFI_MEMORY_DESCRIPTOR *MemoryMap;
324 EFI_MEMORY_DESCRIPTOR *MemoryMapPtr;
325 UINTN MapKey;
326 UINTN DescriptorSize;
327 UINT32 DescriptorVersion;
328 UINTN Pages;
329 UINTN OriginalFdtSize;
330 BOOLEAN CpusNodeExist;
331 UINTN CoreMpId;
332
333 NewFdtBlobAllocation = 0;
334
335 //
336 // Sanity checks on the original FDT blob.
337 //
338 err = fdt_check_header ((VOID*)(UINTN)(*FdtBlobBase));
339 if (err != 0) {
340 Print (L"ERROR: Device Tree header not valid (err:%d)\n", err);
341 return EFI_INVALID_PARAMETER;
342 }
343
344 // The original FDT blob might have been loaded partially.
345 // Check that it is not the case.
346 OriginalFdtSize = (UINTN)fdt_totalsize ((VOID*)(UINTN)(*FdtBlobBase));
347 if (OriginalFdtSize > *FdtBlobSize) {
348 Print (L"ERROR: Incomplete FDT. Only %d/%d bytes have been loaded.\n",
349 *FdtBlobSize, OriginalFdtSize);
350 return EFI_INVALID_PARAMETER;
351 }
352
353 //
354 // Relocate the FDT to its final location.
355 //
356 Status = RelocateFdt (*FdtBlobBase, OriginalFdtSize,
357 &NewFdtBlobBase, &NewFdtBlobSize, &NewFdtBlobAllocation);
358 if (EFI_ERROR (Status)) {
359 goto FAIL_RELOCATE_FDT;
360 }
361
362 fdt = (VOID*)(UINTN)NewFdtBlobBase;
363
364 node = fdt_subnode_offset (fdt, 0, "chosen");
365 if (node < 0) {
366 // The 'chosen' node does not exist, create it
367 node = fdt_add_subnode(fdt, 0, "chosen");
368 if (node < 0) {
369 DEBUG((EFI_D_ERROR,"Error on finding 'chosen' node\n"));
370 Status = EFI_INVALID_PARAMETER;
371 goto FAIL_COMPLETE_FDT;
372 }
373 }
374
375 DEBUG_CODE_BEGIN();
376 BootArg = fdt_getprop(fdt, node, "bootargs", &lenp);
377 if (BootArg != NULL) {
378 DEBUG((EFI_D_ERROR,"BootArg: %a\n",BootArg));
379 }
380 DEBUG_CODE_END();
381
382 //
383 // Set Linux CmdLine
384 //
385 if ((CommandLineArguments != NULL) && (AsciiStrLen (CommandLineArguments) > 0)) {
386 err = fdt_setprop(fdt, node, "bootargs", CommandLineArguments, AsciiStrSize(CommandLineArguments));
387 if (err) {
388 DEBUG((EFI_D_ERROR,"Fail to set new 'bootarg' (err:%d)\n",err));
389 }
390 }
391
392 //
393 // Set Linux Initrd
394 //
395 if (InitrdImageSize != 0) {
396 InitrdImageStart = cpu_to_fdt64 (InitrdImage);
397 err = fdt_setprop(fdt, node, "linux,initrd-start", &InitrdImageStart, sizeof(EFI_PHYSICAL_ADDRESS));
398 if (err) {
399 DEBUG((EFI_D_ERROR,"Fail to set new 'linux,initrd-start' (err:%d)\n",err));
400 }
401 InitrdImageEnd = cpu_to_fdt64 (InitrdImage + InitrdImageSize);
402 err = fdt_setprop(fdt, node, "linux,initrd-end", &InitrdImageEnd, sizeof(EFI_PHYSICAL_ADDRESS));
403 if (err) {
404 DEBUG((EFI_D_ERROR,"Fail to set new 'linux,initrd-start' (err:%d)\n",err));
405 }
406 }
407
408 //
409 // Set Physical memory setup if does not exist
410 //
411 node = fdt_subnode_offset(fdt, 0, "memory");
412 if (node < 0) {
413 // The 'memory' node does not exist, create it
414 node = fdt_add_subnode(fdt, 0, "memory");
415 if (node >= 0) {
416 fdt_setprop_string(fdt, node, "name", "memory");
417 fdt_setprop_string(fdt, node, "device_type", "memory");
418
419 GetSystemMemoryResources (&ResourceList);
420 Resource = (BDS_SYSTEM_MEMORY_RESOURCE*)ResourceList.ForwardLink;
421
422 Region.Base = cpu_to_fdtn ((UINTN)Resource->PhysicalStart);
423 Region.Size = cpu_to_fdtn ((UINTN)Resource->ResourceLength);
424
425 err = fdt_setprop(fdt, node, "reg", &Region, sizeof(Region));
426 if (err) {
427 DEBUG((EFI_D_ERROR,"Fail to set new 'memory region' (err:%d)\n",err));
428 }
429 }
430 }
431
432 //
433 // Add the memory regions reserved by the UEFI Firmware
434 //
435
436 // Retrieve the UEFI Memory Map
437 MemoryMap = NULL;
438 MemoryMapSize = 0;
439 Status = gBS->GetMemoryMap (&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion);
440 if (Status == EFI_BUFFER_TOO_SMALL) {
441 // The UEFI specification advises to allocate more memory for the MemoryMap buffer between successive
442 // calls to GetMemoryMap(), since allocation of the new buffer may potentially increase memory map size.
443 Pages = EFI_SIZE_TO_PAGES (MemoryMapSize) + 1;
444 MemoryMap = AllocatePages (Pages);
445 if (MemoryMap == NULL) {
446 Status = EFI_OUT_OF_RESOURCES;
447 goto FAIL_COMPLETE_FDT;
448 }
449 Status = gBS->GetMemoryMap (&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion);
450 }
451
452 // Go through the list and add the reserved region to the Device Tree
453 if (!EFI_ERROR(Status)) {
454 MemoryMapPtr = MemoryMap;
455 for (Index = 0; Index < (MemoryMapSize / DescriptorSize); Index++) {
456 if (IsLinuxReservedRegion ((EFI_MEMORY_TYPE)MemoryMapPtr->Type)) {
457 DEBUG((DEBUG_VERBOSE, "Reserved region of type %d [0x%lX, 0x%lX]\n",
458 MemoryMapPtr->Type,
459 (UINTN)MemoryMapPtr->PhysicalStart,
460 (UINTN)(MemoryMapPtr->PhysicalStart + MemoryMapPtr->NumberOfPages * EFI_PAGE_SIZE)));
461 err = fdt_add_mem_rsv(fdt, MemoryMapPtr->PhysicalStart, MemoryMapPtr->NumberOfPages * EFI_PAGE_SIZE);
462 if (err != 0) {
463 Print(L"Warning: Fail to add 'memreserve' (err:%d)\n", err);
464 }
465 }
466 MemoryMapPtr = (EFI_MEMORY_DESCRIPTOR*)((UINTN)MemoryMapPtr + DescriptorSize);
467 }
468 }
469
470 //
471 // Setup Arm Mpcore Info if it is a multi-core or multi-cluster platforms.
472 //
473 // For 'cpus' and 'cpu' device tree nodes bindings, refer to this file
474 // in the kernel documentation:
475 // Documentation/devicetree/bindings/arm/cpus.txt
476 //
477 for (Index=0; Index < gST->NumberOfTableEntries; Index++) {
478 // Check for correct GUID type
479 if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
480 MpId = ArmReadMpidr ();
481 ClusterId = GET_CLUSTER_ID(MpId);
482 CoreId = GET_CORE_ID(MpId);
483
484 node = fdt_subnode_offset(fdt, 0, "cpus");
485 if (node < 0) {
486 // Create the /cpus node
487 node = fdt_add_subnode(fdt, 0, "cpus");
488 fdt_setprop_string(fdt, node, "name", "cpus");
489 fdt_setprop_cell (fdt, node, "#address-cells", sizeof (UINTN) / 4);
490 fdt_setprop_cell(fdt, node, "#size-cells", 0);
491 CpusNodeExist = FALSE;
492 } else {
493 CpusNodeExist = TRUE;
494 }
495
496 // Get pointer to ARM processor table
497 ArmProcessorTable = (ARM_PROCESSOR_TABLE *)gST->ConfigurationTable[Index].VendorTable;
498 ArmCoreInfoTable = ArmProcessorTable->ArmCpus;
499
500 for (Index = 0; Index < ArmProcessorTable->NumberOfEntries; Index++) {
501 CoreMpId = (UINTN) GET_MPID (ArmCoreInfoTable[Index].ClusterId,
502 ArmCoreInfoTable[Index].CoreId);
503 AsciiSPrint (Name, 10, "cpu@%x", CoreMpId);
504
505 // If the 'cpus' node did not exist then create all the 'cpu' nodes.
506 // In case 'cpus' node is provided in the original FDT then we do not add
507 // any 'cpu' node.
508 if (!CpusNodeExist) {
509 cpu_node = fdt_add_subnode (fdt, node, Name);
510 if (cpu_node < 0) {
511 DEBUG ((EFI_D_ERROR, "Error on creating '%s' node\n", Name));
512 Status = EFI_INVALID_PARAMETER;
513 goto FAIL_COMPLETE_FDT;
514 }
515
516 fdt_setprop_string (fdt, cpu_node, "device_type", "cpu");
517
518 CoreMpId = cpu_to_fdtn (CoreMpId);
519 fdt_setprop (fdt, cpu_node, "reg", &CoreMpId, sizeof (CoreMpId));
520 } else {
521 cpu_node = fdt_subnode_offset(fdt, node, Name);
522 }
523
524 if (cpu_node >= 0) {
525 Method = fdt_getprop (fdt, cpu_node, "enable-method", &lenp);
526 // We only care when 'enable-method' == 'spin-table'. If the enable-method is not defined
527 // or defined as 'psci' then we ignore its properties.
528 if ((Method != NULL) && (AsciiStrCmp ((CHAR8 *)Method, "spin-table") == 0)) {
529 // There are two cases;
530 // - UEFI firmware parked the secondary cores and/or UEFI firmware is aware of the CPU
531 // release addresses (PcdArmLinuxSpinTable == TRUE)
532 // - the parking of the secondary cores has been managed before starting UEFI and/or UEFI
533 // does not anything about the CPU release addresses - in this case we do nothing
534 if (FeaturePcdGet (PcdArmLinuxSpinTable)) {
535 CpuReleaseAddr = cpu_to_fdt64 (ArmCoreInfoTable[Index].MailboxSetAddress);
536 fdt_setprop (fdt, cpu_node, "cpu-release-addr", &CpuReleaseAddr, sizeof(CpuReleaseAddr));
537
538 // If it is not the primary core than the cpu should be disabled
539 if (((ArmCoreInfoTable[Index].ClusterId != ClusterId) || (ArmCoreInfoTable[Index].CoreId != CoreId))) {
540 fdt_setprop_string(fdt, cpu_node, "status", "disabled");
541 }
542 }
543 }
544 }
545 }
546 break;
547 }
548 }
549
550 DEBUG_CODE_BEGIN();
551 //DebugDumpFdt (fdt);
552 DEBUG_CODE_END();
553
554 // If we succeeded to generate the new Device Tree then free the old Device Tree
555 gBS->FreePages (*FdtBlobBase, EFI_SIZE_TO_PAGES (*FdtBlobSize));
556
557 // Update the real size of the Device Tree
558 fdt_pack ((VOID*)(UINTN)(NewFdtBlobBase));
559
560 *FdtBlobBase = NewFdtBlobBase;
561 *FdtBlobSize = (UINTN)fdt_totalsize ((VOID*)(UINTN)(NewFdtBlobBase));
562 return EFI_SUCCESS;
563
564 FAIL_COMPLETE_FDT:
565 gBS->FreePages (NewFdtBlobAllocation, EFI_SIZE_TO_PAGES (NewFdtBlobSize));
566
567 FAIL_RELOCATE_FDT:
568 *FdtBlobSize = (UINTN)fdt_totalsize ((VOID*)(UINTN)(*FdtBlobBase));
569 // Return success even if we failed to update the FDT blob.
570 // The original one is still valid.
571 return EFI_SUCCESS;
572 }