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