Home | History | Annotate | Download | only in AArch64
      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 <Library/ArmLib.h>
     16 #include <Library/ArmGicLib.h>
     17 #include <Library/IoLib.h>
     18 #include <Library/PcdLib.h>
     19 
     20 #include <Ppi/ArmMpCoreInfo.h>
     21 
     22 #include <Guid/Fdt.h>
     23 
     24 #include "LinuxLoader.h"
     25 
     26 /*
     27   Linux kernel booting: Look at the doc in the Kernel source :
     28     Documentation/arm64/booting.txt
     29   The kernel image must be placed at the start of the memory to be used by the
     30   kernel (2MB aligned) + 0x80000.
     31 
     32   The Device tree blob is expected to be under 2MB and be within the first 512MB
     33   of kernel memory and be 2MB aligned.
     34 
     35   A Flattened Device Tree (FDT) used to boot linux needs to be updated before
     36   the kernel is started. It needs to indicate how secondary cores are brought up
     37   and where they are waiting before loading Linux. The FDT also needs to hold
     38   the correct kernel command line and filesystem RAM-disk information.
     39   At the moment we do not fully support generating this FDT information at
     40   runtime. A prepared FDT should be provided at boot. FDT is the only supported
     41   method for booting the AArch64 Linux kernel.
     42 
     43   Linux does not use any runtime services at this time, so we can let it
     44   overwrite UEFI.
     45 */
     46 
     47 
     48 #define LINUX_ALIGN_VAL       (0x080000) // 2MB + 0x80000 mask
     49 #define LINUX_ALIGN_MASK      (0x1FFFFF) // Bottom 21bits
     50 #define ALIGN_2MB(addr)       ALIGN_POINTER(addr , (2*1024*1024))
     51 
     52 /* ARM32 and AArch64 kernel handover differ.
     53  * x0 is set to FDT base.
     54  * x1-x3 are reserved for future use and should be set to zero.
     55  */
     56 typedef VOID (*LINUX_KERNEL64)(UINTN ParametersBase, UINTN Reserved0,
     57                                UINTN Reserved1, UINTN Reserved2);
     58 
     59 /* These externs are used to relocate some ASM code into Linux memory. */
     60 extern VOID  *SecondariesPenStart;
     61 extern VOID  *SecondariesPenEnd;
     62 extern UINTN *AsmMailboxbase;
     63 
     64 
     65 STATIC
     66 VOID
     67 PreparePlatformHardware (
     68   VOID
     69   )
     70 {
     71   //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
     72 
     73   // Clean before Disable else the Stack gets corrupted with old data.
     74   ArmCleanDataCache ();
     75   ArmDisableDataCache ();
     76   // Invalidate all the entries that might have snuck in.
     77   ArmInvalidateDataCache ();
     78 
     79   // Disable and invalidate the instruction cache
     80   ArmDisableInstructionCache ();
     81   ArmInvalidateInstructionCache ();
     82 
     83   // Turn off MMU
     84   ArmDisableMmu ();
     85 }
     86 
     87 STATIC
     88 EFI_STATUS
     89 StartLinux (
     90   IN  EFI_PHYSICAL_ADDRESS  LinuxImage,
     91   IN  UINTN                 LinuxImageSize,
     92   IN  EFI_PHYSICAL_ADDRESS  FdtBlobBase,
     93   IN  UINTN                 FdtBlobSize
     94   )
     95 {
     96   EFI_STATUS            Status;
     97   LINUX_KERNEL64        LinuxKernel = (LINUX_KERNEL64)LinuxImage;
     98 
     99   // Send msg to secondary cores to go to the kernel pen.
    100   ArmGicSendSgiTo (PcdGet32 (PcdGicDistributorBase), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE, 0x0E, PcdGet32 (PcdGicSgiIntId));
    101 
    102   // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
    103   // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
    104   Status = ShutdownUefiBootServices ();
    105   if (EFI_ERROR (Status)) {
    106     DEBUG ((EFI_D_ERROR, "ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status));
    107     return Status;
    108   }
    109 
    110   // Check if the Linux Image is a uImage
    111   if (*(UINTN*)LinuxKernel == LINUX_UIMAGE_SIGNATURE) {
    112     // Assume the Image Entry Point is just after the uImage header (64-byte size)
    113     LinuxKernel = (LINUX_KERNEL64)((UINTN)LinuxKernel + 64);
    114     LinuxImageSize -= 64;
    115   }
    116 
    117   //
    118   // Switch off interrupts, caches, mmu, etc
    119   //
    120   PreparePlatformHardware ();
    121 
    122   // Register and print out performance information
    123   PERF_END (NULL, "BDS", NULL, 0);
    124   if (PerformanceMeasurementEnabled ()) {
    125     PrintPerformance ();
    126   }
    127 
    128   //
    129   // Start the Linux Kernel
    130   //
    131 
    132   // x1-x3 are reserved (set to zero) for future use.
    133   LinuxKernel ((UINTN)FdtBlobBase, 0, 0, 0);
    134 
    135   // Kernel should never exit
    136   // After Life services are not provided
    137   ASSERT (FALSE);
    138 
    139   // We cannot recover the execution at this stage
    140   while (1);
    141 }
    142 
    143 /**
    144   Start a Linux kernel from a Device Path
    145 
    146   @param  SystemMemoryBase      Base of the system memory
    147   @param  LinuxKernel           Device Path to the Linux Kernel
    148   @param  Parameters            Linux kernel arguments
    149   @param  Fdt                   Device Path to the Flat Device Tree
    150   @param  MachineType           ARM machine type value
    151 
    152   @retval EFI_SUCCESS           All drivers have been connected
    153   @retval EFI_NOT_FOUND         The Linux kernel Device Path has not been found
    154   @retval EFI_OUT_OF_RESOURCES  There is not enough resource memory to store the matching results.
    155   @retval RETURN_UNSUPPORTED    ATAG is not support by this architecture
    156 
    157 **/
    158 EFI_STATUS
    159 BootLinuxAtag (
    160   IN  EFI_PHYSICAL_ADDRESS      SystemMemoryBase,
    161   IN  EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
    162   IN  EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
    163   IN  CONST CHAR8*              CommandLineArguments,
    164   IN  UINTN                     MachineType
    165   )
    166 {
    167   // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
    168   ASSERT (0);
    169 
    170   return EFI_UNSUPPORTED;
    171 }
    172 
    173 /**
    174   Start a Linux kernel from a Device Path
    175 
    176   @param[in]  LinuxKernelDevicePath  Device Path to the Linux Kernel
    177   @param[in]  InitrdDevicePath       Device Path to the Initrd
    178   @param[in]  Arguments              Linux kernel arguments
    179 
    180   @retval EFI_SUCCESS           All drivers have been connected
    181   @retval EFI_NOT_FOUND         The Linux kernel Device Path has not been found
    182   @retval EFI_OUT_OF_RESOURCES  There is not enough resource memory to store the matching results.
    183 
    184 **/
    185 EFI_STATUS
    186 BootLinuxFdt (
    187   IN  EFI_PHYSICAL_ADDRESS      SystemMemoryBase,
    188   IN  EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
    189   IN  EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
    190   IN  EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath,
    191   IN  CONST CHAR8*              Arguments
    192   )
    193 {
    194   EFI_STATUS               Status;
    195   EFI_STATUS               PenBaseStatus;
    196   UINTN                    LinuxImageSize;
    197   UINTN                    InitrdImageSize;
    198   UINTN                    InitrdImageBaseSize;
    199   VOID                     *InstalledFdtBase;
    200   UINTN                    FdtBlobSize;
    201   EFI_PHYSICAL_ADDRESS     FdtBlobBase;
    202   EFI_PHYSICAL_ADDRESS     LinuxImage;
    203   EFI_PHYSICAL_ADDRESS     InitrdImage;
    204   EFI_PHYSICAL_ADDRESS     InitrdImageBase;
    205   ARM_PROCESSOR_TABLE      *ArmProcessorTable;
    206   ARM_CORE_INFO            *ArmCoreInfoTable;
    207   UINTN                    Index;
    208   EFI_PHYSICAL_ADDRESS     PenBase;
    209   UINTN                    PenSize;
    210   UINTN                    MailBoxBase;
    211 
    212   PenBaseStatus = EFI_UNSUPPORTED;
    213   PenSize = 0;
    214   InitrdImage = 0;
    215   InitrdImageSize = 0;
    216   InitrdImageBase = 0;
    217   InitrdImageBaseSize = 0;
    218 
    219   PERF_START (NULL, "BDS", NULL, 0);
    220 
    221   //
    222   // Load the Linux kernel from a device path
    223   //
    224 
    225   // Try to put the kernel at the start of RAM so as to give it access to all memory.
    226   // If that fails fall back to try loading it within LINUX_KERNEL_MAX_OFFSET of memory start.
    227   LinuxImage = SystemMemoryBase + 0x80000;
    228   Status = BdsLoadImage (LinuxKernelDevicePath, AllocateAddress, &LinuxImage, &LinuxImageSize);
    229   if (EFI_ERROR (Status)) {
    230     // Try again but give the loader more freedom of where to put the image.
    231     LinuxImage = LINUX_KERNEL_MAX_OFFSET;
    232     Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize);
    233     if (EFI_ERROR (Status)) {
    234       Print (L"ERROR: Did not find Linux kernel (%r).\n", Status);
    235       return Status;
    236     }
    237   }
    238   // Adjust the kernel location slightly if required. The kernel needs to be placed at start
    239   //  of memory (2MB aligned) + 0x80000.
    240   if ((LinuxImage & LINUX_ALIGN_MASK) != LINUX_ALIGN_VAL) {
    241     LinuxImage = (EFI_PHYSICAL_ADDRESS)CopyMem (ALIGN_2MB (LinuxImage) + 0x80000, (VOID*)(UINTN)LinuxImage, LinuxImageSize);
    242   }
    243 
    244   if (InitrdDevicePath) {
    245     InitrdImageBase = LINUX_KERNEL_MAX_OFFSET;
    246     Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize);
    247     if (Status == EFI_OUT_OF_RESOURCES) {
    248       Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize);
    249     }
    250     if (EFI_ERROR (Status)) {
    251       Print (L"ERROR: Did not find initrd image (%r).\n", Status);
    252       goto EXIT_FREE_LINUX;
    253     }
    254 
    255     // Check if the initrd is a uInitrd
    256     if (*(UINTN*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) {
    257       // Skip the 64-byte image header
    258       InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64);
    259       InitrdImageSize = InitrdImageBaseSize - 64;
    260     } else {
    261       InitrdImage = InitrdImageBase;
    262       InitrdImageSize = InitrdImageBaseSize;
    263     }
    264   }
    265 
    266   if (FdtDevicePath == NULL) {
    267     //
    268     // Get the FDT from the Configuration Table.
    269     // The FDT will be reloaded in PrepareFdt() to a more appropriate
    270     // location for the Linux Kernel.
    271     //
    272     Status = EfiGetSystemConfigurationTable (&gFdtTableGuid, &InstalledFdtBase);
    273     if (EFI_ERROR (Status)) {
    274       Print (L"ERROR: Did not get the Device Tree blob (%r).\n", Status);
    275       goto EXIT_FREE_INITRD;
    276     }
    277     FdtBlobBase = (EFI_PHYSICAL_ADDRESS)InstalledFdtBase;
    278     FdtBlobSize = fdt_totalsize (InstalledFdtBase);
    279   } else {
    280     //
    281     // FDT device path explicitly defined. The FDT is relocated later to a
    282     // more appropriate location for the Linux kernel.
    283     //
    284     FdtBlobBase = LINUX_KERNEL_MAX_OFFSET;
    285     Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, &FdtBlobBase, &FdtBlobSize);
    286     if (EFI_ERROR (Status)) {
    287       Print (L"ERROR: Did not find Device Tree blob (%r).\n", Status);
    288       goto EXIT_FREE_INITRD;
    289     }
    290   }
    291 
    292   //
    293   // Install secondary core pens if the Power State Coordination Interface is not supported
    294   //
    295   if (FeaturePcdGet (PcdArmLinuxSpinTable)) {
    296     // Place Pen at the start of Linux memory. We can then tell Linux to not use this bit of memory
    297     PenBase  = LinuxImage - 0x80000;
    298     PenSize  = (UINTN)&SecondariesPenEnd - (UINTN)&SecondariesPenStart;
    299 
    300     // Reserve the memory as RuntimeServices
    301     PenBaseStatus = gBS->AllocatePages (AllocateAddress, EfiRuntimeServicesCode, EFI_SIZE_TO_PAGES (PenSize), &PenBase);
    302     if (EFI_ERROR (PenBaseStatus)) {
    303       Print (L"Warning: Failed to reserve the memory required for the secondary cores at 0x%lX, Status = %r\n", PenBase, PenBaseStatus);
    304       // Even if there is a risk of memory corruption we carry on
    305     }
    306 
    307     // Put mailboxes below the pen code so we know where they are relative to code.
    308     MailBoxBase = (UINTN)PenBase + ((UINTN)&SecondariesPenEnd - (UINTN)&SecondariesPenStart);
    309     // Make sure this is 8 byte aligned.
    310     if (MailBoxBase % sizeof (MailBoxBase) != 0) {
    311       MailBoxBase += sizeof (MailBoxBase) - MailBoxBase % sizeof (MailBoxBase);
    312     }
    313 
    314     CopyMem ( (VOID*)(PenBase), (VOID*)&SecondariesPenStart, PenSize);
    315 
    316     // Update the MailboxBase variable used in the pen code
    317     *(UINTN*)(PenBase + ((UINTN)&AsmMailboxbase - (UINTN)&SecondariesPenStart)) = MailBoxBase;
    318 
    319     for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    320       // Check for correct GUID type
    321       if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
    322         UINTN i;
    323 
    324         // Get them under our control. Move from depending on 32bit reg(sys_flags) and SWI
    325         // to 64 bit addr and WFE
    326         ArmProcessorTable = (ARM_PROCESSOR_TABLE *)gST->ConfigurationTable[Index].VendorTable;
    327         ArmCoreInfoTable = ArmProcessorTable->ArmCpus;
    328 
    329         for (i = 0; i < ArmProcessorTable->NumberOfEntries; i++ ) {
    330           // This goes into the SYSFLAGS register for the VE platform. We only have one 32bit reg to use
    331           MmioWrite32 (ArmCoreInfoTable[i].MailboxSetAddress, (UINTN)PenBase);
    332 
    333           // So FDT can set the mailboxes correctly with the parser. These are 64bit Memory locations.
    334           ArmCoreInfoTable[i].MailboxSetAddress = (UINTN)MailBoxBase + i*sizeof (MailBoxBase);
    335 
    336           // Clear the mailboxes for the respective cores
    337           *((UINTN*)(ArmCoreInfoTable[i].MailboxSetAddress)) = 0x0;
    338         }
    339       }
    340     }
    341     // Flush caches to make sure our pen gets to mem before we free the cores.
    342     ArmCleanDataCache ();
    343   }
    344 
    345   // By setting address=0 we leave the memory allocation to the function
    346   Status = PrepareFdt (SystemMemoryBase, Arguments, InitrdImage, InitrdImageSize, &FdtBlobBase, &FdtBlobSize);
    347   if (EFI_ERROR (Status)) {
    348     Print (L"ERROR: Can not load Linux kernel with Device Tree. Status=0x%X\n", Status);
    349     goto EXIT_FREE_FDT;
    350   }
    351 
    352   return StartLinux (LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize);
    353 
    354 EXIT_FREE_FDT:
    355   if (!EFI_ERROR (PenBaseStatus)) {
    356     gBS->FreePages (PenBase, EFI_SIZE_TO_PAGES (PenSize));
    357   }
    358 
    359   gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize));
    360 
    361 EXIT_FREE_INITRD:
    362   if (InitrdDevicePath) {
    363     gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
    364   }
    365 
    366 EXIT_FREE_LINUX:
    367   gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
    368 
    369   return Status;
    370 }
    371