UEFI DXE Phase: Driver Discovery and Execution

The Driver Execution Environment (DXE) phase is the central stage of the Unified Extensible Firmware Interface (UEFI) boot flow, responsible for initializing the vast majority of system hardware and setting up runtime services. This article explains how the DXE Foundation initializes, processes the Hand-Off Block (HOB) list from the preceding Pre-EFI Initialization (PEI) phase, and uses the DXE Dispatcher to discover, evaluate, and execute firmware drivers stored in Firmware Volumes (FVs) through an event-driven, dependency-based model.

The DXE Architecture and Entry

The DXE phase begins immediately after the PEI phase completes. The PEI Foundation produces the Hand-Off Block (HOB) list, which contains critical system state information such as physical memory maps, firmware volume locations, and hardware-specific configurations. Control is handed over to the DXE Foundation—a non-architectural core engine compiled as a single binary.

Upon execution, the DXE Foundation establishes core services, including:

  • Boot Services: Memory allocation, event timers, and protocol database management.
  • Runtime Services: NVRAM variable access, time services, and system reset functionality.
  • Protocol Database: A central registry mapping Unique Identifiers (GUIDs) to data structures and function pointers.

Driver Storage and Firmware Volumes

UEFI drivers reside within flash memory encapsulated inside one or more Firmware Volumes (FVs). These FVs are formatted using the Firmware File System (FFS). Individual drivers are packaged as FFS files with specific attributes, typically marked with the file type EFI_FV_FILETYPE_DRIVER or EFI_FV_FILETYPE_DXE_CORE.

Within each driver file, the binary contains:

  1. The Executable Image: Usually a PE32/PE32+ formatted binary containing the driver's logic.
  2. The Dependency Expression (Depex) Section: A compiled binary stream defining the prerequisites that must exist before the driver can safely run.

Driver Discovery: The Role of the DXE Dispatcher

The DXE Dispatcher is the component inside the DXE Foundation responsible for scheduling and executing drivers. It discovers drivers through a systematic scanning process:

  1. Locating Firmware Volumes: The Dispatcher examines the HOB list to identify all published Firmware Volumes. It also listens for EFI_FIRMWARE_VOLUME2_PROTOCOL instances produced during the boot process to discover additional volumes, such as option ROMs on expansion cards.
  2. Parsing the FFS Files: The Dispatcher iterates through each volume, inspecting the file headers to locate unexecuted drivers.
  3. Building the Execution Queue: The Dispatcher adds discovered drivers to an internal list of candidate images, categorizing their dependencies and execution states.

Prioritization and Dependency Evaluation

To avoid race conditions and ensure correct hardware initialization order, the Dispatcher relies on two mechanisms: the aPRIORI file and Dependency Expressions (DEPEX).

The aPRIORI File

Firmware engineers can include an optional aPRIORI file within a Firmware Volume. This file lists GUIDs of critical drivers that must be executed first, in an explicit, linear order, bypassing dependency evaluation. This is commonly used to load fundamental services like memory managers or architectural protocols required by the Dispatcher itself.

Dependency Expressions (DEPEX)

For all drivers not governed by an aPRIORI list, the Dispatcher evaluates their DEPEX section. A DEPEX consists of byte-code instructions representing Boolean logic (such as AND, OR, NOT, TRUE, END) evaluated against the GUIDs registered in the central Protocol Database.

For example, a PCI host controller driver might have a DEPEX requiring the EFI_CPU_IO2_PROTOCOL. The Dispatcher reads this expression and queries the Protocol Database. If the protocol is not present, the expression evaluates to FALSE, and the driver remains deferred.

Execution and the Protocol Loop

Once the Dispatcher evaluates a driver's DEPEX to TRUE, execution proceeds through the following steps:

  1. Loading the Image: The Dispatcher uses the LoadImage() Boot Service to copy the PE32+ driver into system memory, fix up relocations, and assign it an EFI_HANDLE.
  2. Calling the Entry Point: The Dispatcher invokes StartImage(), calling the driver's standard entry function (EFI_IMAGE_ENTRY_POINT).
  3. Protocol Registration: During its execution, the driver initializes its hardware target and registers one or more protocols in the Protocol Database using InstallProtocolInterface() or InstallMultipleProtocolInterfaces().
  4. Re-evaluating the Queue: The addition of new protocols triggers the Dispatcher to re-evaluate the DEPEX expressions of all deferred drivers. A driver that previously evaluated to FALSE may now evaluate to TRUE.

This process runs as an iterative loop. The Dispatcher continuously scans the firmware volumes, executes drivers whose dependencies are satisfied, publishes their new protocols, and evaluates deferred drivers until no more drivers can be executed.

Transition to Boot Device Selection (BDS)

The DXE phase ends when the Dispatcher can execute no further drivers and all core Architectural Protocols (such as Timer, Real Time Clock, Watchdog, and Variable Services) are installed. At this point, the Dispatcher passes execution control to the BDS Architectural Protocol (EFI_BDS_ARCH_PROTOCOL), transferring the platform into the Boot Device Selection phase to detect boot media and load the operating system.