3. Linux PCIe子系统概览

Linux PCIe子系统是内核中层次最清晰、职责划分最严谨的总线子系统之一。它把“总线枚举”“配置空间访问”“资源分配”“设备驱动匹配”和“Endpoint功能抽象”拆成多个协作层,使同一套框架既能支撑x86服务器上的RC(Root Complex),也能支撑ARM SoC上的PCIe控制器,还能支撑以Endpoint模式运行的开发板。理解这套分层结构,是后续编写Endpoint驱动、调试链路训练和排查枚举失败的基础。

一、核心原理:四层协作模型

从内核视角看,PCIe子系统可以概括为四个关键角色:PCI核心、主机控制器驱动、Endpoint框架和设备驱动。PCI核心位于drivers/pci/,负责枚举、配置空间读写、资源分配、总线号管理和驱动匹配;主机控制器驱动(如pcie-designware、pcie-xilinx、pcie-rcar)负责初始化RC硬件、建立配置空间访问通道、处理中断和链路训练;Endpoint框架(drivers/pci/endpoint/)则把SoC内部的PCIe控制器配置为EP模式,提供pci_epc、pci_epf和pci_epf_driver三层抽象;设备驱动最终通过pci_driver与枚举出的设备匹配。

这四层之间不是简单的上下调用,而是通过pci_host_bridge、pci_bus、pci_dev、pci_epc等核心数据结构互相引用。RC侧关注“如何发现设备”,EP侧关注“如何被主机发现”,两者共享配置空间访问语义,但初始化路径完全不同。

Linux PCIe 子系统四层协作模型 PCI 核心(drivers/pci/) 枚举 · 配置空间读写 · 总线号分配 · BAR资源分配 · pci_driver匹配 · sysfs/uevent 核心对象:pci_host_bridge → pci_bus → pci_dev → pci_driver 主机控制器驱动(RC侧) pcie-designware / pcie-xilinx / pcie-rcar 初始化RC · 建立配置访问 · 链路训练 中断映射 · MSI/MSI-X 域管理 Endpoint 框架(EP侧) pci_epc(控制器) · pci_epf(功能) pci_epf_driver(功能驱动) BAR配置 · 地址映射 · 中断上报 PCI 设备驱动 pci_driver · probe/remove · id_table NVMe · 网卡 · 自定义EP功能驱动 EPF 功能驱动 pci_epf_driver · bind/unbind 自定义功能 · 测试功能 · 虚拟功能 共享配置空间语义 关键数据结构关系 pci_host_bridge → pci_bus → pci_dev → pci_driver(RC侧) | pci_epc → pci_epf → pci_epf_driver(EP侧)

二、方法流程:设备枚举与资源分配

PCIe枚举是PCI核心最核心的工作。系统启动时,主机控制器驱动先注册pci_host_bridge,然后PCI核心从总线0开始扫描。枚举过程大致如下:

  1. 读取每个设备的功能号0的Vendor ID和Device ID,若为0xFFFF则跳过。
  2. 读取Header Type,判断是单功能还是多功能设备。
  3. 读取Class Code、Revision ID,创建pci_dev并注册到pci_bus。
  4. 若为桥设备,读取Secondary/Subordinate Bus Number,递归扫描下级总线。
  5. 枚举完成后,调用pci_assign_resources()为所有BAR分配地址空间。
  6. 最后通过pci_bus_add_devices()触发驱动匹配。

资源分配阶段,PCI核心会遍历所有设备的BAR,根据IORESOURCE_MEM、IORESOURCE_IO和IORESOURCE_PREFETCH类型,在RC的地址窗口内寻找合适区间。对于64位BAR,还需要处理高位地址。分配完成后,BAR值被写回配置空间,设备即可响应CPU访问。

# 查看PCI设备树与资源分配
lspci -tv
lspci -vv -s 01:00.0 | grep -A5 "Region"
cat /proc/iomem | grep -i pci
cat /sys/bus/pci/devices/0000:01:00.0/resource

三、案例场景:从RC到EP的完整链路

假设使用Xilinx ZynqMP平台,RC侧运行Linux,EP侧运行另一块开发板。RC侧加载pcie-xilinx-nwl驱动后,内核日志出现:

[    2.345678] pcieport 0000:00:00.0: PCI bridge to [bus 01]
[    2.456789] pcieport 0000:00:00.0:   bridge window [mem 0x80000000-0x8fffffff]
[    2.567890] pci 0000:01:00.0: BAR 0: assigned [mem 0x80000000-0x8000ffff]
[    2.678901] pci 0000:01:00.0: BAR 2: assigned [mem 0x80010000-0x8001ffff 64bit]

EP侧则通过pci_epf_test驱动注册功能,配置BAR0为64KB内存空间,BAR2为64位可预取空间。主机枚举到设备后,EPF驱动收到bind回调,开始处理主机发来的TLP读写请求。这个过程中,EP侧不需要知道主机如何分配地址,只需按pci_epc接口配置BAR和映射窗口即可。

调试提示:若枚举失败,优先检查dmesg | grep -i pci中的链路训练状态、LTSSM是否进入L0,以及RC的mem窗口是否覆盖EP的BAR请求范围。

四、误区与复盘

常见误区 实际机制 排查方法
认为EP驱动直接操作配置空间 EP侧通过pci_epc回调由硬件控制器代劳 查看pci_epc_ops实现
枚举失败就怀疑设备驱动 枚举发生在驱动匹配之前,属于PCI核心职责 lspci能否看到设备
BAR分配失败只改设备树 需同时检查RC地址窗口和pci_assign_resources日志 dmesg | grep -i "BAR.*fail"
EPF驱动与EPC控制器混为一谈 EPC是控制器抽象,EPF是功能抽象,二者通过pci_epc绑定 查看/sys/kernel/config/pci_ep/

复盘时建议按“链路训练→枚举→资源分配→驱动匹配”四阶段逐层排查。链路训练看LTSSM状态,枚举看lspci输出,资源分配看/proc/iomem和BAR值,驱动匹配看/sys/bus/pci/drivers/下的绑定情况。只有把PCI核心、主机控制器驱动、Endpoint框架三者的职责边界分清,才能在Endpoint驱动开发中快速定位问题,而不是在错误的层次反复修改。