]> git.proxmox.com Git - mirror_edk2.git/blame - ArmPkg/Library/BdsLib/BdsLinuxLoader.c
MdeModulePkg: Add unload support for SnpDxe and fix NULL ImageHandle in EfiLibInstall...
[mirror_edk2.git] / ArmPkg / Library / BdsLib / BdsLinuxLoader.c
CommitLineData
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
28STATIC LINUX_ATAG *mLinuxKernelCurrentAtag;
1bfda055 29
30STATIC
31VOID
a355a365 32SetupCoreTag (
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
47STATIC
48VOID
a355a365 49SetupMemTag (
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
64STATIC
65VOID
a355a365 66SetupCmdlineTag (
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
91STATIC
92VOID
a355a365 93SetupEndTag (
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
109STATIC
110EFI_STATUS
a355a365 111PrepareAtagList (
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
160STATIC
161EFI_STATUS
a355a365 162PreparePlatformHardware (
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**/
194EFI_STATUS
195BdsBootLinux (
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
298Exit:
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}