/* $Id: Ide.c $ */
/** @file
 * Ide.c
 */

/*
 * Copyright (C) 2009-2010 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */

/** @file
  The file containing the helper functions implement of the Ide Bus driver

  Copyright (c) 2006 - 2008, 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 "IdeBus.h"

BOOLEAN ChannelDeviceDetected = FALSE;
BOOLEAN SlaveDeviceExist      = FALSE;
UINT8   SlaveDeviceType       = INVALID_DEVICE_TYPE;
BOOLEAN MasterDeviceExist     = FALSE;
UINT8   MasterDeviceType      = INVALID_DEVICE_TYPE;

/**
  read a one-byte data from a IDE port.

  @param  PciIo  The PCI IO protocol instance
  @param  Port   the IDE Port number

  @return  the one-byte data read from IDE port
**/
UINT8
IDEReadPortB (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port
  )
{
  UINT8 Data;

  Data = 0;
  //
  // perform 1-byte data read from register
  //
  PciIo->Io.Read (
              PciIo,
              EfiPciIoWidthUint8,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );
  return Data;
}
/**
  Reads multiple words of data from the IDE data port.
  Call the IO abstraction once to do the complete read,
  not one word at a time

  @param  PciIo Pointer to the EFI_PCI_IO instance
  @param  Port IO port to read
  @param  Count No. of UINT16's to read
  @param  Buffer Pointer to the data buffer for read

**/
VOID
IDEReadPortWMultiple (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port,
  IN  UINTN                 Count,
  OUT VOID                  *Buffer
  )
{
  UINT16  *AlignedBuffer;
  UINT16  *WorkingBuffer;
  UINTN   Size;

  //
  // Prepare an 16-bit aligned working buffer. CpuIo will return failure and
  // not perform actual I/O operations if buffer pointer passed in is not at
  // natural boundary. The "Buffer" argument is passed in by user and may not
  // at 16-bit natural boundary.
  //
  Size = sizeof (UINT16) * Count;

  gBS->AllocatePool (
        EfiBootServicesData,
        Size + 1,
        (VOID**)&WorkingBuffer
        );

  AlignedBuffer = (UINT16 *) ((UINTN)(((UINTN) WorkingBuffer + 0x1) & (~0x1)));

  //
  // Perform UINT16 data read from FIFO
  //
  PciIo->Io.Read (
              PciIo,
              EfiPciIoWidthFifoUint16,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              Count,
              (UINT16*)AlignedBuffer
              );

  //
  // Copy data to user buffer
  //
  CopyMem (Buffer, (UINT16*)AlignedBuffer, Size);
  gBS->FreePool (WorkingBuffer);
}

/**
  write a 1-byte data to a specific IDE port.

  @param  PciIo  PCI IO protocol instance
  @param  Port   The IDE port to be writen
  @param  Data   The data to write to the port
**/
VOID
IDEWritePortB (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port,
  IN  UINT8                 Data
  )
{
  //
  // perform 1-byte data write to register
  //
  PciIo->Io.Write (
              PciIo,
              EfiPciIoWidthUint8,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );

}

/**
  write a 1-word data to a specific IDE port.

  @param  PciIo  PCI IO protocol instance
  @param  Port   The IDE port to be writen
  @param  Data   The data to write to the port
**/
VOID
IDEWritePortW (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port,
  IN  UINT16                Data
  )
{
  //
  // perform 1-word data write to register
  //
  PciIo->Io.Write (
              PciIo,
              EfiPciIoWidthUint16,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              1,
              &Data
              );
}

