2. Linux PCIe 子系统架构:核心层、总线层与驱动层
PCIe 子系统在 Linux 内核中采用分层架构设计,从上到下依次为 驱动层、核心层 和 总线层。这种分层结构使得 PCIe 设备的枚举、配置空间访问、资源分配与驱动绑定等职责清晰分离,同时也为热插拔事件的传递与回调注册提供了稳定的基础设施。
2.1 三层职责划分
| 层次 | 核心职责 | 关键数据结构/接口 | 热插拔相关 |
|---|---|---|---|
| 驱动层 | 设备功能驱动实现,与具体硬件交互 | pci_driver、pci_device_id |
注册 probe()/remove() 回调;PM 回调处理热插拔恢复 |
| 核心层 | 设备枚举、配置空间管理、资源分配、热插拔事件调度 | pci_dev、pci_bus、pci_ops |
pciehp/shpchp 中断服务;总线重扫描触发 |
| 总线层 | 总线类型注册、设备/驱动匹配、属性文件导出 | pci_bus_type、bus_attribute |
通过 bus->probe() 回调驱动层;uevent 通知用户空间 |
关键理解: 驱动层不直接操作总线扫描或配置空间访问,这些由核心层封装。驱动只需关注
pci_driver 结构体中的回调函数,热插拔事件到达时核心层会自动调用 probe() 或 remove()。
2.2 pci_bus 数据结构
pci_bus 是 PCIe 总线节点的内核表示,每个物理 PCI/PCIe 总线对应一个 pci_bus 实例。它维护了该总线上的设备链表、子总线链表以及资源分配信息。
struct pci_bus {
struct list_head node; /* 全局总线链表节点 */
struct pci_bus *parent; /* 父总线(上游) */
struct list_head children; /* 子总线链表 */
struct list_head devices; /* 该总线上的设备链表 */
struct pci_dev *self; /* 若为桥接设备,指向对应的 pci_dev */
struct list_head slots; /* 槽位链表(热插拔相关) */
struct resource *resource[PCI_BRIDGE_RESOURCE_NUM]; /* 桥接窗口资源 */
unsigned char number; /* 总线号 */
unsigned char primary; /* 主总线号(桥接用) */
unsigned char secondary; /* 次总线号 */
unsigned char subordinate; /* 从属总线号范围上限 */
unsigned int is_added : 1; /* 是否已添加到设备模型 */
const struct pci_bus_ops *ops; /* 总线操作(配置空间读写) */
void *sysdata; /* 体系结构私有数据 */
};
2.3 pci_dev 数据结构
pci_dev 是 PCIe 设备在内核中的核心表示,每个物理 PCIe 功能(Function)对应一个 pci_dev 实例。它包含了设备的配置空间信息、资源映射、电源管理状态以及热插拔状态。
struct pci_dev {
struct list_head bus_list; /* 所在总线的设备链表节点 */
struct pci_bus *bus; /* 所属总线 */
struct pci_bus *subordinate; /* 若为桥接设备,指向下游总线 */
unsigned int devfn; /* 设备号+功能号 (device<<3 | function) */
unsigned short vendor; /* Vendor ID */
unsigned short device; /* Device ID */
unsigned short subsystem_vendor; /* Subsystem Vendor ID */
unsigned short subsystem_device; /* Subsystem Device ID */
unsigned int class; /* Class Code */
u8 revision; /* Revision ID */
u8 hdr_type; /* 配置空间头部类型 */
struct resource resource[DEVICE_COUNT_RESOURCE]; /* BAR 资源数组 */
struct pci_driver *driver; /* 绑定的驱动 */
struct device dev; /* 内核设备模型基类 */
unsigned int is_added : 1; /* 是否已添加到驱动核心 */
unsigned int is_busmaster : 1; /* 总线主控使能 */
unsigned int no_d1d2 : 1; /* 不支持 D1/D2 电源状态 */
unsigned int d3_delay; /* D3 状态切换延迟 */
unsigned int current_state; /* 当前电源状态 (D0/D1/D2/D3hot/D3cold) */
int error_state; /* 通道错误状态 */
struct pcie_link_state *link_state; /* PCIe 链路状态 */
};
热插拔关键字段:
bus— 设备所在总线,热插拔移除时从该总线的devices链表摘除driver— 指向当前绑定的pci_driver,热插拔事件触发remove()回调current_state— 电源状态,热插拔恢复时需从 D3cold 切换回 D0error_state— 记录 PCIe 错误状态,热插拔事件可能伴随链路错误
2.4 pci_bus 与 pci_dev 的关联关系
| 关系类型 | 数据结构字段 | 说明 |
|---|---|---|
| 设备 → 总线 | pci_dev.bus |
指向设备所在的 pci_bus,用于配置空间访问和资源查询 |
| 总线 → 设备 | pci_bus.devices |
链表头,遍历该总线上的所有 pci_dev |
| 桥接设备 → 子总线 | pci_dev.subordinate |
若设备是 PCIe 桥,指向下游的 pci_bus |
| 总线 → 父总线 | pci_bus.parent |
指向上游总线,根总线的 parent 为 NULL |
| 总线 → 子总线 | pci_bus.children |
链表头,遍历所有下游子总线 |
2.5 热插拔事件在分层架构中的传递路径
- 硬件中断触发:PCIe 热插拔控制器(如
pciehp)检测到槽位状态变化(Presence Detect 或 Button Pressed),产生中断。 - 总线层响应:中断服务程序调用
pciehp_handle_presence_change(),通过pci_bus的ops操作配置空间。 - 核心层重扫描:调用
pci_scan_slot()/pci_scan_bus()枚举新设备,分配pci_dev并插入pci_bus.devices链表。 - 驱动层回调:核心层通过
pci_bus_add_device()将设备添加到驱动核心,触发pci_driver->probe()回调。 - 移除流程:拔出时核心层调用
pci_stop_and_remove_bus_device(),依次触发driver->remove()并释放pci_dev。
工程注意事项: 驱动层注册热插拔回调时,必须确保
pci_driver 的 probe() 和 remove() 是幂等的(可重复调用),因为热插拔事件可能因电气噪声产生多次触发。同时,pci_dev 的 current_state 在热插拔恢复后可能为 D3cold,驱动需在 probe() 中显式恢复配置空间。
2.6 数据结构在热插拔回调注册中的角色
| 数据结构 | 热插拔回调注册中的角色 | 典型操作 |
|---|---|---|
pci_bus |
提供总线扫描接口和槽位管理链表 | pci_scan_bus() 重新枚举;pci_bus_add_devices() 添加新设备 |
pci_dev |
代表热插拔设备实例,承载驱动绑定和资源信息 | pci_dev_get()/pci_dev_put() 引用计数管理 |
pci_driver |
驱动注册入口,包含 probe/remove 回调 |
pci_register_driver() 注册;pci_unregister_driver() 注销 |
pciehp_slot |
热插拔槽位状态管理,与 pci_bus.slots 关联 |
注册中断处理函数;设置槽位控制寄存器 |
学习路径建议: 理解
pci_bus 和 pci_dev 的关系是掌握 PCIe 热插拔回调注册的基础。下一章将深入 pci_driver 结构体,讲解如何在实际工程中注册热插拔感知的回调函数。建议读者在阅读后续章节前,先在内核源码中查找 include/linux/pci.h 中的这两个结构体定义,加深印象。