3 * Copyright (c) 2011-2014, ARM Limited. All rights reserved.
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
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.
14 #include <Library/ArmGicLib.h>
15 #include <Ppi/ArmMpCoreInfo.h>
16 #include <Library/IoLib.h>
20 #include "BdsInternal.h"
21 #include "BdsLinuxLoader.h"
24 Linux kernel booting: Look at the doc in the Kernel source :
25 Documentation/arm64/booting.txt
26 The kernel image must be placed at the start of the memory to be used by the
27 kernel (2MB aligned) + 0x80000.
29 The Device tree blob is expected to be under 2MB and be within the first 512MB
30 of kernel memory and be 2MB aligned.
32 A Flattened Device Tree (FDT) used to boot linux needs to be updated before
33 the kernel is started. It needs to indicate how secondary cores are brought up
34 and where they are waiting before loading Linux. The FDT also needs to hold
35 the correct kernel command line and filesystem RAM-disk information.
36 At the moment we do not fully support generating this FDT information at
37 runtime. A prepared FDT should be provided at boot. FDT is the only supported
38 method for booting the AArch64 Linux kernel.
40 Linux does not use any runtime services at this time, so we can let it
45 #define LINUX_ALIGN_VAL (0x080000) // 2MB + 0x80000 mask
46 #define LINUX_ALIGN_MASK (0x1FFFFF) // Bottom 21bits
47 #define ALIGN_2MB(addr) ALIGN_POINTER(addr , (2*1024*1024))
49 /* ARM32 and AArch64 kernel handover differ.
50 * x0 is set to FDT base.
51 * x1-x3 are reserved for future use and should be set to zero.
53 typedef VOID (*LINUX_KERNEL64
)(UINTN ParametersBase
, UINTN Reserved0
,
54 UINTN Reserved1
, UINTN Reserved2
);
56 /* These externs are used to relocate some ASM code into Linux memory. */
57 extern VOID
*SecondariesPenStart
;
58 extern VOID
*SecondariesPenEnd
;
59 extern UINTN
*AsmMailboxbase
;
64 PreparePlatformHardware (
68 //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
70 // Clean before Disable else the Stack gets corrupted with old data.
72 ArmDisableDataCache ();
73 // Invalidate all the entries that might have snuck in.
74 ArmInvalidateDataCache ();
76 // Disable and invalidate the instruction cache
77 ArmDisableInstructionCache ();
78 ArmInvalidateInstructionCache ();
89 IN EFI_PHYSICAL_ADDRESS LinuxImage
,
90 IN UINTN LinuxImageSize
,
91 IN EFI_PHYSICAL_ADDRESS FdtBlobBase
,
96 LINUX_KERNEL64 LinuxKernel
= (LINUX_KERNEL64
)LinuxImage
;
98 // Send msg to secondary cores to go to the kernel pen.
99 ArmGicSendSgiTo (PcdGet32(PcdGicDistributorBase
), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE
, 0x0E, PcdGet32 (PcdGicSgiIntId
));
101 // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
102 // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
103 Status
= ShutdownUefiBootServices ();
104 if(EFI_ERROR(Status
)) {
105 DEBUG((EFI_D_ERROR
,"ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status
));
109 // Check if the Linux Image is a uImage
110 if (*(UINTN
*)LinuxKernel
== LINUX_UIMAGE_SIGNATURE
) {
111 // Assume the Image Entry Point is just after the uImage header (64-byte size)
112 LinuxKernel
= (LINUX_KERNEL64
)((UINTN
)LinuxKernel
+ 64);
113 LinuxImageSize
-= 64;
117 // Switch off interrupts, caches, mmu, etc
119 Status
= PreparePlatformHardware ();
120 ASSERT_EFI_ERROR(Status
);
122 // Register and print out performance information
123 PERF_END (NULL
, "BDS", NULL
, 0);
124 if (PerformanceMeasurementEnabled ()) {
129 // Start the Linux Kernel
132 // x1-x3 are reserved (set to zero) for future use.
133 LinuxKernel ((UINTN
)FdtBlobBase
, 0, 0, 0);
135 // Kernel should never exit
136 // After Life services are not provided
140 // Only be here if we fail to start Linux
141 Print (L
"ERROR : Can not start the kernel. Status=0x%X\n", Status
);
143 // Free Runtimee Memory (kernel and FDT)
149 Start a Linux kernel from a Device Path
151 @param LinuxKernel Device Path to the Linux Kernel
152 @param Parameters Linux kernel agruments
153 @param Fdt Device Path to the Flat Device Tree
155 @retval EFI_SUCCESS All drivers have been connected
156 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found
157 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.
162 IN EFI_DEVICE_PATH_PROTOCOL
* LinuxKernelDevicePath
,
163 IN EFI_DEVICE_PATH_PROTOCOL
* InitrdDevicePath
,
164 IN CONST CHAR8
* Arguments
167 // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
170 return RETURN_UNSUPPORTED
;
174 Start a Linux kernel from a Device Path
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
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.
187 IN EFI_DEVICE_PATH_PROTOCOL
* LinuxKernelDevicePath
,
188 IN EFI_DEVICE_PATH_PROTOCOL
* InitrdDevicePath
,
189 IN CONST CHAR8
* Arguments
193 EFI_STATUS PenBaseStatus
;
194 UINTN LinuxImageSize
;
195 UINTN InitrdImageSize
;
196 UINTN InitrdImageBaseSize
;
197 VOID
*InstalledFdtBase
;
199 EFI_PHYSICAL_ADDRESS FdtBlobBase
;
200 EFI_PHYSICAL_ADDRESS LinuxImage
;
201 EFI_PHYSICAL_ADDRESS InitrdImage
;
202 EFI_PHYSICAL_ADDRESS InitrdImageBase
;
203 ARM_PROCESSOR_TABLE
*ArmProcessorTable
;
204 ARM_CORE_INFO
*ArmCoreInfoTable
;
206 EFI_PHYSICAL_ADDRESS PenBase
;
210 PenBaseStatus
= EFI_UNSUPPORTED
;
215 InitrdImageBaseSize
= 0;
217 PERF_START (NULL
, "BDS", NULL
, 0);
220 // Load the Linux kernel from a device path
223 // Try to put the kernel at the start of RAM so as to give it access to all memory.
224 // If that fails fall back to try loading it within LINUX_KERNEL_MAX_OFFSET of memory start.
225 LinuxImage
= PcdGet64 (PcdSystemMemoryBase
) + 0x80000;
226 Status
= BdsLoadImage (LinuxKernelDevicePath
, AllocateAddress
, &LinuxImage
, &LinuxImageSize
);
227 if (EFI_ERROR(Status
)) {
228 // Try again but give the loader more freedom of where to put the image.
229 LinuxImage
= LINUX_KERNEL_MAX_OFFSET
;
230 Status
= BdsLoadImage (LinuxKernelDevicePath
, AllocateMaxAddress
, &LinuxImage
, &LinuxImageSize
);
231 if (EFI_ERROR(Status
)) {
232 Print (L
"ERROR: Did not find Linux kernel (%r).\n", Status
);
236 // Adjust the kernel location slightly if required. The kernel needs to be placed at start
237 // of memory (2MB aligned) + 0x80000.
238 if ((LinuxImage
& LINUX_ALIGN_MASK
) != LINUX_ALIGN_VAL
) {
239 LinuxImage
= (EFI_PHYSICAL_ADDRESS
)CopyMem (ALIGN_2MB(LinuxImage
) + 0x80000, (VOID
*)(UINTN
)LinuxImage
, LinuxImageSize
);
242 if (InitrdDevicePath
) {
243 InitrdImageBase
= LINUX_KERNEL_MAX_OFFSET
;
244 Status
= BdsLoadImage (InitrdDevicePath
, AllocateMaxAddress
, &InitrdImageBase
, &InitrdImageBaseSize
);
245 if (Status
== EFI_OUT_OF_RESOURCES
) {
246 Status
= BdsLoadImage (InitrdDevicePath
, AllocateAnyPages
, &InitrdImageBase
, &InitrdImageBaseSize
);
248 if (EFI_ERROR (Status
)) {
249 Print (L
"ERROR: Did not find initrd image (%r).\n", Status
);
250 goto EXIT_FREE_LINUX
;
253 // Check if the initrd is a uInitrd
254 if (*(UINTN
*)((UINTN
)InitrdImageBase
) == LINUX_UIMAGE_SIGNATURE
) {
255 // Skip the 64-byte image header
256 InitrdImage
= (EFI_PHYSICAL_ADDRESS
)((UINTN
)InitrdImageBase
+ 64);
257 InitrdImageSize
= InitrdImageBaseSize
- 64;
259 InitrdImage
= InitrdImageBase
;
260 InitrdImageSize
= InitrdImageBaseSize
;
265 // Get the FDT from the Configuration Table.
266 // The FDT will be reloaded in PrepareFdt() to a more appropriate
267 // location for the Linux Kernel.
269 Status
= EfiGetSystemConfigurationTable (&gFdtTableGuid
, &InstalledFdtBase
);
270 if (EFI_ERROR (Status
)) {
271 Print (L
"ERROR: Did not get the Device Tree blob (%r).\n", Status
);
272 goto EXIT_FREE_INITRD
;
274 FdtBlobBase
= (EFI_PHYSICAL_ADDRESS
)InstalledFdtBase
;
275 FdtBlobSize
= fdt_totalsize (InstalledFdtBase
);
278 // Install secondary core pens if the Power State Coordination Interface is not supported
280 if (FeaturePcdGet (PcdArmLinuxSpinTable
)) {
281 // Place Pen at the start of Linux memory. We can then tell Linux to not use this bit of memory
282 PenBase
= LinuxImage
- 0x80000;
283 PenSize
= (UINTN
)&SecondariesPenEnd
- (UINTN
)&SecondariesPenStart
;
285 // Reserve the memory as RuntimeServices
286 PenBaseStatus
= gBS
->AllocatePages (AllocateAddress
, EfiRuntimeServicesCode
, EFI_SIZE_TO_PAGES (PenSize
), &PenBase
);
287 if (EFI_ERROR (PenBaseStatus
)) {
288 Print (L
"Warning: Failed to reserve the memory required for the secondary cores at 0x%lX, Status = %r\n", PenBase
, PenBaseStatus
);
289 // Even if there is a risk of memory corruption we carry on
292 // Put mailboxes below the pen code so we know where they are relative to code.
293 MailBoxBase
= (UINTN
)PenBase
+ ((UINTN
)&SecondariesPenEnd
- (UINTN
)&SecondariesPenStart
);
294 // Make sure this is 8 byte aligned.
295 if (MailBoxBase
% sizeof(MailBoxBase
) != 0) {
296 MailBoxBase
+= sizeof(MailBoxBase
) - MailBoxBase
% sizeof(MailBoxBase
);
299 CopyMem ( (VOID
*)(PenBase
), (VOID
*)&SecondariesPenStart
, PenSize
);
301 // Update the MailboxBase variable used in the pen code
302 *(UINTN
*)(PenBase
+ ((UINTN
)&AsmMailboxbase
- (UINTN
)&SecondariesPenStart
)) = MailBoxBase
;
304 for (Index
=0; Index
< gST
->NumberOfTableEntries
; Index
++) {
305 // Check for correct GUID type
306 if (CompareGuid (&gArmMpCoreInfoGuid
, &(gST
->ConfigurationTable
[Index
].VendorGuid
))) {
309 // Get them under our control. Move from depending on 32bit reg(sys_flags) and SWI
310 // to 64 bit addr and WFE
311 ArmProcessorTable
= (ARM_PROCESSOR_TABLE
*)gST
->ConfigurationTable
[Index
].VendorTable
;
312 ArmCoreInfoTable
= ArmProcessorTable
->ArmCpus
;
314 for (i
= 0; i
< ArmProcessorTable
->NumberOfEntries
; i
++ ) {
315 // This goes into the SYSFLAGS register for the VE platform. We only have one 32bit reg to use
316 MmioWrite32(ArmCoreInfoTable
[i
].MailboxSetAddress
, (UINTN
)PenBase
);
318 // So FDT can set the mailboxes correctly with the parser. These are 64bit Memory locations.
319 ArmCoreInfoTable
[i
].MailboxSetAddress
= (UINTN
)MailBoxBase
+ i
*sizeof(MailBoxBase
);
321 // Clear the mailboxes for the respective cores
322 *((UINTN
*)(ArmCoreInfoTable
[i
].MailboxSetAddress
)) = 0x0;
326 // Flush caches to make sure our pen gets to mem before we free the cores.
330 // By setting address=0 we leave the memory allocation to the function
331 Status
= PrepareFdt (Arguments
, InitrdImage
, InitrdImageSize
, &FdtBlobBase
, &FdtBlobSize
);
332 if (EFI_ERROR(Status
)) {
333 Print(L
"ERROR: Can not load Linux kernel with Device Tree. Status=0x%X\n", Status
);
337 return StartLinux (LinuxImage
, LinuxImageSize
, FdtBlobBase
, FdtBlobSize
);
340 if (!EFI_ERROR (PenBaseStatus
)) {
341 gBS
->FreePages (PenBase
, EFI_SIZE_TO_PAGES (PenSize
));
344 gBS
->FreePages (FdtBlobBase
, EFI_SIZE_TO_PAGES (FdtBlobSize
));
347 if (InitrdDevicePath
) {
348 gBS
->FreePages (InitrdImageBase
, EFI_SIZE_TO_PAGES (InitrdImageBaseSize
));
352 gBS
->FreePages (LinuxImage
, EFI_SIZE_TO_PAGES (LinuxImageSize
));