/** @file
  Copyright (c) 2006, Intel Corporation                                                         
  All rights reserved. This program and the accompanying materials                          
  are licensed and made available under the terms and conditions of the BSD License         
  which accompanies this distribution.  The full text of the license may be found at        
  http://opensource.org/licenses/bsd-license.php                                            

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

**/

#include "AtapiPassThru.h"

EFI_STATUS
EFIAPI
AtapiScsiPassThruDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  );

EFI_STATUS
EFIAPI
AtapiScsiPassThruDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  );

EFI_STATUS
EFIAPI
AtapiScsiPassThruDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL     *This,
  IN  EFI_HANDLE                      Controller,
  IN  UINTN                           NumberOfChildren,
  IN  EFI_HANDLE                      *ChildHandleBuffer
  );

///
/// IDE registers' fixed address
///
static IDE_BASE_REGISTERS   gAtapiIoPortRegisters[2] = {
  { 0x1f0, { 0x1f1 }, 0x1f2, 0x1f3, 0x1f4, 0x1f5, 0x1f6, { 0x1f7 }, { 0x3f6 }, 0x3f7, 0 },
  { 0x170, { 0x171 }, 0x172, 0x173, 0x174, 0x175, 0x176, { 0x177 }, { 0x376 }, 0x377, 0 } 
};

static SCSI_COMMAND_SET     gEndTable = { 0xff, 0xff };

///
/// This table contains all the supported ATAPI commands.
///
static SCSI_COMMAND_SET     gSupportedATAPICommands[] = {
  { OP_INQUIRY,                     DataIn  },
  { OP_LOAD_UNLOAD_CD,              NoData  },
  { OP_MECHANISM_STATUS,            DataIn  },
  { OP_MODE_SELECT_10,              DataOut },
  { OP_MODE_SENSE_10,               DataIn  },
  { OP_PAUSE_RESUME,                NoData  },
  { OP_PLAY_AUDIO_10,               DataIn  },
  { OP_PLAY_AUDIO_MSF,              DataIn  },
  { OP_PLAY_CD,                     DataIn  },
  { OP_PLAY_CD_MSF,                 DataIn  },
  { OP_PREVENT_ALLOW_MEDIUM_REMOVAL,NoData  },
  { OP_READ_10,                     DataIn  },
  { OP_READ_12,                     DataIn  },
  { OP_READ_CAPACITY,               DataIn  },
  { OP_READ_CD,                     DataIn  },
  { OP_READ_CD_MSF,                 DataIn  },
  { OP_READ_HEADER,                 DataIn  },
  { OP_READ_SUB_CHANNEL,            DataIn  },
  { OP_READ_TOC,                    DataIn  },
  { OP_REQUEST_SENSE,               DataIn  },
  { OP_SCAN,                        NoData  },
  { OP_SEEK_10,                     NoData  },
  { OP_SET_CD_SPEED,                DataOut },
  { OP_STOPPLAY_SCAN,               NoData  },
  { OP_START_STOP_UNIT,             NoData  },
  { OP_TEST_UNIT_READY,             NoData  },
  { OP_FORMAT_UNIT,                 DataOut },
  { OP_READ_FORMAT_CAPACITIES,      DataIn  },
  { OP_VERIFY,                      DataOut },
  { OP_WRITE_10,                    DataOut },
  { OP_WRITE_12,                    DataOut },
  { OP_WRITE_AND_VERIFY,            DataOut },
  { 0xff,                           0xff    } 
};

static CHAR16               *gControllerNameString  = (CHAR16 *) L"ATAPI Controller";
static CHAR16               *gAtapiChannelString    = (CHAR16 *) L"ATAPI Channel";

EFI_DRIVER_BINDING_PROTOCOL gAtapiScsiPassThruDriverBinding = {
  AtapiScsiPassThruDriverBindingSupported,
  AtapiScsiPassThruDriverBindingStart,
  AtapiScsiPassThruDriverBindingStop,
  0x10,
  NULL,
  NULL
};

