]> git.proxmox.com Git - mirror_edk2.git/blobdiff - ArmPkg/Library/BdsLib/BdsLinuxLoader.c
ArmPkg/BdsLib: InitrdImageSize was not initialized when no initrd was
[mirror_edk2.git] / ArmPkg / Library / BdsLib / BdsLinuxLoader.c
index 69435936b7446429a859a37c1aed85d2d8a915aa..ce4b2a43b66aaa0dbbac7e7d79a281fb6803db4e 100644 (file)
 #include <Library/ArmLib.h>
 #include <Library/HobLib.h>
 
-STATIC
-EFI_STATUS
-GetARMLinuxMachineType (
-    IN  BOOLEAN FdtSupported,
-    OUT UINT32  *MachineType
-) {
-  if (FdtSupported)
-  {
-    // FDT requires that the machine type is set to the maximum 32-bit number.
-    *MachineType = 0xFFFFFFFF;
-  }
-  else
-  {
-    // Non-FDT requires a specific machine type.
-    // This OS Boot loader supports just one machine type,
-    // but that could change in the future.
-    *MachineType = PcdGet32(PcdArmMachineType);
-  }
+#define ALIGN32_BELOW(addr)   ALIGN_POINTER(addr - 32,32)
 
-  return EFI_SUCCESS;
-}
+#define LINUX_ATAG_MAX_OFFSET     (PcdGet32(PcdSystemMemoryBase) + PcdGet32(PcdArmLinuxAtagMaxOffset))
+#define LINUX_KERNEL_MAX_OFFSET   (PcdGet32(PcdSystemMemoryBase) + PcdGet32(PcdArmLinuxKernelMaxOffset))
+
+// Point to the current ATAG
+STATIC LINUX_ATAG *mLinuxKernelCurrentAtag;
 
 STATIC
 VOID
-SetupCoreTag( IN UINT32 PageSize )
+SetupCoreTag (
+  IN UINT32 PageSize
+  )
 {
-  Params->header.size = tag_size(atag_core);
-  Params->header.type = ATAG_CORE;
+  mLinuxKernelCurrentAtag->header.size = tag_size(LINUX_ATAG_CORE);
+  mLinuxKernelCurrentAtag->header.type = ATAG_CORE;
 
-  Params->body.core_tag.flags    = 1;            /* ensure read-only */
-  Params->body.core_tag.pagesize = PageSize;     /* systems PageSize (4k) */
-  Params->body.core_tag.rootdev  = 0;            /* zero root device (typically overridden from kernel command line )*/
+  mLinuxKernelCurrentAtag->body.core_tag.flags    = 1;            /* ensure read-only */
+  mLinuxKernelCurrentAtag->body.core_tag.pagesize = PageSize;     /* systems PageSize (4k) */
+  mLinuxKernelCurrentAtag->body.core_tag.rootdev  = 0;            /* zero root device (typically overridden from kernel command line )*/
 
-  Params = next_tag_address(Params);             /* move pointer to next tag */
+  // move pointer to next tag
+  mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag);
 }
 
 STATIC
 VOID
-SetupMemTag( IN UINTN StartAddress, IN UINT32 Size )
+SetupMemTag (
+  IN UINTN StartAddress,
+  IN UINT32 Size
+  )
 {
-  Params->header.size = tag_size(atag_mem);
-  Params->header.type = ATAG_MEM;
+  mLinuxKernelCurrentAtag->header.size = tag_size(LINUX_ATAG_MEM);
+  mLinuxKernelCurrentAtag->header.type = ATAG_MEM;
 
-  Params->body.mem_tag.start = StartAddress;    /* Start of memory chunk for AtagMem */
-  Params->body.mem_tag.size  = Size;             /* Size of memory chunk for AtagMem */
+  mLinuxKernelCurrentAtag->body.mem_tag.start = StartAddress;    /* Start of memory chunk for AtagMem */
+  mLinuxKernelCurrentAtag->body.mem_tag.size  = Size;             /* Size of memory chunk for AtagMem */
 
-  Params = next_tag_address(Params);             /* move pointer to next tag */
+  // move pointer to next tag
+  mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag);
 }
 
 STATIC
 VOID
