--- /dev/null
+/** @file\r
+*\r
+* Copyright (c) 2011-2013, ARM Limited. All rights reserved.\r
+*\r
+* This program and the accompanying materials\r
+* are licensed and made available under the terms and conditions of the BSD License\r
+* which accompanies this distribution. The full text of the license may be found at\r
+* http://opensource.org/licenses/bsd-license.php\r
+*\r
+* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+*\r
+**/\r
+#include <Library/ArmGicLib.h>\r
+#include <Ppi/ArmMpCoreInfo.h>\r
+#include <Library/IoLib.h>\r
+\r
+#include "BdsInternal.h"\r
+#include "BdsLinuxLoader.h"\r
+\r
+/*\r
+ Linux kernel booting: Look at the doc in the Kernel source :\r
+ Documentation/arm64/booting.txt\r
+ The kernel image must be placed at the start of the memory to be used by the\r
+ kernel (2MB aligned) + 0x80000.\r
+\r
+ The Device tree blob is expected to be under 2MB and be within the first 512MB\r
+ of kernel memory and be 2MB aligned.\r
+\r
+ A Flattened Device Tree (FDT) used to boot linux needs to be updated before\r
+ the kernel is started. It needs to indicate how secondary cores are brought up\r
+ and where they are waiting before loading Linux. The FDT also needs to hold\r
+ the correct kernel command line and filesystem RAM-disk information.\r
+ At the moment we do not fully support generating this FDT information at\r
+ runtime. A prepared FDT should be provided at boot. FDT is the only supported\r
+ method for booting the AArch64 Linux kernel.\r
+\r
+ Linux does not use any runtime services at this time, so we can let it\r
+ overwrite UEFI.\r
+*/\r
+\r
+\r
+#define LINUX_ALIGN_VAL (0x080000) // 2MB + 0x80000 mask\r
+#define LINUX_ALIGN_MASK (0x1FFFFF) // Bottom 21bits\r
+#define ALIGN_2MB(addr) ALIGN_POINTER(addr , (2*1024*1024))\r
+\r
+/* ARM32 and AArch64 kernel handover differ.\r
+ * x0 is set to FDT base.\r
+ * x1-x3 are reserved for future use and should be set to zero.\r
+ */\r
+typedef VOID (*LINUX_KERNEL64)(UINTN ParametersBase, UINTN Reserved0,\r
+ UINTN Reserved1, UINTN Reserved2);\r
+\r
+/* These externs are used to relocate some ASM code into Linux memory. */\r
+extern VOID *SecondariesPenStart;\r
+extern VOID *SecondariesPenEnd;\r
+extern UINTN *AsmMailboxbase;\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+PreparePlatformHardware (\r
+ VOID\r
+ )\r
+{\r
+ //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.\r
+\r
+ // Clean before Disable else the Stack gets corrupted with old data.\r
+ ArmCleanDataCache ();\r
+ ArmDisableDataCache ();\r
+ // Invalidate all the entries that might have snuck in.\r
+ ArmInvalidateDataCache ();\r
+\r
+ // Disable and invalidate the instruction cache\r
+ ArmDisableInstructionCache ();\r
+ ArmInvalidateInstructionCache ();\r
+\r
+ // Turn off MMU\r
+ ArmDisableMmu();\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+STATIC\r
+EFI_STATUS\r
+StartLinux (\r
+ IN EFI_PHYSICAL_ADDRESS LinuxImage,\r
+ IN UINTN LinuxImageSize,\r
+ IN EFI_PHYSICAL_ADDRESS FdtBlobBase,\r
+ IN UINTN FdtBlobSize,\r
+ IN UINT32 MachineType\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ LINUX_KERNEL64 LinuxKernel = (LINUX_KERNEL64)LinuxImage;\r
+\r
+ // Send msg to secondary cores to go to the kernel pen.\r
+ ArmGicSendSgiTo (PcdGet32(PcdGicDistributorBase), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE, 0x0E, PcdGet32 (PcdGicSgiIntId));\r
+\r
+ // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on\r
+ // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.\r
+ Status = ShutdownUefiBootServices ();\r
+ if(EFI_ERROR(Status)) {\r
+ DEBUG((EFI_D_ERROR,"ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status));\r
+ goto Exit;\r
+ }\r
+\r
+ // Check if the Linux Image is a uImage\r
+ if (*(UINTN*)LinuxKernel == LINUX_UIMAGE_SIGNATURE) {\r
+ // Assume the Image Entry Point is just after the uImage header (64-byte size)\r
+ LinuxKernel = (LINUX_KERNEL64)((UINTN)LinuxKernel + 64);\r
+ LinuxImageSize -= 64;\r
+ }\r
+\r
+ //\r
+ // Switch off interrupts, caches, mmu, etc\r
+ //\r
+ Status = PreparePlatformHardware ();\r
+ ASSERT_EFI_ERROR(Status);\r
+\r
+ // Register and print out performance information\r
+ PERF_END (NULL, "BDS", NULL, 0);\r
+ if (PerformanceMeasurementEnabled ()) {\r
+ PrintPerformance ();\r
+ }\r
+\r
+ //\r
+ // Start the Linux Kernel\r
+ //\r
+\r
+ // x1-x3 are reserved (set to zero) for future use.\r
+ LinuxKernel ((UINTN)FdtBlobBase, 0, 0, 0);\r
+\r
+ // Kernel should never exit\r
+ // After Life services are not provided\r
+ ASSERT(FALSE);\r
+\r
+Exit:\r
+ // Only be here if we fail to start Linux\r
+ Print (L"ERROR : Can not start the kernel. Status=0x%X\n", Status);\r
+\r
+ // Free Runtimee Memory (kernel and FDT)\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+ Start a Linux kernel from a Device Path\r
+\r
+ @param LinuxKernel Device Path to the Linux Kernel\r
+ @param Parameters Linux kernel agruments\r
+ @param Fdt Device Path to the Flat Device Tree\r
+\r
+ @retval EFI_SUCCESS All drivers have been connected\r
+ @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found\r
+ @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.\r
+\r
+**/\r
+EFI_STATUS\r
+BdsBootLinuxAtag (\r
+ IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,\r
+ IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,\r
+ IN CONST CHAR8* Arguments\r
+ )\r
+{\r
+ // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.\r
+ ASSERT(0);\r
+\r
+ return RETURN_UNSUPPORTED;\r
+}\r
+\r
+/**\r
+ Start a Linux kernel from a Device Path\r
+\r
+ @param LinuxKernel Device Path to the Linux Kernel\r
+ @param Parameters Linux kernel agruments\r
+ @param Fdt Device Path to the Flat Device Tree\r
+\r
+ @retval EFI_SUCCESS All drivers have been connected\r
+ @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found\r
+ @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.\r
+\r
+**/\r
+EFI_STATUS\r
+BdsBootLinuxFdt (\r
+ IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,\r
+ IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,\r
+ IN CONST CHAR8* Arguments,\r
+ IN EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_STATUS PenBaseStatus;\r
+ UINTN LinuxImageSize;\r
+ UINTN InitrdImageSize;\r
+ UINTN InitrdImageBaseSize;\r
+ UINTN FdtBlobSize;\r
+ EFI_PHYSICAL_ADDRESS FdtBlobBase;\r
+ UINT32 FdtMachineType;\r
+ EFI_PHYSICAL_ADDRESS LinuxImage;\r
+ EFI_PHYSICAL_ADDRESS InitrdImage;\r
+ EFI_PHYSICAL_ADDRESS InitrdImageBase;\r
+ ARM_PROCESSOR_TABLE *ArmProcessorTable;\r
+ ARM_CORE_INFO *ArmCoreInfoTable;\r
+ UINTN Index;\r
+ EFI_PHYSICAL_ADDRESS PenBase;\r
+ UINTN PenSize;\r
+ UINTN MailBoxBase;\r
+\r
+ PenBaseStatus = EFI_UNSUPPORTED;\r
+ PenSize = 0;\r
+ FdtMachineType = 0xFFFFFFFF;\r
+ InitrdImage = 0;\r
+ InitrdImageSize = 0;\r
+ InitrdImageBase = 0;\r
+ InitrdImageBaseSize = 0;\r
+\r
+ PERF_START (NULL, "BDS", NULL, 0);\r
+\r
+ //\r
+ // Load the Linux kernel from a device path\r
+ //\r
+\r
+ // Try to put the kernel at the start of RAM so as to give it access to all memory.\r
+ // If that fails fall back to try loading it within LINUX_KERNEL_MAX_OFFSET of memory start.\r
+ LinuxImage = PcdGet32(PcdSystemMemoryBase) + 0x80000;\r
+ Status = BdsLoadImage (LinuxKernelDevicePath, AllocateAddress, &LinuxImage, &LinuxImageSize);\r
+ if (EFI_ERROR(Status)) {\r
+ // Try again but give the loader more freedom of where to put the image.\r
+ LinuxImage = LINUX_KERNEL_MAX_OFFSET;\r
+ Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize);\r
+ if (EFI_ERROR(Status)) {\r
+ Print (L"ERROR: Did not find Linux kernel.\n");\r
+ return Status;\r
+ }\r
+ }\r
+ // Adjust the kernel location slightly if required. The kernel needs to be placed at start\r
+ // of memory (2MB aligned) + 0x80000.\r
+ if ((LinuxImage & LINUX_ALIGN_MASK) != LINUX_ALIGN_VAL) {\r
+ LinuxImage = (EFI_PHYSICAL_ADDRESS)CopyMem (ALIGN_2MB(LinuxImage) + 0x80000, (VOID*)(UINTN)LinuxImage, LinuxImageSize);\r
+ }\r
+\r
+ if (InitrdDevicePath) {\r
+ InitrdImageBase = LINUX_KERNEL_MAX_OFFSET;\r
+ Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize);\r
+ if (Status == EFI_OUT_OF_RESOURCES) {\r
+ Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize);\r
+ }\r
+ if (EFI_ERROR (Status)) {\r
+ Print (L"ERROR: Did not find initrd image.\n");\r
+ goto EXIT_FREE_LINUX;\r
+ }\r
+\r
+ // Check if the initrd is a uInitrd\r
+ if (*(UINTN*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) {\r
+ // Skip the 64-byte image header\r
+ InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64);\r
+ InitrdImageSize = InitrdImageBaseSize - 64;\r
+ } else {\r
+ InitrdImage = InitrdImageBase;\r
+ InitrdImageSize = InitrdImageBaseSize;\r
+ }\r
+ }\r
+\r
+ // Load the FDT binary from a device path.\r
+ // The FDT will be reloaded later to a more appropriate location for the Linux kernel.\r
+ FdtBlobBase = LINUX_KERNEL_MAX_OFFSET;\r
+ Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, &FdtBlobBase, &FdtBlobSize);\r
+ if (EFI_ERROR(Status)) {\r
+ Print (L"ERROR: Did not find Device Tree blob.\n");\r
+ goto EXIT_FREE_INITRD;\r
+ }\r
+\r
+ //\r
+ // Install secondary core pens if the Power State Coordination Interface is not supported\r
+ //\r
+ if (FeaturePcdGet (PcdArmPsciSupport) == FALSE) {\r
+ // Place Pen at the start of Linux memory. We can then tell Linux to not use this bit of memory\r
+ PenBase = LinuxImage - 0x80000;\r
+ PenSize = (UINTN)&SecondariesPenEnd - (UINTN)&SecondariesPenStart;\r
+\r
+ // Reserve the memory as RuntimeServices\r
+ PenBaseStatus = gBS->AllocatePages (AllocateAddress, EfiRuntimeServicesCode, EFI_SIZE_TO_PAGES (PenSize), &PenBase);\r
+ if (EFI_ERROR (PenBaseStatus)) {\r
+ Print (L"Warning: Failed to reserve the memory required for the secondary cores at 0x%lX, Status = %r\n", PenBase, PenBaseStatus);\r
+ // Even if there is a risk of memory corruption we carry on\r
+ }\r
+\r
+ // Put mailboxes below the pen code so we know where they are relative to code.\r
+ MailBoxBase = (UINTN)PenBase + ((UINTN)&SecondariesPenEnd - (UINTN)&SecondariesPenStart);\r
+ // Make sure this is 8 byte aligned.\r
+ if (MailBoxBase % sizeof(MailBoxBase) != 0) {\r
+ MailBoxBase += sizeof(MailBoxBase) - MailBoxBase % sizeof(MailBoxBase);\r
+ }\r
+\r
+ CopyMem ( (VOID*)(PenBase), (VOID*)&SecondariesPenStart, PenSize);\r
+\r
+ // Update the MailboxBase variable used in the pen code\r
+ *(UINTN*)(PenBase + ((UINTN)&AsmMailboxbase - (UINTN)&SecondariesPenStart)) = MailBoxBase;\r
+\r
+ // Flush caches to make sure our pen gets to mem before we free the cores.\r
+ ArmCleanDataCache();\r
+\r
+ for (Index=0; Index < gST->NumberOfTableEntries; Index++) {\r
+ // Check for correct GUID type\r
+ if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {\r
+ UINTN i;\r
+\r
+ // Get them under our control. Move from depending on 32bit reg(sys_flags) and SWI\r
+ // to 64 bit addr and WFE\r
+ ArmProcessorTable = (ARM_PROCESSOR_TABLE *)gST->ConfigurationTable[Index].VendorTable;\r
+ ArmCoreInfoTable = ArmProcessorTable->ArmCpus;\r
+\r
+ for (i = 0; i < ArmProcessorTable->NumberOfEntries; i++ ) {\r
+ // This goes into the SYSFLAGS register for the VE platform. We only have one 32bit reg to use\r
+ MmioWrite32(ArmCoreInfoTable[i].MailboxSetAddress, (UINTN)PenBase);\r
+\r
+ // So FDT can set the mailboxes correctly with the parser. These are 64bit Memory locations.\r
+ ArmCoreInfoTable[i].MailboxSetAddress = (UINTN)MailBoxBase + i*sizeof(MailBoxBase);\r
+\r
+ // Clear the mailboxes for the respective cores\r
+ *((UINTN*)(ArmCoreInfoTable[i].MailboxSetAddress)) = 0x0;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // By setting address=0 we leave the memory allocation to the function\r
+ Status = PrepareFdt (Arguments, InitrdImage, InitrdImageSize, &FdtBlobBase, &FdtBlobSize);\r
+ if (EFI_ERROR(Status)) {\r
+ Print(L"ERROR: Can not load Linux kernel with Device Tree. Status=0x%X\n", Status);\r
+ goto EXIT_FREE_FDT;\r
+ }\r
+\r
+ return StartLinux (LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize, FdtMachineType);\r
+\r
+EXIT_FREE_FDT:\r
+ if (!EFI_ERROR (PenBaseStatus)) {\r
+ gBS->FreePages (PenBase, EFI_SIZE_TO_PAGES (PenSize));\r
+ }\r
+\r
+ gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize));\r
+\r
+EXIT_FREE_INITRD:\r
+ if (InitrdDevicePath) {\r
+ gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));\r
+ }\r
+\r
+EXIT_FREE_LINUX:\r
+ gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));\r
+\r
+ return Status;\r
+}\r