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回调的标准流程

  1. 使能设备: 调用 pci_enable_device(),唤醒PCIe设备并分配I/O和内存资源。
  2. 设置DMA掩码: 调用 dma_set_mask_and_coherent(),告知设备支持的DMA地址宽度。
  3. 获取BAR资源: 使用 pci_resource_start() 和 pci_resource_len() 获取基地址寄存器(BAR)映射的物理地址和长度。
  4. ioremap: 将物理地址映射到内核虚拟地址空间,以便CPU访问设备寄存器。
  5. 注册中断: 调用 request_irq() 或 pci_request_irq(),绑定中断处理函数。
  6. 初始化硬件: 执行设备特定的初始化序列(如复位、配置FIFO、启动DMA引擎)。
  7. 创建接口: 注册字符设备、网络设备或块设备,向用户空间暴露功能。

3.2.2 remove回调的标准流程

  1. 停止I/O操作: 确保所有未完成的DMA传输和中断处理已完成。
  2. 注销接口: 移除字符设备、网络设备等内核对象。
  3. 释放中断: 调用 free_irq()。
  4. iounmap: 解除虚拟地址映射。
  5. 禁用设备: 调用 pci_disable_device()。
RTOS移植陷阱: Linux的ioremap在RTOS中通常不需要,因为RTOS往往运行在物理地址空间或使用静态页表映射。你需要将pci_resource_start()返回的物理地址直接作为MMIO基址使用,或通过RTOS提供的mmap等效接口进行映射。
probe/remove 回调生命周期与移植映射 Linux probe 回调 pci_enable_device() dma_set_mask() pci_resource_start() / ioremap() request_irq() 硬件初始化 / 设备特定配置 注册字符/网络设备 Linux remove 回调 停止I/O / 等待DMA完成 注销字符/网络设备 free_irq() iounmap() pci_disable_device() 映射到 RTOS 板级初始化函数 RTOS 板级去初始化函数 移植核心:将probe/remove中的资源操作(使能、映射、中断、注册)拆解为RTOS可调用的独立函数

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解释器复杂)
PCIe硬件描述信息获取路径:DT vs ACPI 设备树 (Device Tree) 路径 DTS 源文件 → DTB 二进制 Bootloader (U-Boot) 加载 DTB Linux OF API: of_property_read_u32() 获取中断号、reg、ranges 等资源 ACPI 路径 ASL 源文件 → AML 字节码 UEFI 固件提供 ACPI 表 (RSDP) Linux ACPI API: acpi_evaluate_object() 解析 _CRS / _PRT 获取资源 PCIe 驱动 probe 函数获取硬件资源

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)的配置文件。这可以大幅降低调试难度。