/**
  Supported.

  (Standard DriverBinding Protocol Supported() function)

  @return EFI_STATUS

  @todo    This - add argument and description to function comment
  @todo    Controller - add argument and description to function comment
  @todo    RemainingDevicePath - add argument and description to function comment
  @todo    EFI_UNSUPPORTED - add return value to function comment
**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS          Status;
  EFI_PCI_IO_PROTOCOL *PciIo;
  PCI_TYPE00          Pci;

  //
  // Open the IO Abstraction(s) needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Use the PCI I/O Protocol to see if Controller is a IDE Controller that
  // can be managed by this driver.  Read the PCI Configuration Header
  // for this device.
  //
  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0,
                        sizeof (Pci) / sizeof (UINT32),
                        &Pci
                        );
  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
          Controller,
          &gEfiPciIoProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );
    return EFI_UNSUPPORTED;
  }

  if (Pci.Hdr.ClassCode[2] != PCI_CLASS_MASS_STORAGE || Pci.Hdr.ClassCode[1] != PCI_CLASS_IDE) {

    Status = EFI_UNSUPPORTED;
  }

  gBS->CloseProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  return Status;
}

/**
  Create handles for IDE channels specified by RemainingDevicePath.
  Install SCSI Pass Thru Protocol onto each created handle.

  (Standard DriverBinding Protocol Start() function)

  @return EFI_STATUS

  @todo    This - add argument and description to function comment
  @todo    Controller - add argument and description to function comment
  @todo    RemainingDevicePath - add argument and description to function comment
**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS          Status;
  EFI_PCI_IO_PROTOCOL *PciIo;

  PciIo = NULL;
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PciIo->Attributes (
                    PciIo,
                    EfiPciIoAttributeOperationEnable,
                    EFI_PCI_IO_ATTRIBUTE_IDE_PRIMARY_IO | EFI_PCI_IO_ATTRIBUTE_IDE_SECONDARY_IO | EFI_PCI_DEVICE_ENABLE,
                    NULL
                    );
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  //
  // Create SCSI Pass Thru instance for the IDE channel.
  //
  Status = RegisterAtapiScsiPassThru (This, Controller, PciIo);

Done:
  if (EFI_ERROR (Status)) {
    if (PciIo) {
      PciIo->Attributes (
              PciIo,
              EfiPciIoAttributeOperationDisable,
              EFI_PCI_IO_ATTRIBUTE_IDE_PRIMARY_IO | EFI_PCI_IO_ATTRIBUTE_IDE_SECONDARY_IO | EFI_PCI_DEVICE_ENABLE,
              NULL
              );
    }

    gBS->CloseProtocol (
          Controller,
          &gEfiPciIoProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );
  }

  return Status;
}

/**
  Stop.

  (Standard DriverBinding Protocol Stop() function)

  @return EFI_STATUS

  @todo    This - add argument and description to function comment
  @todo    Controller - add argument and description to function comment
  @todo    NumberOfChildren - add argument and description to function comment
  @todo    ChildHandleBuffer - add argument and description to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL     *This,
  IN  EFI_HANDLE                      Controller,
  IN  UINTN                           NumberOfChildren,
  IN  EFI_HANDLE                      *ChildHandleBuffer
  )
{
  EFI_STATUS                  Status;
  EFI_SCSI_PASS_THRU_PROTOCOL *ScsiPassThru;
  ATAPI_SCSI_PASS_THRU_DEV    *AtapiScsiPrivate;

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiScsiPassThruProtocolGuid,
                  (VOID **) &ScsiPassThru,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  AtapiScsiPrivate = ATAPI_SCSI_PASS_THRU_DEV_FROM_THIS (ScsiPassThru);

  Status = gBS->UninstallProtocolInterface (
                  Controller,
                  &gEfiScsiPassThruProtocolGuid,
                  &AtapiScsiPrivate->ScsiPassThru
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Release Pci Io protocol on the controller handle.
  //
  AtapiScsiPrivate->PciIo->Attributes (
                            AtapiScsiPrivate->PciIo,
                            EfiPciIoAttributeOperationDisable,
                            EFI_PCI_IO_ATTRIBUTE_IDE_PRIMARY_IO | EFI_PCI_IO_ATTRIBUTE_IDE_SECONDARY_IO | EFI_PCI_DEVICE_ENABLE,
                            NULL
                            );

  gBS->CloseProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  gBS->FreePool (AtapiScsiPrivate);

  return EFI_SUCCESS;
}

/**
  Attaches SCSI Pass Thru Protocol for specified IDE channel.

  @param Controller:       Parent device handle to the IDE channel.
  @param PciIo:            PCI I/O protocol attached on the "Controller".

  @return EFI_SUCCESS Always returned unless installing SCSI Pass Thru Protocol failed.

  @todo    This - add argument and description to function comment
  @todo    Controller - add argument and description to function comment
  @todo    PciIo - add argument and description to function comment
  @todo    EFI_OUT_OF_RESOURCES - add return value to function comment
**/
EFI_STATUS
RegisterAtapiScsiPassThru (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                  Controller,
  IN  EFI_PCI_IO_PROTOCOL         *PciIo
  )
{
  EFI_STATUS                Status;
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate;
  UINT64                    Attributes;

  AtapiScsiPrivate = AllocateZeroPool (sizeof (ATAPI_SCSI_PASS_THRU_DEV));
  if (AtapiScsiPrivate == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Attributes = EFI_PCI_IO_ATTRIBUTE_IDE_PRIMARY_IO | EFI_PCI_IO_ATTRIBUTE_IDE_SECONDARY_IO | EFI_PCI_DEVICE_ENABLE;
  CopyMem (AtapiScsiPrivate->ChannelName, gAtapiChannelString, sizeof (gAtapiChannelString));

  //
  // Enable channel
  //
  PciIo->Attributes (PciIo, EfiPciIoAttributeOperationSet, Attributes, NULL);

  AtapiScsiPrivate->Signature = ATAPI_SCSI_PASS_THRU_DEV_SIGNATURE;
  AtapiScsiPrivate->Handle    = Controller;

  //
  // will reset the IoPort inside each API function.
  //
  AtapiScsiPrivate->IoPort  = gAtapiIoPortRegisters;
  AtapiScsiPrivate->PciIo   = PciIo;

  //
  // initialize SCSI Pass Thru Protocol interface
  //
  AtapiScsiPrivate->ScsiPassThru.Mode             = &AtapiScsiPrivate->ScsiPassThruMode;
  AtapiScsiPrivate->ScsiPassThru.PassThru         = AtapiScsiPassThruFunction;
  AtapiScsiPrivate->ScsiPassThru.GetNextDevice    = AtapiScsiPassThruGetNextDevice;
  AtapiScsiPrivate->ScsiPassThru.BuildDevicePath  = AtapiScsiPassThruBuildDevicePath;
  AtapiScsiPrivate->ScsiPassThru.GetTargetLun     = AtapiScsiPassThruGetTargetLun;
  AtapiScsiPrivate->ScsiPassThru.ResetChannel     = AtapiScsiPassThruResetChannel;
  AtapiScsiPrivate->ScsiPassThru.ResetTarget      = AtapiScsiPassThruResetTarget;

  //
  // Set Mode
  //
  CopyMem (AtapiScsiPrivate->ControllerName, gControllerNameString, sizeof (gControllerNameString));

  AtapiScsiPrivate->ScsiPassThruMode.ControllerName = AtapiScsiPrivate->ControllerName;
  AtapiScsiPrivate->ScsiPassThruMode.ChannelName    = AtapiScsiPrivate->ChannelName;
  AtapiScsiPrivate->ScsiPassThruMode.AdapterId      = 4;
  //
  // non-RAID SCSI controllers should set both physical and logical attributes
  //
  AtapiScsiPrivate->ScsiPassThruMode.Attributes = EFI_SCSI_PASS_THRU_ATTRIBUTES_PHYSICAL | 
                                                  EFI_SCSI_PASS_THRU_ATTRIBUTES_LOGICAL;
  AtapiScsiPrivate->ScsiPassThruMode.IoAlign = 0;

  //
  // Initialize the LatestTargetId to 0xFFFFFFFF (for the GetNextDevice() call).
  //
  AtapiScsiPrivate->LatestTargetId  = 0xFFFFFFFF;
  AtapiScsiPrivate->LatestLun       = 0;

  Status = gBS->InstallProtocolInterface (
                  &Controller,
                  &gEfiScsiPassThruProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &AtapiScsiPrivate->ScsiPassThru
                  );
  return Status;
}

/**
  Implements EFI_SCSI_PASS_THRU_PROTOCOL.PassThru() function.

  @param This     The EFI_SCSI_PASS_THRU_PROTOCOL instance.
  @param Target   The Target ID of the ATAPI device to send the SCSI
  Request Packet. To ATAPI devices attached on an IDE
  Channel, Target ID 0 indicates Master device;Target
  ID 1 indicates Slave device.
  @param Lun      The LUN of the ATAPI device to send the SCSI Request
  Packet. To the ATAPI device, Lun is always 0.
  @param Packet   The SCSI Request Packet to send to the ATAPI device
  specified by Target and Lun.
  @param Event    If non-blocking I/O is not supported then Event is ignored,
  and blocking I/O is performed.<br>
  If Event is NULL, then blocking I/O is performed.<br>
  If Event is not NULL and non blocking I/O is supported,
  then non-blocking I/O is performed, and Event will be signaled
  when the SCSI Request Packet completes.

  @todo    This - add argument and description to function comment
  @todo    EFI_INVALID_PARAMETER - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruFunction (
  IN EFI_SCSI_PASS_THRU_PROTOCOL                        *This,
  IN UINT32                                             Target,
  IN UINT64                                             Lun,
  IN OUT EFI_SCSI_PASS_THRU_SCSI_REQUEST_PACKET         *Packet,
  IN EFI_EVENT                                          Event OPTIONAL
  )
{
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate;
  EFI_STATUS                Status;

  AtapiScsiPrivate = ATAPI_SCSI_PASS_THRU_DEV_FROM_THIS (This);

  //
  // Target is not allowed beyond MAX_TARGET_ID
  //
  if (Target > MAX_TARGET_ID) {
    return EFI_INVALID_PARAMETER;
  }
  
  //
  // check the data fields in Packet parameter.
  //
  Status = CheckSCSIRequestPacket (Packet);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // If Request Packet targets at the IDE channel itself,
  // do nothing.
  //
  if (Target == This->Mode->AdapterId) {
    Packet->TransferLength = 0;
    return EFI_SUCCESS;
  }
  
  //
  // According to Target ID, reset the Atapi I/O Register mapping
  // (Target Id in [0,1] area, using gAtapiIoPortRegisters[0],
  //  Target Id in [2,3] area, using gAtapiIoPortRegisters[1]
  //
  if ((Target / 2) == 0) {
    AtapiScsiPrivate->IoPort = &gAtapiIoPortRegisters[0];
  } else {
    AtapiScsiPrivate->IoPort = &gAtapiIoPortRegisters[1];
  }
  
  //
  // the ATAPI SCSI interface does not support non-blocking I/O
  // ignore the Event parameter
  //
  // Performs blocking I/O.
  //
  Status = SubmitBlockingIoCommand (AtapiScsiPrivate, Target, Packet);
  return Status;
}

/**
  Used to retrieve the list of legal Target IDs for SCSI devices 
  on a SCSI channel.

  @param  This Protocol instance pointer.
  @param  Target On input, a pointer to the Target ID of a SCSI
  device present on the SCSI channel.  On output,
  a pointer to the Target ID of the next SCSI device
  present on a SCSI channel.  An input value of
  0xFFFFFFFF retrieves the Target ID of the first
  SCSI device present on a SCSI channel.
  @param  Lun On input, a pointer to the LUN of a SCSI device
  present on the SCSI channel. On output, a pointer
  to the LUN of the next SCSI device present on
  a SCSI channel.

  @retval  EFI_SUCCESS The Target ID and Lun of the next SCSI device
  on the SCSI channel was returned in Target and Lun.
  @retval  EFI_NOT_FOUND There are no more SCSI devices on this SCSI channel.
  @retval  EFI_INVALID_PARAMETER Target is not 0xFFFFFFFF,and Target and Lun were not
  returned on a previous call to GetNextDevice().

**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruGetNextDevice (
  IN  EFI_SCSI_PASS_THRU_PROTOCOL    *This,
  IN OUT UINT32                      *Target,
  IN OUT UINT64                      *Lun
  )
{
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate;

  //
  // Retrieve Device Private Data Structure.
  //
  AtapiScsiPrivate = ATAPI_SCSI_PASS_THRU_DEV_FROM_THIS (This);

  //
  // Check whether Target is valid.
  //
  if (Target == NULL || Lun == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if ((*Target != 0xFFFFFFFF) &&
      ((*Target != AtapiScsiPrivate->LatestTargetId) ||
      (*Lun != AtapiScsiPrivate->LatestLun))) {
    return EFI_INVALID_PARAMETER;
  }

  if (*Target == MAX_TARGET_ID) {
    return EFI_NOT_FOUND;
  }

  if (*Target == 0xFFFFFFFF) {
    *Target = 0;
  } else {
    *Target = AtapiScsiPrivate->LatestTargetId + 1;
  }

  *Lun = 0;

  //
  // Update the LatestTargetId.
  //
  AtapiScsiPrivate->LatestTargetId  = *Target;
  AtapiScsiPrivate->LatestLun       = *Lun;

  return EFI_SUCCESS;

}

/**
  Used to allocate and build a device path node for a SCSI device 
  on a SCSI channel. Would not build device path for a SCSI Host Controller.

  @param  This Protocol instance pointer.
  @param  Target The Target ID of the SCSI device for which
  a device path node is to be allocated and built.
  @param  Lun The LUN of the SCSI device for which a device
  path node is to be allocated and built.
  @param  DevicePath A pointer to a single device path node that
  describes the SCSI device specified by
  Target and Lun. This function is responsible
  for allocating the buffer DevicePath with the boot
  service AllocatePool().  It is the caller's
  responsibility to free DevicePath when the caller
  is finished with DevicePath.

  @retval  EFI_SUCCESS The device path node that describes the SCSI device
  specified by Target and Lun was allocated and
  returned in DevicePath.
  @retval  EFI_NOT_FOUND The SCSI devices specified by Target and Lun does
  not exist on the SCSI channel.
  @retval  EFI_INVALID_PARAMETER DevicePath is NULL.
  @retval  EFI_OUT_OF_RESOURCES There are not enough resources to allocate
  DevicePath.

**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruBuildDevicePath (
  IN     EFI_SCSI_PASS_THRU_PROTOCOL    *This,
  IN     UINT32                         Target,
  IN     UINT64                         Lun,
  IN OUT EFI_DEVICE_PATH_PROTOCOL       **DevicePath
  )
{
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate;
  EFI_DEV_PATH              *Node;

  //
  // Retrieve Device Private Data Structure.
  //
  AtapiScsiPrivate = ATAPI_SCSI_PASS_THRU_DEV_FROM_THIS (This);

  //
  // Validate parameters passed in.
  //
  
  if (DevicePath == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  
  //
  // can not build device path for the SCSI Host Controller.
  //
  if ((Target > (MAX_TARGET_ID - 1)) || (Lun != 0)) {
    return EFI_NOT_FOUND;
  }

  Node = AllocateZeroPool (sizeof (EFI_DEV_PATH));
  if (Node == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Node->DevPath.Type    = MESSAGING_DEVICE_PATH;
  Node->DevPath.SubType = MSG_ATAPI_DP;
  SetDevicePathNodeLength (&Node->DevPath, sizeof (ATAPI_DEVICE_PATH));

  Node->Atapi.PrimarySecondary  = (UINT8) (Target / 2);
  Node->Atapi.SlaveMaster       = (UINT8) (Target % 2);
  Node->Atapi.Lun               = (UINT16) Lun;

  *DevicePath                   = (EFI_DEVICE_PATH_PROTOCOL *) Node;

  return EFI_SUCCESS;
}

/**
  Used to translate a device path node to a Target ID and LUN.

  @param  This Protocol instance pointer.
  @param  DevicePath A pointer to the device path node that
  describes a SCSI device on the SCSI channel.
  @param  Target A pointer to the Target ID of a SCSI device
  on the SCSI channel.
  @param  Lun A pointer to the LUN of a SCSI device on
  the SCSI channel.

  @retval  EFI_SUCCESS DevicePath was successfully translated to a
  Target ID and LUN, and they were returned
  in Target and Lun.
  @retval  EFI_INVALID_PARAMETER DevicePath is NULL.
  @retval  EFI_INVALID_PARAMETER Target is NULL.
  @retval  EFI_INVALID_PARAMETER Lun is NULL.
  @retval  EFI_UNSUPPORTED This driver does not support the device path
  node type in DevicePath.
  @retval  EFI_NOT_FOUND A valid translation from DevicePath to a
  Target ID and LUN does not exist.

**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruGetTargetLun (
  IN  EFI_SCSI_PASS_THRU_PROTOCOL    *This,
  IN  EFI_DEVICE_PATH_PROTOCOL       *DevicePath,
  OUT UINT32                         *Target,
  OUT UINT64                         *Lun
  )
{
  EFI_DEV_PATH  *Node;

  //
  // Validate parameters passed in.
  //
  if (DevicePath == NULL || Target == NULL || Lun == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  
  //
  // Check whether the DevicePath belongs to SCSI_DEVICE_PATH
  //
  if ((DevicePath->Type != MESSAGING_DEVICE_PATH) ||
      (DevicePath->SubType != MSG_ATAPI_DP) ||
      (DevicePathNodeLength(DevicePath) != sizeof(ATAPI_DEVICE_PATH))) {
    return EFI_UNSUPPORTED;
  }

  Node    = (EFI_DEV_PATH *) DevicePath;

  *Target = Node->Atapi.PrimarySecondary * 2 + Node->Atapi.SlaveMaster;
  *Lun    = Node->Atapi.Lun;

  if (*Target > (MAX_TARGET_ID - 1) || *Lun != 0) {
    return EFI_NOT_FOUND;
  }

  return EFI_SUCCESS;
}

/**
  Resets a SCSI channel.This operation resets all the 
  SCSI devices connected to the SCSI channel.

  @param  This Protocol instance pointer.

  @retval  EFI_SUCCESS The SCSI channel was reset.
  @retval  EFI_UNSUPPORTED The SCSI channel does not support
  a channel reset operation.
  @retval  EFI_DEVICE_ERROR A device error occurred while
  attempting to reset the SCSI channel.
  @retval  EFI_TIMEOUT A timeout occurred while attempting
  to reset the SCSI channel.

**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruResetChannel (
  IN  EFI_SCSI_PASS_THRU_PROTOCOL   *This
  )
{
  UINT8                     DeviceControlValue;
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate;
  UINT8                     Index;

  AtapiScsiPrivate = ATAPI_SCSI_PASS_THRU_DEV_FROM_THIS (This);

  //
  // Reset both Primary channel and Secondary channel.
  // so, the IoPort pointer must point to the right I/O Register group
  //
  for (Index = 0; Index < 2; Index++) {
    //
    // Reset
    //
    AtapiScsiPrivate->IoPort  = &gAtapiIoPortRegisters[Index];

    DeviceControlValue        = 0;
    //
    // set SRST bit to initiate soft reset
    //
    DeviceControlValue |= SRST;
    //
    // disable Interrupt
    //
    DeviceControlValue |= bit (1);
    WritePortB (
      AtapiScsiPrivate->PciIo,
      AtapiScsiPrivate->IoPort->Alt.DeviceControl,
      DeviceControlValue
      );

    //
    // Wait 10us
    //
    gBS->Stall (10);

    //
    // Clear SRST bit
    // 0xfb:1111,1011
    //
    DeviceControlValue &= 0xfb;
    
    WritePortB (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Alt.DeviceControl, DeviceControlValue);

    //
    // slave device needs at most 31s to clear BSY
    //
    if (StatusWaitForBSYClear (AtapiScsiPrivate, 31000) == EFI_TIMEOUT) {
      return EFI_DEVICE_ERROR;
    }
  }

  return EFI_SUCCESS;
}

/**
  Resets a SCSI device that is connected to a SCSI channel.

  @param  This Protocol instance pointer.
  @param  Target The Target ID of the SCSI device to reset.
  @param  Lun The LUN of the SCSI device to reset.

  @retval  EFI_SUCCESS The SCSI device specified by Target and
  Lun was reset.
  @retval  EFI_UNSUPPORTED The SCSI channel does not support a target
  reset operation.
  @retval  EFI_INVALID_PARAMETER Target or Lun are invalid.
  @retval  EFI_DEVICE_ERROR A device error occurred while attempting
  to reset the SCSI device specified by Target
  and Lun.
  @retval  EFI_TIMEOUT A timeout occurred while attempting to reset
  the SCSI device specified by Target and Lun.

**/
EFI_STATUS
EFIAPI
AtapiScsiPassThruResetTarget (
  IN EFI_SCSI_PASS_THRU_PROTOCOL    *This,
  IN UINT32                         Target,
  IN UINT64                         Lun
  )
{
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate;
  UINT8                     Command;
  UINT8                     DeviceSelect;

  AtapiScsiPrivate = ATAPI_SCSI_PASS_THRU_DEV_FROM_THIS (This);

  if (Target > MAX_TARGET_ID) {
    return EFI_INVALID_PARAMETER;
  }
  //
  // Directly return EFI_SUCCESS if want to reset the host controller
  //
  if (Target == This->Mode->AdapterId) {
    return EFI_SUCCESS;
  }
  
  //
  // According to Target ID, reset the Atapi I/O Register mapping
  // (Target Id in [0,1] area, using gAtapiIoPortRegisters[0],
  //  Target Id in [2,3] area, using gAtapiIoPortRegisters[1]
  //
  if ((Target / 2) == 0) {
    AtapiScsiPrivate->IoPort = &gAtapiIoPortRegisters[0];
  } else {
    AtapiScsiPrivate->IoPort = &gAtapiIoPortRegisters[1];
  }
  
  //
  // 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) (((bit (7) | bit (5)) | (Target << 4)));
  WritePortB (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Head, DeviceSelect);

  Command = ATAPI_SOFT_RESET_CMD;
  WritePortB (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Reg.Command, Command);

  //
  // BSY clear is the only status return to the host by the device
  // when reset is complete.
  // slave device needs at most 31s to clear BSY
  //
  if (EFI_ERROR (StatusWaitForBSYClear (AtapiScsiPrivate, 31000))) {
    return EFI_DEVICE_ERROR;
  }
  
  //
  // stall 5 seconds to make the device status stable
  //
  gBS->Stall (5000000);

  return EFI_SUCCESS;
}

    
/**
  Checks the parameters in the SCSI Request Packet to make sure
  they are valid for a SCSI Pass Thru request.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    Packet - add argument and description to function comment
  @todo    EFI_INVALID_PARAMETER - add return value to function comment
  @todo    EFI_INVALID_PARAMETER - add return value to function comment
  @todo    EFI_INVALID_PARAMETER - add return value to function comment
  @todo    EFI_UNSUPPORTED - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
CheckSCSIRequestPacket (
  EFI_SCSI_PASS_THRU_SCSI_REQUEST_PACKET      *Packet
  )
{
  if (Packet == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (!ValidCdbLength (Packet->CdbLength)) {
    return EFI_INVALID_PARAMETER;
  }

  if (Packet->Cdb == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  
  //
  // Checks whether the request command is supported.
  //
  if (!IsCommandValid (Packet)) {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**
  Checks the requested SCSI command: 
  Is it supported by this driver?
  Is the Data transfer direction reasonable?

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    Packet - add argument and description to function comment
**/
BOOLEAN
IsCommandValid (
  EFI_SCSI_PASS_THRU_SCSI_REQUEST_PACKET   *Packet
  )
{
  UINT8 Index;
  UINT8 *OpCode;

  OpCode = (UINT8 *) (Packet->Cdb);

  for (Index = 0; CompareMem (&gSupportedATAPICommands[Index], &gEndTable, sizeof (SCSI_COMMAND_SET)); Index++) {

    if (*OpCode == gSupportedATAPICommands[Index].OpCode) {
      //
      // Check whether the requested Command is supported by this driver
      //
      if (Packet->DataDirection == DataIn) {
        //
        // Check whether the requested data direction conforms to
        // what it should be.
        //
        if (gSupportedATAPICommands[Index].Direction == DataOut) {
          return FALSE;
        }
      }

      if (Packet->DataDirection == DataOut) {
        //
        // Check whether the requested data direction conforms to
        // what it should be.
        //
        if (gSupportedATAPICommands[Index].Direction == DataIn) {
          return FALSE;
        }
      }

      return TRUE;
    }
  }

  return FALSE;
}

/**
  Performs blocking I/O request.

  @param AtapiScsiPrivate   Private data structure for the specified channel.
  @param Target             The Target ID of the ATAPI device to send the SCSI
  Request Packet. To ATAPI devices attached on an IDE
  Channel, Target ID 0 indicates Master device;Target
  ID 1 indicates Slave device.
  @param Packet             The SCSI Request Packet to send to the ATAPI device
  specified by Target.

  @todo    AtapiScsiPrivate - add argument and description to function comment
**/
EFI_STATUS
SubmitBlockingIoCommand (
  ATAPI_SCSI_PASS_THRU_DEV                  *AtapiScsiPrivate,
  UINT32                                    Target,
  EFI_SCSI_PASS_THRU_SCSI_REQUEST_PACKET    *Packet
  )
{
  UINT8       PacketCommand[12];
  UINT64      TimeoutInMicroSeconds;
  EFI_STATUS  PacketCommandStatus;

  //
  // Fill ATAPI Command Packet according to CDB
  //
  ZeroMem (&PacketCommand, 12);
  CopyMem (&PacketCommand, Packet->Cdb, Packet->CdbLength);

  //
  // Timeout is 100ns unit, convert it to 1000ns (1us) unit.
  //
  TimeoutInMicroSeconds = DivU64x32 (Packet->Timeout, (UINT32) 10);

  //
  // Submit ATAPI Command Packet
  //
  PacketCommandStatus = AtapiPacketCommand (
                          AtapiScsiPrivate,
                          Target,
                          PacketCommand,
                          Packet->DataBuffer,
                          &(Packet->TransferLength),
                          Packet->DataDirection,
                          TimeoutInMicroSeconds
                          );
  if (!EFI_ERROR (PacketCommandStatus) || (Packet->SenseData == NULL)) {
    Packet->SenseDataLength = 0;
    return PacketCommandStatus;
  }
  //
  // Return SenseData if PacketCommandStatus matches
  // the following return codes.
  //
  if ((PacketCommandStatus == EFI_WARN_BUFFER_TOO_SMALL) ||
      (PacketCommandStatus == EFI_DEVICE_ERROR) ||
      (PacketCommandStatus == EFI_TIMEOUT)) {
    
    //
    // avoid submit request sense command continuously.
    //
    if (PacketCommand[0] == OP_REQUEST_SENSE) {
      Packet->SenseDataLength = 0;
      return PacketCommandStatus;
    }

    RequestSenseCommand (
      AtapiScsiPrivate,
      Target,
      Packet->Timeout,
      Packet->SenseData,
      &Packet->SenseDataLength
      );
  }

  return PacketCommandStatus;
}

/**
  RequestSenseCommand

  @param  AtapiScsiPrivate
  @param  Target
  @param  Timeout
  @param  SenseData
  @param  SenseDataLength

  @todo Add function description
  @todo  AtapiScsiPrivate TODO: add argument description
  @todo  Target TODO: add argument description
  @todo  Timeout TODO: add argument description
  @todo  SenseData TODO: add argument description
  @todo  SenseDataLength TODO: add argument description
  @todo add return values
**/
EFI_STATUS
RequestSenseCommand (
  ATAPI_SCSI_PASS_THRU_DEV    *AtapiScsiPrivate,
  UINT32                      Target,
  UINT64                      Timeout,
  VOID                        *SenseData,
  UINT8                       *SenseDataLength
  )
{
  EFI_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  Packet;
  UINT8                                   Cdb[12];
  EFI_STATUS                              Status;

  ZeroMem (&Packet, sizeof (EFI_SCSI_PASS_THRU_SCSI_REQUEST_PACKET));
  ZeroMem (Cdb, 12);

  Cdb[0]                = OP_REQUEST_SENSE;
  Cdb[4]                = (UINT8) (*SenseDataLength);

  Packet.Timeout        = Timeout;
  Packet.DataBuffer     = SenseData;
  Packet.SenseData      = NULL;
  Packet.Cdb            = Cdb;
  Packet.TransferLength = *SenseDataLength;
  Packet.CdbLength      = 12;
  Packet.DataDirection  = DataIn;

  Status                = SubmitBlockingIoCommand (AtapiScsiPrivate, Target, &Packet);
  *SenseDataLength      = (UINT8) (Packet.TransferLength);
  return Status;
}

/**
  Submits ATAPI command packet to the specified ATAPI device.

  @param AtapiScsiPrivate:   Private data structure for the specified channel.
  @param Target:             The Target ID of the ATAPI device to send the SCSI
  Request Packet. To ATAPI devices attached on an IDE
  Channel, Target ID 0 indicates Master device;Target
  ID 1 indicates Slave device.
  @param PacketCommand:      Points to the ATAPI command packet.
  @param Buffer:             Points to the transferred data.
  @param ByteCount:          When input,indicates the buffer size; when output,
  indicates the actually transferred data size.
  @param Direction:          Indicates the data transfer direction.
  @param TimeoutInMicroSeconds: The timeout, in micro second units,
  to use for the execution of this ATAPI command.
  A TimeoutInMicroSeconds value of 0 means that
  this function will wait indefinitely for the ATAPI
  command to execute.
  <P>
  If TimeoutInMicroSeconds is greater than zero, then
  this function will return EFI_TIMEOUT if the time
  required to execute the ATAPI command is greater
  than TimeoutInMicroSeconds.
  </P>

  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    PacketCommand - add argument and description to function comment
  @todo    Buffer - add argument and description to function comment
  @todo    ByteCount - add argument and description to function comment
  @todo    Direction - add argument and description to function comment
**/
EFI_STATUS
AtapiPacketCommand (
  ATAPI_SCSI_PASS_THRU_DEV    *AtapiScsiPrivate,
  UINT32                      Target,
  UINT8                       *PacketCommand,
  VOID                        *Buffer,
  UINT32                      *ByteCount,
  DATA_DIRECTION              Direction,
  UINT64                      TimeoutInMicroSeconds
  )
{

  UINT16      *CommandIndex;
  UINT8       Count;
  EFI_STATUS  Status;

  //
  // Set all the command parameters by fill related registers.
  // Before write to all the following registers, BSY and DRQ must be 0.
  //
  Status = StatusDRQClear (AtapiScsiPrivate, TimeoutInMicroSeconds);
  if (EFI_ERROR (Status)) {
    if (Status == EFI_ABORTED) {
      Status = EFI_DEVICE_ERROR;
    }

    *ByteCount = 0;
    return Status;
  }
  //
  // Select device via Device/Head Register.
  // "Target = 0" indicates device 0; "Target = 1" indicates device 1
  //
  WritePortB (
    AtapiScsiPrivate->PciIo,
    AtapiScsiPrivate->IoPort->Head,
    (UINT8) ((Target << 4) | DEFAULT_CMD) // DEFAULT_CMD: 0xa0 (1010,0000)
    );

  //
  // No OVL; No DMA (by setting feature register)
  //
  WritePortB (
    AtapiScsiPrivate->PciIo,
    AtapiScsiPrivate->IoPort->Reg1.Feature,
    0x00
    );

  //
  // set the transfersize to MAX_ATAPI_BYTE_COUNT to let the device
  // determine how much data should be transfered.
  //
  WritePortB (
    AtapiScsiPrivate->PciIo,
    AtapiScsiPrivate->IoPort->CylinderLsb,
    (UINT8) (MAX_ATAPI_BYTE_COUNT & 0x00ff)
    );
  WritePortB (
    AtapiScsiPrivate->PciIo,
    AtapiScsiPrivate->IoPort->CylinderMsb,
    (UINT8) (MAX_ATAPI_BYTE_COUNT >> 8)
    );

  //
  //  DEFAULT_CTL:0x0a (0000,1010)
  //  Disable interrupt
  //
  WritePortB (
    AtapiScsiPrivate->PciIo,
    AtapiScsiPrivate->IoPort->Alt.DeviceControl,
    DEFAULT_CTL
    );

  //
  // Send Packet command to inform device
  // that the following data bytes are command packet.
  //
  WritePortB (
    AtapiScsiPrivate->PciIo,
    AtapiScsiPrivate->IoPort->Reg.Command,
    PACKET_CMD
    );

  //
  // Before data transfer, BSY should be 0 and DRQ should be 1.
  // if they are not in specified time frame,
  // retrieve Sense Key from Error Register before return.
  //
  Status = StatusDRQReady (AtapiScsiPrivate, TimeoutInMicroSeconds);
  if (EFI_ERROR (Status)) {
    if (Status == EFI_ABORTED) {
      Status = EFI_DEVICE_ERROR;
    }

    *ByteCount = 0;
    return Status;
  }

  //
  // Send out command packet
  //
  CommandIndex = (UINT16 *) PacketCommand;
  for (Count = 0; Count < 6; Count++, CommandIndex++) {
    WritePortW (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Data, *CommandIndex);
  }

  //
  // call AtapiPassThruPioReadWriteData() function to get
  // requested transfer data form device.
  //
  return AtapiPassThruPioReadWriteData (
          AtapiScsiPrivate,
          Buffer,
          ByteCount,
          Direction,
          TimeoutInMicroSeconds
          );
}

/**
  Performs data transfer between ATAPI device and host after the
  ATAPI command packet is sent.

  @param AtapiScsiPrivate:   Private data structure for the specified channel.
  @param Buffer:             Points to the transferred data.
  @param ByteCount:          When input,indicates the buffer size; when output,
  indicates the actually transferred data size.
  @param Direction:          Indicates the data transfer direction.
  @param TimeoutInMicroSeconds: The timeout, in micro second units,
  to use for the execution of this ATAPI command.
  A TimeoutInMicroSeconds value of 0 means that
  this function will wait indefinitely for the ATAPI
  command to execute.
  <P>
  If TimeoutInMicroSeconds is greater than zero, then
  this function will return EFI_TIMEOUT if the time
  required to execute the ATAPI command is greater
  than TimeoutInMicroSeconds.
  </P>

  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    Buffer - add argument and description to function comment
  @todo    ByteCount - add argument and description to function comment
  @todo    Direction - add argument and description to function comment
  @todo    EFI_DEVICE_ERROR - add return value to function comment
  @todo    EFI_DEVICE_ERROR - add return value to function comment
  @todo    EFI_WARN_BUFFER_TOO_SMALL - add return value to function comment
**/
EFI_STATUS
AtapiPassThruPioReadWriteData (
  ATAPI_SCSI_PASS_THRU_DEV  *AtapiScsiPrivate,
  UINT16                    *Buffer,
  UINT32                    *ByteCount,
  DATA_DIRECTION            Direction,
  UINT64                    TimeoutInMicroSeconds
  )
{
  UINT32      Index;
  UINT32      RequiredWordCount;
  UINT32      ActualWordCount;

  UINT32      WordCount;
  EFI_STATUS  Status;
  UINT16      *ptrBuffer;

  Status = EFI_SUCCESS;

  //
  // Non Data transfer request is also supported.
  //
  if (*ByteCount == 0 || Buffer == NULL) {
    *ByteCount = 0;
    if (EFI_ERROR (StatusWaitForBSYClear (AtapiScsiPrivate, TimeoutInMicroSeconds))) {
      return EFI_DEVICE_ERROR;
    }
  }

  ptrBuffer         = Buffer;
  RequiredWordCount = *ByteCount / 2;

  //
  // ActuralWordCount means the word count of data really transfered.
  //
  ActualWordCount = 0;

  while (ActualWordCount < RequiredWordCount) {
    //
    // before each data transfer stream, the host should poll DRQ bit ready,
    // which indicates device's ready for data transfer .
    //
    Status = StatusDRQReady (AtapiScsiPrivate, TimeoutInMicroSeconds);
    if (EFI_ERROR (Status)) {
      *ByteCount = ActualWordCount * 2;

      AtapiPassThruCheckErrorStatus (AtapiScsiPrivate);

      if (ActualWordCount == 0) {
        return EFI_DEVICE_ERROR;
      }
      //
      // ActualWordCount > 0
      //
      if (ActualWordCount < RequiredWordCount) {
        return EFI_WARN_BUFFER_TOO_SMALL;
      }
    }
    //
    // get current data transfer size from Cylinder Registers.
    //
    WordCount =
      (
        (ReadPortB (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->CylinderMsb) << 8) |
        ReadPortB (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->CylinderLsb)
      ) & 0xffff;
    WordCount /= 2;

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

      if (Direction == DataIn) {

        *ptrBuffer = ReadPortW (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Data);
      } else {

        WritePortW (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Data, *ptrBuffer);
      }

      ptrBuffer++;

    }
  }
  //
  // After data transfer is completed, normally, DRQ bit should clear.
  //
  StatusDRQClear (AtapiScsiPrivate, TimeoutInMicroSeconds);

  //
  // read status register to check whether error happens.
  //
  Status      = AtapiPassThruCheckErrorStatus (AtapiScsiPrivate);

  *ByteCount  = ActualWordCount * 2;

  return Status;
}


