Newer
Older
AMI-Aptio-BIOS-Reversed / AmiModulePkg / AHCI / Ahci / Ahci.c
@Ajax Dong Ajax Dong 2 days ago 95 KB Restructure the repo
/** @file
  AHCI (Advanced Host Controller Interface) Driver for SATA controllers.

  This module implements the UEFI AHCI driver that manages SATA/AHCI host
  controllers. It provides Block I/O and Block I/O 2 protocol interfaces
  for mass storage devices connected via SATA.

  Source: AmiModulePkg/AHCI/AhciBus.c
  Build: DEBUG_VS2015 X64 (HR6N0XMLK platform)

  Implements:
    - EFI_DRIVER_BINDING_PROTOCOL
    - EFI_COMPONENT_NAME2_PROTOCOL
    - EFI_BLOCK_IO_PROTOCOL (per detected device)
    - EFI_BLOCK_IO2_PROTOCOL (per detected device)
    - EFI_ATA_PASS_THRU_PROTOCOL

  Copyright (c) 2019, AMI Corporation. All rights reserved.
**/

#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/TimerLib.h>
#include <Protocol/PciIo.h>
#include <Protocol/IdeControllerInit.h>
#include <Protocol/BlockIo.h>
#include <Protocol/BlockIo2.h>
#include <Protocol/AtaPassThru.h>
#include <Protocol/DevicePath.h>
#include <Protocol/ComponentName2.h>
#include <Protocol/DebugPort.h>
#include "Ahci.h"

//
// Global system table pointers
//
EFI_HANDLE          gImageHandle = NULL;              // +0x8DE8
EFI_SYSTEM_TABLE    *gST        = NULL;               // +0x8DD8
EFI_BOOT_SERVICES   *gBS        = NULL;               // +0x8DE0
EFI_RUNTIME_SERVICES *gRT       = NULL;               // +0x8DF0

//
// Cached boot services table pointer
//
EFI_BOOT_SERVICES   *gBS_Cached = NULL;               // +0x8E08
EFI_SYSTEM_TABLE    *gST_Cached = NULL;               // +0x8E18
EFI_RUNTIME_SERVICES *gRT_Cached = NULL;              // +0x8E10

//
// Driver-specific globals
//
EFI_HANDLE           gControllerHandle = NULL;        // +0x8CE0
UINT64               gAhciControllerBase = 0;         // +0x8E60
UINT64               gAhciControllerFlags = 0;        // +0x8E68
AHCI_CONTROLLER      *gAhciController = NULL;         // +0x8DA8
UINT64               gAhciBarInfo       = 0;
VOID                 *gAhciDebugProtocol = NULL;      // +0x8DF8
UINT64               gAhciSpareVar      = 0;          // +0x8E00
UINT8                gAhciDebugFlags[8] = {0};        // +0x8E20-0x8E28
UINT64               gAhciControllerRegs = 0;         // +0x8E88

//
// HOB list pointer
//
VOID                 *gHobList = NULL;                // +0x8E00

//
// Forward declarations for EFI_BLOCK_IO_PROTOCOL interface
//
EFI_BLOCK_IO_PROTOCOL gBlockIoTemplate = {
  EFI_BLOCK_IO_PROTOCOL_REVISION3,
  NULL,                       // Media - filled per-port
  AhciBlockIoFlush,           // Reset not used
  AhciBlockIoRead,
  AhciBlockIoWrite,
  AhciBlockIoFlush
};

EFI_BLOCK_IO2_PROTOCOL gBlockIo2Template = {
  NULL,                       // Media - filled per-port
  AhciBlockIo2Read,
  AhciBlockIo2Write,
  AhciBlockIoFlush
};

//
// EFI_DRIVER_BINDING_PROTOCOL instance
//
EFI_DRIVER_BINDING_PROTOCOL gAhciDriverBinding = {
  AhciDriverBindingSupported,
  AhciDriverBindingStart,
  AhciDriverBindingStop,
  0x10,                       // Version
  NULL,                       // ImageHandle
  NULL                        // DriverBindingHandle
};

//
// EFI_COMPONENT_NAME2_PROTOCOL instance
//
EFI_COMPONENT_NAME2_PROTOCOL gAhciComponentName2 = {
  AhciGetDriverName,
  AhciGetComponentName,
  "en-US"
};

//
// EFI_ATA_PASS_THRU_PROTOCOL instance (stub - minimal implementation)
//
EFI_ATA_PASS_THRU_PROTOCOL gAhciAtaPassThru = {
  0,
  NULL, NULL, NULL, NULL, NULL
};

//
// Driver name strings
//
CHAR16 *gAhciDriverName[] = {
  L"AMI AHCI Bus Driver"
};

/**
  AHCI Driver Entry Point.

  Entry point for the AHCI driver. Initializes global state, retrieves the
  HOB list, and installs the driver binding and component name protocols.

  @param[in] ImageHandle  The firmware allocated handle for the EFI image.
  @param[in] SystemTable  A pointer to the EFI System Table.

  @retval EFI_SUCCESS           The driver was successfully initialized.
  @retval EFI_ALREADY_STARTED   The driver is already started.
  @retval others                Failure.
**/
EFI_STATUS
EFIAPI
AhciDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_BOOT_SERVICES    *BootServices;
  EFI_STATUS           Status;

  //
  // Save global system table pointers
  //
  gImageHandle = ImageHandle;
  gST          = SystemTable;
  gBS          = SystemTable->BootServices;
  gRT          = SystemTable->RuntimeServices;

  //
  // Cache protocol pointers for faster access
  //
  gBS_Cached = gBS;
  gST_Cached = gST;
  gRT_Cached = gRT;

  //
  // Get HOB list for platform configuration
  //
  AhciGetHobList (ImageHandle);

  //
  // Clear global controller state
  //
  gAhciControllerBase = 0;
  gAhciControllerFlags = 0;

  //
  // Install driver binding, component name, and ATA pass thru protocols
  // on a new driver handle
  //
  gControllerHandle = NULL;
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &gControllerHandle,
                  &gAhciDriverBinding.DriverBindingProtocolGuid,
                  &gAhciDriverBinding,
                  &gAhciComponentName2.SupportedLanguages,
                  &gAhciComponentName2,
                  NULL
                  );
  return Status;
}

/**
  Tests whether a controller is managed by this driver.

  @param[in] This                Pointer to the EFI_DRIVER_BINDING_PROTOTOCOL.
  @param[in] ControllerHandle    Handle of the controller to test.
  @param[in] RemainingDevicePath Optional parameter used to pick a child.

  @retval EFI_SUCCESS            The controller is supported.
  @retval EFI_UNSUPPORTED        The controller is not supported.
**/
EFI_STATUS
EFIAPI
AhciDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS                    Status;
  EFI_PCI_IO_PROTOCOL           *PciIo;
  EFI_IDE_CONTROLLER_INIT_PROTOCOL *IdeControllerInit;
  EFI_ATA_PASS_THRU_PROTOCOL    *AtaPassThru;
  PCI_TYPE00                    Pci;
  UINT64                        Supports;

  //
  // Check if ATA Pass Thru is already installed (another driver claimed it)
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiAtaPassThruProtocolGuid,
                  (VOID **)&AtaPassThru,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED) {
    return Status;
  }

  //
  // Open PCI I/O protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&PciIo,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Read PCI config space to check for AHCI class code
  //
  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0,
                        sizeof (Pci) / sizeof (UINT32),
                        &Pci
                        );
  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return Status;
  }

  //
  // Check for AHCI (Mass Storage Controller - SATA) class code
  // Class Code = 0x01 (Mass Storage), Subclass = 0x06 (SATA),
  // Programming Interface = 0x01 (AHCI)
  //
  if (Pci.Hdr.ClassCode[2] != PCI_CLASS_MASS_STORAGE ||
      Pci.Hdr.ClassCode[1] != PCI_CLASS_MASS_STORAGE_SATA ||
      Pci.Hdr.ClassCode[0] != PCI_IF_MASS_STORAGE_SATA_AHCI) {
    //
    // If not AHCI, check if the IDE Controller Init protocol is installed
    // (some AHCI controllers present as IDE class)
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiIdeControllerInitProtocolGuid,
                    (VOID **)&IdeControllerInit,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_BY_DRIVER
                    );
    if (EFI_ERROR (Status)) {
      gBS->CloseProtocol (
             ControllerHandle,
             &gEfiPciIoProtocolGuid,
             This->DriverBindingHandle,
             ControllerHandle
             );
      return EFI_UNSUPPORTED;
    }

    //
    // Query the IDE Controller Init to see if any channels support AHCI
    //
    Supports = 0;
    Status = IdeControllerInit->GetChannelInfo (
                                  IdeControllerInit,
                                  0,  // Primary
                                  &Supports
                                  );
    if (!EFI_ERROR (Status) && (Supports & 0x1) != 0) {
      //
      // SATA channel found via IDE controller init protocol
      //
      return EFI_SUCCESS;
    }

    Status = IdeControllerInit->GetChannelInfo (
                                  IdeControllerInit,
                                  1,  // Secondary
                                  &Supports
                                  );
    if (!EFI_ERROR (Status) && (Supports & 0x1) != 0) {
      return EFI_SUCCESS;
    }

    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiIdeControllerInitProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return EFI_UNSUPPORTED;
  }

  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );
  return EFI_SUCCESS;
}

/**
  Starts the AHCI controller and enumerates attached SATA devices.

  @param[in] This                Pointer to the EFI_DRIVER_BINDING_PROTOCOL.
  @param[in] ControllerHandle    Handle of the controller.
  @param[in] RemainingDevicePath Optional parameter used to pick a child.

  @retval EFI_SUCCESS            The driver was successfully started.
  @retval others                 Failure.
**/
EFI_STATUS
EFIAPI
AhciDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS                    Status;
  EFI_PCI_IO_PROTOCOL           *PciIo;
  EFI_IDE_CONTROLLER_INIT_PROTOCOL *IdeControllerInit;
  EFI_DEVICE_PATH_PROTOCOL      *DevicePath;
  AHCI_CONTROLLER               *Controller;
  UINT8                         Port;
  UINT8                         MaxPorts;

  AhciDebugTrace (1, 0x2080001);

  //
  // Open required protocols
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&PciIo,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&DevicePath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
           ControllerHandle,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           ControllerHandle
           );
    return Status;
  }

  //
  // Optionally open IDE Controller Init protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiIdeControllerInitProtocolGuid,
                  (VOID **)&IdeControllerInit,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    IdeControllerInit = NULL;
  }

  //
  // Allocate the AHCI_CONTROLLER private structure
  //
  Controller = (AHCI_CONTROLLER *)gBS->AllocatePool (
                                         EfiBootServicesData,
                                         sizeof (AHCI_CONTROLLER)
                                         );
  if (Controller == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorExit;
  }
  ZeroMem (Controller, sizeof (AHCI_CONTROLLER));

  //
  // Initialize the controller (enable PCI bus master, MMIO, etc.)
  //
  Status = AhciInitController (
             Controller,
             PciIo,
             DevicePath,
             ControllerHandle
             );
  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  //
  // Read the Ports Implemented (PI) register to find active ports
  //
  MaxPorts = 32;
  for (Port = 0; Port < MaxPorts; Port++) {
    if ((Controller->PortsImplemented & (1 << Port)) != 0) {
      //
      // Enumerate this port - first initialize port registers, then create port
      //
      Status = AhciInitializePortRegisters (Controller, Port);
      if (!EFI_ERROR (Status)) {
        //
        // Enumerate device on this port (without port multiplier)
        //
        Status = AhciEnumerateDevice (
                   gImageHandle,
                   ControllerHandle,
                   Controller,
                   &gAhciBarInfo,
                   PciIo,
                   Port,
                   0xFF   // Direct-attached device (no PM)
                   );
        if (EFI_ERROR (Status)) {
          AhciDebugPrint (
            AHCI_DEBUG_ERROR,
            " AHCI: DetectAndConfigureDevice Status %x \n",
            Status
            );
        }
      }
    }
  }

  AhciDebugPrint (AHCI_DEBUG_INFO, "AHCI Driver Detection and Configuration Ends\n");
  return EFI_SUCCESS;

ErrorExit:
  if (Controller != NULL) {
    gBS->FreePool (Controller);
  }
  AhciStopController (Controller);
  return Status;
}

