e094413a53b83db914032619abf67d34b734dbf6
[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
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 )
92 {
93 EFI_STATUS Status;
94 LINUX_KERNEL64 LinuxKernel = (LINUX_KERNEL64)LinuxImage;
95
96 // Send msg to secondary cores to go to the kernel pen.
97 ArmGicSendSgiTo (PcdGet32(PcdGicDistributorBase), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE, 0x0E, PcdGet32 (PcdGicSgiIntId));
98
99 // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on
100 // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event.
101 Status = ShutdownUefiBootServices ();
102 if(EFI_ERROR(Status)) {
103 DEBUG((EFI_D_ERROR,"ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status));
104 goto Exit;
105 }
106
107 // Check if the Linux Image is a uImage
108 if (*(UINTN*)LinuxKernel == LINUX_UIMAGE_SIGNATURE) {
109 // Assume the Image Entry Point is just after the uImage header (64-byte size)
110 LinuxKernel = (LINUX_KERNEL64)((UINTN)LinuxKernel + 64);
111 LinuxImageSize -= 64;
112 }
113
114 //
115 // Switch off interrupts, caches, mmu, etc
116 //
117 Status = PreparePlatformHardware ();
118 ASSERT_EFI_ERROR(Status);
119
120 // Register and print out performance information
121 PERF_END (NULL, "BDS", NULL, 0);
122 if (PerformanceMeasurementEnabled ()) {
123 PrintPerformance ();
124 }
125
126 //
127 // Start the Linux Kernel
128 //
129
130 // x1-x3 are reserved (set to zero) for future use.
131 LinuxKernel ((UINTN)FdtBlobBase, 0, 0, 0);
132
133 // Kernel should never exit
134 // After Life services are not provided
135 ASSERT(FALSE);
136
137 Exit:
138 // Only be here if we fail to start Linux
139 Print (L"ERROR : Can not start the kernel. Status=0x%X\n", Status);
140
141 // Free Runtimee Memory (kernel and FDT)
142 return Status;
143 }
144
145
146 /**
147 Start a Linux kernel from a Device Path
148
149 @param LinuxKernel Device Path to the Linux Kernel
150 @param Parameters Linux kernel agruments
151 @param Fdt Device Path to the Flat Device Tree
152
153 @retval EFI_SUCCESS All drivers have been connected
154 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found
155 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.
156
157 **/
158 EFI_STATUS
159 BdsBootLinuxAtag (
160 IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
161 IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
162 IN CONST CHAR8* Arguments
163 )
164 {
165 // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
166 ASSERT(0);
167
168 return RETURN_UNSUPPORTED;
169 }
170
171 /**
172 Start a Linux kernel from a Device Path
173
174 @param LinuxKernel Device Path to the Linux Kernel
175 @param Parameters Linux kernel agruments
176 @param Fdt Device Path to the Flat Device Tree
177
178 @retval EFI_SUCCESS All drivers have been connected
179 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found
180 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results.
181
182 **/
183 EFI_STATUS
184 BdsBootLinuxFdt (
185 IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath,
186 IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath,
187 IN CONST CHAR8* Arguments,
188 IN EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath
189 )
190 {
191 EFI_STATUS Status;
192 EFI_STATUS PenBaseStatus;
193 UINTN LinuxImageSize;
194 UINTN InitrdImageSize;
195 UINTN InitrdImageBaseSize;
196 UINTN FdtBlobSize;
197 EFI_PHYSICAL_ADDRESS FdtBlobBase;
198 EFI_PHYSICAL_ADDRESS LinuxImage;
199 EFI_PHYSICAL_ADDRESS InitrdImage;
200 EFI_PHYSICAL_ADDRESS InitrdImageBase;
201 ARM_PROCESSOR_TABLE *ArmProcessorTable;
202 ARM_CORE_INFO *ArmCoreInfoTable;
203 UINTN Index;
204 EFI_PHYSICAL_ADDRESS PenBase;
205 UINTN PenSize;
206 UINTN MailBoxBase;
207
208 PenBaseStatus = EFI_UNSUPPORTED;
209 PenSize = 0;
210 InitrdImage = 0;
211 InitrdImageSize = 0;
212 InitrdImageBase = 0;
213 InitrdImageBaseSize = 0;
214
215 PERF_START (NULL, "BDS", NULL, 0);
216
217 //
218 // Load the Linux kernel from a device path
219 //
220
221 // Try to put the kernel at the start of RAM so as to give it access to all memory.
222 // If that fails fall back to try loading it within LINUX_KERNEL_MAX_OFFSET of memory start.
223 LinuxImage = PcdGet64 (PcdSystemMemoryBase) + 0x80000;
224 Status = BdsLoadImage (LinuxKernelDevicePath, AllocateAddress, &LinuxImage, &LinuxImageSize);
225 if (EFI_ERROR(Status)) {
226 // Try again but give the loader more freedom of where to put the image.
227 LinuxImage = LINUX_KERNEL_MAX_OFFSET;
228 Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize);
229 if (EFI_ERROR(Status)) {
230 Print (L"ERROR: Did not find Linux kernel (%r).\n", Status);
231 return Status;
232 }
233 }
234 // Adjust the kernel location slightly if required. The kernel needs to be placed at start
235 // of memory (2MB aligned) + 0x80000.
236 if ((LinuxImage & LINUX_ALIGN_MASK) != LINUX_ALIGN_VAL) {
237 LinuxImage = (EFI_PHYSICAL_ADDRESS)CopyMem (ALIGN_2MB(LinuxImage) + 0x80000, (VOID*)(UINTN)LinuxImage, LinuxImageSize);
238 }
239
240 if (InitrdDevicePath) {
241 InitrdImageBase = LINUX_KERNEL_MAX_OFFSET;
242 Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize);
243 if (Status == EFI_OUT_OF_RESOURCES) {
244 Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize);
245 }
246 if (EFI_ERROR (Status)) {
247 Print (L"ERROR: Did not find initrd image (%r).\n", Status);
248 goto EXIT_FREE_LINUX;
249 }
250
251 // Check if the initrd is a uInitrd
252 if (*(UINTN*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) {
253 // Skip the 64-byte image header
254 InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64);
255 InitrdImageSize = InitrdImageBaseSize - 64;
256 } else {
257 InitrdImage = InitrdImageBase;
258 InitrdImageSize = InitrdImageBaseSize;
259 }
260 }
261
262 // Load the FDT binary from a device path.
263 // The FDT will be reloaded later to a more appropriate location for the Linux kernel.
264 FdtBlobBase = LINUX_KERNEL_MAX_OFFSET;
265 Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, &FdtBlobBase, &FdtBlobSize);
266 if (EFI_ERROR(Status)) {
267 Print (L"ERROR: Did not find Device Tree blob (%r).\n", Status);
268 goto EXIT_FREE_INITRD;
269 }
270
271 //
272 // Install secondary core pens if the Power State Coordination Interface is not supported
273 //
274 if (FeaturePcdGet (PcdArmLinuxSpinTable)) {
275 // Place Pen at the start of Linux memory. We can then tell Linux to not use this bit of memory
276 PenBase = LinuxImage - 0x80000;
277 PenSize = (UINTN)&SecondariesPenEnd - (UINTN)&SecondariesPenStart;
278
279 // Reserve the memory as RuntimeServices
280 PenBaseStatus = gBS->AllocatePages (AllocateAddress, EfiRuntimeServicesCode, EFI_SIZE_TO_PAGES (PenSize), &PenBase);
281 if (EFI_ERROR (PenBaseStatus)) {
282 Print (L"Warning: Failed to reserve the memory required for the secondary cores at 0x%lX, Status = %r\n", PenBase, PenBaseStatus);
283 // Even if there is a risk of memory corruption we carry on
284 }
285
286 // Put mailboxes below the pen code so we know where they are relative to code.
287 MailBoxBase = (UINTN)PenBase + ((UINTN)&SecondariesPenEnd - (UINTN)&SecondariesPenStart);
288 // Make sure this is 8 byte aligned.
289 if (MailBoxBase % sizeof(MailBoxBase) != 0) {
290 MailBoxBase += sizeof(MailBoxBase) - MailBoxBase % sizeof(MailBoxBase);
291 }
292
293 CopyMem ( (VOID*)(PenBase), (VOID*)&SecondariesPenStart, PenSize);
294
295 // Update the MailboxBase variable used in the pen code
296 *(UINTN*)(PenBase + ((UINTN)&AsmMailboxbase - (UINTN)&SecondariesPenStart)) = MailBoxBase;
297
298 for (Index=0; Index < gST->NumberOfTableEntries; Index++) {
299 // Check for correct GUID type
300 if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
301 UINTN i;
302
303 // Get them under our control. Move from depending on 32bit reg(sys_flags) and SWI
304 // to 64 bit addr and WFE
305 ArmProcessorTable = (ARM_PROCESSOR_TABLE *)gST->ConfigurationTable[Index].VendorTable;
306 ArmCoreInfoTable = ArmProcessorTable->ArmCpus;
307
308 for (i = 0; i < ArmProcessorTable->NumberOfEntries; i++ ) {
309 // This goes into the SYSFLAGS register for the VE platform. We only have one 32bit reg to use
310 MmioWrite32(ArmCoreInfoTable[i].MailboxSetAddress, (UINTN)PenBase);
311
312 // So FDT can set the mailboxes correctly with the parser. These are 64bit Memory locations.
313 ArmCoreInfoTable[i].MailboxSetAddress = (UINTN)MailBoxBase + i*sizeof(MailBoxBase);
314
315 // Clear the mailboxes for the respective cores
316 *((UINTN*)(ArmCoreInfoTable[i].MailboxSetAddress)) = 0x0;
317 }
318 }
319 }
320 // Flush caches to make sure our pen gets to mem before we free the cores.
321 ArmCleanDataCache();
322 }
323
324 // By setting address=0 we leave the memory allocation to the function
325 Status = PrepareFdt (Arguments, InitrdImage, InitrdImageSize, &FdtBlobBase, &FdtBlobSize);
326 if (EFI_ERROR(Status)) {
327 Print(L"ERROR: Can not load Linux kernel with Device Tree. Status=0x%X\n", Status);
328 goto EXIT_FREE_FDT;
329 }
330
331 return StartLinux (LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize);
332
333 EXIT_FREE_FDT:
334 if (!EFI_ERROR (PenBaseStatus)) {
335 gBS->FreePages (PenBase, EFI_SIZE_TO_PAGES (PenSize));
336 }
337
338 gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize));
339
340 EXIT_FREE_INITRD:
341 if (InitrdDevicePath) {
342 gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
343 }
344
345 EXIT_FREE_LINUX:
346 gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
347
348 return Status;
349 }