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:
- UEFI firmware loads drivers and calls entry points
- Entry functions install driver binding protocols
- System enumerates devices and checks compatibility
- Compatible devices trigger driver start functions
- 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.