Linux PCIe设备枚举与资源分配调试 - 第4节

4.1 PCI 子系统核心数据结构总览

Linux 内核 PCI 子系统围绕三个核心结构体构建:pci_bus(总线)、pci_dev(设备)和 pci_driver(驱动)。它们共同构成了 PCI 设备枚举、资源分配和驱动绑定的基础框架。理解这三个结构体之间的关系,是掌握 PCIe 驱动开发的关键。

核心关系:pci_bus 表示一条物理或逻辑 PCI 总线,pci_dev 表示挂接在总线上的一个 PCI 功能(Function),pci_driver 表示能够驱动某个 pci_dev 的内核驱动模块。三者通过链表和指针形成树状拓扑。
PCI 子系统三大核心结构体关系 pci_bus / pci_dev / pci_driver 关系图 pci_bus 表示一条 PCI 总线 包含 children / devices 链表 pci_dev 表示一个 PCI 功能 包含 bus / driver / dev 指针 pci_driver 表示 PCI 设备驱动 包含 id_table / probe / remove 枚举流程 pci_bus → pci_dev(扫描发现)→ pci_driver(匹配绑定) 每个 pci_dev 通过 bus 指针指向所属 pci_bus,通过 driver 指针指向绑定的 pci_driver pci_bus 通过 devices 链表管理其下所有 pci_dev

4.2 pci_bus 结构体:总线的抽象

pci_bus 是 Linux 内核中对一条 PCI 总线的软件抽象。它既代表物理上的 PCI 总线(如 Bus 0、Bus 1),也代表通过 PCIe 桥接器扩展出的逻辑总线。每个 pci_bus 实例维护了该总线的拓扑信息、资源分配状态以及子设备链表。

4.2.1 关键字段

字段名 类型 说明
number unsigned char 总线号(Bus Number),由 PCI 配置空间分配
primary unsigned char 主总线号(仅对桥设备有意义)
secondary unsigned char 从属总线号(桥下游总线)
subordinate unsigned char 最大从属总线号(桥可访问的总线范围上限)
devices struct list_head 挂接在该总线上的所有 pci_dev 链表头
children struct list_head 子总线链表(通过 PCI 桥连接的下级总线)
self struct pci_dev * 指向代表该总线自身的桥设备(若为根总线则为 NULL)
parent struct pci_bus * 指向父总线(上级总线)
resource struct resource * 总线级别的资源(IO、MEM 地址空间)
ops struct pci_ops * 总线访问操作函数集(配置空间读写)
理解要点:pci_bus 的 number、secondary、subordinate 三个字段共同定义了该总线的地址范围。例如,一个 PCI 桥的 secondary=1、subordinate=3,表示桥下游管理 Bus 1~3 共三条总线。

4.2.2 总线树状拓扑

PCI 总线系统是一个树状结构:根总线(Root Bus)通常是 Bus 0,通过 PCI 桥(PCI-to-PCI Bridge)扩展出子总线。每个 pci_bus 通过 children 链表链接子总线,通过 devices 链表链接直接挂接的设备。

pci_bus 树状拓扑结构 PCI 总线树状拓扑 pci_bus 0 (Root Bus) number=0, secondary=0, subordinate=0 PCI 桥 pci_bus 1 number=1, secondary=1, subordinate=1 pci_bus 2 number=2, secondary=2, subordinate=2 pci_bus 3 number=3, secondary=3, subordinate=3 pci_dev 0:1.0 pci_dev 0:2.0 pci_dev 0:3.0 每个 pci_bus 通过 children 链表链接子总线,通过 devices 链表链接设备

4.3 pci_dev 结构体:设备的抽象

pci_dev 是内核中对一个 PCI 功能(Function)的完整描述。每个 PCI 设备(无论是物理设备还是多功能设备中的某个功能)都对应一个 pci_dev 实例。该结构体包含了设备的配置空间信息、资源信息、电源管理状态以及驱动绑定状态。

4.3.1 关键字段

字段名 类型 说明
bus struct pci_bus * 指向该设备所属的 pci_bus
devfn unsigned int 设备号(高 5 位 Device Number + 低 3 位 Function Number)
vendor unsigned short 厂商 ID(Vendor ID)
device unsigned short 设备 ID(Device ID)
subsystem_vendor unsigned short 子系统厂商 ID
subsystem_device unsigned short 子系统设备 ID
class unsigned int 类别代码(Class Code),包含 Base Class / Sub Class / Interface
irq unsigned int 中断号(INTx 或 MSI/MSI-X 分配后的中断号)
resource[] struct resource[DEVICE_COUNT_RESOURCE] 设备的 BAR 资源数组(最多 6 个 BAR + 扩展 ROM)
driver struct pci_driver * 指向当前绑定的 pci_driver(若未绑定则为 NULL)
dev struct device 内嵌的通用 device 结构体,用于驱动模型和 sysfs

4.3.2 BDF 地址与 devfn

每个 PCI 设备在系统中的唯一标识是 BDF(Bus:Device.Function)地址。内核通过 pci_dev 的 bus->number 和 devfn 字段组合得到 BDF:

// BDF 地址计算
bus_number = pdev->bus->number;          // 总线号
device_number = PCI_SLOT(pdev->devfn);   // 设备号(高 5 位)
function_number = PCI_FUNC(pdev->devfn); // 功能号(低 3 位)

