3. PCIe 地址空间:内存空间、I/O 空间、配置空间、BDF 编码规则

PCIe 总线继承了传统 PCI 的地址空间模型,并在此基础上进行了扩展。理解这三种地址空间(Memory、I/O、Configuration)以及设备标识符 BDF(Bus/Device/Function)是进行设备枚举和驱动开发的基础。本章将深入剖析这些核心概念。

3.1 PCIe 三大地址空间概述

PCIe 规范定义了三种独立的地址空间,用于不同的访问目的。驱动程序通过操作这些空间来控制设备。

地址空间 访问目标 主要用途 地址宽度
Memory Space 设备内部寄存器、数据缓冲区、MMIO 高性能数据交换、控制寄存器访问 32-bit 或 64-bit
I/O Space 传统 I/O 端口(x86 架构特有) 兼容旧设备、简单控制寄存器 32-bit
Configuration Space PCIe 配置寄存器(Capability 结构) 设备枚举、资源分配、错误报告、电源管理 8-bit(通过 ECAM 机制访问)
关键点:在现代 PCIe 驱动开发中,Memory Space 是主要操作对象。I/O Space 在 x86 平台外已基本废弃,而 Configuration Space 是操作系统和固件进行设备发现与初始化的核心战场。

3.2 配置空间(Configuration Space)详解

每个 PCIe 设备(Function)都拥有最多 4KB 的配置空间。前 256 字节(0x00-0xFF)是兼容 PCI 的配置头(Configuration Header),后 256 字节(0x100-0xFFF)用于存放 PCIe 扩展能力(PCIe Extended Capabilities)。

PCIe 配置空间布局(4KB) 传统配置头 (Legacy Configuration Header) - 256 字节 Vendor ID / Device ID Status / Command Class Code / Revision ID Base Address Registers (BAR0 - BAR5) - 6 x 32-bit Capabilities Pointer PCIe 扩展配置空间 (PCIe Extended Configuration Space) - 3840 字节 PCI Express Capability Advanced Error Reporting (AER) Device Serial Number Power Budgeting ... 更多扩展能力,通过链表结构串联 Cap Pointer 指向

3.2.1 配置空间中的关键寄存器

  • Vendor ID (0x00): 设备制造商编号(如 0x8086 为 Intel)。
  • Device ID (0x02): 设备型号编号。
  • Command Register (0x04): 控制设备行为(如 I/O Space Enable、Memory Space Enable、Bus Master Enable)。
  • Status Register (0x06): 反映设备状态(如 Interrupt Status、Capabilities List)。
  • Class Code (0x09-0x0B): 设备功能分类(如 0x010000 为 Mass Storage Controller)。
  • Base Address Registers (BARs) (0x10-0x24): 设备请求的 Memory/I/O 空间基地址和大小。
  • Capabilities Pointer (0x34): 指向第一个 Capability 结构的偏移。
驱动开发实战:在 Linux 内核中,通过 pci_read_config_byte/word/dword() 和 pci_write_config_byte/word/dword() 系列函数访问配置空间。例如,读取 Vendor ID 的代码片段如下:
#include <linux/pci.h>

struct pci_dev *dev = ...; // 获取设备结构体
u16 vendor_id;
pci_read_config_word(dev, PCI_VENDOR_ID, &vendor_id);
printk(KERN_INFO "Vendor ID: 0x%04x\n", vendor_id);

3.3 内存空间与 I/O 空间

设备通过 BAR 寄存器向系统请求地址空间。驱动程序需要将 BAR 中记录的物理地址映射到内核虚拟地址空间才能访问。

3.3.1 Base Address Registers (BARs)

每个 Function 最多有 6 个 BAR(BAR0-BAR5)。BAR 的类型决定了它请求的是 Memory 空间还是 I/O 空间。

BAR 类型 Bit 0 值 地址宽度 典型用途
Memory BAR 0 32-bit 或 64-bit (由 Bit 2-1 决定) 设备寄存器、DMA 缓冲区、帧缓冲
I/O BAR 1 32-bit 传统 I/O 端口(x86 平台)