-SetupCmdlineTag( IN CONST CHAR8 *CmdLine )
+SetupCmdlineTag (
+  IN CONST CHAR8 *CmdLine
+  )
 {
   UINT32 LineLength;
 
@@ -81,98 +76,105 @@ SetupCmdlineTag( IN CONST CHAR8 *CmdLine )
    * Do not insert a tag for an empty CommandLine, don't even modify the tag address pointer.
    * Remember, you have at least one null string terminator character.
    */
-  if( LineLength > 1 )
-  {
-    Params->header.size = ((UINT32)sizeof(struct atag_header) + LineLength + (UINT32)3) >> 2;
-    Params->header.type = ATAG_CMDLINE;
+  if(LineLength > 1) {
+    mLinuxKernelCurrentAtag->header.size = ((UINT32)sizeof(LINUX_ATAG_HEADER) + LineLength + (UINT32)3) >> 2;
+    mLinuxKernelCurrentAtag->header.type = ATAG_CMDLINE;
 
     /* place CommandLine into tag */
-    AsciiStrCpy(Params->body.cmdline_tag.cmdline, CmdLine);
+    AsciiStrCpy(mLinuxKernelCurrentAtag->body.cmdline_tag.cmdline, CmdLine);
 
-    Params = next_tag_address(Params);             /* move pointer to next tag */
+    // move pointer to next tag
+    mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag);
   }
 }
 
 STATIC
 VOID
-SetupEndTag( VOID )
+SetupEndTag (
+  VOID
+  )
 {
   // Empty tag ends list; this has zero length and no body
-  Params->header.type = ATAG_NONE;
-  Params->header.size = 0;
+  mLinuxKernelCurrentAtag->header.type = ATAG_NONE;
+  mLinuxKernelCurrentAtag->header.size = 0;
 
   /* We can not calculate the next address by using the standard macro:
    * Params = next_tag_address(Params);
    * because it relies on the header.size, which here it is 0 (zero).
-   * The easiest way is to add the sizeof(Params->header).
+   * The easiest way is to add the sizeof(mLinuxKernelCurrentAtag->header).
    */
-  Params = (struct atag *)((UINT32)Params + sizeof(Params->header));
+  mLinuxKernelCurrentAtag = (LINUX_ATAG*)((UINT32)mLinuxKernelCurrentAtag + sizeof(mLinuxKernelCurrentAtag->header));
 }
 
 STATIC
 EFI_STATUS