// 示例:BDF = 00:1f.3 表示 Bus 0, Device 31, Function 3
// devfn = (31 << 3) | 3 = 0xFB
注意:devfn 字段在枚举阶段由 PCI 配置空间的 Device Number 和 Function Number 组合而成。对于单功能设备,Function Number 固定为 0。多功能设备最多支持 8 个 Function(Function 0~7)。

4.4 pci_driver 结构体:驱动的抽象

pci_driver 是内核 PCI 子系统与具体设备驱动之间的接口。每个 PCI 设备驱动(如网卡驱动、NVMe 驱动)都需要注册一个 pci_driver 实例,通过 id_table 声明支持的设备,并通过 probe / remove 回调实现设备初始化和卸载。

4.4.1 关键字段

字段名 类型 说明
name const char * 驱动名称,用于 sysfs 和调试
id_table const struct pci_device_id * 驱动支持的设备 ID 列表(匹配 Vendor/Device/Class)
probe int (*probe)(struct pci_dev *, const struct pci_device_id *) 设备匹配成功后的初始化回调
remove void (*remove)(struct pci_dev *) 设备移除或驱动卸载时的清理回调
suspend int (*suspend)(struct pci_dev *, pm_message_t) 电源管理挂起回调(可选)
resume int (*resume)(struct pci_dev *) 电源管理恢复回调(可选)
shutdown void (*shutdown)(struct pci_dev *) 系统关机时的回调(可选)
driver struct device_driver 内嵌的通用 device_driver,用于驱动模型

4.4.2 pci_device_id 匹配表

pci_device_id 结构体定义了驱动与设备之间的匹配规则。内核在枚举设备时,会遍历所有已注册的 pci_driver,将设备的 Vendor ID、Device ID、Subsystem ID、Class Code 与 id_table 中的条目进行比对。

// pci_device_id 结构体定义
struct pci_device_id {
    __u32 vendor, device;      // 厂商 ID 和设备 ID
    __u32 subvendor, subdevice; // 子系统厂商 ID 和设备 ID
    __u32 class, class_mask;   // 类别代码及掩码
    unsigned long driver_data; // 驱动私有数据
};

// 示例:Intel I350 网卡驱动匹配表
static const struct pci_device_id igb_pci_tbl[] = {
    { PCI_VDEVICE(INTEL, 0x1521), 0 },  // I350 千兆网卡
    { PCI_VDEVICE(INTEL, 0x1522), 0 },  // I350 双口
    { PCI_VDEVICE(INTEL, 0x1523), 0 },  // I350 四口
    { 0, }  // 结束标志
};
匹配优先级:内核按照 id_table 中条目的顺序进行匹配。如果设备的 Vendor ID + Device ID 完全匹配,则直接命中;如果未匹配,则尝试 Class Code 匹配(通过 class_mask 控制匹配精度)。匹配成功后调用 probe 回调。

4.5 三者的协作流程

在 PCI 设备枚举过程中,三个结构体按照以下流程协同工作:

  1. 总线扫描:内核从根总线(pci_bus 0)开始,递归扫描所有 PCI 总线,发现每个设备/功能时创建 pci_dev 实例。
  2. 设备注册:新创建的 pci_dev 被添加到所属 pci_bus 的 devices 链表中,同时通过 device_add() 注册到内核驱动模型。
  3. 驱动匹配:内核遍历所有已注册的 pci_driver,调用 pci_match_device() 检查 id_table 是否匹配当前 pci_dev。
  4. 驱动绑定:匹配成功后,内核调用 pci_driver 的 probe 回调,并将 pci_dev 的 driver 指针指向该 pci_driver。
  5. 资源分配:在 probe 过程中,驱动通过 pci_request_regions() 等 API 获取并启用设备的 BAR 资源。
pci_bus / pci_dev / pci_driver 协作流程 PCI 设备枚举协作流程 1. 总线扫描 pci_bus 递归扫描 2. 创建设备 pci_dev 实例化 3. 驱动匹配 pci_driver id_table 4. 驱动绑定 probe 回调执行 5. 资源分配 BAR / IRQ 初始化 关键数据结构关系 pci_bus 管理拓扑 → pci_dev 表示设备 → pci_driver 提供驱动逻辑

4.6 数据结构关系总结

下表从不同维度对比三个核心结构体的职责和关联:

对比维度 pci_bus pci_dev pci_driver
抽象对象 PCI 总线(物理/逻辑) PCI 功能(Function) 设备驱动模块
生命周期 系统启动时创建,关机时销毁 枚举时创建,移除时销毁 模块加载时注册,卸载时注销
核心链表 devices(设备链表)
children(子总线链表)
global_list(全局设备链表)
bus_list(总线设备链表)
driver_list(全局驱动链表)
匹配机制 无(被动管理) 通过 vendor/device/class 匹配 通过 id_table 声明支持
sysfs 路径 /sys/devices/pciXXXX:XX/ /sys/devices/pciXXXX:XX/XXXX:XX:XX.X/ /sys/bus/pci/drivers/<name>/
关键回调 无(由 PCI 核心管理) 无(被动数据载体) probe / remove / suspend / resume
开发建议:编写 PCI 驱动时,开发者主要关注 pci_driver 的注册和 probe 回调实现。内核的 PCI 核心层自动完成 pci_bus 的扫描和 pci_dev 的创建,驱动开发者无需手动管理总线拓扑。