/**
  Write multiple words of data to the IDE data port.
  Call the IO abstraction once to do the complete read,
  not one word at a time

  @param  PciIo Pointer to the EFI_PCI_IO instance
  @param  Port IO port to read
  @param  Count No. of UINT16's to read
  @param  Buffer Pointer to the data buffer for read

**/
VOID
IDEWritePortWMultiple (
  IN  EFI_PCI_IO_PROTOCOL   *PciIo,
  IN  UINT16                Port,
  IN  UINTN                 Count,
  IN  VOID                  *Buffer
  )
{
  UINT16  *AlignedBuffer;
  UINT32  *WorkingBuffer;
  UINTN   Size;

  //
  // Prepare an 16-bit aligned working buffer. CpuIo will return failure and
  // not perform actual I/O operations if buffer pointer passed in is not at
  // natural boundary. The "Buffer" argument is passed in by user and may not
  // at 16-bit natural boundary.
  //
  Size = sizeof (UINT16) * Count;

  gBS->AllocatePool (
        EfiBootServicesData,
        Size + 1,
        (VOID **) &WorkingBuffer
        );

  AlignedBuffer = (UINT16 *) ((UINTN)(((UINTN) WorkingBuffer + 0x1) & (~0x1)));

  //
  // Copy data from user buffer to working buffer
  //
  CopyMem ((UINT16 *) AlignedBuffer, Buffer, Size);

  //
  // perform UINT16 data write to the FIFO
  //
  PciIo->Io.Write (
              PciIo,
              EfiPciIoWidthFifoUint16,
              EFI_PCI_IO_PASS_THROUGH_BAR,
              (UINT64) Port,
              Count,
              (UINT16 *) AlignedBuffer
              );

  gBS->FreePool (WorkingBuffer);
}
/**
  Get IDE IO port registers' base addresses by mode. In 'Compatibility' mode,
  use fixed addresses. In Native-PCI mode, get base addresses from BARs in
  the PCI IDE controller's Configuration Space.

  The steps to get IDE IO port registers' base addresses for each channel
  as follows:

  1. Examine the Programming Interface byte of the Class Code fields in PCI IDE
  controller's Configuration Space to determine the operating mode.

  2. a) In 'Compatibility' mode, use fixed addresses shown in the Table 1 below.
  <pre>
  ___________________________________________
  |           | Command Block | Control Block |
  |  Channel  |   Registers   |   Registers   |
  |___________|_______________|_______________|
  |  Primary  |  1F0h - 1F7h  |  3F6h - 3F7h  |
  |___________|_______________|_______________|
  | Secondary |  170h - 177h  |  376h - 377h  |
  |___________|_______________|_______________|

  Table 1. Compatibility resource mappings
  </pre>

  b) In Native-PCI mode, IDE registers are mapped into IO space using the BARs
  in IDE controller's PCI Configuration Space, shown in the Table 2 below.
  <pre>
  ___________________________________________________
  |           |   Command Block   |   Control Block   |
  |  Channel  |     Registers     |     Registers     |
  |___________|___________________|___________________|
  |  Primary  | BAR at offset 0x10| BAR at offset 0x14|
  |___________|___________________|___________________|
  | Secondary | BAR at offset 0x18| BAR at offset 0x1C|
  |___________|___________________|___________________|

  Table 2. BARs for Register Mapping
  </pre>
  @note Refer to Intel ICH4 datasheet, Control Block Offset: 03F4h for
  primary, 0374h for secondary. So 2 bytes extra offset should be
  added to the base addresses read from BARs.

  For more details, please refer to PCI IDE Controller Specification and Intel
  ICH4 Datasheet.

  @param  PciIo Pointer to the EFI_PCI_IO_PROTOCOL instance
  @param  IdeRegsBaseAddr Pointer to IDE_REGISTERS_BASE_ADDR to
           receive IDE IO port registers' base addresses

  @retval EFI_UNSUPPORTED return this value when the BARs is not IO type
  @retval EFI_SUCCESS     Get the Base address successfully
  @retval other           read the pci configuration data error

**/
EFI_STATUS
GetIdeRegistersBaseAddr (
  IN  EFI_PCI_IO_PROTOCOL         *PciIo,
  OUT IDE_REGISTERS_BASE_ADDR     *IdeRegsBaseAddr
  )
{
  EFI_STATUS  Status;
  PCI_TYPE00  PciData;
  UINT8       PciClass;
  UINT8       PciSubClass;
  BOOLEAN     fIDEAhciEmulation = FALSE;

  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint8,
                        0,
                        sizeof (PciData),
                        &PciData
                        );

  if (EFI_ERROR (Status)) {
    return Status;
  }
  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint8,
                        PCI_CLASSCODE_OFFSET + 2,
                        1,
                        &PciClass
                        );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint8,
                        PCI_CLASSCODE_OFFSET + 1,
                        1,
                        &PciSubClass
                        );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  switch (PciSubClass)
  {
     case PCI_CLASS_MASS_STORAGE_IDE:
        fIDEAhciEmulation = FALSE;
        break;
     case 0x6:
        fIDEAhciEmulation = TRUE;
        break;
     default:
        ASSERT_EFI_ERROR((EFI_UNSUPPORTED));
        return EFI_UNSUPPORTED;
  }
  DEBUG((DEBUG_INFO, "class primary code: %x\n", PciData.Hdr.ClassCode[0] & IDE_PRIMARY_OPERATING_MODE));
  DEBUG((DEBUG_INFO, "class secondary code: %x\n", PciData.Hdr.ClassCode[0] & IDE_SECONDARY_OPERATING_MODE));
  if ((PciData.Hdr.ClassCode[0] & IDE_PRIMARY_OPERATING_MODE) == 0) {
     IdeRegsBaseAddr[IdePrimary].CommandBlockBaseAddr  = fIDEAhciEmulation ? 0x1e8 : 0x1f0;
     IdeRegsBaseAddr[IdePrimary].ControlBlockBaseAddr  = fIDEAhciEmulation ? 0x3e6 :0x3f6;
  } else {
    //
    // The BARs should be of IO type
    //
    if ((PciData.Device.Bar[0] & BIT0) == 0 ||
        (PciData.Device.Bar[1] & BIT0) == 0) {
      return EFI_UNSUPPORTED;
    }

    IdeRegsBaseAddr[IdePrimary].CommandBlockBaseAddr  =
    fIDEAhciEmulation ? 0x1e8 : (UINT16) (PciData.Device.Bar[0] & 0x0000fff8);
    IdeRegsBaseAddr[IdePrimary].ControlBlockBaseAddr  =
    fIDEAhciEmulation ? 0x3e6 :(UINT16) ((PciData.Device.Bar[1] & 0x0000fffc) + 2);
  }
  IdeRegsBaseAddr[IdePrimary].BusMasterBaseAddr     =
  fIDEAhciEmulation ? 0 : (UINT16)((PciData.Device.Bar[4] & 0x0000fff0));

  if ((PciData.Hdr.ClassCode[0] & IDE_SECONDARY_OPERATING_MODE) == 0) {
    IdeRegsBaseAddr[IdeSecondary].CommandBlockBaseAddr  = fIDEAhciEmulation ? 0x168 : 0x170;
    IdeRegsBaseAddr[IdeSecondary].ControlBlockBaseAddr  = fIDEAhciEmulation ? 0x366 : 0x376;
  } else {
    //
    // The BARs should be of IO type
    //
    if ((PciData.Device.Bar[2] & BIT0) == 0 ||
        (PciData.Device.Bar[3] & BIT0) == 0) {
      return EFI_UNSUPPORTED;
    }
    IdeRegsBaseAddr[IdeSecondary].CommandBlockBaseAddr  =
    fIDEAhciEmulation ? 0x168 : (UINT16) (PciData.Device.Bar[2] & 0x0000fff8);
    IdeRegsBaseAddr[IdeSecondary].ControlBlockBaseAddr  =
    fIDEAhciEmulation ? 0x366 : (UINT16) ((PciData.Device.Bar[3] & 0x0000fffc) + 2);
  }
  IdeRegsBaseAddr[IdeSecondary].BusMasterBaseAddr     =
  (UINT16) ((PciData.Device.Bar[fIDEAhciEmulation ? 5 : 4] & 0x0000fff0));

  DEBUG((DEBUG_INFO, "%a:%d CommandBlockBaseAddr:%x, "
                     "ControlBlockBaseAddr:%x, "
                     "BusMasterBaseAddr:%x\n", __FILE__, __LINE__,
                     IdeRegsBaseAddr[IdePrimary].CommandBlockBaseAddr,
                     IdeRegsBaseAddr[IdePrimary].ControlBlockBaseAddr,
                     IdeRegsBaseAddr[IdePrimary].BusMasterBaseAddr));
  DEBUG((DEBUG_INFO, "%a:%d CommandBlockBaseAddr:%x, "
                     "ControlBlockBaseAddr:%x, "
                     "BusMasterBaseAddr:%x\n", __FILE__, __LINE__,
                     IdeRegsBaseAddr[IdeSecondary].CommandBlockBaseAddr,
                     IdeRegsBaseAddr[IdeSecondary].ControlBlockBaseAddr,
                     IdeRegsBaseAddr[IdeSecondary].BusMasterBaseAddr));
  return EFI_SUCCESS;
}

