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