/** @file
PEIM to produce gEfiPeiVirtualBlockIoPpiGuid & gEfiPeiVirtualBlockIo2PpiGuid PPI for
ATA controllers in the platform.

This PPI can be consumed by PEIM which produce gEfiPeiDeviceRecoveryModulePpiGuid
for Atapi CD ROM device.

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

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

**/

#include "AtapiPeim.h"

/**
  Initializes the Atapi Block Io PPI.

  @param[in]  FileHandle           Handle of the file being invoked.
  @param[in]  PeiServices          Describes the list of possible PEI Services.

  @retval     EFI_SUCCESS          Operation performed successfully.
  @retval     EFI_OUT_OF_RESOURCES Not enough memory to allocate.

**/
EFI_STATUS
EFIAPI
AtapiPeimEntry (
  IN EFI_PEI_FILE_HANDLE     FileHandle,
  IN CONST EFI_PEI_SERVICES  **PeiServices
  )
{
  PEI_ATA_CONTROLLER_PPI  *AtaControllerPpi;
  EFI_STATUS              Status;
  ATAPI_BLK_IO_DEV        *AtapiBlkIoDev;

  Status = PeiServicesRegisterForShadow (FileHandle);
  if (!EFI_ERROR (Status)) {
    return Status;
  }

  Status = PeiServicesLocatePpi (
             &gPeiAtaControllerPpiGuid,
             0,
             NULL,
             (VOID **)&AtaControllerPpi
             );
  ASSERT_EFI_ERROR (Status);

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

  AtapiBlkIoDev->Signature        = ATAPI_BLK_IO_DEV_SIGNATURE;
  AtapiBlkIoDev->AtaControllerPpi = AtaControllerPpi;

  //
  // atapi device enumeration and build private data
  //
  AtapiEnumerateDevices (AtapiBlkIoDev);

  AtapiBlkIoDev->AtapiBlkIo.GetNumberOfBlockDevices  = AtapiGetNumberOfBlockDevices;
  AtapiBlkIoDev->AtapiBlkIo.GetBlockDeviceMediaInfo  = AtapiGetBlockDeviceMediaInfo;
  AtapiBlkIoDev->AtapiBlkIo.ReadBlocks               = AtapiReadBlocks;
  AtapiBlkIoDev->AtapiBlkIo2.Revision                = EFI_PEI_RECOVERY_BLOCK_IO2_PPI_REVISION;
  AtapiBlkIoDev->AtapiBlkIo2.GetNumberOfBlockDevices = AtapiGetNumberOfBlockDevices2;
  AtapiBlkIoDev->AtapiBlkIo2.GetBlockDeviceMediaInfo = AtapiGetBlockDeviceMediaInfo2;
  AtapiBlkIoDev->AtapiBlkIo2.ReadBlocks              = AtapiReadBlocks2;

  AtapiBlkIoDev->PpiDescriptor.Flags = EFI_PEI_PPI_DESCRIPTOR_PPI;
  AtapiBlkIoDev->PpiDescriptor.Guid  = &gEfiPeiVirtualBlockIoPpiGuid;
  AtapiBlkIoDev->PpiDescriptor.Ppi   = &AtapiBlkIoDev->AtapiBlkIo;

  AtapiBlkIoDev->PpiDescriptor2.Flags = (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);
  AtapiBlkIoDev->PpiDescriptor2.Guid  = &gEfiPeiVirtualBlockIo2PpiGuid;
  AtapiBlkIoDev->PpiDescriptor2.Ppi   = &AtapiBlkIoDev->AtapiBlkIo2;

  DEBUG ((DEBUG_INFO, "Atatpi Device Count is %d\n", AtapiBlkIoDev->DeviceCount));
  if (AtapiBlkIoDev->DeviceCount != 0) {
    Status = PeiServicesInstallPpi (&AtapiBlkIoDev->PpiDescriptor);
    if (EFI_ERROR (Status)) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  return EFI_SUCCESS;
}

/**
  Gets the count of block I/O devices that one specific block driver detects.

  This function is used for getting the count of block I/O devices that one
  specific block driver detects.  To the PEI ATAPI driver, it returns the number
  of all the detected ATAPI devices it detects during the enumeration process.
  To the PEI legacy floppy driver, it returns the number of all the legacy
  devices it finds during its enumeration process. If no device is detected,
  then the function will return zero.

  @param[in]  PeiServices          General-purpose services that are available
                                   to every PEIM.
  @param[in]  This                 Indicates the EFI_PEI_RECOVERY_BLOCK_IO_PPI
                                   instance.
  @param[out] NumberBlockDevices   The number of block I/O devices discovered.

  @retval     EFI_SUCCESS          Operation performed successfully.

**/
EFI_STATUS
EFIAPI
AtapiGetNumberOfBlockDevices (
  IN   EFI_PEI_SERVICES               **PeiServices,
  IN   EFI_PEI_RECOVERY_BLOCK_IO_PPI  *This,
  OUT  UINTN                          *NumberBlockDevices
  )
{
  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev;

  AtapiBlkIoDev = NULL;

  AtapiBlkIoDev = PEI_RECOVERY_ATAPI_FROM_BLKIO_THIS (This);

  *NumberBlockDevices = AtapiBlkIoDev->DeviceCount;

  return EFI_SUCCESS;
}

/**
  Gets a block device's media information.

  This function will provide the caller with the specified block device's media
  information. If the media changes, calling this function will update the media
  information accordingly.

  @param[in]  PeiServices   General-purpose services that are available to every
                            PEIM
  @param[in]  This          Indicates the EFI_PEI_RECOVERY_BLOCK_IO_PPI instance.
  @param[in]  DeviceIndex   Specifies the block device to which the function wants
                            to talk. Because the driver that implements Block I/O
                            PPIs will manage multiple block devices, the PPIs that
                            want to talk to a single device must specify the
                            device index that was assigned during the enumeration
                            process. This index is a number from one to
                            NumberBlockDevices.
  @param[out] MediaInfo     The media information of the specified block media.
                            The caller is responsible for the ownership of this
                            data structure.

  @retval EFI_SUCCESS           Media information about the specified block device
                                was obtained successfully.
  @retval EFI_DEVICE_ERROR      Cannot get the media information due to a hardware
                                error.
  @retval Others                Other failure occurs.

**/
EFI_STATUS
EFIAPI
AtapiGetBlockDeviceMediaInfo (
  IN   EFI_PEI_SERVICES               **PeiServices,
  IN   EFI_PEI_RECOVERY_BLOCK_IO_PPI  *This,
  IN   UINTN                          DeviceIndex,
  OUT  EFI_PEI_BLOCK_IO_MEDIA         *MediaInfo
  )
{
  UINTN             DeviceCount;
  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev;
  EFI_STATUS        Status;
  UINTN             Index;

  AtapiBlkIoDev = NULL;

  if ((This == NULL) || (MediaInfo == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  AtapiBlkIoDev = PEI_RECOVERY_ATAPI_FROM_BLKIO_THIS (This);

  DeviceCount = AtapiBlkIoDev->DeviceCount;

  //
  // DeviceIndex is a value from 1 to NumberBlockDevices.
  //
  if ((DeviceIndex < 1) || (DeviceIndex > DeviceCount) || (DeviceIndex > MAX_IDE_DEVICES)) {
    return EFI_INVALID_PARAMETER;
  }

  Index = DeviceIndex - 1;

  //
  // probe media and retrieve latest media information
  //
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo DevicePosition is %d\n", AtapiBlkIoDev->DeviceInfo[Index].DevicePosition));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo DeviceType is   %d\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.DeviceType));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo MediaPresent is %d\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.MediaPresent));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo BlockSize is  0x%x\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.BlockSize));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo LastBlock is  0x%x\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.LastBlock));

  Status = DetectMedia (
             AtapiBlkIoDev,
             AtapiBlkIoDev->DeviceInfo[Index].DevicePosition,
             &AtapiBlkIoDev->DeviceInfo[Index].MediaInfo,
             &AtapiBlkIoDev->DeviceInfo[Index].MediaInfo2
             );
  if (Status != EFI_SUCCESS) {
    return EFI_DEVICE_ERROR;
  }

  DEBUG ((DEBUG_INFO, "Atatpi GetInfo DevicePosition is %d\n", AtapiBlkIoDev->DeviceInfo[Index].DevicePosition));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo DeviceType is   %d\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.DeviceType));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo MediaPresent is %d\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.MediaPresent));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo BlockSize is  0x%x\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.BlockSize));
  DEBUG ((DEBUG_INFO, "Atatpi GetInfo LastBlock is  0x%x\n", AtapiBlkIoDev->DeviceInfo[Index].MediaInfo.LastBlock));

  //
  // Get media info from AtapiBlkIoDev
  //
  CopyMem (MediaInfo, &AtapiBlkIoDev->DeviceInfo[Index].MediaInfo, sizeof (EFI_PEI_BLOCK_IO_MEDIA));

  return EFI_SUCCESS;
}

