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