为什么需要地址翻译?

CPU 侧看到的 DDR 物理地址与 PCIe 总线域的地址是两个空间。内存映射 I/O 要求两域地址"看起来连续可用",但 SoC 的 DDR 起始地址往往不在 4GB 之内(例如 64 位 DDR 从 0x800000000 开始),而 32 位设备无法生成高于 4GB 的地址。控制器需要一个地址翻译引擎在硬件上完成两域的映射。

先明确一个概念边界

iATU(Address Translation Unit)是 Synopsys DesignWare 控制器的实现概念,不是 PCIe Base Spec 的标准部件。PCIe 规范只约定 TLP 的总线域地址语义;各控制器 IP 有自己的实现——DesignWare 用 iATU,Cadence 用不同的地址翻译表,某些 SoC 集成 IOMMU/SMMU 时可省去部分翻译。本文以 DesignWare + Linux dw_pcie 框架为主线,寄存器细节取自内核 drivers/pci/controller/dwc/pcie-designware.h,换 IP 时请对照对应 databook。

方向 谁发起 TLP 翻译什么 典型用途
Outbound(出站) CPU 本地(RC 侧) CPU 物理地址 → PCIe 总线地址 MMIO 访问下游设备 BAR、ECAM 配置空间访问窗口
Inbound(入站) 对端(EP 收到 RC 的 MWr / RC 收到 EP 的 MWr) PCIe 总线地址 → 本地 DDR 物理地址 EP 侧:把 RC 发来的地址翻译成 DDR 缓冲区;RC 侧:设备 DMA 落到 DDR

RC 驱动,outbound 窗口是命脉:没有它,CPU 连配置空间都读不到。对 EP 驱动,inbound 窗口把 BAR 命中的 TLP 引向 DDR,这是主机与 EP 交换数据的全部通道。这也是为什么本页同时列在 RC 与 EP 两大方向的必读清单里。

region 寄存器模型

每个 iATU region 是一组寄存器,描述一条翻译规则:命中 [Base, Limit] 区间的入/出站 TLP,地址改写为 Target,并按 Type 生成/路由 TLP。DesignWare 提供 outbound 与 inbound 两组、每组最多 MAX_IATU_OUT/IN = 256 个 region(v4.80+ 支持地址展开 unrolled 访问)。

region 内寄存器布局(相对 region 基址)

每个 region 占 0x200 字节:outbound 在 unroll 基址 (index << 9),inbound 再偏移 BIT(8);unroll 基址相对 DBI 的默认偏移为 DEFAULT_DBI_ATU_OFFSET = 0x300000atu_base 也可由驱动单独映射)。老版本内核/内核用的 viewport 访问方式寄存器为 PCIE_ATU_VIEWPORT = 0x900(BIT(31) 选 inbound 方向,低位选 region 号)。

偏移 寄存器 作用
0x000IATU_REGION_CTRL1TLP 类型(FMT/Type 编码值)与控制:PCIE_ATU_INCREASE_REGION_SIZE BIT13(v4.60A+ 突破 4GB region 上限)、PCIE_ATU_TD BIT8(TD 置位携带 TLP Digest)
0x004IATU_REGION_CTRL2PCIE_ATU_ENABLE BIT31(region 使能)、PCIE_ATU_BAR_MODE_ENABLE BIT30(inbound BAR 匹配模式)、PCIE_ATU_CFG_SHIFT_MODE_ENABLE BIT28(CFG 窗口偏移自动拼接)、PCIE_ATU_INHIBIT_PAYLOAD BIT22(无数据 Message)、PCIE_ATU_FUNC_NUM_MATCH_EN BIT19(仅匹配指定 Function)
0x008 / 0x00CIATU_LBAR / UBAR匹配区间基址(outbound 为 CPU 侧地址;inbound 为 PCIe 总线地址)
0x010 / 0x020IATU_LIMIT / UPPER_LIMIT匹配区间上限(含端点;v4.60A+ 提供 64 位上限)
0x014 / 0x018IATU_LTRAR / UTAR翻译目标地址(outbound 为 PCIe 总线地址;CFG 窗口在此打包 bus/dev/fn)

outbound 窗口的 TLP 类型(Ctrl1 的 type 字段)

内核 drivers/pci/pci.h 中定义(即 FMT/Type 字段的值):

常量说明
PCIE_TLP_TYPE_MEM_RDWR0x00Memory Read/Write(MMIO 窗口)
PCIE_TLP_TYPE_IO_RDWR0x02I/O Read/Write(IO 窗口)
PCIE_TLP_TYPE_CFG0_RDWR0x04Config Type 0(根总线直连设备)
PCIE_TLP_TYPE_CFG1_RDWR0x05Config Type 1(下游 Switch/桥)
PCIE_TLP_TYPE_MSG0x10Message(配合 INHIBIT_PAYLOAD 发送无数据消息)

