CloverBootloader/MdeModulePkg/Core/Dxe/FwVolBlock/FwVolBlock.c

720 lines
22 KiB
C

/** @file
Implementations for Firmware Volume Block protocol.
It consumes FV HOBs and creates read-only Firmare Volume Block protocol
instances for each of them.
Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "DxeMain.h"
#include "FwVolBlock.h"
FV_MEMMAP_DEVICE_PATH mFvMemmapDevicePathTemplate = {
{
{
HARDWARE_DEVICE_PATH,
HW_MEMMAP_DP,
{
(UINT8)(sizeof (MEMMAP_DEVICE_PATH)),
(UINT8)(sizeof (MEMMAP_DEVICE_PATH) >> 8)
}
},
EfiMemoryMappedIO,
(EFI_PHYSICAL_ADDRESS) 0,
(EFI_PHYSICAL_ADDRESS) 0,
},
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{
END_DEVICE_PATH_LENGTH,
0
}
}
};
FV_PIWG_DEVICE_PATH mFvPIWGDevicePathTemplate = {
{
{
MEDIA_DEVICE_PATH,
MEDIA_PIWG_FW_VOL_DP,
{
(UINT8)(sizeof (MEDIA_FW_VOL_DEVICE_PATH)),
(UINT8)(sizeof (MEDIA_FW_VOL_DEVICE_PATH) >> 8)
}
},
{ 0 }
},
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{
END_DEVICE_PATH_LENGTH,
0
}
}
};
EFI_FW_VOL_BLOCK_DEVICE mFwVolBlock = {
FVB_DEVICE_SIGNATURE,
NULL,
NULL,
{
FwVolBlockGetAttributes,
(EFI_FVB_SET_ATTRIBUTES)FwVolBlockSetAttributes,
FwVolBlockGetPhysicalAddress,
FwVolBlockGetBlockSize,
FwVolBlockReadBlock,
(EFI_FVB_WRITE)FwVolBlockWriteBlock,
(EFI_FVB_ERASE_BLOCKS)FwVolBlockEraseBlock,
NULL
},
0,
NULL,
0,
0,
0
};
/**
Retrieves Volume attributes. No polarity translations are done.
@param This Calling context
@param Attributes output buffer which contains attributes
@retval EFI_SUCCESS The firmware volume attributes were returned.
**/
EFI_STATUS
EFIAPI
FwVolBlockGetAttributes (
IN CONST EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
OUT EFI_FVB_ATTRIBUTES_2 *Attributes
)
{
EFI_FW_VOL_BLOCK_DEVICE *FvbDevice;
FvbDevice = FVB_DEVICE_FROM_THIS (This);
//
// Since we are read only, it's safe to get attributes data from our in-memory copy.
//
*Attributes = FvbDevice->FvbAttributes & ~EFI_FVB2_WRITE_STATUS;
return EFI_SUCCESS;
}
/**
Modifies the current settings of the firmware volume according to the input parameter.
@param This Calling context
@param Attributes input buffer which contains attributes
@retval EFI_SUCCESS The firmware volume attributes were returned.
@retval EFI_INVALID_PARAMETER The attributes requested are in conflict with
the capabilities as declared in the firmware
volume header.
@retval EFI_UNSUPPORTED Not supported.
**/
EFI_STATUS
EFIAPI
FwVolBlockSetAttributes (
IN CONST EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
IN CONST EFI_FVB_ATTRIBUTES_2 *Attributes
)
{
return EFI_UNSUPPORTED;
}
/**
The EraseBlock() function erases one or more blocks as denoted by the
variable argument list. The entire parameter list of blocks must be verified
prior to erasing any blocks. If a block is requested that does not exist
within the associated firmware volume (it has a larger index than the last
block of the firmware volume), the EraseBlock() function must return
EFI_INVALID_PARAMETER without modifying the contents of the firmware volume.
@param This Calling context
@param ... Starting LBA followed by Number of Lba to erase.
a -1 to terminate the list.
@retval EFI_SUCCESS The erase request was successfully completed.
@retval EFI_ACCESS_DENIED The firmware volume is in the WriteDisabled
state.
@retval EFI_DEVICE_ERROR The block device is not functioning correctly
and could not be written. The firmware device
may have been partially erased.
@retval EFI_INVALID_PARAMETER One or more of the LBAs listed in the variable
argument list do
@retval EFI_UNSUPPORTED Not supported.
**/
EFI_STATUS
EFIAPI
FwVolBlockEraseBlock (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
...
)
{
return EFI_UNSUPPORTED;
}
/**
Read the specified number of bytes from the block to the input buffer.
@param This Indicates the calling context.
@param Lba The starting logical block index to read.
@param Offset Offset into the block at which to begin reading.
@param NumBytes Pointer to a UINT32. At entry, *NumBytes
contains the total size of the buffer. At exit,
*NumBytes contains the total number of bytes
actually read.
@param Buffer Pinter to a caller-allocated buffer that
contains the destine for the read.
@retval EFI_SUCCESS The firmware volume was read successfully.
@retval EFI_BAD_BUFFER_SIZE The read was attempted across an LBA boundary.
@retval EFI_ACCESS_DENIED Access denied.
@retval EFI_DEVICE_ERROR The block device is malfunctioning and could not
be read.
**/
EFI_STATUS
EFIAPI
FwVolBlockReadBlock (
IN CONST EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
IN CONST EFI_LBA Lba,
IN CONST UINTN Offset,
IN OUT UINTN *NumBytes,
IN OUT UINT8 *Buffer
)
{
EFI_FW_VOL_BLOCK_DEVICE *FvbDevice;
EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;
UINT8 *LbaOffset;
UINTN LbaStart;
UINTN NumOfBytesRead;
UINTN LbaIndex;
FvbDevice = FVB_DEVICE_FROM_THIS (This);
//
// Check if This FW can be read
//
if ((FvbDevice->FvbAttributes & EFI_FVB2_READ_STATUS) == 0) {
return EFI_ACCESS_DENIED;
}
LbaIndex = (UINTN) Lba;
if (LbaIndex >= FvbDevice->NumBlocks) {
//
// Invalid Lba, read nothing.
//
*NumBytes = 0;
return EFI_BAD_BUFFER_SIZE;
}
if (Offset > FvbDevice->LbaCache[LbaIndex].Length) {
//
// all exceed boundary, read nothing.
//
*NumBytes = 0;
return EFI_BAD_BUFFER_SIZE;
}
NumOfBytesRead = *NumBytes;
if (Offset + NumOfBytesRead > FvbDevice->LbaCache[LbaIndex].Length) {
//
// partial exceed boundary, read data from current postion to end.
//
NumOfBytesRead = FvbDevice->LbaCache[LbaIndex].Length - Offset;
}
LbaStart = FvbDevice->LbaCache[LbaIndex].Base;
FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)((UINTN) FvbDevice->BaseAddress);
LbaOffset = (UINT8 *) FwVolHeader + LbaStart + Offset;
//
// Perform read operation
//
CopyMem (Buffer, LbaOffset, NumOfBytesRead);
if (NumOfBytesRead == *NumBytes) {
return EFI_SUCCESS;
}
*NumBytes = NumOfBytesRead;
return EFI_BAD_BUFFER_SIZE;
}
/**
Writes the specified number of bytes from the input buffer to the block.
@param This Indicates the calling context.
@param Lba The starting logical block index to write to.
@param Offset Offset into the block at which to begin writing.
@param NumBytes Pointer to a UINT32. At entry, *NumBytes
contains the total size of the buffer. At exit,
*NumBytes contains the total number of bytes
actually written.
@param Buffer Pinter to a caller-allocated buffer that
contains the source for the write.
@retval EFI_SUCCESS The firmware volume was written successfully.
@retval EFI_BAD_BUFFER_SIZE The write was attempted across an LBA boundary.
On output, NumBytes contains the total number of
bytes actually written.
@retval EFI_ACCESS_DENIED The firmware volume is in the WriteDisabled
state.
@retval EFI_DEVICE_ERROR The block device is malfunctioning and could not
be written.
@retval EFI_UNSUPPORTED Not supported.
**/
EFI_STATUS
EFIAPI
FwVolBlockWriteBlock (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
IN EFI_LBA Lba,
IN UINTN Offset,
IN OUT UINTN *NumBytes,
IN UINT8 *Buffer
)
{
return EFI_UNSUPPORTED;
}
/**
Get Fvb's base address.
@param This Indicates the calling context.
@param Address Fvb device base address.
@retval EFI_SUCCESS Successfully got Fvb's base address.
@retval EFI_UNSUPPORTED Not supported.
**/
EFI_STATUS
EFIAPI
FwVolBlockGetPhysicalAddress (
IN CONST EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
OUT EFI_PHYSICAL_ADDRESS *Address
)
{
EFI_FW_VOL_BLOCK_DEVICE *FvbDevice;
FvbDevice = FVB_DEVICE_FROM_THIS (This);
if ((FvbDevice->FvbAttributes & EFI_FVB2_MEMORY_MAPPED) != 0) {
*Address = FvbDevice->BaseAddress;
return EFI_SUCCESS;
}
return EFI_UNSUPPORTED;
}
/**
Retrieves the size in bytes of a specific block within a firmware volume.
@param This Indicates the calling context.
@param Lba Indicates the block for which to return the
size.
@param BlockSize Pointer to a caller-allocated UINTN in which the
size of the block is returned.
@param NumberOfBlocks Pointer to a caller-allocated UINTN in which the
number of consecutive blocks starting with Lba
is returned. All blocks in this range have a
size of BlockSize.
@retval EFI_SUCCESS The firmware volume base address is returned.
@retval EFI_INVALID_PARAMETER The requested LBA is out of range.
**/
EFI_STATUS
EFIAPI
FwVolBlockGetBlockSize (
IN CONST EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *This,
IN CONST EFI_LBA Lba,
IN OUT UINTN *BlockSize,
IN OUT UINTN *NumberOfBlocks
)
{
UINTN TotalBlocks;
EFI_FW_VOL_BLOCK_DEVICE *FvbDevice;
EFI_FV_BLOCK_MAP_ENTRY *PtrBlockMapEntry;
EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;
FvbDevice = FVB_DEVICE_FROM_THIS (This);
//
// Do parameter checking
//
if (Lba >= FvbDevice->NumBlocks) {
return EFI_INVALID_PARAMETER;
}
FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)((UINTN)FvbDevice->BaseAddress);
PtrBlockMapEntry = FwVolHeader->BlockMap;
//
// Search the block map for the given block
//
TotalBlocks = 0;
while ((PtrBlockMapEntry->NumBlocks != 0) || (PtrBlockMapEntry->Length !=0 )) {
TotalBlocks += PtrBlockMapEntry->NumBlocks;
if (Lba < TotalBlocks) {
//
// We find the range
//
break;
}
PtrBlockMapEntry++;
}
*BlockSize = PtrBlockMapEntry->Length;
*NumberOfBlocks = TotalBlocks - (UINTN)Lba;
return EFI_SUCCESS;
}
/**
Get FVB authentication status
@param FvbProtocol FVB protocol.
@return Authentication status.
**/
UINT32
GetFvbAuthenticationStatus (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol
)
{
EFI_FW_VOL_BLOCK_DEVICE *FvbDevice;
UINT32 AuthenticationStatus;
AuthenticationStatus = 0;
FvbDevice = BASE_CR (FvbProtocol, EFI_FW_VOL_BLOCK_DEVICE, FwVolBlockInstance);
if (FvbDevice->Signature == FVB_DEVICE_SIGNATURE) {
AuthenticationStatus = FvbDevice->AuthenticationStatus;
}
return AuthenticationStatus;
}
/**
This routine produces a firmware volume block protocol on a given
buffer.
@param BaseAddress base address of the firmware volume image
@param Length length of the firmware volume image
@param ParentHandle handle of parent firmware volume, if this image
came from an FV image file and section in another firmware
volume (ala capsules)
@param AuthenticationStatus Authentication status inherited, if this image
came from an FV image file and section in another firmware volume.
@param FvProtocol Firmware volume block protocol produced.
@retval EFI_VOLUME_CORRUPTED Volume corrupted.
@retval EFI_OUT_OF_RESOURCES No enough buffer to be allocated.
@retval EFI_SUCCESS Successfully produced a FVB protocol on given
buffer.
**/
EFI_STATUS
ProduceFVBProtocolOnBuffer (
IN EFI_PHYSICAL_ADDRESS BaseAddress,
IN UINT64 Length,
IN EFI_HANDLE ParentHandle,
IN UINT32 AuthenticationStatus,
OUT EFI_HANDLE *FvProtocol OPTIONAL
)
{
EFI_STATUS Status;
EFI_FW_VOL_BLOCK_DEVICE *FvbDev;
EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader;
UINTN BlockIndex;
UINTN BlockIndex2;
UINTN LinearOffset;
UINT32 FvAlignment;
EFI_FV_BLOCK_MAP_ENTRY *PtrBlockMapEntry;
FvAlignment = 0;
FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN) BaseAddress;
//
// Validate FV Header, if not as expected, return
//
if (FwVolHeader->Signature != EFI_FVH_SIGNATURE) {
return EFI_VOLUME_CORRUPTED;
}
//
// If EFI_FVB2_WEAK_ALIGNMENT is set in the volume header then the first byte of the volume
// can be aligned on any power-of-two boundary. A weakly aligned volume can not be moved from
// its initial linked location and maintain its alignment.
//
if ((FwVolHeader->Attributes & EFI_FVB2_WEAK_ALIGNMENT) != EFI_FVB2_WEAK_ALIGNMENT) {
//
// Get FvHeader alignment
//
FvAlignment = 1 << ((FwVolHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16);
//
// FvAlignment must be greater than or equal to 8 bytes of the minimum FFS alignment value.
//
if (FvAlignment < 8) {
FvAlignment = 8;
}
if ((UINTN)BaseAddress % FvAlignment != 0) {
//
// FvImage buffer is not at its required alignment.
//
DEBUG ((
DEBUG_ERROR,
"Unaligned FvImage found at 0x%lx:0x%lx, the required alignment is 0x%x\n",
BaseAddress,
Length,
FvAlignment
));
return EFI_VOLUME_CORRUPTED;
}
}
//
// Allocate EFI_FW_VOL_BLOCK_DEVICE
//
FvbDev = AllocateCopyPool(sizeof (EFI_FW_VOL_BLOCK_DEVICE), &mFwVolBlock);
if (FvbDev == NULL) {
return EFI_OUT_OF_RESOURCES;
}
FvbDev->BaseAddress = BaseAddress;
FvbDev->FvbAttributes = FwVolHeader->Attributes;
FvbDev->FwVolBlockInstance.ParentHandle = ParentHandle;
FvbDev->AuthenticationStatus = AuthenticationStatus;
//
// Init the block caching fields of the device
// First, count the number of blocks
//
FvbDev->NumBlocks = 0;
for (PtrBlockMapEntry = FwVolHeader->BlockMap;
PtrBlockMapEntry->NumBlocks != 0;
PtrBlockMapEntry++) {
FvbDev->NumBlocks += PtrBlockMapEntry->NumBlocks;
}
//
// Second, allocate the cache
//
if (FvbDev->NumBlocks >= (MAX_ADDRESS / sizeof (LBA_CACHE))) {
CoreFreePool(FvbDev);
return EFI_OUT_OF_RESOURCES;
}
FvbDev->LbaCache = AllocatePool (FvbDev->NumBlocks * sizeof (LBA_CACHE));
if (FvbDev->LbaCache == NULL) {
CoreFreePool(FvbDev);
return EFI_OUT_OF_RESOURCES;
}
//
// Last, fill in the cache with the linear address of the blocks
//
BlockIndex = 0;
LinearOffset = 0;
for (PtrBlockMapEntry = FwVolHeader->BlockMap;
PtrBlockMapEntry->NumBlocks != 0; PtrBlockMapEntry++) {
for (BlockIndex2 = 0; BlockIndex2 < PtrBlockMapEntry->NumBlocks; BlockIndex2++) {
FvbDev->LbaCache[BlockIndex].Base = LinearOffset;
FvbDev->LbaCache[BlockIndex].Length = PtrBlockMapEntry->Length;
LinearOffset += PtrBlockMapEntry->Length;
BlockIndex++;
}
}
//
// Judget whether FV name guid is produced in Fv extension header
//
if (FwVolHeader->ExtHeaderOffset == 0) {
//
// FV does not contains extension header, then produce MEMMAP_DEVICE_PATH
//
FvbDev->DevicePath = (EFI_DEVICE_PATH_PROTOCOL *) AllocateCopyPool(sizeof (FV_MEMMAP_DEVICE_PATH), &mFvMemmapDevicePathTemplate);
if (FvbDev->DevicePath == NULL) {
FreePool(FvbDev);
return EFI_OUT_OF_RESOURCES;
}
((FV_MEMMAP_DEVICE_PATH *) FvbDev->DevicePath)->MemMapDevPath.StartingAddress = BaseAddress;
((FV_MEMMAP_DEVICE_PATH *) FvbDev->DevicePath)->MemMapDevPath.EndingAddress = BaseAddress + FwVolHeader->FvLength - 1;
} else {
//
// FV contains extension header, then produce MEDIA_FW_VOL_DEVICE_PATH
//
FvbDev->DevicePath = (EFI_DEVICE_PATH_PROTOCOL *) AllocateCopyPool(sizeof (FV_PIWG_DEVICE_PATH), &mFvPIWGDevicePathTemplate);
if (FvbDev->DevicePath == NULL) {
FreePool(FvbDev);
return EFI_OUT_OF_RESOURCES;
}
CopyGuid (
&((FV_PIWG_DEVICE_PATH *)FvbDev->DevicePath)->FvDevPath.FvName,
(GUID *)(UINTN)(BaseAddress + FwVolHeader->ExtHeaderOffset)
);
}
//
//
// Attach FvVolBlock Protocol to new handle
//
Status = CoreInstallMultipleProtocolInterfaces (
&FvbDev->Handle,
&gEfiFirmwareVolumeBlockProtocolGuid, &FvbDev->FwVolBlockInstance,
&gEfiDevicePathProtocolGuid, FvbDev->DevicePath,
NULL
);
//
// If they want the handle back, set it.
//
if (FvProtocol != NULL) {
*FvProtocol = FvbDev->Handle;
}
return Status;
}
/**
This routine consumes FV hobs and produces instances of FW_VOL_BLOCK_PROTOCOL as appropriate.
@param ImageHandle The image handle.
@param SystemTable The system table.
@retval EFI_SUCCESS Successfully initialized firmware volume block
driver.
**/
EFI_STATUS
EFIAPI
FwVolBlockDriverInit (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_PEI_HOB_POINTERS FvHob;
EFI_PEI_HOB_POINTERS Fv3Hob;
UINT32 AuthenticationStatus;
//
// Core Needs Firmware Volumes to function
//
FvHob.Raw = GetHobList ();
while ((FvHob.Raw = GetNextHob (EFI_HOB_TYPE_FV, FvHob.Raw)) != NULL) {
AuthenticationStatus = 0;
//
// Get the authentication status propagated from PEI-phase to DXE.
//
Fv3Hob.Raw = GetHobList ();
while ((Fv3Hob.Raw = GetNextHob (EFI_HOB_TYPE_FV3, Fv3Hob.Raw)) != NULL) {
if ((Fv3Hob.FirmwareVolume3->BaseAddress == FvHob.FirmwareVolume->BaseAddress) &&
(Fv3Hob.FirmwareVolume3->Length == FvHob.FirmwareVolume->Length)) {
AuthenticationStatus = Fv3Hob.FirmwareVolume3->AuthenticationStatus;
break;
}
Fv3Hob.Raw = GET_NEXT_HOB (Fv3Hob);
}
//
// Produce an FVB protocol for it
//
ProduceFVBProtocolOnBuffer (FvHob.FirmwareVolume->BaseAddress, FvHob.FirmwareVolume->Length, NULL, AuthenticationStatus, NULL);
FvHob.Raw = GET_NEXT_HOB (FvHob);
}
return EFI_SUCCESS;
}
/**
This DXE service routine is used to process a firmware volume. In
particular, it can be called by BDS to process a single firmware
volume found in a capsule.
Caution: The caller need validate the input firmware volume to follow
PI specification.
DxeCore will trust the input data and process firmware volume directly.
@param FvHeader pointer to a firmware volume header
@param Size the size of the buffer pointed to by FvHeader
@param FVProtocolHandle the handle on which a firmware volume protocol
was produced for the firmware volume passed in.
@retval EFI_OUT_OF_RESOURCES if an FVB could not be produced due to lack of
system resources
@retval EFI_VOLUME_CORRUPTED if the volume was corrupted
@retval EFI_SUCCESS a firmware volume protocol was produced for the
firmware volume
**/
EFI_STATUS
EFIAPI
CoreProcessFirmwareVolume (
IN VOID *FvHeader,
IN UINTN Size,
OUT EFI_HANDLE *FVProtocolHandle
)
{
VOID *Ptr;
EFI_STATUS Status;
*FVProtocolHandle = NULL;
Status = ProduceFVBProtocolOnBuffer (
(EFI_PHYSICAL_ADDRESS) (UINTN) FvHeader,
(UINT64)Size,
NULL,
0,
FVProtocolHandle
);
//
// Since in our implementation we use register-protocol-notify to put a
// FV protocol on the FVB protocol handle, we can't directly verify that
// the FV protocol was produced. Therefore here we will check the handle
// and make sure an FV protocol is on it. This indicates that all went
// well. Otherwise we have to assume that the volume was corrupted
// somehow.
//
if (!EFI_ERROR(Status)) {
ASSERT (*FVProtocolHandle != NULL);
Ptr = NULL;
Status = CoreHandleProtocol (*FVProtocolHandle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **) &Ptr);
if (EFI_ERROR(Status) || (Ptr == NULL)) {
return EFI_VOLUME_CORRUPTED;
}
return EFI_SUCCESS;
}
return Status;
}