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/PcdLib.h> 17 18 #include <Guid/Fdt.h> 19 20 #include "LinuxLoader.h" 21 22 #define ALIGN32_BELOW(addr) ALIGN_POINTER(addr - 32,32) 23 24 #define IS_ADDRESS_IN_REGION(RegionStart, RegionSize, Address) \ 25 (((UINTN)(RegionStart) <= (UINTN)(Address)) && ((UINTN)(Address) <= ((UINTN)(RegionStart) + (UINTN)(RegionSize)))) 26 27 EFI_STATUS 28 PrepareAtagList ( 29 IN EFI_PHYSICAL_ADDRESS SystemMemoryBase, 30 IN CONST CHAR8* CommandLineString, 31 IN EFI_PHYSICAL_ADDRESS InitrdImage, 32 IN UINTN InitrdImageSize, 33 OUT EFI_PHYSICAL_ADDRESS *AtagBase, 34 OUT UINT32 *AtagSize 35 ); 36 37 STATIC 38 VOID 39 PreparePlatformHardware ( 40 VOID 41 ) 42 { 43 //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called. 44 45 // Clean before Disable else the Stack gets corrupted with old data. 46 ArmCleanDataCache (); 47 ArmDisableDataCache (); 48 // Invalidate all the entries that might have snuck in. 49 ArmInvalidateDataCache (); 50 51 // Invalidate and disable the Instruction cache 52 ArmDisableInstructionCache (); 53 ArmInvalidateInstructionCache (); 54 55 // Turn off MMU 56 ArmDisableMmu (); 57 } 58 59 STATIC 60 EFI_STATUS 61 StartLinux ( 62 IN EFI_PHYSICAL_ADDRESS SystemMemoryBase, 63 IN EFI_PHYSICAL_ADDRESS LinuxImage, 64 IN UINTN LinuxImageSize, 65 IN EFI_PHYSICAL_ADDRESS KernelParamsAddress, 66 IN UINTN KernelParamsSize, 67 IN UINT32 MachineType 68 ) 69 { 70 EFI_STATUS Status; 71 LINUX_KERNEL LinuxKernel; 72 73 // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on 74 // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event. 75 Status = ShutdownUefiBootServices (); 76 if (EFI_ERROR (Status)) { 77 DEBUG ((EFI_D_ERROR, "ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status)); 78 return Status; 79 } 80 81 // Move the kernel parameters to any address inside the first 1MB. 82 // This is necessary because the ARM Linux kernel requires 83 // the FTD / ATAG List to reside entirely inside the first 1MB of 84 // physical memory. 85 //Note: There is no requirement on the alignment 86 if (MachineType != ARM_FDT_MACHINE_TYPE) { 87 if (((UINTN)KernelParamsAddress > LINUX_ATAG_MAX_OFFSET) && (KernelParamsSize < PcdGet32 (PcdArmLinuxAtagMaxOffset))) { 88 KernelParamsAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)CopyMem (ALIGN32_BELOW (LINUX_ATAG_MAX_OFFSET - KernelParamsSize), (VOID*)(UINTN)KernelParamsAddress, KernelParamsSize); 89 } 90 } else { 91 if (((UINTN)KernelParamsAddress > LINUX_FDT_MAX_OFFSET) && (KernelParamsSize < PcdGet32 (PcdArmLinuxFdtMaxOffset))) { 92 KernelParamsAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)CopyMem (ALIGN32_BELOW (LINUX_FDT_MAX_OFFSET - KernelParamsSize), (VOID*)(UINTN)KernelParamsAddress, KernelParamsSize); 93 } 94 } 95 96 if ((UINTN)LinuxImage > LINUX_KERNEL_MAX_OFFSET) { 97 //Note: There is no requirement on the alignment 98 LinuxKernel = (LINUX_KERNEL)CopyMem (ALIGN32_BELOW (LINUX_KERNEL_MAX_OFFSET - LinuxImageSize), (VOID*)(UINTN)LinuxImage, LinuxImageSize); 99 } else { 100 LinuxKernel = (LINUX_KERNEL)(UINTN)LinuxImage; 101 } 102 103 // Check if the Linux Image is a uImage 104 if (*(UINT32*)LinuxKernel == LINUX_UIMAGE_SIGNATURE) { 105 // Assume the Image Entry Point is just after the uImage header (64-byte size) 106 LinuxKernel = (LINUX_KERNEL)((UINTN)LinuxKernel + 64); 107 LinuxImageSize -= 64; 108 } 109 110 // Check there is no overlapping between kernel and its parameters 111 // We can only assert because it is too late to fallback to UEFI (ExitBootServices has been called). 112 ASSERT (!IS_ADDRESS_IN_REGION (LinuxKernel, LinuxImageSize, KernelParamsAddress) && 113 !IS_ADDRESS_IN_REGION (LinuxKernel, LinuxImageSize, KernelParamsAddress + KernelParamsSize)); 114 115 // 116 // Switch off interrupts, caches, mmu, etc 117 // 118 PreparePlatformHardware (); 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 // Outside BootServices, so can't use Print(); 131 DEBUG ((EFI_D_ERROR, "\nStarting the kernel:\n\n")); 132 133 // Jump to kernel with register set 134 LinuxKernel ((UINTN)0, MachineType, (UINTN)KernelParamsAddress); 135 136 // Kernel should never exit 137 // After Life services are not provided 138 ASSERT (FALSE); 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 EFI_STATUS Status; 168 UINT32 LinuxImageSize; 169 UINT32 InitrdImageBaseSize = 0; 170 UINT32 InitrdImageSize = 0; 171 UINT32 AtagSize; 172 EFI_PHYSICAL_ADDRESS AtagBase; 173 EFI_PHYSICAL_ADDRESS LinuxImage; 174 EFI_PHYSICAL_ADDRESS InitrdImageBase = 0; 175 EFI_PHYSICAL_ADDRESS InitrdImage = 0; 176 177 PERF_START (NULL, "BDS", NULL, 0); 178 179 // Load the Linux kernel from a device path 180 LinuxImage = LINUX_KERNEL_MAX_OFFSET; 181 Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize); 182 if (EFI_ERROR (Status)) { 183 Print (L"ERROR: Did not find Linux kernel.\n"); 184 return Status; 185 } 186 187 if (InitrdDevicePath) { 188 // Load the initrd near to the Linux kernel 189 InitrdImageBase = LINUX_KERNEL_MAX_OFFSET; 190 Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize); 191 if (Status == EFI_OUT_OF_RESOURCES) { 192 Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize); 193 } 194 if (EFI_ERROR (Status)) { 195 Print (L"ERROR: Did not find initrd image.\n"); 196 goto EXIT_FREE_LINUX; 197 } 198 199 // Check if the initrd is a uInitrd 200 if (*(UINT32*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) { 201 // Skip the 64-byte image header 202 InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64); 203 InitrdImageSize = InitrdImageBaseSize - 64; 204 } else { 205 InitrdImage = InitrdImageBase; 206 InitrdImageSize = InitrdImageBaseSize; 207 } 208 } 209 210 // 211 // Setup the Linux Kernel Parameters 212 // 213 214 // By setting address=0 we leave the memory allocation to the function 215 Status = PrepareAtagList (SystemMemoryBase, CommandLineArguments, InitrdImage, InitrdImageSize, &AtagBase, &AtagSize); 216 if (EFI_ERROR (Status)) { 217 Print (L"ERROR: Can not prepare ATAG list. Status=0x%X\n", Status); 218 goto EXIT_FREE_INITRD; 219 } 220 221 return StartLinux (SystemMemoryBase, LinuxImage, LinuxImageSize, AtagBase, AtagSize, MachineType); 222 223 EXIT_FREE_INITRD: 224 if (InitrdDevicePath) { 225 gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize)); 226 } 227 228 EXIT_FREE_LINUX: 229 gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize)); 230 231 return Status; 232 } 233 234 /** 235 Start a Linux kernel from a Device Path 236 237 @param LinuxKernelDevicePath Device Path to the Linux Kernel 238 @param InitrdDevicePath Device Path to the Initrd 239 @param CommandLineArguments Linux command line 240 241 @retval EFI_SUCCESS All drivers have been connected 242 @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found 243 @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results. 244 245 **/ 246 EFI_STATUS 247 BootLinuxFdt ( 248 IN EFI_PHYSICAL_ADDRESS SystemMemoryBase, 249 IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath, 250 IN EFI_DEVICE_PATH_PROTOCOL* InitrdDevicePath, 251 IN EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath, 252 IN CONST CHAR8* CommandLineArguments 253 ) 254 { 255 EFI_STATUS Status; 256 UINT32 LinuxImageSize; 257 UINT32 InitrdImageBaseSize = 0; 258 UINT32 InitrdImageSize = 0; 259 VOID *InstalledFdtBase; 260 UINT32 FdtBlobSize; 261 EFI_PHYSICAL_ADDRESS FdtBlobBase; 262 EFI_PHYSICAL_ADDRESS LinuxImage; 263 EFI_PHYSICAL_ADDRESS InitrdImageBase = 0; 264 EFI_PHYSICAL_ADDRESS InitrdImage = 0; 265 266 PERF_START (NULL, "BDS", NULL, 0); 267 268 // Load the Linux kernel from a device path 269 LinuxImage = LINUX_KERNEL_MAX_OFFSET; 270 Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize); 271 if (EFI_ERROR (Status)) { 272 Print (L"ERROR: Did not find Linux kernel.\n"); 273 return Status; 274 } 275 276 if (InitrdDevicePath) { 277 InitrdImageBase = LINUX_KERNEL_MAX_OFFSET; 278 Status = BdsLoadImage (InitrdDevicePath, AllocateMaxAddress, &InitrdImageBase, &InitrdImageBaseSize); 279 if (Status == EFI_OUT_OF_RESOURCES) { 280 Status = BdsLoadImage (InitrdDevicePath, AllocateAnyPages, &InitrdImageBase, &InitrdImageBaseSize); 281 } 282 if (EFI_ERROR (Status)) { 283 Print (L"ERROR: Did not find initrd image.\n"); 284 goto EXIT_FREE_LINUX; 285 } 286 287 // Check if the initrd is a uInitrd 288 if (*(UINT32*)((UINTN)InitrdImageBase) == LINUX_UIMAGE_SIGNATURE) { 289 // Skip the 64-byte image header 290 InitrdImage = (EFI_PHYSICAL_ADDRESS)((UINTN)InitrdImageBase + 64); 291 InitrdImageSize = InitrdImageBaseSize - 64; 292 } else { 293 InitrdImage = InitrdImageBase; 294 InitrdImageSize = InitrdImageBaseSize; 295 } 296 } 297 298 if (FdtDevicePath == NULL) { 299 // 300 // Get the FDT from the Configuration Table. 301 // The FDT will be reloaded in PrepareFdt() to a more appropriate 302 // location for the Linux Kernel. 303 // 304 Status = EfiGetSystemConfigurationTable (&gFdtTableGuid, &InstalledFdtBase); 305 if (EFI_ERROR (Status)) { 306 Print (L"ERROR: Did not get the Device Tree blob (%r).\n", Status); 307 goto EXIT_FREE_INITRD; 308 } 309 FdtBlobBase = (EFI_PHYSICAL_ADDRESS)(UINTN)InstalledFdtBase; 310 FdtBlobSize = fdt_totalsize (InstalledFdtBase); 311 } else { 312 // 313 // FDT device path explicitly defined. The FDT is relocated later to a 314 // more appropriate location for the Linux kernel. 315 // 316 FdtBlobBase = LINUX_KERNEL_MAX_OFFSET; 317 Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, &FdtBlobBase, &FdtBlobSize); 318 if (EFI_ERROR (Status)) { 319 Print (L"ERROR: Did not find Device Tree blob (%r).\n", Status); 320 goto EXIT_FREE_INITRD; 321 } 322 } 323 324 // Update the Fdt with the Initrd information. The FDT will increase in size. 325 // By setting address=0 we leave the memory allocation to the function 326 Status = PrepareFdt (SystemMemoryBase, CommandLineArguments, InitrdImage, InitrdImageSize, &FdtBlobBase, &FdtBlobSize); 327 if (EFI_ERROR (Status)) { 328 Print (L"ERROR: Can not load kernel with FDT. Status=%r\n", Status); 329 goto EXIT_FREE_FDT; 330 } 331 332 return StartLinux (SystemMemoryBase, LinuxImage, LinuxImageSize, FdtBlobBase, FdtBlobSize, ARM_FDT_MACHINE_TYPE); 333 334 EXIT_FREE_FDT: 335 gBS->FreePages (FdtBlobBase, EFI_SIZE_TO_PAGES (FdtBlobSize)); 336 337 EXIT_FREE_INITRD: 338 if (InitrdDevicePath) { 339 gBS->FreePages (InitrdImageBase, EFI_SIZE_TO_PAGES (InitrdImageBaseSize)); 340 } 341 342 EXIT_FREE_LINUX: 343 gBS->FreePages (LinuxImage, EFI_SIZE_TO_PAGES (LinuxImageSize)); 344 345 return Status; 346 } 347