/**
  Stops the AHCI controller and releases resources.

  @param[in] This                Pointer to the EFI_DRIVER_BINDING_PROTOCOL.
  @param[in] ControllerHandle    Handle of the controller to stop.
  @param[in] NumberOfChildren    Number of child handles.
  @param[in] ChildHandleBuffer   Array of child handles.

  @retval EFI_SUCCESS            The driver was stopped.
  @retval others                 Failure.
**/
EFI_STATUS
EFIAPI
AhciDriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer OPTIONAL
  )
{
  AHCI_CONTROLLER          *Controller;
  EFI_STATUS               Status;
  EFI_PCI_IO_PROTOCOL      *PciIo;
  LIST_ENTRY               *Entry;
  AHCI_PORT                *Port;

  //
  // Get our private controller structure
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiAtaPassThruProtocolGuid,
                  (VOID **)&Controller,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Stop each port and free resources
  //
  for (Entry = Controller->PortListHead.ForwardLink;
       Entry != &Controller->PortListHead;
       Entry = Entry->ForwardLink) {
    Port = AHCI_PORT_FROM_LIST_ENTRY (Entry);

    //
    // If the port was configured, perform ATA soft reset
    //
    if (Port->State == AHCI_PORT_STATE_READY &&
        Port->BlockIoMedia != NULL &&
        Port->BlockIoMedia->MediaPresent) {
      AhciAtaSoftReset (Port, NULL);
    }
  }

  //
  // Stop the controller hardware
  //
  AhciStopController (Controller);

  //
  // Close protocols
  //
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiDevicePathProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );

  //
  // Free the controller structure
  //
  if (Controller != NULL) {
    gBS->FreePool (Controller);
  }

  return EFI_SUCCESS;
}

/**
  Component Name 2 driver name retrieval.

  @param[in]  This        Pointer to the protocol instance.
  @param[in]  Language    Language code.
  @param[out] DriverName  Pointer to the driver name string.

  @retval EFI_SUCCESS  The driver name was returned.
**/
EFI_STATUS
EFIAPI
AhciGetDriverName (
  IN  EFI_COMPONENT_NAME2_PROTOCOL  *This,
  IN  CHAR8                         *Language,
  OUT CHAR16                        **DriverName
  )
{
  if (Language == NULL || DriverName == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *DriverName = gAhciDriverName[0];
  return EFI_SUCCESS;
}

/**
  Component Name 2 child name retrieval.

  @param[in]  This              Pointer to the protocol instance.
  @param[in]  ControllerHandle  Handle of the controller.
  @param[in]  ChildHandle       Handle of the child.
  @param[in]  Language          Language code.
  @param[out] ComponentName     Pointer to the component name string.

  @retval EFI_SUCCESS           The name was returned.
  @retval EFI_UNSUPPORTED       The child is not managed by this driver.
**/
EFI_STATUS
EFIAPI
AhciGetComponentName (
  IN  EFI_COMPONENT_NAME2_PROTOCOL  *This,
  IN  EFI_HANDLE                    ControllerHandle,
  IN  EFI_HANDLE                    ChildHandle OPTIONAL,
  IN  CHAR8                         *Language,
  OUT CHAR16                        **ComponentName
  )
{
  EFI_BLOCK_IO_PROTOCOL  *BlockIo;
  EFI_STATUS             Status;

  if (ChildHandle == NULL) {
    *ComponentName = L"AHCI Controller";
    return EFI_SUCCESS;
  }

  Status = gBS->OpenProtocol (
                  ChildHandle,
                  &gEfiBlockIoProtocolGuid,
                  (VOID **)&BlockIo,
                  NULL,
                  NULL,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  *ComponentName = L"AHCI SATA Device";
  return EFI_SUCCESS;
}

// ====================================================================
//  CONTROLLER INITIALIZATION
// ====================================================================

/**
  Initializes the AHCI controller hardware.

  Enables PCI bus mastering, enables MMIO space, allocates the AHCI register
  BAR, reads capability registers, and initializes the port list.

  @param[in] Controller         Pointer to the AHCI_CONTROLLER structure.
  @param[in] PciIo              PCI I/O protocol instance.
  @param[in] DevicePath         Device path of the controller.
  @param[in] ControllerHandle   Handle of the controller.

  @retval EFI_SUCCESS           Controller initialized.
  @retval others                Failure.
**/
EFI_STATUS
AhciInitController (
  IN AHCI_CONTROLLER          *Controller,
  IN EFI_PCI_IO_PROTOCOL      *PciIo,
  IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
  IN EFI_HANDLE               ControllerHandle
  )
{
  EFI_STATUS               Status;
  UINT64                   AhciBarAddr;
  UINT32                   Capabilities;
  UINT32                   PortsImplemented;

  //
  // Get the AHCI BAR (BAR5 typically)
  //
  Status = PciIo->GetBarAttributes (
                    PciIo,
                    EFI_PCI_IO_ATTRIBUTE_MEMORY,
                    &AhciBarAddr,
                    NULL
                    );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Enable PCI bus master and memory space
  //
  Status = PciIo->Attributes (
                    PciIo,
                    EfiPciIoAttributeOperationEnable,
                    EFI_PCI_IO_ATTRIBUTE_BUS_MASTER |
                    EFI_PCI_IO_ATTRIBUTE_MEMORY,
                    NULL
                    );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Map the AHCI register BAR to CPU-accessible MMIO
  //
  Controller->AhciRegBase = AhciBarAddr;

  //
  // Read AHCI capabilities
  //
  Capabilities = MmioRead32 (AhciBarAddr + AHCI_GHC_CAP);
  PortsImplemented = MmioRead32 (AhciBarAddr + AHCI_GHC_PI);

  Controller->Capability = Capabilities;
  Controller->PortsImplemented = PortsImplemented;
  Controller->AhciVersion = MmioRead32 (AhciBarAddr + AHCI_GHC_VS);
  Controller->Capability2 = MmioRead32 (AhciBarAddr + AHCI_GHC_CAP2);

  //
  // Clear global state
  //
  gAhciControllerBase = AhciBarAddr;
  gAhciControllerFlags = (Capabilities >> 20) & 0xF;

  //
  // Initialize the port list
  //
  Controller->PortCount = 0;
  InitializeListHead (&Controller->PortListHead);

  return EFI_SUCCESS;
}

/**
  Stops the AHCI controller hardware.

  @param[in] Controller  Pointer to the AHCI_CONTROLLER structure.
**/
VOID
AhciStopController (
  IN AHCI_CONTROLLER  *Controller
  )
{
  //
  // Clear the global controller base so MMIO accesses fail-safe
  //
  gAhciControllerBase = 0;
}

// ====================================================================
//  PORT MANAGEMENT
// ====================================================================

/**
  Initializes AHCI port registers for the given port.

  Allocates and maps the command list, FIS area, and command table for
  the port. Programs the port's CLB, CLBU, FB, and FBU registers.

  @param[in] Controller  Pointer to the AHCI_CONTROLLER.
  @param[in] Port        Port number (0..31).

  @retval EFI_SUCCESS           Port registers initialized.
  @retval EFI_OUT_OF_RESOURCES  Memory allocation failed.
**/
EFI_STATUS
AhciInitializePortRegisters (
  IN AHCI_CONTROLLER  *Controller,
  IN UINT8            Port
  )
{
  EFI_STATUS               Status;
  UINT64                   PortRegBase;
  UINT64                   MemoryBase;
  UINT64                   MemorySize;

  AhciDebugTrace (1, 0x2080003);

  //
  // Calculate the port register base
  //
  PortRegBase = Controller->AhciRegBase + ((UINT64)Port + 2) * 0x80;

  //
  // Allocate memory for the command list (1K aligned, 1K minimum)
  // and the FIS receive area (256 bytes aligned to 256)
  //
  MemoryBase = AhciAllocateMemory (sizeof (AHCI_CMD_HEADER) * 32 +
                                   sizeof (AHCI_CMD_TABLE) +
                                   512);
  if (MemoryBase == 0) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Program the port registers
  //
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_CLB, (UINT32)(MemoryBase & 0xFFFFFFFF));
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_CLBU, (UINT32)(MemoryBase >> 32));
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_FB, (UINT32)((MemoryBase + 0x100) & 0xFFFFFFFF));
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_FBU, (UINT32)((MemoryBase + 0x100) >> 32));

  //
  // Store the BAR info in the port block
  //
  Controller->PortBarInfo = MemorySize;

  //
  // Clear error registers
  //
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_SERR, 0xFFFFFFFF);
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_IS, 0xFFFFFFFF);

  //
  // Enable FIS receive and start the port
  //
  MmioOr32 (PortRegBase + AHCI_PORT_REG_CMD,
            AHCI_PXCMD_FRE | AHCI_PXCMD_ST);

  //
  // Wait for the port to come ready
  //
  AhciMmioPollReady (
    Controller,
    Port,
    AHCI_PORT_REG_CMD,
    AHCI_PXCMD_FR | AHCI_PXCMD_CR,
    10
    );

  return EFI_SUCCESS;
}

/**
  Allocates contiguous memory for AHCI DMA operations.

  Allocates memory aligned to a 4K boundary for use as AHCI command lists,
  command tables, and FIS receive areas.

  @param[in] Size  Size in bytes to allocate.

  @return Pointer to the allocated memory, or 0 if allocation failed.
**/
UINT64
AhciAllocateMemory (
  IN UINT64  Size
  )
{
  EFI_PHYSICAL_ADDRESS  PhysicalAddress;
  EFI_STATUS            Status;
  UINT64                AlignedSize;

  //
  // Align size to 4K boundary
  //
  AlignedSize = ALIGN_VALUE (Size, SIZE_4KB);

  Status = gBS->AllocatePages (
                  AllocateAnyPages,
                  EfiBootServicesData,
                  EFI_SIZE_TO_PAGES (AlignedSize),
                  &PhysicalAddress
                  );
  if (EFI_ERROR (Status)) {
    return 0;
  }

  //
  // Zero the allocated memory
  //
  ZeroMem ((VOID *)(UINTN)PhysicalAddress, AlignedSize);

  return PhysicalAddress;
}

