/** @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.
Copyright (c) 2020, ARM Ltd. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include #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; // // 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); }