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 之间的核心关联:
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 核心层从系统启动到驱动绑定的完整初始化流程:
4.6 数据结构与初始化流程的协同
在初始化流程中,三大数据结构按照以下顺序被创建和关联:
- pci_bus 先行:pci_scan_root_bus() 首先创建根总线的 pci_bus 实例。
- pci_dev 随扫描创建:对总线上的每个设备/功能,分配 pci_dev 并挂入 bus->devices 链表。
- 资源填充:读取配置空间 BAR 寄存器,填充 pci_dev->resource[]。
- pci_driver 匹配:pci_bus_add_devices() 触发 driver_attach(),核心遍历 pci_driver 链表进行匹配。
- 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 中的呈现方式,加深对数据结构的理解。