|
@@ -0,0 +1,412 @@
|
|
|
+/** @file
|
|
|
+
|
|
|
+Copyright (c) 2021, American Megatrends International LLC. All rights reserved.<BR>
|
|
|
+SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
+
|
|
|
+**/
|
|
|
+
|
|
|
+
|
|
|
+#include <PiDxe.h>
|
|
|
+#include <Uefi.h>
|
|
|
+#include <Library/BaseLib.h>
|
|
|
+#include <Library/BaseMemoryLib.h>
|
|
|
+#include <Library/DebugLib.h>
|
|
|
+#include <Library/UefiRuntimeServicesTableLib.h>
|
|
|
+#include <Library/MemoryAllocationLib.h>
|
|
|
+#include <Library/UefiBootServicesTableLib.h>
|
|
|
+#include <Guid/DxeServices.h>
|
|
|
+#include <Library/DxeServicesTableLib.h>
|
|
|
+#include <Library/UefiLib.h>
|
|
|
+#include <Protocol/FirmwareVolume2.h>
|
|
|
+#include <Protocol/LoadedImage.h>
|
|
|
+#include <Guid/MemoryTypeInformation.h>
|
|
|
+#include <Pi/PiDxeCis.h>
|
|
|
+#include <Pi/PiHob.h>
|
|
|
+#include <Library/PcdLib.h>
|
|
|
+#include <Library/DxeServicesLib.h>
|
|
|
+#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;
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|