/**
  This function is used to requery IDE resources. The IDE controller will
  probably switch between native and legacy modes during the EFI->CSM->OS
  transfer. We do this everytime before an BlkIo operation to ensure its
  success.

  @param  IdeDev The BLK_IO private data which specifies the IDE device

  @retval EFI_INVALID_PARAMETER return this value when the channel is invalid
  @retval EFI_SUCCESS           reassign the IDE IO resource successfully
  @retval other                 get the IDE current base address effor

**/
EFI_STATUS
ReassignIdeResources (
  IN  IDE_BLK_IO_DEV  *IdeDev
  )
{
  EFI_STATUS              Status;
  IDE_REGISTERS_BASE_ADDR IdeRegsBaseAddr[IdeMaxChannel];
  UINT16                  CommandBlockBaseAddr;
  UINT16                  ControlBlockBaseAddr;

  if (IdeDev->Channel >= IdeMaxChannel) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Requery IDE IO port registers' base addresses in case of the switch of
  // native and legacy modes
  //
  Status = GetIdeRegistersBaseAddr (IdeDev->PciIo, IdeRegsBaseAddr);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ZeroMem (IdeDev->IoPort, sizeof (IDE_BASE_REGISTERS));
  CommandBlockBaseAddr                = IdeRegsBaseAddr[IdeDev->Channel].CommandBlockBaseAddr;
  ControlBlockBaseAddr                = IdeRegsBaseAddr[IdeDev->Channel].ControlBlockBaseAddr;

  IdeDev->IoPort->Data                = CommandBlockBaseAddr;
  (*(UINT16 *) &IdeDev->IoPort->Reg1) = (UINT16) (CommandBlockBaseAddr + 0x01);
  IdeDev->IoPort->SectorCount         = (UINT16) (CommandBlockBaseAddr + 0x02);
  IdeDev->IoPort->SectorNumber        = (UINT16) (CommandBlockBaseAddr + 0x03);
  IdeDev->IoPort->CylinderLsb         = (UINT16) (CommandBlockBaseAddr + 0x04);
  IdeDev->IoPort->CylinderMsb         = (UINT16) (CommandBlockBaseAddr + 0x05);
  IdeDev->IoPort->Head                = (UINT16) (CommandBlockBaseAddr + 0x06);

  (*(UINT16 *) &IdeDev->IoPort->Reg)  = (UINT16) (CommandBlockBaseAddr + 0x07);
  (*(UINT16 *) &IdeDev->IoPort->Alt)  = ControlBlockBaseAddr;
  IdeDev->IoPort->DriveAddress        = (UINT16) (ControlBlockBaseAddr + 0x01);
  IdeDev->IoPort->MasterSlave         = (UINT16) ((IdeDev->Device == IdeMaster) ? 1 : 0);

  IdeDev->IoPort->BusMasterBaseAddr   = IdeRegsBaseAddr[IdeDev->Channel].BusMasterBaseAddr;
  return EFI_SUCCESS;
}