/**
  Read one byte from a specified I/O port.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    PciIo - add argument and description to function comment
  @todo    Port - add argument and description to function comment
**/
UINT8
ReadPortB (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port
  )
{
  UINT8 Data;

  Data = 0;
  PciIo->Io.Read (
              PciIo,
              EfiPciIoWidthUint8,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );
  return Data;
}


/**
  Read one word from a specified I/O port.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    PciIo - add argument and description to function comment
  @todo    Port - add argument and description to function comment
**/
UINT16
ReadPortW (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port
  )
{
  UINT16  Data;

  Data = 0;
  PciIo->Io.Read (
              PciIo,
              EfiPciIoWidthUint16,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );
  return Data;
}


/**
  Write one byte to a specified I/O port.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    PciIo - add argument and description to function comment
  @todo    Port - add argument and description to function comment
  @todo    Data - add argument and description to function comment
**/
VOID
WritePortB (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port,
  IN  UINT8                 Data
  )
{

  PciIo->Io.Write (
              PciIo,
              EfiPciIoWidthUint8,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );

}


/**
  Write one word to a specified I/O port.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    PciIo - add argument and description to function comment
  @todo    Port - add argument and description to function comment
  @todo    Data - add argument and description to function comment
**/
VOID
WritePortW (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port,
  IN  UINT16                Data
  )
{

  PciIo->Io.Write (
              PciIo,
              EfiPciIoWidthUint16,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );
}

