/** @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);
}