3 * Copyright (c) 2011-2013, 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>
18 #include "BdsInternal.h"
19 #include "BdsLinuxLoader.h"
22 Linux kernel booting: Look at the doc in the Kernel source :
23 Documentation/arm64/booting.txt
24 The kernel image must be placed at the start of the memory to be used by the
25 kernel (2MB aligned) + 0x80000.
27 The Device tree blob is expected to be under 2MB and be within the first 512MB
28 of kernel memory and be 2MB aligned.
30 A Flattened Device Tree (FDT) used to boot linux needs to be updated before
31 the kernel is started. It needs to indicate how secondary cores are brought up
32 and where they are waiting before loading Linux. The FDT also needs to hold
33 the correct kernel command line and filesystem RAM-disk information.
34 At the moment we do not fully support generating this FDT information at
35 runtime. A prepared FDT should be provided at boot. FDT is the only supported
36 method for booting the AArch64 Linux kernel.
38 Linux does not use any runtime services at this time, so we can let it
43 #define LINUX_ALIGN_VAL (0x080000) // 2MB + 0x80000 mask
44 #define LINUX_ALIGN_MASK (0x1FFFFF) // Bottom 21bits
45 #define ALIGN_2MB(addr) ALIGN_POINTER(addr , (2*1024*1024))
47 /* ARM32 and AArch64 kernel handover differ.
48 * x0 is set to FDT base.
49 * x1-x3 are reserved for future use and should be set to zero.
51 typedef VOID (*LINUX_KERNEL64
)(UINTN ParametersBase
, UINTN Reserved0
,
52 UINTN Reserved1
, UINTN Reserved2
);
54 /* These externs are used to relocate some ASM code into Linux memory. */
55 extern VOID
*SecondariesPenStart
;
56 extern VOID
*SecondariesPenEnd
;
57 extern UINTN
*AsmMailboxbase
;
62 PreparePlatformHardware (
66 //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
68 // Clean before Disable else the Stack gets corrupted with old data.
70 ArmDisableDataCache ();
71 // Invalidate all the entries that might have snuck in.
72 ArmInvalidateDataCache ();
74 // Disable and invalidate the instruction cache
75 ArmDisableInstructionCache ();
76 ArmInvalidateInstructionCache ();
87 IN EFI_PHYSICAL_ADDRESS LinuxImage
,
88 IN UINTN LinuxImageSize
,
89 IN EFI_PHYSICAL_ADDRESS FdtBlobBase
,
95 LINUX_KERNEL64 LinuxKernel
= (LINUX_KERNEL64
)LinuxImage
;
97 // Send msg to secondary cores to go to the kernel pen.
98 ArmGicSendSgiTo (PcdGet32(PcdGicDistributorBase
), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE
, 0x0E, PcdGet32 (PcdGicSgiIntId
));
100 // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
101 // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
102 Status
= ShutdownUefiBootServices ();
103 if(EFI_ERROR(Status
)) {
104 DEBUG((EFI_D_ERROR
,"ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status
));
108 // Check if the Linux Image is a uImage
109 if (*(UINTN
*)LinuxKernel
== LINUX_UIMAGE_SIGNATURE
) {
110 // Assume the Image Entry Point is just after the uImage header (64-byte size)
111 LinuxKernel
= (LINUX_KERNEL64
)((UINTN
)LinuxKernel
+ 64);
112 LinuxImageSize
-= 64;
116 // Switch off interrupts, caches, mmu, etc
118 Status
= PreparePlatformHardware ();
119 ASSERT_EFI_ERROR(Status
);
121 // Register and print out performance information
122 PERF_END (NULL
, "BDS", NULL
, 0);
123 if (PerformanceMeasurementEnabled ()) {
128 // Start the Linux Kernel
131 // x1-x3 are reserved (set to zero) for future use.
132 LinuxKernel ((UINTN
)FdtBlobBase
, 0, 0, 0);
134 // Kernel should never exit
135 // After Life services are not provided
139 // Only be here if we fail to start Linux
140 Print (L
"ERROR : Can not start the kernel. Status=0x%X\n", Status
);
142 // Free Runtimee Memory (kernel and FDT)
148 Start a Linux kernel from a Device Path
150 @param LinuxKernel Device Path to the Linux Kernel
151 @param Parameters Linux kernel agruments
152 @param Fdt Device Path to the Flat Device Tree
154 @retval EFI_SUCCESS All drivers have been connected
155 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found
156 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.
161 IN EFI_DEVICE_PATH_PROTOCOL
* LinuxKernelDevicePath
,
162 IN EFI_DEVICE_PATH_PROTOCOL
* InitrdDevicePath
,
163 IN CONST CHAR8
* Arguments
166 // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
169 return RETURN_UNSUPPORTED
;
173 Start a Linux kernel from a Device Path
175 @param LinuxKernel Device Path to the Linux Kernel
176 @param Parameters Linux kernel agruments
177 @param Fdt Device Path to the Flat Device Tree
179 @retval EFI_SUCCESS All drivers have been connected
180 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found
181 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.
186 IN EFI_DEVICE_PATH_PROTOCOL
* LinuxKernelDevicePath
,
187 IN EFI_DEVICE_PATH_PROTOCOL
* InitrdDevicePath
,
188 IN CONST CHAR8
* Arguments
,
189 IN EFI_DEVICE_PATH_PROTOCOL
* FdtDevicePath
193 EFI_STATUS PenBaseStatus
;
194 UINTN LinuxImageSize
;
195 UINTN InitrdImageSize
;
196 UINTN InitrdImageBaseSize
;
198 EFI_PHYSICAL_ADDRESS FdtBlobBase
;
199 UINT32 FdtMachineType
;
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
;
212 FdtMachineType
= 0xFFFFFFFF;
216 InitrdImageBaseSize
= 0;
218 PERF_START (NULL
, "BDS", NULL
, 0);
221 // Load the Linux kernel from a device path
224 // Try to put the kernel at the start of RAM so as to give it access to all memory.
225 // If that fails fall back to try loading it within LINUX_KERNEL_MAX_OFFSET of memory start.
226 LinuxImage
= PcdGet32(PcdSystemMemoryBase
) + 0x80000;
227 Status
= BdsLoadImage (LinuxKernelDevicePath
, AllocateAddress
, &LinuxImage
, &LinuxImageSize
);
228 if (EFI_ERROR(Status
)) {
229 // Try again but give the loader more freedom of where to put the image.
230 LinuxImage
= LINUX_KERNEL_MAX_OFFSET
;
231 Status
= BdsLoadImage (LinuxKernelDevicePath
, AllocateMaxAddress
, &LinuxImage
, &LinuxImageSize
);
232 if (EFI_ERROR(Status
)) {
233 Print (L
"ERROR: Did not find Linux kernel.\n");
237 // Adjust the kernel location slightly if required. The kernel needs to be placed at start
238 // of memory (2MB aligned) + 0x80000.
239 if ((LinuxImage
& LINUX_ALIGN_MASK
) != LINUX_ALIGN_VAL
) {
240 LinuxImage
= (EFI_PHYSICAL_ADDRESS
)CopyMem (ALIGN_2MB(LinuxImage
) + 0x80000, (VOID
*)(UINTN
)LinuxImage
, LinuxImageSize
);
243 if (InitrdDevicePath
) {
244 InitrdImageBase
= LINUX_KERNEL_MAX_OFFSET
;
245 Status
= BdsLoadImage (InitrdDevicePath
, AllocateMaxAddress
, &InitrdImageBase
, &InitrdImageBaseSize
);
246 if (Status
== EFI_OUT_OF_RESOURCES
) {
247 Status
= BdsLoadImage (InitrdDevicePath
, AllocateAnyPages
, &InitrdImageBase
, &InitrdImageBaseSize
);
249 if (EFI_ERROR (Status
)) {
250 Print (L
"ERROR: Did not find initrd image.\n");
251 goto EXIT_FREE_LINUX
;
254 // Check if the initrd is a uInitrd
255 if (*(UINTN
*)((UINTN
)InitrdImageBase
) == LINUX_UIMAGE_SIGNATURE
) {
256 // Skip the 64-byte image header
257 InitrdImage
= (EFI_PHYSICAL_ADDRESS
)((UINTN
)InitrdImageBase
+ 64);
258 InitrdImageSize
= InitrdImageBaseSize
- 64;
260 InitrdImage
= InitrdImageBase
;
261 InitrdImageSize
= InitrdImageBaseSize
;
265 // Load the FDT binary from a device path.
266 // The FDT will be reloaded later to a more appropriate location for the Linux kernel.
267 FdtBlobBase
= LINUX_KERNEL_MAX_OFFSET
;
268 Status
= BdsLoadImage (FdtDevicePath
, AllocateMaxAddress
, &FdtBlobBase
, &FdtBlobSize
);
269 if (EFI_ERROR(Status
)) {
270 Print (L
"ERROR: Did not find Device Tree blob.\n");
271 goto EXIT_FREE_INITRD
;
275 // Install secondary core pens if the Power State Coordination Interface is not supported
277 if (FeaturePcdGet (PcdArmPsciSupport
) == FALSE
) {
278 // Place Pen at the start of Linux memory. We can then tell Linux to not use this bit of memory
279 PenBase
= LinuxImage
- 0x80000;
280 PenSize
= (UINTN
)&SecondariesPenEnd
- (UINTN
)&SecondariesPenStart
;
282 // Reserve the memory as RuntimeServices
283 PenBaseStatus
= gBS
->AllocatePages (AllocateAddress
, EfiRuntimeServicesCode
, EFI_SIZE_TO_PAGES (PenSize
), &PenBase
);
284 if (EFI_ERROR (PenBaseStatus
)) {
285 Print (L
"Warning: Failed to reserve the memory required for the secondary cores at 0x%lX, Status = %r\n", PenBase
, PenBaseStatus
);
286 // Even if there is a risk of memory corruption we carry on
289 // Put mailboxes below the pen code so we know where they are relative to code.
290 MailBoxBase
= (UINTN
)PenBase
+ ((UINTN
)&SecondariesPenEnd
- (UINTN
)&SecondariesPenStart
);
291 // Make sure this is 8 byte aligned.
292 if (MailBoxBase
% sizeof(MailBoxBase
) != 0) {
293 MailBoxBase
+= sizeof(MailBoxBase
) - MailBoxBase
% sizeof(MailBoxBase
);
296 CopyMem ( (VOID
*)(PenBase
), (VOID
*)&SecondariesPenStart
, PenSize
);
298 // Update the MailboxBase variable used in the pen code
299 *(UINTN
*)(PenBase
+ ((UINTN
)&AsmMailboxbase
- (UINTN
)&SecondariesPenStart
)) = MailBoxBase
;
301 for (Index
=0; Index
< gST
->NumberOfTableEntries
; Index
++) {
302 // Check for correct GUID type
303 if (CompareGuid (&gArmMpCoreInfoGuid
, &(gST
->ConfigurationTable
[Index
].VendorGuid
))) {
306 // Get them under our control. Move from depending on 32bit reg(sys_flags) and SWI
307 // to 64 bit addr and WFE
308 ArmProcessorTable
= (ARM_PROCESSOR_TABLE
*)gST
->ConfigurationTable
[Index
].VendorTable
;
309 ArmCoreInfoTable
= ArmProcessorTable
->ArmCpus
;
311 for (i
= 0; i
< ArmProcessorTable
->NumberOfEntries
; i
++ ) {
312 // This goes into the SYSFLAGS register for the VE platform. We only have one 32bit reg to use
313 MmioWrite32(ArmCoreInfoTable
[i
].MailboxSetAddress
, (UINTN
)PenBase
);
315 // So FDT can set the mailboxes correctly with the parser. These are 64bit Memory locations.
316 ArmCoreInfoTable
[i
].MailboxSetAddress
= (UINTN
)MailBoxBase
+ i
*sizeof(MailBoxBase
);
318 // Clear the mailboxes for the respective cores
319 *((UINTN
*)(ArmCoreInfoTable
[i
].MailboxSetAddress
)) = 0x0;
323 // Flush caches to make sure our pen gets to mem before we free the cores.
327 // By setting address=0 we leave the memory allocation to the function
328 Status
= PrepareFdt (Arguments
, InitrdImage
, InitrdImageSize
, &FdtBlobBase
, &FdtBlobSize
);
329 if (EFI_ERROR(Status
)) {
330 Print(L
"ERROR: Can not load Linux kernel with Device Tree. Status=0x%X\n", Status
);
334 return StartLinux (LinuxImage
, LinuxImageSize
, FdtBlobBase
, FdtBlobSize
, FdtMachineType
);
337 if (!EFI_ERROR (PenBaseStatus
)) {
338 gBS
->FreePages (PenBase
, EFI_SIZE_TO_PAGES (PenSize
));
341 gBS
->FreePages (FdtBlobBase
, EFI_SIZE_TO_PAGES (FdtBlobSize
));
344 if (InitrdDevicePath
) {
345 gBS
->FreePages (InitrdImageBase
, EFI_SIZE_TO_PAGES (InitrdImageBaseSize
));
349 gBS
->FreePages (LinuxImage
, EFI_SIZE_TO_PAGES (LinuxImageSize
));