/**
  Check whether DRQ is clear in the Status Register. (BSY must also be cleared)
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  DRQ clear. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_ABORTED - add return value to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
StatusDRQClear (
  ATAPI_SCSI_PASS_THRU_DEV        *AtapiScsiPrivate,
  UINT64                          TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   StatusRegister;
  UINT8   ErrRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {

    StatusRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg.Status
                      );

    //
    // wait for BSY == 0 and DRQ == 0
    //
    if ((StatusRegister & (DRQ | BSY)) == 0) {
      break;
    }
    //
    // check whether the command is aborted by the device
    //
    if ((StatusRegister & (BSY | ERR)) == ERR) {

      ErrRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg1.Error
                      );
      if ((ErrRegister & ABRT_ERR) == ABRT_ERR) {

        return EFI_ABORTED;
      }
    }
    //
    //  Stall for 30 us
    //
    gBS->Stall (30);

    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether DRQ is clear in the Alternate Status Register. 
  (BSY must also be cleared).
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  DRQ clear. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_ABORTED - add return value to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
AltStatusDRQClear (
  ATAPI_SCSI_PASS_THRU_DEV        *AtapiScsiPrivate,
  UINT64                          TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   AltStatusRegister;
  UINT8   ErrRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {

    AltStatusRegister = ReadPortB (
                          AtapiScsiPrivate->PciIo,
                          AtapiScsiPrivate->IoPort->Alt.AltStatus
                          );

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

    if ((AltStatusRegister & (BSY | ERR)) == ERR) {

      ErrRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg1.Error
                      );
      if ((ErrRegister & ABRT_ERR) == ABRT_ERR) {

        return EFI_ABORTED;
      }
    }
    //
    //  Stall for 30 us
    //
    gBS->Stall (30);

    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether DRQ is ready in the Status Register. (BSY must also be cleared)
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  DRQ ready. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_ABORTED - add return value to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
StatusDRQReady (
  ATAPI_SCSI_PASS_THRU_DEV        *AtapiScsiPrivate,
  UINT64                          TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   StatusRegister;
  UINT8   ErrRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {
    //
    //  read Status Register will clear interrupt
    //
    StatusRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg.Status
                      );

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

    if ((StatusRegister & (BSY | ERR)) == ERR) {

      ErrRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg1.Error
                      );
      if ((ErrRegister & ABRT_ERR) == ABRT_ERR) {
        return EFI_ABORTED;
      }
    }

    //
    // Stall for 30 us
    //
    gBS->Stall (30);

    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether DRQ is ready in the Alternate Status Register. 
  (BSY must also be cleared)
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  DRQ ready. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_ABORTED - add return value to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
AltStatusDRQReady (
  ATAPI_SCSI_PASS_THRU_DEV        *AtapiScsiPrivate,
  UINT64                          TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   AltStatusRegister;
  UINT8   ErrRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {
    //
    //  read Status Register will clear interrupt
    //
    AltStatusRegister = ReadPortB (
                          AtapiScsiPrivate->PciIo,
                          AtapiScsiPrivate->IoPort->Alt.AltStatus
                          );
    //
    //  BSY==0,DRQ==1
    //
    if ((AltStatusRegister & (BSY | DRQ)) == DRQ) {
      break;
    }

    if ((AltStatusRegister & (BSY | ERR)) == ERR) {

      ErrRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg1.Error
                      );
      if ((ErrRegister & ABRT_ERR) == ABRT_ERR) {
        return EFI_ABORTED;
      }
    }

    //
    // Stall for 30 us
    //
    gBS->Stall (30);

    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether BSY is clear in the Status Register.
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  BSY clear. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
StatusWaitForBSYClear (
  ATAPI_SCSI_PASS_THRU_DEV    *AtapiScsiPrivate,
  UINT64                      TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   StatusRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {

    StatusRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg.Status
                      );
    if ((StatusRegister & BSY) == 0x00) {
      break;
    }

    //
    // Stall for 30 us
    //
    gBS->Stall (30);

    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether BSY is clear in the Alternate Status Register.
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  BSY clear. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
AltStatusWaitForBSYClear (
  ATAPI_SCSI_PASS_THRU_DEV    *AtapiScsiPrivate,
  UINT64                      TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   AltStatusRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {

    AltStatusRegister = ReadPortB (
                          AtapiScsiPrivate->PciIo,
                          AtapiScsiPrivate->IoPort->Alt.AltStatus
                          );
    if ((AltStatusRegister & BSY) == 0x00) {
      break;
    }

    //
    // Stall for 30 us
    //
    gBS->Stall (30);
    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether DRDY is ready in the Status Register. 
  (BSY must also be cleared)
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  DRDY ready. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_ABORTED - add return value to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
StatusDRDYReady (
  ATAPI_SCSI_PASS_THRU_DEV     *AtapiScsiPrivate,
  UINT64                       TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   StatusRegister;
  UINT8   ErrRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {
    StatusRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg.Status
                      );
    //
    //  BSY == 0 , DRDY == 1
    //
    if ((StatusRegister & (DRDY | BSY)) == DRDY) {
      break;
    }

    if ((StatusRegister & (BSY | ERR)) == ERR) {

      ErrRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg1.Error
                      );
      if ((ErrRegister & ABRT_ERR) == ABRT_ERR) {
        return EFI_ABORTED;
      }
    }
    
    //
    // Stall for 30 us
    //
    gBS->Stall (30);
    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check whether DRDY is ready in the Alternate Status Register. 
  (BSY must also be cleared)
  If TimeoutInMicroSeconds is zero, this routine should wait infinitely for
  DRDY ready. Otherwise, it will return EFI_TIMEOUT when specified time is 
  elapsed.

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    TimeoutInMicroSeconds - add argument and description to function comment
  @todo    EFI_ABORTED - add return value to function comment
  @todo    EFI_TIMEOUT - add return value to function comment
  @todo    EFI_SUCCESS - add return value to function comment
**/
EFI_STATUS
AltStatusDRDYReady (
  ATAPI_SCSI_PASS_THRU_DEV     *AtapiScsiPrivate,
  UINT64                       TimeoutInMicroSeconds
  )
{
  UINT64  Delay;
  UINT8   AltStatusRegister;
  UINT8   ErrRegister;

  if (TimeoutInMicroSeconds == 0) {
    Delay = 2;
  } else {
    Delay = DivU64x32 (TimeoutInMicroSeconds, (UINT32) 30) + 1;
  }

  do {
    AltStatusRegister = ReadPortB (
                          AtapiScsiPrivate->PciIo,
                          AtapiScsiPrivate->IoPort->Alt.AltStatus
                          );
    //
    //  BSY == 0 , DRDY == 1
    //
    if ((AltStatusRegister & (DRDY | BSY)) == DRDY) {
      break;
    }

    if ((AltStatusRegister & (BSY | ERR)) == ERR) {

      ErrRegister = ReadPortB (
                      AtapiScsiPrivate->PciIo,
                      AtapiScsiPrivate->IoPort->Reg1.Error
                      );
      if ((ErrRegister & ABRT_ERR) == ABRT_ERR) {
        return EFI_ABORTED;
      }
    }

    //
    // Stall for 30 us
    //
    gBS->Stall (30);
    //
    // Loop infinitely if not meeting expected condition
    //
    if (TimeoutInMicroSeconds == 0) {
      Delay = 2;
    }

    Delay--;
  } while (Delay);

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

  return EFI_SUCCESS;
}