-PrepareAtagList(
-    IN OUT struct atag      **AtagStartAddress,
-    IN     CONST CHAR8*     CommandLineString,
-    OUT    UINT32           *AtagSize
-) {
-  LIST_ENTRY    *ResourceLink;
-  LIST_ENTRY ResourceList;
+PrepareAtagList (
+  IN  CONST CHAR8*     CommandLineString,
+  IN  EFI_PHYSICAL_ADDRESS InitrdImage,
+  IN  UINTN            InitrdImageSize,
+  OUT LINUX_ATAG       **AtagBase,
+  OUT UINT32           *AtagSize
+  )
+{
+  EFI_STATUS                  Status;
+  LIST_ENTRY                  *ResourceLink;
+  LIST_ENTRY                  ResourceList;
+  EFI_PHYSICAL_ADDRESS        AtagStartAddress;
   BDS_SYSTEM_MEMORY_RESOURCE  *Resource;
 
-  // If no address supplied then this function will decide where to put it
-  if( *AtagStartAddress == 0 )
-  {
-    /* WARNING: At the time of writing (2010-July-30) the linux kernel expects
-     * the atag list it in the first 1MB of memory and preferably at address 0x100.
-     * This has a very high risk of overwriting UEFI code, but as
-     * the linux kernel does not expect any runtime services from uefi
-     * and there is no afterlife section following the linux kernel termination,
-     * it does not matter if we stamp over that memory area.
-     *
-     * The proposed workaround is to create the atag list somewhere in boot services memory
-     * and then transfer it to address 0x100 (or to runtime services memory) immediately
-     * before starting the kernel.
-     * An additional benefit of this is that when we copy the ATAG list to it's final place,
-     * we can trim down the memory allocation size. Before we create the list we don't know
-     * how much space it is going to take, so we are over-allocating space.
-     */
-    *AtagStartAddress = (struct atag *) AllocatePool(ATAG_MAX_SIZE);
+  AtagStartAddress = LINUX_ATAG_MAX_OFFSET;
+  Status = gBS->AllocatePages (AllocateMaxAddress, EfiBootServicesData, EFI_SIZE_TO_PAGES(ATAG_MAX_SIZE), &AtagStartAddress);
+  if (EFI_ERROR(Status)) {
+    DEBUG ((EFI_D_ERROR,"Failed to allocate Atag at 0x%lX (%r)\n",AtagStartAddress,Status));
+    Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData, EFI_SIZE_TO_PAGES(ATAG_MAX_SIZE), &AtagStartAddress);
+    ASSERT_EFI_ERROR(Status);
   }
 
-  // Ensure the pointer is not NULL.
-  ASSERT( *AtagStartAddress != (struct atag *)NULL );
-
   // Ready to setup the atag list
-  Params = *AtagStartAddress;
+  mLinuxKernelCurrentAtag = (LINUX_ATAG*)(UINTN)AtagStartAddress;
 
   // Standard core tag 4k PageSize
   SetupCoreTag( (UINT32)SIZE_4KB );
 
   // Physical memory setup
-  GetSystemMemoryResources(&ResourceList);
+  GetSystemMemoryResources (&ResourceList);
   ResourceLink = ResourceList.ForwardLink;
   while (ResourceLink != NULL && ResourceLink != &ResourceList) {
-    Resource = (BDS_SYSTEM_MEMORY_RESOURCE*)ResourceList.ForwardLink;
+    Resource = (BDS_SYSTEM_MEMORY_RESOURCE*)ResourceLink;
+    DEBUG((EFI_D_INFO,"- [0x%08X,0x%08X]\n",(UINT32)Resource->PhysicalStart,(UINT32)Resource->PhysicalStart+(UINT32)Resource->ResourceLength));
     SetupMemTag( (UINT32)Resource->PhysicalStart, (UINT32)Resource->ResourceLength );
     ResourceLink = ResourceLink->ForwardLink;
   }
 
   // CommandLine setting root device
-  SetupCmdlineTag( CommandLineString );
+  SetupCmdlineTag (CommandLineString);
+
+  if (InitrdImageSize > 0 && InitrdImage != 0) {
+    mLinuxKernelCurrentAtag->header.size = tag_size(LINUX_ATAG_INITRD2);
+    mLinuxKernelCurrentAtag->header.type = ATAG_INITRD2;
+
+    mLinuxKernelCurrentAtag->body.initrd2_tag.start = (UINT32)InitrdImage;
+    mLinuxKernelCurrentAtag->body.initrd2_tag.size = (UINT32)InitrdImageSize;
+
+    // Move pointer to next tag
+    mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag);
+  }
 
   // end of tags
   SetupEndTag();
 
   // Calculate atag list size
-  *AtagSize = (UINT32)Params - (UINT32)*AtagStartAddress + 1;
+  *AtagBase = (LINUX_ATAG*)(UINTN)AtagStartAddress;
+  *AtagSize = (UINT32)mLinuxKernelCurrentAtag - (UINT32)AtagStartAddress + 1;
 
   return EFI_SUCCESS;
 }
 
 STATIC
 EFI_STATUS
