3 * Copyright (c) 2011-2015, 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.
15 #include <Library/ArmLib.h>
16 #include <Library/ArmGicLib.h>
17 #include <Library/IoLib.h>
18 #include <Library/PcdLib.h>
20 #include <Ppi/ArmMpCoreInfo.h>
24 #include "LinuxLoader.h"
27 Linux kernel booting: Look at the doc in the Kernel source :
28 Documentation/arm64/booting.txt
29 The kernel image must be placed at the start of the memory to be used by the
30 kernel (2MB aligned) + 0x80000.
32 The Device tree blob is expected to be under 2MB and be within the first 512MB
33 of kernel memory and be 2MB aligned.
35 A Flattened Device Tree (FDT) used to boot linux needs to be updated before
36 the kernel is started. It needs to indicate how secondary cores are brought up
37 and where they are waiting before loading Linux. The FDT also needs to hold
38 the correct kernel command line and filesystem RAM-disk information.
39 At the moment we do not fully support generating this FDT information at
40 runtime. A prepared FDT should be provided at boot. FDT is the only supported
41 method for booting the AArch64 Linux kernel.
43 Linux does not use any runtime services at this time, so we can let it
48 #define LINUX_ALIGN_VAL (0x080000) // 2MB + 0x80000 mask
49 #define LINUX_ALIGN_MASK (0x1FFFFF) // Bottom 21bits
50 #define ALIGN_2MB(addr) ALIGN_POINTER(addr , (2*1024*1024))
52 /* ARM32 and AArch64 kernel handover differ.
53 * x0 is set to FDT base.
54 * x1-x3 are reserved for future use and should be set to zero.
56 typedef VOID (*LINUX_KERNEL64
)(UINTN ParametersBase
, UINTN Reserved0
,
57 UINTN Reserved1
, UINTN Reserved2
);
59 /* These externs are used to relocate some ASM code into Linux memory. */
60 extern VOID
*SecondariesPenStart
;
61 extern VOID
*SecondariesPenEnd
;
62 extern UINTN
*AsmMailboxbase
;
67 PreparePlatformHardware (
71 //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
73 // Clean before Disable else the Stack gets corrupted with old data.
75 ArmDisableDataCache ();
76 // Invalidate all the entries that might have snuck in.
77 ArmInvalidateDataCache ();
79 // Disable and invalidate the instruction cache
80 ArmDisableInstructionCache ();
81 ArmInvalidateInstructionCache ();
90 IN EFI_PHYSICAL_ADDRESS LinuxImage
,
91 IN UINTN LinuxImageSize
,
92 IN EFI_PHYSICAL_ADDRESS FdtBlobBase
,
97 LINUX_KERNEL64 LinuxKernel
= (LINUX_KERNEL64
)LinuxImage
;
99 // Send msg to secondary cores to go to the kernel pen.
100 ArmGicSendSgiTo (PcdGet64 (PcdGicDistributorBase
), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE
, 0x0E, PcdGet32 (PcdGicSgiIntId
));
102 // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
103 // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
104 Status
= ShutdownUefiBootServices ();
105 if (EFI_ERROR (Status
)) {
106 DEBUG ((EFI_D_ERROR
, "ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status
));
110 // Check if the Linux Image is a uImage
111 if (*(UINTN
*)LinuxKernel
== LINUX_UIMAGE_SIGNATURE
) {
112 // Assume the Image Entry Point is just after the uImage header (64-byte size)
113 LinuxKernel
= (LINUX_KERNEL64
)((UINTN
)LinuxKernel
+ 64);
114 LinuxImageSize
-= 64;
118 // Switch off interrupts, caches, mmu, etc
120 PreparePlatformHardware ();
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
139 // We cannot recover the execution at this stage
144 Start a Linux kernel from a Device Path
146 @param SystemMemoryBase Base of the system memory
147 @param LinuxKernel Device Path to the Linux Kernel
148 @param Parameters Linux kernel arguments
149 @param Fdt Device Path to the Flat Device Tree
150 @param MachineType ARM machine type value
152 @retval EFI_SUCCESS All drivers have been connected
153 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found
154 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.
155 @retval RETURN_UNSUPPORTED ATAG is not support by this architecture
160 IN EFI_PHYSICAL_ADDRESS SystemMemoryBase
,
161 IN EFI_DEVICE_PATH_PROTOCOL
* LinuxKernelDevicePath
,
162 IN EFI_DEVICE_PATH_PROTOCOL
* InitrdDevicePath
,
163 IN CONST CHAR8
* CommandLineArguments
,
167 // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
170 return EFI_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_PHYSICAL_ADDRESS SystemMemoryBase
,
188 IN EFI_DEVICE_PATH_PROTOCOL
* LinuxKernelDevicePath
,
189 IN EFI_DEVICE_PATH_PROTOCOL
* InitrdDevicePath
,
190 IN EFI_DEVICE_PATH_PROTOCOL
* FdtDevicePath
,
191 IN CONST CHAR8
* Arguments
195 EFI_STATUS PenBaseStatus
;
196 UINTN LinuxImageSize
;
197 UINTN InitrdImageSize
;
198 UINTN InitrdImageBaseSize
;
199 VOID
*InstalledFdtBase
;
201 EFI_PHYSICAL_ADDRESS FdtBlobBase
;
202 EFI_PHYSICAL_ADDRESS LinuxImage
;
203 EFI_PHYSICAL_ADDRESS InitrdImage
;
204 EFI_PHYSICAL_ADDRESS InitrdImageBase
;
205 ARM_PROCESSOR_TABLE
*ArmProcessorTable
;
206 ARM_CORE_INFO
*ArmCoreInfoTable
;
208 EFI_PHYSICAL_ADDRESS PenBase
;
212 PenBaseStatus
= EFI_UNSUPPORTED
;
217 InitrdImageBaseSize
= 0;
219 PERF_START (NULL
, "BDS", NULL
, 0);
222 // Load the Linux kernel from a device path
225 // Try to put the kernel at the start of RAM so as to give it access to all memory.
226 // If that fails fall back to try loading it within LINUX_KERNEL_MAX_OFFSET of memory start.
227 LinuxImage
= SystemMemoryBase
+ 0x80000;
228 Status
= BdsLoadImage (LinuxKernelDevicePath
, AllocateAddress
, &LinuxImage
, &LinuxImageSize
);
229 if (EFI_ERROR (Status
)) {
230 // Try again but give the loader more freedom of where to put the image.
231 LinuxImage
= LINUX_KERNEL_MAX_OFFSET
;
232 Status
= BdsLoadImage (LinuxKernelDevicePath
, AllocateMaxAddress
, &LinuxImage
, &LinuxImageSize
);
233 if (EFI_ERROR (Status
)) {
234 Print (L
"ERROR: Did not find Linux kernel (%r).\n", Status
);
238 // Adjust the kernel location slightly if required. The kernel needs to be placed at start
239 // of memory (2MB aligned) + 0x80000.
240 if ((LinuxImage
& LINUX_ALIGN_MASK
) != LINUX_ALIGN_VAL
) {
241 LinuxImage
= (EFI_PHYSICAL_ADDRESS
)CopyMem (ALIGN_2MB (LinuxImage
) + 0x80000, (VOID
*)(UINTN
)LinuxImage
, LinuxImageSize
);
244 if (InitrdDevicePath
) {
245 InitrdImageBase
= LINUX_KERNEL_MAX_OFFSET
;
246 Status
= BdsLoadImage (InitrdDevicePath
, AllocateMaxAddress
, &InitrdImageBase
, &InitrdImageBaseSize
);
247 if (Status
== EFI_OUT_OF_RESOURCES
) {
248 Status
= BdsLoadImage (InitrdDevicePath
, AllocateAnyPages
, &InitrdImageBase
, &InitrdImageBaseSize
);
250 if (EFI_ERROR (Status
)) {
251 Print (L
"ERROR: Did not find initrd image (%r).\n", Status
);
252 goto EXIT_FREE_LINUX
;
255 // Check if the initrd is a uInitrd
256 if (*(UINTN
*)((UINTN
)InitrdImageBase
) == LINUX_UIMAGE_SIGNATURE
) {
257 // Skip the 64-byte image header
258 InitrdImage
= (EFI_PHYSICAL_ADDRESS
)((UINTN
)InitrdImageBase
+ 64);
259 InitrdImageSize
= InitrdImageBaseSize
- 64;
261 InitrdImage
= InitrdImageBase
;
262 InitrdImageSize
= InitrdImageBaseSize
;
266 if (FdtDevicePath
== NULL
) {
268 // Get the FDT from the Configuration Table.
269 // The FDT will be reloaded in PrepareFdt() to a more appropriate
270 // location for the Linux Kernel.
272 Status
= EfiGetSystemConfigurationTable (&gFdtTableGuid
, &InstalledFdtBase
);
273 if (EFI_ERROR (Status
)) {
274 Print (L
"ERROR: Did not get the Device Tree blob (%r).\n", Status
);
275 goto EXIT_FREE_INITRD
;
277 FdtBlobBase
= (EFI_PHYSICAL_ADDRESS
)InstalledFdtBase
;
278 FdtBlobSize
= fdt_totalsize (InstalledFdtBase
);
281 // FDT device path explicitly defined. The FDT is relocated later to a
282 // more appropriate location for the Linux kernel.
284 FdtBlobBase
= LINUX_KERNEL_MAX_OFFSET
;
285 Status
= BdsLoadImage (FdtDevicePath
, AllocateMaxAddress
, &FdtBlobBase
, &FdtBlobSize
);
286 if (EFI_ERROR (Status
)) {
287 Print (L
"ERROR: Did not find Device Tree blob (%r).\n", Status
);
288 goto EXIT_FREE_INITRD
;
293 // Install secondary core pens if the Power State Coordination Interface is not supported
295 if (FeaturePcdGet (PcdArmLinuxSpinTable
)) {
296 // Place Pen at the start of Linux memory. We can then tell Linux to not use this bit of memory
297 PenBase
= LinuxImage
- 0x80000;
298 PenSize
= (UINTN
)&SecondariesPenEnd
- (UINTN
)&SecondariesPenStart
;
300 // Reserve the memory as RuntimeServices
301 PenBaseStatus
= gBS
->AllocatePages (AllocateAddress
, EfiRuntimeServicesCode
, EFI_SIZE_TO_PAGES (PenSize
), &PenBase
);
302 if (EFI_ERROR (PenBaseStatus
)) {
303 Print (L
"Warning: Failed to reserve the memory required for the secondary cores at 0x%lX, Status = %r\n", PenBase
, PenBaseStatus
);
304 // Even if there is a risk of memory corruption we carry on
307 // Put mailboxes below the pen code so we know where they are relative to code.
308 MailBoxBase
= (UINTN
)PenBase
+ ((UINTN
)&SecondariesPenEnd
- (UINTN
)&SecondariesPenStart
);
309 // Make sure this is 8 byte aligned.
310 if (MailBoxBase
% sizeof (MailBoxBase
) != 0) {
311 MailBoxBase
+= sizeof (MailBoxBase
) - MailBoxBase
% sizeof (MailBoxBase
);
314 CopyMem ( (VOID
*)(PenBase
), (VOID
*)&SecondariesPenStart
, PenSize
);
316 // Update the MailboxBase variable used in the pen code
317 *(UINTN
*)(PenBase
+ ((UINTN
)&AsmMailboxbase
- (UINTN
)&SecondariesPenStart
)) = MailBoxBase
;
319 for (Index
= 0; Index
< gST
->NumberOfTableEntries
; Index
++) {
320 // Check for correct GUID type
321 if (CompareGuid (&gArmMpCoreInfoGuid
, &(gST
->ConfigurationTable
[Index
].VendorGuid
))) {
324 // Get them under our control. Move from depending on 32bit reg(sys_flags) and SWI
325 // to 64 bit addr and WFE
326 ArmProcessorTable
= (ARM_PROCESSOR_TABLE
*)gST
->ConfigurationTable
[Index
].VendorTable
;
327 ArmCoreInfoTable
= ArmProcessorTable
->ArmCpus
;
329 for (i
= 0; i
< ArmProcessorTable
->NumberOfEntries
; i
++ ) {
330 // This goes into the SYSFLAGS register for the VE platform. We only have one 32bit reg to use
331 MmioWrite32 (ArmCoreInfoTable
[i
].MailboxSetAddress
, (UINTN
)PenBase
);
333 // So FDT can set the mailboxes correctly with the parser. These are 64bit Memory locations.
334 ArmCoreInfoTable
[i
].MailboxSetAddress
= (UINTN
)MailBoxBase
+ i
*sizeof (MailBoxBase
);
336 // Clear the mailboxes for the respective cores
337 *((UINTN
*)(ArmCoreInfoTable
[i
].MailboxSetAddress
)) = 0x0;
341 // Flush caches to make sure our pen gets to mem before we free the cores.
342 ArmCleanDataCache ();
345 // By setting address=0 we leave the memory allocation to the function
346 Status
= PrepareFdt (SystemMemoryBase
, Arguments
, InitrdImage
, InitrdImageSize
, &FdtBlobBase
, &FdtBlobSize
);
347 if (EFI_ERROR (Status
)) {
348 Print (L
"ERROR: Can not load Linux kernel with Device Tree. Status=0x%X\n", Status
);
352 return StartLinux (LinuxImage
, LinuxImageSize
, FdtBlobBase
, FdtBlobSize
);
355 if (!EFI_ERROR (PenBaseStatus
)) {
356 gBS
->FreePages (PenBase
, EFI_SIZE_TO_PAGES (PenSize
));
359 gBS
->FreePages (FdtBlobBase
, EFI_SIZE_TO_PAGES (FdtBlobSize
));
362 if (InitrdDevicePath
) {
363 gBS
->FreePages (InitrdImageBase
, EFI_SIZE_TO_PAGES (InitrdImageBaseSize
));
367 gBS
->FreePages (LinuxImage
, EFI_SIZE_TO_PAGES (LinuxImageSize
));