| /** @file | |
| X86 specific implementation of QemuLoadImageLib library class interface | |
| with support for loading mixed mode images and non-EFI stub images | |
| Note that this implementation reads the cmdline (and possibly kernel, setup | |
| data, and initrd in the legacy boot mode) from fw_cfg directly. | |
| Copyright (c) 2006 - 2015, Intel Corporation. All rights reserved.<BR> | |
| Copyright (c) 2020, ARM Ltd. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Uefi.h> | |
| #include <Guid/QemuKernelLoaderFsMedia.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/LoadLinuxLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PrintLib.h> | |
| #include <Library/QemuFwCfgLib.h> | |
| #include <Library/QemuLoadImageLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Protocol/DevicePath.h> | |
| #include <Protocol/LoadedImage.h> | |
| #include <Protocol/OvmfLoadedX86LinuxKernel.h> | |
| #pragma pack (1) | |
| typedef struct { | |
| EFI_DEVICE_PATH_PROTOCOL FilePathHeader; | |
| CHAR16 FilePath[ARRAY_SIZE (L"kernel")]; | |
| } KERNEL_FILE_DEVPATH; | |
| typedef struct { | |
| VENDOR_DEVICE_PATH VenMediaNode; | |
| KERNEL_FILE_DEVPATH FileNode; | |
| EFI_DEVICE_PATH_PROTOCOL EndNode; | |
| } KERNEL_VENMEDIA_FILE_DEVPATH; | |
| #pragma pack () | |
| STATIC CONST KERNEL_VENMEDIA_FILE_DEVPATH mKernelDevicePath = { | |
| { | |
| { | |
| MEDIA_DEVICE_PATH, MEDIA_VENDOR_DP, | |
| { sizeof (VENDOR_DEVICE_PATH) } | |
| }, | |
| QEMU_KERNEL_LOADER_FS_MEDIA_GUID | |
| }, { | |
| { | |
| MEDIA_DEVICE_PATH, MEDIA_FILEPATH_DP, | |
| { sizeof (KERNEL_FILE_DEVPATH) } | |
| }, | |
| L"kernel", | |
| }, { | |
| END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
| { sizeof (EFI_DEVICE_PATH_PROTOCOL) } | |
| } | |
| }; | |
| STATIC | |
| VOID | |
| FreeLegacyImage ( | |
| IN OVMF_LOADED_X86_LINUX_KERNEL *LoadedImage | |
| ) | |
| { | |
| if (LoadedImage->SetupBuf != NULL) { | |
| FreePages ( | |
| LoadedImage->SetupBuf, | |
| EFI_SIZE_TO_PAGES (LoadedImage->SetupSize) | |
| ); | |
| } | |
| if (LoadedImage->KernelBuf != NULL) { | |
| FreePages ( | |
| LoadedImage->KernelBuf, | |
| EFI_SIZE_TO_PAGES (LoadedImage->KernelInitialSize) | |
| ); | |
| } | |
| if (LoadedImage->CommandLine != NULL) { | |
| FreePages ( | |
| LoadedImage->CommandLine, | |
| EFI_SIZE_TO_PAGES (LoadedImage->CommandLineSize) | |
| ); | |
| } | |
| if (LoadedImage->InitrdData != NULL) { | |
| FreePages ( | |
| LoadedImage->InitrdData, | |
| EFI_SIZE_TO_PAGES (LoadedImage->InitrdSize) | |
| ); | |
| } | |
| } | |
| STATIC | |
| EFI_STATUS | |
| QemuLoadLegacyImage ( | |
| OUT EFI_HANDLE *ImageHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN KernelSize; | |
| UINTN SetupSize; | |
| OVMF_LOADED_X86_LINUX_KERNEL *LoadedImage; | |
| QemuFwCfgSelectItem (QemuFwCfgItemKernelSize); | |
| KernelSize = (UINTN)QemuFwCfgRead32 (); | |
| QemuFwCfgSelectItem (QemuFwCfgItemKernelSetupSize); | |
| SetupSize = (UINTN)QemuFwCfgRead32 (); | |
| if ((KernelSize == 0) || (SetupSize == 0)) { | |
| DEBUG ((DEBUG_INFO, "qemu -kernel was not used.\n")); | |
| return EFI_NOT_FOUND; | |
| } | |
| LoadedImage = AllocateZeroPool (sizeof (*LoadedImage)); | |
| if (LoadedImage == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| LoadedImage->SetupSize = SetupSize; | |
| LoadedImage->SetupBuf = LoadLinuxAllocateKernelSetupPages ( | |
| EFI_SIZE_TO_PAGES (LoadedImage->SetupSize) | |
| ); | |
| if (LoadedImage->SetupBuf == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Unable to allocate memory for kernel setup!\n")); | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto FreeImageDesc; | |
| } | |
| DEBUG ((DEBUG_INFO, "Setup size: 0x%x\n", (UINT32)LoadedImage->SetupSize)); | |
| DEBUG ((DEBUG_INFO, "Reading kernel setup image ...")); | |
| QemuFwCfgSelectItem (QemuFwCfgItemKernelSetupData); | |
| QemuFwCfgReadBytes (LoadedImage->SetupSize, LoadedImage->SetupBuf); | |
| DEBUG ((DEBUG_INFO, " [done]\n")); | |
| Status = LoadLinuxCheckKernelSetup ( | |
| LoadedImage->SetupBuf, | |
| LoadedImage->SetupSize | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeImage; | |
| } | |
| Status = LoadLinuxInitializeKernelSetup (LoadedImage->SetupBuf); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeImage; | |
| } | |
| LoadedImage->KernelInitialSize = LoadLinuxGetKernelSize ( | |
| LoadedImage->SetupBuf, | |
| KernelSize | |
| ); | |
| if (LoadedImage->KernelInitialSize == 0) { | |
| Status = EFI_UNSUPPORTED; | |
| goto FreeImage; | |
| } | |
| LoadedImage->KernelBuf = LoadLinuxAllocateKernelPages ( | |
| LoadedImage->SetupBuf, | |
| EFI_SIZE_TO_PAGES (LoadedImage->KernelInitialSize) | |
| ); | |
| if (LoadedImage->KernelBuf == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Unable to allocate memory for kernel!\n")); | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto FreeImage; | |
| } | |
| DEBUG ((DEBUG_INFO, "Kernel size: 0x%x\n", (UINT32)KernelSize)); | |
| DEBUG ((DEBUG_INFO, "Reading kernel image ...")); | |
| QemuFwCfgSelectItem (QemuFwCfgItemKernelData); | |
| QemuFwCfgReadBytes (KernelSize, LoadedImage->KernelBuf); | |
| DEBUG ((DEBUG_INFO, " [done]\n")); | |
| QemuFwCfgSelectItem (QemuFwCfgItemCommandLineSize); | |
| LoadedImage->CommandLineSize = (UINTN)QemuFwCfgRead32 (); | |
| if (LoadedImage->CommandLineSize > 0) { | |
| LoadedImage->CommandLine = LoadLinuxAllocateCommandLinePages ( | |
| EFI_SIZE_TO_PAGES ( | |
| LoadedImage->CommandLineSize | |
| ) | |
| ); | |
| if (LoadedImage->CommandLine == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Unable to allocate memory for kernel command line!\n")); | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto FreeImage; | |
| } | |
| QemuFwCfgSelectItem (QemuFwCfgItemCommandLineData); | |
| QemuFwCfgReadBytes (LoadedImage->CommandLineSize, LoadedImage->CommandLine); | |
| } | |
| Status = LoadLinuxSetCommandLine ( | |
| LoadedImage->SetupBuf, | |
| LoadedImage->CommandLine | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeImage; | |
| } | |
| QemuFwCfgSelectItem (QemuFwCfgItemInitrdSize); | |
| LoadedImage->InitrdSize = (UINTN)QemuFwCfgRead32 (); | |
| if (LoadedImage->InitrdSize > 0) { | |
| LoadedImage->InitrdData = LoadLinuxAllocateInitrdPages ( | |
| LoadedImage->SetupBuf, | |
| EFI_SIZE_TO_PAGES (LoadedImage->InitrdSize) | |
| ); | |
| if (LoadedImage->InitrdData == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Unable to allocate memory for initrd!\n")); | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto FreeImage; | |
| } | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "Initrd size: 0x%x\n", | |
| (UINT32)LoadedImage->InitrdSize | |
| )); | |
| DEBUG ((DEBUG_INFO, "Reading initrd image ...")); | |
| QemuFwCfgSelectItem (QemuFwCfgItemInitrdData); | |
| QemuFwCfgReadBytes (LoadedImage->InitrdSize, LoadedImage->InitrdData); | |
| DEBUG ((DEBUG_INFO, " [done]\n")); | |
| } | |
| Status = LoadLinuxSetInitrd ( | |
| LoadedImage->SetupBuf, | |
| LoadedImage->InitrdData, | |
| LoadedImage->InitrdSize | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeImage; | |
| } | |
| *ImageHandle = NULL; | |
| Status = gBS->InstallProtocolInterface ( | |
| ImageHandle, | |
| &gOvmfLoadedX86LinuxKernelProtocolGuid, | |
| EFI_NATIVE_INTERFACE, | |
| LoadedImage | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeImage; | |
| } | |
| return EFI_SUCCESS; | |
| FreeImage: | |
| FreeLegacyImage (LoadedImage); | |
| FreeImageDesc: | |
| FreePool (LoadedImage); | |
| return Status; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| QemuStartLegacyImage ( | |
| IN EFI_HANDLE ImageHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| OVMF_LOADED_X86_LINUX_KERNEL *LoadedImage; | |
| Status = gBS->OpenProtocol ( | |
| ImageHandle, | |
| &gOvmfLoadedX86LinuxKernelProtocolGuid, | |
| (VOID **)&LoadedImage, | |
| gImageHandle, // AgentHandle | |
| NULL, // ControllerHandle | |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| return LoadLinux (LoadedImage->KernelBuf, LoadedImage->SetupBuf); | |
| } | |
| STATIC | |
| EFI_STATUS | |
| QemuUnloadLegacyImage ( | |
| IN EFI_HANDLE ImageHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| OVMF_LOADED_X86_LINUX_KERNEL *LoadedImage; | |
| Status = gBS->OpenProtocol ( | |
| ImageHandle, | |
| &gOvmfLoadedX86LinuxKernelProtocolGuid, | |
| (VOID **)&LoadedImage, | |
| gImageHandle, // AgentHandle | |
| NULL, // ControllerHandle | |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| Status = gBS->UninstallProtocolInterface ( | |
| ImageHandle, | |
| &gOvmfLoadedX86LinuxKernelProtocolGuid, | |
| LoadedImage | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| FreeLegacyImage (LoadedImage); | |
| FreePool (LoadedImage); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Download the kernel, the initial ramdisk, and the kernel command line from | |
| QEMU's fw_cfg. The kernel will be instructed via its command line to load | |
| the initrd from the same Simple FileSystem where the kernel was loaded from. | |
| @param[out] ImageHandle The image handle that was allocated for | |
| loading the image | |
| @retval EFI_SUCCESS The image was loaded successfully. | |
| @retval EFI_NOT_FOUND Kernel image was not found. | |
| @retval EFI_OUT_OF_RESOURCES Memory allocation failed. | |
| @retval EFI_PROTOCOL_ERROR Unterminated kernel command line. | |
| @return Error codes from any of the underlying | |
| functions. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| QemuLoadKernelImage ( | |
| OUT EFI_HANDLE *ImageHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_HANDLE KernelImageHandle; | |
| EFI_LOADED_IMAGE_PROTOCOL *KernelLoadedImage; | |
| UINTN CommandLineSize; | |
| CHAR8 *CommandLine; | |
| UINTN InitrdSize; | |
| // | |
| // Redundant assignment to work around GCC48/GCC49 limitations. | |
| // | |
| CommandLine = NULL; | |
| // | |
| // Load the image. This should call back into the QEMU EFI loader file system. | |
| // | |
| Status = gBS->LoadImage ( | |
| FALSE, // BootPolicy: exact match required | |
| gImageHandle, // ParentImageHandle | |
| (EFI_DEVICE_PATH_PROTOCOL *)&mKernelDevicePath, | |
| NULL, // SourceBuffer | |
| 0, // SourceSize | |
| &KernelImageHandle | |
| ); | |
| switch (Status) { | |
| case EFI_SUCCESS: | |
| break; | |
| case EFI_NOT_FOUND: | |
| // | |
| // The image does not exist - no -kernel image was supplied via the | |
| // command line so no point in invoking the legacy fallback | |
| // | |
| return EFI_NOT_FOUND; | |
| case EFI_SECURITY_VIOLATION: | |
| // | |
| // Since the image has been loaded, we need to unload it before proceeding | |
| // to the EFI_ACCESS_DENIED case below. | |
| // | |
| gBS->UnloadImage (KernelImageHandle); | |
| // | |
| // Fall through | |
| // | |
| case EFI_ACCESS_DENIED: | |
| // | |
| // We are running with UEFI secure boot enabled, and the image failed to | |
| // authenticate. For compatibility reasons, we fall back to the legacy | |
| // loader in this case. | |
| // | |
| // Fall through | |
| // | |
| case EFI_UNSUPPORTED: | |
| // | |
| // The image is not natively supported or cross-type supported. Let's try | |
| // loading it using the loader that parses the bzImage metadata directly. | |
| // | |
| Status = QemuLoadLegacyImage (&KernelImageHandle); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: QemuLoadLegacyImage(): %r\n", | |
| __func__, | |
| Status | |
| )); | |
| return Status; | |
| } | |
| *ImageHandle = KernelImageHandle; | |
| return EFI_SUCCESS; | |
| default: | |
| DEBUG ((DEBUG_ERROR, "%a: LoadImage(): %r\n", __func__, Status)); | |
| return Status; | |
| } | |
| // | |
| // Construct the kernel command line. | |
| // | |
| Status = gBS->OpenProtocol ( | |
| KernelImageHandle, | |
| &gEfiLoadedImageProtocolGuid, | |
| (VOID **)&KernelLoadedImage, | |
| gImageHandle, // AgentHandle | |
| NULL, // ControllerHandle | |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| QemuFwCfgSelectItem (QemuFwCfgItemCommandLineSize); | |
| CommandLineSize = (UINTN)QemuFwCfgRead32 (); | |
| if (CommandLineSize == 0) { | |
| KernelLoadedImage->LoadOptionsSize = 0; | |
| } else { | |
| CommandLine = AllocatePool (CommandLineSize); | |
| if (CommandLine == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto UnloadImage; | |
| } | |
| QemuFwCfgSelectItem (QemuFwCfgItemCommandLineData); | |
| QemuFwCfgReadBytes (CommandLineSize, CommandLine); | |
| // | |
| // Verify NUL-termination of the command line. | |
| // | |
| if (CommandLine[CommandLineSize - 1] != '\0') { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: kernel command line is not NUL-terminated\n", | |
| __func__ | |
| )); | |
| Status = EFI_PROTOCOL_ERROR; | |
| goto FreeCommandLine; | |
| } | |
| // | |
| // Drop the terminating NUL, convert to UTF-16. | |
| // | |
| KernelLoadedImage->LoadOptionsSize = (UINT32)((CommandLineSize - 1) * 2); | |
| } | |
| QemuFwCfgSelectItem (QemuFwCfgItemInitrdSize); | |
| InitrdSize = (UINTN)QemuFwCfgRead32 (); | |
| if (InitrdSize > 0) { | |
| // | |
| // Append ' initrd=initrd' in UTF-16. | |
| // | |
| KernelLoadedImage->LoadOptionsSize += sizeof (L" initrd=initrd") - 2; | |
| } | |
| if (KernelLoadedImage->LoadOptionsSize == 0) { | |
| KernelLoadedImage->LoadOptions = NULL; | |
| } else { | |
| // | |
| // NUL-terminate in UTF-16. | |
| // | |
| KernelLoadedImage->LoadOptionsSize += 2; | |
| KernelLoadedImage->LoadOptions = AllocatePool ( | |
| KernelLoadedImage->LoadOptionsSize | |
| ); | |
| if (KernelLoadedImage->LoadOptions == NULL) { | |
| KernelLoadedImage->LoadOptionsSize = 0; | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto FreeCommandLine; | |
| } | |
| UnicodeSPrintAsciiFormat ( | |
| KernelLoadedImage->LoadOptions, | |
| KernelLoadedImage->LoadOptionsSize, | |
| "%a%a", | |
| (CommandLineSize == 0) ? "" : CommandLine, | |
| (InitrdSize == 0) ? "" : " initrd=initrd" | |
| ); | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: command line: \"%s\"\n", | |
| __func__, | |
| (CHAR16 *)KernelLoadedImage->LoadOptions | |
| )); | |
| } | |
| *ImageHandle = KernelImageHandle; | |
| Status = EFI_SUCCESS; | |
| FreeCommandLine: | |
| if (CommandLineSize > 0) { | |
| FreePool (CommandLine); | |
| } | |
| UnloadImage: | |
| if (EFI_ERROR (Status)) { | |
| gBS->UnloadImage (KernelImageHandle); | |
| } | |
| return Status; | |
| } | |
| /** | |
| Transfer control to a kernel image loaded with QemuLoadKernelImage () | |
| @param[in,out] ImageHandle Handle of image to be started. May assume a | |
| different value on return if the image was | |
| reloaded. | |
| @retval EFI_INVALID_PARAMETER ImageHandle is either an invalid image handle | |
| or the image has already been initialized with | |
| StartImage | |
| @retval EFI_SECURITY_VIOLATION The current platform policy specifies that the | |
| image should not be started. | |
| @return Error codes returned by the started image | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| QemuStartKernelImage ( | |
| IN OUT EFI_HANDLE *ImageHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| OVMF_LOADED_X86_LINUX_KERNEL *LoadedImage; | |
| Status = gBS->OpenProtocol ( | |
| *ImageHandle, | |
| &gOvmfLoadedX86LinuxKernelProtocolGuid, | |
| (VOID **)&LoadedImage, | |
| gImageHandle, // AgentHandle | |
| NULL, // ControllerHandle | |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
| ); | |
| if (!EFI_ERROR (Status)) { | |
| return QemuStartLegacyImage (*ImageHandle); | |
| } | |
| Status = gBS->StartImage ( | |
| *ImageHandle, | |
| NULL, // ExitDataSize | |
| NULL // ExitData | |
| ); | |
| #ifdef MDE_CPU_IA32 | |
| if (Status == EFI_UNSUPPORTED) { | |
| EFI_HANDLE KernelImageHandle; | |
| // | |
| // On IA32, EFI_UNSUPPORTED means that the image's machine type is X64 while | |
| // we are expecting a IA32 one, and the StartImage () boot service is unable | |
| // to handle it, either because the image does not have the special .compat | |
| // PE/COFF section that Linux specifies for mixed mode capable images, or | |
| // because we are running without the support code for that. So load the | |
| // image again, using the legacy loader, and unload the normally loaded | |
| // image before starting the legacy one. | |
| // | |
| Status = QemuLoadLegacyImage (&KernelImageHandle); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // Note: no change to (*ImageHandle), the caller will release it. | |
| // | |
| return Status; | |
| } | |
| // | |
| // Swap in the legacy-loaded image. | |
| // | |
| QemuUnloadKernelImage (*ImageHandle); | |
| *ImageHandle = KernelImageHandle; | |
| return QemuStartLegacyImage (KernelImageHandle); | |
| } | |
| #endif | |
| return Status; | |
| } | |
| /** | |
| Unloads an image loaded with QemuLoadKernelImage (). | |
| @param ImageHandle Handle that identifies the image to be | |
| unloaded. | |
| @retval EFI_SUCCESS The image has been unloaded. | |
| @retval EFI_UNSUPPORTED The image has been started, and does not | |
| support unload. | |
| @retval EFI_INVALID_PARAMETER ImageHandle is not a valid image handle. | |
| @return Exit code from the image's unload function. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| QemuUnloadKernelImage ( | |
| IN EFI_HANDLE ImageHandle | |
| ) | |
| { | |
| EFI_LOADED_IMAGE_PROTOCOL *KernelLoadedImage; | |
| EFI_STATUS Status; | |
| Status = gBS->OpenProtocol ( | |
| ImageHandle, | |
| &gEfiLoadedImageProtocolGuid, | |
| (VOID **)&KernelLoadedImage, | |
| gImageHandle, // AgentHandle | |
| NULL, // ControllerHandle | |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
| ); | |
| if (Status == EFI_UNSUPPORTED) { | |
| // | |
| // The handle exists but does not have an instance of the standard loaded | |
| // image protocol installed on it. Attempt to unload it as a legacy image | |
| // instead. | |
| // | |
| return QemuUnloadLegacyImage (ImageHandle); | |
| } | |
| if (EFI_ERROR (Status)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // We are unloading a normal, non-legacy loaded image, either on behalf of | |
| // an external caller, or called from QemuStartKernelImage() on IA32, while | |
| // switching from the normal to the legacy method to load and start a X64 | |
| // image. | |
| // | |
| if (KernelLoadedImage->LoadOptions != NULL) { | |
| FreePool (KernelLoadedImage->LoadOptions); | |
| KernelLoadedImage->LoadOptions = NULL; | |
| } | |
| KernelLoadedImage->LoadOptionsSize = 0; | |
| return gBS->UnloadImage (ImageHandle); | |
| } |