| /** @file | |
| Try to load an EFI-stubbed ARM Linux kernel from QEMU's fw_cfg. | |
| This implementation differs from OvmfPkg/Library/LoadLinuxLib. An EFI | |
| stub in the subject kernel is a hard requirement here. | |
| Copyright (C) 2014-2016, Red Hat, Inc. | |
| This program and the accompanying materials are licensed and made available | |
| under the terms and conditions of the BSD License which accompanies this | |
| distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT | |
| WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include <Guid/FileInfo.h> | |
| #include <Guid/FileSystemInfo.h> | |
| #include <Guid/FileSystemVolumeLabelInfo.h> | |
| #include <Library/PrintLib.h> | |
| #include <Library/QemuFwCfgLib.h> | |
| #include <Protocol/DevicePath.h> | |
| #include <Protocol/LoadedImage.h> | |
| #include <Protocol/SimpleFileSystem.h> | |
| #include "PlatformBm.h" | |
| // | |
| // Static data that hosts the fw_cfg blobs and serves file requests. | |
| // | |
| typedef enum { | |
| KernelBlobTypeKernel, | |
| KernelBlobTypeInitrd, | |
| KernelBlobTypeCommandLine, | |
| KernelBlobTypeMax | |
| } KERNEL_BLOB_TYPE; | |
| typedef struct { | |
| FIRMWARE_CONFIG_ITEM CONST SizeKey; | |
| FIRMWARE_CONFIG_ITEM CONST DataKey; | |
| CONST CHAR16 * CONST Name; | |
| UINT32 Size; | |
| UINT8 *Data; | |
| } KERNEL_BLOB; | |
| STATIC KERNEL_BLOB mKernelBlob[KernelBlobTypeMax] = { | |
| { QemuFwCfgItemKernelSize, QemuFwCfgItemKernelData, L"kernel" }, | |
| { QemuFwCfgItemInitrdSize, QemuFwCfgItemInitrdData, L"initrd" }, | |
| { QemuFwCfgItemCommandLineSize, QemuFwCfgItemCommandLineData, L"cmdline" } | |
| }; | |
| STATIC UINT64 mTotalBlobBytes; | |
| // | |
| // Device path for the handle that incorporates our "EFI stub filesystem". The | |
| // GUID is arbitrary and need not be standardized or advertized. | |
| // | |
| #pragma pack(1) | |
| typedef struct { | |
| VENDOR_DEVICE_PATH VenHwNode; | |
| EFI_DEVICE_PATH_PROTOCOL EndNode; | |
| } SINGLE_VENHW_NODE_DEVPATH; | |
| #pragma pack() | |
| STATIC CONST SINGLE_VENHW_NODE_DEVPATH mFileSystemDevicePath = { | |
| { | |
| { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH) } }, | |
| { | |
| 0xb0fae7e7, 0x6b07, 0x49d0, | |
| { 0x9e, 0x5b, 0x3b, 0xde, 0xc8, 0x3b, 0x03, 0x9d } | |
| } | |
| }, | |
| { | |
| 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) | |
| // | |
| // Tentative definition of the file protocol template. The initializer | |
| // (external definition) will be provided later. | |
| // | |
| STATIC CONST EFI_FILE_PROTOCOL mEfiFileProtocolTemplate; | |
| // | |
| // Protocol member functions for File. | |
| // | |
| /** | |
| Opens a new file relative to the source file's location. | |
| @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 | |
| ) | |
| { | |
| 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; | |
| } | |
| /** | |
| 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 | |
| }; | |
| // | |
| // 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 | |
| }; | |
| // | |
| // 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; | |
| // | |
| // Read blob size. | |
| // | |
| QemuFwCfgSelectItem (Blob->SizeKey); | |
| Blob->Size = QemuFwCfgRead32 (); | |
| if (Blob->Size == 0) { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Read blob. | |
| // | |
| Blob->Data = AllocatePool (Blob->Size); | |
| if (Blob->Data == NULL) { | |
| DEBUG ((EFI_D_ERROR, "%a: failed to allocate %Ld bytes for \"%s\"\n", | |
| __FUNCTION__, (INT64)Blob->Size, Blob->Name)); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| DEBUG ((EFI_D_INFO, "%a: loading %Ld bytes for \"%s\"\n", __FUNCTION__, | |
| (INT64)Blob->Size, Blob->Name)); | |
| QemuFwCfgSelectItem (Blob->DataKey); | |
| Left = Blob->Size; | |
| do { | |
| UINT32 Chunk; | |
| Chunk = (Left < SIZE_1MB) ? Left : SIZE_1MB; | |
| QemuFwCfgReadBytes (Chunk, Blob->Data + (Blob->Size - Left)); | |
| Left -= Chunk; | |
| DEBUG ((EFI_D_VERBOSE, "%a: %Ld bytes remaining for \"%s\"\n", | |
| __FUNCTION__, (INT64)Left, Blob->Name)); | |
| } while (Left > 0); | |
| 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, and load and start the kernel from it. | |
| The kernel will be instructed via its command line to load the initrd from | |
| the same Simple FileSystem. | |
| @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 | |
| TryRunningQemuKernel ( | |
| VOID | |
| ) | |
| { | |
| UINTN BlobType; | |
| KERNEL_BLOB *CurrentBlob; | |
| KERNEL_BLOB *KernelBlob, *InitrdBlob, *CommandLineBlob; | |
| EFI_STATUS Status; | |
| EFI_HANDLE FileSystemHandle; | |
| EFI_DEVICE_PATH_PROTOCOL *KernelDevicePath; | |
| EFI_HANDLE KernelImageHandle; | |
| EFI_LOADED_IMAGE_PROTOCOL *KernelLoadedImage; | |
| Status = gRT->GetTime (&mInitTime, NULL /* Capabilities */); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "%a: GetTime(): %r\n", __FUNCTION__, Status)); | |
| return Status; | |
| } | |
| // | |
| // Fetch all blobs. | |
| // | |
| for (BlobType = 0; BlobType < KernelBlobTypeMax; ++BlobType) { | |
| CurrentBlob = &mKernelBlob[BlobType]; | |
| Status = FetchBlob (CurrentBlob); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeBlobs; | |
| } | |
| mTotalBlobBytes += CurrentBlob->Size; | |
| } | |
| KernelBlob = &mKernelBlob[KernelBlobTypeKernel]; | |
| InitrdBlob = &mKernelBlob[KernelBlobTypeInitrd]; | |
| CommandLineBlob = &mKernelBlob[KernelBlobTypeCommandLine]; | |
| if (KernelBlob->Data == NULL) { | |
| Status = EFI_NOT_FOUND; | |
| goto FreeBlobs; | |
| } | |
| // | |
| // Create a new handle with a single VenHw() 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 ((EFI_D_ERROR, "%a: InstallMultipleProtocolInterfaces(): %r\n", | |
| __FUNCTION__, Status)); | |
| goto FreeBlobs; | |
| } | |
| // | |
| // Create a device path for the kernel image to be loaded from that will call | |
| // back into our file system. | |
| // | |
| KernelDevicePath = FileDevicePath (FileSystemHandle, KernelBlob->Name); | |
| if (KernelDevicePath == NULL) { | |
| DEBUG ((EFI_D_ERROR, "%a: failed to allocate kernel device path\n", | |
| __FUNCTION__)); | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto UninstallProtocols; | |
| } | |
| // | |
| // Load the image. This should call back into our file system. | |
| // | |
| Status = gBS->LoadImage ( | |
| FALSE, // BootPolicy: exact match required | |
| gImageHandle, // ParentImageHandle | |
| KernelDevicePath, | |
| NULL, // SourceBuffer | |
| 0, // SourceSize | |
| &KernelImageHandle | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "%a: LoadImage(): %r\n", __FUNCTION__, Status)); | |
| goto FreeKernelDevicePath; | |
| } | |
| // | |
| // 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); | |
| if (CommandLineBlob->Data == NULL) { | |
| KernelLoadedImage->LoadOptionsSize = 0; | |
| } else { | |
| // | |
| // Verify NUL-termination of the command line. | |
| // | |
| if (CommandLineBlob->Data[CommandLineBlob->Size - 1] != '\0') { | |
| DEBUG ((EFI_D_ERROR, "%a: kernel command line is not NUL-terminated\n", | |
| __FUNCTION__)); | |
| Status = EFI_PROTOCOL_ERROR; | |
| goto UnloadKernelImage; | |
| } | |
| // | |
| // Drop the terminating NUL, convert to UTF-16. | |
| // | |
| KernelLoadedImage->LoadOptionsSize = (CommandLineBlob->Size - 1) * 2; | |
| } | |
| if (InitrdBlob->Data != NULL) { | |
| // | |
| // Append ' initrd=<name>' in UTF-16. | |
| // | |
| KernelLoadedImage->LoadOptionsSize += | |
| (8 + StrLen(InitrdBlob->Name)) * 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 UnloadKernelImage; | |
| } | |
| UnicodeSPrintAsciiFormat ( | |
| KernelLoadedImage->LoadOptions, | |
| KernelLoadedImage->LoadOptionsSize, | |
| "%a%a%s", | |
| (CommandLineBlob->Data == NULL) ? "" : (CHAR8 *)CommandLineBlob->Data, | |
| (InitrdBlob->Data == NULL) ? "" : " initrd=", | |
| (InitrdBlob->Data == NULL) ? L"" : InitrdBlob->Name | |
| ); | |
| DEBUG ((EFI_D_INFO, "%a: command line: \"%s\"\n", __FUNCTION__, | |
| (CHAR16 *)KernelLoadedImage->LoadOptions)); | |
| } | |
| // | |
| // Signal the EFI_EVENT_GROUP_READY_TO_BOOT event. | |
| // | |
| EfiSignalEventReadyToBoot(); | |
| // | |
| // Start the image. | |
| // | |
| Status = gBS->StartImage ( | |
| KernelImageHandle, | |
| NULL, // ExitDataSize | |
| NULL // ExitData | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((EFI_D_ERROR, "%a: StartImage(): %r\n", __FUNCTION__, Status)); | |
| } | |
| if (KernelLoadedImage->LoadOptions != NULL) { | |
| FreePool (KernelLoadedImage->LoadOptions); | |
| } | |
| KernelLoadedImage->LoadOptionsSize = 0; | |
| UnloadKernelImage: | |
| gBS->UnloadImage (KernelImageHandle); | |
| FreeKernelDevicePath: | |
| FreePool (KernelDevicePath); | |
| UninstallProtocols: | |
| gBS->UninstallMultipleProtocolInterfaces (FileSystemHandle, | |
| &gEfiSimpleFileSystemProtocolGuid, &mFileSystem, | |
| &gEfiDevicePathProtocolGuid, &mFileSystemDevicePath, | |
| NULL); | |
| FreeBlobs: | |
| while (BlobType > 0) { | |
| CurrentBlob = &mKernelBlob[--BlobType]; | |
| if (CurrentBlob->Data != NULL) { | |
| FreePool (CurrentBlob->Data); | |
| CurrentBlob->Size = 0; | |
| CurrentBlob->Data = NULL; | |
| } | |
| } | |
| return Status; | |
| } |