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

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

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