/** @file | |
DXE driver to expose the 'kernel', 'initrd' and 'cmdline' blobs | |
provided by QEMU as files in an abstract file system | |
Copyright (C) 2014-2016, Red Hat, Inc. | |
Copyright (C) 2020, Arm, Limited. | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <PiDxe.h> | |
#include <Guid/FileInfo.h> | |
#include <Guid/FileSystemInfo.h> | |
#include <Guid/FileSystemVolumeLabelInfo.h> | |
#include <Guid/LinuxEfiInitrdMedia.h> | |
#include <Guid/QemuKernelLoaderFsMedia.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/BlobVerifierLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/QemuFwCfgLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiRuntimeServicesTableLib.h> | |
#include <Protocol/DevicePath.h> | |
#include <Protocol/LoadFile2.h> | |
#include <Protocol/SimpleFileSystem.h> | |
// | |
// Static data that hosts the fw_cfg blobs and serves file requests. | |
// | |
typedef enum { | |
KernelBlobTypeKernel, | |
KernelBlobTypeInitrd, | |
KernelBlobTypeCommandLine, | |
KernelBlobTypeMax | |
} KERNEL_BLOB_TYPE; | |
typedef struct { | |
CONST CHAR16 Name[8]; | |
struct { | |
FIRMWARE_CONFIG_ITEM CONST SizeKey; | |
FIRMWARE_CONFIG_ITEM CONST DataKey; | |
UINT32 Size; | |
} FwCfgItem[2]; | |
UINT32 Size; | |
UINT8 *Data; | |
} KERNEL_BLOB; | |
STATIC KERNEL_BLOB mKernelBlob[KernelBlobTypeMax] = { | |
{ | |
L"kernel", | |
{ | |
{ QemuFwCfgItemKernelSetupSize, QemuFwCfgItemKernelSetupData, }, | |
{ QemuFwCfgItemKernelSize, QemuFwCfgItemKernelData, }, | |
} | |
}, { | |
L"initrd", | |
{ | |
{ QemuFwCfgItemInitrdSize, QemuFwCfgItemInitrdData, }, | |
} | |
}, { | |
L"cmdline", | |
{ | |
{ QemuFwCfgItemCommandLineSize, QemuFwCfgItemCommandLineData, }, | |
} | |
} | |
}; | |
STATIC UINT64 mTotalBlobBytes; | |
// | |
// Device path for the handle that incorporates our "EFI stub filesystem". | |
// | |
#pragma pack (1) | |
typedef struct { | |
VENDOR_DEVICE_PATH VenMediaNode; | |
EFI_DEVICE_PATH_PROTOCOL EndNode; | |
} SINGLE_VENMEDIA_NODE_DEVPATH; | |
#pragma pack () | |
STATIC CONST SINGLE_VENMEDIA_NODE_DEVPATH mFileSystemDevicePath = { | |
{ | |
{ | |
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 CONST SINGLE_VENMEDIA_NODE_DEVPATH mInitrdDevicePath = { | |
{ | |
{ | |
MEDIA_DEVICE_PATH, MEDIA_VENDOR_DP, | |
{ sizeof (VENDOR_DEVICE_PATH) } | |
}, | |
LINUX_EFI_INITRD_MEDIA_GUID | |
}, { | |
END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
{ sizeof (EFI_DEVICE_PATH_PROTOCOL) } | |
} | |
}; | |
// | |
// The "file in the EFI stub filesystem" abstraction. | |
// | |
STATIC EFI_TIME mInitTime; | |
#define STUB_FILE_SIG SIGNATURE_64 ('S', 'T', 'U', 'B', 'F', 'I', 'L', 'E') | |
typedef struct { | |
UINT64 Signature; // Carries STUB_FILE_SIG. | |
KERNEL_BLOB_TYPE BlobType; // Index into mKernelBlob. KernelBlobTypeMax | |
// denotes the root directory of the filesystem. | |
UINT64 Position; // Byte position for regular files; | |
// next directory entry to return for the root | |
// directory. | |
EFI_FILE_PROTOCOL File; // Standard protocol interface. | |
} STUB_FILE; | |
#define STUB_FILE_FROM_FILE(FilePointer) \ | |
CR (FilePointer, STUB_FILE, File, STUB_FILE_SIG) | |
// | |
// Protocol member functions for File. | |
// | |
/** | |
Opens a new file relative to the source file's location. | |
(Forward declaration.) | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is | |
the file handle to the source location. This would | |
typically be an open handle to a directory. | |
@param[out] NewHandle A pointer to the location to return the opened handle | |
for the new file. | |
@param[in] FileName The Null-terminated string of the name of the file to | |
be opened. The file name may contain the following | |
path modifiers: "\", ".", and "..". | |
@param[in] OpenMode The mode to open the file. The only valid | |
combinations that the file may be opened with are: | |
Read, Read/Write, or Create/Read/Write. | |
@param[in] Attributes Only valid for EFI_FILE_MODE_CREATE, in which case | |
these are the attribute bits for the newly created | |
file. | |
@retval EFI_SUCCESS The file was opened. | |
@retval EFI_NOT_FOUND The specified file could not be found on the | |
device. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_MEDIA_CHANGED The device has a different medium in it or the | |
medium is no longer supported. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_WRITE_PROTECTED An attempt was made to create a file, or open a | |
file for write when the media is | |
write-protected. | |
@retval EFI_ACCESS_DENIED The service denied access to the file. | |
@retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the | |
file. | |
@retval EFI_VOLUME_FULL The volume is full. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileOpen ( | |
IN EFI_FILE_PROTOCOL *This, | |
OUT EFI_FILE_PROTOCOL **NewHandle, | |
IN CHAR16 *FileName, | |
IN UINT64 OpenMode, | |
IN UINT64 Attributes | |
); | |
/** | |
Closes a specified file handle. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file | |
handle to close. | |
@retval EFI_SUCCESS The file was closed. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileClose ( | |
IN EFI_FILE_PROTOCOL *This | |
) | |
{ | |
FreePool (STUB_FILE_FROM_FILE (This)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Close and delete the file handle. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the | |
handle to the file to delete. | |
@retval EFI_SUCCESS The file was closed and deleted, and the | |
handle was closed. | |
@retval EFI_WARN_DELETE_FAILURE The handle was closed, but the file was not | |
deleted. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileDelete ( | |
IN EFI_FILE_PROTOCOL *This | |
) | |
{ | |
FreePool (STUB_FILE_FROM_FILE (This)); | |
return EFI_WARN_DELETE_FAILURE; | |
} | |
/** | |
Helper function that formats an EFI_FILE_INFO structure into the | |
user-allocated buffer, for any valid KERNEL_BLOB_TYPE value (including | |
KernelBlobTypeMax, which stands for the root directory). | |
The interface follows the EFI_FILE_GET_INFO -- and for directories, the | |
EFI_FILE_READ -- interfaces. | |
@param[in] BlobType The KERNEL_BLOB_TYPE value identifying the fw_cfg | |
blob backing the STUB_FILE that information is | |
being requested about. If BlobType equals | |
KernelBlobTypeMax, then information will be | |
provided about the root directory of the | |
filesystem. | |
@param[in,out] BufferSize On input, the size of Buffer. On output, the | |
amount of data returned in Buffer. In both cases, | |
the size is measured in bytes. | |
@param[out] Buffer A pointer to the data buffer to return. The | |
buffer's type is EFI_FILE_INFO. | |
@retval EFI_SUCCESS The information was returned. | |
@retval EFI_BUFFER_TOO_SMALL BufferSize is too small to store the | |
EFI_FILE_INFO structure. BufferSize has been | |
updated with the size needed to complete the | |
request. | |
**/ | |
STATIC | |
EFI_STATUS | |
ConvertKernelBlobTypeToFileInfo ( | |
IN KERNEL_BLOB_TYPE BlobType, | |
IN OUT UINTN *BufferSize, | |
OUT VOID *Buffer | |
) | |
{ | |
CONST CHAR16 *Name; | |
UINT64 FileSize; | |
UINT64 Attribute; | |
UINTN NameSize; | |
UINTN FileInfoSize; | |
EFI_FILE_INFO *FileInfo; | |
UINTN OriginalBufferSize; | |
if (BlobType == KernelBlobTypeMax) { | |
// | |
// getting file info about the root directory | |
// | |
Name = L"\\"; | |
FileSize = KernelBlobTypeMax; | |
Attribute = EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY; | |
} else { | |
CONST KERNEL_BLOB *Blob; | |
Blob = &mKernelBlob[BlobType]; | |
Name = Blob->Name; | |
FileSize = Blob->Size; | |
Attribute = EFI_FILE_READ_ONLY; | |
} | |
NameSize = (StrLen (Name) + 1) * 2; | |
FileInfoSize = OFFSET_OF (EFI_FILE_INFO, FileName) + NameSize; | |
ASSERT (FileInfoSize >= sizeof *FileInfo); | |
OriginalBufferSize = *BufferSize; | |
*BufferSize = FileInfoSize; | |
if (OriginalBufferSize < *BufferSize) { | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
FileInfo = (EFI_FILE_INFO *)Buffer; | |
FileInfo->Size = FileInfoSize; | |
FileInfo->FileSize = FileSize; | |
FileInfo->PhysicalSize = FileSize; | |
FileInfo->Attribute = Attribute; | |
CopyMem (&FileInfo->CreateTime, &mInitTime, sizeof mInitTime); | |
CopyMem (&FileInfo->LastAccessTime, &mInitTime, sizeof mInitTime); | |
CopyMem (&FileInfo->ModificationTime, &mInitTime, sizeof mInitTime); | |
CopyMem (FileInfo->FileName, Name, NameSize); | |
return EFI_SUCCESS; | |
} | |
/** | |
Reads data from a file, or continues scanning a directory. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that | |
is the file handle to read data from. | |
@param[in,out] BufferSize On input, the size of the Buffer. On output, the | |
amount of data returned in Buffer. In both cases, | |
the size is measured in bytes. If the read goes | |
beyond the end of the file, the read length is | |
truncated to the end of the file. | |
If This is a directory, the function reads the | |
directory entry at the current position and | |
returns the entry (as EFI_FILE_INFO) in Buffer. If | |
there are no more directory entries, the | |
BufferSize is set to zero on output. | |
@param[out] Buffer The buffer into which the data is read. | |
@retval EFI_SUCCESS Data was read. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_DEVICE_ERROR An attempt was made to read from a deleted | |
file. | |
@retval EFI_DEVICE_ERROR On entry, the current file position is beyond | |
the end of the file. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to store the | |
current directory entry as a EFI_FILE_INFO | |
structure. BufferSize has been updated with the | |
size needed to complete the request, and the | |
directory position has not been advanced. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileRead ( | |
IN EFI_FILE_PROTOCOL *This, | |
IN OUT UINTN *BufferSize, | |
OUT VOID *Buffer | |
) | |
{ | |
STUB_FILE *StubFile; | |
CONST KERNEL_BLOB *Blob; | |
UINT64 Left; | |
StubFile = STUB_FILE_FROM_FILE (This); | |
// | |
// Scanning the root directory? | |
// | |
if (StubFile->BlobType == KernelBlobTypeMax) { | |
EFI_STATUS Status; | |
if (StubFile->Position == KernelBlobTypeMax) { | |
// | |
// Scanning complete. | |
// | |
*BufferSize = 0; | |
return EFI_SUCCESS; | |
} | |
Status = ConvertKernelBlobTypeToFileInfo ( | |
(KERNEL_BLOB_TYPE)StubFile->Position, | |
BufferSize, | |
Buffer | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
++StubFile->Position; | |
return EFI_SUCCESS; | |
} | |
// | |
// Reading a file. | |
// | |
Blob = &mKernelBlob[StubFile->BlobType]; | |
if (StubFile->Position > Blob->Size) { | |
return EFI_DEVICE_ERROR; | |
} | |
Left = Blob->Size - StubFile->Position; | |
if (*BufferSize > Left) { | |
*BufferSize = (UINTN)Left; | |
} | |
if (Blob->Data != NULL) { | |
CopyMem (Buffer, Blob->Data + StubFile->Position, *BufferSize); | |
} | |
StubFile->Position += *BufferSize; | |
return EFI_SUCCESS; | |
} | |
/** | |
Writes data to a file. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that | |
is the file handle to write data to. | |
@param[in,out] BufferSize On input, the size of the Buffer. On output, the | |
amount of data actually written. In both cases, | |
the size is measured in bytes. | |
@param[in] Buffer The buffer of data to write. | |
@retval EFI_SUCCESS Data was written. | |
@retval EFI_UNSUPPORTED Writes to open directory files are not | |
supported. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_DEVICE_ERROR An attempt was made to write to a deleted file. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_WRITE_PROTECTED The file or medium is write-protected. | |
@retval EFI_ACCESS_DENIED The file was opened read only. | |
@retval EFI_VOLUME_FULL The volume is full. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileWrite ( | |
IN EFI_FILE_PROTOCOL *This, | |
IN OUT UINTN *BufferSize, | |
IN VOID *Buffer | |
) | |
{ | |
STUB_FILE *StubFile; | |
StubFile = STUB_FILE_FROM_FILE (This); | |
return (StubFile->BlobType == KernelBlobTypeMax) ? | |
EFI_UNSUPPORTED : | |
EFI_WRITE_PROTECTED; | |
} | |
/** | |
Returns a file's current position. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the | |
file handle to get the current position on. | |
@param[out] Position The address to return the file's current position | |
value. | |
@retval EFI_SUCCESS The position was returned. | |
@retval EFI_UNSUPPORTED The request is not valid on open directories. | |
@retval EFI_DEVICE_ERROR An attempt was made to get the position from a | |
deleted file. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileGetPosition ( | |
IN EFI_FILE_PROTOCOL *This, | |
OUT UINT64 *Position | |
) | |
{ | |
STUB_FILE *StubFile; | |
StubFile = STUB_FILE_FROM_FILE (This); | |
if (StubFile->BlobType == KernelBlobTypeMax) { | |
return EFI_UNSUPPORTED; | |
} | |
*Position = StubFile->Position; | |
return EFI_SUCCESS; | |
} | |
/** | |
Sets a file's current position. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the | |
file handle to set the requested position on. | |
@param[in] Position The byte position from the start of the file to set. For | |
regular files, MAX_UINT64 means "seek to end". For | |
directories, zero means "rewind directory scan". | |
@retval EFI_SUCCESS The position was set. | |
@retval EFI_UNSUPPORTED The seek request for nonzero is not valid on open | |
directories. | |
@retval EFI_DEVICE_ERROR An attempt was made to set the position of a | |
deleted file. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileSetPosition ( | |
IN EFI_FILE_PROTOCOL *This, | |
IN UINT64 Position | |
) | |
{ | |
STUB_FILE *StubFile; | |
KERNEL_BLOB *Blob; | |
StubFile = STUB_FILE_FROM_FILE (This); | |
if (StubFile->BlobType == KernelBlobTypeMax) { | |
if (Position == 0) { | |
// | |
// rewinding a directory scan is allowed | |
// | |
StubFile->Position = 0; | |
return EFI_SUCCESS; | |
} | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// regular file seek | |
// | |
Blob = &mKernelBlob[StubFile->BlobType]; | |
if (Position == MAX_UINT64) { | |
// | |
// seek to end | |
// | |
StubFile->Position = Blob->Size; | |
} else { | |
// | |
// absolute seek from beginning -- seeking past the end is allowed | |
// | |
StubFile->Position = Position; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Returns information about a file. | |
@param[in] This A pointer to the EFI_FILE_PROTOCOL instance | |
that is the file handle the requested | |
information is for. | |
@param[in] InformationType The type identifier GUID for the information | |
being requested. The following information | |
types are supported, storing the | |
corresponding structures in Buffer: | |
- gEfiFileInfoGuid: EFI_FILE_INFO | |
- gEfiFileSystemInfoGuid: | |
EFI_FILE_SYSTEM_INFO | |
- gEfiFileSystemVolumeLabelInfoIdGuid: | |
EFI_FILE_SYSTEM_VOLUME_LABEL | |
@param[in,out] BufferSize On input, the size of Buffer. On output, the | |
amount of data returned in Buffer. In both | |
cases, the size is measured in bytes. | |
@param[out] Buffer A pointer to the data buffer to return. The | |
buffer's type is indicated by | |
InformationType. | |
@retval EFI_SUCCESS The information was returned. | |
@retval EFI_UNSUPPORTED The InformationType is not known. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to store the | |
information structure requested by | |
InformationType. BufferSize has been updated | |
with the size needed to complete the request. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileGetInfo ( | |
IN EFI_FILE_PROTOCOL *This, | |
IN EFI_GUID *InformationType, | |
IN OUT UINTN *BufferSize, | |
OUT VOID *Buffer | |
) | |
{ | |
CONST STUB_FILE *StubFile; | |
UINTN OriginalBufferSize; | |
StubFile = STUB_FILE_FROM_FILE (This); | |
if (CompareGuid (InformationType, &gEfiFileInfoGuid)) { | |
return ConvertKernelBlobTypeToFileInfo ( | |
StubFile->BlobType, | |
BufferSize, | |
Buffer | |
); | |
} | |
OriginalBufferSize = *BufferSize; | |
if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) { | |
EFI_FILE_SYSTEM_INFO *FileSystemInfo; | |
*BufferSize = sizeof *FileSystemInfo; | |
if (OriginalBufferSize < *BufferSize) { | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
FileSystemInfo = (EFI_FILE_SYSTEM_INFO *)Buffer; | |
FileSystemInfo->Size = sizeof *FileSystemInfo; | |
FileSystemInfo->ReadOnly = TRUE; | |
FileSystemInfo->VolumeSize = mTotalBlobBytes; | |
FileSystemInfo->FreeSpace = 0; | |
FileSystemInfo->BlockSize = 1; | |
FileSystemInfo->VolumeLabel[0] = L'\0'; | |
return EFI_SUCCESS; | |
} | |
if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) { | |
EFI_FILE_SYSTEM_VOLUME_LABEL *FileSystemVolumeLabel; | |
*BufferSize = sizeof *FileSystemVolumeLabel; | |
if (OriginalBufferSize < *BufferSize) { | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
FileSystemVolumeLabel = (EFI_FILE_SYSTEM_VOLUME_LABEL *)Buffer; | |
FileSystemVolumeLabel->VolumeLabel[0] = L'\0'; | |
return EFI_SUCCESS; | |
} | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
Sets information about a file. | |
@param[in] File A pointer to the EFI_FILE_PROTOCOL instance that | |
is the file handle the information is for. | |
@param[in] InformationType The type identifier for the information being | |
set. | |
@param[in] BufferSize The size, in bytes, of Buffer. | |
@param[in] Buffer A pointer to the data buffer to write. The | |
buffer's type is indicated by InformationType. | |
@retval EFI_SUCCESS The information was set. | |
@retval EFI_UNSUPPORTED The InformationType is not known. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_WRITE_PROTECTED InformationType is EFI_FILE_INFO_ID and the | |
media is read-only. | |
@retval EFI_WRITE_PROTECTED InformationType is | |
EFI_FILE_PROTOCOL_SYSTEM_INFO_ID and the media | |
is read only. | |
@retval EFI_WRITE_PROTECTED InformationType is | |
EFI_FILE_SYSTEM_VOLUME_LABEL_ID and the media | |
is read-only. | |
@retval EFI_ACCESS_DENIED An attempt is made to change the name of a file | |
to a file that is already present. | |
@retval EFI_ACCESS_DENIED An attempt is being made to change the | |
EFI_FILE_DIRECTORY Attribute. | |
@retval EFI_ACCESS_DENIED An attempt is being made to change the size of | |
a directory. | |
@retval EFI_ACCESS_DENIED InformationType is EFI_FILE_INFO_ID and the | |
file was opened read-only and an attempt is | |
being made to modify a field other than | |
Attribute. | |
@retval EFI_VOLUME_FULL The volume is full. | |
@retval EFI_BAD_BUFFER_SIZE BufferSize is smaller than the size of the type | |
indicated by InformationType. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileSetInfo ( | |
IN EFI_FILE_PROTOCOL *This, | |
IN EFI_GUID *InformationType, | |
IN UINTN BufferSize, | |
IN VOID *Buffer | |
) | |
{ | |
return EFI_WRITE_PROTECTED; | |
} | |
/** | |
Flushes all modified data associated with a file to a device. | |
@param [in] This A pointer to the EFI_FILE_PROTOCOL instance that is the | |
file handle to flush. | |
@retval EFI_SUCCESS The data was flushed. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_WRITE_PROTECTED The file or medium is write-protected. | |
@retval EFI_ACCESS_DENIED The file was opened read-only. | |
@retval EFI_VOLUME_FULL The volume is full. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileFlush ( | |
IN EFI_FILE_PROTOCOL *This | |
) | |
{ | |
return EFI_WRITE_PROTECTED; | |
} | |
// | |
// External definition of the file protocol template. | |
// | |
STATIC CONST EFI_FILE_PROTOCOL mEfiFileProtocolTemplate = { | |
EFI_FILE_PROTOCOL_REVISION, // revision 1 | |
StubFileOpen, | |
StubFileClose, | |
StubFileDelete, | |
StubFileRead, | |
StubFileWrite, | |
StubFileGetPosition, | |
StubFileSetPosition, | |
StubFileGetInfo, | |
StubFileSetInfo, | |
StubFileFlush, | |
NULL, // OpenEx, revision 2 | |
NULL, // ReadEx, revision 2 | |
NULL, // WriteEx, revision 2 | |
NULL // FlushEx, revision 2 | |
}; | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileOpen ( | |
IN EFI_FILE_PROTOCOL *This, | |
OUT EFI_FILE_PROTOCOL **NewHandle, | |
IN CHAR16 *FileName, | |
IN UINT64 OpenMode, | |
IN UINT64 Attributes | |
) | |
{ | |
CONST STUB_FILE *StubFile; | |
UINTN BlobType; | |
STUB_FILE *NewStubFile; | |
// | |
// We're read-only. | |
// | |
switch (OpenMode) { | |
case EFI_FILE_MODE_READ: | |
break; | |
case EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE: | |
case EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE: | |
return EFI_WRITE_PROTECTED; | |
default: | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Only the root directory supports opening files in it. | |
// | |
StubFile = STUB_FILE_FROM_FILE (This); | |
if (StubFile->BlobType != KernelBlobTypeMax) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Locate the file. | |
// | |
for (BlobType = 0; BlobType < KernelBlobTypeMax; ++BlobType) { | |
if (StrCmp (FileName, mKernelBlob[BlobType].Name) == 0) { | |
break; | |
} | |
} | |
if (BlobType == KernelBlobTypeMax) { | |
return EFI_NOT_FOUND; | |
} | |
// | |
// Found it. | |
// | |
NewStubFile = AllocatePool (sizeof *NewStubFile); | |
if (NewStubFile == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
NewStubFile->Signature = STUB_FILE_SIG; | |
NewStubFile->BlobType = (KERNEL_BLOB_TYPE)BlobType; | |
NewStubFile->Position = 0; | |
CopyMem ( | |
&NewStubFile->File, | |
&mEfiFileProtocolTemplate, | |
sizeof mEfiFileProtocolTemplate | |
); | |
*NewHandle = &NewStubFile->File; | |
return EFI_SUCCESS; | |
} | |
// | |
// Protocol member functions for SimpleFileSystem. | |
// | |
/** | |
Open the root directory on a volume. | |
@param[in] This A pointer to the volume to open the root directory on. | |
@param[out] Root A pointer to the location to return the opened file handle | |
for the root directory in. | |
@retval EFI_SUCCESS The device was opened. | |
@retval EFI_UNSUPPORTED This volume does not support the requested file | |
system type. | |
@retval EFI_NO_MEDIA The device has no medium. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_VOLUME_CORRUPTED The file system structures are corrupted. | |
@retval EFI_ACCESS_DENIED The service denied access to the file. | |
@retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of | |
resources. | |
@retval EFI_MEDIA_CHANGED The device has a different medium in it or the | |
medium is no longer supported. Any existing | |
file handles for this volume are no longer | |
valid. To access the files on the new medium, | |
the volume must be reopened with OpenVolume(). | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
StubFileSystemOpenVolume ( | |
IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This, | |
OUT EFI_FILE_PROTOCOL **Root | |
) | |
{ | |
STUB_FILE *StubFile; | |
StubFile = AllocatePool (sizeof *StubFile); | |
if (StubFile == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
StubFile->Signature = STUB_FILE_SIG; | |
StubFile->BlobType = KernelBlobTypeMax; | |
StubFile->Position = 0; | |
CopyMem ( | |
&StubFile->File, | |
&mEfiFileProtocolTemplate, | |
sizeof mEfiFileProtocolTemplate | |
); | |
*Root = &StubFile->File; | |
return EFI_SUCCESS; | |
} | |
STATIC CONST EFI_SIMPLE_FILE_SYSTEM_PROTOCOL mFileSystem = { | |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION, | |
StubFileSystemOpenVolume | |
}; | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
InitrdLoadFile2 ( | |
IN EFI_LOAD_FILE2_PROTOCOL *This, | |
IN EFI_DEVICE_PATH_PROTOCOL *FilePath, | |
IN BOOLEAN BootPolicy, | |
IN OUT UINTN *BufferSize, | |
OUT VOID *Buffer OPTIONAL | |
) | |
{ | |
CONST KERNEL_BLOB *InitrdBlob = &mKernelBlob[KernelBlobTypeInitrd]; | |
ASSERT (InitrdBlob->Size > 0); | |
if (BootPolicy) { | |
return EFI_UNSUPPORTED; | |
} | |
if ((BufferSize == NULL) || !IsDevicePathValid (FilePath, 0)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((FilePath->Type != END_DEVICE_PATH_TYPE) || | |
(FilePath->SubType != END_ENTIRE_DEVICE_PATH_SUBTYPE)) | |
{ | |
return EFI_NOT_FOUND; | |
} | |
if ((Buffer == NULL) || (*BufferSize < InitrdBlob->Size)) { | |
*BufferSize = InitrdBlob->Size; | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
CopyMem (Buffer, InitrdBlob->Data, InitrdBlob->Size); | |
*BufferSize = InitrdBlob->Size; | |
return EFI_SUCCESS; | |
} | |
STATIC CONST EFI_LOAD_FILE2_PROTOCOL mInitrdLoadFile2 = { | |
InitrdLoadFile2, | |
}; | |
// | |
// Utility functions. | |
// | |
/** | |
Populate a blob in mKernelBlob. | |
param[in,out] Blob Pointer to the KERNEL_BLOB element in mKernelBlob that is | |
to be filled from fw_cfg. | |
@retval EFI_SUCCESS Blob has been populated. If fw_cfg reported a | |
size of zero for the blob, then Blob->Data has | |
been left unchanged. | |
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory for Blob->Data. | |
**/ | |
STATIC | |
EFI_STATUS | |
FetchBlob ( | |
IN OUT KERNEL_BLOB *Blob | |
) | |
{ | |
UINT32 Left; | |
UINTN Idx; | |
UINT8 *ChunkData; | |
// | |
// Read blob size. | |
// | |
Blob->Size = 0; | |
for (Idx = 0; Idx < ARRAY_SIZE (Blob->FwCfgItem); Idx++) { | |
if (Blob->FwCfgItem[Idx].SizeKey == 0) { | |
break; | |
} | |
QemuFwCfgSelectItem (Blob->FwCfgItem[Idx].SizeKey); | |
Blob->FwCfgItem[Idx].Size = QemuFwCfgRead32 (); | |
Blob->Size += Blob->FwCfgItem[Idx].Size; | |
} | |
if (Blob->Size == 0) { | |
return EFI_SUCCESS; | |
} | |
// | |
// Read blob. | |
// | |
Blob->Data = AllocatePool (Blob->Size); | |
if (Blob->Data == NULL) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: failed to allocate %Ld bytes for \"%s\"\n", | |
__func__, | |
(INT64)Blob->Size, | |
Blob->Name | |
)); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
DEBUG (( | |
DEBUG_INFO, | |
"%a: loading %Ld bytes for \"%s\"\n", | |
__func__, | |
(INT64)Blob->Size, | |
Blob->Name | |
)); | |
ChunkData = Blob->Data; | |
for (Idx = 0; Idx < ARRAY_SIZE (Blob->FwCfgItem); Idx++) { | |
if (Blob->FwCfgItem[Idx].DataKey == 0) { | |
break; | |
} | |
QemuFwCfgSelectItem (Blob->FwCfgItem[Idx].DataKey); | |
Left = Blob->FwCfgItem[Idx].Size; | |
while (Left > 0) { | |
UINT32 Chunk; | |
Chunk = (Left < SIZE_1MB) ? Left : SIZE_1MB; | |
QemuFwCfgReadBytes (Chunk, ChunkData + Blob->FwCfgItem[Idx].Size - Left); | |
Left -= Chunk; | |
DEBUG (( | |
DEBUG_VERBOSE, | |
"%a: %Ld bytes remaining for \"%s\" (%d)\n", | |
__func__, | |
(INT64)Left, | |
Blob->Name, | |
(INT32)Idx | |
)); | |
} | |
ChunkData += Blob->FwCfgItem[Idx].Size; | |
} | |
return EFI_SUCCESS; | |
} | |
// | |
// The entry point of the feature. | |
// | |
/** | |
Download the kernel, the initial ramdisk, and the kernel command line from | |
QEMU's fw_cfg. Construct a minimal SimpleFileSystem that contains the two | |
image files. | |
@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. On success, the function doesn't | |
return. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
QemuKernelLoaderFsDxeEntrypoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
UINTN BlobType; | |
KERNEL_BLOB *CurrentBlob; | |
KERNEL_BLOB *KernelBlob; | |
EFI_STATUS Status; | |
EFI_HANDLE FileSystemHandle; | |
EFI_HANDLE InitrdLoadFile2Handle; | |
if (!QemuFwCfgIsAvailable ()) { | |
return EFI_NOT_FOUND; | |
} | |
Status = gRT->GetTime (&mInitTime, NULL /* Capabilities */); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "%a: GetTime(): %r\n", __func__, Status)); | |
return Status; | |
} | |
// | |
// Fetch all blobs. | |
// | |
for (BlobType = 0; BlobType < KernelBlobTypeMax; ++BlobType) { | |
CurrentBlob = &mKernelBlob[BlobType]; | |
Status = FetchBlob (CurrentBlob); | |
if (EFI_ERROR (Status)) { | |
goto FreeBlobs; | |
} | |
Status = VerifyBlob ( | |
CurrentBlob->Name, | |
CurrentBlob->Data, | |
CurrentBlob->Size | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeBlobs; | |
} | |
mTotalBlobBytes += CurrentBlob->Size; | |
} | |
KernelBlob = &mKernelBlob[KernelBlobTypeKernel]; | |
if (KernelBlob->Data == NULL) { | |
Status = EFI_NOT_FOUND; | |
goto FreeBlobs; | |
} | |
// | |
// Create a new handle with a single VenMedia() node device path protocol on | |
// it, plus a custom SimpleFileSystem protocol on it. | |
// | |
FileSystemHandle = NULL; | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&FileSystemHandle, | |
&gEfiDevicePathProtocolGuid, | |
&mFileSystemDevicePath, | |
&gEfiSimpleFileSystemProtocolGuid, | |
&mFileSystem, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: InstallMultipleProtocolInterfaces(): %r\n", | |
__func__, | |
Status | |
)); | |
goto FreeBlobs; | |
} | |
if (KernelBlob[KernelBlobTypeInitrd].Size > 0) { | |
InitrdLoadFile2Handle = NULL; | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&InitrdLoadFile2Handle, | |
&gEfiDevicePathProtocolGuid, | |
&mInitrdDevicePath, | |
&gEfiLoadFile2ProtocolGuid, | |
&mInitrdLoadFile2, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: InstallMultipleProtocolInterfaces(): %r\n", | |
__func__, | |
Status | |
)); | |
goto UninstallFileSystemHandle; | |
} | |
} | |
return EFI_SUCCESS; | |
UninstallFileSystemHandle: | |
Status = gBS->UninstallMultipleProtocolInterfaces ( | |
FileSystemHandle, | |
&gEfiDevicePathProtocolGuid, | |
&mFileSystemDevicePath, | |
&gEfiSimpleFileSystemProtocolGuid, | |
&mFileSystem, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
FreeBlobs: | |
while (BlobType > 0) { | |
CurrentBlob = &mKernelBlob[--BlobType]; | |
if (CurrentBlob->Data != NULL) { | |
FreePool (CurrentBlob->Data); | |
CurrentBlob->Size = 0; | |
CurrentBlob->Data = NULL; | |
} | |
} | |
return Status; | |
} |