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 之间的静态关联关系:

PCI 子系统核心数据结构关系图 pci_bus number = 0 (Root Bus) children → pci_bus (次级) devices → list_head pci_dev vendor=0x10EE, device=0x7021 resource[0..5] → BAR 地址 bus → pci_bus * pci_driver name = "my_pcie_drv" id_table → pci_device_id[] probe() / remove() devices 链表 driver 匹配 说明:pci_bus 通过 devices 链表持有多个 pci_dev;每个 pci_dev 通过 bus 指针回指所属总线;pci_driver 通过 id_table 与 pci_dev 匹配,匹配成功后调用 probe()。 三者形成“总线 → 设备 → 驱动”的三角关系,由内核 PCI 核心层协调。

3.3 驱动注册流程:pci_register_driver

PCI 设备驱动的入口是 pci_register_driver(),它完成以下关键步骤:

  1. 填充 pci_driver 结构体:设置 name、id_table、probe、remove 等回调。
  2. 调用 pci_register_driver():内核 PCI 核心将驱动加入全局驱动链表。
  3. 遍历已枚举的设备:对系统中每个 pci_dev,用 id_table 进行匹配。
  4. 匹配成功则调用 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 核心执行以下探测流程:

PCI 设备探测流程(从枚举到 probe) 1. 总线枚举 (pci_scan_bus) 2. 创建 pci_dev 实例 3. 读取配置空间 (Vendor/Device ID) 4. 分配资源 (BAR/IRQ) 5. 匹配驱动 → 调用 probe() 注:步骤 4 中 BAR 资源分配由 PCI 核心完成,驱动在 probe() 中通过 pci_resource_start() 获取。

3.5 匹配机制:pci_device_id 与动态 ID

驱动与设备的匹配通过 pci_device_id 表实现,支持以下匹配方式:

匹配方式宏/字段说明
Vendor + Device IDPCI_DEVICE(vendor, device)精确匹配厂商与设备号
Vendor + ClassPCI_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 配置的前提。