/** @file Generic implementation of QemuLoadImageLib library class interface. 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; typedef struct { VENDOR_DEVICE_PATH VenMediaNode; EFI_DEVICE_PATH_PROTOCOL EndNode; } SINGLE_VENMEDIA_NODE_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 CONST SINGLE_VENMEDIA_NODE_DEVPATH mQemuKernelLoaderFsDevicePath = { { { MEDIA_DEVICE_PATH, MEDIA_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH) } }, QEMU_KERNEL_LOADER_FS_MEDIA_GUID }, { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL) } } }; STATIC EFI_STATUS GetQemuKernelLoaderBlobSize ( IN EFI_FILE_HANDLE Root, IN CHAR16 *FileName, OUT UINTN *Size ) { EFI_STATUS Status; EFI_FILE_HANDLE FileHandle; UINT64 FileSize; Status = Root->Open (Root, &FileHandle, FileName, EFI_FILE_MODE_READ, 0); if (EFI_ERROR (Status)) { return Status; } Status = FileHandleGetSize (FileHandle, &FileSize); if (EFI_ERROR (Status)) { goto CloseFile; } if (FileSize > MAX_UINTN) { Status = EFI_UNSUPPORTED; goto CloseFile; } *Size = (UINTN)FileSize; Status = EFI_SUCCESS; CloseFile: FileHandle->Close (FileHandle); return Status; } STATIC EFI_STATUS ReadWholeQemuKernelLoaderBlob ( IN EFI_FILE_HANDLE Root, IN CHAR16 *FileName, IN UINTN Size, OUT VOID *Buffer ) { EFI_STATUS Status; EFI_FILE_HANDLE FileHandle; UINTN ReadSize; Status = Root->Open (Root, &FileHandle, FileName, EFI_FILE_MODE_READ, 0); if (EFI_ERROR (Status)) { return Status; } ReadSize = Size; Status = FileHandle->Read (FileHandle, &ReadSize, Buffer); if (EFI_ERROR (Status)) { goto CloseFile; } if (ReadSize != Size) { Status = EFI_PROTOCOL_ERROR; goto CloseFile; } Status = EFI_SUCCESS; CloseFile: FileHandle->Close (FileHandle); return Status; } /** 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. @retval EFI_ACCESS_DENIED The underlying LoadImage boot service call returned EFI_SECURITY_VIOLATION, and the image was unloaded again. @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; EFI_DEVICE_PATH_PROTOCOL *DevicePathNode; EFI_HANDLE FsVolumeHandle; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FsProtocol; EFI_FILE_HANDLE Root; 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_SECURITY_VIOLATION: // // In this case, the image was loaded but failed to authenticate. // Status = EFI_ACCESS_DENIED; goto UnloadImage; default: DEBUG (( Status == EFI_NOT_FOUND ? DEBUG_INFO : 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); // // Open the Qemu Kernel Loader abstract filesystem (volume) which will be // used to query the "initrd" and to read the "cmdline" synthetic files. // DevicePathNode = (EFI_DEVICE_PATH_PROTOCOL *)&mQemuKernelLoaderFsDevicePath; Status = gBS->LocateDevicePath ( &gEfiSimpleFileSystemProtocolGuid, &DevicePathNode, &FsVolumeHandle ); if (EFI_ERROR (Status)) { goto UnloadImage; } Status = gBS->HandleProtocol ( FsVolumeHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&FsProtocol ); if (EFI_ERROR (Status)) { goto UnloadImage; } Status = FsProtocol->OpenVolume (FsVolumeHandle, &Root); if (EFI_ERROR (Status)) { goto UnloadImage; } Status = GetQemuKernelLoaderBlobSize (Root, L"cmdline", &CommandLineSize); if (EFI_ERROR (Status)) { goto CloseRoot; } if (CommandLineSize == 0) { KernelLoadedImage->LoadOptionsSize = 0; } else { CommandLine = AllocatePool (CommandLineSize); if (CommandLine == NULL) { Status = EFI_OUT_OF_RESOURCES; goto CloseRoot; } Status = ReadWholeQemuKernelLoaderBlob ( Root, L"cmdline", CommandLineSize, CommandLine ); if (EFI_ERROR (Status)) { goto FreeCommandLine; } // // 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); } Status = GetQemuKernelLoaderBlobSize (Root, L"initrd", &InitrdSize); if (EFI_ERROR (Status)) { goto FreeCommandLine; } 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); } CloseRoot: Root->Close (Root); 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 ) { return gBS->StartImage ( *ImageHandle, NULL, // ExitDataSize NULL // ExitData ); } /** 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 (EFI_ERROR (Status)) { return EFI_INVALID_PARAMETER; } if (KernelLoadedImage->LoadOptions != NULL) { FreePool (KernelLoadedImage->LoadOptions); KernelLoadedImage->LoadOptions = NULL; } KernelLoadedImage->LoadOptionsSize = 0; return gBS->UnloadImage (ImageHandle); }