/**
  Check Error Register for Error Information. 

  @todo function comment is missing 'Routine Description:'
  @todo function comment is missing 'Arguments:'
  @todo function comment is missing 'Returns:'
  @todo    AtapiScsiPrivate - add argument and description to function comment
  @todo    EFI_SUCCESS - add return value to function comment
  @todo    EFI_DEVICE_ERROR - add return value to function comment
**/
EFI_STATUS
AtapiPassThruCheckErrorStatus (
  ATAPI_SCSI_PASS_THRU_DEV        *AtapiScsiPrivate
  )
{
  UINT8 StatusRegister;

//#ifdef EFI_DEBUG

  UINT8 ErrorRegister;

//#endif

  StatusRegister = ReadPortB (
                    AtapiScsiPrivate->PciIo,
                    AtapiScsiPrivate->IoPort->Reg.Status
                    );
  DEBUG_CODE (

    if (StatusRegister & DWF) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Write Fault\n",
      StatusRegister)
      );
  }

  if (StatusRegister & CORR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Corrected Data\n",
      StatusRegister)
      );
  }

  if (StatusRegister & ERR) {
    ErrorRegister = ReadPortB (AtapiScsiPrivate->PciIo, AtapiScsiPrivate->IoPort->Reg1.Error);

    if (ErrorRegister & BBK_ERR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Bad Block Detected\n",
      ErrorRegister)
      );
  }

  if (ErrorRegister & UNC_ERR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Uncorrectable Data\n",
      ErrorRegister)
      );
  }

  if (ErrorRegister & MC_ERR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Media Change\n",
      ErrorRegister)
      );
  }

  if (ErrorRegister & ABRT_ERR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Abort\n",
      ErrorRegister)
      );
  }

  if (ErrorRegister & TK0NF_ERR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Track 0 Not Found\n",
      ErrorRegister)
      );
  }

  if (ErrorRegister & AMNF_ERR) {
    DEBUG (
      (EFI_D_BLKIO,
      "AtapiPassThruCheckErrorStatus()-- %02x : Error : Address Mark Not Found\n",
      ErrorRegister)
      );
  }

  }
  );

  if ((StatusRegister & (ERR | DWF | CORR)) == 0) {

    return EFI_SUCCESS;
  }

  return EFI_DEVICE_ERROR;

}
