From b2824a8e1362b89285663e1ab9b88e9fbb4bc572 Mon Sep 17 00:00:00 2001 From: jljusten Date: Thu, 1 Sep 2011 19:57:46 +0000 Subject: IntelFrameworkModulePkg: Add UpdateDriverDxe driver Signed-off-by: jljusten Reviewed-by: rsun3 Reviewed-by: lgao4 git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@12257 6f19259b-4bc3-4df7-8a09-765794883524 --- .../FirmwareVolume/UpdateDriverDxe/FlashUpdate.c | 1218 ++++++++++++++++++++ 1 file changed, 1218 insertions(+) create mode 100644 IntelFrameworkModulePkg/Universal/FirmwareVolume/UpdateDriverDxe/FlashUpdate.c (limited to 'IntelFrameworkModulePkg/Universal/FirmwareVolume/UpdateDriverDxe/FlashUpdate.c') diff --git a/IntelFrameworkModulePkg/Universal/FirmwareVolume/UpdateDriverDxe/FlashUpdate.c b/IntelFrameworkModulePkg/Universal/FirmwareVolume/UpdateDriverDxe/FlashUpdate.c new file mode 100644 index 0000000000..56514c9855 --- /dev/null +++ b/IntelFrameworkModulePkg/Universal/FirmwareVolume/UpdateDriverDxe/FlashUpdate.c @@ -0,0 +1,1218 @@ +/** @file + Functions in this file will program the image into flash area. + + Copyright (c) 2002 - 2010, Intel Corporation. All rights reserved.
+ + 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 "UpdateDriver.h" + +/** + Write a block size data into flash. + + @param FvbProtocol Pointer to FVB protocol. + @param Lba Logic block index to be updated. + @param BlockSize Block size + @param Buffer Buffer data to be written. + + @retval EFI_SUCCESS Write data successfully. + @retval other errors Write data failed. + +**/ +EFI_STATUS +UpdateOneBlock ( + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN EFI_LBA Lba, + IN UINTN BlockSize, + IN UINT8 *Buffer + ) +{ + EFI_STATUS Status; + UINTN Size; + + // + // First erase the block + // + Status = FvbProtocol->EraseBlocks ( + FvbProtocol, + Lba, // Lba + 1, // NumOfBlocks + EFI_LBA_LIST_TERMINATOR + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Write the block + // + Size = BlockSize; + Status = FvbProtocol->Write ( + FvbProtocol, + Lba, // Lba + 0, // Offset + &Size, // Size + Buffer // Buffer + ); + if ((EFI_ERROR (Status)) || (Size != BlockSize)) { + return Status; + } + + return EFI_SUCCESS; +} + +/** + Write buffer data in a flash block. + + @param FvbProtocol Pointer to FVB protocol. + @param Lba Logic block index to be updated. + @param Offset The offset within the block. + @param Length Size of buffer to be updated. + @param BlockSize Block size. + @param Buffer Buffer data to be updated. + + @retval EFI_SUCCESS Write data successfully. + @retval other errors Write data failed. + +**/ +EFI_STATUS +UpdateBufferInOneBlock ( + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN Length, + IN UINTN BlockSize, + IN UINT8 *Buffer + ) +{ + EFI_STATUS Status; + UINTN Size; + UINT8 *ReservedBuffer; + + // + // If we are going to update a whole block + // + if ((Offset == 0) && (Length == BlockSize)) { + Status = UpdateOneBlock ( + FvbProtocol, + Lba, + BlockSize, + Buffer + ); + return Status; + } + + // + // If it is not a full block update, we need to coalesce data in + // the block that is not going to be updated and new data together. + // + + // + // Allocate a reserved buffer to make up the final buffer for update + // + ReservedBuffer = NULL; + ReservedBuffer = AllocatePool (BlockSize); + if (ReservedBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + // + // First get the original content of the block + // + Size = BlockSize; + Status = FvbProtocol->Read ( + FvbProtocol, + Lba, + 0, + &Size, + ReservedBuffer + ); + if ((EFI_ERROR (Status)) || (Size != BlockSize)) { + FreePool (ReservedBuffer); + return Status; + } + + // + // Overwrite the reserved buffer with new content + // + CopyMem (ReservedBuffer + Offset, Buffer, Length); + + Status = UpdateOneBlock ( + FvbProtocol, + Lba, + BlockSize, + ReservedBuffer + ); + + FreePool (ReservedBuffer); + + return Status; +} + +/** + Get the last write log, and check the status of last write. + If not complete, restart will be taken. + + @param FvbHandle Handle of FVB protocol. + @param FtwProtocol FTW protocol instance. + @param ConfigData Config data on updating driver. + @param PrivateDataSize bytes from the private data + stored for this write. + @param PrivateData A pointer to a buffer. The function will copy. + @param Lba The logical block address of the last write. + @param Offset The offset within the block of the last write. + @param Length The length of the last write. + @param Pending A Boolean value with TRUE indicating + that the write was completed. + + @retval EFI_OUT_OF_RESOURCES No enough memory is allocated. + @retval EFI_ABORTED The FTW work space is damaged. + @retval EFI_NOT_FOUND The last write is not done by this driver. + @retval EFI_SUCCESS Last write log is got. + +**/ +EFI_STATUS +RetrieveLastWrite ( + IN EFI_HANDLE FvbHandle, + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *FtwProtocol, + IN UPDATE_CONFIG_DATA *ConfigData, + IN UINTN PrivateDataSize, + IN OUT UPDATE_PRIVATE_DATA *PrivateData, + IN OUT EFI_LBA *Lba, + IN OUT UINTN *Offset, + IN OUT UINTN *Length, + IN OUT BOOLEAN *Pending + ) +{ + EFI_STATUS Status; + EFI_GUID CallerId; + UINTN PrivateBufferSize; + BOOLEAN Complete; + VOID *PrivateDataBuffer; + + // + // Get the last write + // + *Pending = FALSE; + PrivateBufferSize = PrivateDataSize; + PrivateDataBuffer = NULL; + Status = FtwProtocol->GetLastWrite ( + FtwProtocol, + &CallerId, + Lba, + Offset, + Length, + &PrivateBufferSize, + PrivateData, + &Complete + ); + if (EFI_ERROR (Status)) { + // + // If there is no incompleted record, return success. + // + if ((Status == EFI_NOT_FOUND) && Complete) { + return EFI_SUCCESS; + } else if (Status == EFI_BUFFER_TOO_SMALL) { + // + // If buffer too small, reallocate buffer and call getlastwrite again + // + PrivateDataBuffer = AllocatePool (PrivateBufferSize); + + if (PrivateDataBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = FtwProtocol->GetLastWrite ( + FtwProtocol, + &CallerId, + Lba, + Offset, + Length, + &PrivateBufferSize, + PrivateDataBuffer, + &Complete + ); + if (EFI_ERROR (Status)) { + FreePool ( PrivateDataBuffer); + return EFI_ABORTED; + } else { + CopyMem (PrivateData, PrivateDataBuffer, PrivateDataSize); + FreePool (PrivateDataBuffer); + PrivateDataBuffer = NULL; + } + } else { + return EFI_ABORTED; + } + } + + *Pending = TRUE; + + // + // If the caller is not the update driver, then return. + // The update driver cannot continue to perform the update + // + if (CompareMem (&CallerId, &gEfiCallerIdGuid, sizeof (EFI_GUID)) != 0) { + return EFI_NOT_FOUND; + } + + // + // Check the private data and see if it is the one I need. + // + if (CompareMem (&(PrivateData->FileGuid), &(ConfigData->FileGuid), sizeof(EFI_GUID)) != 0) { + return EFI_NOT_FOUND; + } + + // + // If the caller is the update driver and complete is not true, then restart(). + // + if (!Complete) { + // + // Re-start the update + // + Status = FtwProtocol->Restart ( + FtwProtocol, + FvbHandle + ); + // + // If restart() error, then abort(). + // + if (EFI_ERROR (Status)) { + FtwProtocol->Abort (FtwProtocol); + // + // Now set Pending as FALSE as this record has been cleared + // + *Pending = FALSE; + return EFI_SUCCESS; + } + + } + + return Status; +} + +/** + Update the whole FV image in fault tolerant write method. + + @param FvbHandle Handle of FVB protocol for the updated flash range. + @param FvbProtocol FVB protocol. + @param BlockMap Block array to specify flash area. + @param ConfigData Config data on updating driver. + @param ImageBuffer Image buffer to be updated. + @param ImageSize Image size. + + @retval EFI_SUCCESS FV image is writed into flash. + @retval EFI_INVALID_PARAMETER Config data is not valid. + @retval EFI_NOT_FOUND FTW protocol doesn't exist. + @retval EFI_OUT_OF_RESOURCES No enough backup space. + @retval EFI_ABORTED Error happen when update FV. + +**/ +EFI_STATUS +FaultTolerantUpdateOnWholeFv ( + IN EFI_HANDLE FvbHandle, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN EFI_FV_BLOCK_MAP_ENTRY *BlockMap, + IN UPDATE_CONFIG_DATA *ConfigData, + IN UINT8 *ImageBuffer, + IN UINTN ImageSize + ) +{ + EFI_STATUS Status; + EFI_FAULT_TOLERANT_WRITE_PROTOCOL *FtwProtocol; + UINTN MaxBlockSize; + UINTN FtwMaxBlockSize; + BOOLEAN Pending; + UPDATE_PRIVATE_DATA PrivateData; + EFI_LBA PendingLba; + EFI_LBA Lba; + UINTN PendingOffset; + UINTN Offset; + UINTN PendingLength; + UINTN Length; + EFI_FV_BLOCK_MAP_ENTRY *PtrMap; + UINTN NumOfBlocks; + UINTN Index; + UINT8 *UpdateBuffer; + + if ((ConfigData->UpdateType != UpdateWholeFV) + || (!ConfigData->FaultTolerant)) { + return EFI_INVALID_PARAMETER; + } + + // + // Get the FTW protocol + // + Status = gBS->LocateProtocol ( + &gEfiFaultTolerantWriteProtocolGuid, + NULL, + (VOID **) &FtwProtocol + ); + if (EFI_ERROR (Status)) { + return EFI_NOT_FOUND; + } + + // + // Get the maximum block size of the FV, and number of blocks + // NumOfBlocks will be the NumOfUdpates. + // + MaxBlockSize = 0; + NumOfBlocks = 0; + PtrMap = BlockMap; + while (TRUE) { + if ((PtrMap->NumBlocks == 0) || (PtrMap->Length == 0)) { + break; + } + if (MaxBlockSize < PtrMap->Length) { + MaxBlockSize = PtrMap->Length; + } + NumOfBlocks = NumOfBlocks + PtrMap->NumBlocks; + PtrMap++; + } + + FtwProtocol->GetMaxBlockSize (FtwProtocol, &FtwMaxBlockSize); + // + // Not enough backup space. return directly + // + if (FtwMaxBlockSize < MaxBlockSize) { + return EFI_OUT_OF_RESOURCES; + } + + PendingLba = 0; + PendingOffset = 0; + PendingLength = 0; + Pending = FALSE; + + // + // Fault Tolerant Write can only support actual fault tolerance if the write + // is a reclaim operation, which means the data buffer (new and old) are + // acutally both stored in flash. But for component update write, the data + // are now in memory. So we cannot actually recover the data after power + // failure. + // + Status = RetrieveLastWrite ( + FvbHandle, + FtwProtocol, + ConfigData, + sizeof (UPDATE_PRIVATE_DATA), + &PrivateData, + &PendingLba, + &PendingOffset, + &PendingLength, + &Pending + ); + + if (Pending && (Status == EFI_NOT_FOUND)) { + // + // Cannot continue with the write operation + // + return EFI_ABORTED; + } + + if (EFI_ERROR(Status)) { + return EFI_ABORTED; + } + + // + // Currently we start from the pending write if there is any. But as we + // are going to update a whole FV, we can just abort last write and start + // from the very begining. + // + if (!Pending) { + // + // Now allocte the update private data in FTW. If there is pending + // write, it has already been allocated and no need to allocate here. + // + Status = FtwProtocol->Allocate ( + FtwProtocol, + &gEfiCallerIdGuid, + sizeof (UPDATE_PRIVATE_DATA), + NumOfBlocks + ); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // Perform the update now. If there are pending writes, we need to + // start from the pending write instead of the very beginning. + // + PtrMap = BlockMap; + Lba = 0; + Offset = 0; + UpdateBuffer = ImageBuffer; + CopyMem ( + (VOID *) &PrivateData.FileGuid, + (VOID *) &ConfigData->FileGuid, + sizeof (EFI_GUID) + ); + + while (TRUE) { + if ((PtrMap->NumBlocks == 0) || (PtrMap->Length == 0)) { + break; + } + Length = (UINTN)PtrMap->Length; + for (Index = 0; Index < PtrMap->NumBlocks; Index++) { + + // + // Add an extra check here to see if the pending record is correct + // + if (Pending && (Lba == PendingLba)) { + if ((PendingOffset != Offset) || (PendingLength != Length)) { + // + // Error. + // + Status = EFI_ABORTED; + break; + } + } + + if ((!Pending) || (Lba >= PendingLba)) { + Status = FtwProtocol->Write ( + FtwProtocol, + Lba, // Lba + Offset, // Offset + Length, // Size + &PrivateData, // Private Data + FvbHandle, // FVB handle + UpdateBuffer // Buffer + ); + } + + if (EFI_ERROR (Status)) { + break; + } + Lba++; + UpdateBuffer = (UINT8 *) ((UINTN)UpdateBuffer + Length); + } + + if (EFI_ERROR (Status)) { + break; + } + PtrMap++; + } + + return Status; + +} + +/** + Directly update the whole FV image without fault tolerant write method. + + @param FvbHandle Handle of FVB protocol for the updated flash range. + @param FvbProtocol FVB protocol. + @param BlockMap Block array to specify flash area. + @param ConfigData Config data on updating driver. + @param ImageBuffer Image buffer to be updated. + @param ImageSize Image size. + + @retval EFI_SUCCESS FV image is writed into flash. + @retval EFI_INVALID_PARAMETER Config data is not valid. + @retval EFI_ABORTED Error happen when update FV. + +**/ +EFI_STATUS +NonFaultTolerantUpdateOnWholeFv ( + IN EFI_HANDLE FvbHandle, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN EFI_FV_BLOCK_MAP_ENTRY *BlockMap, + IN UPDATE_CONFIG_DATA *ConfigData, + IN UINT8 *ImageBuffer, + IN UINTN ImageSize + ) +{ + EFI_STATUS Status; + EFI_FV_BLOCK_MAP_ENTRY *PtrMap; + UINTN Index; + EFI_LBA UpdateLba; + UINT8 *UpdateBuffer; + UINTN UpdateSize; + + if ((ConfigData->UpdateType != UpdateWholeFV ) + || (ConfigData->FaultTolerant)) { + return EFI_INVALID_PARAMETER; + } + + Status = EFI_SUCCESS; + PtrMap = BlockMap; + UpdateLba = 0; + UpdateBuffer = ImageBuffer; + + // + // Perform the update now + // + while (TRUE) { + if ((PtrMap->NumBlocks == 0) || (PtrMap->Length == 0)) { + break; + } + UpdateSize = (UINTN)PtrMap->Length; + for (Index = 0; Index < PtrMap->NumBlocks; Index++) { + Status = UpdateOneBlock ( + FvbProtocol, + UpdateLba, + UpdateSize, + UpdateBuffer + ); + if (EFI_ERROR (Status)) { + break; + } + + UpdateLba++; + UpdateBuffer = (UINT8 *) ((UINTN)UpdateBuffer + UpdateSize); + } + + if (EFI_ERROR (Status)) { + break; + } + PtrMap++; + } + + return Status; +} + +/** + Update the whole FV image, and reinsall FVB protocol for the updated FV image. + + @param FvbHandle Handle of FVB protocol for the updated flash range. + @param FvbProtocol FVB protocol. + @param ConfigData Config data on updating driver. + @param ImageBuffer Image buffer to be updated. + @param ImageSize Image size. + + @retval EFI_INVALID_PARAMETER Update type is not UpdateWholeFV. + Or Image size is not same to the size of whole FV. + @retval EFI_OUT_OF_RESOURCES No enoug memory is allocated. + @retval EFI_SUCCESS FV image is updated, and its FVB protocol is reinstalled. + +**/ +EFI_STATUS +PerformUpdateOnWholeFv ( + IN EFI_HANDLE FvbHandle, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN UPDATE_CONFIG_DATA *ConfigData, + IN UINT8 *ImageBuffer, + IN UINTN ImageSize +) +{ + EFI_STATUS Status; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader; + EFI_FV_BLOCK_MAP_ENTRY *BlockMap; + CHAR16 *TmpStr; + + if (ConfigData->UpdateType != UpdateWholeFV) { + return EFI_INVALID_PARAMETER; + } + + // + // Get the header of the firmware volume + // + FwVolHeader = NULL; + FwVolHeader = AllocatePool (((EFI_FIRMWARE_VOLUME_HEADER *) ((UINTN) (ConfigData->BaseAddress)))->HeaderLength); + if (FwVolHeader == NULL) { + return EFI_OUT_OF_RESOURCES; + } + CopyMem ( + FwVolHeader, + (VOID *) ((UINTN) (ConfigData->BaseAddress)), + ((EFI_FIRMWARE_VOLUME_HEADER *) ((UINTN) (ConfigData->BaseAddress)))->HeaderLength + ); + + // + // Check if ImageSize is the same as the size of the whole FV + // + if ((UINT64)ImageSize != FwVolHeader->FvLength) { + FreePool (FwVolHeader); + return EFI_INVALID_PARAMETER; + } + + // + // Print on screen + // + TmpStr = HiiGetString (gHiiHandle, STRING_TOKEN(UPDATE_FIRMWARE_VOLUME), NULL); + if (TmpStr != NULL) { + Print (TmpStr, ConfigData->BaseAddress, (FwVolHeader->FvLength + ConfigData->BaseAddress)); + FreePool (TmpStr); + } + + DEBUG ((EFI_D_UPDATE, "UpdateDriver: updating whole FV from %08LX to %08LX\n", + ConfigData->BaseAddress, (FwVolHeader->FvLength + ConfigData->BaseAddress))); + + // + // Get the block map of the firmware volume + // + BlockMap = &(FwVolHeader->BlockMap[0]); + + // + // It is about the same if we are going to fault tolerantly update + // a certain FV in our current design. But we divide non-fault tolerant + // and fault tolerant udpate here for better maintenance as fault + // tolerance may change and may be done more wisely if we have space. + // + if (ConfigData->FaultTolerant) { + Status = FaultTolerantUpdateOnWholeFv ( + FvbHandle, + FvbProtocol, + BlockMap, + ConfigData, + ImageBuffer, + ImageSize + ); + } else { + Status = NonFaultTolerantUpdateOnWholeFv ( + FvbHandle, + FvbProtocol, + BlockMap, + ConfigData, + ImageBuffer, + ImageSize + ); + } + + FreePool (FwVolHeader); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // As the whole FV has been replaced, the FV driver shall re-parse the + // firmware volume. So re-install FVB protocol here + // + Status = gBS->ReinstallProtocolInterface ( + FvbHandle, + &gEfiFirmwareVolumeBlockProtocolGuid, + FvbProtocol, + FvbProtocol + ); + + return Status; +} + +/** + Update certain file in the FV. + + @param FvbHandle Handle of FVB protocol for the updated flash range. + @param FvbProtocol FVB protocol. + @param ConfigData Config data on updating driver. + @param ImageBuffer Image buffer to be updated. + @param ImageSize Image size. + @param FileType FFS file type. + @param FileAttributes FFS file attribute + + @retval EFI_INVALID_PARAMETER Update type is not UpdateFvFile. + Or Image size is not same to the size of whole FV. + @retval EFI_UNSUPPORTED PEIM FFS is unsupported to be updated. + @retval EFI_SUCCESS The FFS file is added into FV. + +**/ +EFI_STATUS +PerformUpdateOnFvFile ( + IN EFI_HANDLE FvbHandle, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN UPDATE_CONFIG_DATA *ConfigData, + IN UINT8 *ImageBuffer, + IN UINTN ImageSize, + IN EFI_FV_FILETYPE FileType, + IN EFI_FV_FILE_ATTRIBUTES FileAttributes + ) +{ + EFI_STATUS Status; + EFI_FIRMWARE_VOLUME2_PROTOCOL *FwVolProtocol; + EFI_FV_WRITE_FILE_DATA FileData; + CHAR16 *TmpStr; + + if (ConfigData->UpdateType != UpdateFvFile) { + return EFI_INVALID_PARAMETER; + } + + // + // Print on screen + // + TmpStr = HiiGetString (gHiiHandle, STRING_TOKEN(UPDATE_FIRMWARE_VOLUME_FILE), NULL); + if (TmpStr != NULL) { + Print (TmpStr, &(ConfigData->FileGuid)); + FreePool (TmpStr); + } + + DEBUG ((EFI_D_UPDATE, "UpdateDriver: updating file: %g\n", + &(ConfigData->FileGuid))); + + // + // Get Firmware volume protocol on this FVB protocol + // + Status = gBS->HandleProtocol ( + FvbHandle, + &gEfiFirmwareVolume2ProtocolGuid, + (VOID **) &FwVolProtocol + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // If it is a PEIM, we need first to rebase it before committing + // the write to target + // + if ((FileType == EFI_FV_FILETYPE_PEI_CORE) || (FileType == EFI_FV_FILETYPE_PEIM ) + || (FileType == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)) { + return EFI_UNSUPPORTED; + } + + FileData.NameGuid = &(ConfigData->FileGuid); + FileData.Type = FileType; + FileData.FileAttributes = FileAttributes; + FileData.Buffer = ImageBuffer; + FileData.BufferSize = (UINT32) ImageSize; + + Status = FwVolProtocol->WriteFile ( + FwVolProtocol, + 1, // NumberOfFiles + (EFI_FV_WRITE_POLICY)ConfigData->FaultTolerant, + &FileData + ); + return Status; +} + +/** + Update the buffer into flash area in fault tolerant write method. + + @param ImageBuffer Image buffer to be updated. + @param SizeLeft Size of the image buffer. + @param UpdatedSize Size of the updated buffer. + @param ConfigData Config data on updating driver. + @param FlashAddress Flash address to be updated as start address. + @param FvbProtocol FVB protocol. + @param FvbHandle Handle of FVB protocol for the updated flash range. + + @retval EFI_SUCCESS Buffer data is updated into flash. + @retval EFI_INVALID_PARAMETER Base flash address is not in FVB flash area. + @retval EFI_NOT_FOUND FTW protocol doesn't exist. + @retval EFI_OUT_OF_RESOURCES No enough backup space. + @retval EFI_ABORTED Error happen when update flash area. + +**/ +EFI_STATUS +FaultTolerantUpdateOnPartFv ( + IN UINT8 *ImageBuffer, + IN UINTN SizeLeft, + IN OUT UINTN *UpdatedSize, + IN UPDATE_CONFIG_DATA *ConfigData, + IN EFI_PHYSICAL_ADDRESS FlashAddress, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN EFI_HANDLE FvbHandle + ) +{ + EFI_STATUS Status; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeaderTmp; + EFI_PHYSICAL_ADDRESS BaseAddress; + EFI_PHYSICAL_ADDRESS FvBase; + EFI_PHYSICAL_ADDRESS NextBlock; + EFI_FV_BLOCK_MAP_ENTRY *BlockMap; + EFI_FV_BLOCK_MAP_ENTRY *PtrMap; + UINTN NumOfUpdates; + UINTN TotalSize; + EFI_PHYSICAL_ADDRESS StartAddress; + EFI_FAULT_TOLERANT_WRITE_PROTOCOL *FtwProtocol; + UINTN MaxBlockSize; + UINTN FtwMaxBlockSize; + BOOLEAN Pending; + UPDATE_PRIVATE_DATA PrivateData; + EFI_LBA PendingLba; + EFI_LBA Lba; + UINTN BlockSize; + UINTN PendingOffset; + UINTN Offset; + UINTN PendingLength; + UINTN Length; + UINTN Index; + UINT8 *Image; + + // + // Get the block map to update the block one by one + // + Status = FvbProtocol->GetPhysicalAddress ( + FvbProtocol, + &FvBase + ); + if (EFI_ERROR (Status)) { + return Status; + } + + FwVolHeaderTmp = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN)FvBase; + if ((FlashAddress < FvBase) || (FlashAddress > (FvBase + FwVolHeaderTmp->FvLength))) { + return EFI_INVALID_PARAMETER; + } + + FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)AllocateCopyPool ( + FwVolHeaderTmp->HeaderLength, + FwVolHeaderTmp + ); + if (FwVolHeader == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // For fault tolerant write, we have to know how many blocks we need to + // update. So we will calculate number of updates and max block size first + // + NumOfUpdates = 0; + MaxBlockSize = 0; + TotalSize = SizeLeft; + StartAddress = FlashAddress; + BaseAddress = FvBase; + BlockMap = &(FwVolHeader->BlockMap[0]); + PtrMap = BlockMap; + + while (TotalSize > 0) { + if ((PtrMap->NumBlocks == 0) || (PtrMap->Length == 0)) { + break; + } + + BlockSize = PtrMap->Length; + for (Index = 0; Index < PtrMap->NumBlocks; Index++) { + NextBlock = BaseAddress + BlockSize; + // + // Check if this block need to be updated + // + if ((StartAddress >= BaseAddress) && (StartAddress < NextBlock)) { + // + // Get the maximum block size + // + if (MaxBlockSize < BlockSize) { + MaxBlockSize = BlockSize; + } + + // + // This block shall be udpated. So increment number of updates + // + NumOfUpdates++; + Offset = (UINTN) (StartAddress - BaseAddress); + Length = TotalSize; + if ((Length + Offset ) > BlockSize) { + Length = BlockSize - Offset; + } + + StartAddress = StartAddress + Length; + TotalSize = TotalSize - Length; + if (TotalSize <= 0) { + break; + } + } + BaseAddress = NextBlock; + } + PtrMap++; + } + + // + // Get the FTW protocol + // + Status = gBS->LocateProtocol ( + &gEfiFaultTolerantWriteProtocolGuid, + NULL, + (VOID **) &FtwProtocol + ); + if (EFI_ERROR (Status)) { + FreePool (FwVolHeader); + return EFI_NOT_FOUND; + } + + FtwProtocol->GetMaxBlockSize (FtwProtocol, &FtwMaxBlockSize); + + // + // Not enough backup space. return directly + // + if (FtwMaxBlockSize < MaxBlockSize) { + FreePool (FwVolHeader); + return EFI_OUT_OF_RESOURCES; + } + + PendingLba = 0; + PendingOffset = 0; + PendingLength = 0; + Pending = FALSE; + + // + // Fault Tolerant Write can only support actual fault tolerance if the write + // is a reclaim operation, which means the data buffer (new and old) are + // acutally both stored in flash. But for component update write, the data + // are now in memory. So we cannot actually recover the data after power + // failure. + // + Status = RetrieveLastWrite ( + FvbHandle, + FtwProtocol, + ConfigData, + sizeof (UPDATE_PRIVATE_DATA), + &PrivateData, + &PendingLba, + &PendingOffset, + &PendingLength, + &Pending + ); + if (Pending && (Status == EFI_NOT_FOUND)) { + // + // I'm not the owner of the pending fault tolerant write record + // Cannot continue with the write operation + // + FreePool (FwVolHeader); + return EFI_ABORTED; + } + + if (EFI_ERROR(Status)) { + FreePool (FwVolHeader); + return EFI_ABORTED; + } + + // + // Currently we start from the pending write if there is any. But if the + // caller is exactly the same, and the new data is already a in memory, (it + // cannot be stored in flash in last write,) we can just abort last write + // and start from the very begining. + // + if (!Pending) { + // + // Now allocte the update private data in FTW. If there is pending + // write, it has already been allocated and no need to allocate here. + // + Status = FtwProtocol->Allocate ( + FtwProtocol, + &gEfiCallerIdGuid, + sizeof (UPDATE_PRIVATE_DATA), + NumOfUpdates + ); + if (EFI_ERROR (Status)) { + FreePool (FwVolHeader); + return Status; + } + } + + // + // Perform the update now. If there are pending writes, we need to + // start from the pending write instead of the very beginning. + // + TotalSize = SizeLeft; + Lba = 0; + StartAddress = FlashAddress; + BaseAddress = FvBase; + PtrMap = BlockMap; + Image = ImageBuffer; + CopyMem ( + (VOID *) &PrivateData.FileGuid, + (VOID *) &ConfigData->FileGuid, + sizeof (EFI_GUID) + ); + + while (TotalSize > 0) { + if ((PtrMap->NumBlocks == 0) || (PtrMap->Length == 0)) { + break; + } + + BlockSize = (UINTN)PtrMap->Length; + for (Index = 0; Index < PtrMap->NumBlocks; Index++) { + NextBlock = BaseAddress + BlockSize; + if ((StartAddress >= BaseAddress) && (StartAddress < NextBlock)) { + // + // So we need to update this block + // + Offset = (UINTN) (StartAddress - BaseAddress); + Length = TotalSize; + if ((Length + Offset ) > BlockSize) { + Length = BlockSize - Offset; + } + + // + // Add an extra check here to see if the pending record is correct + // + if (Pending && (Lba == PendingLba)) { + if ((PendingOffset != Offset) || (PendingLength != Length)) { + // + // Error. + // + Status = EFI_ABORTED; + break; + } + } + + if ((!Pending) || (Lba >= PendingLba)) { + DEBUG ((EFI_D_UPDATE, "Update Flash area from %08LX to %08LX\n", StartAddress, (UINT64)StartAddress + Length)); + Status = FtwProtocol->Write ( + FtwProtocol, + Lba, // Lba + Offset, // Offset + Length, // Size + &PrivateData, // Private Data + FvbHandle, // FVB handle + Image // Buffer + ); + if (EFI_ERROR (Status)) { + break; + } + } + + // + // Now increment StartAddress, ImageBuffer and decrease the + // left size to prepare for the next block update. + // + StartAddress = StartAddress + Length; + Image = Image + Length; + TotalSize = TotalSize - Length; + if (TotalSize <= 0) { + break; + } + } + BaseAddress = NextBlock; + Lba++; + } + + if (EFI_ERROR (Status)) { + break; + } + PtrMap++; + } + + FreePool (FwVolHeader); + + *UpdatedSize = SizeLeft - TotalSize; + + return EFI_SUCCESS; +} + +/** + Directly update the buffer into flash area without fault tolerant write method. + + @param ImageBuffer Image buffer to be updated. + @param SizeLeft Size of the image buffer. + @param UpdatedSize Size of the updated buffer. + @param FlashAddress Flash address to be updated as start address. + @param FvbProtocol FVB protocol. + @param FvbHandle Handle of FVB protocol for the updated flash range. + + @retval EFI_SUCCESS Buffer data is updated into flash. + @retval EFI_INVALID_PARAMETER Base flash address is not in FVB flash area. + @retval EFI_OUT_OF_RESOURCES No enough backup space. + +**/ +EFI_STATUS +NonFaultTolerantUpdateOnPartFv ( + IN UINT8 *ImageBuffer, + IN UINTN SizeLeft, + IN OUT UINTN *UpdatedSize, + IN EFI_PHYSICAL_ADDRESS FlashAddress, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvbProtocol, + IN EFI_HANDLE FvbHandle + ) +{ + EFI_STATUS Status; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeaderTmp; + EFI_PHYSICAL_ADDRESS BaseAddress; + EFI_PHYSICAL_ADDRESS NextBlock; + EFI_FV_BLOCK_MAP_ENTRY *BlockMap; + UINTN Index; + UINTN TotalSize; + UINTN BlockSize; + EFI_LBA Lba; + UINTN Offset; + UINTN Length; + UINT8 *Image; + + // + // Get the block map to update the block one by one + // + Status = FvbProtocol->GetPhysicalAddress ( + FvbProtocol, + &BaseAddress + ); + if (EFI_ERROR (Status)) { + return Status; + } + + FwVolHeaderTmp = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN)BaseAddress; + if ((FlashAddress < BaseAddress) || (FlashAddress > ( BaseAddress + FwVolHeaderTmp->FvLength ))) { + return EFI_INVALID_PARAMETER; + } + + FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *)AllocateCopyPool ( + FwVolHeaderTmp->HeaderLength, + FwVolHeaderTmp + ); + if (FwVolHeader == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Image = ImageBuffer; + TotalSize = SizeLeft; + BlockMap = &(FwVolHeader->BlockMap[0]); + Lba = 0; + + while (TotalSize > 0) { + if ((BlockMap->NumBlocks == 0) || (BlockMap->Length == 0)) { + break; + } + + BlockSize = BlockMap->Length; + for (Index = 0 ; Index < BlockMap->NumBlocks ; Index++) { + NextBlock = BaseAddress + BlockSize; + if ((FlashAddress >= BaseAddress) && (FlashAddress < NextBlock)) { + // + // So we need to update this block + // + Offset = (UINTN) FlashAddress - (UINTN) BaseAddress; + Length = TotalSize; + if ((Length + Offset ) > BlockSize) { + Length = BlockSize - Offset; + } + + DEBUG ((EFI_D_UPDATE, "Update Flash area from %08LX to %08LX\n", FlashAddress, (UINT64)FlashAddress + Length)); + // + // Update the block + // + Status = UpdateBufferInOneBlock ( + FvbProtocol, + Lba, + Offset, + Length, + BlockSize, + Image + ); + if (EFI_ERROR (Status)) { + FreePool (FwVolHeader); + return Status; + } + + // + // Now increment FlashAddress, ImageBuffer and decrease the + // left size to prepare for the next block update. + // + FlashAddress = FlashAddress + Length; + Image = Image + Length; + TotalSize = TotalSize - Length; + if (TotalSize <= 0) { + break; + } + } + BaseAddress = NextBlock; + Lba++; + } + + if (EFI_ERROR (Status)) { + break; + } + BlockMap++; + } + + FreePool (FwVolHeader); + + *UpdatedSize = SizeLeft - TotalSize; + + return EFI_SUCCESS; +} -- cgit v1.2.3