| /** @file | |
| Generic implementation of QemuLoadImageLib library class interface. | |
| Copyright (c) 2020, ARM Ltd. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Uefi.h> | |
| #include <Base.h> | |
| #include <Guid/QemuKernelLoaderFsMedia.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/FileHandleLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PrintLib.h> | |
| #include <Library/QemuLoadImageLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Protocol/DevicePath.h> | |
| #include <Protocol/LoadedImage.h> | |
| #include <Protocol/SimpleFileSystem.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; | |
| 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); | |
| } |