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 */
56typedef 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. */
60extern VOID  *SecondariesPenStart;
61extern VOID  *SecondariesPenEnd;
62extern UINTN *AsmMailboxbase;
63
64
65STATIC
66VOID
67PreparePlatformHardware (
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
87STATIC
88EFI_STATUS
89StartLinux (
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**/
158EFI_STATUS
159BootLinuxAtag (
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**/
185EFI_STATUS
186BootLinuxFdt (
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
354EXIT_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
361EXIT_FREE_INITRD:
362  if (InitrdDevicePath) {
363    gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize));
364  }
365
366EXIT_FREE_LINUX:
367  gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize));
368
369  return Status;
370}
371