4. Linux PCI 子系统架构:pci_bus、pci_dev、pci_driver 数据结构关系与内核初始化流程

Linux PCI 子系统是内核中最为成熟的设备模型之一,它通过三个核心数据结构 pci_bus、pci_dev 和 pci_driver 构建了从物理总线拓扑到设备驱动的完整抽象层。理解这三者之间的关系,以及 PCI 核心层在系统启动时的初始化流程,是掌握 PCIe 驱动开发的基础。

4.1 三大核心数据结构及其关系

Linux 内核使用面向对象的设计思想,将 PCI 总线、设备和驱动分别抽象为以下结构体:

数据结构 内核头文件 核心字段 职责
pci_bus <linux/pci.h> parent, children, devices, ops, busn_res 表示一条 PCI 总线,管理总线上的设备链表和子总线
pci_dev <linux/pci.h> bus, devfn, vendor, device, resource[], driver 表示一个 PCI 功能设备,包含配置空间、资源、驱动绑定
pci_driver <linux/pci.h> name, id_table, probe, remove, suspend, resume 表示一个 PCI 设备驱动,通过 id_table 匹配设备
关键理解: pci_bus 是拓扑骨架,pci_dev 是设备节点,pci_driver 是功能逻辑。三者通过内核设备模型(kobject / kset)和 sysfs 文件系统暴露给用户空间。

下图展示了 pci_bus、pci_dev 和 pci_driver 之间的核心关联:

pci_bus / pci_dev / pci_driver 关系图 pci_bus / pci_dev / pci_driver 核心关系 pci_bus parent / children devices (链表头) pci_dev vendor / device resource[] / irq pci_driver id_table / probe remove / suspend dev_list driver 指针 说明: pci_bus 通过 dev_list 链表挂载所有 pci_dev;pci_dev 通过 driver 指针指向匹配的 pci_driver。

4.2 pci_bus:总线拓扑的骨架

pci_bus 结构体不仅描述了一条 PCI 总线的属性,还通过 parent 和 children 指针构建了树形拓扑。系统启动时,PCI 核心层通过递归扫描发现所有总线,形成如下层次:

  • 根总线(Root Bus):通常对应 PCI 域 0 的总线 0,由平台代码注册。
  • 子总线(Secondary Bus):通过 PCI-PCI 桥接器扩展,每个桥接器创建一个新的 pci_bus 实例。
  • 总线资源:每个 pci_bus 维护 busn_res(总线号范围)和桥接器转发的 MMIO/I/O 资源窗口。
调试技巧: 在 /sys/devices/pci0000:00/ 下可以查看总线拓扑。使用 lspci -t 可以以树形结构显示总线层级。

4.3 pci_dev:设备功能的完整描述

每个 pci_dev 实例对应一个 PCI 功能(Function),其关键字段包括:

struct pci_dev {
    struct pci_bus *bus;        /* 所属总线 */
    unsigned int devfn;         /* 设备号(高5位) + 功能号(低3位) */
    unsigned short vendor;      /* 厂商ID */
    unsigned short device;      /* 设备ID */
    unsigned short subsystem_vendor; /* 子系统厂商ID */
    unsigned short subsystem_device; /* 子系统设备ID */
    struct resource resource[DEVICE_COUNT_RESOURCE]; /* BAR 资源数组 */
    struct pci_driver *driver;  /* 绑定的驱动指针 */
    unsigned int irq;           /* 中断号 */
    /* ... 更多字段 */
};

pci_dev 的 resource[] 数组存储了从配置空间 BAR 寄存器解析出的地址范围,这是驱动进行 MMIO 映射和 DMA 配置的基础。

4.4 pci_driver:驱动的注册与匹配

pci_driver 是驱动开发者直接打交道的结构体。其核心是 id_table 和 probe 回调:

struct pci_driver {
    const char *name;
    const struct pci_device_id *id_table; /* 匹配表 */
    int  (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
    void (*remove)(struct pci_dev *dev);
    int  (*suspend)(struct pci_dev *dev, pm_message_t state);
    int  (*resume)(struct pci_dev *dev);
};

匹配过程由 PCI 核心层自动完成:当一个新的 pci_dev 被添加时,核心遍历已注册的 pci_driver 列表,调用 id_table 进行匹配。匹配成功后调用 probe 回调。

常见错误: id_table 中的 vendor 和 device 字段必须与设备配置空间完全一致。如果驱动未正确匹配,请检查 lspci -n 输出的 ID 是否与 id_table 一致。

4.5 内核 PCI 核心层初始化流程

Linux 内核在启动过程中,PCI 子系统的初始化分为以下几个阶段:

阶段 函数/入口 主要工作
1. 早期初始化 pci_init() 初始化 PCI 子系统内部数据结构,注册总线类型
2. 总线扫描 pci_scan_root_bus() 从根总线开始递归扫描,创建 pci_bus 和 pci_dev
3. 资源分配 pci_assign_unassigned_resources() 为未分配 BAR 的设备分配 MMIO/I/O 资源
4. 设备添加 pci_bus_add_devices() 将 pci_dev 注册到内核设备模型,触发驱动匹配
5. 驱动绑定 driver_attach() / pci_match_device() 遍历驱动列表,匹配 id_table,调用 probe

下图展示了 PCI 核心层从系统启动到驱动绑定的完整初始化流程:

Linux PCI 核心层初始化流程 Linux PCI 核心层初始化流程 1. pci_init() 早期初始化 注册 PCI 总线类型、初始化链表 2. pci_scan_root_bus() 总线扫描 递归扫描,创建 pci_bus / pci_dev 3. 资源分配 pci_assign_unassigned_resources() 4. pci_bus_add_devices() 设备添加 注册到内核设备模型 5. 驱动匹配与 probe pci_match_device() → probe() 流程不可逆:总线扫描 → 资源分配 → 设备添加 → 驱动绑定,每个阶段依赖前一阶段完成。

4.6 数据结构与初始化流程的协同

在初始化流程中,三大数据结构按照以下顺序被创建和关联:

  1. pci_bus 先行:pci_scan_root_bus() 首先创建根总线的 pci_bus 实例。
  2. pci_dev 随扫描创建:对总线上的每个设备/功能,分配 pci_dev 并挂入 bus->devices 链表。
  3. 资源填充:读取配置空间 BAR 寄存器,填充 pci_dev->resource[]。
  4. pci_driver 匹配:pci_bus_add_devices() 触发 driver_attach(),核心遍历 pci_driver 链表进行匹配。
  5. probe 绑定:匹配成功后,pci_dev->driver 指向匹配的 pci_driver,并调用 probe 回调。
内核调试方法: 在 drivers/pci/probe.c 和 drivers/pci/bus.c 中添加 printk 或使用 trace_pci 跟踪点,可以实时观察 pci_bus 创建、pci_dev 添加和驱动匹配的过程。

4.7 总结:架构设计的精妙之处

Linux PCI 子系统的架构设计体现了以下优点:

  • 分层清晰:总线拓扑、设备描述、驱动逻辑三者解耦,各自独立演化。
  • 自动发现:核心层自动完成扫描、资源分配和驱动匹配,驱动开发者只需关注 probe/remove 回调。
  • 可扩展性:通过 pci_bus->ops 支持不同平台(x86、ARM、PowerPC)的配置空间访问方法。
  • 调试友好:sysfs 暴露了 pci_bus、pci_dev、pci_driver 的完整层次,便于用户空间诊断。
学习建议: 在开发板上执行 ls /sys/bus/pci/devices/ 和 cat /sys/bus/pci/devices/0000:00:00.0/uevent,观察 pci_dev 在 sysfs 中的呈现方式,加深对数据结构的理解。