/** @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; } //---------------------------------------------------------------------------