/**
  Creates a new AHCI_PORT structure and links it into the controller's port list.

  @param[in,out] Controller  The AHCI controller.
  @param[in]     PciIo       PCI I/O protocol.
  @param[in]     Port        SATA port number.
  @param[in]     PmPort      Port multiplier port number (0xFF for direct).

  @retval EFI_SUCCESS           Port created.
  @retval EFI_OUT_OF_RESOURCES  Allocation failed.
**/
EFI_STATUS
AhciCreatePort (
  IN OUT AHCI_CONTROLLER      *Controller,
  IN     EFI_PCI_IO_PROTOCOL  *PciIo,
  IN     UINT8                Port,
  IN     UINT8                PmPort
  )
{
  AHCI_PORT                 *AhciPort;
  UINT64                    PortRegBase;
  EFI_STATUS                Status;

  AhciDebugTrace (1, 0x2080003);

  //
  // Check if this port already exists in the list
  //
  AhciPort = NULL;
  if (!IsListEmpty (&Controller->PortListHead)) {
    LIST_ENTRY *Entry;
    for (Entry = Controller->PortListHead.ForwardLink;
         Entry != &Controller->PortListHead;
         Entry = Entry->ForwardLink) {
      AHCI_PORT *Candidate = AHCI_PORT_FROM_LIST_ENTRY (Entry);
      if (Candidate->Port == Port && Candidate->PmPort == PmPort) {
        AhciPort = Candidate;
        break;
      }
    }
    if (AhciPort != NULL && (AhciPort->State == AHCI_PORT_STATE_CONFIGURED ||
        AhciPort->State == AHCI_PORT_STATE_READY)) {
      return EFI_SUCCESS;    // Already configured
    }
  }

  //
  // Allocate the port structure
  //
  AhciPort = (AHCI_PORT *)gBS->AllocatePool (
                                 EfiBootServicesData,
                                 sizeof (AHCI_PORT)
                                 );
  if (AhciPort == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  ZeroMem (AhciPort, sizeof (AHCI_PORT));

  //
  // Initialize port fields
  //
  AhciPort->Port   = Port;
  AhciPort->PmPort = PmPort;
  AhciPort->State  = AHCI_PORT_STATE_CREATED;

  //
  // Compute port register base
  //
  PortRegBase = Controller->AhciRegBase + ((UINT64)Port + 2) * 0x80;

  //
  // Store AHCI BAR info for this port
  //
  AhciPort->AhciBarBase = PortRegBase;
  AhciPort->AhciBarSize = 0x80;

  //
  // Link back to the controller
  //
  AhciPort->Controller = Controller;

  //
  // Insert into the linked list
  //
  InsertTailList (&Controller->PortListHead, &AhciPort->Link);
  Controller->PortCount++;

  //
  // Read the port signature to determine device type
  //
  AhciPort->PortType = MmioRead32 (PortRegBase + AHCI_PORT_REG_SIG);
  switch (AhciPort->PortType) {
  case AHCI_SIG_ATA:
    AhciPort->MediaType = AHCI_MEDIA_TYPE_HDD;
    break;
  case AHCI_SIG_ATAPI:
    AhciPort->MediaType = AHCI_MEDIA_TYPE_ATAPI;
    break;
  default:
    AhciPort->MediaType = AHCI_MEDIA_TYPE_NONE;
    break;
  }

  return EFI_SUCCESS;
}

// ====================================================================
//  DEVICE ENUMERATION
// ====================================================================

/**
  Enumerates a SATA device on the given port.

  This function is the main enumeration routine. It creates the port,
  identifies the attached device, configures it, sets up block I/O
  media structures, and installs the Block I/O protocol.

  @param[in] ImageHandle       Image handle.
  @param[in] ControllerHandle  Controller handle.
  @param[in] Controller        Pointer to the AHCI_CONTROLLER.
  @param[in] AhciBarInfo       AHCI BAR information.
  @param[in] PciIo             PCI I/O protocol.
  @param[in] Port              SATA port number.
  @param[in] PmPort            Port multiplier port number.

  @retval EFI_SUCCESS  Device enumerated.
  @retval others       Failure.
**/
EFI_STATUS
AhciEnumerateDevice (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_HANDLE           ControllerHandle,
  IN AHCI_CONTROLLER      *Controller,
  IN VOID                 *AhciBarInfo,
  IN EFI_PCI_IO_PROTOCOL  *PciIo,
  IN UINT8                Port,
  IN UINT8                PmPort
  )
{
  AHCI_PORT                *AhciPort;
  EFI_STATUS               Status;
  UINT64                   Capacity;
  AHCI_PORT                *ExistingPort;
  LIST_ENTRY               *Entry;
  UINT16                   PmType;

  //
  // Check if we already have this port and it's good
  //
  ExistingPort = NULL;
  for (Entry = Controller->PortListHead.ForwardLink;
       Entry != &Controller->PortListHead;
       Entry = Entry->ForwardLink) {
    AHCI_PORT *Candidate = AHCI_PORT_FROM_LIST_ENTRY (Entry);
    if (Candidate->Port == Port && Candidate->PmPort == PmPort) {
      ExistingPort = Candidate;
      break;
    }
  }

  if (ExistingPort != NULL && (ExistingPort->State == AHCI_PORT_STATE_CONFIGURED ||
      ExistingPort->State == AHCI_PORT_STATE_READY)) {
    return EFI_SUCCESS;
  }

  //
  // Create the port (or get existing one)
  //
  Status = AhciCreatePort (Controller, PciIo, Port, PmPort);
  if (EFI_ERROR (Status)) {
    goto ErrorExit;
  }

  //
  // Find the port in the list again
  //
  AhciPort = NULL;
  for (Entry = Controller->PortListHead.ForwardLink;
       Entry != &Controller->PortListHead;
       Entry = Entry->ForwardLink) {
    AHCI_PORT *Candidate = AHCI_PORT_FROM_LIST_ENTRY (Entry);
    if (Candidate->Port == Port && Candidate->PmPort == PmPort) {
      AhciPort = Candidate;
      break;
    }
  }

  if (AhciPort == NULL) {
    return EFI_UNSUPPORTED;
  }

  AhciPort->State = AHCI_PORT_STATE_CONFIGURED;

  //
  // If it's a port multiplier, check the PM type
  //
  if (AhciPort->PortType == AHCI_SIG_PM && AhciPort->MediaType == AHCI_MEDIA_TYPE_NONE) {
    AhciSataPhyCommand (AhciPort, 15, 2, &PmType, 0);
    AhciPort->PmType = (UINT8)PmType;
  }

  //
  // Configure the device (soft reset, read capacity, etc.)
  //
  if (AhciPort->MediaType != AHCI_MEDIA_TYPE_NONE) {
    Capacity = 0;
    Status = AhciConfigureDevice (AhciPort, &Capacity);
    if (EFI_ERROR (Status)) {
      AhciDebugTrace (0x80000002, 0x2080005);
      AhciPort->State = AHCI_PORT_STATE_ERROR;
      return Status;
    }

    //
    // Set device features
    //
    Status = AhciSetDeviceFeature (AhciPort, Capacity);
    if (EFI_ERROR (Status)) {
      AhciDebugTrace (0x80000002, 0x2080005);
      AhciPort->State = AHCI_PORT_STATE_ERROR;
      return Status;
    }

    AhciPort->State = AHCI_PORT_STATE_READY;

    //
    // Set port interface power
    //
    AhciSetPortInterfacePower (AhciPort);

    //
    // Initialize the task file for this device
    //
    AhciInitTaskFile (AhciPort, PciIo, NULL, 0);

    //
    // Set up the Block I/O media
    //
    Status = AhciSetupBlockIoMedia (AhciPort);
    if (EFI_ERROR (Status)) {
      AhciPort->State = AHCI_PORT_STATE_ERROR;
      return Status;
    }

    //
    // Install Block I/O protocol on a new child handle
    //
    Status = gBS->InstallMultipleProtocolInterfaces (
                    &AhciPort->BlockIo,
                    &gEfiBlockIoProtocolGuid,
                    AhciPort->BlockIo,
                    &gEfiDevicePathProtocolGuid,
                    &ControllerHandle,
                    NULL
                    );
    if (EFI_ERROR (Status)) {
      AhciDebugPrint (AHCI_DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
      AhciAssert (__FILE__, 949, "!EFI_ERROR (Status)");
    }

    //
    // Install Block I/O 2 protocol
    //
    gBS->OpenProtocol (
           AhciPort->BlockIo,
           &gEfiBlockIo2ProtocolGuid,
           &AhciPort->BlockIo2,
           ImageHandle,
           ControllerHandle,
           EFI_OPEN_PROTOCOL_BY_DRIVER
           );

    //
    // Install Block I/O 2 on child handle
    //
    Status = gBS->InstallProtocolInterface (
                    &AhciPort->BlockIo2,
                    &gEfiBlockIo2ProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    AhciPort->BlockIo2
                    );
    if (EFI_ERROR (Status)) {
      AhciDebugPrint (AHCI_DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
      AhciAssert (__FILE__, 988, "!EFI_ERROR (Status)");
    }
  }

  AhciDebugPrint (
    AHCI_DEBUG_INFO,
    "AHCI: SATA Device type %x detected at Port Number : %x, PM Port Number : %x\n",
    AhciPort->MediaType,
    AhciPort->Port,
    AhciPort->PmPort
    );

  return EFI_SUCCESS;

ErrorExit:
  if (AhciPort != NULL) {
    AhciPort->State = AHCI_PORT_STATE_ERROR;
  }
  return Status;
}

// ====================================================================
//  PORT IDENTIFICATION
// ====================================================================

/**
  Identifies a directly-attached SATA device (no port multiplier).

  Resets the port, waits for it to be ready, programs AHCI mode,
  and identifies the device.

  @param[in] Port         Pointer to the AHCI_PORT.
  @param[in] PciIo        PCI I/O protocol.
  @param[in] PortNumber   SATA port number.
  @param[in] PmPortNumber Port multiplier port (0xFF for direct).

  @retval EFI_SUCCESS  Port identified.
  @retval others       Failure.
**/
EFI_STATUS
AhciIdentifySataPort (
  IN AHCI_PORT               *Port,
  IN EFI_PCI_IO_PROTOCOL     *PciIo,
  IN UINT8                   PortNumber,
  IN UINT8                   PmPortNumber
  )
{
  EFI_STATUS  Status;
  UINT32      SerrValue;

  //
  // Reset the port
  //
  Status = AhciPortReset (
             Port->Controller,
             PciIo,
             PortNumber,
             PmPortNumber,
             0,
             0
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Poll for port ready
  //
  Status = AhciPollPortReady (Port, NULL, 10, 0xFF);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Set AHCI mode (enable FIS, start port)
  //
  Status = AhciSetAhciMode (Port->Controller, PortNumber, Port->AhciBarBase);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Clear port error status
  //
  MmioWrite32 (Port->AhciBarBase + AHCI_PORT_REG_SERR, 0xFFFFFFFF);

  return EFI_SUCCESS;
}

/**
  Identifies a port multiplier-attached device.

  @param[in] Port     Pointer to the AHCI_PORT.
  @param[in] PciIo    PCI I/O protocol.
  @param[in] Port     SATA port number.
  @param[in] PmPort   Port multiplier port number.

  @retval EFI_SUCCESS  Port identified.
  @retval others       Failure.
**/
EFI_STATUS
AhciIdentifyPmPort (
  IN AHCI_PORT               *Port,
  IN EFI_PCI_IO_PROTOCOL     *PciIo,
  IN UINT8                   PortNum,
  IN UINT8                   PmPort
  )
{
  EFI_STATUS  Status;
  UINT16      PhyResult;

  //
  // Send SATA Phy command to identify device on this PM port
  //
  Status = AhciSataPhyCommand (Port, 0, 0, &PhyResult, 0);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Reset the port
  //
  Status = AhciPortReset (
             Port->Controller,
             PciIo,
             PortNum,
             PmPort,
             0,
             0
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Port->PmType = (UINT8)PhyResult;
  return EFI_SUCCESS;
}

// ====================================================================
//  PORT POWER MANAGEMENT
// ====================================================================

/**
  Sets the interface power state for the port (Partial/Slumber).

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Operation succeeded.
**/
EFI_STATUS
AhciSetPortInterfacePower (
  IN AHCI_PORT  *Port
  )
{
  UINT64  PortRegBase;
  UINT32  CmdValue;

  if (Port->MediaType != AHCI_MEDIA_TYPE_HDD) {
    return EFI_SUCCESS;
  }

  PortRegBase = Port->Controller->AhciRegBase + ((UINT64)Port->Port + 2) * 0x80;

  //
  // Enable Aggressive Link Power Management
  //
  CmdValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_CMD);
  CmdValue |= AHCI_PXCMD_ALPE | AHCI_PXCMD_ASP;
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_CMD, CmdValue);

  //
  // Set Partial/Slumber timer
  //
  MmioWrite32 (PortRegBase + 0x44, 0x00010001);  // PxDEVSLP

  return EFI_SUCCESS;
}

// ====================================================================
//  DEVICE CONFIGURATION
// ====================================================================

/**
  Configures the attached SATA device.

  Performs soft reset, sets transfer mode, enables RW DMA setup,
  and reads capacity.

  @param[in] Port      Pointer to the AHCI_PORT.
  @param[out] Capacity  Pointer to receive the device capacity.

  @retval EFI_SUCCESS  Device configured.
  @retval others       Failure.
**/
EFI_STATUS
AhciConfigureDevice (
  IN AHCI_PORT    *Port,
  OUT UINT64      *Capacity
  )
{
  EFI_STATUS  Status;
  UINT8       TaskFile[8];

  //
  // Prepare port for access
  //
  Status = AhciPreparePortAccess (Port);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Perform ATA soft reset
  //
  Status = AhciAtaSoftReset (Port, TaskFile);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Identify device
  //
  if (Port->MediaType == AHCI_MEDIA_TYPE_ATAPI) {
    //
    // ATAPI - use IDENTIFY PACKET DEVICE
    //
    // (packet IDENTIFY is handled through the SATA Phy command path)
    //
    Status = AhciSataPhyCommand (Port, 0xA1, 0, (UINT16 *)TaskFile, 0);
  } else {
    //
    // ATA - send IDENTIFY DEVICE via SATA Phy path
    //
    Status = AhciSataPhyCommand (Port, 0xEC, 0, (UINT16 *)TaskFile, 0);
  }

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

  //
  // Set transfer mode
  //
  Status = AhciSetTransferMode (Port);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Enable RW DMA Setup
  //
  Status = AhciSetFeatureRwDmaSetup (Port);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Read capacity
  //
  Status = AhciReadCapacity (Port);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  Sets ATA transfer mode (PIO, Multiword DMA, or Ultra DMA).

  Uses ATA SET FEATURES command.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Transfer mode set.
**/
EFI_STATUS
AhciSetTransferMode (
  IN AHCI_PORT  *Port
  )
{
  UINT8  TaskFile[49];
  UINT8  TransferMode;

  //
  // Determine best transfer mode from identify data
  //
  if (Port->IdentifyData[201] & 0x20) {
    //
    // Ultra DMA supported - select highest UDMA mode
    //
    TransferMode = ATA_TRANSFER_MODE_UDMA | 0x07;  // UDMA/133
  } else if (Port->IdentifyData[207] & 0x20) {
    //
    // Multiword DMA
    //
    TransferMode = ATA_TRANSFER_MODE_MULTI_DMA | 0x02;
  } else {
    //
    // PIO mode
    //
    TransferMode = ATA_TRANSFER_MODE_PIO_DEFAULT | 0x00;
  }

  //
  // Build SET FEATURES task file
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);
  TaskFile[23] = ATA_CMD_SET_FEATURES;
  TaskFile[12] = ATA_SET_FEATURES_TRANSFER_MODE;
  TaskFile[16] = TransferMode;

  //
  // Execute the command
  //
  return AhciAtaSoftReset (Port, TaskFile);
}

/**
  Enables or Disables RW DMA Setup feature.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Feature set successfully.
**/
EFI_STATUS
AhciSetFeatureRwDmaSetup (
  IN AHCI_PORT  *Port
  )
{
  UINT8  TaskFile[49];
  UINT8  SubCommand;

  //
  // Check if device supports RW DMA Setup
  //
  if ((Port->Controller->Capability & 0x4000000) == 0) {
    return EFI_SUCCESS;
  }

  //
  // Enable RW DMA Setup
  //
  SubCommand = ATA_RW_DMA_SETUP_ENABLE;

  SetMem (TaskFile, sizeof (TaskFile), 0);
  TaskFile[23] = ATA_CMD_SET_FEATURES;
  TaskFile[12] = SubCommand;

  return AhciAtaSoftReset (Port, TaskFile);
}

/**
  Reads the device capacity via READ CAPACITY command.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Capacity read.
**/
EFI_STATUS
AhciReadCapacity (
  IN AHCI_PORT  *Port
  )
{
  UINT8       TaskFile[49];
  EFI_STATUS  Status;

  //
  // Build READ CAPACITY (or READ NATIVE MAX) task file
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);
  TaskFile[23] = 0x25;   // READ DMA EXT

  //
  // Execute the non-data command
  //
  Status = AhciNonDataCommand (Port, (UINT8 *)TaskFile, 0);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Parse the response in the ATA registers
  //
  Port->IdentifyData[154] |= Port->IdentifyData[155] << 16;
  Port->IdentifyData[156] |= Port->IdentifyData[157] << 16;

  return EFI_SUCCESS;
}

// ====================================================================
//  BLOCK I/O INTERFACE
// ====================================================================

/**
  Returns the Block I/O media info for the device.

  @param[in]  This              Pointer to the EFI_BLOCK_IO_PROTOCOL.
  @param[in]  ControllerHandle  Handle of the controller.
  @param[out] MediaId           Pointer to receive the media ID.

  @retval EFI_SUCCESS  Media info returned.
  @retval EFI_NO_MEDIA No media present.
**/
EFI_STATUS
EFIAPI
AhciBlockIoGetMediaInfo (
  IN  EFI_BLOCK_IO_PROTOCOL  *This,
  IN  EFI_HANDLE             ControllerHandle,
  OUT UINT32                 *MediaId
  )
{
  AHCI_PORT  *Port;

  Port = (AHCI_PORT *)This->Media;

  if (Port == NULL || Port->BlockIoMedia == NULL) {
    return EFI_NOT_STARTED;
  }

  if (!Port->BlockIoMedia->MediaPresent) {
    return EFI_NO_MEDIA;
  }

  if (MediaId != NULL) {
    *MediaId = Port->BlockIoMedia->MediaId;
  }

  return EFI_SUCCESS;
}

/**
  Returns the device path for the Block I/O device.

  @param[in]  This              Pointer to the EFI_BLOCK_IO_PROTOCOL.
  @param[in]  ControllerHandle  Handle of the controller.
  @param[out] DevicePath        Pointer to receive the device path.

  @retval EFI_SUCCESS  Device path returned.
**/
EFI_STATUS
EFIAPI
AhciBlockIoGetDevicePath (
  IN  EFI_BLOCK_IO_PROTOCOL  *This,
  IN  EFI_HANDLE             ControllerHandle,
  OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath
  )
{
  AHCI_PORT  *Port;

  Port = (AHCI_PORT *)This->Media;

  if (Port == NULL || DevicePath == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Return a device path node for this port
  //
  *DevicePath = NULL;
  return EFI_SUCCESS;
}

/**
  Flushes buffered writes to the device.

  @param[in] This  Pointer to the EFI_BLOCK_IO_PROTOCOL.

  @retval EFI_SUCCESS  Flush completed.
  @retval EFI_NO_MEDIA No media.
  @retval others       Failure.
**/
EFI_STATUS
EFIAPI
AhciBlockIoFlush (
  IN EFI_BLOCK_IO_PROTOCOL  *This
  )
{
  //
  // For AHCI, writes go directly to device, so flush is a no-op
  // (or issue FLUSH CACHE if needed)
  //
  return EFI_SUCCESS;
}

/**
  Reads blocks from the device.

  @param[in]  This        Pointer to the EFI_BLOCK_IO_PROTOCOL.
  @param[in]  MediaId     Media ID to verify.
  @param[in]  Lba         Starting LBA.
  @param[in]  BufferSize  Size of the buffer in bytes.
  @param[out] Buffer      Pointer to the buffer.

  @retval EFI_SUCCESS  Blocks read.
**/
EFI_STATUS
EFIAPI
AhciBlockIoReadBlocks (
  IN EFI_BLOCK_IO_PROTOCOL  *This,
  IN UINT32                 MediaId,
  IN EFI_LBA                Lba,
  IN UINTN                  BufferSize,
  OUT VOID                  *Buffer
  )
{
  return AhciBlockIoRwDispatch (This, MediaId, Lba, BufferSize, Buffer, FALSE);
}

/**
  Block I/O read dispatch.

  Validates parameters and dispatches to AhciReadLba.

  @param[in]  This        Pointer to the EFI_BLOCK_IO_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  BufferSize  Size in bytes.
  @param[out] Buffer      Buffer to fill.
  @param[in]  IsWrite     TRUE for write, FALSE for read.

  @retval EFI_SUCCESS  Operation completed.
**/
EFI_STATUS
EFIAPI
AhciBlockIoRwDispatch (
  IN EFI_BLOCK_IO_PROTOCOL   *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer,
  IN BOOLEAN                 IsWrite
  )
{
  AHCI_PORT          *Port;
  EFI_BLOCK_IO_MEDIA *Media;
  UINTN              BlockCount;

  Port  = (AHCI_PORT *)This->Media;
  Media = Port->BlockIoMedia;

  //
  // Verify media ID
  //
  if (Media->MediaId != MediaId) {
    return EFI_MEDIA_CHANGED;
  }

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

  //
  // Validate buffer size is block-aligned
  //
  if (BufferSize % Media->BlockSize != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  BlockCount = BufferSize / Media->BlockSize;

  //
  // Check for valid LBA range
  //
  if (Lba + BlockCount > Media->LastBlock + 1) {
    return EFI_INVALID_PARAMETER;
  }

  if (IsWrite) {
    return AhciWriteLba (Port, Lba, BlockCount, Buffer, 0x39, FALSE);
  } else {
    return AhciReadLba (Port, Lba, BlockCount, Buffer, 0x39, FALSE);
  }
}

/**
  Block I/O 2 read/write dispatch.

  @param[in]  This        Pointer to the EFI_BLOCK_IO2_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  Token       Optional token for non-blocking operation.
  @param[in]  BufferSize  Size in bytes.
  @param[out] Buffer      Buffer.
  @param[in]  IsWrite     TRUE for write, FALSE for read.

  @retval EFI_SUCCESS  Operation completed.
**/
EFI_STATUS
EFIAPI
AhciBlockIoRwExDispatch (
  IN EFI_BLOCK_IO2_PROTOCOL  *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN EFI_BLOCK_IO2_TOKEN     *Token,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer,
  IN BOOLEAN                 IsWrite
  )
{
  AHCI_PORT          *Port;
  EFI_BLOCK_IO_MEDIA *Media;
  UINTN              BlockCount;
  EFI_STATUS         Status;

  Port  = (AHCI_PORT *)This->Media;
  Media = Port->BlockIoMedia;

  //
  // Validate parameters
  //
  if (Media->MediaId != MediaId) {
    return EFI_MEDIA_CHANGED;
  }

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

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

  BlockCount = BufferSize / Media->BlockSize;

  if (Lba + BlockCount > Media->LastBlock + 1) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check media status
  //
  Status = AhciDetectMedia (Port);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Perform the DMA operation
  //
  if (IsWrite) {
    Status = AhciDmaCommand (Port, (UINT8 *)&Lba, 0);
  } else {
    Status = AhciDmaCommand (Port, (UINT8 *)&Lba, 0);
  }

  if (EFI_ERROR (Status)) {
    AhciResetPort (Port);
  }

  return Status;
}

/**
  Sets up the Block I/O media structure from ATA identify data.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Media info set up.
**/
EFI_STATUS
AhciSetupBlockIoMedia (
  IN AHCI_PORT  *Port
  )
{
  EFI_BLOCK_IO_MEDIA *Media;
  UINT64             LastBlock;
  UINT32             BlockSize;

  //
  // Check if the device is SSD (word 35 non-zero) or HDD
  //
  if (Port->MediaType != AHCI_MEDIA_TYPE_NONE) {
    //
    // Check for extended sector sizes (512e / 4Kn)
    //
    BlockSize = 512;
    if ((Port->IdentifyData[247] & 0xD000) == 0x5000) {
      //
      // Logical sector size > 512 bytes
      //
      BlockSize = 2 * (Port->IdentifyData[269] | (Port->IdentifyData[271] << 16));
    }

    //
    // Determine last block from identify data
    //
    if ((Port->IdentifyData[201] & 0x400) != 0) {
      //
      // 48-bit LBA supported
      //
      LastBlock = Port->IdentifyData[235] - 1;
    } else {
      LastBlock = (Port->IdentifyData[155] - 1);
    }
  } else {
    return EFI_UNSUPPORTED;
  }

  //
  // Allocate and fill the media structure
  //
  Media = (EFI_BLOCK_IO_MEDIA *)AhciAllocateZeroPool (sizeof (EFI_BLOCK_IO_MEDIA));
  if (Media == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Media->MediaId          = 0;
  Media->MediaPresent     = TRUE;
  Media->LogicalPartition = FALSE;
  Media->ReadOnly         = FALSE;
  Media->WriteCaching     = TRUE;
  Media->BlockSize        = BlockSize;
  Media->IoAlign          = 4;
  Media->LastBlock        = LastBlock;

  Port->BlockIoMedia = Media;

  //
  // Fill the Block I/O protocol instance
  //
  EFI_BLOCK_IO_PROTOCOL *BlockIo;
  BlockIo = (EFI_BLOCK_IO_PROTOCOL *)AhciAllocateZeroPool (sizeof (EFI_BLOCK_IO_PROTOCOL));
  if (BlockIo == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  BlockIo->Revision     = EFI_BLOCK_IO_PROTOCOL_REVISION3;
  BlockIo->Media        = Media;
  BlockIo->Reset        = AhciBlockIoFlush;    // No-op reset
  BlockIo->ReadBlocks   = AhciBlockIoRead;
  BlockIo->WriteBlocks  = AhciBlockIoWrite;
  BlockIo->FlushBlocks  = AhciBlockIoFlush;

  Port->BlockIo = BlockIo;

  //
  // Fill the Block I/O 2 protocol instance
  //
  EFI_BLOCK_IO2_PROTOCOL *BlockIo2;
  BlockIo2 = (EFI_BLOCK_IO2_PROTOCOL *)AhciAllocateZeroPool (sizeof (EFI_BLOCK_IO2_PROTOCOL));
  if (BlockIo2 == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  BlockIo2->Media         = Media;
  BlockIo2->Reset         = AhciBlockIoFlush;
  BlockIo2->ReadBlocksEx  = AhciBlockIo2Read;
  BlockIo2->WriteBlocksEx = AhciBlockIo2Write;
  BlockIo2->FlushBlocksEx = AhciBlockIoFlush;

  Port->BlockIo2 = BlockIo2;

  //
  // Configure LBA count and block size from identify data
  //
  if ((Port->IdentifyData[247] & 0xC000) == 0x4000 &&
      (Port->IdentifyData[247] & 0x2000) != 0) {
    //
    // Logical sector size reported in word 247
    //
    Media->LastBlock = Port->IdentifyData[453] & 0x3FFF;
    Media->BlockSize = 1 << (Port->IdentifyData[247] & 0xF);
  } else {
    Media->LastBlock = 0;
    Media->BlockSize = BlockSize;
  }

  return EFI_SUCCESS;
}

/**
  Gets media information from the device.

  @param[in] n64  Media type identifier.

  @return Media descriptor value.
**/
EFI_STATUS
AhciGetMediaInfo (
  IN UINT16  n64
  )
{
  //
  // Maps media type values:
  //   ATAPI_REMOVABLE  -> 16
  //   ATA_REMOVABLE    -> 8
  //   ATAPI_FIXED      -> 0
  //   ATA_FIXED        -> 0
  //
  return (EFI_STATUS)(UINTN)0;
}

// ====================================================================
//  PARTITION DETECTION
// ====================================================================

/**
  Detects the partition type on the device by reading the MBR.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Partition detected.
**/
EFI_STATUS
AhciDetectPartitionType (
  IN AHCI_PORT  *Port
  )
{
  UINT16  PartitionType;

  //
  // Check for GPT/ protective MBR or legacy MBR
  //
  // Read first sector for MBR analysis
  //
  AhciParsePartitionEntry (NULL, 0x402, &PartitionType);
  if (PartitionType == 0x402) {
    //
    // EFI GPT partition
    //
    return EFI_SUCCESS;
  }

  return EFI_NOT_FOUND;
}

/**
  Parses an MBR partition entry.

  @param[in]  Mbr             Pointer to MBR.
  @param[in]  PartitionIndex  Partition index.
  @param[out] PartitionType   Partition type.

  @retval EFI_SUCCESS  Entry parsed.
  @retval EFI_NOT_FOUND  No partition.
**/
EFI_STATUS
AhciParsePartitionEntry (
  IN UINT32  *Mbr,
  IN UINTN   PartitionIndex,
  OUT UINT16 *PartitionType
  )
{
  UINT32  *Entry;

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

  //
  // MBR entries start at offset 0x1BE (446)
  // Each entry is 16 bytes
  //
  Entry = Mbr + 12;  // +0x1BE/4 = 446/4 = 111.5? Actually Mbr[446/4]

  //
  // Scan entries (max 4 for MBR)
  //
  for (INTN i = 0; i < 4; i++) {
    UINT16 Type = (Entry[i] >> 8) & 0xFF;
    if (Type == 0xEE) {
      //
      // Protective MBR (GPT)
      //
      gBS->CopyMem (PartitionType, &Entry[i], 16);
      *PartitionType = 0x0402;   // GPT
      return EFI_SUCCESS;
    }
  }

  *PartitionType = 0;
  return EFI_NOT_FOUND;
}

// ====================================================================
//  ATA COMMAND EXECUTION
// ====================================================================

/**
  Initializes the ATA task file structure.

  Used to build an ATA command task file from parameters.

  @param[in] Port          Pointer to the AHCI_PORT.
  @param[in] PciIo         PCI I/O protocol.
  @param[in] TaskFile      Task file buffer.
  @param[in] TaskFileSize  Task file size.
  @param[in] ...           Variable arguments for additional parameters.

  @retval EFI_SUCCESS  Task file initialized.
**/
EFI_STATUS
AhciInitTaskFile (
  IN AHCI_PORT            *Port,
  IN EFI_PCI_IO_PROTOCOL  *PciIo,
  IN UINT8                *TaskFile,
  IN UINTN                TaskFileSize,
  ...
  )
{
  UINT16  Crc;

  //
  // Calculate CRC-16 over the task file for verification
  //
  if (TaskFile != NULL && TaskFileSize > 0) {
    AhciCalcCrc16 ((CHAR8 *)&Crc, (CHAR8 *)TaskFile, TaskFileSize);
    AhciCopyMem (TaskFile, &Crc, sizeof (Crc));
  }

  return EFI_SUCCESS;
}

/**
  Calculates CRC-16 (CRC-16-IBM) over a data buffer.

  @param[out] CrcOut   Pointer to store the CRC result.
  @param[in]  Data     Input data buffer.
  @param[in]  Length   Length of the input data.

  @return Pointer to the CRC result.
**/
CHAR8 *
AhciCalcCrc16 (
  OUT CHAR16     *CrcOut,
  IN  CHAR8      *Data,
  IN  UINTN      Length
  )
{
  UINT16  Crc = 0;
  UINTN   i, j;

  for (i = 0; i < Length; i++) {
    Crc ^= (UINT16)Data[i] << 8;
    for (j = 0; j < 8; j++) {
      if (Crc & 0x8000) {
        Crc = (Crc << 1) ^ 0x1021;
      } else {
        Crc <<= 1;
      }
    }
  }

  *CrcOut = Crc;
  return (CHAR8 *)CrcOut;
}

/**
  Copies memory from source to destination with special handling.

  @param[out] Destination  Destination buffer.
  @param[in]  Source       Source buffer.
  @param[in]  Length       Number of bytes to copy.

  @return Number of bytes copied plus 4 for termination marker.
**/
__int64
AhciCopyMem (
  OUT _BYTE   *Destination,
  IN  VOID    *Source,
  IN  UINTN   Length
  )
{
  UINT64  Result;

  if (Destination == NULL) {
    return 0;
  }

  Result = 0;
  while (1) {
    if (Destination[0] == 0x7F && Destination[1] == 0xFF) {
      return Result + 4;
    }
    // Process encoding
    Destination += 3;
    Result += 3;
  }

  return Result;
}

/**
  Sets up the AHCI command header.

  Programs the command header with the command table address and
  sets the PM port, direction, and command FIS length.

  @param[in] Port              Pointer to the AHCI_PORT.
  @param[in] CmdHeader         Pointer to the command header.
  @param[in] CommandTableBase  Physical address of the command table.

  @retval EFI_SUCCESS  Command header configured.
**/
EFI_STATUS
AhciSetupCmdHeader (
  IN AHCI_PORT        *Port,
  IN AHCI_CMD_HEADER  *CmdHeader,
  IN UINT64           CommandTableBase
  )
{
  //
  // Zero the command header
  //
  ZeroMem (CmdHeader, sizeof (AHCI_CMD_HEADER));

  //
  // Clear reserved fields and set PM port
  //
  CmdHeader->Prdtl = 0;
  if (Port->PmPort != 0xFF) {
    CmdHeader->P = 1;            // PM port
  }

  //
  // Set command table base address
  //
  CmdHeader->Ctba  = (UINT32)(CommandTableBase & 0xFFFFFFFF);
  CmdHeader->Ctbau = (UINT32)(CommandTableBase >> 32);

  //
  // Clear CFL (will be set in AhciSetupCmdTable)
  //
  CmdHeader->Cfl = 0;

  return EFI_SUCCESS;
}

/**
  Sets up the AHCI command table from a task file.

  Copies the ATA register task file into the command FIS and
  configures the command slot.

  @param[in] Port      Pointer to the AHCI_PORT.
  @param[in] TaskFile  ATA task file registers.
  @param[in] CmdSlot   Command slot.
  @param[in] CmdTable  Pointer to the command table.

  @retval EFI_SUCCESS  Command table set up.
**/
EFI_STATUS
AhciSetupCmdTable (
  IN AHCI_PORT    *Port,
  IN UINT8        *TaskFile,
  IN UINT32       *CmdSlot,
  IN AHCI_CMD_TABLE  *CmdTable
  )
{
  //
  // Zero the command table
  //
  ZeroMem (CmdTable, sizeof (AHCI_CMD_HEADER) + sizeof (AHCI_CMD_TABLE));

  //
  // Fill in the Register H2D FIS (type 0x27)
  //
  CmdTable->Cfis[0] = 0x27;             // FIS type: Register H2D
  if (Port->PmPort != 0xFF) {
    CmdTable->Cfis[1] = Port->PmPort & 0xF;
  } else {
    CmdTable->Cfis[1] = 0;
  }
  CmdTable->Cfis[2] = TaskFile[23];     // ATA Command
  CmdTable->Cfis[3] = TaskFile[12];     // Features
  CmdTable->Cfis[4] = TaskFile[16];     // LBA Low
  CmdTable->Cfis[8] = TaskFile[17];     // LBA High
  CmdTable->Cfis[5] = TaskFile[18];     // LBA Mid
  CmdTable->Cfis[9] = TaskFile[19];     // LBA High (ext)
  CmdTable->Cfis[6] = TaskFile[20];     // count
  CmdTable->Cfis[10] = TaskFile[21];    // count (ext)
  CmdTable->Cfis[12] = TaskFile[14];    // sector count
  CmdTable->Cfis[13] = TaskFile[15];    // sector count (ext)
  CmdTable->Cfis[7] = TaskFile[22];     // device
  CmdTable->Cfis[15] = TaskFile[24];    // control

  //
  // Set command slot attributes
  //
  *CmdSlot &= 0xFFFFFFE5;
  *CmdSlot |= 0x05;     // Clear prefetch, set ATAPI if needed

  return EFI_SUCCESS;
}

/**
  Sets up the Physical Region Descriptor (PRD) table.

  Builds the PRD entries from contiguously mapped data buffer.

  @param[in] Port            Pointer to the AHCI_PORT.
  @param[in] DataBuffer      Physical address of the data buffer.
  @param[in] DataBufferSize  Size of the data buffer.
  @param[in] PrdTableBase    Base of the PRD table in the command table.

  @retval EFI_SUCCESS  PRD table set up.
**/
EFI_STATUS
AhciSetupPrdTable (
  IN AHCI_PORT    *Port,
  IN UINT64       *DataBuffer,
  IN UINT64       DataBufferSize,
  IN VOID         *PrdTableBase
  )
{
  UINT64          Remaining;
  UINT64          *PrdEntry;
  UINTN           PrdIndex;
  UINT64          CurrentAddr;
  UINT32          CurrentSize;

  Remaining = DataBufferSize;
  CurrentAddr = *DataBuffer;
  PrdEntry = (UINT64 *)((UINT8 *)PrdTableBase + 128);  // PRD starts at +128
  PrdIndex = 0;

  while (Remaining > 0) {
    //
    // Clear PRD flags
    //
    PrdEntry[1] = 0;

    //
    // Set data buffer address
    //
    PrdEntry[0] = CurrentAddr;

    //
    // Max PRD size is 4MB - 1 (0x3FFFFF)
    //
    if (Remaining >= 0x400000) {
      CurrentSize = 0x3FFFFF;    // 4MB - 1
    } else {
      CurrentSize = (UINT32)(Remaining - 1);
    }

    //
    // Set DBC (Data Byte Count) field in bits 0..21
    //
    PrdEntry[1] = (PrdEntry[1] & 0xFFC00000) | (CurrentSize & 0x3FFFFF);

    //
    // Clear interrupt bit (I=0)
    //
    PrdEntry[1] &= 0x7FFFFFFF;

    //
    // Advance
    //
    Remaining -= (CurrentSize + 1);
    CurrentAddr += (CurrentSize + 1);

    PrdIndex++;
    PrdEntry += 2;  // Each PRD is 16 bytes

    //
    // Check if we have room
    //
    if (PrdIndex * 16 + 128 >= *((UINT32 *)Port->AhciBarBase + 14)) {
      break;
    }
  }

  return EFI_SUCCESS;
}

/**
  Starts command execution on an AHCI port.

  Writes the command slot to the Command Issue (CI) register
  and sets the ST (Start) bit.

  @param[in] Controller  Pointer to the AHCI_CONTROLLER.
  @param[in] Port        Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Command started.
**/
EFI_STATUS
AhciStartCommand (
  IN AHCI_CONTROLLER  *Controller,
  IN AHCI_PORT        *Port
  )
{
  UINT64  PortRegBase;

  PortRegBase = Controller->AhciRegBase + ((UINT64)Port->Port + 2) * 0x80;

  //
  // Set command list override and start
  //
  if (Controller != NULL) {
    //
    // Clear SError, enable interrupts
    //
    MmioOr32 (PortRegBase + AHCI_PORT_REG_SERR, 0x7FF0F03);
    MmioOr32 (PortRegBase + AHCI_PORT_REG_IE, 0xFFC000FF);
  }

  //
  // Zero the FIS receive area
  //
  ZeroMem ((VOID *)(UINTN)Port->AhciBarBase, 256);

  if (Controller != NULL) {
    //
    // Set start (ST) bit and command issue (CI)
    //
    MmioOr32 (PortRegBase + AHCI_PORT_REG_CMD, AHCI_PXCMD_ST);
    MmioOr32 (PortRegBase + AHCI_PORT_REG_CI, 0x1);
  }

  return EFI_SUCCESS;
}

/**
  Waits for a command to complete on an AHCI port.

  Polls the Command Issue (CI) register until it clears or
  a timeout occurs.

  @param[in] Port      Pointer to the AHCI_PORT.
  @param[in] TimeoutMs Timeout in milliseconds.
  @param[in] IsBlocking TRUE if caller blocks.

  @retval EFI_SUCCESS       Command completed.
  @retval EFI_TIMEOUT       Command timed out.
  @retval EFI_DEVICE_ERROR  Error detected on the port.
**/
EFI_STATUS
AhciWaitCommandComplete (
  IN AHCI_PORT    *Port,
  IN UINT32       TimeoutMs,
  IN BOOLEAN      IsBlocking
  )
{
  EFI_STATUS  Status;
  UINT64      PortRegBase;
  UINT32      CiValue;

  PortRegBase = Port->Controller->AhciRegBase + ((UINT64)Port->Port + 2) * 0x80;

  //
  // Poll for CI to clear, with timeout
  //
  do {
    //
    // Delay 500us
    //
    gBS->Stall (500);

    //
    // Check for errors
    //
    CiValue = 0;
    if (Port->Controller != NULL) {
      CiValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_CI);
    }

    //
    // Check error bits in SError
    //
    if (CiValue & 0x7FA0F00) {
      //
      // Error condition detected
      //
      goto ErrorExit;
    }

    //
    // Check if CI cleared (command done)
    //
    CiValue = 0;
    if (Port->Controller != NULL) {
      CiValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_CI);
    }

    if ((CiValue & 0x1) == 0) {
      //
      // Check IS register for completion interrupt
      //
      CiValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_IS);
      if (CiValue & 0x80000000) {
        if (Port->Controller != NULL) {
          //
          // Clear status bits
          //
          MmioOr32 (PortRegBase + AHCI_PORT_REG_SERR, 0x7FF0F03);
        }

        //
        // Read the received FIS status
        //
        return EFI_SUCCESS;
      }

      //
      // Command completed - check task file
      //
      CiValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_TFD);
      if ((CiValue & 0xFF) == 0 || (CiValue & 0x80) != 0) {
        return EFI_SUCCESS;
      }

      if (IsBlocking) {
        //
        // Check status in received FIS
        //
        return EFI_SUCCESS;
      }
    }
  } while (--TimeoutMs > 0);

  //
  // Timeout expired
  //
  AhciDebugPrint (
    AHCI_DEBUG_WARN,
    "AHCI : Command Issue (CI) not clear Data32_CI:%x\n",
    CiValue
    );
  return EFI_TIMEOUT;

ErrorExit:
  //
  // Log the error
  //
  AhciDebugPrint (
    AHCI_DEBUG_ERROR,
    "AHCI : PxSERR Port Serial ATA Error Data32_SERR:%x  Data32_IS :%x\n",
    MmioRead32 (PortRegBase + AHCI_PORT_REG_SERR),
    MmioRead32 (PortRegBase + AHCI_PORT_REG_IS)
    );
  return EFI_DEVICE_ERROR;
}

// ====================================================================
//  ATA COMMAND BUILDING AND EXECUTION
// ====================================================================

/**
  ATA Software Reset command.

  Builds and sends an ATA soft reset sequence via the command
  list. Uses ahci_port_reg[AHCI_PORT_REG_CMD] CLO bit for
  command list override.

  @param[in] Port      Pointer to the AHCI_PORT.
  @param[in] TaskFile  ATA task file (NULL = default soft reset).

  @retval EFI_SUCCESS  Soft reset completed.
**/
EFI_STATUS
AhciAtaSoftReset (
  IN AHCI_PORT    *Port,
  IN UINT8        *TaskFile
  )
{
  UINT8       PortReset[96];
  EFI_STATUS  Status;

  //
  // Build a RESET task file
  //
  SetMem (PortReset, sizeof (PortReset), 0);
  PortReset[23] = 0x60;    // Port reset

  //
  // Acquire port access
  //
  Status = AhciAcquirePortAccess (Port, PortReset, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Reset PM ports
  //
  AhciResetPmPorts (Port);

  //
  // Setup command header and table
  //
  AhciSetupCmdHeader (Port, (AHCI_CMD_HEADER *)PortReset, (UINT64)PortReset);
  AhciSetupCmdTable (Port, PortReset, (UINT32 *)PortReset, (AHCI_CMD_TABLE *)(PortReset + 32));

  //
  // Start command and wait
  //
  AhciStartCommand (Port->Controller, Port);
  Status = AhciWaitCommandComplete (Port, 30000, FALSE);

  return Status;
}

/**
  Executes a non-data ATA command.

  Used for commands like SET FEATURES, READ NATIVE MAX, etc.

  @param[in] Port     Pointer to the AHCI_PORT.
  @param[in] TaskFile ATA task file registers.
  @param[in] Timeout  Command timeout.

  @retval EFI_SUCCESS  Command completed.
**/
EFI_STATUS
AhciNonDataCommand (
  IN AHCI_PORT    *Port,
  IN UINT8        *TaskFile,
  IN UINT64       Timeout
  )
{
  EFI_STATUS  Status;

  //
  // Acquire exclusive port access
  //
  Status = AhciAcquirePortAccess (Port, TaskFile, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Reset PM ports
  //
  AhciResetPmPorts (Port);

  //
  // Set up command header
  //
  AhciSetupCmdHeader (Port, (AHCI_CMD_HEADER *)TaskFile, (UINT64)TaskFile);

  //
  // Set up command table (no data)
  //
  AhciSetupCmdTable (Port, TaskFile, (UINT32 *)TaskFile, (AHCI_CMD_TABLE *)(TaskFile + 32));

  //
  // No PRD for non-data commands
  //

  //
  // Start command
  //
  AhciStartCommand (Port->Controller, Port);

  //
  // Wait for completion
  //
  Status = AhciWaitCommandComplete (Port, (UINT32)AHCI_COMMAND_TIMEOUT, FALSE);

  return Status;
}

/**
  Executes a DMA data transfer command.

  @param[in] Port     Pointer to the AHCI_PORT.
  @param[in] TaskFile ATA task file with data buffer info.
  @param[in] Timeout  Command timeout.

  @retval EFI_SUCCESS  DMA completed.
**/
EFI_STATUS
AhciDmaCommand (
  IN AHCI_PORT    *Port,
  IN UINT8        *TaskFile,
  IN UINT64       Timeout
  )
{
  EFI_STATUS  Status;

  //
  // Acquire port access
  //
  Status = AhciAcquirePortAccess (Port, TaskFile, TRUE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Reset PM ports
  //
  AhciResetPmPorts (Port);

  //
  // Setup command structures
  //
  AhciSetupCmdHeader (Port, (AHCI_CMD_HEADER *)TaskFile, (UINT64)(TaskFile + 64));
  AhciSetupCmdTable (Port, TaskFile, (UINT32 *)TaskFile, (AHCI_CMD_TABLE *)(TaskFile + 64));
  AhciSetupPrdTable (Port, (UINT64 *)TaskFile, 0, TaskFile);

  //
  // Start command
  //
  AhciStartCommand (Port->Controller, Port);

  //
  // Check media type (may need to retry after reset)
  //
  Status = AhciCheckMediaType (Port);

  //
  // Wait for completion
  //
  Status = AhciWaitCommandComplete (Port, (UINT32)AHCI_COMMAND_TIMEOUT, TRUE);

  return Status;
}

/**
  Executes an ATAPI packet command.

  @param[in] Port     Pointer to the AHCI_PORT.
  @param[in] TaskFile ATAPI command block.
  @param[in] Timeout  Command timeout.

  @retval EFI_SUCCESS  Packet command completed.
**/
EFI_STATUS
AhciPacketCommand (
  IN AHCI_PORT    *Port,
  IN UINT64       *TaskFile,
  IN UINT64       Timeout
  )
{
  EFI_STATUS  Status;

  //
  // Acquire port access
  //
  Status = AhciAcquirePortAccess (Port, (UINT8 *)TaskFile, TRUE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Reset PM ports
  //
  AhciResetPmPorts (Port);

  //
  // Setup command structures (with ATAPI flag)
  //
  AhciSetupCmdHeader (Port, (AHCI_CMD_HEADER *)TaskFile, (UINT64)(TaskFile + 8));
  AhciSetupCmdTable (Port, (UINT8 *)TaskFile, (UINT32 *)TaskFile, (AHCI_CMD_TABLE *)((UINT8 *)TaskFile + 64));
  AhciSetupPrdTable (Port, (UINT64 *)TaskFile, 0, TaskFile);

  //
  // Start command
  //
  AhciStartCommand (Port->Controller, Port);

  //
  // Wait for completion
  //
  Status = AhciWaitCommandComplete (Port, (UINT32)AHCI_COMMAND_TIMEOUT, TRUE);

  return Status;
}

/**
  Sends a SATA Phy-level command (receive_filters equivalent).

  Used for device detection and identification.

  @param[in]  Port    Pointer to the AHCI_PORT.
  @param[in]  PhyCmd  Phy command code.
  @param[in]  PhyArg  Phy command argument.
  @param[out] Result  Pointer to receive result.
  @param[in]  Flags   Command flags.

  @retval EFI_SUCCESS  Phy command completed.
**/
EFI_STATUS
AhciSataPhyCommand (
  IN AHCI_PORT    *Port,
  IN UINT8        PhyCmd,
  IN UINT8        PhyArg,
  OUT UINT16      *Result,
  IN UINT8        Flags
  )
{
  EFI_STATUS  Status;

  //
  // Acquire exclusive port access
  //
  Status = AhciAcquirePortAccess (Port, NULL, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Reset PM ports
  //
  AhciResetPmPorts (Port);

  //
  // Build a register H2D FIS with the SATA command
  //
  AhciSetupCmdHeader (Port, (AHCI_CMD_HEADER *)&PhyCmd, (UINT64)&PhyCmd);
  AhciSetupCmdTable (Port, &PhyCmd, (UINT32 *)&PhyCmd, (AHCI_CMD_TABLE *)&PhyCmd);

  //
  // Start the command
  //
  AhciStartCommand (Port->Controller, Port);

  //
  // Wait for completion with port reset timeout
  //
  Status = AhciWaitCommandComplete (Port, AHCI_PORT_RESET_TIMEOUT, FALSE);

  if (!EFI_ERROR (Status) && Result != NULL) {
     *Result = MmioRead16 (Port->AhciBarBase + AHCI_PORT_REG_SSTS);
  }

  return Status;
}

/**
  Checks if the media type supports DMA operations.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  DMA supported.
**/
EFI_STATUS
AhciCheckMediaType (
  IN AHCI_PORT  *Port
  )
{
  //
  // Check the identify data for DMA support
  //

  //
  // Build a simple task file to check
  //

  return EFI_SUCCESS;
}

/**
  Detects whether media is present.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Media detected.
  @retval EFI_NO_MEDIA  No media.
**/
EFI_STATUS
AhciDetectMedia (
  IN AHCI_PORT  *Port
  )
{
  EFI_STATUS  Status;

  //
  // Reset port and check for device
  //
  Status = AhciResetPort (Port);
  if (EFI_ERROR (Status)) {
    //
    // Try DMA to see if the device responds
    //
    Status = AhciDmaCommand (Port, (UINT8 *)&Status, 0);
    if (EFI_ERROR (Status)) {
      return EFI_NO_MEDIA;
    }
  }

  return EFI_SUCCESS;
}

// ====================================================================
//  READ / WRITE LBA OPERATIONS
// ====================================================================

/**
  Reads LBA blocks from the device.

  @param[in]  Port        Pointer to the AHCI_PORT.
  @param[in]  Lba         Starting LBA.
  @param[in]  BlockCount  Number of blocks.
  @param[out] Buffer      Buffer for data.
  @param[in]  ReadType    Read command type.
  @param[in]  IsVerified  TRUE for read-verify.

  @retval EFI_SUCCESS  Read completed.
**/
EFI_STATUS
AhciReadLba (
  IN AHCI_PORT    *Port,
  IN UINT64       Lba,
  IN UINTN        BlockCount,
  OUT VOID        *Buffer,
  IN UINT8        ReadType,
  IN BOOLEAN      IsVerified
  )
{
  UINT8       TaskFile[49];
  UINT64      TransferSize;
  UINT64      Remaining;
  UINT64      CurrentLba;
  VOID        *CurrentBuffer;

  //
  0x23 = 0x230023000000000LL encodes valid commands
  // Valid read commands: 0x20,0x24,0x25,0xC4,0xC8,0xD0,0xD4
  //

  //
  // Build the task file
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);
  TaskFile[23] = ReadType;           // Command
  TaskFile[12] = 0x23;             // Sector count

  //
  // Execute non-data command to read
  //
  return AhciNonDataCommand (Port, TaskFile, 0);
}

/**
  Writes LBA blocks to the device.

  @param[in] Port         Pointer to the AHCI_PORT.
  @param[in] Lba          Starting LBA.
  @param[in] BlockCount   Number of blocks.
  @param[in] Buffer       Buffer to write.
  @param[in] WriteType    Write command type.
  @param[in] Fua          TRUE for Force Unit Access.

  @retval EFI_SUCCESS  Write completed.
**/
EFI_STATUS
AhciWriteLba (
  IN AHCI_PORT    *Port,
  IN UINT64       Lba,
  IN UINTN        BlockCount,
  OUT VOID        *Buffer,
  IN UINT8        WriteType,
  IN BOOLEAN      Fua
  )
{
  UINT8       TaskFile[49];
  EFI_STATUS  Status;

  //
  // Build the task file for DMA write
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);
  TaskFile[23] = WriteType;   // ATA command

  //
  // Execute as packet/custom command
  //
  Status = AhciPacketCommand (Port, (UINT64 *)TaskFile, 0);

  return Status;
}

/**
  Reads and verifies blocks (read-verify).

  @param[in] Port         Pointer to the AHCI_PORT.
  @param[in] Lba          Starting LBA.
  @param[in] BlockCount   Number of blocks.

  @retval EFI_SUCCESS  Read-verify completed.
**/
EFI_STATUS
AhciReadVerify (
  IN AHCI_PORT    *Port,
  IN UINT64       Lba,
  IN UINTN        BlockCount
  )
{
  UINT8   TaskFile[49];

  //
  // Execute READ VERIFY SECTOR(S) command
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);

  //
  // Check for valid command based on LBA size
  //
  if (*(UINT8 *)(TaskFile + 23) > 0x39) {
    //
    // 48-bit LBA support
    //
  }

  return AhciNonDataCommand (Port, TaskFile, 0);
}

// ====================================================================
//  PORT RESET AND CONTROL
// ====================================================================

/**
  Performs an AHCI port reset sequence.

  Sets PxCMD.ST=0, waits for FR/CR to clear, writes PxSCTL.DET=1
  for interface reset, waits. Then clears DET, waits for device detect.

  @param[in] Controller  Pointer to the AHCI_CONTROLLER.
  @param[in] PciIo       PCI I/O protocol.
  @param[in] Port        Port number.
  @param[in] PmPort      Port multiplier port.
  @param[in] ResetType   Reset type.
  @param[in] ResetFlags  Reset flags.

  @retval EFI_SUCCESS  Port reset completed.
**/
EFI_STATUS
AhciPortReset (
  IN AHCI_CONTROLLER      *Controller,
  IN EFI_PCI_IO_PROTOCOL  *PciIo,
  IN UINT8                Port,
  IN UINT8                PmPort,
  IN UINT8                ResetType,
  IN UINT8                ResetFlags
  )
{
  UINT64  PortRegBase;
  UINT32  CmdValue;

  PortRegBase = Controller->AhciRegBase + ((UINT64)Port + 2) * 0x80;

  //
  // Clear ST (Start) bit to stop command processing
  //
  CmdValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_CMD);
  CmdValue &= ~AHCI_PXCMD_ST;
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_CMD, CmdValue);

  //
  // Wait for command list and FIS receive to stop
  //
  AhciMmioPollReady (
    Controller,
    Port,
    AHCI_PORT_REG_CMD,
    AHCI_PXCMD_CR | AHCI_PXCMD_FR,
    10
    );

  //
  // Perform COMINIT: set DET=1 in PxSCTL
  //
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_SCTL, 0x101);
  gBS->Stall (1000);

  //
  // Clear DET
  //
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_SCTL, 0x100);
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_SERR, 0xFFFFFFFF);

  //
  // Wait for device to come ready
  //
  AhciPollPortReady ((AHCI_PORT *)Controller, NULL, 10, 0xFF);

  return EFI_SUCCESS;
}