-PreparePlatformHardware( VOID )
+PreparePlatformHardware (
+  VOID
+  )
 {
   //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
 
@@ -185,153 +187,151 @@ PreparePlatformHardware( VOID )
   ArmDisableInstructionCache ();
 
   // turn off MMU
-  ArmInvalidateTlb();
   ArmDisableMmu();
 
   return EFI_SUCCESS;
 }
 
-/*************************************************
- * R0, R1, R2 correspond to registers R0, R1, R2
- *************************************************/
-//STATIC
-EFI_STATUS
-StartLinuxKernel( IN VOID* KernelAddress, IN UINTN R0, IN UINTN R1, IN UINTN R2 )
-{
-  VOID     (*Kernel)(UINT32 Zero, UINT32 Arch, UINTN AtagListParams);
-
-  // set the kernel address
-  Kernel = (VOID (*)(UINT32, UINT32, UINTN)) KernelAddress;
+/**
+  Start a Linux kernel from a Device Path
 
-  // Outside BootServices, so can't use Print();
-  DEBUG((EFI_D_ERROR, "\nStarting the kernel:\n\n"));
+  @param  LinuxKernel           Device Path to the Linux Kernel
+  @param  Parameters            Linux kernel agruments
+  @param  Fdt                   Device Path to the Flat Device Tree
 
-  // jump to kernel with register set
-  Kernel( R0, R1, R2 );
+  @retval EFI_SUCCESS           All drivers have been connected
+  @retval EFI_NOT_FOUND         The Linux kernel Device Path has not been found
+  @retval EFI_OUT_OF_RESOURCES  There is not enough resource memory to store the matching results.
 
-  // Kernel should never exit
-  // After Life services are not provided
-  ASSERT( FALSE );
-
-  return EFI_SUCCESS;
-}
+**/
+EFI_STATUS
+BdsBootLinux (
+  IN  EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
+  IN  EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
+  IN  CONST CHAR8*  Arguments,
+  IN  EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath
+  )
+{
+  EFI_STATUS            Status;
+  UINT32                LinuxImageSize;
+  UINT32                InitrdImageSize;
+  UINT32                KernelParamsSize;
+  EFI_PHYSICAL_ADDRESS  KernelParamsAddress;
+  UINT32                MachineType;
+  BOOLEAN               FdtSupported;
+  LINUX_KERNEL          LinuxKernel;
+  EFI_PHYSICAL_ADDRESS  LinuxImage;
+  EFI_PHYSICAL_ADDRESS  InitrdImage;
+
+  InitrdImageSize = 0;
+  FdtSupported = FALSE;
+       
+  // Ensure the System Memory PCDs have been initialized (PcdSystemMemoryBase and PcdSystemMemorySize)
+  ASSERT (PcdGet32(PcdSystemMemorySize) != 0);
+
+  PERF_START (NULL, "BDS", NULL, 0);
+
+  // Load the Linux kernel from a device path
+  LinuxImage = LINUX_KERNEL_MAX_OFFSET;
+  Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize);
+  if (EFI_ERROR(Status)) {
+    Print (L"ERROR: Did not find Linux kernel.\n");
+    return Status;
+  }
+  LinuxKernel = (LINUX_KERNEL)(UINTN)LinuxImage;
 
-EFI_STATUS BdsBootLinux(
-    IN  CONST CHAR16* LinuxKernel,
-    IN  CONST CHAR8*  ATag,
-    IN  CONST CHAR16* Fdt
-) {
-    BDS_FILE   LinuxKernelFile;
-    BDS_FILE   FdtFile;
-    EFI_STATUS          Status;
-    VOID*               LinuxImage;
-
-    UINT32              KernelParamsSize;
-    VOID*               KernelParamsAddress = NULL;
-    UINTN               KernelParamsNewAddress;
-    UINTN               *AtagAddress;
-    UINT32              MachineType;
-    BOOLEAN             FdtSupported = FALSE;
-    EFI_HOB_RESOURCE_DESCRIPTOR *ResHob;
-
-    // Load the Linux kernel from a device path
-    Status = BdsLoadFilePath(LinuxKernel, &LinuxKernelFile);
+  if (InitrdDevicePath) {
+    Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImage, &InitrdImageSize);
     if (EFI_ERROR(Status)) {
-        DEBUG ((EFI_D_ERROR, "ERROR: Do not find Linux kernel %s\n",LinuxKernel));
-        return Status;
+      Print (L"ERROR: Did not find initrd image.\n");
+      return Status;
     }
+  }
 
-    // Copy the Linux Kernel from the raw file to Runtime memory
-    Status = BdsCopyRawFileToRuntimeMemory(&LinuxKernelFile,&LinuxImage,NULL);
+  if (FdtDevicePath) {
+    // Load the FDT binary from a device path
+    KernelParamsAddress = LINUX_ATAG_MAX_OFFSET;
+    Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, &KernelParamsAddress, &KernelParamsSize);
     if (EFI_ERROR(Status)) {
-        goto Exit;
+      Print (L"ERROR: Did not find Device Tree blob.\n");
+      return Status;
     }
+    FdtSupported = TRUE;
+  }
 