3.3.2 内存空间映射(MMIO)

Memory BAR 请求的物理地址空间,在驱动中必须通过 ioremap() 映射为内核虚拟地址才能安全访问。

// 假设已经获取了 pci_dev 结构体 dev
// 获取 BAR0 的物理地址和大小
resource_size_t phys_addr = pci_resource_start(dev, 0);
resource_size_t size = pci_resource_len(dev, 0);

// 将物理地址映射到内核虚拟地址空间
void __iomem *virt_addr = ioremap(phys_addr, size);
if (!virt_addr) {
    dev_err(&dev->dev, "ioremap failed\n");
    return -ENOMEM;
}

// 通过映射后的虚拟地址读写设备寄存器
u32 reg_val = ioread32(virt_addr + REG_OFFSET);
iowrite32(0x1, virt_addr + CONTROL_REG);

// 使用完毕后取消映射
iounmap(virt_addr);
常见错误:直接对 phys_addr 进行指针解引用会导致内核崩溃。必须使用 ioremap() 和 ioread32()/iowrite32() 系列函数进行访问。

3.4 BDF 编码规则

BDF(Bus:Device.Function)是 PCIe 总线上唯一标识一个 Function 的地址。它由三个字段组成:

  • Bus Number (8-bit): 总线编号,范围 0-255。Root Complex 下的总线通常为 0。
  • Device Number (5-bit): 设备编号,范围 0-31。每个 Bus 最多挂载 32 个 Device。
  • Function Number (3-bit): 功能编号,范围 0-7。每个 Device 最多支持 8 个 Function。
BDF 编码结构 Bus Number 8-bit (0-255) Device Number 5-bit (0-31) Function 3-bit (0-7) Bit 15-8 Bit 7-3 Bit 2-0 示例:BDF = 00:1f.0 Bus=0x00, Device=0x1f (31), Function=0x0 通常对应 LPC 控制器或 ISA Bridge

3.4.1 BDF 在配置空间访问中的应用

在 x86 平台上,访问配置空间有两种传统方法:

  1. I/O 端口方法(CF8/CFC): 向 I/O 端口 0xCF8 写入 BDF 和寄存器偏移,然后从 0xCFC 读写数据。
  2. ECAM(Enhanced Configuration Access Mechanism): 将配置空间映射到一段连续的 Memory 区域,通过 MMIO 直接访问。这是 PCIe 的推荐方法。

ECAM 的地址映射公式如下:

// ECAM 地址计算
// 基地址 + (Bus * 32 * 8 * 4096) + (Device * 8 * 4096) + (Function * 4096) + RegisterOffset
// 简化公式:
// ECAM_Address = ECAM_Base + (BDF << 12) + RegisterOffset
// 其中 BDF = (Bus << 8) | (Device << 3) | Function

u32 bdf = (bus << 8) | (device << 3) | function;
void __iomem *ecam_base = ...; // 系统提供的 ECAM 基地址
void __iomem *reg_addr = ecam_base + (bdf << 12) + reg_offset;
u32 value = ioread32(reg_addr);
内核视角:Linux 内核的 PCI 子系统已经封装了 BDF 和 ECAM 的细节。驱动开发者通常不需要手动计算 ECAM 地址,而是使用 pci_get_domain_bus_and_slot() 等高层 API 来定位设备。
struct pci_dev *dev;
// 查找 BDF 为 0000:00:1f.0 的设备
dev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x1f, 0));
if (dev) {
    // 操作设备
    pci_dev_put(dev); // 释放引用
}

3.5 本章小结

  • PCIe 定义了 Memory、I/O 和 Configuration 三种地址空间,驱动主要操作 Memory 和 Configuration 空间。
  • 配置空间是设备枚举和初始化的核心,包含 Vendor ID、BARs、Capabilities 等关键信息。
  • BAR 寄存器决定了设备请求的地址空间类型和大小,驱动必须通过 ioremap() 映射后才能访问。
  • BDF 是设备的唯一硬件地址,在 ECAM 机制下直接用于计算配置空间的 MMIO 偏移。