Home | History | Annotate | Download | only in LinuxLoader
      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