UEFI PCI Subsystem Overview

Understanding how UEFI EDK2 discovers, enumerates, and manages PCI/PCIe devices

PEI Phase
DXE Enum
Resource Alloc
Driver Install
BDS Boot
P

What is PCI/PCIe?

B

Why should BIOS engineers understand PCI?

A

x86_64 vs ARM64

PCI Configuration Space

Full 256-byte PCI header and 4K PCIe Extended Configuration Space

Type 0 Header (Endpoint Device)

Type 1 Header (PCI Bridge)

PCIe Extended Config Space

Configuration Space Access Mechanism

How x86 and ARM platforms read/write PCI configuration space

ECAM_BASE + (Bus << 20) + (Dev << 15) + (Fun << 12) + Offset

x86_64 Access Methods

Legacy I/O Port (CF8h/CFCh)

ECAM / MMCONFIG

ARM64 Access Method

Pure ECAM (Memory-Mapped)

PCI Enumeration Flow

Complete device discovery and resource allocation flow in PciBusDxe

Step 1 / 11

PCI Root Bridge IO Protocol

Platform-level PCI access abstraction

Producer: PciHostBridgeDxe (platform-specific) Consumer: PciBusDxe, Option ROM loader

Pci.Read / Pci.Write

Mem.Read / Mem.Write

Io.Read / Io.Write

Map / Unmap

AllocateBuffer / FreeBuffer

Configuration

PCI IO Protocol

Device-level PCI access interface

Producer: PciBusDxe (auto-installed per device) Consumer: Device drivers (NVMe, USB, NIC, GOP, etc.)

Pci.Read / Pci.Write

Mem.Read / Mem.Write

Io.Read / Io.Write

Map / Unmap

AllocateBuffer / FreeBuffer

GetLocation

Attributes / GetBarAttributes / SetBarAttributes

RomImage / RomSize

x86_64 vs ARM64 Architecture Comparison

Key differences in the PCI subsystem across architectures

Code Analysis

Key EDK2 PCI subsystem source code snippets

PCI Bus Scan (PciBusDxe)

// MdeModulePkg/Bus/Pci/PciBusDxe/PciEnumerator.c (simplified)
for (Bus = StartBus; Bus <= EndBus; Bus++) {
  for (Device = 0; Device <= PCI_MAX_DEVICE; Device++) {
    for (Func = 0; Func <= PCI_MAX_FUNC; Func++) {

      Status = PciRootBridgeIo->Pci.Read (
                 PciRootBridgeIo,
                 EfiPciWidthUint32,
                 EFI_PCI_ADDRESS (Bus, Device, Func, 0),
                 1,
                 &PciData
               );

      if (PciData.Hdr.VendorId == 0xFFFF) {
        // No device present
        if (Func == 0) break;  // Skip remaining functions
        continue;
      }

      // Device found — create PCI_IO_DEVICE
      PciDevice = CreatePciIoDevice (Bridge, Bus, Device, Func);

      // Check multi-function bit
      if (Func == 0 &&
          !IS_PCI_MULTI_FUNC (&PciData)) {
        break;  // Not multi-function, skip Func 1-7
      }
    }
  }
}

BAR Sizing

// MdeModulePkg/Bus/Pci/PciBusDxe/PciResourceSupport.c (simplified)
for (Offset = 0x10; Offset <= 0x24; Offset += 4) {
  // 1. Save original BAR value
  PciIo->Pci.Read  (PciIo, EfiPciIoWidthUint32, Offset, 1, &OriginalValue);

  // 2. Write all 1s
  AllOnes = 0xFFFFFFFF;
  PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, Offset, 1, &AllOnes);

  // 3. Read back to get size mask
  PciIo->Pci.Read  (PciIo, EfiPciIoWidthUint32, Offset, 1, &ReadBack);

  // 4. Restore original value
  PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, Offset, 1, &OriginalValue);

  if (ReadBack == 0) continue;  // BAR not implemented

  if (ReadBack & 0x01) {
    // I/O BAR: mask low 2 bits
    Size = ~(ReadBack & 0xFFFFFFFC) + 1;
  } else {
    // Memory BAR: mask low 4 bits
    Size = ~(ReadBack & 0xFFFFFFF0) + 1;
    Is64Bit = ((ReadBack & 0x06) == 0x04);
  }
}

PCI IO Protocol Usage Example

// Example: Device driver reads Vendor/Device ID
EFI_STATUS           Status;
EFI_PCI_IO_PROTOCOL  *PciIo;
UINT16               VendorId;
UINT16               DeviceId;

// Open PCI IO Protocol on the device handle
Status = gBS->OpenProtocol (
               DeviceHandle,
               &gEfiPciIoProtocolGuid,
               (VOID **)&PciIo,
               DriverBinding->DriverBindingHandle,
               DeviceHandle,
               EFI_OPEN_PROTOCOL_BY_DRIVER
             );

// Read Vendor ID (offset 0x00)
Status = PciIo->Pci.Read (
           PciIo,
           EfiPciIoWidthUint16,
           PCI_VENDOR_ID_OFFSET,   // 0x00
           1,
           &VendorId
         );

// Read Device ID (offset 0x02)
Status = PciIo->Pci.Read (
           PciIo,
           EfiPciIoWidthUint16,
           PCI_DEVICE_ID_OFFSET,   // 0x02
           1,
           &DeviceId
         );

DEBUG ((DEBUG_INFO, "Found device %04X:%04X\n", VendorId, DeviceId));

Root Bridge IO Config Space Access

// MdeModulePkg/Bus/Pci/PciHostBridgeDxe (ECAM path, simplified)
EFI_STATUS
RootBridgeIoPciRead (
  IN     EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       *This,
  IN     EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH  Width,
  IN     UINT64                                 Address,
  IN     UINTN                                  Count,
  OUT    VOID                                   *Buffer
  )
{
  PCI_ROOT_BRIDGE_INSTANCE  *RootBridge;
  UINT64                    EcamBase;
  UINT64                    PciAddress;

  RootBridge = ROOT_BRIDGE_FROM_THIS (This);
  EcamBase   = RootBridge->EcamBase;

  // Calculate ECAM address:
  // Address encodes Bus[23:16] Dev[15:11] Fun[10:8] Reg[7:0]
  PciAddress = EcamBase
             + ((RB_PCI_BUS(Address))  << 20)
             + ((RB_PCI_DEV(Address))  << 15)
             + ((RB_PCI_FUNC(Address)) << 12)
             + RB_PCI_REG(Address);

  // Perform memory-mapped read
  return MmioReadBuffer (Width, PciAddress, Count, Buffer);
}

References

Further reading and official specifications

PCI/PCIe Specifications

PCI-SIG official specifications — PCI Local Bus, PCIe Base Spec, ECN

EDK2 Source Code

MdeModulePkg/Bus/Pci — PciBusDxe, PciHostBridgeDxe, PciSioSerialDxe

UEFI/PI Specifications

UEFI Spec Chapter 14 (PCI Bus Support), PI Spec Vol 5 (PCI Host Bridge)

Other Resources

OSDev Wiki PCI page, ECAM specification, MCFG ACPI table documentation