CloverBootloader/MdeModulePkg/Core/Dxe/FwVol/FwVol.c
2019-09-03 12:58:42 +03:00

730 lines
20 KiB
C

/** @file
Firmware File System driver that produce Firmware Volume protocol.
Layers on top of Firmware Block protocol to produce a file abstraction
of FV based files.
Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "DxeMain.h"
#include "FwVolDriver.h"
//
// Protocol notify related globals
//
VOID *gEfiFwVolBlockNotifyReg;
EFI_EVENT gEfiFwVolBlockEvent;
FV_DEVICE mFvDevice = {
FV2_DEVICE_SIGNATURE,
NULL,
NULL,
{
FvGetVolumeAttributes,
FvSetVolumeAttributes,
FvReadFile,
FvReadFileSection,
FvWriteFile,
FvGetNextFile,
sizeof (UINTN),
NULL,
FvGetVolumeInfo,
FvSetVolumeInfo
},
NULL,
NULL,
NULL,
NULL,
{ NULL, NULL },
0,
0,
FALSE,
FALSE
};
//
// FFS helper functions
//
/**
Read data from Firmware Block by FVB protocol Read.
The data may cross the multi block ranges.
@param Fvb The FW_VOL_BLOCK_PROTOCOL instance from which to read data.
@param StartLba Pointer to StartLba.
On input, the start logical block index from which to read.
On output,the end logical block index after reading.
@param Offset Pointer to Offset
On input, offset into the block at which to begin reading.
On output, offset into the end block after reading.
@param DataSize Size of data to be read.
@param Data Pointer to Buffer that the data will be read into.
@retval EFI_SUCCESS Successfully read data from firmware block.
@retval others
**/
EFI_STATUS
ReadFvbData (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb,
IN OUT EFI_LBA *StartLba,
IN OUT UINTN *Offset,
IN UINTN DataSize,
OUT UINT8 *Data
)
{
UINTN BlockSize;
UINTN NumberOfBlocks;
UINTN BlockIndex;
UINTN ReadDataSize;
EFI_STATUS Status;
//
// Try read data in current block
//
BlockIndex = 0;
ReadDataSize = DataSize;
Status = Fvb->Read (Fvb, *StartLba, *Offset, &ReadDataSize, Data);
if (Status == EFI_SUCCESS) {
*Offset += DataSize;
return EFI_SUCCESS;
} else if (Status != EFI_BAD_BUFFER_SIZE) {
//
// other error will direct return
//
return Status;
}
//
// Data crosses the blocks, read data from next block
//
DataSize -= ReadDataSize;
Data += ReadDataSize;
*StartLba = *StartLba + 1;
while (DataSize > 0) {
Status = Fvb->GetBlockSize (Fvb, *StartLba, &BlockSize, &NumberOfBlocks);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Read data from the crossing blocks
//
BlockIndex = 0;
while (BlockIndex < NumberOfBlocks && DataSize >= BlockSize) {
Status = Fvb->Read (Fvb, *StartLba + BlockIndex, 0, &BlockSize, Data);
if (EFI_ERROR (Status)) {
return Status;
}
Data += BlockSize;
DataSize -= BlockSize;
BlockIndex ++;
}
//
// Data doesn't exceed the current block range.
//
if (DataSize < BlockSize) {
break;
}
//
// Data must be got from the next block range.
//
*StartLba += NumberOfBlocks;
}
//
// read the remaining data
//
if (DataSize > 0) {
Status = Fvb->Read (Fvb, *StartLba + BlockIndex, 0, &DataSize, Data);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Update Lba and Offset used by the following read.
//
*StartLba += BlockIndex;
*Offset = DataSize;
return EFI_SUCCESS;
}
/**
Given the supplied FW_VOL_BLOCK_PROTOCOL, allocate a buffer for output and
copy the real length volume header into it.
@param Fvb The FW_VOL_BLOCK_PROTOCOL instance from which to
read the volume header
@param FwVolHeader Pointer to pointer to allocated buffer in which
the volume header is returned.
@retval EFI_OUT_OF_RESOURCES No enough buffer could be allocated.
@retval EFI_SUCCESS Successfully read volume header to the allocated
buffer.
@retval EFI_INVALID_PARAMETER The FV Header signature is not as expected or
the file system could not be understood.
**/
EFI_STATUS
GetFwVolHeader (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb,
OUT EFI_FIRMWARE_VOLUME_HEADER **FwVolHeader
)
{
EFI_STATUS Status;
EFI_FIRMWARE_VOLUME_HEADER TempFvh;
UINTN FvhLength;
EFI_LBA StartLba;
UINTN Offset;
UINT8 *Buffer;
//
// Read the standard FV header
//
StartLba = 0;
Offset = 0;
FvhLength = sizeof (EFI_FIRMWARE_VOLUME_HEADER);
Status = ReadFvbData (Fvb, &StartLba, &Offset, FvhLength, (UINT8 *)&TempFvh);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Validate FV Header signature, if not as expected, continue.
//
if (TempFvh.Signature != EFI_FVH_SIGNATURE) {
return EFI_INVALID_PARAMETER;
}
//
// Check to see that the file system is indeed formatted in a way we can
// understand it...
//
if ((!CompareGuid (&TempFvh.FileSystemGuid, &gEfiFirmwareFileSystem2Guid)) &&
(!CompareGuid (&TempFvh.FileSystemGuid, &gEfiFirmwareFileSystem3Guid))) {
return EFI_INVALID_PARAMETER;
}
//
// Allocate a buffer for the caller
//
*FwVolHeader = AllocatePool (TempFvh.HeaderLength);
if (*FwVolHeader == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Copy the standard header into the buffer
//
CopyMem (*FwVolHeader, &TempFvh, sizeof (EFI_FIRMWARE_VOLUME_HEADER));
//
// Read the rest of the header
//
FvhLength = TempFvh.HeaderLength - sizeof (EFI_FIRMWARE_VOLUME_HEADER);
Buffer = (UINT8 *)*FwVolHeader + sizeof (EFI_FIRMWARE_VOLUME_HEADER);
Status = ReadFvbData (Fvb, &StartLba, &Offset, FvhLength, Buffer);
if (EFI_ERROR (Status)) {
//
// Read failed so free buffer
//
CoreFreePool (*FwVolHeader);
}
return Status;
}
/**
Free FvDevice resource when error happens
@param FvDevice pointer to the FvDevice to be freed.
**/
VOID
FreeFvDeviceResource (
IN FV_DEVICE *FvDevice
)
{
FFS_FILE_LIST_ENTRY *FfsFileEntry;
LIST_ENTRY *NextEntry;
//
// Free File List Entry
//
FfsFileEntry = (FFS_FILE_LIST_ENTRY *)FvDevice->FfsFileListHeader.ForwardLink;
while (&FfsFileEntry->Link != &FvDevice->FfsFileListHeader) {
NextEntry = (&FfsFileEntry->Link)->ForwardLink;
if (FfsFileEntry->StreamHandle != 0) {
//
// Close stream and free resources from SEP
//
CloseSectionStream (FfsFileEntry->StreamHandle, FALSE);
}
if (FfsFileEntry->FileCached) {
//
// Free the cached file buffer.
//
CoreFreePool (FfsFileEntry->FfsHeader);
}
CoreFreePool (FfsFileEntry);
FfsFileEntry = (FFS_FILE_LIST_ENTRY *) NextEntry;
}
if (!FvDevice->IsMemoryMapped) {
//
// Free the cached FV buffer.
//
CoreFreePool (FvDevice->CachedFv);
}
//
// Free Volume Header
//
CoreFreePool (FvDevice->FwVolHeader);
return;
}
/**
Check if an FV is consistent and allocate cache for it.
@param FvDevice A pointer to the FvDevice to be checked.
@retval EFI_OUT_OF_RESOURCES No enough buffer could be allocated.
@retval EFI_SUCCESS FV is consistent and cache is allocated.
@retval EFI_VOLUME_CORRUPTED File system is corrupted.
**/
EFI_STATUS
FvCheck (
IN OUT FV_DEVICE *FvDevice
)
{
EFI_STATUS Status;
EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb;
EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;
EFI_FIRMWARE_VOLUME_EXT_HEADER *FwVolExtHeader;
EFI_FVB_ATTRIBUTES_2 FvbAttributes;
EFI_FV_BLOCK_MAP_ENTRY *BlockMap;
FFS_FILE_LIST_ENTRY *FfsFileEntry;
EFI_FFS_FILE_HEADER *FfsHeader;
UINT8 *CacheLocation;
UINTN Index;
EFI_LBA LbaIndex;
UINTN Size;
EFI_FFS_FILE_STATE FileState;
UINT8 *TopFvAddress;
UINTN TestLength;
EFI_PHYSICAL_ADDRESS PhysicalAddress;
BOOLEAN FileCached;
UINTN WholeFileSize;
EFI_FFS_FILE_HEADER *CacheFfsHeader;
FileCached = FALSE;
CacheFfsHeader = NULL;
Fvb = FvDevice->Fvb;
FwVolHeader = FvDevice->FwVolHeader;
Status = Fvb->GetAttributes (Fvb, &FvbAttributes);
if (EFI_ERROR (Status)) {
return Status;
}
Size = (UINTN) FwVolHeader->FvLength;
if ((FvbAttributes & EFI_FVB2_MEMORY_MAPPED) != 0) {
FvDevice->IsMemoryMapped = TRUE;
Status = Fvb->GetPhysicalAddress (Fvb, &PhysicalAddress);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Don't cache memory mapped FV really.
//
FvDevice->CachedFv = (UINT8 *) (UINTN) PhysicalAddress;
} else {
FvDevice->IsMemoryMapped = FALSE;
FvDevice->CachedFv = AllocatePool (Size);
if (FvDevice->CachedFv == NULL) {
return EFI_OUT_OF_RESOURCES;
}
}
//
// Remember a pointer to the end of the CachedFv
//
FvDevice->EndOfCachedFv = FvDevice->CachedFv + Size;
if (!FvDevice->IsMemoryMapped) {
//
// Copy FV into memory using the block map.
//
BlockMap = FwVolHeader->BlockMap;
CacheLocation = FvDevice->CachedFv;
LbaIndex = 0;
while ((BlockMap->NumBlocks != 0) || (BlockMap->Length != 0)) {
//
// read the FV data
//
Size = BlockMap->Length;
for (Index = 0; Index < BlockMap->NumBlocks; Index++) {
Status = Fvb->Read (
Fvb,
LbaIndex,
0,
&Size,
CacheLocation
);
//
// Not check EFI_BAD_BUFFER_SIZE, for Size = BlockMap->Length
//
if (EFI_ERROR (Status)) {
goto Done;
}
LbaIndex++;
CacheLocation += BlockMap->Length;
}
BlockMap++;
}
}
//
// Scan to check the free space & File list
//
if ((FvbAttributes & EFI_FVB2_ERASE_POLARITY) != 0) {
FvDevice->ErasePolarity = 1;
} else {
FvDevice->ErasePolarity = 0;
}
//
// go through the whole FV cache, check the consistence of the FV.
// Make a linked list of all the Ffs file headers
//
Status = EFI_SUCCESS;
InitializeListHead (&FvDevice->FfsFileListHeader);
//
// Build FFS list
//
if (FwVolHeader->ExtHeaderOffset != 0) {
//
// Searching for files starts on an 8 byte aligned boundary after the end of the Extended Header if it exists.
//
FwVolExtHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER *) (FvDevice->CachedFv + FwVolHeader->ExtHeaderOffset);
FfsHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FwVolExtHeader + FwVolExtHeader->ExtHeaderSize);
} else {
FfsHeader = (EFI_FFS_FILE_HEADER *) (FvDevice->CachedFv + FwVolHeader->HeaderLength);
}
FfsHeader = (EFI_FFS_FILE_HEADER *) ALIGN_POINTER (FfsHeader, 8);
TopFvAddress = FvDevice->EndOfCachedFv;
while (((UINTN) FfsHeader >= (UINTN) FvDevice->CachedFv) && ((UINTN) FfsHeader <= (UINTN) ((UINTN) TopFvAddress - sizeof (EFI_FFS_FILE_HEADER)))) {
if (FileCached) {
CoreFreePool (CacheFfsHeader);
FileCached = FALSE;
}
TestLength = TopFvAddress - ((UINT8 *) FfsHeader);
if (TestLength > sizeof (EFI_FFS_FILE_HEADER)) {
TestLength = sizeof (EFI_FFS_FILE_HEADER);
}
if (IsBufferErased (FvDevice->ErasePolarity, FfsHeader, TestLength)) {
//
// We have found the free space so we are done!
//
goto Done;
}
if (!IsValidFfsHeader (FvDevice->ErasePolarity, FfsHeader, &FileState)) {
if ((FileState == EFI_FILE_HEADER_INVALID) ||
(FileState == EFI_FILE_HEADER_CONSTRUCTION)) {
if (IS_FFS_FILE2 (FfsHeader)) {
if (!FvDevice->IsFfs3Fv) {
DEBUG ((EFI_D_ERROR, "Found a FFS3 formatted file: %g in a non-FFS3 formatted FV.\n", &FfsHeader->Name));
}
FfsHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FfsHeader + sizeof (EFI_FFS_FILE_HEADER2));
} else {
FfsHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FfsHeader + sizeof (EFI_FFS_FILE_HEADER));
}
continue;
} else {
//
// File system is corrputed
//
Status = EFI_VOLUME_CORRUPTED;
goto Done;
}
}
CacheFfsHeader = FfsHeader;
if ((CacheFfsHeader->Attributes & FFS_ATTRIB_CHECKSUM) == FFS_ATTRIB_CHECKSUM) {
if (FvDevice->IsMemoryMapped) {
//
// Memory mapped FV has not been cached.
// Here is to cache FFS file to memory buffer for following checksum calculating.
// And then, the cached file buffer can be also used for FvReadFile.
//
WholeFileSize = IS_FFS_FILE2 (CacheFfsHeader) ? FFS_FILE2_SIZE (CacheFfsHeader): FFS_FILE_SIZE (CacheFfsHeader);
CacheFfsHeader = AllocateCopyPool (WholeFileSize, CacheFfsHeader);
if (CacheFfsHeader == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
FileCached = TRUE;
}
}
if (!IsValidFfsFile (FvDevice->ErasePolarity, CacheFfsHeader)) {
//
// File system is corrupted
//
Status = EFI_VOLUME_CORRUPTED;
goto Done;
}
if (IS_FFS_FILE2 (CacheFfsHeader)) {
ASSERT (FFS_FILE2_SIZE (CacheFfsHeader) > 0x00FFFFFF);
if (!FvDevice->IsFfs3Fv) {
DEBUG ((EFI_D_ERROR, "Found a FFS3 formatted file: %g in a non-FFS3 formatted FV.\n", &CacheFfsHeader->Name));
FfsHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FfsHeader + FFS_FILE2_SIZE (CacheFfsHeader));
//
// Adjust pointer to the next 8-byte aligned boundary.
//
FfsHeader = (EFI_FFS_FILE_HEADER *) (((UINTN) FfsHeader + 7) & ~0x07);
continue;
}
}
FileState = GetFileState (FvDevice->ErasePolarity, CacheFfsHeader);
//
// check for non-deleted file
//
if (FileState != EFI_FILE_DELETED) {
//
// Create a FFS list entry for each non-deleted file
//
FfsFileEntry = AllocateZeroPool (sizeof (FFS_FILE_LIST_ENTRY));
if (FfsFileEntry == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
FfsFileEntry->FfsHeader = CacheFfsHeader;
FfsFileEntry->FileCached = FileCached;
FileCached = FALSE;
InsertTailList (&FvDevice->FfsFileListHeader, &FfsFileEntry->Link);
}
if (IS_FFS_FILE2 (CacheFfsHeader)) {
FfsHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FfsHeader + FFS_FILE2_SIZE (CacheFfsHeader));
} else {
FfsHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FfsHeader + FFS_FILE_SIZE (CacheFfsHeader));
}
//
// Adjust pointer to the next 8-byte aligned boundary.
//
FfsHeader = (EFI_FFS_FILE_HEADER *)(((UINTN)FfsHeader + 7) & ~0x07);
}
Done:
if (EFI_ERROR (Status)) {
if (FileCached) {
CoreFreePool (CacheFfsHeader);
FileCached = FALSE;
}
FreeFvDeviceResource (FvDevice);
}
return Status;
}
/**
This notification function is invoked when an instance of the
EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL is produced. It layers an instance of the
EFI_FIRMWARE_VOLUME2_PROTOCOL on the same handle. This is the function where
the actual initialization of the EFI_FIRMWARE_VOLUME2_PROTOCOL is done.
@param Event The event that occured
@param Context For EFI compatiblity. Not used.
**/
VOID
EFIAPI
NotifyFwVolBlock (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_HANDLE Handle;
EFI_STATUS Status;
UINTN BufferSize;
EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb;
EFI_FIRMWARE_VOLUME2_PROTOCOL *Fv;
FV_DEVICE *FvDevice;
EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;
//
// Examine all new handles
//
for (;;) {
//
// Get the next handle
//
BufferSize = sizeof (Handle);
Status = CoreLocateHandle (
ByRegisterNotify,
NULL,
gEfiFwVolBlockNotifyReg,
&BufferSize,
&Handle
);
//
// If not found, we're done
//
if (EFI_NOT_FOUND == Status) {
break;
}
if (EFI_ERROR (Status)) {
continue;
}
//
// Get the FirmwareVolumeBlock protocol on that handle
//
Status = CoreHandleProtocol (Handle, &gEfiFirmwareVolumeBlockProtocolGuid, (VOID **)&Fvb);
ASSERT_EFI_ERROR (Status);
ASSERT (Fvb != NULL);
//
// Make sure the Fv Header is O.K.
//
Status = GetFwVolHeader (Fvb, &FwVolHeader);
if (EFI_ERROR (Status)) {
continue;
}
ASSERT (FwVolHeader != NULL);
if (!VerifyFvHeaderChecksum (FwVolHeader)) {
CoreFreePool (FwVolHeader);
continue;
}
//
// Check if there is an FV protocol already installed in that handle
//
Status = CoreHandleProtocol (Handle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&Fv);
if (!EFI_ERROR (Status)) {
//
// Update Fv to use a new Fvb
//
FvDevice = BASE_CR (Fv, FV_DEVICE, Fv);
if (FvDevice->Signature == FV2_DEVICE_SIGNATURE) {
//
// Only write into our device structure if it's our device structure
//
FvDevice->Fvb = Fvb;
}
} else {
//
// No FwVol protocol on the handle so create a new one
//
FvDevice = AllocateCopyPool (sizeof (FV_DEVICE), &mFvDevice);
if (FvDevice == NULL) {
return;
}
FvDevice->Fvb = Fvb;
FvDevice->Handle = Handle;
FvDevice->FwVolHeader = FwVolHeader;
FvDevice->IsFfs3Fv = CompareGuid (&FwVolHeader->FileSystemGuid, &gEfiFirmwareFileSystem3Guid);
FvDevice->Fv.ParentHandle = Fvb->ParentHandle;
//
// Inherit the authentication status from FVB.
//
FvDevice->AuthenticationStatus = GetFvbAuthenticationStatus (Fvb);
if (!EFI_ERROR (FvCheck (FvDevice))) {
//
// Install an New FV protocol on the existing handle
//
Status = CoreInstallProtocolInterface (
&Handle,
&gEfiFirmwareVolume2ProtocolGuid,
EFI_NATIVE_INTERFACE,
&FvDevice->Fv
);
ASSERT_EFI_ERROR (Status);
} else {
//
// Free FvDevice Buffer for the corrupt FV image.
//
CoreFreePool (FvDevice);
}
}
}
return;
}
/**
This routine is the driver initialization entry point. It registers
a notification function. This notification function are responsible
for building the FV stack dynamically.
@param ImageHandle The image handle.
@param SystemTable The system table.
@retval EFI_SUCCESS Function successfully returned.
**/
EFI_STATUS
EFIAPI
FwVolDriverInit (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
gEfiFwVolBlockEvent = EfiCreateProtocolNotifyEvent (
&gEfiFirmwareVolumeBlockProtocolGuid,
TPL_CALLBACK,
NotifyFwVolBlock,
NULL,
&gEfiFwVolBlockNotifyReg
);
return EFI_SUCCESS;
}