/**
  This function is called by DiscoverIdeDevice(). It is used for detect
  whether the IDE device exists in the specified Channel as the specified
  Device Number.

  There is two IDE channels: one is Primary Channel, the other is
  Secondary Channel.(Channel is the logical name for the physical "Cable".)
  Different channel has different register group.

  On each IDE channel, at most two IDE devices attach,
  one is called Device 0 (Master device), the other is called Device 1
  (Slave device). The devices on the same channel co-use the same register
  group, so before sending out a command for a specified device via command
  register, it is a must to select the current device to accept the command
  by set the device number in the Head/Device Register.

  @param IdeDev  pointer to IDE_BLK_IO_DEV data structure, used to record all the
                 information of the IDE device.

  @retval EFI_SUCCESS successfully detects device.

  @retval other       any failure during detection process will return this value.

**/
EFI_STATUS
DetectIDEController (
  IN  IDE_BLK_IO_DEV  *IdeDev
  )
{
  EFI_STATUS  Status;
  UINT8       SectorCountReg;
  UINT8       LBALowReg;
  UINT8       LBAMidReg;
  UINT8       LBAHighReg;
  UINT8       InitStatusReg;
  UINT8       StatusReg;

  //
  // Select slave device
  //
  IDEWritePortB (
    IdeDev->PciIo,
    IdeDev->IoPort->Head,
    (UINT8) ((1 << 4) | 0xe0)
    );
  gBS->Stall (100);

  //
  // Save the init slave status register
  //
  InitStatusReg = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status);

  //
  // Select Master back
  //
  IDEWritePortB (
    IdeDev->PciIo,
    IdeDev->IoPort->Head,
    (UINT8) ((0 << 4) | 0xe0)
    );
  gBS->Stall (100);

  //
  // Send ATA Device Execut Diagnostic command.
  // This command should work no matter DRDY is ready or not
  //
  IDEWritePortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Command, 0x90);

  Status    = WaitForBSYClear (IdeDev, 3500);
  if (EFI_ERROR (Status)) {
    DEBUG((EFI_D_ERROR, "New detecting method: Send Execute Diagnostic Command: WaitForBSYClear: Status: %d\n", Status));
    return Status;
  }
  //
  // Read device signature
  //
  //
  // Select Master
  //
  IDEWritePortB (
    IdeDev->PciIo,
    IdeDev->IoPort->Head,
    (UINT8) ((0 << 4) | 0xe0)
    );
  gBS->Stall (100);
  SectorCountReg = IDEReadPortB (
                     IdeDev->PciIo,
                     IdeDev->IoPort->SectorCount
                     );
  LBALowReg      = IDEReadPortB (
                     IdeDev->PciIo,
                     IdeDev->IoPort->SectorNumber
                     );
  LBAMidReg      = IDEReadPortB (
                     IdeDev->PciIo,
                     IdeDev->IoPort->CylinderLsb
                     );
  LBAHighReg     = IDEReadPortB (
                     IdeDev->PciIo,
                     IdeDev->IoPort->CylinderMsb
                     );
  if ((SectorCountReg == 0x1) &&
      (LBALowReg      == 0x1) &&
      (LBAMidReg      == 0x0) &&
      (LBAHighReg     == 0x0)) {
    MasterDeviceExist = TRUE;
    MasterDeviceType  = ATA_DEVICE_TYPE;
  } else {
    if ((LBAMidReg      == 0x14) &&
        (LBAHighReg     == 0xeb)) {
      MasterDeviceExist = TRUE;
      MasterDeviceType  = ATAPI_DEVICE_TYPE;
    }
  }

  //
  // For some Hard Drive, it takes some time to get
  // the right signature when operating in single slave mode.
  // We stall 20ms to work around this.
  //
  if (!MasterDeviceExist) {
    gBS->Stall (20000);
  }

  //
  // Select Slave
  //
  IDEWritePortB (
    IdeDev->PciIo,
    IdeDev->IoPort->Head,
    (UINT8) ((1 << 4) | 0xe0)
    );
  gBS->Stall (100);
  SectorCountReg = IDEReadPortB (
                     IdeDev->PciIo,
                     IdeDev->IoPort->SectorCount
                     );
  LBALowReg  = IDEReadPortB (
                 IdeDev->PciIo,
                 IdeDev->IoPort->SectorNumber
                 );
  LBAMidReg  = IDEReadPortB (
                 IdeDev->PciIo,
                 IdeDev->IoPort->CylinderLsb
                 );
  LBAHighReg = IDEReadPortB (
                 IdeDev->PciIo,
                 IdeDev->IoPort->CylinderMsb
                 );
  StatusReg  = IDEReadPortB (
                 IdeDev->PciIo,
                 IdeDev->IoPort->Reg.Status
                 );
  if ((SectorCountReg == 0x1) &&
      (LBALowReg      == 0x1) &&
      (LBAMidReg      == 0x0) &&
      (LBAHighReg     == 0x0)) {
    SlaveDeviceExist = TRUE;
    SlaveDeviceType  = ATA_DEVICE_TYPE;
  } else {
    if ((LBAMidReg     == 0x14) &&
        (LBAHighReg    == 0xeb)) {
      SlaveDeviceExist = TRUE;
      SlaveDeviceType  = ATAPI_DEVICE_TYPE;
    }
  }

  //
  // When single master is plugged, slave device
  // will be wrongly detected. Here's the workaround
  // for ATA devices by detecting DRY bit in status
  // register.
  // NOTE: This workaround doesn't apply to ATAPI.
  //
  if (MasterDeviceExist && SlaveDeviceExist &&
      (StatusReg & ATA_STSREG_DRDY) == 0               &&
      (InitStatusReg & ATA_STSREG_DRDY) == 0           &&
      MasterDeviceType == SlaveDeviceType   &&
      SlaveDeviceType != ATAPI_DEVICE_TYPE) {
    SlaveDeviceExist = FALSE;
  }

  //
  // Indicate this channel has been detected
  //
  ChannelDeviceDetected = TRUE;
  return EFI_SUCCESS;
}
/**
  Detect if there is disk attached to this port

  @param  IdeDev The BLK_IO private data which specifies the IDE device.

  @retval EFI_NOT_FOUND   The device or channel is not found
  @retval EFI_SUCCESS     The device is found

**/
EFI_STATUS
DiscoverIdeDevice (
  IN IDE_BLK_IO_DEV *IdeDev
  )
{
  EFI_STATUS  Status;
  EFI_STATUS  LongPhyStatus;

  //
  // If a channel has not been checked, check it now. Then set it to "checked" state
  // After this step, all devices in this channel have been checked.
  //
  if (!ChannelDeviceDetected) {
    Status = DetectIDEController (IdeDev);
    if (EFI_ERROR (Status)) {
      return EFI_NOT_FOUND;
    }
  }

  Status = EFI_NOT_FOUND;

  //
  // Device exists. test if it is an ATA device.
  // Prefer the result from DetectIDEController,
  // if failed, try another device type to handle
  // devices that not follow the spec.
  //
  if ((IdeDev->Device == IdeMaster) && (MasterDeviceExist)) {
    if (MasterDeviceType == ATA_DEVICE_TYPE) {
      Status = ATAIdentify (IdeDev);
      if (EFI_ERROR (Status)) {
        Status = ATAPIIdentify (IdeDev);
        if (!EFI_ERROR (Status)) {
          MasterDeviceType = ATAPI_DEVICE_TYPE;
        }
      }
    } else {
      Status = ATAPIIdentify (IdeDev);
      if (EFI_ERROR (Status)) {
        Status = ATAIdentify (IdeDev);
        if (!EFI_ERROR (Status)) {
          MasterDeviceType = ATA_DEVICE_TYPE;
        }
      }
    }
  }
  if ((IdeDev->Device == IdeSlave) && (SlaveDeviceExist)) {
    if (SlaveDeviceType == ATA_DEVICE_TYPE) {
      Status = ATAIdentify (IdeDev);
      if (EFI_ERROR (Status)) {
        Status = ATAPIIdentify (IdeDev);
        if (!EFI_ERROR (Status)) {
          SlaveDeviceType = ATAPI_DEVICE_TYPE;
        }
      }
    } else {
      Status = ATAPIIdentify (IdeDev);
      if (EFI_ERROR (Status)) {
        Status = ATAIdentify (IdeDev);
        if (!EFI_ERROR (Status)) {
          SlaveDeviceType = ATA_DEVICE_TYPE;
        }
      }
    }
  }
  if (EFI_ERROR (Status)) {
    return EFI_NOT_FOUND;
  }
  //
  // Init Block I/O interface
  //
  LongPhyStatus = AtaEnableLongPhysicalSector (IdeDev);
  if (!EFI_ERROR (LongPhyStatus)) {
    IdeDev->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION2;
  } else {
    IdeDev->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION;
  }
  IdeDev->BlkIo.Reset               = IDEBlkIoReset;
  IdeDev->BlkIo.ReadBlocks          = IDEBlkIoReadBlocks;
  IdeDev->BlkIo.WriteBlocks         = IDEBlkIoWriteBlocks;
  IdeDev->BlkIo.FlushBlocks         = IDEBlkIoFlushBlocks;

  IdeDev->BlkMedia.LogicalPartition = FALSE;
  IdeDev->BlkMedia.WriteCaching     = FALSE;

  //
  // Init Disk Info interface
  //
  gBS->CopyMem (&IdeDev->DiskInfo.Interface, &gEfiDiskInfoIdeInterfaceGuid, sizeof (EFI_GUID));
  IdeDev->DiskInfo.Inquiry    = IDEDiskInfoInquiry;
  IdeDev->DiskInfo.Identify   = IDEDiskInfoIdentify;
  IdeDev->DiskInfo.SenseData  = IDEDiskInfoSenseData;
  IdeDev->DiskInfo.WhichIde   = IDEDiskInfoWhichIde;

  return EFI_SUCCESS;
}

