/** @file
Copyright (c) 2021, American Megatrends International LLC. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "LinuxBoot.h"
//16b60e5d-f1c5-42f0-9b34-08C81C430473
#define LINUX_BOOT_INITRD_GUID \
{ \
0x16b60e5d, 0xf1c5, 0x42f0, {0x9b, 0x34, 0x08, 0xc8, 0x1c, 0x43, 0x04, 0x73} \
}
#define LINUX_BOOT_KERNEL_GUID \
{ \
0x81339b04, 0xfa8c, 0x4be0, {0x9c, 0xa7, 0x91, 0x6f, 0xc5, 0x31, 0x9e, 0xb5} \
}
EFI_STATUS
EFIAPI
LoadLinuxCheckKernelSetup (
IN VOID *KernelSetup,
IN UINTN KernelSetupSize
);
VOID*
EFIAPI
LoadLinuxAllocateKernelSetupPages (
IN UINTN Pages
);
EFI_STATUS
EFIAPI
LoadLinuxInitializeKernelSetup (
IN VOID *KernelSetup
);
VOID*
EFIAPI
LoadLinuxAllocateKernelPages (
IN VOID *KernelSetup,
IN UINTN Pages
);
EFI_STATUS
EFIAPI
LoadLinuxSetCommandLine (
IN OUT VOID *KernelSetup,
IN CHAR8 *CommandLine
);
EFI_STATUS
EFIAPI
LoadLinux (
IN VOID *Kernel,
IN OUT VOID *KernelSetup
);
VOID*
EFIAPI
LoadLinuxAllocateInitrdPages (
IN VOID *KernelSetup,
IN UINTN Pages
);
EFI_GUID gLinuxBootInitrdFileGuid = LINUX_BOOT_INITRD_GUID;
EFI_GUID gLinuxBootKernelFileGuid = LINUX_BOOT_KERNEL_GUID;
//---------------------------------------------------------------------------
/**
Dump some hexadecimal data to the screen.
@note Function taken from ShellPkg/Library/UefiShellCommandLib/UefiShellCommandLib.c in EDKII
@param[in] Indent How many spaces to indent the output.
@param[in] Offset The offset of the printing.
@param[in] DataSize The size in bytes of UserData.
@param[in] UserData The data to print out.
**/
static
VOID
DumpHex (
IN UINTN Indent,
IN UINTN Offset,
IN UINTN DataSize,
IN VOID *UserData
)
{
UINT8 *Data;
CHAR8 Val[50];
CHAR8 Str[20];
UINT8 TempByte;
UINTN Size;
UINTN Index;
CHAR8 Hex[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
DEBUG((DEBUG_INFO, "%*a 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n", Indent, ""));
DEBUG((DEBUG_INFO, "%*a ------------------------------------------------\n", Indent, ""));
Data = UserData;
while (DataSize != 0) {
Size = 16;
if (Size > DataSize) {
Size = DataSize;
}
for (Index = 0; Index < Size; Index += 1) {
TempByte = Data[Index];
Val[Index * 3 + 0] = Hex[TempByte >> 4];
Val[Index * 3 + 1] = Hex[TempByte & 0xF];
Val[Index * 3 + 2] = (CHAR8) ((Index == 7) ? '-' : ' ');
Str[Index] = (CHAR8) ((TempByte < ' ' || TempByte > 'z') ? '.' : TempByte);
}
Val[Index * 3] = 0;
Str[Index] = 0;
DEBUG((DEBUG_INFO, "%*a%08X: %-48a %a\n", Indent, "", Offset, Val, Str));
Data += Size;
Offset += Size;
DataSize -= Size;
}
}
/**
* This function completes a minimal amount of the necessary BDS functions to prepare
* for booting the kernel.
*
* @param None
*
* @retval EFI_SUCCESS Successfully completed remaining tasks
* @return EFI_ERROR Could not complete BDS tasks
*/
EFI_STATUS
CompleteBdsTasks (
VOID
)
{
return EFI_SUCCESS;
}
/**
* This function will load and launch the Linux kernel from a BIOS FV.
*
* @note This function is not intended to return. Any exiting from this function indicates
* a problem loading or launching the kernel.
*
* @param None
*
* @return EFI_ERROR Any error code
*/
EFI_STATUS
LoadAndLaunchKernel (
VOID
)
{
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage = NULL;
EFI_STATUS Status;
EFI_HANDLE KernelHandle = NULL;
VOID *KernelBuffer = NULL;
VOID *KernelFfsBuffer = NULL;
UINTN KernelFfsSize = 0;
VOID *InitrdData = NULL;
VOID *InitrdBuffer = NULL;
UINTN InitrdSize = 0;
struct BootParams *BootParams = NULL;
struct BootParams *HandoverParams = NULL;
UINT32 StartOffset = 0;
UINT32 KernelLength = 0;
UINT8 *Temp;
UINT8 CmdLine[] = " ";
DEBUG((DEBUG_INFO, "LoadAndLaunchKernel Entry\n"));
///
/// Kernel load and preparation
///
DEBUG((DEBUG_INFO, "Preparing the kernel...\n"));
// Retrieve the kernel from the firmware volume
Status = GetSectionFromAnyFv(
&gLinuxBootKernelFileGuid,
EFI_SECTION_PE32,
0,
&KernelFfsBuffer,
&KernelFfsSize
);
DEBUG((DEBUG_INFO, "Status %r\n",Status));
DEBUG((DEBUG_INFO, "KernelFfsBuffer %x\n",KernelFfsBuffer));
DEBUG((DEBUG_INFO, "KernelFfsSize %x\n",KernelFfsSize));
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Could not retrieve kernel; %r.\n", Status));
goto FatalError;
}
DEBUG((DEBUG_INFO, "Loaded kernel to buffer at 0x%p with size 0x%X.\n", KernelFfsBuffer, KernelFfsSize));
DEBUG((DEBUG_INFO, "Printing first 0x%X bytes:\n", MIN(KernelFfsSize, 0x100)));
DumpHex(2, 0, MIN(0x100, KernelFfsSize), KernelFfsBuffer);
// Create a LoadImage protocol for the kernel
Status = gBS->LoadImage(TRUE, gImageHandle, NULL, KernelFfsBuffer, KernelFfsSize, &KernelHandle);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Could not create LoadImage for kernel %r\n", Status));
goto FatalError;
}
// Get the new LoadedImage protocol to retrieve information about the kernel
Status = gBS->HandleProtocol(KernelHandle, &gEfiLoadedImageProtocolGuid, (VOID **) &LoadedImage);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Could not get kernel LoadedImage protocol; %r\n", Status));
goto FatalError;
}
DEBUG((DEBUG_INFO, "Kernel LoadedImage information:\n"));
DEBUG((DEBUG_INFO, " ImageBase = 0x%p\n", LoadedImage->ImageBase));
DEBUG((DEBUG_INFO, " ImageSize = 0x%p\n", LoadedImage->ImageSize));
// Verify the kernel boot parameters from the LoadedImage and allocate an initalization buffer once verified
BootParams = (struct BootParams*) LoadedImage->ImageBase;
Status = LoadLinuxCheckKernelSetup((VOID *) BootParams, sizeof(struct BootParams));
if (EFI_ERROR (Status)) {
DEBUG((DEBUG_ERROR, "LoadLinuxCheckKernelSetup failed; %r.\n", Status));
goto FatalError;
}
HandoverParams = LoadLinuxAllocateKernelSetupPages(EFI_SIZE_TO_PAGES(KERNEL_SETUP_SIZE));
if (HandoverParams == NULL) {
DEBUG((DEBUG_ERROR, "Could not allocate memory for kernel handover parameters.\n"));
goto FatalError;
}
DEBUG((DEBUG_INFO, "Handover parameters allocated at 0x%p\n", HandoverParams));
gBS->CopyMem(&HandoverParams->Hdr, &BootParams->Hdr, sizeof(struct SetupHeader));
Status = LoadLinuxInitializeKernelSetup(HandoverParams);
if (EFI_ERROR (Status)) {
DEBUG((DEBUG_ERROR, "Unable to initialize the handover parameters; %r.\n", Status));
goto FatalError;
}
// Allocate space for the kernel and copy it into the new buffer
KernelBuffer = LoadLinuxAllocateKernelPages(HandoverParams, EFI_SIZE_TO_PAGES(HandoverParams->Hdr.InitSize));
if (KernelBuffer == NULL) {
DEBUG((DEBUG_ERROR, "Unable to allocate memory for kernel.\n"));
goto FatalError;
}
StartOffset = (HandoverParams->Hdr.SetupSecs + 1) * 512;
KernelLength = (UINT32) (KernelFfsSize - StartOffset);
Temp = (UINT8 *) LoadedImage->ImageBase;
DEBUG((DEBUG_INFO, "Kernel starts at offset 0x%X with length 0x%X\n", StartOffset, KernelLength));
gBS->CopyMem(KernelBuffer, (Temp + StartOffset), KernelLength);
DEBUG((DEBUG_INFO, "First 0x%X bytes of new kernel buffer contents:\n", MIN(0x100, KernelLength)));
DumpHex(2, 0, MIN(0x100, KernelLength), KernelBuffer);
// Prepare the command line
Status = LoadLinuxSetCommandLine(HandoverParams, (UINT8 *) &CmdLine);
if (EFI_ERROR (Status)) {
DEBUG((EFI_D_INFO, "Unable to set linux command line; %r.\n", Status));
goto FatalError;
}
HandoverParams->Hdr.Code32Start = (UINT32)(UINTN) KernelBuffer;
HandoverParams->Hdr.LoaderId = 0x21;
DEBUG((DEBUG_INFO, "Kernel loaded.\n"));
//
// Initrd load and preparation
//
DEBUG((DEBUG_INFO, "Preparing the initrd...\n"));
// Retrieve the initrd from the firmware volume
Status = GetSectionFromAnyFv(
&gLinuxBootInitrdFileGuid,
EFI_SECTION_RAW,
0,
&InitrdBuffer,
&InitrdSize
);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "Could not retrieve initrd; %r.\n", Status));
goto FatalError;
}
DEBUG((DEBUG_INFO, "Loaded initrd to buffer at 0x%p with size 0x%X.\n", InitrdBuffer, InitrdSize));
DEBUG((DEBUG_INFO, "Printing first 0x%X bytes:\n", MIN(0x100, InitrdSize)));
DumpHex(2, 0, MIN(0x100, InitrdSize), InitrdBuffer);
// Allocate the initrd for the kernel and copy it in
InitrdData = LoadLinuxAllocateInitrdPages(HandoverParams, EFI_SIZE_TO_PAGES(InitrdSize));
if (InitrdData == NULL) {
DEBUG((DEBUG_ERROR, "Unable to allocate memory for initrd.\n"));
goto FatalError;
}
gBS->CopyMem(InitrdData, InitrdBuffer, InitrdSize);
HandoverParams->Hdr.RamDiskStart = (UINT32)(UINTN) InitrdData;
HandoverParams->Hdr.RamDiskLen = (UINT32) InitrdSize;
DEBUG((DEBUG_INFO, "Initrd loaded.\n"));
DEBUG((DEBUG_INFO, "Printing first 0x%X bytes of initrd buffer:\n", MIN(0x100, InitrdSize)));
DumpHex(2, 0, MIN(0x100, InitrdSize), InitrdData);
// General cleanup before launching the kernel
gBS->FreePool(InitrdBuffer);
InitrdBuffer = NULL;
gBS->UnloadImage(KernelHandle);
gBS->FreePool(KernelFfsBuffer);
KernelFfsBuffer = NULL;
DEBUG((DEBUG_ERROR, "Launching the kernel\n"));
//
// Signal the EFI_EVENT_GROUP_READY_TO_BOOT event.
//
EfiSignalEventReadyToBoot();
// Launch the kernel
Status = LoadLinux(KernelBuffer, HandoverParams);
///
/// LoadLinux should never return if the kernel boots. Anything past here is an error scenario
///
DEBUG((DEBUG_ERROR, "ERROR: LoadLinux has returned with status; %r.\n", Status));
FatalError:
// Free everything
if (InitrdData != NULL) gBS->FreePages((EFI_PHYSICAL_ADDRESS) InitrdData, EFI_SIZE_TO_PAGES(InitrdSize));
if (KernelBuffer != NULL) gBS->FreePages((EFI_PHYSICAL_ADDRESS) KernelBuffer, EFI_SIZE_TO_PAGES(HandoverParams->Hdr.InitSize));
if (HandoverParams != NULL) gBS->FreePages((EFI_PHYSICAL_ADDRESS) HandoverParams, EFI_SIZE_TO_PAGES(KERNEL_SETUP_SIZE));
if (InitrdBuffer != NULL) gBS->FreePool(InitrdBuffer);
if (KernelHandle != NULL) gBS->UnloadImage(KernelHandle);
if (KernelFfsBuffer != NULL) gBS->FreePool(KernelFfsBuffer);
return EFI_NOT_FOUND;
}
/**
* This is the main function for this feature. This will handle finding and launching
* the Linux kernel.
*
* @note In general, this function will never return to BDS. The LINUXBOOT_ALLOW_RETURN_TO_BDS
* token will allow you to return to BDS if the kernel fails to launch for some reason.
*
* @param None
*
* @retval None
*/
EFI_STATUS
LinuxBootStart (
VOID
)
{
EFI_STATUS Status = EFI_SUCCESS;
// Finish BDS and then try to launch the kernel
//Status = CompleteBdsTasks();
if (!EFI_ERROR(Status)) {
LoadAndLaunchKernel();
}
DEBUG((DEBUG_ERROR, "-----------------------------------\n"));
DEBUG((DEBUG_ERROR, " ERROR: Kernel failed to launch.\n"));
DEBUG((DEBUG_ERROR, "-----------------------------------\n"));
return Status;
}
//---------------------------------------------------------------------------