PCIe驱动加载与内核调试工程部署实战 - 第3节

3. Linux PCIe子系统架构

Linux PCIe 子系统采用分层设计,从底层的硬件访问到上层的驱动绑定,形成了一条清晰的数据与控制路径。理解 pci_bus、pci_dev、pci_driver 这三个核心数据结构及其相互关系,是掌握 PCIe 驱动开发的基础。

核心要点: PCIe 子系统通过 pci_bus 管理总线拓扑,通过 pci_dev 描述具体设备,通过 pci_driver 实现驱动逻辑。内核的 PCI 核心 API 负责这三者的创建、注册与匹配。

下图展示了 PCIe 子系统在 Linux 内核中的层次位置与核心组件关系:

Linux PCIe 子系统架构图 Linux PCIe 子系统核心架构 PCI 核心层 (PCI Core) pci_bus pci_dev pci_driver 硬件抽象层 (配置空间访问 / MMIO / DMA) PCIe 硬件设备 / 总线 / 桥 说明: • pci_bus:管理总线拓扑,包含父总线、子总线、设备链表。 • pci_dev:描述单个 PCIe 设备,包含配置空间、资源、IRQ 等信息。 • pci_driver:驱动实体,包含 probe/remove 回调及 ID 匹配表。

3.2 核心数据结构详解

3.2.1 pci_bus:总线管理单元

pci_bus 结构体代表一条 PCI 总线,是总线拓扑管理的核心。每个 PCI 段(Segment)下的每条总线都有一个对应的 pci_bus 实例。

成员类型描述
numberunsigned char总线号(Bus Number)
primaryunsigned char主总线号(用于桥接设备)
secondaryunsigned char从总线号
subordinateunsigned char最大从总线号
devicesstruct list_head挂载在此总线上的所有 pci_dev 链表
childrenstruct list_head子总线链表
parentstruct pci_bus *指向父总线
opsstruct pci_ops *总线访问操作函数集
resourcestruct 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 被调用,经历了完整的设备枚举与匹配过程。下图展示了这一关键流程:

PCIe 设备枚举与驱动绑定流程 PCIe 设备枚举与驱动绑定流程 系统启动 / PCI 总线扫描 创建 pci_dev 实例 分配资源 (BAR/IRQ) 设备加入 pci_bus->devices 链表 内核调用 pci_bus_match() 匹配成功 → 调用 pci_driver->probe() 关键点: • pci_bus_match() 遍历所有已注册的 pci_driver,对比 id_table。 • 匹配成功后,内核自动调用 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 的必备步骤。