news 2026/6/15 3:05:01

RC PCIE枚举的过程,发送的数据包,怎么知道要通过板子上的PCIE硬件接口传出去

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RC PCIE枚举的过程,发送的数据包,怎么知道要通过板子上的PCIE硬件接口传出去

可以把这个问题理解成:

Linux/BIOS 并不是直接“选择某根 PCIe 线发包”,而是访问了某个 Root Complex/Host Bridge 的配置访问窗口;主板/SoC 的地址译码把这个 CPU 访问送到对应 PCIe 控制器;PCIe 控制器再从自己连接的物理 PCIe Lane 发出去。

也就是说,路径是:

软件枚举逻辑 ↓ CPU 发起配置空间访问 ↓ 命中某个 PCIe RC / Host Bridge 的配置访问窗口 ↓ RC 硬件生成 Configuration Read / Write TLP ↓ 通过该 RC 连接的 PCIe PHY / Lane 发出去 ↓ Endpoint 响应 Completion

1. 软件不是直接发 TLP,而是访问“配置空间窗口”

Linux 枚举 PCIe 时,代码里看起来像是在读:

pci_bus_read_config_dword(bus,devfn,offset,&val);

但 CPU 不会自己生成 PCIe TLP。

CPU 做的是:

读/写某个特殊的 MMIO 地址

这个 MMIO 地址属于:

PCIe 配置空间访问窗口

常见叫法包括:

ECAM MMCONFIG PCI configuration space window

如果是标准 ECAM,配置空间地址大致按下面方式编码:

ECAM_BASE + Bus Number × 1MB + Device Number × 32KB + Function Number × 4KB + Register Offset

例如软件想读:

Bus 1, Device 0, Function 0, Offset 0x00

也就是读 Vendor ID / Device ID。

它实际可能访问:

ECAM_BASE + (1 << 20) + (0 << 15) + (0 << 12) + 0x00

这个 CPU 读操作会进入主机内部地址总线。

然后主机地址译码发现:

这个地址属于 PCIe RC 的 ECAM 配置窗口

于是把访问送到对应的 PCIe Root Complex 硬件。


2. “怎么知道通过板子上的 PCIe 硬件接口传出去?”

靠的是主机地址映射 + PCIe Host Bridge 注册信息 + 硬件连接关系

你可以这样理解:

CPU 并不知道板子上哪根 PCIe Lane 接了 FPGA Linux 也不是直接控制物理 Lane 真正知道的是: 这个 MMIO 配置窗口属于哪个 PCIe Root Complex 这个 Root Complex 的 PHY/Lane 物理上连到了哪个插槽/接口

比如主板上有两个 PCIe 控制器:

PCIe RC0 → 接 M.2 插槽 PCIe RC1 → 接 FPGA 板卡插槽

系统启动时,BIOS/UEFI 或 Device Tree / ACPI 会告诉 Linux:

RC0 的配置访问窗口在哪里 RC0 的 bus range 是多少 RC0 的 MMIO resource window 是多少 RC1 的配置访问窗口在哪里 RC1 的 bus range 是多少 RC1 的 MMIO resource window 是多少

Linux 注册两个 Host Bridge:

PCI domain 0000, bus 00-3f → RC0 PCI domain 0001, bus 00-3f → RC1

之后 Linux 扫描 RC1 时,访问的是 RC1 的配置窗口。

所以配置访问自然进入 RC1,然后从 RC1 的 PCIe 硬件接口发出去。

不是软件手动说:

从这根 PCIe 线发

而是:

访问了哪个 RC 的配置窗口,就由哪个 RC 发

3. 关键硬件路径:地址译码

主机内部有地址映射。

例如简化成这样:

CPU 地址空间: 0x0000_0000_0000 ~ 0x0000_FFFF_FFFF DRAM 0x8000_0000_0000 ~ 0x8000_0FFF_FFFF PCIe RC0 ECAM 0x9000_0000_0000 ~ 0x9000_0FFF_FFFF PCIe RC1 ECAM 0xA000_0000_0000 ~ 0xA000_FFFF_FFFF PCIe RC0 MMIO outbound window 0xB000_0000_0000 ~ 0xB000_FFFF_FFFF PCIe RC1 MMIO outbound window

如果 CPU 访问:

0x9000_0010_0000

地址译码发现:

这是 PCIe RC1 的 ECAM 配置空间

于是访问进入:

PCIe RC1

然后 RC1 根据访问地址中的:

Bus / Device / Function / Register Offset

生成 PCIe Configuration TLP。

所以问题的答案是:

不是数据包自己知道去哪,而是 CPU 地址访问先被主机地址译码送到某个 RC,RC 再把访问转换成 PCIe TLP,从自己的物理 PCIe 端口发出去。


4. Linux 内核怎么知道每个 RC 的配置窗口?

来源一般有两类。

4.1 PC / x86 平台

通常来自:

BIOS / UEFI ACPI MCFG 表 _CRS 资源描述

其中 MCFG 会告诉系统:

PCIe ECAM 配置空间基地址 PCI segment/domain bus range

Linux 读取这些信息后,就知道:

哪个 PCI domain 的配置空间窗口在哪里

4.2 ARM / 嵌入式平台

通常来自 Device Tree。

比如设备树里可能有:

pcie@fd0e0000 { compatible = "vendor,pcie-host"; reg = <0x0 0xfd0e0000 0x0 0x100000>; bus-range = <0x00 0xff>; ranges = <...>; interrupts = <...>; };

这些信息告诉 Linux:

PCIe 控制器寄存器在哪里 配置访问窗口在哪里 outbound memory window 怎么映射 中断怎么接 bus number 范围是多少

Linux 的 PCIe host controller driver 会根据这些信息初始化 RC,然后把它注册成一个 PCI Host Bridge。


5. Linux 内部是怎么分发到对应 RC 的?

Linux 里每个 PCI bus 都属于某个 host bridge。

简化结构是:

pci_host_bridge ↓ pci_bus ↓ pci_dev

每个 host bridge 会带一组配置访问函数,通常抽象成:

structpci_ops{int(*read)(structpci_bus*bus,unsignedintdevfn,intwhere,intsize,u32*val);int(*write)(structpci_bus*bus,unsignedintdevfn,intwhere,intsize,u32 val);};

当 Linux 要读某个设备配置空间时:

pci_bus_read_config_dword() ↓ 找到这个 bus 对应的 pci_ops ↓ 调用该 RC/host bridge 的 read() ↓ read() 访问对应 RC 的 ECAM 或 RC 控制器寄存器 ↓ RC 生成 PCIe Configuration TLP

所以如果设备属于 RC1 管理的 bus,Linux 就调用 RC1 的配置访问函数。

这就是软件层面的“知道走哪个 PCIe 控制器”。


6. RC 生成什么 TLP?

枚举时主要是:

Configuration Read Request Configuration Write Request Completion with Data Completion without Data

例如软件想读 Vendor ID / Device ID:

CPU 读 ECAM 地址 ↓ RC 生成 Configuration Read Request TLP ↓ Endpoint 收到配置读请求 ↓ Endpoint 返回 Completion with Data ↓ RC 把 Completion 数据返回给 CPU

从软件角度看,只是一次普通的 MMIO read。

从 PCIe 总线上看,则是:

Configuration Read Request ↓ Completion with Data

7. Type 0 和 Type 1 配置 TLP 怎么决定?

PCIe 配置请求有两类:

Configuration Type 0 Configuration Type 1

大致可以这样理解:

类型用途
Type 0访问当前下游总线上的设备
Type 1穿过 PCIe Bridge / Switch,访问更下级总线

比如:

RC / Root Port ↓ Endpoint

