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 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 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 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 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