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