直接挂在 Root Port 后面的 Endpoint,最终下发到链路上时通常是 Type 0 配置请求。

如果中间有 Switch:

RC / Root Port ↓ PCIe Switch ↓ Endpoint

访问 Switch 后面的设备时,可能先发 Type 1 请求,让 Switch 根据 bus number 继续转发,到了目标下游端口再变成 Type 0。

所以路由依据主要是:

Bus Number Device Number Function Number Bridge 的 secondary/subordinate bus number

也就是 PCIe 的 BDF 路由体系。


8. 多个 PCIe 口时,系统怎么区分?

假设主机有两个 Root Port:

Root Port A → 插槽 A Root Port B → 插槽 B

系统可能分配:

Root Port A 下游 bus = 01 Root Port B 下游 bus = 02

那么:

访问 01:00.0 ↓ 走 Root Port A 访问 02:00.0 ↓ 走 Root Port B

这个映射关系是在枚举时建立的。

Root Port 本身像一个 PCI-to-PCI Bridge,有:

Primary Bus Number Secondary Bus Number Subordinate Bus Number

例如 Root Port A:

Primary Bus = 00 Secondary Bus = 01 Subordinate Bus = 01

Root Port B:

Primary Bus = 00 Secondary Bus = 02 Subordinate Bus = 02

所以当软件访问 bus 01 的设备时,PCI core/RC/bridge routing 会把配置请求导向 Root Port A。

当访问 bus 02 时,会导向 Root Port B。


9. 普通 Memory TLP 也是类似逻辑,但依据是地址窗口

枚举时主要是配置 TLP。

枚举完成后,Linux 会给设备 BAR 分配地址。

例如:

FPGA BAR0 分配到 Host 物理地址 0xB000_0000

驱动中:

bar0=pci_iomap(pdev,0,0);

之后 CPU 写:

writel(0x1,bar0+DMA_CTRL);

从总线角度看:

CPU 写某个 MMIO 地址 ↓ 地址译码命中 PCIe outbound memory window ↓ RC 生成 Memory Write TLP ↓ TLP 通过 PCIe link 到 FPGA ↓ FPGA PCIe IP 根据 BAR hit 把写请求交给用户逻辑

所以配置阶段和普通 MMIO 阶段类似:

阶段CPU 访问什么RC 生成什么 TLP路由依据
枚举阶段配置空间窗口 ECAMConfiguration Read/WriteBDF
驱动访问 BARBAR 对应 MMIO 地址Memory Read/WriteAddress
DMA 阶段FPGA 主动访问 Host 内存Memory Read/WriteAddress

10. 对 FPGA Endpoint 来说,你看到的是什么?

你的 FPGA PCIe IP 会收到来自主机的 TLP。

枚举阶段,FPGA PCIe IP 主要处理:

Configuration Read Configuration Write

这些通常由 PCIe IP 内部配置空间模块响应。

比如主机读:

Vendor ID / Device ID BAR MSI Capability PCIe Capability

多数情况下,Xilinx/Intel PCIe Endpoint IP 已经帮你实现了配置空间响应。

你的用户逻辑通常不需要自己解析这些配置 TLP。

驱动阶段,主机访问 BAR 时,FPGA 侧会看到:

Memory Read / Memory Write TLP

这时 PCIe IP 会把 BAR 命中的访问转换成 AXI-Lite、Avalon-MM 或 AXI-Stream 接口给你的逻辑。

所以:

枚举配置空间:多由 PCIe IP 内部处理 BAR 寄存器访问:交给你的 FPGA 用户逻辑处理 DMA TLP:由你的 DMA 引擎/PCIe IP 用户接口处理

11. 你可以用一个完整例子串起来

假设 FPGA 插在 PCIe 插槽上。

阶段一:链路建立

主机上电 ↓ PCIe Refclk 稳定 ↓ PERST# 释放 ↓ RC 和 FPGA PCIe IP 进行 LTSSM 链路训练 ↓ Link up