/**
  Reads the requested number of blocks from the specified block device.

  The function reads the requested number of blocks from the device. All the
  blocks are read, or an error is returned. If there is no media in the device,
  the function returns EFI_NO_MEDIA.

  @param[in]  PeiServices   General-purpose services that are available to
                            every PEIM.
  @param[in]  This          Indicates the EFI_PEI_RECOVERY_BLOCK_IO_PPI instance.
  @param[in]  DeviceIndex   Specifies the block device to which the function wants
                            to talk. Because the driver that implements Block I/O
                            PPIs will manage multiple block devices, the PPIs that
                            want to talk to a single device must specify the device
                            index that was assigned during the enumeration process.
                            This index is a number from one to NumberBlockDevices.
  @param[in]  StartLBA      The starting logical block address (LBA) to read from
                            on the device
  @param[in]  BufferSize    The size of the Buffer in bytes. This number must be
                            a multiple of the intrinsic block size of the device.
  @param[out] Buffer        A pointer to the destination buffer for the data.
                            The caller is responsible for the ownership of the
                            buffer.

  @retval EFI_SUCCESS             The data was read correctly from the device.
  @retval EFI_DEVICE_ERROR        The device reported an error while attempting
                                  to perform the read operation.
  @retval EFI_INVALID_PARAMETER   The read request contains LBAs that are not
                                  valid, or the buffer is not properly aligned.
  @retval EFI_NO_MEDIA            There is no media in the device.
  @retval EFI_BAD_BUFFER_SIZE     The BufferSize parameter is not a multiple of
                                  the intrinsic block size of the device.

**/
EFI_STATUS
EFIAPI
AtapiReadBlocks (
  IN   EFI_PEI_SERVICES               **PeiServices,
  IN   EFI_PEI_RECOVERY_BLOCK_IO_PPI  *This,
  IN   UINTN                          DeviceIndex,
  IN   EFI_PEI_LBA                    StartLBA,
  IN   UINTN                          BufferSize,
  OUT  VOID                           *Buffer
  )
{
  EFI_PEI_BLOCK_IO_MEDIA  MediaInfo;
  EFI_STATUS              Status;
  UINTN                   NumberOfBlocks;
  UINTN                   BlockSize;
  ATAPI_BLK_IO_DEV        *AtapiBlkIoDev;

  AtapiBlkIoDev = NULL;

  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  AtapiBlkIoDev = PEI_RECOVERY_ATAPI_FROM_BLKIO_THIS (This);

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (BufferSize == 0) {
    return EFI_SUCCESS;
  }

  Status = AtapiGetBlockDeviceMediaInfo (
             PeiServices,
             This,
             DeviceIndex,
             &MediaInfo
             );
  if (Status != EFI_SUCCESS) {
    return EFI_DEVICE_ERROR;
  }

  if (!MediaInfo.MediaPresent) {
    return EFI_NO_MEDIA;
  }

  BlockSize = MediaInfo.BlockSize;

  if (BufferSize % BlockSize != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  NumberOfBlocks = BufferSize / BlockSize;

  if ((StartLBA + NumberOfBlocks - 1) > AtapiBlkIoDev->DeviceInfo[DeviceIndex - 1].MediaInfo2.LastBlock) {
    return EFI_INVALID_PARAMETER;
  }

  Status = ReadSectors (
             AtapiBlkIoDev,
             AtapiBlkIoDev->DeviceInfo[DeviceIndex - 1].DevicePosition,
             Buffer,
             StartLBA,
             NumberOfBlocks,
             BlockSize
             );
  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Gets the count of block I/O devices that one specific block driver detects.

  This function is used for getting the count of block I/O devices that one
  specific block driver detects.  To the PEI ATAPI driver, it returns the number
  of all the detected ATAPI devices it detects during the enumeration process.
  To the PEI legacy floppy driver, it returns the number of all the legacy
  devices it finds during its enumeration process. If no device is detected,
  then the function will return zero.

  @param[in]  PeiServices          General-purpose services that are available
                                   to every PEIM.
  @param[in]  This                 Indicates the EFI_PEI_RECOVERY_BLOCK_IO2_PPI
                                   instance.
  @param[out] NumberBlockDevices   The number of block I/O devices discovered.

  @retval     EFI_SUCCESS          Operation performed successfully.

**/
EFI_STATUS
EFIAPI
AtapiGetNumberOfBlockDevices2 (
  IN   EFI_PEI_SERVICES                **PeiServices,
  IN   EFI_PEI_RECOVERY_BLOCK_IO2_PPI  *This,
  OUT  UINTN                           *NumberBlockDevices
  )
{
  EFI_STATUS        Status;
  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev;

  AtapiBlkIoDev = PEI_RECOVERY_ATAPI_FROM_BLKIO2_THIS (This);

  Status = AtapiGetNumberOfBlockDevices (
             PeiServices,
             &AtapiBlkIoDev->AtapiBlkIo,
             NumberBlockDevices
             );

  return Status;
}

/**
  Gets a block device's media information.

  This function will provide the caller with the specified block device's media
  information. If the media changes, calling this function will update the media
  information accordingly.

  @param[in]  PeiServices   General-purpose services that are available to every
                            PEIM
  @param[in]  This          Indicates the EFI_PEI_RECOVERY_BLOCK_IO2_PPI instance.
  @param[in]  DeviceIndex   Specifies the block device to which the function wants
                            to talk. Because the driver that implements Block I/O
                            PPIs will manage multiple block devices, the PPIs that
                            want to talk to a single device must specify the
                            device index that was assigned during the enumeration
                            process. This index is a number from one to
                            NumberBlockDevices.
  @param[out] MediaInfo     The media information of the specified block media.
                            The caller is responsible for the ownership of this
                            data structure.

  @retval EFI_SUCCESS           Media information about the specified block device
                                was obtained successfully.
  @retval EFI_DEVICE_ERROR      Cannot get the media information due to a hardware
                                error.
  @retval Others                Other failure occurs.

**/
EFI_STATUS
EFIAPI
AtapiGetBlockDeviceMediaInfo2 (
  IN   EFI_PEI_SERVICES                **PeiServices,
  IN   EFI_PEI_RECOVERY_BLOCK_IO2_PPI  *This,
  IN   UINTN                           DeviceIndex,
  OUT  EFI_PEI_BLOCK_IO2_MEDIA         *MediaInfo
  )
{
  ATAPI_BLK_IO_DEV        *AtapiBlkIoDev;
  EFI_STATUS              Status;
  EFI_PEI_BLOCK_IO_MEDIA  Media;

  AtapiBlkIoDev = NULL;

  if ((This == NULL) || (MediaInfo == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  AtapiBlkIoDev = PEI_RECOVERY_ATAPI_FROM_BLKIO2_THIS (This);

  Status = AtapiGetBlockDeviceMediaInfo (
             PeiServices,
             &AtapiBlkIoDev->AtapiBlkIo,
             DeviceIndex,
             &Media
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Get media info from AtapiBlkIoDev
  //
  CopyMem (MediaInfo, &AtapiBlkIoDev->DeviceInfo[DeviceIndex - 1].MediaInfo2, sizeof (EFI_PEI_BLOCK_IO2_MEDIA));

  return EFI_SUCCESS;
}

/**
  Reads the requested number of blocks from the specified block device.

  The function reads the requested number of blocks from the device. All the
  blocks are read, or an error is returned. If there is no media in the device,
  the function returns EFI_NO_MEDIA.

  @param[in]  PeiServices   General-purpose services that are available to
                            every PEIM.
  @param[in]  This          Indicates the EFI_PEI_RECOVERY_BLOCK_IO2_PPI instance.
  @param[in]  DeviceIndex   Specifies the block device to which the function wants
                            to talk. Because the driver that implements Block I/O
                            PPIs will manage multiple block devices, the PPIs that
                            want to talk to a single device must specify the device
                            index that was assigned during the enumeration process.
                            This index is a number from one to NumberBlockDevices.
  @param[in]  StartLBA      The starting logical block address (LBA) to read from
                            on the device
  @param[in]  BufferSize    The size of the Buffer in bytes. This number must be
                            a multiple of the intrinsic block size of the device.
  @param[out] Buffer        A pointer to the destination buffer for the data.
                            The caller is responsible for the ownership of the
                            buffer.

  @retval EFI_SUCCESS             The data was read correctly from the device.
  @retval EFI_DEVICE_ERROR        The device reported an error while attempting
                                  to perform the read operation.
  @retval EFI_INVALID_PARAMETER   The read request contains LBAs that are not
                                  valid, or the buffer is not properly aligned.
  @retval EFI_NO_MEDIA            There is no media in the device.
  @retval EFI_BAD_BUFFER_SIZE     The BufferSize parameter is not a multiple of
                                  the intrinsic block size of the device.

**/
EFI_STATUS
EFIAPI
AtapiReadBlocks2 (
  IN   EFI_PEI_SERVICES                **PeiServices,
  IN   EFI_PEI_RECOVERY_BLOCK_IO2_PPI  *This,
  IN   UINTN                           DeviceIndex,
  IN   EFI_PEI_LBA                     StartLBA,
  IN   UINTN                           BufferSize,
  OUT  VOID                            *Buffer
  )
{
  EFI_STATUS        Status;
  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev;

  AtapiBlkIoDev = NULL;

  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  AtapiBlkIoDev = PEI_RECOVERY_ATAPI_FROM_BLKIO2_THIS (This);

  Status = AtapiReadBlocks (
             PeiServices,
             &AtapiBlkIoDev->AtapiBlkIo,
             DeviceIndex,
             StartLBA,
             BufferSize,
             Buffer
             );

  return Status;
}

/**
  Enumerate Atapi devices.

  This function is used to enumerate Atatpi device in Ide channel.

  @param[in]  AtapiBlkIoDev  A pointer to atapi block IO device

**/
VOID
AtapiEnumerateDevices (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev
  )
{
  UINT8                    Index1;
  UINT8                    Index2;
  UINTN                    DevicePosition;
  EFI_PEI_BLOCK_IO_MEDIA   MediaInfo;
  EFI_PEI_BLOCK_IO2_MEDIA  MediaInfo2;
  EFI_STATUS               Status;
  UINTN                    DeviceCount;
  UINT16                   CommandBlockBaseAddr;
  UINT16                   ControlBlockBaseAddr;
  UINT32                   IdeEnabledNumber;
  IDE_REGS_BASE_ADDR       IdeRegsBaseAddr[MAX_IDE_CHANNELS];

  DeviceCount    = 0;
  DevicePosition = 0;

  //
  // Scan IDE bus for ATAPI devices
  //

  //
  // Enable Sata and IDE controller.
  //
  AtapiBlkIoDev->AtaControllerPpi->EnableAtaChannel (
                                     (EFI_PEI_SERVICES **)GetPeiServicesTablePointer (),
                                     AtapiBlkIoDev->AtaControllerPpi,
                                     PEI_ICH_IDE_PRIMARY | PEI_ICH_IDE_SECONDARY
                                     );

  //
  // Allow SATA Devices to spin-up. This is needed if
  // SEC and PEI phase is too short, for example Release Build.
  //
  DEBUG ((DEBUG_INFO, "Delay for %d seconds for SATA devices to spin-up\n", PcdGet16 (PcdSataSpinUpDelayInSecForRecoveryPath)));
  MicroSecondDelay (PcdGet16 (PcdSataSpinUpDelayInSecForRecoveryPath) * 1000 * 1000); //

  //
  // Get four channels (primary or secondary Pata, Sata Channel) Command and Control Regs Base address.
  //
  IdeEnabledNumber = AtapiBlkIoDev->AtaControllerPpi->GetIdeRegsBaseAddr (
                                                        (EFI_PEI_SERVICES **)GetPeiServicesTablePointer (),
                                                        AtapiBlkIoDev->AtaControllerPpi,
                                                        IdeRegsBaseAddr
                                                        );

  //
  // Using Command and Control Regs Base Address to fill other registers.
  //
  for (Index1 = 0; Index1 < IdeEnabledNumber; Index1++) {
    CommandBlockBaseAddr                             = IdeRegsBaseAddr[Index1].CommandBlockBaseAddr;
    AtapiBlkIoDev->IdeIoPortReg[Index1].Data         = CommandBlockBaseAddr;
    AtapiBlkIoDev->IdeIoPortReg[Index1].Reg1.Feature = (UINT16)(CommandBlockBaseAddr + 0x1);
    AtapiBlkIoDev->IdeIoPortReg[Index1].SectorCount  = (UINT16)(CommandBlockBaseAddr + 0x2);
    AtapiBlkIoDev->IdeIoPortReg[Index1].SectorNumber = (UINT16)(CommandBlockBaseAddr + 0x3);
    AtapiBlkIoDev->IdeIoPortReg[Index1].CylinderLsb  = (UINT16)(CommandBlockBaseAddr + 0x4);
    AtapiBlkIoDev->IdeIoPortReg[Index1].CylinderMsb  = (UINT16)(CommandBlockBaseAddr + 0x5);
    AtapiBlkIoDev->IdeIoPortReg[Index1].Head         = (UINT16)(CommandBlockBaseAddr + 0x6);
    AtapiBlkIoDev->IdeIoPortReg[Index1].Reg.Command  = (UINT16)(CommandBlockBaseAddr + 0x7);

    ControlBlockBaseAddr                                  = IdeRegsBaseAddr[Index1].ControlBlockBaseAddr;
    AtapiBlkIoDev->IdeIoPortReg[Index1].Alt.DeviceControl = ControlBlockBaseAddr;
    AtapiBlkIoDev->IdeIoPortReg[Index1].DriveAddress      = (UINT16)(ControlBlockBaseAddr + 0x1);

    //
    // Scan IDE bus for ATAPI devices IDE or Sata device
    //
    for (Index2 = IdeMaster; Index2 < IdeMaxDevice; Index2++) {
      //
      // Pata & Sata, Primary & Secondary channel, Master & Slave device
      //
      DevicePosition = Index1 * 2 + Index2;

      if (DiscoverAtapiDevice (AtapiBlkIoDev, DevicePosition, &MediaInfo, &MediaInfo2)) {
        //
        // ATAPI Device at DevicePosition is found.
        //
        AtapiBlkIoDev->DeviceInfo[DeviceCount].DevicePosition = DevicePosition;
        //
        // Retrieve Media Info
        //
        Status = DetectMedia (AtapiBlkIoDev, DevicePosition, &MediaInfo, &MediaInfo2);
        CopyMem (&(AtapiBlkIoDev->DeviceInfo[DeviceCount].MediaInfo), &MediaInfo, sizeof (MediaInfo));
        CopyMem (&(AtapiBlkIoDev->DeviceInfo[DeviceCount].MediaInfo2), &MediaInfo2, sizeof (MediaInfo2));

        DEBUG ((DEBUG_INFO, "Atatpi Device Position is %d\n", DevicePosition));
        DEBUG ((DEBUG_INFO, "Atatpi DeviceType is   %d\n", MediaInfo.DeviceType));
        DEBUG ((DEBUG_INFO, "Atatpi MediaPresent is %d\n", MediaInfo.MediaPresent));
        DEBUG ((DEBUG_INFO, "Atatpi BlockSize is  0x%x\n", MediaInfo.BlockSize));

        if (EFI_ERROR (Status)) {
          AtapiBlkIoDev->DeviceInfo[DeviceCount].MediaInfo.MediaPresent  = FALSE;
          AtapiBlkIoDev->DeviceInfo[DeviceCount].MediaInfo.LastBlock     = 0;
          AtapiBlkIoDev->DeviceInfo[DeviceCount].MediaInfo2.MediaPresent = FALSE;
          AtapiBlkIoDev->DeviceInfo[DeviceCount].MediaInfo2.LastBlock    = 0;
        }

        DeviceCount += 1;
      }
    }
  }

  AtapiBlkIoDev->DeviceCount = DeviceCount;
}

/**
  Detect Atapi devices.

  @param[in]  AtapiBlkIoDev   A pointer to atapi block IO device.
  @param[in]  DevicePosition  An integer to signify device position.
  @param[out] MediaInfo       The media information of the specified block media.
  @param[out] MediaInfo2      The media information 2 of the specified block media.

  @retval TRUE                Atapi device exists in specified position.
  @retval FALSE               Atapi device does not exist in specified position.

**/
BOOLEAN
DiscoverAtapiDevice (
  IN  ATAPI_BLK_IO_DEV         *AtapiBlkIoDev,
  IN  UINTN                    DevicePosition,
  OUT EFI_PEI_BLOCK_IO_MEDIA   *MediaInfo,
  OUT EFI_PEI_BLOCK_IO2_MEDIA  *MediaInfo2
  )
{
  EFI_STATUS  Status;

  if (!DetectIDEController (AtapiBlkIoDev, DevicePosition)) {
    return FALSE;
  }

  //
  // test if it is an ATAPI device (only supported device)
  //
  if (ATAPIIdentify (AtapiBlkIoDev, DevicePosition) == EFI_SUCCESS) {
    Status = Inquiry (AtapiBlkIoDev, DevicePosition, MediaInfo, MediaInfo2);
    if (!EFI_ERROR (Status)) {
      return TRUE;
    }
  }

  return FALSE;
}

/**
  Check power mode of Atapi devices.

  @param[in]  AtapiBlkIoDev   A pointer to atapi block IO device.
  @param[in]  DevicePosition  An integer to signify device position.
  @param[in]  AtaCommand      The Ata Command passed in.

  @retval EFI_SUCCESS         The Atapi device support power mode.
  @retval EFI_NOT_FOUND       The Atapi device not found.
  @retval EFI_TIMEOUT         Atapi command transaction is time out.
  @retval EFI_ABORTED         Atapi command abort.

**/
EFI_STATUS
CheckPowerMode (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINTN             DevicePosition,
  IN  UINT8             AtaCommand
  )
{
  UINT8       Channel;
  UINT8       Device;
  UINT16      StatusRegister;
  UINT16      HeadRegister;
  UINT16      CommandRegister;
  UINT16      ErrorRegister;
  UINT16      SectorCountRegister;
  EFI_STATUS  Status;
  UINT8       StatusValue;
  UINT8       ErrorValue;
  UINT8       SectorCountValue;

  Channel = (UINT8)(DevicePosition / 2);
  Device  = (UINT8)(DevicePosition % 2);

  ASSERT (Channel < MAX_IDE_CHANNELS);

  StatusRegister      = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Status;
  HeadRegister        = AtapiBlkIoDev->IdeIoPortReg[Channel].Head;
  CommandRegister     = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Command;
  ErrorRegister       = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg1.Error;
  SectorCountRegister = AtapiBlkIoDev->IdeIoPortReg[Channel].SectorCount;

  //
  // select device
  //
  IoWrite8 (HeadRegister, (UINT8)((Device << 4) | 0xe0));

  //
  // refresh the SectorCount register
  //
  SectorCountValue = 0x55;
  IoWrite8 (SectorCountRegister, SectorCountValue);

  //
  // select device
  //
  IoWrite8 (HeadRegister, (UINT8)((Device << 4) | 0xe0));

  Status = DRDYReady (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), 100);

  //
  // select device
  //
  IoWrite8 (HeadRegister, (UINT8)((Device << 4) | 0xe0));
  //
  // send 'check power' commandd via Command Register
  //
  IoWrite8 (CommandRegister, AtaCommand);

  Status = WaitForBSYClear (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), 3000);
  if (EFI_ERROR (Status)) {
    return EFI_TIMEOUT;
  }

  StatusValue = IoRead8 (StatusRegister);

  //
  // command returned status is DRDY, indicating device supports the command,
  // so device is present.
  //
  if ((StatusValue & ATA_STSREG_DRDY) == ATA_STSREG_DRDY) {
    return EFI_SUCCESS;
  }

  SectorCountValue = IoRead8 (SectorCountRegister);

  //
  // command returned status is ERR & ABRT_ERR, indicating device does not support
  // the command, so device is present.
  //
  if ((StatusValue & ATA_STSREG_ERR) == ATA_STSREG_ERR) {
    ErrorValue = IoRead8 (ErrorRegister);
    if ((ErrorValue & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
      return EFI_ABORTED;
    } else {
      //
      // According to spec, no other error code is valid
      //
      return EFI_NOT_FOUND;
    }
  }

  if ((SectorCountValue == 0x00) || (SectorCountValue == 0x80) || (SectorCountValue == 0xff)) {
    //
    // Write SectorCount 0x55 but return valid state value. Maybe no device
    // exists or some slow kind of ATAPI device exists.
    //
    IoWrite8 (HeadRegister, (UINT8)((Device << 4) | 0xe0));

    //
    // write 0x55 and 0xaa to SectorCounter register,
    // if the data could be written into the register,
    // indicating the device is present, otherwise the device is not present.
    //
    SectorCountValue = 0x55;
    IoWrite8 (SectorCountRegister, SectorCountValue);
    MicroSecondDelay (10000);

    SectorCountValue = IoRead8 (SectorCountRegister);
    if (SectorCountValue != 0x55) {
      return EFI_NOT_FOUND;
    }

    //
    // Send a "ATAPI TEST UNIT READY" command ... slow but accurate
    //
    Status = TestUnitReady (AtapiBlkIoDev, DevicePosition);
    return Status;
  }

  return EFI_NOT_FOUND;
}

/**
  Detect if an IDE controller exists in specified position.

  @param[in]  AtapiBlkIoDev   A pointer to atapi block IO device.
  @param[in]  DevicePosition  An integer to signify device position.

  @retval TRUE         The Atapi device exists.
  @retval FALSE        The Atapi device does not present.

**/
BOOLEAN
DetectIDEController (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINTN             DevicePosition
  )
{
  UINT8       Channel;
  EFI_STATUS  Status;
  UINT8       AtaCommand;

  Channel = (UINT8)(DevicePosition / 2);

  ASSERT (Channel < MAX_IDE_CHANNELS);
  //
  //  Wait 31 seconds for BSY clear
  //
  Status = WaitForBSYClear (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), 31000);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  //
  // Send 'check power' command for IDE device
  //
  AtaCommand = 0xE5;
  Status     = CheckPowerMode (AtapiBlkIoDev, DevicePosition, AtaCommand);
  if ((Status == EFI_ABORTED) || (Status == EFI_SUCCESS)) {
    return TRUE;
  }

  return FALSE;
}

/**
  Wait specified time interval to poll for BSY bit clear in the Status Register.

  @param[in]  AtapiBlkIoDev          A pointer to atapi block IO device.
  @param[in]  IdeIoRegisters         A pointer to IDE IO registers.
  @param[in]  TimeoutInMilliSeconds  Time specified in milliseconds.

  @retval EFI_SUCCESS        BSY bit is cleared in the specified time interval.
  @retval EFI_TIMEOUT        BSY bit is not cleared in the specified time interval.

**/
EFI_STATUS
WaitForBSYClear (
  IN  ATAPI_BLK_IO_DEV    *AtapiBlkIoDev,
  IN  IDE_BASE_REGISTERS  *IdeIoRegisters,
  IN  UINTN               TimeoutInMilliSeconds
  )
{
  UINTN   Delay;
  UINT16  StatusRegister;
  UINT8   StatusValue;

  StatusValue = 0;

  StatusRegister = IdeIoRegisters->Reg.Status;

  Delay = ((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 250) + 1;
  do {
    StatusValue = IoRead8 (StatusRegister);
    if ((StatusValue & ATA_STSREG_BSY) == 0x00) {
      break;
    }

    MicroSecondDelay (250);

    Delay--;
  } while (Delay != 0);

  if (Delay == 0) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Wait specified time interval to poll for DRDY bit set in the Status register.

  @param[in]  AtapiBlkIoDev          A pointer to atapi block IO device.
  @param[in]  IdeIoRegisters         A pointer to IDE IO registers.
  @param[in]  TimeoutInMilliSeconds  Time specified in milliseconds.

  @retval EFI_SUCCESS        DRDY bit is set in the specified time interval.
  @retval EFI_TIMEOUT        DRDY bit is not set in the specified time interval.

**/
EFI_STATUS
DRDYReady (
  IN  ATAPI_BLK_IO_DEV    *AtapiBlkIoDev,
  IN  IDE_BASE_REGISTERS  *IdeIoRegisters,
  IN  UINTN               TimeoutInMilliSeconds
  )
{
  UINTN   Delay;
  UINT16  StatusRegister;
  UINT8   StatusValue;
  UINT8   ErrValue;

  StatusValue = 0;

  StatusRegister = IdeIoRegisters->Reg.Status;

  Delay = ((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 250) + 1;
  do {
    StatusValue = IoRead8 (StatusRegister);
    //
    //  BSY == 0 , DRDY == 1
    //
    if ((StatusValue & (ATA_STSREG_DRDY | ATA_STSREG_BSY)) == ATA_STSREG_DRDY) {
      break;
    }

    if ((StatusValue & (ATA_STSREG_ERR | ATA_STSREG_BSY)) == ATA_STSREG_ERR) {
      ErrValue = IoRead8 (IdeIoRegisters->Reg1.Error);
      if ((ErrValue & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    MicroSecondDelay (250);

    Delay--;
  } while (Delay != 0);

  if (Delay == 0) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Wait specified time interval to poll for DRQ bit clear in the Status Register.

  @param[in]  AtapiBlkIoDev          A pointer to atapi block IO device.
  @param[in]  IdeIoRegisters         A pointer to IDE IO registers.
  @param[in]  TimeoutInMilliSeconds  Time specified in milliseconds.

  @retval EFI_SUCCESS        DRQ bit is cleared in the specified time interval.
  @retval EFI_TIMEOUT        DRQ bit is not cleared in the specified time interval.

**/
EFI_STATUS
DRQClear (
  IN  ATAPI_BLK_IO_DEV    *AtapiBlkIoDev,
  IN  IDE_BASE_REGISTERS  *IdeIoRegisters,
  IN  UINTN               TimeoutInMilliSeconds
  )
{
  UINTN   Delay;
  UINT16  StatusRegister;
  UINT8   StatusValue;
  UINT8   ErrValue;

  StatusValue = 0;

  StatusRegister = IdeIoRegisters->Reg.Status;

  Delay = ((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 250) + 1;
  do {
    StatusValue = IoRead8 (StatusRegister);

    //
    // wait for BSY == 0 and DRQ == 0
    //
    if ((StatusValue & (ATA_STSREG_DRQ | ATA_STSREG_BSY)) == 0) {
      break;
    }

    if ((StatusValue & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {
      ErrValue = IoRead8 (IdeIoRegisters->Reg1.Error);
      if ((ErrValue & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    MicroSecondDelay (250);

    Delay--;
  } while (Delay != 0);

  if (Delay == 0) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Wait specified time interval to poll for DRQ bit clear in the Alternate Status Register.

  @param[in]  AtapiBlkIoDev          A pointer to atapi block IO device.
  @param[in]  IdeIoRegisters         A pointer to IDE IO registers.
  @param[in]  TimeoutInMilliSeconds  Time specified in milliseconds.

  @retval EFI_SUCCESS        DRQ bit is cleared in the specified time interval.
  @retval EFI_TIMEOUT        DRQ bit is not cleared in the specified time interval.

**/
EFI_STATUS
DRQClear2 (
  IN  ATAPI_BLK_IO_DEV    *AtapiBlkIoDev,
  IN  IDE_BASE_REGISTERS  *IdeIoRegisters,
  IN  UINTN               TimeoutInMilliSeconds
  )
{
  UINTN   Delay;
  UINT16  AltStatusRegister;
  UINT8   AltStatusValue;
  UINT8   ErrValue;

  AltStatusValue = 0;

  AltStatusRegister = IdeIoRegisters->Alt.AltStatus;

  Delay = ((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 250) + 1;
  do {
    AltStatusValue = IoRead8 (AltStatusRegister);

    //
    // wait for BSY == 0 and DRQ == 0
    //
    if ((AltStatusValue & (ATA_STSREG_DRQ | ATA_STSREG_BSY)) == 0) {
      break;
    }

    if ((AltStatusValue & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {
      ErrValue = IoRead8 (IdeIoRegisters->Reg1.Error);
      if ((ErrValue & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    MicroSecondDelay (250);

    Delay--;
  } while (Delay != 0);

  if (Delay == 0) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Wait specified time interval to poll for DRQ bit set in the Status Register.

  @param[in]  AtapiBlkIoDev          A pointer to atapi block IO device.
  @param[in]  IdeIoRegisters         A pointer to IDE IO registers.
  @param[in]  TimeoutInMilliSeconds  Time specified in milliseconds.

  @retval EFI_SUCCESS        DRQ bit is set in the specified time interval.
  @retval EFI_TIMEOUT        DRQ bit is not set in the specified time interval.
  @retval EFI_ABORTED        Operation Aborted.

**/
EFI_STATUS
DRQReady (
  IN  ATAPI_BLK_IO_DEV    *AtapiBlkIoDev,
  IN  IDE_BASE_REGISTERS  *IdeIoRegisters,
  IN  UINTN               TimeoutInMilliSeconds
  )
{
  UINTN   Delay;
  UINT16  StatusRegister;
  UINT8   StatusValue;
  UINT8   ErrValue;

  StatusValue = 0;
  ErrValue    = 0;

  StatusRegister = IdeIoRegisters->Reg.Status;

  Delay = ((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 250) + 1;
  do {
    //
    //  read Status Register will clear interrupt
    //
    StatusValue = IoRead8 (StatusRegister);

    //
    //  BSY==0,DRQ==1
    //
    if ((StatusValue & (ATA_STSREG_BSY | ATA_STSREG_DRQ)) == ATA_STSREG_DRQ) {
      break;
    }

    if ((StatusValue & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {
      ErrValue = IoRead8 (IdeIoRegisters->Reg1.Error);
      if ((ErrValue & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    MicroSecondDelay (250);

    Delay--;
  } while (Delay != 0);

  if (Delay == 0) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Wait specified time interval to poll for DRQ bit set in the Alternate Status Register.

  @param[in]  AtapiBlkIoDev          A pointer to atapi block IO device.
  @param[in]  IdeIoRegisters         A pointer to IDE IO registers.
  @param[in]  TimeoutInMilliSeconds  Time specified in milliseconds.

  @retval EFI_SUCCESS        DRQ bit is set in the specified time interval.
  @retval EFI_TIMEOUT        DRQ bit is not set in the specified time interval.
  @retval EFI_ABORTED        Operation Aborted.

**/
EFI_STATUS
DRQReady2 (
  IN  ATAPI_BLK_IO_DEV    *AtapiBlkIoDev,
  IN  IDE_BASE_REGISTERS  *IdeIoRegisters,
  IN  UINTN               TimeoutInMilliSeconds
  )
{
  UINTN   Delay;
  UINT16  AltStatusRegister;
  UINT8   AltStatusValue;
  UINT8   ErrValue;

  AltStatusValue = 0;

  AltStatusRegister = IdeIoRegisters->Alt.AltStatus;

  Delay = ((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 250) + 1;
  do {
    AltStatusValue = IoRead8 (AltStatusRegister);

    //
    //  BSY==0,DRQ==1
    //
    if ((AltStatusValue & (ATA_STSREG_BSY | ATA_STSREG_DRQ)) == ATA_STSREG_DRQ) {
      break;
    }

    if ((AltStatusValue & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {
      ErrValue = IoRead8 (IdeIoRegisters->Reg1.Error);
      if ((ErrValue & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    MicroSecondDelay (250);

    Delay--;
  } while (Delay != 0);

  if (Delay == 0) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Check if there is an error in Status Register.

  @param[in]  AtapiBlkIoDev     A pointer to atapi block IO device.
  @param[in]  StatusReg         The address to IDE IO registers.

  @retval EFI_SUCCESS        Operation success.
  @retval EFI_DEVICE_ERROR   Device error.

**/
EFI_STATUS
CheckErrorStatus (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINT16            StatusReg
  )
{
  UINT8  StatusValue;

  StatusValue = IoRead8 (StatusReg);

  if ((StatusValue & (ATA_STSREG_ERR | ATA_STSREG_DWF | ATA_STSREG_CORR)) == 0) {
    return EFI_SUCCESS;
  }

  return EFI_DEVICE_ERROR;
}

/**
  Idendify Atapi devices.

  @param[in]  AtapiBlkIoDev     A pointer to atapi block IO device.
  @param[in]  DevicePosition    An integer to signify device position.

  @retval EFI_SUCCESS        Identify successfully.
  @retval EFI_DEVICE_ERROR   Device cannot be identified successfully.

**/
EFI_STATUS
ATAPIIdentify (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINTN             DevicePosition
  )
{
  ATAPI_IDENTIFY_DATA  AtapiIdentifyData;
  UINT8                Channel;
  UINT8                Device;
  UINT16               StatusReg;
  UINT16               HeadReg;
  UINT16               CommandReg;
  UINT16               DataReg;
  UINT16               SectorCountReg;
  UINT16               SectorNumberReg;
  UINT16               CylinderLsbReg;
  UINT16               CylinderMsbReg;

  UINT32  WordCount;
  UINT32  Increment;
  UINT32  Index;
  UINT32  ByteCount;
  UINT16  *Buffer16;

  EFI_STATUS  Status;

  ByteCount = sizeof (AtapiIdentifyData);
  Buffer16  = (UINT16 *)&AtapiIdentifyData;

  Channel = (UINT8)(DevicePosition / 2);
  Device  = (UINT8)(DevicePosition % 2);

  ASSERT (Channel < MAX_IDE_CHANNELS);

  StatusReg       = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Status;
  HeadReg         = AtapiBlkIoDev->IdeIoPortReg[Channel].Head;
  CommandReg      = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Command;
  DataReg         = AtapiBlkIoDev->IdeIoPortReg[Channel].Data;
  SectorCountReg  = AtapiBlkIoDev->IdeIoPortReg[Channel].SectorCount;
  SectorNumberReg = AtapiBlkIoDev->IdeIoPortReg[Channel].SectorNumber;
  CylinderLsbReg  = AtapiBlkIoDev->IdeIoPortReg[Channel].CylinderLsb;
  CylinderMsbReg  = AtapiBlkIoDev->IdeIoPortReg[Channel].CylinderMsb;

  //
  // Send ATAPI Identify Command to get IDENTIFY data.
  //
  if (WaitForBSYClear (
        AtapiBlkIoDev,
        &(AtapiBlkIoDev->IdeIoPortReg[Channel]),
        ATATIMEOUT
        ) != EFI_SUCCESS)
  {
    return EFI_DEVICE_ERROR;
  }

  //
  // select device via Head/Device register.
  // Before write Head/Device register, BSY and DRQ must be 0.
  //
  if (DRQClear2 (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), ATATIMEOUT) != EFI_SUCCESS) {
    return EFI_DEVICE_ERROR;
  }

  //
  //  e0:1110,0000-- bit7 and bit5 are reserved bits.
  //           bit6 set means LBA mode
  //
  IoWrite8 (HeadReg, (UINT8)((Device << 4) | 0xe0));

  //
  // set all the command parameters
  // Before write to all the following registers, BSY and DRQ must be 0.
  //
  if (DRQClear2 (
        AtapiBlkIoDev,
        &(AtapiBlkIoDev->IdeIoPortReg[Channel]),
        ATATIMEOUT
        ) != EFI_SUCCESS)
  {
    return EFI_DEVICE_ERROR;
  }

  IoWrite8 (SectorCountReg, 0);
  IoWrite8 (SectorNumberReg, 0);
  IoWrite8 (CylinderLsbReg, 0);
  IoWrite8 (CylinderMsbReg, 0);

  //
  // send command via Command Register
  //
  IoWrite8 (CommandReg, ATA_CMD_IDENTIFY_DEVICE);

  //
  // According to PIO data in protocol, host can perform a series of reads to the
  // data register after each time device set DRQ ready;
  // The data size of "a series of read" is command specific.
  // For most ATA command, data size received from device will not exceed 1 sector,
  // hense the data size for "a series of read" can be the whole data size of one command request.
  // For ATA command such as Read Sector command, whole data size of one ATA command request is often larger
  // than 1 sector, according to the Read Sector command, the data size of "a series of read" is exactly
  // 1 sector.
  // Here for simplification reason, we specify the data size for "a series of read" to
  // 1 sector (256 words) if whole data size of one ATA commmand request is larger than 256 words.
  //
  Increment = 256;
  //
  // 256 words
  //
  WordCount = 0;
  //
  // WordCount is used to record bytes of currently transfered data
  //
  while (WordCount < ByteCount / 2) {
    //
    // Poll DRQ bit set, data transfer can be performed only when DRQ is ready.
    //
    Status = DRQReady2 (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), ATATIMEOUT);
    if (Status != EFI_SUCCESS) {
      return Status;
    }

    if (CheckErrorStatus (AtapiBlkIoDev, StatusReg) != EFI_SUCCESS) {
      return EFI_DEVICE_ERROR;
    }

    //
    // Get the byte count for one series of read
    //
    if ((WordCount + Increment) > ByteCount / 2) {
      Increment = ByteCount / 2 - WordCount;
    }

    //
    // perform a series of read without check DRQ ready
    //
    for (Index = 0; Index < Increment; Index++) {
      *Buffer16++ = IoRead16 (DataReg);
    }

    WordCount += Increment;
  }

  //
  // while
  //
  if (DRQClear (
        AtapiBlkIoDev,
        &(AtapiBlkIoDev->IdeIoPortReg[Channel]),
        ATATIMEOUT
        ) != EFI_SUCCESS)
  {
    return CheckErrorStatus (AtapiBlkIoDev, StatusReg);
  }

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Test Unit Ready Packet Command to the specified device
  to find out whether device is accessible.

  @param[in]  AtapiBlkIoDev     A pointer to atapi block IO device.
  @param[in]  DevicePosition    An integer to signify device position.

  @retval EFI_SUCCESS        TestUnit command executed successfully.
  @retval EFI_DEVICE_ERROR   Device cannot be executed TestUnit command successfully.

**/
EFI_STATUS
TestUnitReady (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINTN             DevicePosition
  )
{
  ATAPI_PACKET_COMMAND  Packet;
  EFI_STATUS            Status;

  //
  // fill command packet
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  Packet.TestUnitReady.opcode = ATA_CMD_TEST_UNIT_READY;

  //
  // send command packet
  //
  Status = AtapiPacketCommandIn (AtapiBlkIoDev, DevicePosition, &Packet, NULL, 0, ATAPITIMEOUT);
  return Status;
}

/**
  Send out ATAPI commands conforms to the Packet Command with PIO Data In Protocol.

  @param[in]  AtapiBlkIoDev         A pointer to atapi block IO device.
  @param[in]  DevicePosition        An integer to signify device position.
  @param[in]  Packet                A pointer to ATAPI command packet.
  @param[in]  Buffer                Buffer to contain requested transfer data from device.
  @param[in]  ByteCount             Requested transfer data length.
  @param[in]  TimeoutInMilliSeconds Time out value, in unit of milliseconds.

  @retval EFI_SUCCESS        Command executed successfully.
  @retval EFI_DEVICE_ERROR   Device cannot be executed command successfully.

**/
EFI_STATUS
AtapiPacketCommandIn (
  IN  ATAPI_BLK_IO_DEV      *AtapiBlkIoDev,
  IN  UINTN                 DevicePosition,
  IN  ATAPI_PACKET_COMMAND  *Packet,
  IN  UINT16                *Buffer,
  IN  UINT32                ByteCount,
  IN  UINTN                 TimeoutInMilliSeconds
  )
{
  UINT8       Channel;
  UINT8       Device;
  UINT16      StatusReg;
  UINT16      HeadReg;
  UINT16      CommandReg;
  UINT16      FeatureReg;
  UINT16      CylinderLsbReg;
  UINT16      CylinderMsbReg;
  UINT16      DeviceControlReg;
  UINT16      DataReg;
  EFI_STATUS  Status;
  UINT32      Count;
  UINT16      *CommandIndex;
  UINT16      *PtrBuffer;
  UINT32      Index;
  UINT8       StatusValue;
  UINT32      WordCount;

  //
  // required transfer data in word unit.
  //
  UINT32  RequiredWordCount;

  //
  // actual transfer data in word unit.
  //
  UINT32  ActualWordCount;

  Channel = (UINT8)(DevicePosition / 2);
  Device  = (UINT8)(DevicePosition % 2);

  ASSERT (Channel < MAX_IDE_CHANNELS);

  StatusReg        = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Status;
  HeadReg          = AtapiBlkIoDev->IdeIoPortReg[Channel].Head;
  CommandReg       = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Command;
  FeatureReg       = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg1.Feature;
  CylinderLsbReg   = AtapiBlkIoDev->IdeIoPortReg[Channel].CylinderLsb;
  CylinderMsbReg   = AtapiBlkIoDev->IdeIoPortReg[Channel].CylinderMsb;
  DeviceControlReg = AtapiBlkIoDev->IdeIoPortReg[Channel].Alt.DeviceControl;
  DataReg          = AtapiBlkIoDev->IdeIoPortReg[Channel].Data;

  //
  // Set all the command parameters by fill related registers.
  // Before write to all the following registers, BSY and DRQ must be 0.
  //
  if (DRQClear2 (
        AtapiBlkIoDev,
        &(AtapiBlkIoDev->IdeIoPortReg[Channel]),
        ATATIMEOUT
        ) != EFI_SUCCESS)
  {
    return EFI_DEVICE_ERROR;
  }

  //
  // Select device via Device/Head Register.
  // DEFAULT_CMD: 0xa0 (1010,0000)
  //
  IoWrite8 (HeadReg, (UINT8)((Device << 4) | ATA_DEFAULT_CMD));

  //
  // No OVL; No DMA
  //
  IoWrite8 (FeatureReg, 0x00);

  //
  // set the transfersize to MAX_ATAPI_BYTE_COUNT to let the device
  // determine how many data should be transfered.
  //
  IoWrite8 (CylinderLsbReg, (UINT8)(ATAPI_MAX_BYTE_COUNT & 0x00ff));
  IoWrite8 (CylinderMsbReg, (UINT8)(ATAPI_MAX_BYTE_COUNT >> 8));

  //
  //  DEFAULT_CTL:0x0a (0000,1010)
  //  Disable interrupt
  //
  IoWrite8 (DeviceControlReg, ATA_DEFAULT_CTL);

  //
  // Send Packet command to inform device
  // that the following data bytes are command packet.
  //
  IoWrite8 (CommandReg, ATA_CMD_PACKET);

  Status = DRQReady (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), TimeoutInMilliSeconds);
  if (Status != EFI_SUCCESS) {
    return Status;
  }

  //
  // Send out command packet
  //
  CommandIndex = Packet->Data16;
  for (Count = 0; Count < 6; Count++, CommandIndex++) {
    IoWrite16 (DataReg, *CommandIndex);
    MicroSecondDelay (10);
  }

  StatusValue = IoRead8 (StatusReg);
  if ((StatusValue & ATA_STSREG_ERR) == ATA_STSREG_ERR) {
    //
    // Trouble! Something's wrong here... Wait some time and return. 3 second is
    // supposed to be long enough for a device reset latency or error recovery
    //
    MicroSecondDelay (3000000);
    return EFI_DEVICE_ERROR;
  }

  if ((Buffer == NULL) || (ByteCount == 0)) {
    return EFI_SUCCESS;
  }

  //
  // call PioReadWriteData() function to get
  // requested transfer data form device.
  //
  PtrBuffer         = Buffer;
  RequiredWordCount = ByteCount / 2;
  //
  // ActuralWordCount means the word count of data really transfered.
  //
  ActualWordCount = 0;

  Status = EFI_SUCCESS;
  while ((Status == EFI_SUCCESS) && (ActualWordCount < RequiredWordCount)) {
    //
    // before each data transfer stream, the host should poll DRQ bit ready,
    // which informs device is ready to transfer data.
    //
    if (DRQReady2 (
          AtapiBlkIoDev,
          &(AtapiBlkIoDev->IdeIoPortReg[Channel]),
          TimeoutInMilliSeconds
          ) != EFI_SUCCESS)
    {
      return CheckErrorStatus (AtapiBlkIoDev, StatusReg);
    }

    //
    // read Status Register will clear interrupt
    //
    StatusValue = IoRead8 (StatusReg);

    //
    // get current data transfer size from Cylinder Registers.
    //
    WordCount  = IoRead8 (CylinderMsbReg) << 8;
    WordCount  = WordCount | IoRead8 (CylinderLsbReg);
    WordCount  = WordCount & 0xffff;
    WordCount /= 2;

    //
    // perform a series data In/Out.
    //
    for (Index = 0; (Index < WordCount) && (ActualWordCount < RequiredWordCount); Index++, ActualWordCount++) {
      *PtrBuffer = IoRead16 (DataReg);

      PtrBuffer++;
    }

    if ((((ATAPI_REQUEST_SENSE_CMD *)Packet)->opcode == ATA_CMD_REQUEST_SENSE) && (ActualWordCount >= 4)) {
      RequiredWordCount = MIN (
                            RequiredWordCount,
                            (UINT32)(4 + (((ATAPI_REQUEST_SENSE_DATA *)Buffer)->addnl_sense_length / 2))
                            );
    }
  }

  //
  // After data transfer is completed, normally, DRQ bit should clear.
  //
  Status = DRQClear2 (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), TimeoutInMilliSeconds);
  if (Status != EFI_SUCCESS) {
    return EFI_DEVICE_ERROR;
  }

  //
  // read status register to check whether error happens.
  //
  Status = CheckErrorStatus (AtapiBlkIoDev, StatusReg);
  return Status;
}

/**
  Sends out ATAPI Inquiry Packet Command to the specified device.
  This command will return INQUIRY data of the device.

  @param[in]  AtapiBlkIoDev   A pointer to atapi block IO device.
  @param[in]  DevicePosition  An integer to signify device position.
  @param[out] MediaInfo       The media information of the specified block media.
  @param[out] MediaInfo2      The media information 2 of the specified block media.

  @retval EFI_SUCCESS        Command executed successfully.
  @retval EFI_DEVICE_ERROR   Device cannot be executed command successfully.
  @retval EFI_UNSUPPORTED    Unsupported device type.

**/
EFI_STATUS
Inquiry (
  IN  ATAPI_BLK_IO_DEV         *AtapiBlkIoDev,
  IN  UINTN                    DevicePosition,
  OUT EFI_PEI_BLOCK_IO_MEDIA   *MediaInfo,
  OUT EFI_PEI_BLOCK_IO2_MEDIA  *MediaInfo2
  )
{
  ATAPI_PACKET_COMMAND  Packet;
  EFI_STATUS            Status;
  ATAPI_INQUIRY_DATA    Idata;

  //
  // prepare command packet for the ATAPI Inquiry Packet Command.
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  ZeroMem (&Idata, sizeof (ATAPI_INQUIRY_DATA));

  Packet.Inquiry.opcode            = ATA_CMD_INQUIRY;
  Packet.Inquiry.page_code         = 0;
  Packet.Inquiry.allocation_length = (UINT8)sizeof (ATAPI_INQUIRY_DATA);

  //
  // Send command packet and get requested Inquiry data.
  //
  Status = AtapiPacketCommandIn (
             AtapiBlkIoDev,
             DevicePosition,
             &Packet,
             (UINT16 *)(&Idata),
             sizeof (ATAPI_INQUIRY_DATA),
             ATAPITIMEOUT
             // 50
             );

  if (Status != EFI_SUCCESS) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Identify device type via INQUIRY data.
  //
  switch (Idata.peripheral_type & 0x1f) {
    case 0x00:
      //
      // Magnetic Disk
      //
      MediaInfo->DeviceType      = IdeLS120;
      MediaInfo->MediaPresent    = FALSE;
      MediaInfo->LastBlock       = 0;
      MediaInfo->BlockSize       = 0x200;
      MediaInfo2->InterfaceType  = MSG_ATAPI_DP;
      MediaInfo2->RemovableMedia = TRUE;
      MediaInfo2->MediaPresent   = FALSE;
      MediaInfo2->ReadOnly       = FALSE;
      MediaInfo2->BlockSize      = 0x200;
      MediaInfo2->LastBlock      = 0;
      break;

    case 0x05:
      //
      // CD-ROM
      //
      MediaInfo->DeviceType      = IdeCDROM;
      MediaInfo->MediaPresent    = FALSE;
      MediaInfo->LastBlock       = 0;
      MediaInfo->BlockSize       = 0x800;
      MediaInfo2->InterfaceType  = MSG_ATAPI_DP;
      MediaInfo2->RemovableMedia = TRUE;
      MediaInfo2->MediaPresent   = FALSE;
      MediaInfo2->ReadOnly       = TRUE;
      MediaInfo2->BlockSize      = 0x200;
      MediaInfo2->LastBlock      = 0;
      break;

    default:
      return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**
  Used before read/write blocks from/to ATAPI device media.
  Since ATAPI device media is removable, it is necessary to detect
  whether media is present and get current present media's information.

  @param[in]  AtapiBlkIoDev     A pointer to atapi block IO device.
  @param[in]  DevicePosition    An integer to signify device position.
  @param[in, out] MediaInfo     The media information of the specified block media.
  @param[in, out] MediaInfo2    The media information 2 of the specified block media.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.
  @retval EFI_OUT_OF_RESOURCES  Can not allocate required resources.

**/
EFI_STATUS
DetectMedia (
  IN  ATAPI_BLK_IO_DEV            *AtapiBlkIoDev,
  IN  UINTN                       DevicePosition,
  IN OUT EFI_PEI_BLOCK_IO_MEDIA   *MediaInfo,
  IN OUT EFI_PEI_BLOCK_IO2_MEDIA  *MediaInfo2
  )
{
  UINTN                     Index;
  UINTN                     RetryNum;
  UINTN                     MaxRetryNum;
  ATAPI_REQUEST_SENSE_DATA  *SenseBuffers;
  BOOLEAN                   NeedReadCapacity;
  BOOLEAN                   NeedRetry;
  EFI_STATUS                Status;
  UINT8                     SenseCounts;

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

  //
  // Test Unit Ready command is used to detect whether device is accessible,
  // the device will produce corresponding Sense data.
  //
  for (Index = 0; Index < 2; Index++) {
    Status = TestUnitReady (AtapiBlkIoDev, DevicePosition);
    if (Status != EFI_SUCCESS) {
      Status = ResetDevice (AtapiBlkIoDev, DevicePosition, FALSE);

      if (Status != EFI_SUCCESS) {
        ResetDevice (AtapiBlkIoDev, DevicePosition, TRUE);
      }
    } else {
      break;
    }
  }

  SenseCounts      = MAX_SENSE_KEY_COUNT;
  Status           = EFI_SUCCESS;
  NeedReadCapacity = TRUE;

  for (Index = 0; Index < 5; Index++) {
    SenseCounts = MAX_SENSE_KEY_COUNT;
    Status      = RequestSense (
                    AtapiBlkIoDev,
                    DevicePosition,
                    SenseBuffers,
                    &SenseCounts
                    );
    DEBUG ((DEBUG_INFO, "Atapi Request Sense Count is %d\n", SenseCounts));
    if (IsDeviceStateUnclear (SenseBuffers, SenseCounts) || IsNoMedia (SenseBuffers, SenseCounts)) {
      //
      // We are not sure whether the media is present or not, try again
      //
      TestUnitReady (AtapiBlkIoDev, DevicePosition);
    } else {
      break;
    }
  }

  if (Status == EFI_SUCCESS) {
    if (IsNoMedia (SenseBuffers, SenseCounts)) {
      NeedReadCapacity         = FALSE;
      MediaInfo->MediaPresent  = FALSE;
      MediaInfo->LastBlock     = 0;
      MediaInfo2->MediaPresent = FALSE;
      MediaInfo2->LastBlock    = 0;
    }

    if (IsMediaError (SenseBuffers, SenseCounts)) {
      return EFI_DEVICE_ERROR;
    }
  }

  if (NeedReadCapacity) {
    //
    // at most retry 5 times
    //
    MaxRetryNum = 5;
    RetryNum    = 1;
    //
    // initial retry once
    //
    for (Index = 0; (Index < RetryNum) && (Index < MaxRetryNum); Index++) {
      Status = ReadCapacity (AtapiBlkIoDev, DevicePosition, MediaInfo, MediaInfo2);
      MicroSecondDelay (200000);
      SenseCounts = MAX_SENSE_KEY_COUNT;

      if (Status != EFI_SUCCESS) {
        Status = RequestSense (AtapiBlkIoDev, DevicePosition, SenseBuffers, &SenseCounts);
        //
        // If Request Sense data failed, reset the device and retry.
        //
        if (Status != EFI_SUCCESS) {
          Status = ResetDevice (AtapiBlkIoDev, DevicePosition, FALSE);
          //
          // if ATAPI soft reset fail,
          // use stronger reset mechanism -- ATA soft reset.
          //
          if (Status != EFI_SUCCESS) {
            ResetDevice (AtapiBlkIoDev, DevicePosition, TRUE);
          }

          RetryNum++;
          //
          // retry once more
          //
          continue;
        }

        //
        // No Media
        //
        if (IsNoMedia (SenseBuffers, SenseCounts)) {
          MediaInfo->MediaPresent  = FALSE;
          MediaInfo->LastBlock     = 0;
          MediaInfo2->MediaPresent = FALSE;
          MediaInfo2->LastBlock    = 0;
          break;
        }

        if (IsMediaError (SenseBuffers, SenseCounts)) {
          return EFI_DEVICE_ERROR;
        }

        if (!IsDriveReady (SenseBuffers, SenseCounts, &NeedRetry)) {
          //
          // Drive not ready: if NeedRetry, then retry once more;
          // else return error
          //
          if (NeedRetry) {
            RetryNum++;
            continue;
          } else {
            return EFI_DEVICE_ERROR;
          }
        }

        //
        // if read capacity fail not for above reasons, retry once more
        //
        RetryNum++;
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Reset specified Atapi device.

  @param[in]  AtapiBlkIoDev     A pointer to atapi block IO device.
  @param[in]  DevicePosition    An integer to signify device position.
  @param[in]  Extensive         If TRUE, use ATA soft reset, otherwise use Atapi soft reset.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
ResetDevice (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINTN             DevicePosition,
  IN  BOOLEAN           Extensive
  )
{
  UINT8   DevControl;
  UINT8   Command;
  UINT8   DeviceSelect;
  UINT16  DeviceControlReg;
  UINT16  CommandReg;
  UINT16  HeadReg;
  UINT8   Channel;
  UINT8   Device;

  Channel = (UINT8)(DevicePosition / 2);
  Device  = (UINT8)(DevicePosition % 2);

  ASSERT (Channel < MAX_IDE_CHANNELS);

  DeviceControlReg = AtapiBlkIoDev->IdeIoPortReg[Channel].Alt.DeviceControl;
  CommandReg       = AtapiBlkIoDev->IdeIoPortReg[Channel].Reg.Command;
  HeadReg          = AtapiBlkIoDev->IdeIoPortReg[Channel].Head;

  if (Extensive) {
    DevControl  = 0;
    DevControl |= ATA_CTLREG_SRST;
    //
    // set SRST bit to initiate soft reset
    //
    DevControl |= BIT1;
    //
    // disable Interrupt
    //
    IoWrite8 (DeviceControlReg, DevControl);

    //
    // Wait 10us
    //
    MicroSecondDelay (10);

    //
    // Clear SRST bit
    //
    DevControl &= 0xfb;
    //
    // 0xfb:1111,1011
    //
    IoWrite8 (DeviceControlReg, DevControl);

    //
    // slave device needs at most 31s to clear BSY
    //
    if (WaitForBSYClear (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), 31000) == EFI_TIMEOUT) {
      return EFI_DEVICE_ERROR;
    }
  } else {
    //
    // for ATAPI device, no need to wait DRDY ready after device selecting.
    // bit7 and bit5 are both set to 1 for backward compatibility
    //
    DeviceSelect = (UINT8)(((BIT7 | BIT5) | (Device << 4)));
    IoWrite8 (HeadReg, DeviceSelect);

    Command = ATA_CMD_SOFT_RESET;
    IoWrite8 (CommandReg, Command);

    //
    // BSY cleared is the only status return to the host by the device when reset is completed
    // slave device needs at most 31s to clear BSY
    //
    if (WaitForBSYClear (AtapiBlkIoDev, &(AtapiBlkIoDev->IdeIoPortReg[Channel]), 31000) != EFI_SUCCESS) {
      return EFI_DEVICE_ERROR;
    }

    //
    // stall 5 seconds to make the device status stable
    //
    MicroSecondDelay (STALL_1_SECONDS * 5);
  }

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Request Sense Packet Command to the specified device.

  @param[in]      AtapiBlkIoDev   A pointer to atapi block IO device.
  @param[in]      DevicePosition  An integer to signify device position.
  @param[in]      SenseBuffers    Pointer to sense buffer.
  @param[in, out] SenseCounts     Length of sense buffer.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
RequestSense (
  IN  ATAPI_BLK_IO_DEV          *AtapiBlkIoDev,
  IN  UINTN                     DevicePosition,
  IN  ATAPI_REQUEST_SENSE_DATA  *SenseBuffers,
  IN  OUT  UINT8                *SenseCounts
  )
{
  EFI_STATUS                Status;
  ATAPI_REQUEST_SENSE_DATA  *Sense;
  UINT16                    *Ptr;
  BOOLEAN                   SenseReq;
  ATAPI_PACKET_COMMAND      Packet;

  ZeroMem (SenseBuffers, sizeof (ATAPI_REQUEST_SENSE_DATA) * (*SenseCounts));
  //
  // fill command packet for Request Sense Packet Command
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  Packet.RequestSence.opcode            = ATA_CMD_REQUEST_SENSE;
  Packet.RequestSence.allocation_length = (UINT8)sizeof (ATAPI_REQUEST_SENSE_DATA);

  Ptr = (UINT16 *)SenseBuffers;
  //
  // initialize pointer
  //
  *SenseCounts = 0;
  //
  //  request sense data from device continiously until no sense data exists in the device.
  //
  for (SenseReq = TRUE; SenseReq;) {
    Sense = (ATAPI_REQUEST_SENSE_DATA *)Ptr;

    //
    // send out Request Sense Packet Command and get one Sense data form device
    //
    Status = AtapiPacketCommandIn (
               AtapiBlkIoDev,
               DevicePosition,
               &Packet,
               Ptr,
               sizeof (ATAPI_REQUEST_SENSE_DATA),
               ATAPITIMEOUT
               );
    //
    // failed to get Sense data
    //
    if (Status != EFI_SUCCESS) {
      if (*SenseCounts == 0) {
        return EFI_DEVICE_ERROR;
      } else {
        return EFI_SUCCESS;
      }
    }

    (*SenseCounts)++;

    if (*SenseCounts > MAX_SENSE_KEY_COUNT) {
      return EFI_SUCCESS;
    }

    //
    // We limit MAX sense data count to 20 in order to avoid dead loop. Some
    // incompatible ATAPI devices don't retrive NO_SENSE when there is no media.
    // In this case, dead loop occurs if we don't have a gatekeeper. 20 is
    // supposed to be large enough for any ATAPI device.
    //
    if ((Sense->sense_key != ATA_SK_NO_SENSE) && ((*SenseCounts) < 20)) {
      Ptr += sizeof (ATAPI_REQUEST_SENSE_DATA) / 2;
      //
      // Ptr is word based pointer
      //
    } else {
      //
      // when no sense key, skip out the loop
      //
      SenseReq = FALSE;
    }
  }

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Read Capacity Packet Command to the specified device.
  This command will return the information regarding the capacity of the
  media in the device.

  @param[in]  AtapiBlkIoDev     A pointer to atapi block IO device.
  @param[in]  DevicePosition    An integer to signify device position.
  @param[in, out] MediaInfo     The media information of the specified block media.
  @param[in, out] MediaInfo2    The media information 2 of the specified block media.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
ReadCapacity (
  IN  ATAPI_BLK_IO_DEV            *AtapiBlkIoDev,
  IN  UINTN                       DevicePosition,
  IN OUT EFI_PEI_BLOCK_IO_MEDIA   *MediaInfo,
  IN OUT EFI_PEI_BLOCK_IO2_MEDIA  *MediaInfo2
  )
{
  EFI_STATUS            Status;
  ATAPI_PACKET_COMMAND  Packet;

  //
  // used for capacity data returned from ATAPI device
  //
  ATAPI_READ_CAPACITY_DATA         Data;
  ATAPI_READ_FORMAT_CAPACITY_DATA  FormatData;

  ZeroMem (&Data, sizeof (Data));
  ZeroMem (&FormatData, sizeof (FormatData));

  if (MediaInfo->DeviceType == IdeCDROM) {
    ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
    Packet.Inquiry.opcode = ATA_CMD_READ_CAPACITY;
    Status                = AtapiPacketCommandIn (
                              AtapiBlkIoDev,
                              DevicePosition,
                              &Packet,
                              (UINT16 *)(&Data),
                              sizeof (ATAPI_READ_CAPACITY_DATA),
                              ATAPITIMEOUT
                              );
  } else {
    //
    // DeviceType == IdeLS120
    //
    ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
    Packet.ReadFormatCapacity.opcode               = ATA_CMD_READ_FORMAT_CAPACITY;
    Packet.ReadFormatCapacity.allocation_length_lo = 12;
    Status                                         = AtapiPacketCommandIn (
                                                       AtapiBlkIoDev,
                                                       DevicePosition,
                                                       &Packet,
                                                       (UINT16 *)(&FormatData),
                                                       sizeof (ATAPI_READ_FORMAT_CAPACITY_DATA),
                                                       ATAPITIMEOUT*10
                                                       );
  }

  if (Status == EFI_SUCCESS) {
    if (MediaInfo->DeviceType == IdeCDROM) {
      MediaInfo->LastBlock    = ((UINT32)Data.LastLba3 << 24) | (Data.LastLba2 << 16) | (Data.LastLba1 << 8) | Data.LastLba0;
      MediaInfo->MediaPresent = TRUE;
      //
      // Because the user data portion in the sector of the Data CD supported
      // is always 800h
      //
      MediaInfo->BlockSize = 0x800;

      MediaInfo2->LastBlock    = MediaInfo->LastBlock;
      MediaInfo2->MediaPresent = MediaInfo->MediaPresent;
      MediaInfo2->BlockSize    = (UINT32)MediaInfo->BlockSize;
    }

    if (MediaInfo->DeviceType == IdeLS120) {
      if (FormatData.DesCode == 3) {
        MediaInfo->MediaPresent  = FALSE;
        MediaInfo->LastBlock     = 0;
        MediaInfo2->MediaPresent = FALSE;
        MediaInfo2->LastBlock    = 0;
      } else {
        MediaInfo->LastBlock = ((UINT32)FormatData.LastLba3 << 24) |
                               (FormatData.LastLba2 << 16) |
                               (FormatData.LastLba1 << 8) |
                               FormatData.LastLba0;
        MediaInfo->LastBlock--;

        MediaInfo->MediaPresent = TRUE;

        MediaInfo->BlockSize = 0x200;

        MediaInfo2->LastBlock    = MediaInfo->LastBlock;
        MediaInfo2->MediaPresent = MediaInfo->MediaPresent;
        MediaInfo2->BlockSize    = (UINT32)MediaInfo->BlockSize;
      }
    }

    return EFI_SUCCESS;
  } else {
    return EFI_DEVICE_ERROR;
  }
}

/**
  Perform read from disk in block unit.

  @param[in]  AtapiBlkIoDev   A pointer to atapi block IO device.
  @param[in]  DevicePosition  An integer to signify device position.
  @param[in]  Buffer          Buffer to contain read data.
  @param[in]  StartLba        Starting LBA address.
  @param[in]  NumberOfBlocks  Number of blocks to read.
  @param[in]  BlockSize       Size of each block.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
ReadSectors (
  IN  ATAPI_BLK_IO_DEV  *AtapiBlkIoDev,
  IN  UINTN             DevicePosition,
  IN  VOID              *Buffer,
  IN  EFI_PEI_LBA       StartLba,
  IN  UINTN             NumberOfBlocks,
  IN  UINTN             BlockSize
  )
{
  ATAPI_PACKET_COMMAND  Packet;
  ATAPI_READ10_CMD      *Read10Packet;
  EFI_STATUS            Status;
  UINTN                 BlocksRemaining;
  UINT32                Lba32;
  UINT32                ByteCount;
  UINT16                SectorCount;
  VOID                  *PtrBuffer;
  UINT16                MaxBlock;

  //
  // fill command packet for Read(10) command
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  Read10Packet = &Packet.Read10;
  Lba32        = (UINT32)StartLba;
  PtrBuffer    = Buffer;

  //
  // limit the data bytes that can be transfered by one Read(10) Command
  //
  MaxBlock = (UINT16)(0x10000 / BlockSize);
  //
  // (64k bytes)
  //
  BlocksRemaining = NumberOfBlocks;

  Status = EFI_SUCCESS;
  while (BlocksRemaining > 0) {
    if (BlocksRemaining <= MaxBlock) {
      SectorCount = (UINT16)BlocksRemaining;
    } else {
      SectorCount = MaxBlock;
    }

    //
    // fill the Packet data sturcture
    //
    Read10Packet->opcode = ATA_CMD_READ_10;

    //
    // Lba0 ~ Lba3 specify the start logical block address of the data transfer.
    // Lba0 is MSB, Lba3 is LSB
    //
    Read10Packet->Lba3 = (UINT8)(Lba32 & 0xff);
    Read10Packet->Lba2 = (UINT8)(Lba32 >> 8);
    Read10Packet->Lba1 = (UINT8)(Lba32 >> 16);
    Read10Packet->Lba0 = (UINT8)(Lba32 >> 24);

    //
    // TranLen0 ~ TranLen1 specify the transfer length in block unit.
    // TranLen0 is MSB, TranLen is LSB
    //
    Read10Packet->TranLen1 = (UINT8)(SectorCount & 0xff);
    Read10Packet->TranLen0 = (UINT8)(SectorCount >> 8);

    ByteCount = (UINT32)(SectorCount * BlockSize);

    Status = AtapiPacketCommandIn (
               AtapiBlkIoDev,
               DevicePosition,
               &Packet,
               (UINT16 *)PtrBuffer,
               ByteCount,
               ATAPILONGTIMEOUT
               );
    if (Status != EFI_SUCCESS) {
      return Status;
    }

    Lba32           += SectorCount;
    PtrBuffer        = (UINT8 *)PtrBuffer + SectorCount * BlockSize;
    BlocksRemaining -= SectorCount;
  }

  return Status;
}

/**
  Check if there is media according to sense data.

  @param[in]  SenseData   Pointer to sense data.
  @param[in]  SenseCounts Count of sense data.

  @retval TRUE    No media
  @retval FALSE   Media exists

**/
BOOLEAN
IsNoMedia (
  IN  ATAPI_REQUEST_SENSE_DATA  *SenseData,
  IN  UINTN                     SenseCounts
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   IsNoMedia;

  IsNoMedia = FALSE;

  SensePtr = SenseData;

  for (Index = 0; Index < SenseCounts; Index++) {
    if ((SensePtr->sense_key == ATA_SK_NOT_READY) && (SensePtr->addnl_sense_code == ATA_ASC_NO_MEDIA)) {
      IsNoMedia = TRUE;
    }

    SensePtr++;
  }

  return IsNoMedia;
}

/**
  Check if device state is unclear according to sense data.

  @param[in]  SenseData   Pointer to sense data.
  @param[in]  SenseCounts Count of sense data.

  @retval TRUE    Device state is unclear
  @retval FALSE   Device state is clear

**/
BOOLEAN
IsDeviceStateUnclear (
  IN  ATAPI_REQUEST_SENSE_DATA  *SenseData,
  IN  UINTN                     SenseCounts
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   Unclear;

  Unclear = FALSE;

  SensePtr = SenseData;

  for (Index = 0; Index < SenseCounts; Index++) {
    if (SensePtr->sense_key == 0x06) {
      //
      // Sense key is 0x06 means the device is just be reset or media just
      // changed. The current state of the device is unclear.
      //
      Unclear = TRUE;
      break;
    }

    SensePtr++;
  }

  return Unclear;
}

/**
  Check if there is media error according to sense data.

  @param[in]  SenseData   Pointer to sense data.
  @param[in]  SenseCounts Count of sense data.

  @retval TRUE    Media error
  @retval FALSE   No media error

**/
BOOLEAN
IsMediaError (
  IN  ATAPI_REQUEST_SENSE_DATA  *SenseData,
  IN  UINTN                     SenseCounts
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   IsError;

  IsError = FALSE;

  SensePtr = SenseData;

  for (Index = 0; Index < SenseCounts; Index++) {
    switch (SensePtr->sense_key) {
      case ATA_SK_MEDIUM_ERROR:
        switch (SensePtr->addnl_sense_code) {
          case ATA_ASC_MEDIA_ERR1:
          //
          // fall through
          //
          case ATA_ASC_MEDIA_ERR2:
          //
          // fall through
          //
          case ATA_ASC_MEDIA_ERR3:
          //
          // fall through
          //
          case ATA_ASC_MEDIA_ERR4:
            IsError = TRUE;
            break;

          default:
            break;
        }

        break;

      case ATA_SK_NOT_READY:
        switch (SensePtr->addnl_sense_code) {
          case ATA_ASC_MEDIA_UPSIDE_DOWN:
            IsError = TRUE;
            break;

          default:
            break;
        }

        break;

      default:
        break;
    }

    SensePtr++;
  }

  return IsError;
}

/**
  Check if drive is ready according to sense data.

  @param[in]  SenseData   Pointer to sense data.
  @param[in]  SenseCounts Count of sense data.
  @param[out] NeedRetry   Indicate if retry is needed.

  @retval TRUE    Drive ready
  @retval FALSE   Drive not ready

**/
BOOLEAN
IsDriveReady (
  IN  ATAPI_REQUEST_SENSE_DATA  *SenseData,
  IN  UINTN                     SenseCounts,
  OUT BOOLEAN                   *NeedRetry
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   IsReady;

  IsReady    = TRUE;
  *NeedRetry = FALSE;

  SensePtr = SenseData;

  for (Index = 0; Index < SenseCounts; Index++) {
    switch (SensePtr->sense_key) {
      case ATA_SK_NOT_READY:
        switch (SensePtr->addnl_sense_code) {
          case ATA_ASC_NOT_READY:
            switch (SensePtr->addnl_sense_code_qualifier) {
              case ATA_ASCQ_IN_PROGRESS:
                IsReady    = FALSE;
                *NeedRetry = TRUE;
                break;

              default:
                IsReady    = FALSE;
                *NeedRetry = FALSE;
                break;
            }

            break;

          default:
            break;
        }

        break;

      default:
        break;
    }

    SensePtr++;
  }

  return IsReady;
}
