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侧关注“如何被主机发现”,两者共享配置空间访问语义,但初始化路径完全不同。
二、方法流程:设备枚举与资源分配
PCIe枚举是PCI核心最核心的工作。系统启动时,主机控制器驱动先注册pci_host_bridge,然后PCI核心从总线0开始扫描。枚举过程大致如下:
- 读取每个设备的功能号0的Vendor ID和Device ID,若为
0xFFFF则跳过。 - 读取Header Type,判断是单功能还是多功能设备。
- 读取Class Code、Revision ID,创建
pci_dev并注册到pci_bus。 - 若为桥设备,读取Secondary/Subordinate Bus Number,递归扫描下级总线。
- 枚举完成后,调用
pci_assign_resources()为所有BAR分配地址空间。 - 最后通过
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驱动开发中快速定位问题,而不是在错误的层次反复修改。