这一步是硬件链路层完成的。


阶段二:Linux/BIOS 枚举

Linux PCI core 想扫描某个 bus/dev/function ↓ 访问对应 RC 的 ECAM 配置窗口 ↓ 主机地址译码把访问送到 PCIe RC ↓ RC 生成 Configuration Read TLP ↓ TLP 从 RC 的 PCIe PHY/Lane 发出 ↓ FPGA PCIe IP 收到配置读 ↓ 返回 Vendor ID / Device ID ↓ Linux 知道这个 BDF 上有设备

阶段三:分配 BAR

Linux 读取 BAR 大小 ↓ 给 BAR0 分配 Host 物理地址 ↓ 通过 Configuration Write TLP 写入 BAR 寄存器 ↓ FPGA PCIe IP 记录 BAR base/size 信息

阶段四:驱动访问 BAR

你的 pci_driver probe() ↓ pci_request_regions() ↓ pci_iomap() ↓ writel(DMA_START, bar0 + offset) ↓ CPU 写 BAR 对应 MMIO 地址 ↓ RC 生成 Memory Write TLP ↓ FPGA PCIe IP BAR hit ↓ 用户逻辑收到寄存器写操作

12. 最核心结论

你的问题:

RC PCIe 枚举的过程,发送的数据包怎么知道要通过板子上的 PCIe 硬件接口传出去?

答案是:

1. Linux/BIOS 枚举时不是直接手写 TLP; 2. 它访问某个 PCIe Host Bridge / RC 的配置空间窗口; 3. CPU 地址译码把这个访问送到对应 RC 硬件; 4. 这个 RC 硬件把配置访问转换成 PCIe Configuration TLP; 5. TLP 只能从这个 RC 物理连接的 PCIe PHY/Lane 发出去; 6. 如果有多个 Root Port,则根据 host bridge、bus number、bridge bus range 选择对应路径; 7. Endpoint 收到配置 TLP 后返回 Completion; 8. Linux 得到 Vendor ID / Device ID 后,创建 pci_dev 并继续枚举。

你可以记成一句话:

软件通过“访问哪个 RC 的配置窗口”来选择 PCIe 控制器;硬件通过“地址译码和 Root Port 拓扑”决定 TLP 从哪个 PCIe 物理接口发出去。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 3:01:55

PCIe 6.0调试笔记:用逻辑分析仪抓取并解析Optimized_Update_FC流控包

PCIe 6.0调试实战&#xff1a;逻辑分析仪捕获与解析Optimized_Update_FC流控包全流程当PCIe 6.0设备的链路层出现流控异常时&#xff0c;工程师常会遇到一个关键问题&#xff1a;如何从海量的Flit数据中准确识别并解析Optimized_Update_FC&#xff08;OFC&#xff09;包&#x…

作者头像 李华
网站建设 2026/6/15 2:57:51

python协同过滤算法,一算一个准,推荐系统灵魂暴击

可通过以下步骤来实现协同过滤推荐系统, 首先是数据准备, 要获取用户与物品的评分数据, 就像等等这样的数据&#xff1b;接着构建用户与物品矩阵, 运用所使用的pivot方法去转换数据结构&#xff1b;然后计算相似度, 这是基于用户或者物品来进行的, 常用的是余弦相似度或者皮尔逊…

作者头像 李华
网站建设 2026/6/15 2:47:50

LDO选型避坑指南:从‘热死机’到‘压差不足’,我用TPS79501踩过的那些坑

LDO选型避坑指南&#xff1a;从热失效到压差不足的实战经验去年夏天&#xff0c;我负责的一个便携式医疗设备项目差点因为LDO选型失误而延期交付。当第一批样机在高温测试中频繁死机时&#xff0c;我才意识到那些数据手册上容易被忽略的小字参数有多重要。本文将分享从惨痛教训…

作者头像 李华