Linux PCIe驱动开发调试与部署 - 第3节
3.1 PCIe 子系统整体架构
Linux PCIe 子系统采用分层设计,从底层的硬件访问到上层的驱动绑定,形成了一条清晰的数据与控制路径。理解 pci_bus、pci_dev、pci_driver 这三个核心数据结构及其相互关系,是掌握 PCIe 驱动开发的基础。
核心要点: PCIe 子系统通过
pci_bus 管理总线拓扑,通过 pci_dev 描述具体设备,通过 pci_driver 实现驱动逻辑。内核的 PCI 核心 API 负责这三者的创建、注册与匹配。
下图展示了 PCIe 子系统在 Linux 内核中的层次位置与核心组件关系:
3.2 核心数据结构详解
3.2.1 pci_bus:总线管理单元
pci_bus 结构体代表一条 PCI 总线,是总线拓扑管理的核心。每个 PCI 段(Segment)下的每条总线都有一个对应的 pci_bus 实例。
| 成员 | 类型 | 描述 |
|---|---|---|
number | unsigned char | 总线号(Bus Number) |
primary | unsigned char | 主总线号(用于桥接设备) |
secondary | unsigned char | 从总线号 |
subordinate | unsigned char | 最大从总线号 |
devices | struct list_head | 挂载在此总线上的所有 pci_dev 链表 |
children | struct list_head | 子总线链表 |
parent | struct pci_bus * | 指向父总线 |
ops | struct pci_ops * | 总线访问操作函数集 |
resource | struct resource * | 总线资源(IO/MEM 范围) |
调试技巧: 通过
/sys/bus/pci/devices/ 下的符号链接可以反向查找设备所属的 pci_bus。使用 lspci -t 可以查看总线树形拓扑。
3.2.2 pci_dev:设备描述符
pci_dev 是 PCIe 设备在内核中的核心描述符,包含设备的配置空间信息、资源分配、电源管理状态等。每个物理功能(PF)和虚拟功能(VF)都对应一个 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 资源数组
unsigned int irq; // 中断号
struct pci_driver *driver; // 绑定的驱动
// ... 更多成员
};
注意:
devfn 字段编码了设备号和功能号,高 5 位为设备号,低 3 位为功能号。使用 PCI_SLOT(devfn) 和 PCI_FUNC(devfn) 宏进行解包。
3.2.3 pci_driver:驱动实体
pci_driver 是驱动开发者需要直接操作的结构体。它定义了驱动的生命周期回调函数以及设备 ID 匹配表。
struct pci_driver {
const char *name; // 驱动名称
const struct pci_device_id *id_table; // 支持的设备 ID 表
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);
// ... 其他回调
};
设备 ID 表 pci_device_id 用于驱动与设备的匹配:
struct pci_device_id {
__u32 vendor, device; // Vendor/Device ID
__u32 subvendor, subdevice; // Subsystem ID
__u32 class, class_mask; // Class Code 及其掩码
unsigned long driver_data; // 驱动私有数据
};
匹配优先级: 内核优先匹配
vendor + device,其次匹配 subvendor + subdevice,最后匹配 class。匹配顺序由 id_table 中的条目顺序决定。
3.3 内核 PCI 核心 API
PCI 核心 API 是驱动开发者与内核 PCI 子系统交互的桥梁。以下按功能分类列出最常用的 API。
3.3.1 驱动注册与注销
| API | 功能 | 调用时机 |
|---|---|---|
pci_register_driver() | 注册 pci_driver 到内核 | 模块初始化 |
pci_unregister_driver() | 注销 pci_driver | 模块退出 |
pci_match_id() | 手动匹配设备 ID 表 | probe 回调中 |
3.3.2 设备资源访问
| API | 功能 |
|---|---|
pci_resource_start() | 获取指定 BAR 的起始物理地址 |
pci_resource_end() | 获取指定 BAR 的结束物理地址 |
pci_resource_len() | 获取指定 BAR 的长度 |
pci_resource_flags() | 获取指定 BAR 的类型标志(IO/MEM/64bit) |
pci_iomap() | 将 BAR 映射到内核虚拟地址空间 |
pci_iounmap() | 解除映射 |
3.3.3 配置空间访问
// 读取配置空间
int pci_read_config_byte (struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word (struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
// 写入配置空间
int pci_write_config_byte (struct pci_dev *dev, int where, u8 val);
int pci_write_config_word (struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
3.3.4 DMA 与中断
| API | 功能 |
|---|---|
pci_set_master() | 启用设备总线主控能力(Bus Master) |
pci_alloc_consistent() | 分配一致性 DMA 缓冲区 |
pci_free_consistent() | 释放一致性 DMA 缓冲区 |
pci_request_irq() | 注册中断处理函数(已废弃,推荐 request_irq) |
pci_enable_msi() | 启用 MSI 中断 |
pci_enable_msix() | 启用 MSI-X 中断 |
最佳实践: 在 probe 回调中,典型的初始化顺序为:
pci_enable_device() → pci_set_master() → pci_request_regions() → pci_iomap() → pci_enable_msi()。
3.4 设备枚举与驱动绑定流程
从系统启动到驱动 probe 被调用,经历了完整的设备枚举与匹配过程。下图展示了这一关键流程:
调试验证: 在
/sys/bus/pci/drivers/ 目录下可以查看所有已注册的 pci_driver。使用 echo "vendor device" > /sys/bus/pci/drivers/xxx/bind 可以手动绑定驱动。
3.5 数据结构关系总结
下表总结了三个核心数据结构在 PCIe 子系统中的角色与关系:
| 数据结构 | 角色 | 关键链表/指针 | 生命周期 |
|---|---|---|---|
pci_bus | 总线拓扑节点 | devices、children、parent | 系统启动时创建,热插拔时动态更新 |
pci_dev | 设备实例 | bus_list、bus、driver | 枚举时创建,设备移除时销毁 |
pci_driver | 驱动逻辑 | id_table、probe、remove | 模块加载时注册,卸载时注销 |
常见错误: 在 probe 中未调用
pci_enable_device() 会导致设备无法响应 MMIO 访问;未调用 pci_request_regions() 会导致资源冲突。这两个调用是 probe 的必备步骤。