2. Linux PCIe驱动框架:pci_driver结构体、probe/remove回调、设备ID表

在将Linux PCIe驱动移植到RTOS之前,必须深入理解Linux内核为PCIe设备驱动提供的标准框架。该框架以pci_driver结构体为核心,通过probe/remove回调机制实现设备的热插拔与生命周期管理,并依赖设备ID表完成驱动与硬件的匹配。本章将逐一拆解这三个关键要素,为后续的RTOS适配打下坚实基础。

2.1 pci_driver结构体:驱动的“身份证”

在Linux内核中,每个PCIe驱动都需要定义一个struct pci_driver实例,并将其注册到PCI核心子系统。该结构体是驱动与内核之间的契约,包含了驱动的名称、支持的设备列表、以及关键的钩子函数指针。

核心要点: pci_driver是驱动模块的入口点,内核通过它来识别驱动能力并调用相应的初始化与去初始化函数。

以下是struct pci_driver在Linux内核中的简化定义(位于include/linux/pci.h):

struct pci_driver {
    const char *name;                    // 驱动名称,用于sysfs和调试
    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);
    // ... 其他可选回调(如shutdown、sriov_configure等)
};

该结构体中的关键字段及其在移植过程中的重要性如下表所示:

字段 类型 作用 RTOS移植关注点
name const char * 驱动名称标识 RTOS中通常保留,用于日志或调试
id_table const struct pci_device_id * 定义驱动支持的Vendor/Device ID范围 结构体定义可直接复用,匹配逻辑需重写
probe 函数指针 设备发现时调用,完成资源分配与初始化 核心逻辑保留,调用时机由RTOS枚举流程触发
remove 函数指针 设备移除或驱动卸载时调用,释放资源 核心逻辑保留,需绑定到RTOS设备注销接口
suspend/resume 函数指针 电源管理回调 RTOS若支持低功耗管理则保留,否则可置空
移植技巧: 在RTOS中,我们通常将pci_driver结构体直接作为驱动控制块的一部分,或者将其关键字段(name, id_table, probe, remove)提取到一个自定义的pci_drv_ops结构体中,以适配RTOS的驱动模型。
pci_driver 结构体与内核PCI核心的关系 Linux PCI Core struct pci_driver 实例 name = "my_pcie_driver" id_table → [设备ID表] probe() / remove() 回调 pci_register_driver() pci_unregister_driver() 说明:驱动通过 pci_register_driver() 将自身注册到PCI Core,内核在发现匹配设备时自动调用 probe()。

2.2 probe/remove回调:驱动的生命周期

probe和remove是驱动中最重要的两个回调函数,它们定义了设备从“被发现”到“被移除”的完整生命周期。

2.2.1 probe回调:设备初始化入口

当PCI核心检测到一个新设备,并且该设备的Vendor ID、Device ID与驱动的id_table匹配时,内核会调用驱动的probe函数。该函数通常执行以下操作:

  1. 启用设备: 调用 pci_enable_device() 激活PCIe设备。
  2. 请求资源: 调用 pci_request_regions() 获取BAR空间。
  3. 映射BAR: 调用 pci_iomap() 或 ioremap() 将物理地址映射到内核虚拟地址空间。
  4. 设置DMA掩码: 调用 dma_set_mask() 和 dma_set_coherent_mask()。
  5. 注册中断: 调用 request_irq() 或 pci_alloc_irq_vectors() 申请MSI/MSI-X中断。
  6. 初始化私有数据结构: 分配驱动私有数据,初始化硬件状态机。
  7. 注册子系统接口: 如注册字符设备、网络设备或块设备。
移植警告: RTOS中通常没有pci_enable_device()等封装函数。这些操作需要被拆解为对PCIe配置空间的直接读写(通过ECAM或IO端口),以及对中断控制器和DMA控制器的直接操作。

2.2.2 remove回调:设备清理出口

remove函数是probe的逆操作,在设备热移除或驱动卸载时被调用。其典型流程包括:

  • 注销子系统接口(如 device_destroy())。
  • 释放中断(free_irq())。
  • 取消I/O映射(pci_iounmap())。
  • 释放资源(pci_release_regions())。
  • 禁用设备(pci_disable_device())。
probe / remove 回调生命周期流程 设备插入/匹配 驱动 probe() 被调用 probe() 内部典型步骤 1. pci_enable_device() → 启用PCIe设备 2. pci_request_regions() → 请求BAR资源 3. pci_iomap() → 映射BAR到虚拟地址 4. dma_set_mask() → 设置DMA掩码 5. request_irq() → 注册中断处理 6. 初始化私有数据 & 注册子系统 设备正常运行 设备移除/驱动卸载 驱动 remove() 被调用 remove() 逆操作 1. 注销子系统 2. free_irq() 3. pci_iounmap()

2.3 设备ID表:驱动与硬件的匹配规则

设备ID表是pci_driver结构体中的id_table字段所指向的数据结构,其类型为struct pci_device_id。该表定义了该驱动能够支持的所有PCIe设备。

struct pci_device_id {
    __u32 vendor;    // 厂商ID (Vendor ID)
    __u32 device;    // 设备ID (Device ID)
    __u32 subvendor; // 子系统厂商ID (通常为PCI_ANY_ID)
    __u32 subdevice; // 子系统设备ID (通常为PCI_ANY_ID)
    __u32 class;     // 设备类别
    __u32 class_mask;// 类别掩码
    unsigned long driver_data; // 驱动私有数据,用于区分不同设备变体
};

在驱动代码中,通常使用MODULE_DEVICE_TABLE(pci, id_table)宏将表导出,以便内核在模块加载时进行热插拔匹配。一个典型的ID表定义如下:

static const struct pci_device_id my_pcie_ids[] = {
    { PCI_DEVICE(0x10EE, 0x9034) },  // 匹配Vendor=0x10EE, Device=0x9034
    { PCI_DEVICE(0x10EE, 0x9038), .driver_data = (unsigned long)&variant_a_cfg },
    { PCI_DEVICE_SUB(0x10EE, 0x9034, 0x10EE, 0x0001) }, // 精确匹配子系统
    { 0, }  // 结束标志
};
MODULE_DEVICE_TABLE(pci, my_pcie_ids);
移植要点: struct pci_device_id的定义在RTOS中可以直接复用。但匹配逻辑需要重写:RTOS的PCIe枚举器在扫描总线时,需要遍历每个驱动的ID表,调用类似pci_match_id()的函数来判断是否匹配。在RTOS中,我们通常将ID表与驱动控制块绑定,并在枚举循环中手动比较vendor和device字段。
设备ID表匹配流程 PCIe设备配置空间 Vendor ID = 0x10EE Device ID = 0x9034 Class Code = 0x020000 匹配引擎 遍历 id_table[] 比较 vendor & device 检查 class & mask 驱动 ID 表 [0]: 0x10EE / 0x9034 [1]: 0x10EE / 0x9038 [2]: 0x10EE / 0x9034 (sub) 匹配成功 → 调用 probe()

2.4 本章小结

Linux PCIe驱动框架围绕pci_driver结构体构建,通过probe/remove回调实现设备生命周期管理,并依赖pci_device_id表完成硬件匹配。在RTOS移植过程中:

  • 结构体定义可以直接复用或轻微改造。
  • probe/remove核心逻辑(资源分配、中断注册、DMA设置)需要保留,但底层API调用需替换为RTOS原生接口。
  • 设备ID表的匹配算法需要由RTOS的PCIe枚举器重新实现,通常采用线性查找。