1 /** @file
2 *
3 *  Copyright (c) 2011-2015, 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 <PiDxe.h>
16 #include <Library/ArmLib.h>
17 #include <Library/HobLib.h>
18 
19 #include <Guid/ArmMpCoreInfo.h>
20 
21 #include "LinuxLoader.h"
22 
23 #define ALIGN(x, a)     (((x) + ((a) - 1)) & ~((a) - 1))
24 #define PALIGN(p, a)    ((void *)(ALIGN ((unsigned long)(p), (a))))
25 #define GET_CELL(p)     (p += 4, *((const UINT32 *)(p-4)))
26 
27 STATIC
28 UINTN
cpu_to_fdtn(UINTN x)29 cpu_to_fdtn (UINTN x) {
30   if (sizeof (UINTN) == sizeof (UINT32)) {
31     return cpu_to_fdt32 (x);
32   } else {
33     return cpu_to_fdt64 (x);
34   }
35 }
36 
37 typedef struct {
38   UINTN   Base;
39   UINTN   Size;
40 } FDT_REGION;
41 
42 STATIC
43 BOOLEAN
IsLinuxReservedRegion(IN EFI_MEMORY_TYPE MemoryType)44 IsLinuxReservedRegion (
45   IN EFI_MEMORY_TYPE MemoryType
46   )
47 {
48   switch (MemoryType) {
49   case EfiRuntimeServicesCode:
50   case EfiRuntimeServicesData:
51   case EfiUnusableMemory:
52   case EfiACPIReclaimMemory:
53   case EfiACPIMemoryNVS:
54   case EfiReservedMemoryType:
55     return TRUE;
56   default:
57     return FALSE;
58   }
59 }
60 
61 /**
62 ** Relocate the FDT blob to a more appropriate location for the Linux kernel.
63 ** This function will allocate memory for the relocated FDT blob.
64 **
65 ** @retval EFI_SUCCESS on success.
66 ** @retval EFI_OUT_OF_RESOURCES or EFI_INVALID_PARAMETER on failure.
67 */
68 STATIC
69 EFI_STATUS
RelocateFdt(EFI_PHYSICAL_ADDRESS SystemMemoryBase,EFI_PHYSICAL_ADDRESS OriginalFdt,UINTN OriginalFdtSize,EFI_PHYSICAL_ADDRESS * RelocatedFdt,UINTN * RelocatedFdtSize,EFI_PHYSICAL_ADDRESS * RelocatedFdtAlloc)70 RelocateFdt (
71   EFI_PHYSICAL_ADDRESS   SystemMemoryBase,
72   EFI_PHYSICAL_ADDRESS   OriginalFdt,
73   UINTN                  OriginalFdtSize,
74   EFI_PHYSICAL_ADDRESS   *RelocatedFdt,
75   UINTN                  *RelocatedFdtSize,
76   EFI_PHYSICAL_ADDRESS   *RelocatedFdtAlloc
77   )
78 {
79   EFI_STATUS            Status;
80   INTN                  Error;
81   UINT64                FdtAlignment;
82 
83   *RelocatedFdtSize = OriginalFdtSize + FDT_ADDITIONAL_ENTRIES_SIZE;
84 
85   // If FDT load address needs to be aligned, allocate more space.
86   FdtAlignment = PcdGet32 (PcdArmLinuxFdtAlignment);
87   if (FdtAlignment != 0) {
88     *RelocatedFdtSize += FdtAlignment;
89   }
90 
91   // Try below a watermark address.
92   Status = EFI_NOT_FOUND;
93   if (PcdGet32 (PcdArmLinuxFdtMaxOffset) != 0) {
94     *RelocatedFdt = LINUX_FDT_MAX_OFFSET;
95     Status = gBS->AllocatePages (AllocateMaxAddress, EfiBootServicesData,
96                     EFI_SIZE_TO_PAGES (*RelocatedFdtSize), RelocatedFdt);
97     if (EFI_ERROR (Status)) {
98       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));
99     }
100   }
101 
102   // Try anywhere there is available space.
103   if (EFI_ERROR (Status)) {
104     Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData,
105                     EFI_SIZE_TO_PAGES (*RelocatedFdtSize), RelocatedFdt);
106     if (EFI_ERROR (Status)) {
107       ASSERT_EFI_ERROR (Status);
108       return EFI_OUT_OF_RESOURCES;
109     } else {
110       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));
111     }
112   }
113 
114   *RelocatedFdtAlloc = *RelocatedFdt;
115   if (FdtAlignment != 0) {
116     *RelocatedFdt = ALIGN (*RelocatedFdt, FdtAlignment);
117   }
118 
119   // Load the Original FDT tree into the new region
120   Error = fdt_open_into ((VOID*)(UINTN) OriginalFdt,
121             (VOID*)(UINTN)(*RelocatedFdt), *RelocatedFdtSize);
122   if (Error) {
123     DEBUG ((EFI_D_ERROR, "fdt_open_into(): %a\n", fdt_strerror (Error)));
124     gBS->FreePages (*RelocatedFdtAlloc, EFI_SIZE_TO_PAGES (*RelocatedFdtSize));
125     return EFI_INVALID_PARAMETER;
126   }
127 
128   return EFI_SUCCESS;
129 }
130 
131 EFI_STATUS
PrepareFdt(IN EFI_PHYSICAL_ADDRESS SystemMemoryBase,IN CONST CHAR8 * CommandLineArguments,IN EFI_PHYSICAL_ADDRESS InitrdImage,IN UINTN InitrdImageSize,IN OUT EFI_PHYSICAL_ADDRESS * FdtBlobBase,IN OUT UINTN * FdtBlobSize)132 PrepareFdt (
133   IN     EFI_PHYSICAL_ADDRESS SystemMemoryBase,
134   IN     CONST CHAR8*         CommandLineArguments,
135   IN     EFI_PHYSICAL_ADDRESS InitrdImage,
136   IN     UINTN                InitrdImageSize,
137   IN OUT EFI_PHYSICAL_ADDRESS *FdtBlobBase,
138   IN OUT UINTN                *FdtBlobSize
139   )
140 {
141   EFI_STATUS            Status;
142   EFI_PHYSICAL_ADDRESS  NewFdtBlobBase;
143   EFI_PHYSICAL_ADDRESS  NewFdtBlobAllocation;
144   UINTN                 NewFdtBlobSize;
145   VOID*                 fdt;
146   INTN                  err;
147   INTN                  node;
148   INTN                  cpu_node;
149   INT32                 lenp;
150   CONST VOID*           BootArg;
151   CONST VOID*           Method;
152   EFI_PHYSICAL_ADDRESS  InitrdImageStart;
153   EFI_PHYSICAL_ADDRESS  InitrdImageEnd;
154   FDT_REGION            Region;
155   UINTN                 Index;
156   CHAR8                 Name[10];
157   LIST_ENTRY            ResourceList;
158   SYSTEM_MEMORY_RESOURCE  *Resource;
159   ARM_PROCESSOR_TABLE   *ArmProcessorTable;
160   ARM_CORE_INFO         *ArmCoreInfoTable;
161   UINT32                MpId;
162   UINT32                ClusterId;
163   UINT32                CoreId;
164   UINT64                CpuReleaseAddr;
165   UINTN                 MemoryMapSize;
166   EFI_MEMORY_DESCRIPTOR *MemoryMap;
167   EFI_MEMORY_DESCRIPTOR *MemoryMapPtr;
168   UINTN                 MapKey;
169   UINTN                 DescriptorSize;
170   UINT32                DescriptorVersion;
171   UINTN                 Pages;
172   UINTN                 OriginalFdtSize;
173   BOOLEAN               CpusNodeExist;
174   UINTN                 CoreMpId;
175 
176   NewFdtBlobAllocation = 0;
177 
178   //
179   // Sanity checks on the original FDT blob.
180   //
181   err = fdt_check_header ((VOID*)(UINTN)(*FdtBlobBase));
182   if (err != 0) {
183     Print (L"ERROR: Device Tree header not valid (err:%d)\n", err);
184     return EFI_INVALID_PARAMETER;
185   }
186 
187   // The original FDT blob might have been loaded partially.
188   // Check that it is not the case.
189   OriginalFdtSize = (UINTN)fdt_totalsize ((VOID*)(UINTN)(*FdtBlobBase));
190   if (OriginalFdtSize > *FdtBlobSize) {
191     Print (L"ERROR: Incomplete FDT. Only %d/%d bytes have been loaded.\n",
192            *FdtBlobSize, OriginalFdtSize);
193     return EFI_INVALID_PARAMETER;
194   }
195 
196   //
197   // Relocate the FDT to its final location.
198   //
199   Status = RelocateFdt (SystemMemoryBase, *FdtBlobBase, OriginalFdtSize,
200              &NewFdtBlobBase, &NewFdtBlobSize, &NewFdtBlobAllocation);
201   if (EFI_ERROR (Status)) {
202     goto FAIL_RELOCATE_FDT;
203   }
204 
205   fdt = (VOID*)(UINTN)NewFdtBlobBase;
206 
207   node = fdt_subnode_offset (fdt, 0, "chosen");
208   if (node < 0) {
209     // The 'chosen' node does not exist, create it
210     node = fdt_add_subnode (fdt, 0, "chosen");
211     if (node < 0) {
212       DEBUG ((EFI_D_ERROR, "Error on finding 'chosen' node\n"));
213       Status = EFI_INVALID_PARAMETER;
214       goto FAIL_COMPLETE_FDT;
215     }
216   }
217 
218   DEBUG_CODE_BEGIN ();
219     BootArg = fdt_getprop (fdt, node, "bootargs", &lenp);
220     if (BootArg != NULL) {
221       DEBUG ((EFI_D_ERROR, "BootArg: %a\n", BootArg));
222     }
223   DEBUG_CODE_END ();
224 
225   //
226   // Set Linux CmdLine
227   //
228   if ((CommandLineArguments != NULL) && (AsciiStrLen (CommandLineArguments) > 0)) {
229     err = fdt_setprop (fdt, node, "bootargs", CommandLineArguments, AsciiStrSize (CommandLineArguments));
230     if (err) {
231       DEBUG ((EFI_D_ERROR, "Fail to set new 'bootarg' (err:%d)\n", err));
232     }
233   }
234 
235   //
236   // Set Linux Initrd
237   //
238   if (InitrdImageSize != 0) {
239     InitrdImageStart = cpu_to_fdt64 (InitrdImage);
240     err = fdt_setprop (fdt, node, "linux,initrd-start", &InitrdImageStart, sizeof (EFI_PHYSICAL_ADDRESS));
241     if (err) {
242       DEBUG ((EFI_D_ERROR, "Fail to set new 'linux,initrd-start' (err:%d)\n", err));
243     }
244     InitrdImageEnd = cpu_to_fdt64 (InitrdImage + InitrdImageSize);
245     err = fdt_setprop (fdt, node, "linux,initrd-end", &InitrdImageEnd, sizeof (EFI_PHYSICAL_ADDRESS));
246     if (err) {
247       DEBUG ((EFI_D_ERROR, "Fail to set new 'linux,initrd-start' (err:%d)\n", err));
248     }
249   }
250 
251   //
252   // Set Physical memory setup if does not exist
253   //
254   node = fdt_subnode_offset (fdt, 0, "memory");
255   if (node < 0) {
256     // The 'memory' node does not exist, create it
257     node = fdt_add_subnode (fdt, 0, "memory");
258     if (node >= 0) {
259       fdt_setprop_string (fdt, node, "name", "memory");
260       fdt_setprop_string (fdt, node, "device_type", "memory");
261 
262       GetSystemMemoryResources (&ResourceList);
263       Resource = (SYSTEM_MEMORY_RESOURCE*)ResourceList.ForwardLink;
264 
265       Region.Base = cpu_to_fdtn ((UINTN)Resource->PhysicalStart);
266       Region.Size = cpu_to_fdtn ((UINTN)Resource->ResourceLength);
267 
268       err = fdt_setprop (fdt, node, "reg", &Region, sizeof (Region));
269       if (err) {
270         DEBUG ((EFI_D_ERROR, "Fail to set new 'memory region' (err:%d)\n", err));
271       }
272     }
273   }
274 
275   //
276   // Add the memory regions reserved by the UEFI Firmware
277   //
278 
279   // Retrieve the UEFI Memory Map
280   MemoryMap = NULL;
281   MemoryMapSize = 0;
282   Status = gBS->GetMemoryMap (&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion);
283   if (Status == EFI_BUFFER_TOO_SMALL) {
284     // The UEFI specification advises to allocate more memory for the MemoryMap buffer between successive
285     // calls to GetMemoryMap(), since allocation of the new buffer may potentially increase memory map size.
286     Pages = EFI_SIZE_TO_PAGES (MemoryMapSize) + 1;
287     MemoryMap = AllocatePages (Pages);
288     if (MemoryMap == NULL) {
289       Status = EFI_OUT_OF_RESOURCES;
290       goto FAIL_COMPLETE_FDT;
291     }
292     Status = gBS->GetMemoryMap (&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion);
293   }
294 
295   // Go through the list and add the reserved region to the Device Tree
296   if (!EFI_ERROR (Status)) {
297     MemoryMapPtr = MemoryMap;
298     for (Index = 0; Index < (MemoryMapSize / DescriptorSize); Index++) {
299       if (IsLinuxReservedRegion ((EFI_MEMORY_TYPE)MemoryMapPtr->Type)) {
300         DEBUG ((DEBUG_VERBOSE, "Reserved region of type %d [0x%lX, 0x%lX]\n",
301             MemoryMapPtr->Type,
302             (UINTN)MemoryMapPtr->PhysicalStart,
303             (UINTN)(MemoryMapPtr->PhysicalStart + MemoryMapPtr->NumberOfPages * EFI_PAGE_SIZE)));
304         err = fdt_add_mem_rsv (fdt, MemoryMapPtr->PhysicalStart, MemoryMapPtr->NumberOfPages * EFI_PAGE_SIZE);
305         if (err != 0) {
306           Print (L"Warning: Fail to add 'memreserve' (err:%d)\n", err);
307         }
308       }
309       MemoryMapPtr = (EFI_MEMORY_DESCRIPTOR*)((UINTN)MemoryMapPtr + DescriptorSize);
310     }
311   }
312 
313   //
314   // Setup Arm Mpcore Info if it is a multi-core or multi-cluster platforms.
315   //
316   // For 'cpus' and 'cpu' device tree nodes bindings, refer to this file
317   // in the kernel documentation:
318   // Documentation/devicetree/bindings/arm/cpus.txt
319   //
320   for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
321     // Check for correct GUID type
322     if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
323       MpId = ArmReadMpidr ();
324       ClusterId = GET_CLUSTER_ID (MpId);
325       CoreId    = GET_CORE_ID (MpId);
326 
327       node = fdt_subnode_offset (fdt, 0, "cpus");
328       if (node < 0) {
329         // Create the /cpus node
330         node = fdt_add_subnode (fdt, 0, "cpus");
331         fdt_setprop_string (fdt, node, "name", "cpus");
332         fdt_setprop_cell (fdt, node, "#address-cells", sizeof (UINTN) / 4);
333         fdt_setprop_cell (fdt, node, "#size-cells", 0);
334         CpusNodeExist = FALSE;
335       } else {
336         CpusNodeExist = TRUE;
337       }
338 
339       // Get pointer to ARM processor table
340       ArmProcessorTable = (ARM_PROCESSOR_TABLE *)gST->ConfigurationTable[Index].VendorTable;
341       ArmCoreInfoTable = ArmProcessorTable->ArmCpus;
342 
343       for (Index = 0; Index < ArmProcessorTable->NumberOfEntries; Index++) {
344         CoreMpId = (UINTN) GET_MPID (ArmCoreInfoTable[Index].ClusterId,
345                              ArmCoreInfoTable[Index].CoreId);
346         AsciiSPrint (Name, 10, "cpu@%x", CoreMpId);
347 
348         // If the 'cpus' node did not exist then create all the 'cpu' nodes.
349         // In case 'cpus' node is provided in the original FDT then we do not add
350         // any 'cpu' node.
351         if (!CpusNodeExist) {
352           cpu_node = fdt_add_subnode (fdt, node, Name);
353           if (cpu_node < 0) {
354             DEBUG ((EFI_D_ERROR, "Error on creating '%s' node\n", Name));
355             Status = EFI_INVALID_PARAMETER;
356             goto FAIL_COMPLETE_FDT;
357           }
358 
359           fdt_setprop_string (fdt, cpu_node, "device_type", "cpu");
360 
361           CoreMpId = cpu_to_fdtn (CoreMpId);
362           fdt_setprop (fdt, cpu_node, "reg", &CoreMpId, sizeof (CoreMpId));
363         } else {
364           cpu_node = fdt_subnode_offset (fdt, node, Name);
365         }
366 
367         if (cpu_node >= 0) {
368           Method = fdt_getprop (fdt, cpu_node, "enable-method", &lenp);
369           // We only care when 'enable-method' == 'spin-table'. If the enable-method is not defined
370           // or defined as 'psci' then we ignore its properties.
371           if ((Method != NULL) && (AsciiStrCmp ((CHAR8 *)Method, "spin-table") == 0)) {
372             // There are two cases;
373             //  - UEFI firmware parked the secondary cores and/or UEFI firmware is aware of the CPU
374             //    release addresses (PcdArmLinuxSpinTable == TRUE)
375             //  - the parking of the secondary cores has been managed before starting UEFI and/or UEFI
376             //    does not anything about the CPU release addresses - in this case we do nothing
377             if (FeaturePcdGet (PcdArmLinuxSpinTable)) {
378               CpuReleaseAddr = cpu_to_fdt64 (ArmCoreInfoTable[Index].MailboxSetAddress);
379               fdt_setprop (fdt, cpu_node, "cpu-release-addr", &CpuReleaseAddr, sizeof (CpuReleaseAddr));
380 
381               // If it is not the primary core than the cpu should be disabled
382               if (((ArmCoreInfoTable[Index].ClusterId != ClusterId) || (ArmCoreInfoTable[Index].CoreId != CoreId))) {
383                 fdt_setprop_string (fdt, cpu_node, "status", "disabled");
384               }
385             }
386           }
387         }
388       }
389       break;
390     }
391   }
392 
393   // If we succeeded to generate the new Device Tree then free the old Device Tree
394   gBS->FreePages (*FdtBlobBase, EFI_SIZE_TO_PAGES (*FdtBlobSize));
395 
396   // Update the real size of the Device Tree
397   fdt_pack ((VOID*)(UINTN)(NewFdtBlobBase));
398 
399   *FdtBlobBase = NewFdtBlobBase;
400   *FdtBlobSize = (UINTN)fdt_totalsize ((VOID*)(UINTN)(NewFdtBlobBase));
401   return EFI_SUCCESS;
402 
403 FAIL_COMPLETE_FDT:
404   gBS->FreePages (NewFdtBlobAllocation, EFI_SIZE_TO_PAGES (NewFdtBlobSize));
405 
406 FAIL_RELOCATE_FDT:
407   *FdtBlobSize = (UINTN)fdt_totalsize ((VOID*)(UINTN)(*FdtBlobBase));
408   // Return success even if we failed to update the FDT blob.
409   // The original one is still valid.
410   return EFI_SUCCESS;
411 }
412