/**
  This interface is used to initialize all state data related to the detection of one
  channel.
**/
VOID
InitializeIDEChannelData (
  VOID
  )
{
  ChannelDeviceDetected = FALSE;
  MasterDeviceExist = FALSE;
  MasterDeviceType  = 0xff;
  SlaveDeviceExist  = FALSE;
  SlaveDeviceType   = 0xff;
}
/**
  This function is used to poll for the DRQ bit clear in the Status
  Register. DRQ is cleared when the device is finished transferring data.
  So this function is called after data transfer is finished.

  @param IdeDev                 pointer pointing to IDE_BLK_IO_DEV data structure, used
                                to record all the information of the IDE device.
  @param TimeoutInMilliSeconds  used to designate the timeout for the DRQ clear.

  @retval EFI_SUCCESS           DRQ bit clear within the time out.

  @retval EFI_TIMEOUT           DRQ bit not clear within the time out.

  @note
  Read Status Register will clear interrupt status.

**/
EFI_STATUS
DRQClear (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           TimeoutInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   StatusRegister;
  UINT8   ErrorRegister;

  Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {

    StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status);

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

    if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {

      ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error);
      if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

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

    Delay--;

  } while (Delay > 0);

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

  return EFI_SUCCESS;
}
/**
  This function is used to poll for the DRQ bit clear in the Alternate
  Status Register. DRQ is cleared when the device is finished
  transferring data. So this function is called after data transfer
  is finished.

  @param IdeDev                pointer pointing to IDE_BLK_IO_DEV data structure, used
                               to record all the information of the IDE device.

  @param TimeoutInMilliSeconds used to designate the timeout for the DRQ clear.

  @retval EFI_SUCCESS          DRQ bit clear within the time out.

  @retval EFI_TIMEOUT          DRQ bit not clear within the time out.
  @note   Read Alternate Status Register will not clear interrupt status.

**/
EFI_STATUS
DRQClear2 (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           TimeoutInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   AltRegister;
  UINT8   ErrorRegister;

  Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {

    AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus);

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

    if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {

      ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error);
      if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

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

    Delay--;

  } while (Delay > 0);

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

  return EFI_SUCCESS;
}

