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
PreparePlatformHardware(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
StartLinux(IN EFI_PHYSICAL_ADDRESS LinuxImage,IN UINTN LinuxImageSize,IN EFI_PHYSICAL_ADDRESS FdtBlobBase,IN UINTN FdtBlobSize)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 (PcdGet32 (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
BootLinuxAtag(IN EFI_PHYSICAL_ADDRESS SystemMemoryBase,IN EFI_DEVICE_PATH_PROTOCOL * LinuxKernelDevicePath,IN EFI_DEVICE_PATH_PROTOCOL * InitrdDevicePath,IN CONST CHAR8 * CommandLineArguments,IN UINTN MachineType)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
BootLinuxFdt(IN EFI_PHYSICAL_ADDRESS SystemMemoryBase,IN EFI_DEVICE_PATH_PROTOCOL * LinuxKernelDevicePath,IN EFI_DEVICE_PATH_PROTOCOL * InitrdDevicePath,IN EFI_DEVICE_PATH_PROTOCOL * FdtDevicePath,IN CONST CHAR8 * Arguments)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 }
371