3. Linux PCIe驱动架构:pci_driver结构体、probe/remove回调、设备树与ACPI
在将PCIe驱动从Linux移植到RTOS之前,必须深入理解Linux PCIe驱动的核心架构。本章聚焦于驱动注册的基石——pci_driver结构体、生命周期管理回调probe/remove,以及两种硬件描述机制:设备树(DT)与ACPI。掌握这些要素,是后续进行RTOS适配的关键前提。
3.1 pci_driver结构体:驱动的“身份证”
在Linux内核中,每个PCIe驱动都必须定义一个pci_driver实例,并通过pci_register_driver()向PCI核心层注册。该结构体是驱动与内核PCI子系统之间的契约。
核心字段解析: 下表列出了
pci_driver中与移植最密切的字段。
| 字段名 | 类型 | 作用与移植关注点 |
|---|---|---|
name |
const char * |
驱动名称,用于匹配和设备管理。RTOS中通常保留为调试标识。 |
id_table |
const struct pci_device_id * |
设备ID匹配表(Vendor ID、Device ID等)。RTOS需自行实现匹配逻辑。 |
probe |
int (*probe)(struct pci_dev *, const struct pci_device_id *) |
设备发现时的初始化入口。RTOS中需重写为板级初始化函数。 |
remove |
void (*remove)(struct pci_dev *) |
设备移除或驱动卸载时的清理出口。RTOS中对应去初始化函数。 |
shutdown |
void (*shutdown)(struct pci_dev *) |
系统关机或重启时的回调。RTOS中通常合并到remove或电源管理。 |
driver |
struct device_driver |
内嵌的通用设备驱动结构体,包含电源管理、锁等。RTOS中需剥离。 |
// Linux pci_driver 定义示例
static struct pci_driver my_pcie_driver = {
.name = "my_pcie_device",
.id_table = my_pcie_ids, // 指向 pci_device_id 数组
.probe = my_pcie_probe,
.remove = my_pcie_remove,
.shutdown = my_pcie_shutdown, // 可选
};
module_pci_driver(my_pcie_driver); // 宏封装了注册与卸载
移植要点: RTOS中没有
module_pci_driver宏,也没有pci_register_driver。你需要将probe和remove中的核心逻辑提取出来,封装成RTOS下的初始化/去初始化函数,并在系统启动表中显式调用。
3.2 probe/remove回调:驱动的生命周期
probe和remove是驱动与硬件绑定的两个关键动作。理解它们内部的标准流程,是移植时保持功能一致性的基础。
3.2.1 probe回调的标准流程
- 使能设备: 调用
pci_enable_device(),唤醒PCIe设备并分配I/O和内存资源。 - 设置DMA掩码: 调用
dma_set_mask_and_coherent(),告知设备支持的DMA地址宽度。 - 获取BAR资源: 使用
pci_resource_start()和pci_resource_len()获取基地址寄存器(BAR)映射的物理地址和长度。 - ioremap: 将物理地址映射到内核虚拟地址空间,以便CPU访问设备寄存器。
- 注册中断: 调用
request_irq()或pci_request_irq(),绑定中断处理函数。 - 初始化硬件: 执行设备特定的初始化序列(如复位、配置FIFO、启动DMA引擎)。
- 创建接口: 注册字符设备、网络设备或块设备,向用户空间暴露功能。
3.2.2 remove回调的标准流程
- 停止I/O操作: 确保所有未完成的DMA传输和中断处理已完成。
- 注销接口: 移除字符设备、网络设备等内核对象。
- 释放中断: 调用
free_irq()。 - iounmap: 解除虚拟地址映射。
- 禁用设备: 调用
pci_disable_device()。
RTOS移植陷阱: Linux的
ioremap在RTOS中通常不需要,因为RTOS往往运行在物理地址空间或使用静态页表映射。你需要将pci_resource_start()返回的物理地址直接作为MMIO基址使用,或通过RTOS提供的mmap等效接口进行映射。
3.3 设备树与ACPI:硬件描述机制
Linux通过设备树(Device Tree)和ACPI(Advanced Configuration and Power Interface)两种方式描述非枚举型硬件信息(如中断号、复位GPIO、时钟频率)。PCIe设备本身是枚举型,但其根复杂体(Root Complex)和下游桥设备的配置信息通常依赖这两种机制。
3.3.1 设备树(DT)方式
设备树主要用于ARM/ARM64、RISC-V等架构。PCIe控制器节点在DTS中定义,包含reg(配置空间地址)、interrupt-map(中断路由)、ranges(地址转换)等关键属性。
// 设备树中PCIe根复杂体节点示例
pcie0: pcie@fe000000 {
compatible = "vendor, pcie-controller";
reg = <0x0 0xfe000000 0x0 0x1000000>; // 配置空间基址
ranges = <0x02000000 0x0 0x40000000 0x0 0x40000000 0x0 0x10000000>; // 32位非预取内存
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &gic GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <3>;
#size-cells = <2>;
device_type = "pci";
bus-range = <0x00 0xff>;
};
RTOS适配策略: 在RTOS中,设备树解析库(如
libfdt)通常不可用或过于庞大。建议将DTS中与PCIe驱动相关的关键属性(如中断号、寄存器地址)硬编码为板级头文件中的宏定义,或使用RTOS自带的轻量级配置表。
3.3.2 ACPI方式
ACPI主要用于x86和部分ARM64服务器平台。PCIe设备信息通过ACPI命名空间中的_SB_.PCI0等对象描述,使用_CRS(当前资源设置)报告中断和地址资源。
| 特性 | 设备树 (DT) | ACPI |
|---|---|---|
| 主要应用架构 | ARM/ARM64, RISC-V, PowerPC | x86/x64, ARM64 (Server) |
| 数据格式 | 文本DTS / 二进制DTB | AML字节码 / ASL源码 |
| PCIe资源描述 | ranges, interrupt-map |
_CRS, _PRT (PCI路由表) |
| 运行时动态性 | 静态(启动时加载) | 支持动态命名空间修改 |
| RTOS移植难度 | 中等(可硬编码) | 高(AML解释器复杂) |
3.4 移植适配总结:从Linux到RTOS的映射
将Linux PCIe驱动移植到RTOS,本质上是对pci_driver框架、probe/remove流程以及硬件描述机制进行“去Linux化”重构。下表总结了核心映射关系:
| Linux 概念 | RTOS 等价物 / 移植策略 |
|---|---|
pci_driver + pci_register_driver() |
静态驱动表 + 手动调用初始化函数 |
probe 回调 |
板级初始化函数(如 pcie_dev_init()) |
remove 回调 |
板级去初始化函数(如 pcie_dev_deinit()) |
ioremap() |
直接使用物理地址或RTOS的静态映射 |
request_irq() |
RTOS中断注册API(如 int_connect()) |
| 设备树 / ACPI | 硬编码板级头文件或轻量配置表 |
pci_enable_device() |
直接操作PCIe配置空间命令寄存器 |
实践建议: 在开始移植前,建议先使用
lspci -vvv和cat /proc/iomem在Linux目标板上收集完整的PCIe设备资源信息(BAR地址、中断号、Capability列表),然后将其整理为RTOS板级支持包(BSP)的配置文件。这可以大幅降低调试难度。