/**
  This function is used to poll for the DRQ bit set in the
  Status Register.
  DRQ is set when the device is ready to transfer data. So this function
  is called after the command is sent to the device and before required
  data is transferred.

  @param IdeDev                pointer pointing to IDE_BLK_IO_DEV data structure,used to
                               record all the information of the IDE device.
  @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready.

  @retval EFI_SUCCESS          DRQ bit set within the time out.
  @retval EFI_TIMEOUT          DRQ bit not set within the time out.
  @retval EFI_ABORTED          DRQ bit not set caused by the command abort.

  @note  Read Status Register will clear interrupt status.

**/
EFI_STATUS
DRQReady (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           TimeoutInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   StatusRegister;
  UINT8   ErrorRegister;

  Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {
    //
    //  read Status Register will clear interrupt
    //
    StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status);

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

    if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {

      ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error);
      if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

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

    Delay--;
  } while (Delay > 0);

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

  return EFI_SUCCESS;
}
/**
  This function is used to poll for the DRQ bit set in the Alternate Status Register.
  DRQ is set when the device is ready to transfer data. So this function is called after
  the command is sent to the device and before required data is transferred.

  @param IdeDev                pointer pointing to IDE_BLK_IO_DEV data structure, used to
                               record all the information of the IDE device.

  @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready.

  @retval EFI_SUCCESS           DRQ bit set within the time out.
  @retval EFI_TIMEOUT           DRQ bit not set within the time out.
  @retval EFI_ABORTED           DRQ bit not set caused by the command abort.
  @note  Read Alternate Status Register will not clear interrupt status.

**/
EFI_STATUS
DRQReady2 (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           TimeoutInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   AltRegister;
  UINT8   ErrorRegister;

  Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);

  do {
    //
    //  Read Alternate Status Register will not clear interrupt status
    //
    AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus);
    //
    // BSY == 0 , DRQ == 1
    //
    if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_DRQ)) == ATA_STSREG_DRQ) {
      break;
    }

    if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {

      ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error);
      if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

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

    Delay--;
  } while (Delay > 0);

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

  return EFI_SUCCESS;
}