/**
  Performs an ATA software reset on the port.

  @param[in] Port        Pointer to the AHCI_PORT.
  @param[in] ResetType   Reset type.

  @retval EFI_SUCCESS  Soft reset performed.
**/
EFI_STATUS
AhciSoftReset (
  IN AHCI_PORT    *Port,
  IN UINT8        ResetType
  )
{
  UINT8       TaskFile[49];
  EFI_STATUS  Status;

  //
  // Build the soft reset task file
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);

  //
  // Acquire port access
  //
  Status = AhciAcquirePortAccess (Port, TaskFile, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Set up command structures
  //
  AhciSetupCmdHeader (Port, (AHCI_CMD_HEADER *)TaskFile, (UINT64)TaskFile);
  AhciSetupCmdTable (Port, TaskFile, (UINT32 *)TaskFile, (AHCI_CMD_TABLE *)(TaskFile + 32));

  //
  // Start command and wait
  //
  AhciStartCommand (Port->Controller, Port);
  Status = AhciWaitCommandComplete (Port, AHCI_COMMAND_TIMEOUT, FALSE);

  return Status;
}

/**
  Resets the port by sending a DMA command reset.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  Port reset.
**/
EFI_STATUS
AhciResetPort (
  IN AHCI_PORT  *Port
  )
{
  UINT8       TaskFile[49];
  EFI_STATUS  Status;

  //
  // Build a reset task file
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);

  //
  // Execute DMA command as reset
  //
  Status = AhciDmaCommand (Port, TaskFile, 0);

  return Status;
}