RC 实战一:ECAM 配置窗口(outbound)

RC 驱动最关键的 outbound 用途是把一段 CPU 地址空间变成配置空间访问窗口。内核 dw_pcie_setup_cfg_atu()drivers/pci/controller/dwc/pcie-designware-host.c)的真实做法是:

  • 根总线(bus 0)不需要 iATU——RC 自身的配置空间通过 DBI(控制器内部寄存器空间)直接访问;
  • 根总线下的第一层设备用 Type 0 窗口,覆盖 1MB = 1 bus × 32 dev × 8 func × 4KB
  • 其余总线用 Type 1 窗口,覆盖 bus-range 的剩余部分,发给下游 Switch/桥继续路由。
/* 摘自 drivers/pci/controller/dwc/pcie-designware-host.c(节选) */
atu.index = 0;
atu.type = PCIE_TLP_TYPE_CFG0_RDWR;
atu.parent_bus_addr = pp->cfg0_base + SZ_1M;   /* 窗口的 CPU 侧起点 */
atu.size = SZ_1M;                              /* 1MB = 1 条总线 */
atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE;    /* 关键:CFG 偏移拼接 */
ret = dw_pcie_prog_outbound_atu(pci, &atu);

/* 剩余总线用 Type 1 */
atu.index = 1;
atu.type = PCIE_TLP_TYPE_CFG1_RDWR;
atu.parent_bus_addr = pp->cfg0_base + SZ_2M;
atu.size = (SZ_1M * bus_range_max) - SZ_2M;
atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE;

CFG SHIFT MODE 的语义

CPU 访问配置空间时,内核按 ECAM 规则拼地址:

CFG 窗口基址 + ((bus − 1) << 20 | dev << 15 | func << 12 | 寄存器偏移)

其中 bus 部分要被 iATU"抽走"并写进 TLP 头的 Bus Number 字段(CFG1 还要把 bus 相对值 +1 换算回绝对总线号),dev/func 写进 Target 寄存器的对应位段:PCIE_ATU_BUS(x) 占 BIT[31:24]、PCIE_ATU_DEV(x) 占 BIT[23:19]、PCIE_ATU_FUNC(x) 占 BIT[18:16]。CFG_SHIFT_MODE_ENABLE 让控制器自动完成这组拆包/打包,驱动只需要一个窗口服务整条总线。

MEM/IO outbound 窗口

设备树 ranges 属性里的每个内存/IO 窗口,通常各对应一个 outbound region:CPU 地址段 → PCI 地址段,type 为 MEM_RDWR / IO_RDWRRC 控制器驱动页的设备树示例中 ranges = <0x02000000 0x0 0x80000000 0x80000000 0x0 0x20000000> 即"PCI 0x80000000(512MB)↔ CPU 0x80000000"的映射,由驱动换算成 region 的 Base/Limit/Target 三元组。

编程约束(源码级)

  • Base 与 Limit 必须落在同一个 4GB(或 region 上限)边界内(limit & ~region_limit) != (base & ~region_limit) 会直接返回 -EINVAL;跨边界需 INCREASE_REGION_SIZE 或拆分 region。
  • 地址需满足 region_align 对齐,size 不能为 0;Limit 写的是包含端点的最后一个地址(addr + size − 1)。
  • 写完 CTRL2 使能后,内核会回读 ENABLE 位确认生效(重试 5 次),未生效报 "Outbound iATU is not being enabled"——这是现场最常见的配置窗失效日志。

EP 实战二:BAR 入站映射(inbound)

EP 侧 RC 发来 MWr/MRd,其地址是主机分配的 PCI 地址(通常是 EP BAR 的地址)。EP 控制器需要把命中 BAR 区间的入站 TLP 翻译到本地 DDR 缓冲区。两种方式:

方式机制适用
BAR shadow 寄存器 控制器提供 BAR 寄存器直接指向 DDR 地址,地址翻译由控制器内部完成,不占 iATU region 简单映射,功能驱动经 pci_epc_set_bar() 配置
iATU BAR 匹配模式 region 以 BAR_MODE_ENABLE(CTRL2 BIT30)按 BAR 号匹配命中,Target 指向 DDR;或按地址区间匹配 需要细粒度拆分(如按 Function 匹配 FUNC_NUM_MATCH_EN)、BAR 尺寸超出单个 region 或需要地址重映射时

