/** @file
  Source file for CD recovery PEIM

Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR>

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

**/

#include "PeiCdExpress.h"

PEI_CD_EXPRESS_PRIVATE_DATA *mPrivateData = NULL;
CHAR8 *mRecoveryFileName;
UINTN mRecoveryFileNameSize;

/**
  Installs the Device Recovery Module PPI, Initialize BlockIo Ppi
  installation notification

  @param  FileHandle            The file handle of the image.
  @param  PeiServices           General purpose services available to every PEIM.

  @retval EFI_SUCCESS           The function completed successfully.
  @retval EFI_OUT_OF_RESOURCES  There is not enough system memory.

**/
EFI_STATUS
EFIAPI
CdExpressPeimEntry (
  IN EFI_PEI_FILE_HANDLE       FileHandle,
  IN CONST EFI_PEI_SERVICES    **PeiServices
  )
{
  EFI_STATUS                  Status;
  PEI_CD_EXPRESS_PRIVATE_DATA *PrivateData;

  if (!EFI_ERROR (PeiServicesRegisterForShadow (FileHandle))) {
    return EFI_SUCCESS;
  }

  PrivateData = AllocatePages (EFI_SIZE_TO_PAGES (sizeof (*PrivateData)));
  if (PrivateData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  mRecoveryFileNameSize = PcdGetSize(PcdRecoveryFileName) / sizeof(CHAR16);
  mRecoveryFileName = AllocatePool(mRecoveryFileNameSize);
  if (mRecoveryFileName == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  Status = UnicodeStrToAsciiStrS(PcdGetPtr(PcdRecoveryFileName), mRecoveryFileName, mRecoveryFileNameSize);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  //
  // Initialize Private Data (to zero, as is required by subsequent operations)
  //
  ZeroMem (PrivateData, sizeof (*PrivateData));
  PrivateData->Signature    = PEI_CD_EXPRESS_PRIVATE_DATA_SIGNATURE;

  PrivateData->BlockBuffer  = AllocatePages (EFI_SIZE_TO_PAGES (PEI_CD_BLOCK_SIZE));
  if (PrivateData->BlockBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  PrivateData->CapsuleCount = 0;
  Status = UpdateBlocksAndVolumes (PrivateData, TRUE);
  Status = UpdateBlocksAndVolumes (PrivateData, FALSE);

  //
  // Installs Ppi
  //
  PrivateData->DeviceRecoveryPpi.GetNumberRecoveryCapsules  = GetNumberRecoveryCapsules;
  PrivateData->DeviceRecoveryPpi.GetRecoveryCapsuleInfo     = GetRecoveryCapsuleInfo;
  PrivateData->DeviceRecoveryPpi.LoadRecoveryCapsule        = LoadRecoveryCapsule;

  PrivateData->PpiDescriptor.Flags = (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);
  PrivateData->PpiDescriptor.Guid  = &gEfiPeiDeviceRecoveryModulePpiGuid;
  PrivateData->PpiDescriptor.Ppi   = &PrivateData->DeviceRecoveryPpi;

  Status = PeiServicesInstallPpi (&PrivateData->PpiDescriptor);
  if (EFI_ERROR (Status)) {
    return EFI_OUT_OF_RESOURCES;
  }
  //
  // PrivateData is allocated now, set it to the module variable
  //
  mPrivateData = PrivateData;

  //
  // Installs Block Io Ppi notification function
  //
  PrivateData->NotifyDescriptor.Flags =
    (
      EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK
    );
  PrivateData->NotifyDescriptor.Guid    = &gEfiPeiVirtualBlockIoPpiGuid;
  PrivateData->NotifyDescriptor.Notify  = BlockIoNotifyEntry;

  PrivateData->NotifyDescriptor2.Flags =
    (
      EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK |
      EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST
    );
  PrivateData->NotifyDescriptor2.Guid    = &gEfiPeiVirtualBlockIo2PpiGuid;
  PrivateData->NotifyDescriptor2.Notify  = BlockIoNotifyEntry;

  return PeiServicesNotifyPpi (&PrivateData->NotifyDescriptor);

}

/**
  BlockIo installation notification function.

  This function finds out all the current Block IO PPIs in the system and add them
  into private data.

  @param  PeiServices            Indirect reference to the PEI Services Table.
  @param  NotifyDescriptor       Address of the notification descriptor data structure.
  @param  Ppi                    Address of the PPI that was installed.

  @retval EFI_SUCCESS            The function completes successfully.

**/
EFI_STATUS
EFIAPI
BlockIoNotifyEntry (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  )
{
  if (CompareGuid (NotifyDescriptor->Guid, &gEfiPeiVirtualBlockIo2PpiGuid)) {
    UpdateBlocksAndVolumes (mPrivateData, TRUE);
  } else {
    UpdateBlocksAndVolumes (mPrivateData, FALSE);
  }

  return EFI_SUCCESS;
}

/**
  Finds out all the current Block IO PPIs in the system and add them into private data.

  @param PrivateData                    The private data structure that contains recovery module information.
  @param BlockIo2                       Boolean to show whether using BlockIo2 or BlockIo.

  @retval EFI_SUCCESS                   The blocks and volumes are updated successfully.

**/
EFI_STATUS
UpdateBlocksAndVolumes (
  IN OUT PEI_CD_EXPRESS_PRIVATE_DATA     *PrivateData,
  IN     BOOLEAN                         BlockIo2
  )
{
  EFI_STATUS                      Status;
  EFI_PEI_PPI_DESCRIPTOR          *TempPpiDescriptor;
  UINTN                           BlockIoPpiInstance;
  EFI_PEI_RECOVERY_BLOCK_IO_PPI   *BlockIoPpi;
  EFI_PEI_RECOVERY_BLOCK_IO2_PPI  *BlockIo2Ppi;
  UINTN                           NumberBlockDevices;
  UINTN                           IndexBlockDevice;
  EFI_PEI_BLOCK_IO_MEDIA          Media;
  EFI_PEI_BLOCK_IO2_MEDIA         Media2;
  EFI_PEI_SERVICES                **PeiServices;

  IndexBlockDevice = 0;
  BlockIo2Ppi      = NULL;
  BlockIoPpi       = NULL;
  //
  // Find out all Block Io Ppi instances within the system
  // Assuming all device Block Io Peims are dispatched already
  //
  for (BlockIoPpiInstance = 0; BlockIoPpiInstance < PEI_CD_EXPRESS_MAX_BLOCK_IO_PPI; BlockIoPpiInstance++) {
    if (BlockIo2) {
      Status = PeiServicesLocatePpi (
                                &gEfiPeiVirtualBlockIo2PpiGuid,
                                BlockIoPpiInstance,
                                &TempPpiDescriptor,
                                (VOID **) &BlockIo2Ppi
                                );
    } else {
      Status = PeiServicesLocatePpi (
                                &gEfiPeiVirtualBlockIoPpiGuid,
                                BlockIoPpiInstance,
                                &TempPpiDescriptor,
                                (VOID **) &BlockIoPpi
                                );
    }
    if (EFI_ERROR (Status)) {
      //
      // Done with all Block Io Ppis
      //
      break;
    }

    PeiServices = (EFI_PEI_SERVICES  **) GetPeiServicesTablePointer ();
    if (BlockIo2) {
      Status = BlockIo2Ppi->GetNumberOfBlockDevices (
                              PeiServices,
                              BlockIo2Ppi,
                              &NumberBlockDevices
                              );
    } else {
      Status = BlockIoPpi->GetNumberOfBlockDevices (
                            PeiServices,
                            BlockIoPpi,
                            &NumberBlockDevices
                            );
    }
    if (EFI_ERROR (Status) || (NumberBlockDevices == 0)) {
      continue;
    }
    //
    // Just retrieve the first block, should emulate all blocks.
    //
    for (IndexBlockDevice = 1; IndexBlockDevice <= NumberBlockDevices && PrivateData->CapsuleCount < PEI_CD_EXPRESS_MAX_CAPSULE_NUMBER; IndexBlockDevice ++) {
      if (BlockIo2) {
        Status = BlockIo2Ppi->GetBlockDeviceMediaInfo (
                                PeiServices,
                                BlockIo2Ppi,
                                IndexBlockDevice,
                                &Media2
                                );
        if (EFI_ERROR (Status) ||
            !Media2.MediaPresent ||
             ((Media2.InterfaceType != MSG_ATAPI_DP) && (Media2.InterfaceType != MSG_USB_DP)) ||
            (Media2.BlockSize != PEI_CD_BLOCK_SIZE)
            ) {
          continue;
        }
        DEBUG ((EFI_D_INFO, "PeiCdExpress InterfaceType is %d\n", Media2.InterfaceType));
        DEBUG ((EFI_D_INFO, "PeiCdExpress MediaPresent is %d\n", Media2.MediaPresent));
        DEBUG ((EFI_D_INFO, "PeiCdExpress BlockSize is  0x%x\n", Media2.BlockSize));
      } else {
        Status = BlockIoPpi->GetBlockDeviceMediaInfo (
                              PeiServices,
                              BlockIoPpi,
                              IndexBlockDevice,
                              &Media
                              );
        if (EFI_ERROR (Status) ||
            !Media.MediaPresent ||
             ((Media.DeviceType != IdeCDROM) && (Media.DeviceType != UsbMassStorage)) ||
            (Media.BlockSize != PEI_CD_BLOCK_SIZE)
            ) {
          continue;
        }
        DEBUG ((EFI_D_INFO, "PeiCdExpress DeviceType is %d\n", Media.DeviceType));
        DEBUG ((EFI_D_INFO, "PeiCdExpress MediaPresent is %d\n", Media.MediaPresent));
        DEBUG ((EFI_D_INFO, "PeiCdExpress BlockSize is  0x%x\n", Media.BlockSize));
      }

      DEBUG ((EFI_D_INFO, "PeiCdExpress Status is %d\n", Status));

      DEBUG ((EFI_D_INFO, "IndexBlockDevice is %d\n", IndexBlockDevice));
      PrivateData->CapsuleData[PrivateData->CapsuleCount].IndexBlock = IndexBlockDevice;
      if (BlockIo2) {
        PrivateData->CapsuleData[PrivateData->CapsuleCount].BlockIo2 = BlockIo2Ppi;
      } else {
        PrivateData->CapsuleData[PrivateData->CapsuleCount].BlockIo  = BlockIoPpi;
      }
      Status = FindRecoveryCapsules (PrivateData);
      DEBUG ((EFI_D_INFO, "Status is %d\n", Status));

      if (EFI_ERROR (Status)) {
        continue;
      }

      PrivateData->CapsuleCount++;
    }

  }

  return EFI_SUCCESS;
}

/**
  Finds out the recovery capsule in the current volume.

  @param PrivateData                    The private data structure that contains recovery module information.

  @retval EFI_SUCCESS                   The recovery capsule is successfully found in the volume.
  @retval EFI_NOT_FOUND                 The recovery capsule is not found in the volume.

**/
EFI_STATUS
EFIAPI
FindRecoveryCapsules (
  IN OUT PEI_CD_EXPRESS_PRIVATE_DATA            *PrivateData
  )
{
  EFI_STATUS                      Status;
  UINTN                           Lba;
  EFI_PEI_RECOVERY_BLOCK_IO_PPI   *BlockIoPpi;
  EFI_PEI_RECOVERY_BLOCK_IO2_PPI  *BlockIo2Ppi;
  UINTN                           BufferSize;
  UINT8                           *Buffer;
  UINT8                           Type;
  UINT8                           *StandardID;
  UINT32                          RootDirLBA;
  PEI_CD_EXPRESS_DIR_FILE_RECORD  *RoorDirRecord;
  UINTN                           VolumeSpaceSize;
  BOOLEAN                         StartOfVolume;
  UINTN                           OriginalLBA;
  UINTN                           IndexBlockDevice;

  Buffer      = PrivateData->BlockBuffer;
  BufferSize  = PEI_CD_BLOCK_SIZE;

  Lba         = 16;
  //
  // The volume descriptor starts on Lba 16
  //
  IndexBlockDevice = PrivateData->CapsuleData[PrivateData->CapsuleCount].IndexBlock;
  BlockIoPpi       = PrivateData->CapsuleData[PrivateData->CapsuleCount].BlockIo;
  BlockIo2Ppi      = PrivateData->CapsuleData[PrivateData->CapsuleCount].BlockIo2;

  VolumeSpaceSize = 0;
  StartOfVolume   = TRUE;
  OriginalLBA     = 16;

  while (TRUE) {
    SetMem (Buffer, BufferSize, 0);
    if (BlockIo2Ppi != NULL) {
      Status = BlockIo2Ppi->ReadBlocks (
                            (EFI_PEI_SERVICES **) GetPeiServicesTablePointer (),
                            BlockIo2Ppi,
                            IndexBlockDevice,
                            Lba,
                            BufferSize,
                            Buffer
                            );
    } else {
      Status = BlockIoPpi->ReadBlocks (
                            (EFI_PEI_SERVICES **) GetPeiServicesTablePointer (),
                            BlockIoPpi,
                            IndexBlockDevice,
                            Lba,
                            BufferSize,
                            Buffer
                            );
    }
    if (EFI_ERROR (Status)) {
      return Status;
    }

    StandardID = (UINT8 *) (Buffer + PEI_CD_EXPRESS_STANDARD_ID_OFFSET);
    if (!StringCmp (StandardID, (UINT8 *) PEI_CD_STANDARD_ID, PEI_CD_EXPRESS_STANDARD_ID_SIZE, TRUE)) {
      break;
    }

    if (StartOfVolume) {
      OriginalLBA   = Lba;
      StartOfVolume = FALSE;
    }

    Type = *(UINT8 *) (Buffer + PEI_CD_EXPRESS_VOLUME_TYPE_OFFSET);
    if (Type == PEI_CD_EXPRESS_VOLUME_TYPE_TERMINATOR) {
      if (VolumeSpaceSize == 0) {
        break;
      } else {
        Lba             = (OriginalLBA + VolumeSpaceSize);
        VolumeSpaceSize = 0;
        StartOfVolume   = TRUE;
        continue;
      }
    }

    if (Type != PEI_CD_EXPRESS_VOLUME_TYPE_PRIMARY) {
      Lba++;
      continue;
    }

    VolumeSpaceSize = *(UINT32 *) (Buffer + PEI_CD_EXPRESS_VOLUME_SPACE_OFFSET);

    RoorDirRecord   = (PEI_CD_EXPRESS_DIR_FILE_RECORD *) (Buffer + PEI_CD_EXPRESS_ROOT_DIR_RECORD_OFFSET);
    RootDirLBA      = RoorDirRecord->LocationOfExtent[0];

    Status          = RetrieveCapsuleFileFromRoot (PrivateData, BlockIoPpi, BlockIo2Ppi, IndexBlockDevice, RootDirLBA);
    if (!EFI_ERROR (Status)) {
      //
      // Just look for the first primary descriptor
      //
      return EFI_SUCCESS;
    }

    Lba++;
  }

  return EFI_NOT_FOUND;
}

/**
  Retrieves the recovery capsule in root directory of the current volume.

  @param PrivateData                    The private data structure that contains recovery module information.
  @param BlockIoPpi                     The Block IO PPI used to access the volume.
  @param BlockIo2Ppi                    The Block IO 2 PPI used to access the volume.
  @param IndexBlockDevice               The index of current block device.
  @param Lba                            The starting logic block address to retrieve capsule.

  @retval EFI_SUCCESS                   The recovery capsule is successfully found in the volume.
  @retval EFI_NOT_FOUND                 The recovery capsule is not found in the volume.
  @retval Others

**/
EFI_STATUS
EFIAPI
RetrieveCapsuleFileFromRoot (
  IN OUT PEI_CD_EXPRESS_PRIVATE_DATA        *PrivateData,
  IN EFI_PEI_RECOVERY_BLOCK_IO_PPI          *BlockIoPpi,
  IN EFI_PEI_RECOVERY_BLOCK_IO2_PPI         *BlockIo2Ppi,
  IN UINTN                                  IndexBlockDevice,
  IN UINT32                                 Lba
  )
{
  EFI_STATUS                      Status;
  UINTN                           BufferSize;
  UINT8                           *Buffer;
  PEI_CD_EXPRESS_DIR_FILE_RECORD  *FileRecord;
  UINTN                           Index;

  Buffer      = PrivateData->BlockBuffer;
  BufferSize  = PEI_CD_BLOCK_SIZE;

  SetMem (Buffer, BufferSize, 0);

  if (BlockIo2Ppi != NULL) {
    Status = BlockIo2Ppi->ReadBlocks (
                          (EFI_PEI_SERVICES **) GetPeiServicesTablePointer (),
                          BlockIo2Ppi,
                          IndexBlockDevice,
                          Lba,
                          BufferSize,
                          Buffer
                          );
  } else {
    Status = BlockIoPpi->ReadBlocks (
                          (EFI_PEI_SERVICES **) GetPeiServicesTablePointer (),
                          BlockIoPpi,
                          IndexBlockDevice,
                          Lba,
                          BufferSize,
                          Buffer
                          );
  }
  if (EFI_ERROR (Status)) {
    return Status;
  }

  while (1) {
    FileRecord = (PEI_CD_EXPRESS_DIR_FILE_RECORD *) Buffer;

    if (FileRecord->Length == 0) {
      break;
    }
    //
    // Not intend to check other flag now
    //
    if ((FileRecord->Flag & PEI_CD_EXPRESS_DIR_FILE_REC_FLAG_ISDIR) != 0) {
      Buffer += FileRecord->Length;
      continue;
    }

    for (Index = 0; Index < FileRecord->FileIDLength; Index++) {
      if (FileRecord->FileID[Index] == ';') {
        break;
      }
    }

    if (Index != mRecoveryFileNameSize - 1) {
      Buffer += FileRecord->Length;
      continue;
    }

    if (!StringCmp (FileRecord->FileID, (UINT8 *)mRecoveryFileName, mRecoveryFileNameSize - 1, FALSE)) {
      Buffer += FileRecord->Length;
      continue;
    }

    PrivateData->CapsuleData[PrivateData->CapsuleCount].CapsuleStartLBA = FileRecord->LocationOfExtent[0];
    PrivateData->CapsuleData[PrivateData->CapsuleCount].CapsuleBlockAlignedSize =
      (
        FileRecord->DataLength[0] /
        PEI_CD_BLOCK_SIZE +
        1
      ) *
      PEI_CD_BLOCK_SIZE;
    PrivateData->CapsuleData[PrivateData->CapsuleCount].CapsuleSize = FileRecord->DataLength[0];

    return EFI_SUCCESS;
  }

  return EFI_NOT_FOUND;
}

/**
  Returns the number of DXE capsules residing on the device.

  This function searches for DXE capsules from the associated device and returns
  the number and maximum size in bytes of the capsules discovered. Entry 1 is
  assumed to be the highest load priority and entry N is assumed to be the lowest
  priority.

  @param[in]  PeiServices              General-purpose services that are available
                                       to every PEIM
  @param[in]  This                     Indicates the EFI_PEI_DEVICE_RECOVERY_MODULE_PPI
                                       instance.
  @param[out] NumberRecoveryCapsules   Pointer to a caller-allocated UINTN. On
                                       output, *NumberRecoveryCapsules contains
                                       the number of recovery capsule images
                                       available for retrieval from this PEIM
                                       instance.

  @retval EFI_SUCCESS        One or more capsules were discovered.
  @retval EFI_DEVICE_ERROR   A device error occurred.
  @retval EFI_NOT_FOUND      A recovery DXE capsule cannot be found.

**/
EFI_STATUS
EFIAPI
GetNumberRecoveryCapsules (
  IN EFI_PEI_SERVICES                               **PeiServices,
  IN EFI_PEI_DEVICE_RECOVERY_MODULE_PPI             *This,
  OUT UINTN                                         *NumberRecoveryCapsules
  )
{
  PEI_CD_EXPRESS_PRIVATE_DATA *PrivateData;

  PrivateData = PEI_CD_EXPRESS_PRIVATE_DATA_FROM_THIS (This);
  UpdateBlocksAndVolumes (PrivateData, TRUE);
  UpdateBlocksAndVolumes (PrivateData, FALSE);
  *NumberRecoveryCapsules = PrivateData->CapsuleCount;

  if (*NumberRecoveryCapsules == 0) {
    return EFI_NOT_FOUND;
  }

  return EFI_SUCCESS;
}

/**
  Returns the size and type of the requested recovery capsule.

  This function gets the size and type of the capsule specified by CapsuleInstance.

  @param[in]  PeiServices       General-purpose services that are available to every PEIM
  @param[in]  This              Indicates the EFI_PEI_DEVICE_RECOVERY_MODULE_PPI
                                instance.
  @param[in]  CapsuleInstance   Specifies for which capsule instance to retrieve
                                the information.  This parameter must be between
                                one and the value returned by GetNumberRecoveryCapsules()
                                in NumberRecoveryCapsules.
  @param[out] Size              A pointer to a caller-allocated UINTN in which
                                the size of the requested recovery module is
                                returned.
  @param[out] CapsuleType       A pointer to a caller-allocated EFI_GUID in which
                                the type of the requested recovery capsule is
                                returned.  The semantic meaning of the value
                                returned is defined by the implementation.

  @retval EFI_SUCCESS        One or more capsules were discovered.
  @retval EFI_DEVICE_ERROR   A device error occurred.
  @retval EFI_NOT_FOUND      A recovery DXE capsule cannot be found.

**/
EFI_STATUS
EFIAPI
GetRecoveryCapsuleInfo (
  IN  EFI_PEI_SERVICES                              **PeiServices,
  IN  EFI_PEI_DEVICE_RECOVERY_MODULE_PPI            *This,
  IN  UINTN                                         CapsuleInstance,
  OUT UINTN                                         *Size,
  OUT EFI_GUID                                      *CapsuleType
  )
{
  PEI_CD_EXPRESS_PRIVATE_DATA *PrivateData;
  UINTN                       NumberRecoveryCapsules;
  EFI_STATUS                  Status;

  Status = GetNumberRecoveryCapsules (PeiServices, This, &NumberRecoveryCapsules);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  if ((CapsuleInstance == 0) || (CapsuleInstance > NumberRecoveryCapsules)) {
    return EFI_NOT_FOUND;
  }

  PrivateData = PEI_CD_EXPRESS_PRIVATE_DATA_FROM_THIS (This);

  *Size = PrivateData->CapsuleData[CapsuleInstance - 1].CapsuleSize;
  CopyMem (
    CapsuleType,
    &gRecoveryOnDataCdGuid,
    sizeof (EFI_GUID)
    );

  return EFI_SUCCESS;
}

/**
  Loads a DXE capsule from some media into memory.

  This function, by whatever mechanism, retrieves a DXE capsule from some device
  and loads it into memory. Note that the published interface is device neutral.

  @param[in]     PeiServices       General-purpose services that are available
                                   to every PEIM
  @param[in]     This              Indicates the EFI_PEI_DEVICE_RECOVERY_MODULE_PPI
                                   instance.
  @param[in]     CapsuleInstance   Specifies which capsule instance to retrieve.
  @param[out]    Buffer            Specifies a caller-allocated buffer in which
                                   the requested recovery capsule will be returned.

  @retval EFI_SUCCESS        The capsule was loaded correctly.
  @retval EFI_DEVICE_ERROR   A device error occurred.
  @retval EFI_NOT_FOUND      A requested recovery DXE capsule cannot be found.

**/
EFI_STATUS
EFIAPI
LoadRecoveryCapsule (
  IN EFI_PEI_SERVICES                             **PeiServices,
  IN EFI_PEI_DEVICE_RECOVERY_MODULE_PPI           *This,
  IN UINTN                                        CapsuleInstance,
  OUT VOID                                        *Buffer
  )
{
  EFI_STATUS                      Status;
  PEI_CD_EXPRESS_PRIVATE_DATA     *PrivateData;
  EFI_PEI_RECOVERY_BLOCK_IO_PPI   *BlockIoPpi;
  EFI_PEI_RECOVERY_BLOCK_IO2_PPI  *BlockIo2Ppi;
  UINTN                           NumberRecoveryCapsules;

  Status = GetNumberRecoveryCapsules (PeiServices, This, &NumberRecoveryCapsules);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  if ((CapsuleInstance == 0) || (CapsuleInstance > NumberRecoveryCapsules)) {
    return EFI_NOT_FOUND;
  }

  PrivateData = PEI_CD_EXPRESS_PRIVATE_DATA_FROM_THIS (This);
  BlockIoPpi  = PrivateData->CapsuleData[CapsuleInstance - 1].BlockIo;
  BlockIo2Ppi = PrivateData->CapsuleData[CapsuleInstance - 1].BlockIo2;

  if (BlockIo2Ppi != NULL) {
    Status = BlockIo2Ppi->ReadBlocks (
                          PeiServices,
                          BlockIo2Ppi,
                          PrivateData->CapsuleData[CapsuleInstance - 1].IndexBlock,
                          PrivateData->CapsuleData[CapsuleInstance - 1].CapsuleStartLBA,
                          PrivateData->CapsuleData[CapsuleInstance - 1].CapsuleBlockAlignedSize,
                          Buffer
                          );
  } else {
    Status = BlockIoPpi->ReadBlocks (
                          PeiServices,
                          BlockIoPpi,
                          PrivateData->CapsuleData[CapsuleInstance - 1].IndexBlock,
                          PrivateData->CapsuleData[CapsuleInstance - 1].CapsuleStartLBA,
                          PrivateData->CapsuleData[CapsuleInstance - 1].CapsuleBlockAlignedSize,
                          Buffer
                          );
  }
  return Status;
}

/**
  This function compares two ASCII strings in case sensitive/insensitive way.

  @param  Source1           The first string.
  @param  Source2           The second string.
  @param  Size              The maximum comparison length.
  @param  CaseSensitive     Flag to indicate whether the comparison is case sensitive.

  @retval TRUE              The two strings are the same.
  @retval FALSE             The two string are not the same.

**/
BOOLEAN
StringCmp (
  IN UINT8      *Source1,
  IN UINT8      *Source2,
  IN UINTN      Size,
  IN BOOLEAN    CaseSensitive
  )
{
  UINTN Index;
  UINT8 Dif;

  for (Index = 0; Index < Size; Index++) {
    if (Source1[Index] == Source2[Index]) {
      continue;
    }

    if (!CaseSensitive) {
      Dif = (UINT8) ((Source1[Index] > Source2[Index]) ? (Source1[Index] - Source2[Index]) : (Source2[Index] - Source1[Index]));
      if (Dif == ('a' - 'A')) {
        continue;
      }
    }

    return FALSE;
  }

  return TRUE;
}