/**
  This function is used to poll for the BSY bit clear in the Status Register. BSY
  is clear when the device is not busy. Every command must be sent after device is not busy.

  @param IdeDev                pointer pointing to IDE_BLK_IO_DEV data structure, used
                               to record all the information of the IDE device.
  @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready.

  @retval EFI_SUCCESS          BSY bit clear within the time out.
  @retval EFI_TIMEOUT          BSY bit not clear within the time out.

  @note Read Status Register will clear interrupt status.
**/
EFI_STATUS
WaitForBSYClear (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           TimeoutInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   StatusRegister;

  Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {

    StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status);
    if ((StatusRegister & ATA_STSREG_BSY) == 0x00) {
      break;
    }

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

    Delay--;

  } while (Delay > 0);

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

  return EFI_SUCCESS;
}
/**
  This function is used to poll for the BSY bit clear in the Alternate Status Register.
  BSY is clear when the device is not busy. Every command must be sent after device is
  not busy.

  @param IdeDev               pointer pointing to IDE_BLK_IO_DEV data structure, used to record
                              all the information of the IDE device.
  @param TimeoutInMilliSeconds used to designate the timeout for the DRQ ready.

  @retval EFI_SUCCESS         BSY bit clear within the time out.
  @retval EFI_TIMEOUT         BSY bit not clear within the time out.
  @note   Read Alternate Status Register will not clear interrupt status.

**/
EFI_STATUS
WaitForBSYClear2 (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           TimeoutInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   AltRegister;

  Delay = (UINT32) (((TimeoutInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {
    AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus);
    if ((AltRegister & ATA_STSREG_BSY) == 0x00) {
      break;
    }

    gBS->Stall (30);

    Delay--;

  } while (Delay > 0);

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

  return EFI_SUCCESS;
}
/**
  This function is used to poll for the DRDY bit set in the Status Register. DRDY
  bit is set when the device is ready to accept command. Most ATA commands must be
  sent after DRDY set except the ATAPI Packet Command.

  @param IdeDev               pointer pointing to IDE_BLK_IO_DEV data structure, used
                              to record all the information of the IDE device.
  @param DelayInMilliSeconds  used to designate the timeout for the DRQ ready.

  @retval EFI_SUCCESS         DRDY bit set within the time out.
  @retval EFI_TIMEOUT         DRDY bit not set within the time out.

  @note  Read Status Register will clear interrupt status.
**/
EFI_STATUS
DRDYReady (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           DelayInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   StatusRegister;
  UINT8   ErrorRegister;

  Delay = (UINT32) (((DelayInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {
    StatusRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg.Status);
    //
    //  BSY == 0 , DRDY == 1
    //
    if ((StatusRegister & (ATA_STSREG_DRDY | ATA_STSREG_BSY)) == ATA_STSREG_DRDY) {
      break;
    }

    if ((StatusRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {

      ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error);
      if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    gBS->Stall (30);

    Delay--;
  } while (Delay > 0);

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

  return EFI_SUCCESS;
}
/**
  This function is used to poll for the DRDY bit set in the Alternate Status Register.
  DRDY bit is set when the device is ready to accept command. Most ATA commands must
  be sent after DRDY set except the ATAPI Packet Command.

  @param IdeDev              pointer pointing to IDE_BLK_IO_DEV data structure, used
                             to record all the information of the IDE device.
  @param DelayInMilliSeconds used to designate the timeout for the DRQ ready.

  @retval EFI_SUCCESS      DRDY bit set within the time out.
  @retval EFI_TIMEOUT      DRDY bit not set within the time out.

  @note  Read Alternate Status Register will clear interrupt status.

**/
EFI_STATUS
DRDYReady2 (
  IN  IDE_BLK_IO_DEV  *IdeDev,
  IN  UINTN           DelayInMilliSeconds
  )
{
  UINT32  Delay;
  UINT8   AltRegister;
  UINT8   ErrorRegister;

  Delay = (UINT32) (((DelayInMilliSeconds * STALL_1_MILLI_SECOND) / 30) + 1);
  do {
    AltRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Alt.AltStatus);
    //
    //  BSY == 0 , DRDY == 1
    //
    if ((AltRegister & (ATA_STSREG_DRDY | ATA_STSREG_BSY)) == ATA_STSREG_DRDY) {
      break;
    }

    if ((AltRegister & (ATA_STSREG_BSY | ATA_STSREG_ERR)) == ATA_STSREG_ERR) {

      ErrorRegister = IDEReadPortB (IdeDev->PciIo, IdeDev->IoPort->Reg1.Error);
      if ((ErrorRegister & ATA_ERRREG_ABRT) == ATA_ERRREG_ABRT) {
        return EFI_ABORTED;
      }
    }

    gBS->Stall (30);

    Delay--;
  } while (Delay > 0);

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

  return EFI_SUCCESS;
}
/**
  Release resources of an IDE device before stopping it.

  @param IdeBlkIoDevice  Standard IDE device private data structure

**/
VOID
ReleaseIdeResources (
  IN  IDE_BLK_IO_DEV  *IdeBlkIoDevice
  )
{
  if (IdeBlkIoDevice == NULL) {
    return ;
  }

  //
  // Release all the resources occupied by the IDE_BLK_IO_DEV
  //

  if (IdeBlkIoDevice->SenseData != NULL) {
    gBS->FreePool (IdeBlkIoDevice->SenseData);
    IdeBlkIoDevice->SenseData = NULL;
  }

  if (IdeBlkIoDevice->Cache != NULL) {
    gBS->FreePool (IdeBlkIoDevice->Cache);
    IdeBlkIoDevice->Cache = NULL;
  }

  if (IdeBlkIoDevice->IdData != NULL) {
    gBS->FreePool (IdeBlkIoDevice->IdData);
    IdeBlkIoDevice->IdData = NULL;
  }

  if (IdeBlkIoDevice->InquiryData != NULL) {
    gBS->FreePool (IdeBlkIoDevice->InquiryData);
    IdeBlkIoDevice->InquiryData = NULL;
  }

  if (IdeBlkIoDevice->ControllerNameTable != NULL) {
    FreeUnicodeStringTable (IdeBlkIoDevice->ControllerNameTable);
    IdeBlkIoDevice->ControllerNameTable = NULL;
  }

  if (IdeBlkIoDevice->IoPort != NULL) {
    gBS->FreePool (IdeBlkIoDevice->IoPort);
  }

  if (IdeBlkIoDevice->DevicePath != NULL) {
    gBS->FreePool (IdeBlkIoDevice->DevicePath);
  }

  if (IdeBlkIoDevice->ExitBootServiceEvent != NULL) {
    gBS->CloseEvent (IdeBlkIoDevice->ExitBootServiceEvent);
    IdeBlkIoDevice->ExitBootServiceEvent = NULL;
  }

  gBS->FreePool (IdeBlkIoDevice);
  IdeBlkIoDevice = NULL;

  return ;
}
/**
  Set the calculated Best transfer mode to a detected device.

  @param IdeDev       Standard IDE device private data structure
  @param TransferMode The device transfer mode to be set
  @return Set transfer mode Command execute status.

**/
EFI_STATUS
SetDeviceTransferMode (
  IN IDE_BLK_IO_DEV       *IdeDev,
  IN ATA_TRANSFER_MODE    *TransferMode
  )
{
  EFI_STATUS  Status;
  UINT8       DeviceSelect;
  UINT8       SectorCount;

  DeviceSelect  = 0;
  DeviceSelect  = (UINT8) ((IdeDev->Device) << 4);
  SectorCount   = *((UINT8 *) TransferMode);

  //
  // Send SET FEATURE command (sub command 0x03) to set pio mode.
  //
  Status = AtaNonDataCommandIn (
            IdeDev,
            ATA_CMD_SET_FEATURES,
            DeviceSelect,
            0x03,
            SectorCount,
            0,
            0,
            0
            );

  return Status;
}
/**
  Set drive parameters for devices not support PACKETS command.

  @param IdeDev          Standard IDE device private data structure
  @param DriveParameters The device parameters to be set into the disk
  @return SetParameters Command execute status.

**/
EFI_STATUS
SetDriveParameters (
  IN IDE_BLK_IO_DEV       *IdeDev,
  IN ATA_DRIVE_PARMS      *DriveParameters
  )
{
  EFI_STATUS  Status;
  UINT8       DeviceSelect;

  DeviceSelect  = 0;
  DeviceSelect  = (UINT8) ((IdeDev->Device) << 4);

  //
  // Send Init drive parameters
  //
  Status = AtaNonDataCommandIn (
            IdeDev,
            ATA_CMD_INIT_DRIVE_PARAM,
            (UINT8) (DeviceSelect + DriveParameters->Heads),
            0,
            DriveParameters->Sector,
            0,
            0,
            0
            );

  //
  // Send Set Multiple parameters
  //
  Status = AtaNonDataCommandIn (
            IdeDev,
            ATA_CMD_SET_MULTIPLE_MODE,
            DeviceSelect,
            0,
            DriveParameters->MultipleSector,
            0,
            0,
            0
            );
  return Status;
}

/**
  Enable Interrupt on IDE controller.

  @param  IdeDev   Standard IDE device private data structure

  @retval  EFI_SUCCESS Enable Interrupt successfully
**/
EFI_STATUS
EnableInterrupt (
  IN IDE_BLK_IO_DEV       *IdeDev
  )
{
  UINT8 DeviceControl;

  //
  // Enable interrupt for DMA operation
  //
  DeviceControl = 0;
  IDEWritePortB (IdeDev->PciIo, IdeDev->IoPort->Alt.DeviceControl, DeviceControl);

  return EFI_SUCCESS;
}