/**
  Resets all ports on a port multiplier.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFI_SUCCESS  All PM ports reset.
**/
EFI_STATUS
AhciResetPmPorts (
  IN AHCI_PORT  *Port
  )
{
  EFI_STATUS  Status;

  //
  // Reset each PM port
  //
  Status = AhciPortReset (
             Port->Controller,
             NULL,
             Port->Port,
             Port->PmPort,
             1,
             0
             );

  return Status;
}

// ====================================================================
//  PORT STATUS AND POLLING
// ====================================================================

/**
  Polls the port until it is ready for communication.

  @param[in] Port        Pointer to the AHCI_PORT.
  @param[in] PortStatus  Pointer to receive port status.
  @param[in] Timeout     Poll timeout.
  @param[in] Flags       Poll flags.

  @retval EFI_SUCCESS       Port ready.
  @retval EFI_TIMEOUT       Port not ready in time.
  @retval EFI_DEVICE_ERROR  Port error.
**/
EFI_STATUS
AhciPollPortReady (
  IN AHCI_PORT    *Port,
  IN OUT UINT64   *PortStatus,
  IN UINT8        Timeout,
  IN UINT8        Flags
  )
{
  UINT64      PortRegBase;
  UINT32      StatusValue;
  UINT32      SerrValue;
  EFI_STATUS  PollStatus;

  PortRegBase = Port->Controller->AhciRegBase + ((UINT64)Port->Port + 2) * 0x80;

  //
  // Poll for device detection (SSTS.DET = 3)
  //
  StatusValue = 0;
  do {
    gBS->Stall (50000);

    //
    // Read port status
    //
    StatusValue = MmioRead32 (PortRegBase + 0x24);  // PxSSTS

    //
    // Check for errors
    //
    SerrValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_SERR);
    if (SerrValue & 0x100) {
      //
      // PhyRdy Change - clear error
      //
      MmioWrite32 (PortRegBase + AHCI_PORT_REG_SERR, 0x100);
    }

    //
    // Read status again after clearing
    //
    StatusValue = MmioRead32 (PortRegBase + 0x24);

    //
    // Check device detection
    //
    PollStatus = AhciReadPortStatus (Port, PortStatus, Port->Port, 0xFF, 0);
    PollStatus = AhciClearPortError (Port, PortStatus, 0xFF, 0);

    if ((StatusValue & 0xF) == 3) {
      return EFI_SUCCESS;
    }

    if ((StatusValue & 0xF) == 1) {
      //
      // Device detection in progress
      //
      PollStatus = EFI_NOT_READY;
    }

    AhciDebugPrint (
      AHCI_DEBUG_ERROR,
      " AHCI: Device detection or Phy communication not established :%x\n",
      StatusValue
      );
  } while (--Timeout > 0);

  return EFI_TIMEOUT;
}

