]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPkg/Library/BdsLib/AArch64/BdsLinuxLoader.c
ArmPlatformPkg: Add the LinuxLoader.efi EFI application
[mirror_edk2.git] / ArmPkg / Library / BdsLib / AArch64 / BdsLinuxLoader.c
1 /** @file
2 *
3 * Copyright (c) 2011-2014, ARM Limited. All rights reserved.
4 *
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
9 *
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.
12 *
13 **/
14 #include <Library/ArmGicLib.h>
15 #include <Ppi/ArmMpCoreInfo.h>
16 #include <Library/IoLib.h>
17 #include <Guid/Fdt.h>
18 #include <libfdt.h>
19
20 #include "BdsInternal.h"
21 #include "BdsLinuxLoader.h"
22
23 /*
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.
28
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.
31
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.
39
40 Linux does not use any runtime services at this time, so we can let it
41 overwrite UEFI.
42 */
43
44
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))
48
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.
52 */
53 typedef VOID (*LINUX_KERNEL64)(UINTN ParametersBase, UINTN Reserved0,
54 UINTN Reserved1, UINTN Reserved2);
55
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;
60
61
62 STATIC
63 EFI_STATUS
64 PreparePlatformHardware (
65 VOID
66 )
67 {
68 //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
69
70 // Clean before Disable else the Stack gets corrupted with old data.
71 ArmCleanDataCache ();
72 ArmDisableDataCache ();
73 // Invalidate all the entries that might have snuck in.
74 ArmInvalidateDataCache ();
75
76 // Disable and invalidate the instruction cache
77 ArmDisableInstructionCache ();
78 ArmInvalidateInstructionCache ();
79
80 // Turn off MMU
81 ArmDisableMmu();
82
83 return EFI_SUCCESS;
84 }
85
86 STATIC
87 EFI_STATUS
88 StartLinux (
89 IN EFI_PHYSICAL_ADDRESS LinuxImage,
90 IN UINTN LinuxImageSize,
91 IN EFI_PHYSICAL_ADDRESS FdtBlobBase,
92 IN UINTN FdtBlobSize
93 )
94 {
95 EFI_STATUS Status;
96 LINUX_KERNEL64 LinuxKernel = (LINUX_KERNEL64)LinuxImage;
97
98 // Send msg to secondary cores to go to the kernel pen.
99 ArmGicSendSgiTo (PcdGet32(PcdGicDistributorBase), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE, 0x0E, PcdGet32 (PcdGicSgiIntId));
100
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));
106 goto Exit;
107 }
108
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;
114 }
115
116 //
117 // Switch off interrupts, caches, mmu, etc
118 //
119 Status = PreparePlatformHardware ();
120 ASSERT_EFI_ERROR(Status);
121
122 // Register and print out performance information
123 PERF_END (NULL, "BDS", NULL, 0);
124 if (PerformanceMeasurementEnabled ()) {
125 PrintPerformance ();
126 }
127
128 //
129 // Start the Linux Kernel
130 //
131
132 // x1-x3 are reserved (set to zero) for future use.
133 LinuxKernel ((UINTN)FdtBlobBase, 0, 0, 0);
134
135 // Kernel should never exit
136 // After Life services are not provided
137 ASSERT(FALSE);
138
139 Exit:
140 // Only be here if we fail to start Linux
141 Print (L"ERROR : Can not start the kernel. Status=0x%X\n", Status);
142
143 // Free Runtimee Memory (kernel and FDT)
144 return Status;
145 }
146
147
148 /**
149 Start a Linux kernel from a Device Path
150
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
154
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.
158
159 **/
160 EFI_STATUS
161 BdsBootLinuxAtag (
162 IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
163 IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
164 IN CONST CHAR8* Arguments
165 )
166 {
167 // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
168 ASSERT(0);
169
170 return RETURN_UNSUPPORTED;
171 }
172
173 /**
174 Start a Linux kernel from a Device Path
175
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
179
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.
183
184 **/
185 EFI_STATUS
186 BdsBootLinuxFdt (
187 IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
188 IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
189 IN CONST CHAR8* Arguments
190 )
191 {
192 EFI_STATUS Status;
193 EFI_STATUS PenBaseStatus;
194 UINTN LinuxImageSize;
195 UINTN InitrdImageSize;
196 UINTN InitrdImageBaseSize;
197 VOID *InstalledFdtBase;
198 UINTN FdtBlobSize;
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;
205 UINTN Index;
206 EFI_PHYSICAL_ADDRESS PenBase;
207 UINTN PenSize;
208 UINTN MailBoxBase;
209
210 PenBaseStatus = EFI_UNSUPPORTED;
211 PenSize = 0;
212 InitrdImage = 0;
213 InitrdImageSize = 0;
214 InitrdImageBase = 0;
215 InitrdImageBaseSize = 0;
216
217 PERF_START (NULL, "BDS", NULL, 0);
218
219 //
220 // Load the Linux kernel from a device path
221 //
222
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);
233 return Status;
234 }
235 }
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);
240 }
241
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);
247 }
248 if (EFI_ERROR (Status)) {
249 Print (L"ERROR: Did not find initrd image (%r).\n", Status);
250 goto EXIT_FREE_LINUX;
251 }
252
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;
258 } else {
259 InitrdImage = InitrdImageBase;
260 InitrdImageSize = InitrdImageBaseSize;
261 }
262 }
263
264 //
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.
268 //
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;
273 }
274 FdtBlobBase = (EFI_PHYSICAL_ADDRESS)InstalledFdtBase;
275 FdtBlobSize = fdt_totalsize (InstalledFdtBase);
276
277 //
278 // Install secondary core pens if the Power State Coordination Interface is not supported
279 //
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;
284
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
290 }
291
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);
297 }
298
299 CopyMem ( (VOID*)(PenBase), (VOID*)&SecondariesPenStart, PenSize);
300
301 // Update the MailboxBase variable used in the pen code
302 *(UINTN*)(PenBase + ((UINTN)&AsmMailboxbase - (UINTN)&SecondariesPenStart)) = MailBoxBase;
303
304 for (Index=0; Index < gST->NumberOfTableEntries; Index++) {
305 // Check for correct GUID type
306 if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
307 UINTN i;
308
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;
313
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);
317
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);
320
321 // Clear the mailboxes for the respective cores
322 *((UINTN*)(ArmCoreInfoTable[i].MailboxSetAddress)) = 0x0;
323 }
324 }
325 }
326 // Flush caches to make sure our pen gets to mem before we free the cores.
327 ArmCleanDataCache();
328 }
329
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);
334 goto EXIT_FREE_FDT;
335 }
336
337 return StartLinux (LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize);
338
339 EXIT_FREE_FDT:
340 if (!EFI_ERROR (PenBaseStatus)) {
341 gBS->FreePages (PenBase, EFI_SIZE_TO_PAGES (PenSize));
342 }
343
344 gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize));
345
346 EXIT_FREE_INITRD:
347 if (InitrdDevicePath) {
348 gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
349 }
350
351 EXIT_FREE_LINUX:
352 gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
353
354 return Status;
355 }