-    // Load the FDT binary from a device path
-    Status = BdsLoadFilePath(Fdt, &FdtFile);
-    if (!EFI_ERROR(Status)) {
-        // Copy the FDT binary from the raw file to Runtime memory
-        Status = BdsCopyRawFileToRuntimeMemory(&FdtFile,&KernelParamsAddress,&KernelParamsSize);
-        if (EFI_ERROR(Status)) {
-            goto Exit;
-        } else {
-            FdtSupported = TRUE;
-        }
-    }
+  //
+  // Setup the Linux Kernel Parameters
+  //
+  if (!FdtSupported) {
+    // Non-FDT requires a specific machine type.
+    // This OS Boot loader supports just one machine type,
+    // but that could change in the future.
+    MachineType = PcdGet32(PcdArmMachineType);
 
-    /**********************************************************
-    * Setup the platform type
-    **********************************************************/
-    Status = GetARMLinuxMachineType(FdtSupported, &MachineType);
-    if(EFI_ERROR(Status))
-    {
-        Print(L"ERROR  : Can not prepare ARM Linux machine type. Status=0x%X\n", Status);
-        goto Exit;
+    // By setting address=0 we leave the memory allocation to the function
+    Status = PrepareAtagList (Arguments, InitrdImage, InitrdImageSize, (LINUX_ATAG**)&KernelParamsAddress, &KernelParamsSize);
+    if(EFI_ERROR(Status)) {
+      Print(L"ERROR: Can not prepare ATAG list. Status=0x%X\n", Status);
+      goto Exit;
     }
+  } else {
+    MachineType = 0xFFFFFFFF;
+  }
 
-    if (!FdtSupported) {
-        /**********************************************************
-         * Setup the ATAG list
-         **********************************************************/
-        // By setting address=0 we leave the memory allocation to the function
-        AtagAddress = 0;
-        Status = PrepareAtagList( (struct atag **)&AtagAddress, ATag, &KernelParamsSize );
-        KernelParamsAddress = (VOID*)AtagAddress;
-        if(EFI_ERROR(Status))
-        {
-            Print(L"ERROR  : Can not prepare ATAG list. Status=0x%X\n", Status);
-            goto Exit;
-        }
-    }
+  // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
+  // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
+  Status = ShutdownUefiBootServices ();
+  if(EFI_ERROR(Status)) {
+    DEBUG((EFI_D_ERROR,"ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status));
+    goto Exit;
+  }
 
-    /**********************************************************
-    * Switch off interrupts, caches, mmu, etc
-    **********************************************************/
-    Status = PreparePlatformHardware();
-    if(EFI_ERROR(Status))
-    {
-        Print(L"ERROR  : Can not prepare platform hardware. Status=0x%X\n", Status);
-        goto Exit;
-    }
+  // Move the kernel parameters to any address inside the first 1MB.
+  // This is necessary because the ARM Linux kernel requires
+  // the FTD / ATAG List to reside entirely inside the first 1MB of
+  // physical memory.
+  if ((UINTN)KernelParamsAddress > LINUX_ATAG_MAX_OFFSET) {
+    //Note: There is no requirement on the alignment
+    KernelParamsAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)CopyMem (ALIGN32_BELOW(LINUX_ATAG_MAX_OFFSET - KernelParamsSize), (VOID*)(UINTN)KernelParamsAddress, KernelParamsSize);
+  }
 
-    // Initialize the ATag destination
-    KernelParamsNewAddress = 0x100;
-
-    // Update the ATag destination by finding the start address of the first System Memory Resource Descriptor Hob
-    ResHob = (EFI_HOB_RESOURCE_DESCRIPTOR *)GetFirstHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR);
-    while (ResHob != NULL) {
-        if (ResHob->ResourceType == EFI_RESOURCE_SYSTEM_MEMORY) {
-            KernelParamsNewAddress = (UINTN)ResHob->PhysicalStart + 0x100;
-            break;
-        }
-        ResHob = (EFI_HOB_RESOURCE_DESCRIPTOR *)GetNextHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, (VOID *)((UINTN)ResHob + ResHob->Header.HobLength)); 
-    }
+  if ((UINTN)LinuxImage > LINUX_KERNEL_MAX_OFFSET) {
+    //Note: There is no requirement on the alignment
+    LinuxKernel = (LINUX_KERNEL)CopyMem (ALIGN32_BELOW(LINUX_KERNEL_MAX_OFFSET - LinuxImageSize), (VOID*)(UINTN)LinuxImage, LinuxImageSize);
+  }
 