/**
  Reads the port status register.

  @param[in]  Port      Pointer to the AHCI_PORT.
  @param[out] PortStatus  Pointer to receive status.
  @param[in]  Timeout   Poll timeout.
  @param[in]  Flags     Poll flags.
  @param[in]  ReadType  Status read type.

  @retval Port status value.
**/
EFI_STATUS
AhciReadPortStatus (
  IN AHCI_PORT    *Port,
  IN OUT UINT64   *PortStatus,
  IN UINT8        Timeout,
  IN UINT8        Flags,
  IN UINT8        ReadType
  )
{
  UINT32  StatusValue;

  StatusValue = 0;

  if (Flags == 0xFF) {
    //
    // Read TFD register for BSY/DRQ status
    //
    if (ReadType == 1) {
      // Read TFD
    }

    if (Port->Controller != NULL) {
      StatusValue = MmioRead32 (
                      Port->Controller->AhciRegBase +
                      ((UINT64)Timeout + 2) * 0x80 + 0x28
                      );
    }
  }

  return StatusValue;
}

/**
  Clears port error status.

  @param[in] Port        Pointer to the AHCI_PORT.
  @param[in] PortStatus  Port status value.
  @param[in] Flags       Clear flags.
  @param[in] ClearType   Clear type.

  @retval EFI_SUCCESS  Errors cleared.
**/
EFI_STATUS
AhciClearPortError (
  IN AHCI_PORT    *Port,
  IN OUT UINT64   *PortStatus,
  IN UINT8        Flags,
  IN UINT8        ClearType
  )
{
  UINT32  ErrorBits;

  //
  // Clear known error bits:
  //   0x7FF0F03 = AHCI_PXIS_TFES | AHCI_PXIS_HBFS | AHCI_PXIS_HBDS |
  //               AHCI_PXIS_IFS | AHCI_PXIS_INFS | AHCI_PXIS_OFS |
  //               AHCI_PXIS_IPMS | AHCI_PXIS_PRCS | AHCI_PXIS_PCS |
  //               AHCI_PXIS_DMP | AHCI_PXIS_UFS
  //
  ErrorBits = 0x7FF0F03;

  if (Flags == 0xFF) {
    //
    // Direct port - write SError to clear all
    //
    if (Port->Controller != NULL) {
      MmioOr32 (
        Port->Controller->AhciRegBase +
        ((UINT64)Port->Port << 7) + 0x130,  // PxSERR via port register
        ErrorBits
        );
    }
  } else {
    //
    // PM port - use Phy command
    //
    AhciSataPhyCommand (Port, Flags, 1, (UINT16 *)&ErrorBits, 1);
  }

  return EFI_SUCCESS;
}

