Home | History | Annotate | Download | only in Arm
      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/PcdLib.h>
     17 
     18 #include <Guid/Fdt.h>
     19 
     20 #include "LinuxLoader.h"
     21 
     22 #define ALIGN32_BELOW(addr)   ALIGN_POINTER(addr - 32,32)
     23 
     24 #define IS_ADDRESS_IN_REGION(RegionStart, RegionSize, Address) \
     25     (((UINTN)(RegionStart) <= (UINTN)(Address)) && ((UINTN)(Address) <= ((UINTN)(RegionStart) + (UINTN)(RegionSize))))
     26 
     27 EFI_STATUS
     28 PrepareAtagList (
     29   IN  EFI_PHYSICAL_ADDRESS  SystemMemoryBase,
     30   IN  CONST CHAR8*          CommandLineString,
     31   IN  EFI_PHYSICAL_ADDRESS  InitrdImage,
     32   IN  UINTN                 InitrdImageSize,
     33   OUT EFI_PHYSICAL_ADDRESS  *AtagBase,
     34   OUT UINT32                *AtagSize
     35   );
     36 
     37 STATIC
     38 VOID
     39 PreparePlatformHardware (
     40   VOID
     41   )
     42 {
     43   //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
     44 
     45   // Clean before Disable else the Stack gets corrupted with old data.
     46   ArmCleanDataCache ();
     47   ArmDisableDataCache ();
     48   // Invalidate all the entries that might have snuck in.
     49   ArmInvalidateDataCache ();
     50 
     51   // Invalidate and disable the Instruction cache
     52   ArmDisableInstructionCache ();
     53   ArmInvalidateInstructionCache ();
     54 
     55   // Turn off MMU
     56   ArmDisableMmu ();
     57 }
     58 
     59 STATIC
     60 EFI_STATUS
     61 StartLinux (
     62   IN  EFI_PHYSICAL_ADDRESS  SystemMemoryBase,
     63   IN  EFI_PHYSICAL_ADDRESS  LinuxImage,
     64   IN  UINTN                 LinuxImageSize,
     65   IN  EFI_PHYSICAL_ADDRESS  KernelParamsAddress,
     66   IN  UINTN                 KernelParamsSize,
     67   IN  UINT32                MachineType
     68   )
     69 {
     70   EFI_STATUS            Status;
     71   LINUX_KERNEL          LinuxKernel;
     72 
     73   // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
     74   // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
     75   Status = ShutdownUefiBootServices ();
     76   if (EFI_ERROR (Status)) {
     77     DEBUG ((EFI_D_ERROR, "ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status));
     78     return Status;
     79   }
     80 
     81   // Move the kernel parameters to any address inside the first 1MB.
     82   // This is necessary because the ARM Linux kernel requires
     83   // the FTD / ATAG List to reside entirely inside the first 1MB of
     84   // physical memory.
     85   //Note: There is no requirement on the alignment
     86   if (MachineType != ARM_FDT_MACHINE_TYPE) {
     87     if (((UINTN)KernelParamsAddress > LINUX_ATAG_MAX_OFFSET) && (KernelParamsSize < PcdGet32 (PcdArmLinuxAtagMaxOffset))) {
     88       KernelParamsAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)CopyMem (ALIGN32_BELOW (LINUX_ATAG_MAX_OFFSET - KernelParamsSize), (VOID*)(UINTN)KernelParamsAddress, KernelParamsSize);
     89     }
     90   } else {
     91     if (((UINTN)KernelParamsAddress > LINUX_FDT_MAX_OFFSET) && (KernelParamsSize < PcdGet32 (PcdArmLinuxFdtMaxOffset))) {
     92       KernelParamsAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)CopyMem (ALIGN32_BELOW (LINUX_FDT_MAX_OFFSET - KernelParamsSize), (VOID*)(UINTN)KernelParamsAddress, KernelParamsSize);
     93     }
     94   }
     95 
     96   if ((UINTN)LinuxImage > LINUX_KERNEL_MAX_OFFSET) {
     97     //Note: There is no requirement on the alignment
     98     LinuxKernel = (LINUX_KERNEL)CopyMem (ALIGN32_BELOW (LINUX_KERNEL_MAX_OFFSET - LinuxImageSize), (VOID*)(UINTN)LinuxImage, LinuxImageSize);
     99   } else {
    100     LinuxKernel = (LINUX_KERNEL)(UINTN)LinuxImage;
    101   }
    102 
    103   // Check if the Linux Image is a uImage
    104   if (*(UINT32*)LinuxKernel == LINUX_UIMAGE_SIGNATURE) {
    105     // Assume the Image Entry Point is just after the uImage header (64-byte size)
    106     LinuxKernel = (LINUX_KERNEL)((UINTN)LinuxKernel + 64);
    107     LinuxImageSize -= 64;
    108   }
    109 
    110   // Check there is no overlapping between kernel and its parameters
    111   // We can only assert because it is too late to fallback to UEFI (ExitBootServices has been called).
    112   ASSERT (!IS_ADDRESS_IN_REGION (LinuxKernel, LinuxImageSize, KernelParamsAddress) &&
    113           !IS_ADDRESS_IN_REGION (LinuxKernel, LinuxImageSize, KernelParamsAddress + KernelParamsSize));
    114 
    115   //
    116   // Switch off interrupts, caches, mmu, etc
    117   //
    118   PreparePlatformHardware ();
    119 
    120   // Register and print out performance information
    121   PERF_END (NULL, "BDS", NULL, 0);
    122   if (PerformanceMeasurementEnabled ()) {
    123     PrintPerformance ();
    124   }
    125 
    126   //
    127   // Start the Linux Kernel
    128   //
    129 
    130   // Outside BootServices, so can't use Print();
    131   DEBUG ((EFI_D_ERROR, "\nStarting the kernel:\n\n"));
    132 
    133   // Jump to kernel with register set
    134   LinuxKernel ((UINTN)0, MachineType, (UINTN)KernelParamsAddress);
    135 
    136   // Kernel should never exit
    137   // After Life services are not provided
    138   ASSERT (FALSE);
    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   EFI_STATUS            Status;
    168   UINT32                LinuxImageSize;
    169   UINT32                InitrdImageBaseSize = 0;
    170   UINT32                InitrdImageSize = 0;
    171   UINT32                AtagSize;
    172   EFI_PHYSICAL_ADDRESS  AtagBase;
    173   EFI_PHYSICAL_ADDRESS  LinuxImage;
    174   EFI_PHYSICAL_ADDRESS  InitrdImageBase = 0;
    175   EFI_PHYSICAL_ADDRESS  InitrdImage = 0;
    176 
    177   PERF_START (NULL, "BDS", NULL, 0);
    178 
    179   // Load the Linux kernel from a device path
    180   LinuxImage = LINUX_KERNEL_MAX_OFFSET;
    181   Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize);
    182   if (EFI_ERROR (Status)) {
    183     Print (L"ERROR: Did not find Linux kernel.\n");
    184     return Status;
    185   }
    186 
    187   if (InitrdDevicePath) {
    188     // Load the initrd near to the Linux kernel
    189     InitrdImageBase = LINUX_KERNEL_MAX_OFFSET;
    190     Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize);
    191     if (Status == EFI_OUT_OF_RESOURCES) {
    192       Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize);
    193     }
    194     if (EFI_ERROR (Status)) {
    195       Print (L"ERROR: Did not find initrd image.\n");
    196       goto EXIT_FREE_LINUX;
    197     }
    198 
    199     // Check if the initrd is a uInitrd
    200     if (*(UINT32*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) {
    201       // Skip the 64-byte image header
    202       InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64);
    203       InitrdImageSize = InitrdImageBaseSize - 64;
    204     } else {
    205       InitrdImage = InitrdImageBase;
    206       InitrdImageSize = InitrdImageBaseSize;
    207     }
    208   }
    209 
    210   //
    211   // Setup the Linux Kernel Parameters
    212   //
    213 
    214   // By setting address=0 we leave the memory allocation to the function
    215   Status = PrepareAtagList (SystemMemoryBase, CommandLineArguments, InitrdImage, InitrdImageSize, &AtagBase, &AtagSize);
    216   if (EFI_ERROR (Status)) {
    217     Print (L"ERROR: Can not prepare ATAG list. Status=0x%X\n", Status);
    218     goto EXIT_FREE_INITRD;
    219   }
    220 
    221   return StartLinux (SystemMemoryBase, LinuxImage, LinuxImageSize, AtagBase, AtagSize, MachineType);
    222 
    223 EXIT_FREE_INITRD:
    224   if (InitrdDevicePath) {
    225     gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
    226   }
    227 
    228 EXIT_FREE_LINUX:
    229   gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
    230 
    231   return Status;
    232 }
    233 
    234 /**
    235   Start a Linux kernel from a Device Path
    236 
    237   @param  LinuxKernelDevicePath  Device Path to the Linux Kernel
    238   @param  InitrdDevicePath       Device Path to the Initrd
    239   @param  CommandLineArguments   Linux command line
    240 
    241   @retval EFI_SUCCESS           All drivers have been connected
    242   @retval EFI_NOT_FOUND         The Linux kernel Device Path has not been found
    243   @retval EFI_OUT_OF_RESOURCES  There is not enough resource memory to store the matching results.
    244 
    245 **/
    246 EFI_STATUS
    247 BootLinuxFdt (
    248   IN  EFI_PHYSICAL_ADDRESS      SystemMemoryBase,
    249   IN  EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
    250   IN  EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
    251   IN  EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath,
    252   IN  CONST CHAR8*              CommandLineArguments
    253   )
    254 {
    255   EFI_STATUS               Status;
    256   UINT32                   LinuxImageSize;
    257   UINT32                   InitrdImageBaseSize = 0;
    258   UINT32                   InitrdImageSize = 0;
    259   VOID                     *InstalledFdtBase;
    260   UINT32                   FdtBlobSize;
    261   EFI_PHYSICAL_ADDRESS     FdtBlobBase;
    262   EFI_PHYSICAL_ADDRESS     LinuxImage;
    263   EFI_PHYSICAL_ADDRESS     InitrdImageBase = 0;
    264   EFI_PHYSICAL_ADDRESS     InitrdImage = 0;
    265 
    266   PERF_START (NULL, "BDS", NULL, 0);
    267 
    268   // Load the Linux kernel from a device path
    269   LinuxImage = LINUX_KERNEL_MAX_OFFSET;
    270   Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize);
    271   if (EFI_ERROR (Status)) {
    272     Print (L"ERROR: Did not find Linux kernel.\n");
    273     return Status;
    274   }
    275 
    276   if (InitrdDevicePath) {
    277     InitrdImageBase = LINUX_KERNEL_MAX_OFFSET;
    278     Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize);
    279     if (Status == EFI_OUT_OF_RESOURCES) {
    280       Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize);
    281     }
    282     if (EFI_ERROR (Status)) {
    283       Print (L"ERROR: Did not find initrd image.\n");
    284       goto EXIT_FREE_LINUX;
    285     }
    286 
    287     // Check if the initrd is a uInitrd
    288     if (*(UINT32*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) {
    289       // Skip the 64-byte image header
    290       InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64);
    291       InitrdImageSize = InitrdImageBaseSize - 64;
    292     } else {
    293       InitrdImage = InitrdImageBase;
    294       InitrdImageSize = InitrdImageBaseSize;
    295     }
    296   }
    297 
    298   if (FdtDevicePath == NULL) {
    299     //
    300     // Get the FDT from the Configuration Table.
    301     // The FDT will be reloaded in PrepareFdt() to a more appropriate
    302     // location for the Linux Kernel.
    303     //
    304     Status = EfiGetSystemConfigurationTable (&gFdtTableGuid, &InstalledFdtBase);
    305     if (EFI_ERROR (Status)) {
    306       Print (L"ERROR: Did not get the Device Tree blob (%r).\n", Status);
    307       goto EXIT_FREE_INITRD;
    308     }
    309     FdtBlobBase = (EFI_PHYSICAL_ADDRESS)(UINTN)InstalledFdtBase;
    310     FdtBlobSize = fdt_totalsize (InstalledFdtBase);
    311   } else {
    312     //
    313     // FDT device path explicitly defined. The FDT is relocated later to a
    314     // more appropriate location for the Linux kernel.
    315     //
    316     FdtBlobBase = LINUX_KERNEL_MAX_OFFSET;
    317     Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, &FdtBlobBase, &FdtBlobSize);
    318     if (EFI_ERROR (Status)) {
    319       Print (L"ERROR: Did not find Device Tree blob (%r).\n", Status);
    320       goto EXIT_FREE_INITRD;
    321     }
    322   }
    323 
    324   // Update the Fdt with the Initrd information. The FDT will increase in size.
    325   // By setting address=0 we leave the memory allocation to the function
    326   Status = PrepareFdt (SystemMemoryBase, CommandLineArguments, InitrdImage, InitrdImageSize, &FdtBlobBase, &FdtBlobSize);
    327   if (EFI_ERROR (Status)) {
    328     Print (L"ERROR: Can not load kernel with FDT. Status=%r\n", Status);
    329     goto EXIT_FREE_FDT;
    330   }
    331 
    332   return StartLinux (SystemMemoryBase, LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize, ARM_FDT_MACHINE_TYPE);
    333 
    334 EXIT_FREE_FDT:
    335   gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize));
    336 
    337 EXIT_FREE_INITRD:
    338   if (InitrdDevicePath) {
    339     gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
    340   }
    341 
    342 EXIT_FREE_LINUX:
    343   gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
    344 
    345   return Status;
    346 }
    347