内核 EP 框架中,pci_epc_set_bar() 最终进入控制器驱动的 set_bar() 回调(如 dw_pcie_ep_set_bar()),以 dw_pcie_prog_ep_inbound_atu() 把 BAR 区间接翻译到 phys_addr(DDR)。功能驱动(pci_epf_*)不需要碰 iATU——这正是 EPC/EPF 分层把控制器细节隔离在 EP 控制器驱动里的意义。

RC 侧也有 inbound

不要以为 inbound 只属于 EP:设备的 DMA 写(MWr)到达 RC 时,也要经 inbound 翻译落进 DDR。许多 SoC 的 RC inbound 默认直通(1:1 映射),配合 IOMMU/SMMU 做细粒度管控;当 PCI 总线地址与 DDR 物理地址不一致(如 DMA 区受限)时,同样需要 inbound region 或地址窗口(dma-ranges)参与翻译。

dw_pcie 框架速览

Linux 把 DesignWare 的公共逻辑收敛在 drivers/pci/controller/dwc/pcie-designware*.c,厂商驱动(qcom/layerscape/imx6 等)只实现差异部分:

接口职责
核心struct dw_pcie / dw_pcie_ops读写 DBI/ATU、链路能力查询、iATU 编程(dw_pcie_prog_outbound_atu() 等)
Hostdw_pcie_host_init() / dw_pcie_rpECAM/配置窗口、MSI 域、pci_host_probe() 枚举
EPdw_pcie_ep_init() / dw_pcie_eppci_epc_ops 实现(set_bar/raise_irq 等)、inbound BAR 映射
厂商 glueplatform_driver.probe时钟/复位/电源、PHY 初始化、平台差异寄存器

RC 控制器驱动的骨架对照阅读:你在厂商 probe 里调用 dw_pcie_host_init() 后,前文所有 iATU 编程都发生在它内部。

控制器 DMA 引擎(DWC eDMA)

除了 CPU 经 MMIO 搬数据,DesignWare 还内置 eDMA:独立的读/写引擎 + 多通道,由链式描述符(LLP,Link List Pointer)驱动,可把 EP 侧大块数据在本地 DDR 与 PCIe 之间自主搬运,把 CPU 从逐包 MMIO 中解放出来。要点:

  • 读引擎/写引擎分离:读引擎把 DDR 数据发向 PCIe(对应主机方向收数据),写引擎把入站 TLP 数据写入 DDR;每个方向支持多个通道并行。
  • 描述符(LLP):每个描述符描述一段传输(源/目的、长度、下一个 LLP 指针),硬件按链自动推进,适合大块/多段散射聚合传输。
  • 通道与中断:各通道独立使能与门铃(doorbell)寄存器,完成/错误经独立中断上报。
  • Linux 侧可经 dmaengine 框架把 eDMA 通道暴露为通用 DMA 通道,设备驱动无需感知控制器差异。

性能视角:在 性能调优里讨论的 MPS/MRRS 对 eDMA 同样生效——描述符里的单段长度会拆成 MPS 大小的 MWr 序列。

iATU 相关调试

症状常见原因手段
枚举时读回 0xFFFFFFFFCFG 窗口未使能/未生效,bus-range 超出窗口覆盖检查 "Outbound iATU is not being enabled" 日志;回读 CTRL2 ENABLE;核对 CFG0/CFG1 窗口与 bus-range
MMIO 访问总线错误或死循环MEM 窗口 Base/Limit 跨 4GB 边界被拒绝;地址未按 region_align 对齐打印 dw_pcie_prog_outbound_atu() 的返回值;核对 ranges 与 region 参数
RC 写 EP 数据对不上inbound region 目标地址错、BAR 与 region 尺寸不一致、多 Function 命中错 regionFUNC_NUM_MATCH_EN 隔离 Function;核对 Target 与 DDR 物理地址;lspci 确认主机分配的 BAR 地址落在窗口内
切换 Function 后中断丢失region 的 func_num 未配置或未开匹配检查 CTRL1 FUNC_NUM(pf) 与 CTRL2 FUNC_NUM_MATCH_EN

现场可把本站 速查表配置空间解析器(粘贴 lspci dump 验证 BAR 分配结果)配合使用。

理解检测
1. 关于 iATU,下列说法正确的是?
iATU 是 Synopsys DesignWare 的实现概念而非规范标准部件(Cadence 等 IP 各有自己的机制);DesignWare RC 的根总线配置空间经 DBI 直接访问、无需 iATU;inbound 翻译在 RC 侧同样存在(设备 DMA 落 DDR)。
2. 配置 Type 1 的 outbound 窗口(CFG1)用于访问什么?
CFG0(Type 0)覆盖根总线下的第一层设备;CFG1(Type 1)发给下游 Switch/桥,由其继续向子树路由。RC 自身配置空间走 DBI,与 iATU 无关。