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若支持低功耗管理则保留,否则可置空 |
pci_driver结构体直接作为驱动控制块的一部分,或者将其关键字段(name, id_table, probe, remove)提取到一个自定义的pci_drv_ops结构体中,以适配RTOS的驱动模型。
2.2 probe/remove回调:驱动的生命周期
probe和remove是驱动中最重要的两个回调函数,它们定义了设备从“被发现”到“被移除”的完整生命周期。
2.2.1 probe回调:设备初始化入口
当PCI核心检测到一个新设备,并且该设备的Vendor ID、Device ID与驱动的id_table匹配时,内核会调用驱动的probe函数。该函数通常执行以下操作:
- 启用设备: 调用
pci_enable_device()激活PCIe设备。 - 请求资源: 调用
pci_request_regions()获取BAR空间。 - 映射BAR: 调用
pci_iomap()或ioremap()将物理地址映射到内核虚拟地址空间。 - 设置DMA掩码: 调用
dma_set_mask()和dma_set_coherent_mask()。 - 注册中断: 调用
request_irq()或pci_alloc_irq_vectors()申请MSI/MSI-X中断。 - 初始化私有数据结构: 分配驱动私有数据,初始化硬件状态机。
- 注册子系统接口: 如注册字符设备、网络设备或块设备。
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())。
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字段。
2.4 本章小结
Linux PCIe驱动框架围绕pci_driver结构体构建,通过probe/remove回调实现设备生命周期管理,并依赖pci_device_id表完成硬件匹配。在RTOS移植过程中:
- 结构体定义可以直接复用或轻微改造。
- probe/remove核心逻辑(资源分配、中断注册、DMA设置)需要保留,但底层API调用需替换为RTOS原生接口。
- 设备ID表的匹配算法需要由RTOS的PCIe枚举器重新实现,通常采用线性查找。