#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;
* 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.
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;
}