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