3. Linux PCI 子系统架构:核心数据结构与驱动探测流程
Linux PCI 子系统是内核中最为成熟的设备模型之一,它通过三层核心数据结构——pci_bus、pci_dev、pci_driver——将物理总线拓扑、设备实例与驱动逻辑解耦。理解这三者的关系以及驱动注册与探测流程,是编写 PCIe 设备驱动的基石。
3.1 三层核心数据结构概览
下表总结了三个核心数据结构在 PCI 子系统中的角色与关键字段:
| 数据结构 | 内核头文件 | 核心角色 | 关键字段(部分) |
|---|---|---|---|
pci_bus | <linux/pci.h> | 代表一条 PCI 总线,管理挂载其上的设备与子总线 | number, parent, children, devices, ops |
pci_dev | <linux/pci.h> | 代表一个 PCI 功能(Function),包含配置空间、BAR 等资源 | bus, devfn, vendor, device, resource[] |
pci_driver | <linux/pci.h> | 代表一个 PCI 设备驱动,提供探测、移除、电源管理等回调 | name, id_table, probe, remove, driver |
关键理解:
pci_bus 是拓扑容器,pci_dev 是硬件实体,pci_driver 是软件逻辑。驱动不直接操作总线,而是通过 pci_dev 访问硬件资源。
3.2 数据结构关系:从总线到设备到驱动
下图展示了 PCI 子系统中 pci_bus、pci_dev、pci_driver 之间的静态关联关系:
3.3 驱动注册流程:pci_register_driver
PCI 设备驱动的入口是 pci_register_driver(),它完成以下关键步骤:
- 填充
pci_driver结构体:设置name、id_table、probe、remove等回调。 - 调用
pci_register_driver():内核 PCI 核心将驱动加入全局驱动链表。 - 遍历已枚举的设备:对系统中每个
pci_dev,用id_table进行匹配。 - 匹配成功则调用
probe():驱动获得设备控制权,进行资源分配与初始化。
典型驱动入口代码片段:
static struct pci_device_id my_pci_ids[] = {
{ PCI_DEVICE(0x10EE, 0x7021) },
{ /* 结束标记 */ }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
// 启用设备、分配 BAR、注册中断等
return 0;
}
static struct pci_driver my_pci_driver = {
.name = "my_pcie_drv",
.id_table = my_pci_ids,
.probe = my_pci_probe,
.remove = my_pci_remove,
};
static int __init my_init(void) {
return pci_register_driver(&my_pci_driver);
}
module_init(my_init);
3.4 设备探测流程:从枚举到 probe 回调
当系统启动或热插拔事件发生时,PCI 核心执行以下探测流程:
3.5 匹配机制:pci_device_id 与动态 ID
驱动与设备的匹配通过 pci_device_id 表实现,支持以下匹配方式:
| 匹配方式 | 宏/字段 | 说明 |
|---|---|---|
| Vendor + Device ID | PCI_DEVICE(vendor, device) | 精确匹配厂商与设备号 |
| Vendor + Class | PCI_DEVICE_CLASS(vendor, class) | 匹配厂商与设备类别 |
| Subsystem 匹配 | PCI_DEVICE_SUB(v, d, sv, sd) | 匹配子系统厂商与设备号 |
| 通配符 | PCI_ANY_ID | 匹配任意厂商/设备(慎用) |
注意事项:
id_table必须以{0,}结尾,否则内核遍历时会越界。- 驱动加载后可通过
/sys/bus/pci/drivers/<driver_name>/new_id动态添加 ID,无需重新编译。 - 热插拔设备会触发重新匹配,调用
probe()或remove()。
3.6 数据结构关系总结
以下表格从“访问路径”角度总结三者关系:
| 访问路径 | 示例代码 | 说明 |
|---|---|---|
| 从设备找总线 | pdev->bus | 获取设备所在 pci_bus |
| 从总线找设备 | list_for_each_entry(dev, &bus->devices, bus_list) | 遍历总线上的所有设备 |
| 从驱动找设备 | driver_find_device(&drv->driver, ...) | 通过驱动核心层查找已绑定的设备 |
| 从设备找驱动 | pdev->dev.driver | 获取绑定到设备的 device_driver |
实战要点: 在
probe() 中,驱动通过 pci_dev 指针获取 BAR 物理地址(pci_resource_start())、中断号(pdev->irq)等关键资源。理解 pci_bus → pci_dev → pci_driver 的三角关系,是后续 BAR 映射与 DMA 配置的前提。