/** @file
  Functions in this file will program the image into flash area.

  Copyright (c) 2002 - 2018, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#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;
}
