]>
Commit | Line | Data |
---|---|---|
1bfda055 | 1 | /** @file |
2 | * | |
3 | * Copyright (c) 2011, 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 | #include <Library/PcdLib.h> | |
19 | #include <Library/ArmLib.h> | |
20 | #include <Library/HobLib.h> | |
21 | ||
a355a365 | 22 | #define ALIGN32_BELOW(addr) ALIGN_POINTER(addr - 32,32) |
1bfda055 | 23 | |
a355a365 | 24 | #define LINUX_ATAG_MAX_OFFSET (PcdGet32(PcdSystemMemoryBase) + PcdGet32(PcdArmLinuxAtagMaxOffset)) |
25 | #define LINUX_KERNEL_MAX_OFFSET (PcdGet32(PcdSystemMemoryBase) + PcdGet32(PcdArmLinuxKernelMaxOffset)) | |
26 | ||
27 | // Point to the current ATAG | |
28 | STATIC LINUX_ATAG *mLinuxKernelCurrentAtag; | |
1bfda055 | 29 | |
30 | STATIC | |
31 | VOID | |
a355a365 | 32 | SetupCoreTag ( |
33 | IN UINT32 PageSize | |
34 | ) | |
1bfda055 | 35 | { |
a355a365 | 36 | mLinuxKernelCurrentAtag->header.size = tag_size(LINUX_ATAG_CORE); |
37 | mLinuxKernelCurrentAtag->header.type = ATAG_CORE; | |
1bfda055 | 38 | |
a355a365 | 39 | mLinuxKernelCurrentAtag->body.core_tag.flags = 1; /* ensure read-only */ |
40 | mLinuxKernelCurrentAtag->body.core_tag.pagesize = PageSize; /* systems PageSize (4k) */ | |
41 | mLinuxKernelCurrentAtag->body.core_tag.rootdev = 0; /* zero root device (typically overridden from kernel command line )*/ | |
1bfda055 | 42 | |
a355a365 | 43 | // move pointer to next tag |
44 | mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag); | |
1bfda055 | 45 | } |
46 | ||
47 | STATIC | |
48 | VOID | |
a355a365 | 49 | SetupMemTag ( |
50 | IN UINTN StartAddress, | |
51 | IN UINT32 Size | |
52 | ) | |
1bfda055 | 53 | { |
a355a365 | 54 | mLinuxKernelCurrentAtag->header.size = tag_size(LINUX_ATAG_MEM); |
55 | mLinuxKernelCurrentAtag->header.type = ATAG_MEM; | |
1bfda055 | 56 | |
a355a365 | 57 | mLinuxKernelCurrentAtag->body.mem_tag.start = StartAddress; /* Start of memory chunk for AtagMem */ |
58 | mLinuxKernelCurrentAtag->body.mem_tag.size = Size; /* Size of memory chunk for AtagMem */ | |
1bfda055 | 59 | |
a355a365 | 60 | // move pointer to next tag |
61 | mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag); | |
1bfda055 | 62 | } |
63 | ||
64 | STATIC | |
65 | VOID | |
a355a365 | 66 | SetupCmdlineTag ( |
67 | IN CONST CHAR8 *CmdLine | |
68 | ) | |
1bfda055 | 69 | { |
70 | UINT32 LineLength; | |
71 | ||
72 | // Increment the line length by 1 to account for the null string terminator character | |
73 | LineLength = AsciiStrLen(CmdLine) + 1; | |
74 | ||
75 | /* Check for NULL strings. | |
76 | * Do not insert a tag for an empty CommandLine, don't even modify the tag address pointer. | |
77 | * Remember, you have at least one null string terminator character. | |
78 | */ | |
a355a365 | 79 | if(LineLength > 1) { |
80 | mLinuxKernelCurrentAtag->header.size = ((UINT32)sizeof(LINUX_ATAG_HEADER) + LineLength + (UINT32)3) >> 2; | |
81 | mLinuxKernelCurrentAtag->header.type = ATAG_CMDLINE; | |
1bfda055 | 82 | |
83 | /* place CommandLine into tag */ | |
a355a365 | 84 | AsciiStrCpy(mLinuxKernelCurrentAtag->body.cmdline_tag.cmdline, CmdLine); |
1bfda055 | 85 | |
a355a365 | 86 | // move pointer to next tag |
87 | mLinuxKernelCurrentAtag = next_tag_address(mLinuxKernelCurrentAtag); | |
1bfda055 | 88 | } |
89 | } | |
90 | ||
91 | STATIC | |
92 | VOID | |
a355a365 | 93 | SetupEndTag ( |
94 | VOID | |
95 | ) | |
1bfda055 | 96 | { |
97 | // Empty tag ends list; this has zero length and no body | |
a355a365 | 98 | mLinuxKernelCurrentAtag->header.type = ATAG_NONE; |
99 | mLinuxKernelCurrentAtag->header.size = 0; | |
1bfda055 | 100 | |
101 | /* We can not calculate the next address by using the standard macro: | |
102 | * Params = next_tag_address(Params); | |
103 | * because it relies on the header.size, which here it is 0 (zero). | |
a355a365 | 104 | * The easiest way is to add the sizeof(mLinuxKernelCurrentAtag->header). |
1bfda055 | 105 | */ |
a355a365 | 106 | mLinuxKernelCurrentAtag = (LINUX_ATAG*)((UINT32)mLinuxKernelCurrentAtag + sizeof(mLinuxKernelCurrentAtag->header)); |
1bfda055 | 107 | } |
108 | ||
109 | STATIC | |
110 | EFI_STATUS | |
a355a365 | 111 | PrepareAtagList ( |
112 | IN CONST CHAR8* CommandLineString, | |
113 | OUT LINUX_ATAG **AtagBase, | |
114 | OUT UINT32 *AtagSize | |
115 | ) | |
116 | { | |
117 | EFI_STATUS Status; | |
118 | LIST_ENTRY *ResourceLink; | |
119 | LIST_ENTRY ResourceList; | |
120 | EFI_PHYSICAL_ADDRESS AtagStartAddress; | |
1bfda055 | 121 | BDS_SYSTEM_MEMORY_RESOURCE *Resource; |
122 | ||
a355a365 | 123 | AtagStartAddress = LINUX_ATAG_MAX_OFFSET; |
124 | Status = gBS->AllocatePages (AllocateMaxAddress, EfiBootServicesData, EFI_SIZE_TO_PAGES(ATAG_MAX_SIZE), &AtagStartAddress); | |
125 | if (EFI_ERROR(Status)) { | |
126 | DEBUG ((EFI_D_ERROR,"Failed to allocate Atag at 0x%lX (%r)\n",AtagStartAddress,Status)); | |
127 | Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData, EFI_SIZE_TO_PAGES(ATAG_MAX_SIZE), &AtagStartAddress); | |
128 | ASSERT_EFI_ERROR(Status); | |
1bfda055 | 129 | } |
130 | ||
1bfda055 | 131 | // Ready to setup the atag list |
a355a365 | 132 | mLinuxKernelCurrentAtag = (LINUX_ATAG*)(UINTN)AtagStartAddress; |
1bfda055 | 133 | |
134 | // Standard core tag 4k PageSize | |
135 | SetupCoreTag( (UINT32)SIZE_4KB ); | |
136 | ||
137 | // Physical memory setup | |
a355a365 | 138 | GetSystemMemoryResources (&ResourceList); |
1bfda055 | 139 | ResourceLink = ResourceList.ForwardLink; |
140 | while (ResourceLink != NULL && ResourceLink != &ResourceList) { | |
a355a365 | 141 | Resource = (BDS_SYSTEM_MEMORY_RESOURCE*)ResourceLink; |
142 | DEBUG((EFI_D_INFO,"- [0x%08X,0x%08X]\n",(UINT32)Resource->PhysicalStart,(UINT32)Resource->PhysicalStart+(UINT32)Resource->ResourceLength)); | |
1bfda055 | 143 | SetupMemTag( (UINT32)Resource->PhysicalStart, (UINT32)Resource->ResourceLength ); |
144 | ResourceLink = ResourceLink->ForwardLink; | |
145 | } | |
146 | ||
147 | // CommandLine setting root device | |
a355a365 | 148 | SetupCmdlineTag (CommandLineString); |
1bfda055 | 149 | |
150 | // end of tags | |
151 | SetupEndTag(); | |
152 | ||
153 | // Calculate atag list size | |
a355a365 | 154 | *AtagBase = (LINUX_ATAG*)(UINTN)AtagStartAddress; |
155 | *AtagSize = (UINT32)mLinuxKernelCurrentAtag - (UINT32)AtagStartAddress + 1; | |
1bfda055 | 156 | |
157 | return EFI_SUCCESS; | |
158 | } | |
159 | ||
160 | STATIC | |
161 | EFI_STATUS | |
a355a365 | 162 | PreparePlatformHardware ( |
163 | VOID | |
164 | ) | |
1bfda055 | 165 | { |
166 | //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called. | |
167 | ||
168 | // clean, invalidate, disable data cache | |
169 | ArmCleanInvalidateDataCache(); | |
170 | ArmDisableDataCache(); | |
171 | ||
172 | // Invalidate and disable the Instruction cache | |
173 | ArmInvalidateInstructionCache (); | |
174 | ArmDisableInstructionCache (); | |
175 | ||
176 | // turn off MMU | |
1bfda055 | 177 | ArmDisableMmu(); |
178 | ||
179 | return EFI_SUCCESS; | |
180 | } | |
181 | ||
a355a365 | 182 | /** |
183 | Start a Linux kernel from a Device Path | |
1bfda055 | 184 | |
a355a365 | 185 | @param LinuxKernel Device Path to the Linux Kernel |
186 | @param Parameters Linux kernel agruments | |
187 | @param Fdt Device Path to the Flat Device Tree | |
1bfda055 | 188 | |
a355a365 | 189 | @retval EFI_SUCCESS All drivers have been connected |
190 | @retval EFI_NOT_FOUND The Linux kernel Device Path has not been found | |
191 | @retval EFI_OUT_OF_RESOURCES There is not enough resource memory to store the matching results. | |
1bfda055 | 192 | |
a355a365 | 193 | **/ |
194 | EFI_STATUS | |
195 | BdsBootLinux ( | |
196 | IN EFI_DEVICE_PATH_PROTOCOL* LinuxKernelDevicePath, | |
197 | IN CONST CHAR8* Arguments, | |
198 | IN EFI_DEVICE_PATH_PROTOCOL* FdtDevicePath | |
199 | ) | |
200 | { | |
201 | EFI_STATUS Status; | |
202 | UINT32 LinuxImageSize; | |
203 | UINT32 KernelParamsSize; | |
204 | VOID* KernelParamsAddress = NULL; | |
205 | UINT32 MachineType; | |
206 | BOOLEAN FdtSupported = FALSE; | |
207 | LINUX_KERNEL LinuxKernel; | |
208 | EFI_PHYSICAL_ADDRESS LinuxImage;; | |
209 | ||
210 | ||
211 | PERF_START (NULL, "BDS", NULL, 0); | |
212 | ||
213 | // Load the Linux kernel from a device path | |
214 | LinuxImage = LINUX_KERNEL_MAX_OFFSET; | |
215 | Status = BdsLoadImage (LinuxKernelDevicePath, AllocateMaxAddress, &LinuxImage, &LinuxImageSize); | |
216 | if (EFI_ERROR(Status)) { | |
217 | DEBUG ((EFI_D_ERROR, "ERROR: Do not find Linux kernel.\n")); | |
218 | return Status; | |
219 | } | |
220 | LinuxKernel = (LINUX_KERNEL)(UINTN)LinuxImage; | |
1bfda055 | 221 | |
a355a365 | 222 | // Load the FDT binary from a device path |
223 | KernelParamsAddress = (VOID*)LINUX_ATAG_MAX_OFFSET; | |
224 | Status = BdsLoadImage (FdtDevicePath, AllocateMaxAddress, (EFI_PHYSICAL_ADDRESS*)&KernelParamsAddress, &KernelParamsSize); | |
225 | if (!EFI_ERROR(Status)) { | |
226 | FdtSupported = TRUE; | |
227 | } | |
1bfda055 | 228 | |
a355a365 | 229 | // |
230 | // Setup the Linux Kernel Parameters | |
231 | // | |
232 | if (!FdtSupported) { | |
233 | // Non-FDT requires a specific machine type. | |
234 | // This OS Boot loader supports just one machine type, | |
235 | // but that could change in the future. | |
236 | MachineType = PcdGet32(PcdArmMachineType); | |
1bfda055 | 237 | |
a355a365 | 238 | // By setting address=0 we leave the memory allocation to the function |
239 | Status = PrepareAtagList (Arguments, (LINUX_ATAG**)&KernelParamsAddress, &KernelParamsSize); | |
240 | if(EFI_ERROR(Status)) { | |
241 | Print(L"ERROR: Can not prepare ATAG list. Status=0x%X\n", Status); | |
242 | goto Exit; | |
1bfda055 | 243 | } |
a355a365 | 244 | } else { |
245 | MachineType = 0xFFFFFFFF; | |
246 | } | |
1bfda055 | 247 | |
a355a365 | 248 | // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on |
249 | // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event. | |
250 | Status = ShutdownUefiBootServices (); | |
251 | if(EFI_ERROR(Status)) { | |
252 | DEBUG((EFI_D_ERROR,"ERROR: Can not shutdown UEFI boot services. Status=0x%X\n", Status)); | |
253 | goto Exit; | |
254 | } | |
1bfda055 | 255 | |
a355a365 | 256 | // Move the kernel parameters to any address inside the first 1MB. |
257 | // This is necessary because the ARM Linux kernel requires | |
258 | // the FTD / ATAG List to reside entirely inside the first 1MB of | |
259 | // physical memory. | |
260 | if ((UINTN)KernelParamsAddress > LINUX_ATAG_MAX_OFFSET) { | |
261 | //Note: There is no requirement on the alignment | |
262 | KernelParamsAddress = CopyMem (ALIGN32_BELOW(LINUX_ATAG_MAX_OFFSET - KernelParamsSize), KernelParamsAddress, KernelParamsSize); | |
263 | } | |
1bfda055 | 264 | |
a355a365 | 265 | if ((UINTN)LinuxImage > LINUX_KERNEL_MAX_OFFSET) { |
266 | //Note: There is no requirement on the alignment | |
267 | LinuxKernel = (LINUX_KERNEL)CopyMem (ALIGN32_BELOW(LINUX_KERNEL_MAX_OFFSET - LinuxImageSize), (VOID*)(UINTN)LinuxImage, LinuxImageSize); | |
268 | } | |
1bfda055 | 269 | |
a355a365 | 270 | //TODO: Check there is no overlapping between kernel and Atag |
1bfda055 | 271 | |
a355a365 | 272 | // |
273 | // Switch off interrupts, caches, mmu, etc | |
274 | // | |
275 | Status = PreparePlatformHardware (); | |
276 | ASSERT_EFI_ERROR(Status); | |
1bfda055 | 277 | |
a355a365 | 278 | // Register and print out performance information |
279 | PERF_END (NULL, "BDS", NULL, 0); | |
280 | if (PerformanceMeasurementEnabled ()) { | |
281 | PrintPerformance (); | |
282 | } | |
1bfda055 | 283 | |
a355a365 | 284 | // |
285 | // Start the Linux Kernel | |
286 | // | |
1bfda055 | 287 | |
a355a365 | 288 | // Outside BootServices, so can't use Print(); |
289 | DEBUG((EFI_D_ERROR, "\nStarting the kernel:\n\n")); | |
1bfda055 | 290 | |
a355a365 | 291 | // jump to kernel with register set |
292 | LinuxKernel ((UINTN)0, (UINTN)MachineType, (UINTN)KernelParamsAddress); | |
1bfda055 | 293 | |
a355a365 | 294 | // Kernel should never exit |
295 | // After Life services are not provided | |
296 | ASSERT(FALSE); | |
1bfda055 | 297 | |
298 | Exit: | |
a355a365 | 299 | // Only be here if we fail to start Linux |
300 | Print (L"ERROR : Can not start the kernel. Status=0x%X\n", Status); | |
301 | ||
302 | // Free Runtimee Memory (kernel and FDT) | |
303 | return Status; | |
1bfda055 | 304 | } |