CPU 访问 PCIe 设备:CPU 物理地址(PA) → Root Port(ATU) → PCIe 总线地址
PCIe 设备 访问 CPU(DMA):PCIe 总线地址 → Root Port(ATU/IOMMU)→ CPU 物理地址(PA)
中间没有魔法,全部是地址映射 + 路由转发。
3 种地址
- CPU 物理地址(CPU PA)
- CPU 看到的地址
- ECAM、BAR 都属于这类
- PCIe 总线地址(Bus Address)
- PCIe 总线上传输的地址
- 设备寄存器、DMA 都用这个
- 设备本地偏移(Device Offset)
- 设备内部寄存器的偏移(如 0x00, 0x10)
CPU → 访问 PCIe 设备(读 / 写寄存器)
完整转化路径(硬件真实流程):
① 驱动发出虚拟地址
ioread32(virt_addr + 0x10);② MMU 转为 CPU 物理地址(BAR 地址)
虚拟地址 → MMU → **CPU PA = 0x8010_0000**③ 送到 PCIe Root Port
Root Port 查ATU(地址转换表):
CPU PA 0x8010_0000 → 映射为 PCIe 总线地址 0x0000_0010④ Root Port 打包成 PCIe 报文
- 地址:0x0000_0010
- 路由:由BDF找到目标设备
- 类型:Memory Write/Read
⑤ 设备收到报文
设备只认识PCIe 总线地址,不认识 CPU PA:
总线地址 0x0010 → 设备内部寄存器偏移 0x10PCIe 设备 → 访问 CPU(DMA 写内存)
① 设备驱动告诉设备 DMA 地址
驱动写的是CPU PA,但设备不懂。
② Root Port 再次做转换
设备发:
PCIe 总线地址 = 0x8000_0000Root Port 查表:
PCIe 总线地址 0x8000_0000 → CPU 物理地址 0x8000_0000③ 发给内存控制器
最终写到 DRAM。
地址转化的两个硬件模块
①Root Port 内部的 ATU
Address Translation Unit
- 管:CPU PA ↔ PCIe 总线地址
- 作用:让两边互不感知对方地址
- 内核在枚举时配置好
②IOMMU(可选,但服务器必开)
- 管:PCIe 总线地址 → 最终内存地址
- 作用:隔离、安全、虚拟化
- 开启后:
PCIe 地址 → IOMMU → 内存 PA
3 组关系
| 方向 | 地址 A | 转化硬件 | 地址 B | 用途 |
|---|---|---|---|---|
| CPU→设备 | CPU 物理地址(BAR) | Root Port ATU | PCIe 总线地址 | 寄存器读写 |
| DMA←设备 | PCIe 总线地址 | Root Port / IOMMU | CPU 物理地址 | DMA 访问内存 |
| 配置访问 | ECAM 物理地址 | Root Port 解析 | BDF 路由 | 配置空间 |
BDF 路由表
每个 Bridge(Root Port / Switch Upstream / Switch Downstream)都有一张路由表,由三个寄存器构成:
- Primary Bus Number(上游总线号)
- Secondary Bus Number(本端口直连的下游总线)
- Subordinate Bus Number(此端口下面所有子总线最大值)
这三个数,就是BDF 路由表。
路由规则
当一个配置报文到达 Bridge:
目标 Bus = B 目标 Dev = D 目标 Func = FBridge 只看Bus 号 B:
B < Secondary Bus→ 报文是发给上游的 →往回发(Primary 方向)
B == Secondary Bus→ 是直连设备 →从本端口发出去Dev/Func 由设备自己判断
Secondary Bus < B ≤ Subordinate Bus→ 是我下面更深层的桥 →继续往下发(本端口)
B > Subordinate Bus→ 不归我管 →不管,丢给上游
和 ATU 的区别
ATU 表:地址 → 地址(Mem 访问用)
BDF 路由表:Bus 号 → 哪个端口(Config 访问用)
- 读 BAR、写 BAR、枚举设备 →走 BDF 路由
- 读写寄存器、DMA →走地址(ATU)
极简总结
- BDF 路由表 = Primary / Secondary / Subordinate
- 只根据 Bus 号路由,不看 Dev/Func
- 每个桥都有一张
- 专门给配置事务(ECAM、config)用
- 内核扫描时自动配置
- 和 ATU 完全两回事:
- ATU = 地址翻译
- BDF 路由 = 配置报文转发路径
内核配置 ATU & BDF,扫描 PCIe 时:
- 通过ECAM找到设备BDF
- 读取BAR,知道需要多大地址窗口
- 分配一段CPU 物理地址
- 把CPU PA → PCIe 地址写入ATU
- 记住这个ATU 表项 对应 这个 BDF 设备
之后每次访问该 CPU PA:
- Root Port 自动用ATU 翻译地址
- 自动用BDF 路由到设备
关键总结
- CPU 不直接跟设备说话,靠 Root Port 翻译。
- CPU 用物理地址 PA,设备用总线地址。
- ATU 是翻译官:
- CPU 访问设备:PA → 总线地址
- DMA 写内存:总线地址 → PA
- BAR 地址 = CPU 端的门牌号总线地址 = 设备端的门牌号Root Port 就是门牌翻译器