]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPkg/Library/BdsLib/BdsLinuxLoader.c
Sync up ArmPkg with patch from mailing list. Changed name of BdsLib.h to BdsUnixLib...
[mirror_edk2.git] / ArmPkg / Library / BdsLib / BdsLinuxLoader.c
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 }