]>
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 | ||
22 | STATIC | |
23 | EFI_STATUS | |
24 | GetARMLinuxMachineType ( | |
25 | IN BOOLEAN FdtSupported, | |
26 | OUT UINT32 *MachineType | |
27 | ) { | |
28 | if (FdtSupported) | |
29 | { | |
30 | // FDT requires that the machine type is set to the maximum 32-bit number. | |
31 | *MachineType = 0xFFFFFFFF; | |
32 | } | |
33 | else | |
34 | { | |
35 | // Non-FDT requires a specific machine type. | |
36 | // This OS Boot loader supports just one machine type, | |
37 | // but that could change in the future. | |
38 | *MachineType = PcdGet32(PcdArmMachineType); | |
39 | } | |
40 | ||
41 | return EFI_SUCCESS; | |
42 | } | |
43 | ||
44 | STATIC | |
45 | VOID | |
46 | SetupCoreTag( IN UINT32 PageSize ) | |
47 | { | |
48 | Params->header.size = tag_size(atag_core); | |
49 | Params->header.type = ATAG_CORE; | |
50 | ||
51 | Params->body.core_tag.flags = 1; /* ensure read-only */ | |
52 | Params->body.core_tag.pagesize = PageSize; /* systems PageSize (4k) */ | |
53 | Params->body.core_tag.rootdev = 0; /* zero root device (typically overridden from kernel command line )*/ | |
54 | ||
55 | Params = next_tag_address(Params); /* move pointer to next tag */ | |
56 | } | |
57 | ||
58 | STATIC | |
59 | VOID | |
60 | SetupMemTag( IN UINTN StartAddress, IN UINT32 Size ) | |
61 | { | |
62 | Params->header.size = tag_size(atag_mem); | |
63 | Params->header.type = ATAG_MEM; | |
64 | ||
65 | Params->body.mem_tag.start = StartAddress; /* Start of memory chunk for AtagMem */ | |
66 | Params->body.mem_tag.size = Size; /* Size of memory chunk for AtagMem */ | |
67 | ||
68 | Params = next_tag_address(Params); /* move pointer to next tag */ | |
69 | } | |
70 | ||
71 | STATIC | |
72 | VOID | |
73 | SetupCmdlineTag( IN CONST CHAR8 *CmdLine ) | |
74 | { | |
75 | UINT32 LineLength; | |
76 | ||
77 | // Increment the line length by 1 to account for the null string terminator character | |
78 | LineLength = AsciiStrLen(CmdLine) + 1; | |
79 | ||
80 | /* Check for NULL strings. | |
81 | * Do not insert a tag for an empty CommandLine, don't even modify the tag address pointer. | |
82 | * Remember, you have at least one null string terminator character. | |
83 | */ | |
84 | if( LineLength > 1 ) | |
85 | { | |
86 | Params->header.size = ((UINT32)sizeof(struct atag_header) + LineLength + (UINT32)3) >> 2; | |
87 | Params->header.type = ATAG_CMDLINE; | |
88 | ||
89 | /* place CommandLine into tag */ | |
90 | AsciiStrCpy(Params->body.cmdline_tag.cmdline, CmdLine); | |
91 | ||
92 | Params = next_tag_address(Params); /* move pointer to next tag */ | |
93 | } | |
94 | } | |
95 | ||
96 | STATIC | |
97 | VOID | |
98 | SetupEndTag( VOID ) | |
99 | { | |
100 | // Empty tag ends list; this has zero length and no body | |
101 | Params->header.type = ATAG_NONE; | |
102 | Params->header.size = 0; | |
103 | ||
104 | /* We can not calculate the next address by using the standard macro: | |
105 | * Params = next_tag_address(Params); | |
106 | * because it relies on the header.size, which here it is 0 (zero). | |
107 | * The easiest way is to add the sizeof(Params->header). | |
108 | */ | |
109 | Params = (struct atag *)((UINT32)Params + sizeof(Params->header)); | |
110 | } | |
111 | ||
112 | STATIC | |
113 | EFI_STATUS | |
114 | PrepareAtagList( | |
115 | IN OUT struct atag **AtagStartAddress, | |
116 | IN CONST CHAR8* CommandLineString, | |
117 | OUT UINT32 *AtagSize | |
118 | ) { | |
119 | LIST_ENTRY *ResourceLink; | |
120 | LIST_ENTRY ResourceList; | |
121 | BDS_SYSTEM_MEMORY_RESOURCE *Resource; | |
122 | ||
123 | // If no address supplied then this function will decide where to put it | |
124 | if( *AtagStartAddress == 0 ) | |
125 | { | |
126 | /* WARNING: At the time of writing (2010-July-30) the linux kernel expects | |
127 | * the atag list it in the first 1MB of memory and preferably at address 0x100. | |
128 | * This has a very high risk of overwriting UEFI code, but as | |
129 | * the linux kernel does not expect any runtime services from uefi | |
130 | * and there is no afterlife section following the linux kernel termination, | |
131 | * it does not matter if we stamp over that memory area. | |
132 | * | |
133 | * The proposed workaround is to create the atag list somewhere in boot services memory | |
134 | * and then transfer it to address 0x100 (or to runtime services memory) immediately | |
135 | * before starting the kernel. | |
136 | * An additional benefit of this is that when we copy the ATAG list to it's final place, | |
137 | * we can trim down the memory allocation size. Before we create the list we don't know | |
138 | * how much space it is going to take, so we are over-allocating space. | |
139 | */ | |
140 | *AtagStartAddress = (struct atag *) AllocatePool(ATAG_MAX_SIZE); | |
141 | } | |
142 | ||
143 | // Ensure the pointer is not NULL. | |
144 | ASSERT( *AtagStartAddress != (struct atag *)NULL ); | |
145 | ||
146 | // Ready to setup the atag list | |
147 | Params = *AtagStartAddress; | |
148 | ||
149 | // Standard core tag 4k PageSize | |
150 | SetupCoreTag( (UINT32)SIZE_4KB ); | |
151 | ||
152 | // Physical memory setup | |
153 | GetSystemMemoryResources(&ResourceList); | |
154 | ResourceLink = ResourceList.ForwardLink; | |
155 | while (ResourceLink != NULL && ResourceLink != &ResourceList) { | |
156 | Resource = (BDS_SYSTEM_MEMORY_RESOURCE*)ResourceList.ForwardLink; | |
157 | SetupMemTag( (UINT32)Resource->PhysicalStart, (UINT32)Resource->ResourceLength ); | |
158 | ResourceLink = ResourceLink->ForwardLink; | |
159 | } | |
160 | ||
161 | // CommandLine setting root device | |
162 | SetupCmdlineTag( CommandLineString ); | |
163 | ||
164 | // end of tags | |
165 | SetupEndTag(); | |
166 | ||
167 | // Calculate atag list size | |
168 | *AtagSize = (UINT32)Params - (UINT32)*AtagStartAddress + 1; | |
169 | ||
170 | return EFI_SUCCESS; | |
171 | } | |
172 | ||
173 | STATIC | |
174 | EFI_STATUS | |
175 | PreparePlatformHardware( VOID ) | |
176 | { | |
177 | //Note: Interrupts will be disabled by the GIC driver when ExitBootServices() will be called. | |
178 | ||
179 | // clean, invalidate, disable data cache | |
180 | ArmCleanInvalidateDataCache(); | |
181 | ArmDisableDataCache(); | |
182 | ||
183 | // Invalidate and disable the Instruction cache | |
184 | ArmInvalidateInstructionCache (); | |
185 | ArmDisableInstructionCache (); | |
186 | ||
187 | // turn off MMU | |
188 | ArmInvalidateTlb(); | |
189 | ArmDisableMmu(); | |
190 | ||
191 | return EFI_SUCCESS; | |
192 | } | |
193 | ||
194 | /************************************************* | |
195 | * R0, R1, R2 correspond to registers R0, R1, R2 | |
196 | *************************************************/ | |
197 | //STATIC | |
198 | EFI_STATUS | |
199 | StartLinuxKernel( IN VOID* KernelAddress, IN UINTN R0, IN UINTN R1, IN UINTN R2 ) | |
200 | { | |
201 | VOID (*Kernel)(UINT32 Zero, UINT32 Arch, UINTN AtagListParams); | |
202 | ||
203 | // set the kernel address | |
204 | Kernel = (VOID (*)(UINT32, UINT32, UINTN)) KernelAddress; | |
205 | ||
206 | // Outside BootServices, so can't use Print(); | |
207 | DEBUG((EFI_D_ERROR, "\nStarting the kernel:\n\n")); | |
208 | ||
209 | // jump to kernel with register set | |
210 | Kernel( R0, R1, R2 ); | |
211 | ||
212 | // Kernel should never exit | |
213 | // After Life services are not provided | |
214 | ASSERT( FALSE ); | |
215 | ||
216 | return EFI_SUCCESS; | |
217 | } | |
218 | ||
219 | EFI_STATUS BdsBootLinux( | |
220 | IN CONST CHAR16* LinuxKernel, | |
221 | IN CONST CHAR8* ATag, | |
222 | IN CONST CHAR16* Fdt | |
223 | ) { | |
224 | BDS_FILE LinuxKernelFile; | |
225 | BDS_FILE FdtFile; | |
226 | EFI_STATUS Status; | |
227 | VOID* LinuxImage; | |
228 | ||
229 | UINT32 KernelParamsSize; | |
230 | VOID* KernelParamsAddress = NULL; | |
231 | UINTN KernelParamsNewAddress; | |
232 | UINTN *AtagAddress; | |
233 | UINT32 MachineType; | |
234 | BOOLEAN FdtSupported = FALSE; | |
235 | EFI_HOB_RESOURCE_DESCRIPTOR *ResHob; | |
236 | ||
237 | // Load the Linux kernel from a device path | |
238 | Status = BdsLoadFilePath(LinuxKernel, &LinuxKernelFile); | |
239 | if (EFI_ERROR(Status)) { | |
240 | DEBUG ((EFI_D_ERROR, "ERROR: Do not find Linux kernel %s\n",LinuxKernel)); | |
241 | return Status; | |
242 | } | |
243 | ||
244 | // Copy the Linux Kernel from the raw file to Runtime memory | |
245 | Status = BdsCopyRawFileToRuntimeMemory(&LinuxKernelFile,&LinuxImage,NULL); | |
246 | if (EFI_ERROR(Status)) { | |
247 | goto Exit; | |
248 | } | |
249 | ||
250 | // Load the FDT binary from a device path | |
251 | Status = BdsLoadFilePath(Fdt, &FdtFile); | |
252 | if (!EFI_ERROR(Status)) { | |
253 | // Copy the FDT binary from the raw file to Runtime memory | |
254 | Status = BdsCopyRawFileToRuntimeMemory(&FdtFile,&KernelParamsAddress,&KernelParamsSize); | |
255 | if (EFI_ERROR(Status)) { | |
256 | goto Exit; | |
257 | } else { | |
258 | FdtSupported = TRUE; | |
259 | } | |
260 | } | |
261 | ||
262 | /********************************************************** | |
263 | * Setup the platform type | |
264 | **********************************************************/ | |
265 | Status = GetARMLinuxMachineType(FdtSupported, &MachineType); | |
266 | if(EFI_ERROR(Status)) | |
267 | { | |
268 | Print(L"ERROR : Can not prepare ARM Linux machine type. Status=0x%X\n", Status); | |
269 | goto Exit; | |
270 | } | |
271 | ||
272 | if (!FdtSupported) { | |
273 | /********************************************************** | |
274 | * Setup the ATAG list | |
275 | **********************************************************/ | |
276 | // By setting address=0 we leave the memory allocation to the function | |
277 | AtagAddress = 0; | |
278 | Status = PrepareAtagList( (struct atag **)&AtagAddress, ATag, &KernelParamsSize ); | |
279 | KernelParamsAddress = (VOID*)AtagAddress; | |
280 | if(EFI_ERROR(Status)) | |
281 | { | |
282 | Print(L"ERROR : Can not prepare ATAG list. Status=0x%X\n", Status); | |
283 | goto Exit; | |
284 | } | |
285 | } | |
286 | ||
287 | /********************************************************** | |
288 | * Switch off interrupts, caches, mmu, etc | |
289 | **********************************************************/ | |
290 | Status = PreparePlatformHardware(); | |
291 | if(EFI_ERROR(Status)) | |
292 | { | |
293 | Print(L"ERROR : Can not prepare platform hardware. Status=0x%X\n", Status); | |
294 | goto Exit; | |
295 | } | |
296 | ||
297 | // Initialize the ATag destination | |
298 | KernelParamsNewAddress = 0x100; | |
299 | ||
300 | // Update the ATag destination by finding the start address of the first System Memory Resource Descriptor Hob | |
301 | ResHob = (EFI_HOB_RESOURCE_DESCRIPTOR *)GetFirstHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR); | |
302 | while (ResHob != NULL) { | |
303 | if (ResHob->ResourceType == EFI_RESOURCE_SYSTEM_MEMORY) { | |
304 | KernelParamsNewAddress = (UINTN)ResHob->PhysicalStart + 0x100; | |
305 | break; | |
306 | } | |
307 | ResHob = (EFI_HOB_RESOURCE_DESCRIPTOR *)GetNextHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, (VOID *)((UINTN)ResHob + ResHob->Header.HobLength)); | |
308 | } | |
309 | ||
310 | // Shut down UEFI boot services. ExitBootServices() will notify every driver that created an event on | |
311 | // ExitBootServices event. Example the Interrupt DXE driver will disable the interrupts on this event. | |
312 | Status = ShutdownUefiBootServices(); | |
313 | if(EFI_ERROR(Status)) | |
314 | { | |
315 | Print(L"ERROR : Can not shutdown UEFI boot services. Status=0x%X\n", Status); | |
316 | goto Exit; | |
317 | } | |
318 | ||
319 | // Move the kernel parameters to any address inside the first 1MB. | |
320 | // This is necessary because the ARM Linux kernel requires | |
321 | // the FTD / ATAG List to reside entirely inside the first 1MB of | |
322 | // physical memory. | |
323 | CopyMem((VOID*)KernelParamsNewAddress, KernelParamsAddress, KernelParamsSize); | |
324 | ||
325 | //********************************************************** | |
326 | // * Start the Linux Kernel | |
327 | // ********************************************************** | |
328 | // Lift off ... | |
329 | Status = StartLinuxKernel(LinuxImage, (UINTN)0, (UINTN)MachineType, KernelParamsNewAddress ); | |
330 | ||
331 | // Only be here if we fail to start Linux | |
332 | DEBUG((EFI_D_ERROR, "ERROR : Can not start the kernel. Status=0x%X\n", Status)); | |
333 | ||
334 | Exit: | |
335 | // Free Runtimee Memory (kernel and FDT) | |
336 | return Status; | |
337 | } |