Understanding the PassThru Mechanism in UEFI Storage Protocols

PassThru Operations in UEFI Storage Stack

In UEFI firmware development, the PassThru function serves as a critical interface for storage protocol communication. This function facilitates command execution between the system and storage devices by managing internal register initialization, command dispatch, transfer initiation, and optional completion tracking.

Robust error handling within PassThru implementations is essential for developing stable storage drivers. While most ATA host controllers support both blocking and non-blocking transfer modes, certain implementations may restrict operation to blocking transfers only.

Implementing NCQ Read Operations

The following demonstrates how to implement a Native Command Queuing (NCQ) read operation using the UEFI ATA PassThru protocol. NCQ enables storage devices to optimize command execution order internally, significantly improving performance.

#include <Uefi.h>
#include <Protocol/AtaPassThru.h>

EFI_STATUS
ExecuteNCQRead (
  IN EFI_ATA_PASS_THRU_PROTOCOL  *AtaInterface,
  IN UINT16                       TargetPort,
  IN UINT16                       PortMultiplier,
  IN UINT64                       LogicalBlockAddr,
  IN UINT32                       BlockCount,
  IN VOID                        *DataBuffer
  )
{
  EFI_ATA_COMMAND_BLOCK              CommandBlock = {0};
  EFI_ATA_STATUS_BLOCK               StatusBlock = {0};
  EFI_ATA_PASS_THRU_COMMAND_PACKET   CommandPacket = {0};
  EFI_STATUS                         Result;
  
  // Configure NCQ read command
  CommandBlock.AtaCommand = 0x60; // READ FPDMA QUEUED opcode
  CommandBlock.AtaSectorNumber = (UINT8)(LogicalBlockAddr & 0xFF);
  CommandBlock.AtaCylinderLow = (UINT8)((LogicalBlockAddr >> 8) & 0xFF);
  CommandBlock.AtaCylinderHigh = (UINT8)((LogicalBlockAddr >> 16) & 0xFF);
  CommandBlock.AtaDeviceHead = (UINT8)(0x40 | ((LogicalBlockAddr >> 24) & 0x0F));
  CommandBlock.AtaSectorCount = (UINT8)(BlockCount & 0xFF);
  
  // Prepare command packet
  CommandPacket.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_IN;
  CommandPacket.Length = EFI_ATA_PASS_THRU_LENGTH_BYTES | EFI_ATA_PASS_THRU_LENGTH_SECTOR_COUNT;
  CommandPacket.Acb = &CommandBlock;
  CommandPacket.Asb = &StatusBlock;
  CommandPacket.Timeout = 30000000; // 3 seconds in 100ns units
  CommandPacket.InDataBuffer = DataBuffer;
  CommandPacket.InTransferLength = BlockCount * 512; // Standard sector size
  
  // Execute command
  Result = AtaInterface->PassThru(
             AtaInterface,
             TargetPort,
             PortMultiplier,
             &CommandPacket,
             NULL
             );
  
  return Result;
}

This implementation populates the ATA command block with NCQ-specific parameters, configures the command packet for data transfer, and executes the operation through the PassThru interface.

Key Data Structures

Understanding the core data structures is fundamental to implementing ATA PassThru operations effectively.

Command Block Structure

The EFI_ATA_COMMAND_BLOCK encapsulates all command parameters:

  • AtaCommand: The ATA command opcode (e.g., 0x25 for READ SECTORS)
  • AtaFeatures: Additional command-specific features
  • AtaSectorNumber: Lower 8 bits of LBA
  • AtaCylinderLow/High: Middle bits of LBA address
  • AtaDeviceHead: Device/head selection bits
  • AtaSectorCount: Number of sectors to operate on

Status Block Structure

The EFI_ATA_STATUS_BLOCK returns command execution status:

  • AtaStatus: Command completion status flags
  • AtaError: Detailed error information when status indicates failure

Command Packet Structure

The EFI_ATA_PASS_THRU_COMMAND_PACKET serves as the primary interface:

  • Timeout: Command execution timeout in 100ns units
  • Protocol: Data transfer protocol specification
  • Length: Size indicators for command and data
  • Acb/Acb: Pointers to command and status blocks
  • Data Buffers: Input/output buffer references
  • Transfer Length: Data transfer size in bytes

Logical Block Addressing (LBA)

LBA provides linear addressing of storage sectors, abstracting physical geometry. ATA commands utilize LBA to specify data locations, supporting either 28-bit or 48-bit addressing for large capacity devices.

Memory Allocation for Command Packets

Proper memory allocation ensures alignment requirements are met:

EFI_ATA_PASS_THRU_COMMAND_PACKET *Packet;
Packet = AllocateAlignedBuffer(
           EFI_SIZE_TO_PAGES(sizeof(EFI_ATA_PASS_THRU_COMMAND_PACKET)),
           sizeof(UINT64)
           );

PassThru Implementation Architecture

The PassThru functionality follows UEFI driver binding patterns. While exploring EDK II source code, particularly for NVMe implementations, reveals similar architectural patterns.

The NVMe PassThru protocol (EFI_NVM_EXPRESS_PASS_THRU_PASSTHRU) provides analogous functionality for NVMe devices:

typedef
EFI_STATUS
(EFIAPI *EFI_NVM_EXPRESS_PASS_THRU_FUNCTION)(
  IN     EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL          *Interface,
  IN     UINT32                                      NamespaceIdentifier,
  IN OUT EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET    *CommandPackage,
  IN     EFI_EVENT                                   CompletionEvent OPTIONAL
  );

This function pointer type defines the interface for NVMe command submission, supporting both blocking and non-blocking operations.

Driver Binding Mechanism

The implementation resides in specific driver files (e.g., NvmExpressPassthru.c), with protocol binding occurring during driver initialization:

EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL  *NvmeProtocol;

// During driver initialization
NvmeProtocol->PassThru = NvmePassThruFunction;

This binding enables protocol-based function calls that route to the actual implementation, maintaining abstraction and modularity.

Driver Lifecycle Management

The UEFI driver binding protocol manages driver lifecycle through defined functions:

EFI_DRIVER_BINDING_PROTOCOL gNvmExpressDriverBinding = {
  NvmExpressSupportCheck,
  NvmExpressStartDevice,
  NvmExpressStopDevice,
  0x10,
  NULL,
  NULL
};

The system automatically manages driver loading through these interface functions:

  1. UEFI firmware loads drivers and calls entry points
  2. Entry functions install driver binding protocols
  3. System enumerates devices and checks compatibility
  4. Compatible devices trigger driver start functions
  5. Driver stop functions handle shutdown scenarios

The driver entry point installs the binding protocol:

EFI_STATUS
EFIAPI
NvmeDriverInitialize (
  IN EFI_HANDLE        DriverImage,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           DriverImage,
           SystemTable,
           &gNvmExpressDriverBinding,
           DriverImage,
           &gNvmExpressComponentName,
           &gNvmExpressComponentName2
           );
}

This architecture enables automatic driver management without explicit function calls in the codebase, as UEFI firmware handles the orchestration of driver lifecycle events.

Tags: UEFI PassThru ATA NVMe Storage

Posted on Wed, 10 Jun 2026 16:34:22 +0000 by mrwutang