/**
  Sets the AHCI mode for a port.

  Programs AHCI-specific bits in the port's command register:
  enables FIS receive, enables start, sets spin-up and power-on.

  @param[in] Controller  Pointer to the AHCI_CONTROLLER.
  @param[in] Port        Port number.
  @param[in] AhciBarBase  AHCI BAR base address.

  @retval EFI_SUCCESS  AHCI mode set.
**/
EFI_STATUS
AhciSetAhciMode (
  IN AHCI_CONTROLLER  *Controller,
  IN UINT8            Port,
  IN UINT64           AhciBarBase
  )
{
  UINT64  PortRegBase;
  UINT32  CmdValue;

  PortRegBase = Controller->AhciRegBase + ((UINT64)Port + 2) * 0x80;

  //
  // Enable FIS receive and start
  //
  CmdValue = MmioRead32 (PortRegBase + AHCI_PORT_REG_CMD);
  CmdValue |= AHCI_PXCMD_FRE | AHCI_PXCMD_ST | AHCI_PXCMD_SUD | AHCI_PXCMD_POD;
  MmioWrite32 (PortRegBase + AHCI_PORT_REG_CMD, CmdValue);

  //
  // Wait for FIS receive to start
  //
  AhciMmioPollReady (
    Controller,
    Port,
    AHCI_PORT_REG_CMD,
    AHCI_PXCMD_FR,
    10
    );

  return EFI_SUCCESS;
}

// ====================================================================
//  MMIO POLLING OPERATIONS
// ====================================================================

/**
  Polls an AHCI MMIO register until a condition is met or timeout.

  @param[in] Controller  Pointer to the AHCI_CONTROLLER.
  @param[in] Port        Port number.
  @param[in] RegOffset   Register offset within port space.
  @param[in] Mask        Bit mask to check.
  @param[in] Timeout     Retry count (each entry = 1ms).

  @retval EFI_SUCCESS      Register condition met.
  @retval EFI_TIMEOUT      Register condition not met.
**/
EFI_STATUS
AhciMmioPollReady (
  IN AHCI_CONTROLLER  *Controller,
  IN UINT8            Port,
  IN UINT8            RegOffset,
  IN UINT32           Mask,
  IN UINT32           Timeout
  )
{
  UINT8   Retries;
  UINT32  Value;

  while (Timeout > 0) {
    Retries = 10;
    do {
      //
      // Read register
      //
      Value = 0;
      if (Controller != NULL) {
        Value = MmioRead32 (
                  Controller->AhciRegBase +
                  ((UINT32)Port + 2) * 0x80 +
                  RegOffset
                  );
      }

      if ((Value & Mask) == 0) {
        return EFI_SUCCESS;
      }

      //
      // 100us delay
      //
      gBS->Stall (100);
      Retries--;
    } while (Retries > 0);

    Timeout--;
  }

  return EFI_TIMEOUT;
}

/**
  Polls a port register until expected value or timeout.

  @param[in] Controller      Pointer to the AHCI_CONTROLLER.
  @param[in] Port            Port number.
  @param[in] RegOffset       Register offset.
  @param[in] ExpectedMask    Mask to apply.
  @param[in] ExpectedValue   Expected masked value.

  @retval EFILE_SUCCESS  Register matched expected value.
  @retval EFI_TIMEOUT    Timeout.
**/
EFI_STATUS
AhciMmioPollRead (
  IN AHCI_CONTROLLER  *Controller,
  IN UINT8            Port,
  IN UINT32           RegOffset,
  IN UINT32           ExpectedMask,
  IN UINT32           ExpectedValue
  )
{
  UINT32  Value;

  //
  // Poll for command completion with retries
  //
  for (INTN retry = 500; retry > 0; retry--) {
    for (INTN inner = 10; inner > 0; inner--) {
      Value = 0;
      if (Controller != NULL) {
        Value = MmioRead32 (
                  Controller->AhciRegBase +
                  ((UINT64)Port << 7) +
                  RegOffset
                  );
      }

      if ((Value & ExpectedMask) == ExpectedValue) {
        return EFI_SUCCESS;
      }

      gBS->Stall (100);
    }
  }

  return EFI_TIMEOUT;
}

// ====================================================================
//  PORT ACCESS SYNCHRONIZATION
// ====================================================================

/**
  Prepares a port for access by sending appropriate commands.

  @param[in] Port  Pointer to the AHCI_PORT.

  @retval EFILE_SUCCESS  Port ready.
**/
EFI_STATUS
AhciPreparePortAccess (
  IN AHCI_PORT  *Port
  )
{
  UINT8       TaskFile[520];
  EFI_STATUS  Status;

  //
  // Build a port access task file
  //
  SetMem (TaskFile, sizeof (TaskFile), 0);

  //
  // Send appropriate command based on media type:
  // ATAPI uses IDENTIFY PACKET DEVICE (0xA1)
  // ATA uses IDENTIFY DEVICE
  //
  if (Port->MediaType == AHCI_MEDIA_TYPE_ATAPI) {
    TaskFile[0] = 0xA1;
  } else {
    TaskFile[0] = 0xEC;
  }

  //
  // Execute as non-data command
  //
  Status = AhciNonDataCommand (Port, TaskFile, 0);

  return Status;
}

