]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPkg/Application/LinuxLoader/AArch64/LinuxStarter.c
d82cf85111381b7c96e5ac83e6269ef53c1d2c7a
[mirror_edk2.git] / ArmPkg / Application / LinuxLoader / AArch64 / LinuxStarter.c
1 /** @file
2 *
3 * Copyright (c) 2011-2015, 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
15 #include <Library/ArmLib.h>
16 #include <Library/ArmGicLib.h>
17 #include <Library/IoLib.h>
18 #include <Library/PcdLib.h>
19
20 #include <Ppi/ArmMpCoreInfo.h>
21
22 #include <Guid/Fdt.h>
23
24 #include "LinuxLoader.h"
25
26 /*
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.
31
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.
34
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.
42
43 Linux does not use any runtime services at this time, so we can let it
44 overwrite UEFI.
45 */
46
47
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))
51
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.
55 */
56 typedef VOID (*LINUX_KERNEL64)(UINTN ParametersBase, UINTN Reserved0,
57 UINTN Reserved1, UINTN Reserved2);
58
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;
63
64
65 STATIC
66 VOID
67 PreparePlatformHardware (
68 VOID
69 )
70 {
71 //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called.
72
73 // Clean before Disable else the Stack gets corrupted with old data.
74 ArmCleanDataCache ();
75 ArmDisableDataCache ();
76 // Invalidate all the entries that might have snuck in.
77 ArmInvalidateDataCache ();
78
79 // Disable and invalidate the instruction cache
80 ArmDisableInstructionCache ();
81 ArmInvalidateInstructionCache ();
82
83 // Turn off MMU
84 ArmDisableMmu ();
85 }
86
87 STATIC
88 EFI_STATUS
89 StartLinux (
90 IN EFI_PHYSICAL_ADDRESS LinuxImage,
91 IN UINTN LinuxImageSize,
92 IN EFI_PHYSICAL_ADDRESS FdtBlobBase,
93 IN UINTN FdtBlobSize
94 )
95 {
96 EFI_STATUS Status;
97 LINUX_KERNEL64 LinuxKernel = (LINUX_KERNEL64)LinuxImage;
98
99 // Send msg to secondary cores to go to the kernel pen.
100 ArmGicSendSgiTo (PcdGet64 (PcdGicDistributorBase), ARM_GIC_ICDSGIR_FILTER_EVERYONEELSE, 0x0E, PcdGet32 (PcdGicSgiIntId));
101
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));
107 return Status;
108 }
109
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;
115 }
116
117 //
118 // Switch off interrupts, caches, mmu, etc
119 //
120 PreparePlatformHardware ();
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 // We cannot recover the execution at this stage
140 while (1);
141 }
142
143 /**
144 Start a Linux kernel from a Device Path
145
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
151
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
156
157 **/
158 EFI_STATUS
159 BootLinuxAtag (
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,
164 IN UINTN MachineType
165 )
166 {
167 // NOTE : AArch64 Linux kernel does not support ATAG, FDT only.
168 ASSERT (0);
169
170 return EFI_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 BootLinuxFdt (
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
192 )
193 {
194 EFI_STATUS Status;
195 EFI_STATUS PenBaseStatus;
196 UINTN LinuxImageSize;
197 UINTN InitrdImageSize;
198 UINTN InitrdImageBaseSize;
199 VOID *InstalledFdtBase;
200 UINTN FdtBlobSize;
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;
207 UINTN Index;
208 EFI_PHYSICAL_ADDRESS PenBase;
209 UINTN PenSize;
210 UINTN MailBoxBase;
211
212 PenBaseStatus = EFI_UNSUPPORTED;
213 PenSize = 0;
214 InitrdImage = 0;
215 InitrdImageSize = 0;
216 InitrdImageBase = 0;
217 InitrdImageBaseSize = 0;
218
219 PERF_START (NULL, "BDS", NULL, 0);
220
221 //
222 // Load the Linux kernel from a device path
223 //
224
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);
235 return Status;
236 }
237 }
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);
242 }
243
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);
249 }
250 if (EFI_ERROR (Status)) {
251 Print (L"ERROR: Did not find initrd image (%r).\n", Status);
252 goto EXIT_FREE_LINUX;
253 }
254
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;
260 } else {
261 InitrdImage = InitrdImageBase;
262 InitrdImageSize = InitrdImageBaseSize;
263 }
264 }
265
266 if (FdtDevicePath == NULL) {
267 //
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.
271 //
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;
276 }
277 FdtBlobBase = (EFI_PHYSICAL_ADDRESS)InstalledFdtBase;
278 FdtBlobSize = fdt_totalsize (InstalledFdtBase);
279 } else {
280 //
281 // FDT device path explicitly defined. The FDT is relocated later to a
282 // more appropriate location for the Linux kernel.
283 //
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;
289 }
290 }
291
292 //
293 // Install secondary core pens if the Power State Coordination Interface is not supported
294 //
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;
299
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
305 }
306
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);
312 }
313
314 CopyMem ( (VOID*)(PenBase), (VOID*)&SecondariesPenStart, PenSize);
315
316 // Update the MailboxBase variable used in the pen code
317 *(UINTN*)(PenBase + ((UINTN)&AsmMailboxbase - (UINTN)&SecondariesPenStart)) = MailBoxBase;
318
319 for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
320 // Check for correct GUID type
321 if (CompareGuid (&gArmMpCoreInfoGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
322 UINTN i;
323
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;
328
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);
332
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);
335
336 // Clear the mailboxes for the respective cores
337 *((UINTN*)(ArmCoreInfoTable[i].MailboxSetAddress)) = 0x0;
338 }
339 }
340 }
341 // Flush caches to make sure our pen gets to mem before we free the cores.
342 ArmCleanDataCache ();
343 }
344
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);
349 goto EXIT_FREE_FDT;
350 }
351
352 return StartLinux (LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize);
353
354 EXIT_FREE_FDT:
355 if (!EFI_ERROR (PenBaseStatus)) {
356 gBS->FreePages (PenBase, EFI_SIZE_TO_PAGES (PenSize));
357 }
358
359 gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize));
360
361 EXIT_FREE_INITRD:
362 if (InitrdDevicePath) {
363 gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
364 }
365
366 EXIT_FREE_LINUX:
367 gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
368
369 return Status;
370 }