-    // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
-    // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
-    Status = ShutdownUefiBootServices();
-    if(EFI_ERROR(Status))
-    {
-        Print(L"ERROR  : Can not shutdown UEFI boot services. Status=0x%X\n", Status);
-        goto Exit;
-    }
+  //TODO: Check there is no overlapping between kernel and Atag
+
+  //
+  // Switch off interrupts, caches, mmu, etc
+  //
+  Status = PreparePlatformHardware ();
+  ASSERT_EFI_ERROR(Status);
+
+  // Register and print out performance information
+  PERF_END (NULL, "BDS", NULL, 0);
+  if (PerformanceMeasurementEnabled ()) {
+    PrintPerformance ();
+  }
+
+  //
+  // Start the Linux Kernel
+  //
 
-    // Move the kernel parameters to any address inside the first 1MB.
-    // This is necessary because the ARM Linux kernel requires
-    // the FTD / ATAG List to reside entirely inside the first 1MB of
-    // physical memory.
-    CopyMem((VOID*)KernelParamsNewAddress, KernelParamsAddress, KernelParamsSize);
+  // Outside BootServices, so can't use Print();
+  DEBUG((EFI_D_ERROR, "\nStarting the kernel:\n\n"));
 
-    //**********************************************************
-    // * Start the Linux Kernel
-    // **********************************************************
-    // Lift off ...
-    Status = StartLinuxKernel(LinuxImage, (UINTN)0, (UINTN)MachineType, KernelParamsNewAddress );
+  // jump to kernel with register set
+  LinuxKernel ((UINTN)0, (UINTN)MachineType, (UINTN)KernelParamsAddress);
 
-    // Only be here if we fail to start Linux
-    DEBUG((EFI_D_ERROR, "ERROR  : Can not start the kernel. Status=0x%X\n", Status));
+  // Kernel should never exit
+  // After Life services are not provided
+  ASSERT(FALSE);
 
 Exit:
-    // Free Runtimee Memory (kernel and FDT)
-    return Status;
+  // Only be here if we fail to start Linux
+  Print (L"ERROR  : Can not start the kernel. Status=0x%X\n", Status);
+
+  // Free Runtimee Memory (kernel and FDT)
+  return Status;
 }