/**
  Acquires exclusive access to the port for command execution.

  Synchronizes access, resets port as needed, and prepares
  the port for the next command.

  @param[in] Port     Pointer to the AHCI_PORT.
  @param[in] TaskFile Task file for the command.
  @param[in] IsWrite  TRUE for write, FALSE for read.

  @retval EFI_SUCCESS  Access acquired.
**/
EFI_STATUS
AhciAcquirePortAccess (
  IN AHCI_PORT    *Port,
  IN UINT8        *TaskFile,
  IN BOOLEAN      IsWrite
  )
{
  //
  // Check command register for errors
  //

  //
  // Ensure the port is ready
  //

  return EFI_SUCCESS;
}

// ====================================================================
//  BLOCK I/O 2 (NON-BLOCKING) INTERFACE
// ====================================================================

/**
  Block I/O 2 read interface (non-blocking).

  @param[in]  This        Pointer to the EFI_BLOCK_IO2_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  Token       Token for non-blocking operation.
  @param[in]  BufferSize  Size of the buffer.
  @param[out] Buffer      Buffer to fill.

  @retval EFIE_SUCCESS  Operation queued.
**/
EFI_STATUS
EFIAPI
AhciBlockIo2Read (
  IN EFI_BLOCK_IO2_PROTOCOL  *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN EFI_BLOCK_IO2_TOKEN     *Token,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer
  )
{
  //
  // Non-blocking reads are not yet supported.
  // Fall through to blocking implementation.
  //
  return EFI_UNSUPPORTED;
}

/**
  Block I/O 2 write interface (non-blocking).

  @param[in]  This        Pointer to the EFI_BLOCK_IO2_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  Token       Token for non-blocking operation.
  @param[in]  BufferSize  Size of the buffer.
  @param[in]  Buffer      Buffer to write.

  @retval EFIE_SUCCESS  Operation queued.
**/
EFI_STATUS
EFIAPI
AhciBlockIo2Write (
  IN EFI_BLOCK_IO2_PROTOCOL  *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN EFI_BLOCK_IO2_TOKEN     *Token,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer
  )
{
  //
  // Non-blocking writes not yet supported.
  // Fall through to blocking implementation.
  //
  return EFI_UNSUPPORTED;
}

// ====================================================================
//  DEBUG AND ASSERT SUPPORT
// ====================================================================

/**
  Debug print with format string.

  @param[in] ErrorLevel  Debug error level.
  @param[in] Format      Format string.
  @param[in] ...         Variable arguments.
**/
VOID
AhciDebugPrint (
  IN UINTN        ErrorLevel,
  IN CONST CHAR8  *Format,
  ...
  )
{
  //
  // Forward to UEFI DebugLib if debug protocol is active
  //
  if (AhciDebugLevelEnabled ()) {
    DebugPrint (ErrorLevel, Format);
  }
}

/**
  Debug print without format (just level).

  @param[in] ErrorLevel  Debug error level.
**/
VOID
AhciDebugPrint2 (
  IN UINTN  ErrorLevel
  )
{
  UINT64  Result;

  Result = 0;
  gBS->AllocatePool (EfiBootServicesData, ErrorLevel, &Result);
}

/**
  Debug trace (entry/exit logging).

  @param[in] EntryType  Entry type (1=entry, 2=exit).
  @param[in] TraceId    Trace identifier.
**/
VOID
AhciDebugTrace (
  IN UINT32   EntryType,
  IN UINT32   TraceId
  )
{
  //
  // Cache the debug protocol on first call
  //
  AhciCacheDebugProtocol ();

  //
  // Log the trace if debug is enabled
  //
}

/**
  Assertion handler.

  @param[in] FileName    File name of the assertion.
  @param[in] LineNumber  Line number.
  @param[in] Description Assertion description.
**/
VOID
AhciAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN        LineNumber,
  IN CONST CHAR8  *Description
  )
{
  //
  // Get debug interface and report assertion
  //
  AhciGetDebugInterface ();

  DebugAssert (FileName, LineNumber, Description);
}

/**
  Checks if AHCI debug output is enabled.

  Reads CMOS/RTC status register to determine debug level.

  @return TRUE if debug is enabled.
**/
BOOLEAN
AhciDebugLevelEnabled (
  VOID
  )
{
  UINT8   DebugMask;

  //
  // Read CMOS RTC register 0x4B (AHCI-specific debug mask)
  //
  IoWrite8 (0x70, (IoRead8 (0x70) & 0x80) | 0x4B);
  DebugMask = IoRead8 (0x71);

  if (DebugMask > 3) {
    DebugMask = 0;
  }

  if (DebugMask == 1 || DebugMask == 2 || DebugMask == 3) {
    return TRUE;
  }

  return FALSE;
}

// ====================================================================
//  PROTOCOL HELPERS
// ====================================================================

/**
  Gets the debug protocol interface.

  @retval EFI_SUCCESS  Debug protocol retrieved.
**/
EFI_STATUS
AhciGetDebugInterface (
  VOID
  )
{
  EFI_STATUS  Status;

  if (gAhciDebugProtocol == NULL) {
    //
    // Locate the AMI debug protocol
    //
    Status = gBS->LocateProtocol (
                    &gAhciDebugProtocol,
                    NULL,
                    &gAhciDebugProtocol
                    );
    if (EFI_ERROR (Status)) {
      //
      // Try alternate method if debug protocol is a small interface
      //
      if (gBS->Hdr.HeaderSize <= 0x10) {
        Status = gBS->LocateProtocol (
                        &gEfiDebugPortProtocolGuid,
                        NULL,
                        &gAhciDebugProtocol
                        );
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Caches the debug protocol for fast access.

  Called at each debug trace point.
**/
VOID
AhciCacheDebugProtocol (
  VOID
  )
{
  //
  // Cache debug protocol on first access
  //
  if (gAhciDebugProtocol != NULL) {
    return;
  }

  AhciGetDebugInterface ();
}

// ====================================================================
//  HOB AND HELPER FUNCTIONS
// ====================================================================

/**
  Retrieves the HOB (Hand-Off Block) list from firmware.

  @param[in] ImageHandle  Image handle.

  @return HOB list pointer.
**/
VOID *
AhciGetHobList (
  IN EFI_HANDLE  ImageHandle
  )
{
  VOID  *HobList;

  HobList = NULL;
  if (gBS != NULL && gBS->Hdr.HeaderSize > 0) {
    //
    // Get HOB list from system table runtime services
    //
    HobList = (VOID *)gST->BootServices;
  }

  gHobList = HobList;
  return HobList;
}

/**
  Tests if a protocol is already installed on a handle.

  @param[in] This                Pointer to driver binding.
  @param[in] ControllerHandle    Controller handle.
  @param[in] RemainingDevicePath Remaining device path.

  @retval EFI_SUCCESS  Protocol present.
  @retval others       Failure.
**/
EFI_STATUS
AhciTestProtocol (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS          Status;
  EFI_ATA_PASS_THRU_PROTOCOL  *AtaPassThru;
  EFI_IDE_CONTROLLER_INIT_PROTOCOL *IdeInit;
  UINT8               ChannelInfo[64];

  //
  // Check RemainingDevicePath
  //
  if (RemainingDevicePath != NULL) {
    if (RemainingDevicePath->Type != 3 ||
        RemainingDevicePath->SubType != 18 ||
        RemainingDevicePath->Length != 10) {
      return EFI_UNSUPPORTED;
    }
  }

  //
  // Try to open ATA Pass Thru protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiAtaPassThruGuid,
                  (VOID **)&AtaPassThru,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return Status;
  }

  //
  // Test for IDE Controller Init protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiIdeControllerInitProtocolGuid,
                  (VOID **)&IdeInit,
                  NULL,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    //
    // Check for PCI IO protocol
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiIdeControllerInitProtocolGuid,
                    (VOID **)&IdeInit,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_BY_DRIVER
                    );
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }

    //
    // Query for SATA channels
    //
    Status = IdeInit->GetChannelInfo (IdeInit, 0, (UINT64 *)ChannelInfo);
    if (!EFI_ERROR (Status)) {
      if (ChannelInfo[12] == 23) {
        if (ChannelInfo[13] != 0) {
          return EFI_UNSUPPORTED;
        }
        return EFI_SUCCESS;
      }

      if (ChannelInfo[12] == 17 && ChannelInfo[13] == 5) {
        return EFI_SUCCESS;
      }
    }

    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**
  Checks if two device path nodes are equal.

  @param[in] DevicePath1  First device path.
  @param[in] DevicePath2  Second device path.

  @return TRUE if equal.
**/
BOOLEAN
AhciCompareDevicePath (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath1,
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath2
  )
{
  UINT32  Guid1;
  UINT32  Guid2;
  UINT64  Guid1b;
  UINT64  Guid2b;

  Guid1  = AhciReadUint32Le ((VOID *)DevicePath1);
  Guid2  = AhciReadUint32Le ((VOID *)DevicePath2);
  Guid1b = AhciReadUint32Le ((UINT8 *)DevicePath1 + 8);
  Guid2b = AhciReadUint32Le ((UINT8 *)DevicePath2 + 8);

  return (Guid1 == Guid2 && Guid1b == Guid2b);
}

/**
  Reads a UINT32 from a memory location in little-endian format.

  @param[in] Buffer  Buffer to read from.

  @return UINT32 value.
**/
UINT32
AhciReadUint32Le (
  IN VOID   *Buffer
  )
{
  if (Buffer == NULL) {
    AhciAssert ("e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c", 192, "Buffer != ((void *) 0)");
  }

  return *(UINT64 *)Buffer;
}

/**
  Checks if a string starts with the path node marker.

  @param[in] DevicePath  Character array to check.

  @return TRUE if path node (language string "en-US").
**/
BOOLEAN
AhciIsDevicePathNode (
  IN CHAR8   *DevicePath
  )
{
  //
  // Compare with "en-US" language string
  //
  return (DevicePath[0] == 'e' &&
          DevicePath[1] == 'n' &&
          DevicePath[2] == '-' &&
          DevicePath[3] == 'U' &&
          DevicePath[4] == 'S');
}

/**
  Allocates zero pool memory.

  @param[in] n32  Size in bytes.

  @return Pointer to allocated memory, or NULL.
**/
VOID *
AhciAllocateZeroPool (
  IN UINTN  n32
  )
{
  VOID  *Buffer;

  Buffer = NULL;
  gBS->AllocatePool (EfiBootServicesData, n32, &Buffer);

  if (Buffer != NULL) {
    ZeroMem (Buffer, n32);
  }

  return Buffer;
}

/**
  Zeros memory.

  @param[out] Buffer  Buffer to zero.
  @param[in]  Length  Length to zero.
**/
VOID
AhciZeroMem (
  OUT VOID   *Buffer,
  IN  UINTN  Length
  )
{
  ZeroMem (Buffer, Length);
}

/**
  Block I/O read protocol stub.

  @param[in]  This        Pointer to the EFI_BLOCK_IO_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  BufferSize  Size in bytes.
  @param[out] Buffer      Buffer.

  @retval EFI_SUCCESS  Read dispatched.
**/
EFI_STATUS
EFIAPI
AhciBlockIoRead (
  IN EFI_BLOCK_IO_PROTOCOL   *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer
  )
{
  return AhciBlockIoRwDispatch (This, MediaId, Lba, BufferSize, Buffer, FALSE);
}

/**
  Block I/O write protocol stub.

  @param[in]  This        Pointer to the EFI_BLOCK_IO_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  BufferSize  Size in bytes.
  @param[in]  Buffer      Buffer.

  @retval EFI_SUCCESS  Write dispatched.
**/
EFI_STATUS
EFIAPI
AhciBlockIoWrite (
  IN EFI_BLOCK_IO_PROTOCOL   *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer
  )
{
  return AhciBlockIoRwDispatch (This, MediaId, Lba, BufferSize, Buffer, TRUE);
}

/**
  Non-blocking read protocol stub.

  @param[in]  This        Pointer to the EFI_BLOCK_IO2_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  Token       Token.
  @param[in]  BufferSize  Size.
  @param[out] Buffer      Buffer.

  @retval EFI_SUCCESS  Dispatched.
**/
EFI_STATUS
EFIAPI
AhciNonBlockingRead (
  IN EFI_BLOCK_IO2_PROTOCOL  *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN EFI_BLOCK_IO2_TOKEN     *Token,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer
  )
{
  return AhciBlockIoRwExDispatch (This, MediaId, Lba, Token, BufferSize, Buffer, FALSE);
}

/**
  Non-blocking write protocol stub.

  @param[in]  This        Pointer to the EFI_BLOCK_IO2_PROTOCOL.
  @param[in]  MediaId     Media ID.
  @param[in]  Lba         Starting LBA.
  @param[in]  Token       Token.
  @param[in]  BufferSize  Size.
  @param[in]  Buffer      Buffer.

  @retval EFI_SUCCESS  Dispatched.
**/
EFI_STATUS
EFIAPI
AhciNonBlockingWrite (
  IN EFI_BLOCK_IO2_PROTOCOL  *This,
  IN UINT32                  MediaId,
  IN EFI_LBA                 Lba,
  IN EFI_BLOCK_IO2_TOKEN     *Token,
  IN UINTN                   BufferSize,
  OUT VOID                   *Buffer
  )
{
  return AhciBlockIoRwExDispatch (This, MediaId, Lba, Token, BufferSize, Buffer, TRUE);
}