AMBA CHI 协议学习笔记

基于 Arm 官方文档 “Learn the architecture - Introducing AMBA CHI” (Issue 01, June 2025, 102页) 整理。
面向对象:芯片固件工程师、BSP 工程师、SoC 验证工程师、驱动开发人员。

一、前言:从 ACE 到 CHI

1.1 演进背景

AMBA CHI (Coherent Hub Interface) 是 AXI Coherency Extensions (ACE) 协议的继任者。ACE 为多核一致性提供了良好的基础,但随着 CPU 核数增加、异构计算(GPU / NPU / PCIe 子系统)引入,ACE 的两个根本瓶颈暴露出来:

  1. 总线式拓扑:ACE 基于点对点总线,当组件数增长时带宽无法线性扩展。
  2. Master/Slave 的语义局限:难以描述多级缓存、Snoop Filter、目录式一致性等复杂场景。

CHI 直接在协议层引入了拓扑无关的抽象——将片上网络 (NoC) 的组织(Ring / Mesh / Crossbar)交给实现方,协议只定义节点角色、通道分工和事务语义。这一设计哲学类似于网络栈中 TCP/IP 对物理层透明:不管你跑 Ethernet 还是 Wi-Fi,我只看 packet。

1.2 术语更新

Arm 在 CHI 中彻底废弃了 Master/Slave 称谓:

旧术语 (ACE/AXI) 新术语 (CHI) 含义
Master Requester 发起事务的节点
Slave Subordinate 响应事务的节点
Slave Completer 完成事务的节点

软件视角:如果你在 bare-metal 或 RTOS 下编写过 AXI DMA 驱动,你一定见过 DMA_SRC_ADDR / DMA_DST_ADDR 这类寄存器。切换到 CHI 后,需要理解的是 Node ID、TxnID、DBID 这三元组而不是 AXI 的 AWID/WID/BID。

1.3 协议迭代全景

CHI 目前有七个 Issue:A → B → C → D → E → F → G,相邻 Issue 之间都不向后兼容(各通道字段宽度不同)。它们对应的 Arm 架构版本如下:

Issue 对应架构 核心新增特性
A Armv8.0 基础 CHI:四通道、Flit 格式、一致性流
B Armv8.1/8.2 原子事务、RAS、DMT/DCT、Cache Stashing、I/O Deallocation、MESI 支持
C CompAck 提前、DataSepResp、NCBWrDataCompAck
D Armv8.4 MPAM、DVM 提前完成、TxnID 扩至 10-bit、接口奇偶保护
E Armv8.4/8.5 MTE、DWT、WriteZero、Combined Write+CMO、TLBI Range
F Armv9.2 RME、CAH、PBHA、Deferrable Write
G Armv9.3 MEC、PrefetchTgtHint、MTE_Support 属性、12-bit PartID、Limited Data Elision

二、CHI 协议核心概念

2.1 三种网络拓扑与适用场景

CHI 不规定连接方式,但定义了三种典型拓扑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
图 2.1 — Ring 拓扑 (中规模系统)
┌───┐ ┌───┐
│RN0│─────│RN1│
└─┬─┘ └─┬─┘
│ ┌───┐ │
└──│HN │──┘
┌──│ │──┐
│ └───┘ │
┌─┴─┐ ┌─┴─┐
│SN0│─────│SN1│
└───┘ └───┘
延迟随节点数线性增长,适合 ~8-16 个节点。

图 2.2 — Mesh 拓扑 (大规模系统)
RN0 ──┬── RN1 ──┬── RN2
│ │ │ │ │
├────┼────┼────┼────┤
│ │ │ │ │
HN0 ──┴── HN1 ──┴── HN2
│ │ │ │ │
├────┼────┼────┼────┤
│ │ │ │ │
SN0 ──┴── SN1 ──┴── SN2
多路径提供更高带宽,面积代价大,适合 16+ 节点、数据中心芯片。

图 2.3 — Crossbar 拓扑 (小规模系统)
┌─ RN0
HN ───┼─ RN1
└─ RN2
每对节点直连,性能最优但布线成本 O(n²),适合 ≤ 8 个节点。

2.2 节点分类体系

CHI 定义了三类主节点,每类又按一致性能力细分:

1
2
3
4
5
6
7
8
9
10
11
12
图 2.4 — CHI 节点分类树
┌── RN-F (Full Coherent, 有缓存, 接受 Snoop)
Request Node (RN) ──────┼── RN-I (I/O Coherent, 无缓存, 不接受 Snoop)
└── RN-D (I/O Coherent + DVM 支持)

Home Node (HN) ─────────┼── HN-F (Full Coherent, 给 RN-F 的请求排序/发 Snoop)
└── HN-I (Non-Coherent, 处理 I/O 子系统请求)

Subordinate Node (SN) ──┼── SN-F (内存控制器, 连接 coherent 内存空间)
└── SN (I/O 外设 / Non-Coherent 内存)

Miscellaneous Node (MN) ─── 处理 DVM 事务 (有时被实现为 HN-D)

软件视角:在 BSP 开发中,HNF 的 System Address Map (SAM) 直接决定了物理地址到 Node ID 的映射。当你配置一个外设的 DMA 缓冲区地址时,这个地址在 SAM 中必须命中正确的 HN-I 对应的地址范围,否则写数据会发到错误的目标节点。理解 Node ID 分配是调试 coherency 问题(尤其是 cache 一致性问题)的第一步。

2.3 Cache Line 七态模型

CHI 在 ACE 的 MOESI 基础上扩展出七种状态。关键是新增了 PartialEmpty 维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
图 2.5 — CHI 缓存行状态层次树

Invalid

Valid
/ \
Unique Shared
/ \ / \
Dirty Clean Dirty Clean
/ \ |
Full Partial Full
/ \
Full Empty

七态完整定义:

状态 全称 缓存行特征 写权限 其他缓存中?
I Invalid 行不存在于本缓存
UD Unique Dirty 仅本缓存持有,已被修改 ✅ (无需通知)
UDP Unique Dirty Partial 仅本缓存持有,部分字节有效(可能全无效或全有效)
SD Shared Dirty 被修改过,本缓存负责写回内存 ❌ (需获取 Unique) 可能
UC Unique Clean 未修改,仅本缓存持有 ✅ (无需通知)
UCE Unique Clean Empty 仅本缓存持有,无有效字节(仅有所有权)
SC Shared Clean 未修改 (或不在本缓存),可能存在于多缓存 ❌ (需获取 Unique) 可能

关键规则:

  • 写操作只允许在 Unique 状态下本地执行。
  • UDP 状态的缓存行不能直接转发给原始 Requester(即使 Snoop 要求),必须等全部字节写入有效或转为其他状态。
  • UCE 的 Snoop 响应也不能返回数据给 HN 或 Requestor。

软件视角:如果你在调试一个 “dirty cache line lost” 或 “write data corruption” 问题,理解 UDP 和 UCE 状态尤其重要。当你看到 CPU 写了一个 partial store(比如 strb 写 1 字节),缓存控制器会进入 UDP 状态。如果此时另一个 CPU 发起对该地址的 Snoop,HN-F 必须 stall,不能直接用 Stash 机制获取数据。

2.4 System Address Map (SAM)

SAM 的作用是物理地址到 Node ID 的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
图 2.6 — SAM 两阶段地址转换

[RN 0 发出地址 0x8000_0000 的事务]


┌──────────┐
│ RN SAM │ ──── 查表: 0x8000_0000 → HN Node ID = 5
└──────────┘

▼ 路由到 Node 5
┌──────────┐
│ HN SAM │ ──── 查表: 0x8000_0000 → SN Node ID = 2
└──────────┘

▼ 路由到 Node 2
[SN (内存控制器)]

SAM 的三个硬性要求:

  1. 必须完整描述整个系统地址空间。
  2. 未对应物理组件的地址必须映射到能返回错误响应的节点。
  3. 所有 RN 的 SAM 必须一致——无论哪个 RN 发起请求,同一物理地址必须到达同一 HN。

软件视角:SAM 的格式是 IMPLEMENTATION DEFINED,由硬件设计决定。但在设备树 (Device Tree) 或 ACPI 表的 _CRS / _DMA 描述中,地址范围划分隐含了 SAM 的拓扑。当你为新 SoC 编写 Trusted Firmware-A (TF-A) 的地址转换表 (xlat table) 或配置 SMMU Stream Table Entry 时,必须保证地址编码与 SAM 对齐。

2.5 四大通道 (REQ / RSP / SNP / DAT) 与 Flit 报文结构

CHI 使用四个独立通道传递协议消息——这和 ACE 的五个通道 (AW/W/B/AR/R) 完全不同:

1
2
3
4
5
6
7
8
9
10
11
图 2.7 — RN-F 的 CHI 接口通道

TXREQ ────────────────► (发送 Read/Write/Cache Maintenance 请求)
TXRSP ────────────────► (发送 CompAck / Snoop 响应)
TXDAT ────────────────► (发送 Write Data / Snoop 响应数据)
TXSNP ────────────────► (只有 HN-F / MN 发送 Snoop / DVM)

RXREQ ◄──────────────── (接收来自其他节点的请求)
RXRSP ◄──────────────── (接收 Completion / DBIDResp)
RXDAT ◄──────────────── (接收 Read Data)
RXSNP ◄──────────────── (RN-F 接收 Snoop)

TX 前缀 = 本节点发送方向;RX 前缀 = 本节点接收方向。

Flit 概念:所有协议消息以 Flit (Flow Control Unit) 形式传输,所有字段并行发送(不是 PCIe / Ethernet 的串行化方式)。Flit 传输采用 credit-based 流控:

  1. Transmitter 拉高 FLITV 指示 Flit 有效。
  2. 下一个 Clock 上升沿完成传输。
  3. 只有预先收到接收方 LCRDV 信号(credit)才能发送。

2.6 Flit 关键标识字段

以 Request Flit (REQ) 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
图 2.8 — Request Flit 关键字段 (示例布局)

位范围(示例*) | 字段 | 位宽 | 说明
────────────────┼────────────────┼────────┼─────────────────────────────
3 : 0 | QoS | 4 | 服务质量等级
14 : 4 | TgtID | 11 | 目标 Node ID (路由用)
25 : 15 | SrcID | 11 | 源 Node ID
33 : 26 | TxnID | 8 | 事务 ID (每个 SrcID 下唯一)
59 : 54 | Opcode | 6 | 请求类型 (ReadNoSnp=0x04 等)
62 : 60 | Size | 3 | 数据大小 (0=1B, 7=64B)
110 : 63 | Addr | 48 | 物理地址
111 : 111 | NS | 1 | Non-Secure 标记
123 : 120 | MemAttr | 4 | 内存属性 (Cacheable / Device 等)
124 : 124 | SnpAttr | 1 | 是否可 Snoop
129 : 125 | LPID | 5 | 逻辑处理器 ID
130 : 130 | Excl / SnoopMe | 1 | Exclusive Access / SnoopMe
────────────────┴────────────────┴────────┴─────────────────────────────
* 实际位范围根据 NodeID / Address / RSVDC 宽度而不同

DBID (Data Buffer ID) 的关键作用

  • 写事务:Requester 必须收到 Completer 的 DBIDResp 后才能发送写数据。
  • **读事务 (需要 CompAck)**:Data Flit 携带 DBID,Requester 用此 DBID 作为 TxnID 发 CompAck。

引用原文的一句话:”The Target Node uses this identifier to signal availability to receive Write Data, and to deallocate transactions that require Completion Acknowledgement.” — 这句话概括了 DBID 的全部语义。

各通道使用的标识符总结:

Identifier REQ RSP DAT SNP
SrcID
TgtID —*
TxnID
DBID

*SNP 通道不含 TgtID。因为 CHI 允许 IMPLEMENTATION DEFINED 的 snoop 路由方式(广播 vs Snoop Filter)。当 Snoop Flit 离开 interconnect 时,它已经确定了目标节点。

软件视角 — Crashdump 分析:当你在 EL3 固件中遇到总线错误需要解析 CHI Transaction Trace 时,第一件事就是定位 Flit 的 SrcID / TgtID / TxnID / Opcode 四元组。TxnID 8-bit 意味着一个 Requester 最多 256 个未完成事务(CHI-D 后扩展到 1024)。如果固件日志里看到 TxnID 复用冲突或未等待的 DBID,基本就是 protocol violation。


三、事务处理流程

3.1 ReadNoSnp 事务完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
图 3.1 — ReadNoSnp 事务时序

[RN-0, NodeID=0] [Interconnect] [HN-3, NodeID=3] [SN-5, NodeID=5]
│ │ │
│ TXREQ: ReadNoSnp │ │
│ {SrcID=0,TgtID=3,TxnID=A} │ │
│───────────────────────────────────────► │
│ │ TXREQ:ReadNoSnp │
│ │ {SrcID=3,TgtID=5,│
│ │ TxnID=B} │
│ │───────────────────►
│ │ │
│ │ TXDAT:CompData │
│ │ {SrcID=5, │
│ │ TgtID=3, │
│ │ TxnID=B, │
│ │ Data} │
│ │◄──────────────────│
│ │ │
│ RXDAT: CompData │ │
│ {SrcID=3,TgtID=0, │ │
│ TxnID=A,Data} │ │
│◄──────────────────────────────────────│ │

步骤拆解:

  1. RN-0 通过 SAM 确定地址对应 HN-3,发送 ReadNoSnp 到其 TXREQ。
  2. HN-3 通过自己的 HN SAM 确定对应 SN-5,发起下游 ReadNoSnp。
  3. SN-5 返回数据给 HN-3。
  4. HN-3 转发数据给 RN-0。

3.2 WriteNoSnp 事务完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
图 3.2 — WriteNoSnp 事务时序

[RN-0, NodeID=0] [HN-3, NodeID=3] [SN-5, NodeID=5]
│ │ │
│ TXREQ:WriteNoSnp │ │
│ {TxnID=A} │ │
│──────────────────────►│ │
│ │ │
│ TXRSP:DBIDResp │ │
│ {TxnID=A, DBID=0} │ │
│◄──────────────────────│ │
│ │ │
│ ├─── 以下两步骤可并行 ────┤
│ │ │
│ TXDAT:NCBWrData │ TXREQ:WriteNoSnp │
│ {TxnID=0, Data} │ {TxnID=B} │
│──────────────────────►│──────────────────────►│
│ │ │
│ │ TXRSP:DBIDResp │
│ │ {TxnID=B,DBID=C} │
│ │◄──────────────────────│
│ │ │
│ │ TXDAT:NCBWrData │
│ │ {TxnID=C, Data} │
│ │──────────────────────►│

软件视角:关键点在于 DBIDResp 后数据步骤可与下游 WriteNoSnp 并行。这意味着如果固件在配置 DMA descriptor 时连续写出多个 WriteNoSnp 事务,CHI 硬件可以在收到第一个 DBIDResp 后立即开始数据传输,无需等待下游内存控制器响应。

3.3 Completion Acknowledgment

这段内容直击 CHI 协议中分布式缓存一致性设计最核心、也最难搞的”时序竞争(Race Condition)”问题

先说大白话结论:**CompAck 的本质,是 RN-F(CPU)给 HN-F(总管)发的一张”安全收货回执”。在总管拿到这张回执之前,它会强行把该地址的”窥探(Snoop)通道”锁死,不准任何人来打扰这个 CPU。**

我们把它拆解为以下三个核心问题来理解。


3.3.1 为什么会有这个危机?(如果没有 CompAck 会发生什么)

CHI 是一套网络化(NoC)总线,数据包在总线里飞是有延迟的,而且不同通道(REQ、DAT、SNP)走的物理路径不同,速度也不同

如果没有 CompAck,会发生下面这个灾难性的时序冲突:

  1. CPU 0 (RN-F) 的 Cache 里没有地址 X 的数据。它发出一个读请求 ReadShared
  2. 总管 (HN-F) 收到后,从内存里捞出了最新数据,通过 CompData 发送给 CPU 0。
  3. 【危机发生】:在数据包还在总线铜线里”飞”的途中,CPU 1 突然发起了一笔写请求,想要修改地址 X。
  4. 总管 (HN-F) 一看,既然有人要写,那我得把所有持有地址 X 的人的 Cache 擦除(Snoop Invalidate)。HN-F 查表发现,刚才已经把数据发给 CPU 0 了。于是 HN-F 立刻顺着 SNP 通道轰了一个 Snoop 信号给 CPU 0。
  5. 【灾难降临】:由于 SNP 通道(走控制线)可能比 DAT 通道(走数据线,数据大,延迟高)跑得快,结果 Snoop 信号比 CompData 数据包更早到达了 CPU 0!

💥 CPU 0 此时彻底懵了:我的状态机还在苦苦等待数据(过渡态),根本还没拿到这笔数据,你现在居然来收缴(Snoop)我的数据?我是该拒绝你,还是该接收你?硬件状态机直接陷入死锁(Deadlock)或协议违规(Protocol Violation)。


3.3.2 CompAck 机制是如何化解危机的?

引入 CompAck 后,时序图里的三步构成了一个完美的闭环保护锁

1
2
3
4
5
6
7
8
9
10
11
图 3.3 — CompAck 三步闭锁流程

RN-F (CPU) HN-F (总管)
│ │
│ ─── REQ: Read* {ExpCompAck=1} ──────────────────────> │ (1) 显式声明:我要回执
│ │ 【HN-F 锁死地址 X 的 Snoop】
│ │
│ <─── CompData {DBID=Z, Data} ──────────────────────── │ (2) 数据送达
│ │
│ ─── RSP: CompAck {TxnID=Z} ─────────────────────────> │ (3) 报告总管:我已安全收货
│ │ 【HN-F 解锁地址 X 的 Snoop】
  • 第一步:占坑挂锁
    RN-F 发送读请求时,带上一个标志位 ExpCompAck=1(Expect Completion Acknowledgment,意思是”我这次传输需要回执”)。HN-F 收到后,立刻把地址 X 放进黑名单(阻塞后续到地址 X 的 Snoop)。这意味着,哪怕此时 CPU 1 急着要写地址 X,HN-F 也会把 CPU 1 的请求按住,绝对不往 RN-F 发 Snoop。

  • 第二步:安全收货
    RN-F 顺利、安全地收到了 CompData,把数据妥妥地锁进了自己的 L2 Cache,状态机安全退出过渡态。

  • 第三步:解锁放行
    RN-F 通过 RSP 通道给 HN-F 发送 CompAck。HN-F 收到这个回执后,悬着的心放下了,这才释放地址 X 的 Snoop 阻塞,允许后续的 Snoop 飞向 RN-F。此时 RN-F 已经处于稳定态,完全具备了处理 Snoop 的能力。


3.3.3 为什么并非所有事务都需要 CompAck

这是为了兼顾系统性能。如果每一笔总线传输都要强行多走一步 CompAck 握手,总线带宽就会被无意义的回执信号占满(就像你每网购一个小物件,都要强制给快递员手写一份纸质回执一样)。

  • 需要 CompAck 的场景:涉及到 Coherent(缓存一致性) 的操作。因为 RN-F 内部有 Cache 状态机,它对时序先后顺序极其敏感,绝对不能出现”未娶先休(没收到数据先收到 Snoop)”的情况。

  • 不需要 CompAck 的场景:比如 WriteNoSnp(非一致性写)或者对 MMIO 外设寄存器的读写。这些操作不经过 CPU 的一致性 Cache,数据砸过去或者读回来就完事了,根本没有状态机过渡态,也没有人会去 Snoop 它,因此 ExpCompAck 直接置 0,省去了 CompAck 的开销,总线效率最高。

软件视角:在调试多核一致性问题时,CompAck 机制解释了为什么某些 CPU 的 SEV (Send Event) / WFE (Wait For Event) 同步原语在 CHI 上可以安全工作。当 CPU 0 用 Exclusive Store 写入一个同步变量后发 SEV,如果 CPU 1 在 CPU 0 发出 CompAck 之前就尝试读这个变量,HN-F 对地址的 Snoop 阻塞会强制 CPU 1 的读等待——这和软件的 spinlock 在硬件层面形成了一道”协议级 gate”。

3.4 Endpoint Order vs Request Order

CHI 网络中各通道(REQ / RSP / SNP / DAT)彼此物理独立,事务无需共享同一路径即可到达同一目标。这意味着两个先后发出的请求,可能在网络中走不同的路由、经历不同的拥塞,最终以与发出顺序相反的顺序到达 Subordinate。对 Coherent 事务而言,缓存一致性状态机与 CompAck(见 3.3 节)已经约束了语义——Snoop 阻塞、事务串行化等手段保证写入的可见性顺序。但对于 Device 类型或 Non-Cacheable 内存的访问,没有缓存状态机参与,如果硬件不做任何约束,ioread32() 完全可能读到上一个 iowrite32() 之前的状态值。

这就是 REQ.Order 字段存在的理由:由 Requester 显式声明每条 Non-Cacheable 事务的排序约束,让 CHI 网络在需要严格顺序时阻塞请求、在无需顺序时放行以换取吞吐。

Order 字段有三种编码:

编码 名称 语义
2'b00 None 无排序保证,允许任意重排序
2'b01 ReqOrder 请求顺序 — 同一 Requester 对同一地址的事务必须按发出顺序到达
2'b10 EndpointOrder 端点顺序 — 同一 Requester 对同一 Subordinate 地址范围内的事务严格保序

3.4.1 Request Order — 对同一地址的保序

请求顺序约束的是同一 Requester 对同一地址的连续访问。这里的”同一地址”匹配粒度是 IMPLEMENTATION DEFINED,不必精确到字节——例如可能以 4KB 为窗口。

保序机制依靠 Subordinate 的显式确认:Requester 发出 Order=1 的请求后,在收到 Subordinate 的 ReadReceipt(读)或 DBIDResp(写)之前,该 Requester 对该地址的下一个请求将被硬件阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
图 3.4 — Request Order 时序

[Requester] [Subordinate]
│ │
│ TXREQ: ReadNoSnp {TxnID=A, Addr=X, Order=1} │
│──────────────────────────────────────────────►│
│ │
│ (Requester 准备发出 Read#2 到地址 X, │
│ 但 Read#1 尚未收到 ReadReceipt → 阻塞!) │
│ │
│ RXRSP: ReadReceipt {TxnID=A} │
│◄──────────────────────────────────────────────│ Subordinate:
│ │ "#1 已接收, 保序保证已建立"
│ │
│ TXREQ: ReadNoSnp {TxnID=B, Addr=X, Order=1} │
│──────────────────────────────────────────────►│ Read#2 放行
│ │
│ RXDAT: CompData {TxnID=A} │
│◄──────────────────────────────────────────────│
│ RXDAT: CompData {TxnID=B} │
│◄──────────────────────────────────────────────│

典型场景:对同一 Non-Cacheable 地址执行 FIFO 式交互。Requester 先写 0xAA 到设备控制寄存器以触发某个操作,随后立即从同一地址读取状态——若写入尚未被 Subordinate 确认接收、读取已返回旧状态值,驱动将据此做出错误判断。

3.4.2 Endpoint Order — 对同一 Subordinate 的保序

Endpoint Order 约束的是同一 Requester 对同一 Subordinate 地址范围内的所有事务。范围比 Request Order 更宽(同一目标节点、不需要同一地址),开销也更高——前一个事务收到响应前,后续所有事务都被阻塞。

典型场景:NVMe 驱动发起一次 I/O 操作的时间线:

  1. WriteNoSnp 到提交队列 (Submission Queue),填入命令条目。
  2. WriteNoSnp 到 Doorbell 寄存器,通知控制器有新命令。

这两笔写目标地址不同(一个在 SQ 缓冲区地址,一个在 Doorbell 寄存器地址),但因发往同一 Subordinate(NVMe 控制器),Endpoint Order 可保证它们按序到达。如果 Doorbell 写先于 SQ 写被控制器观察到,控制器将读取尚未完整填充的队列——结果是全零命令条目或 stale data。

CHI-D 起,Endpoint Order 不再隐含 Request Order。此前(CHI-A/B/C)设置 Endpoint Order 会自动同时满足 Request Order 约束;但 CHI-D 将二者解耦,允许实现方在 Endpoint Order 场景中只保证端点级顺序、不保证地址级顺序(虽然后者通常仍是前者的子集)。

3.4.3 仅对 Non-Cacheable 事务生效

Order 字段只适用于以下请求类型

  • ReadNoSnp / ReadOnce* 系列
  • WriteNoSnp* / WriteUnique* 系列

为什么 Coherent 事务(ReadSharedReadUniqueMakeUnique 等)不在此列?因为它们的目标地址在缓存中,一致性协议已经通过 CompAck + Snoop 阻塞(见 3.3 节) 提供了更强的排序保证。只有 Device / Normal Non-Cacheable 属性的访问——这些绕开缓存、由 Requester 直接打到 Subordinate 的事务——才必须依赖软件通过 MMU 页表属性间接设置的 Order 字段来显式约束。

3.4.4 从 MMU 页表属性到 Order 字段的映射

驱动工程师在代码中使用 ioremap() / ioremap_nocache() 或 DTS 中声明 reg 属性时,实际上在指定 MMU 页表条目的内存类型。CHI Requester 根据页表属性自动填充 Request Flit 的 Order 字段:

MMU 内存类型 (Arm) CHI Order 编码 行为
Device-nGnRnE / Strongly-Ordered Order=2'b10 (EndpointOrder) 同端点所有访问严格保序
Normal Non-Cacheable Order=2'b01 (ReqOrder) 同地址访问保序
Normal Cacheable (WriteBack / WriteThrough) Order=2'b00 (None) 不做排序约束, 一致性协议兜底

软件视角:Linux 内核的 iowrite32() / ioread32() 系列通过 Device 内存属性生成 CHI 上的 WriteNoSnp / ReadNoSnp 事务,Order 由页表属性确定。如果你使用 dma_alloc_coherent() 分配的 Non-Cacheable 缓冲区(在 coherent DMA 系统中实为 Cacheable,由硬件维护一致性),则对应 Order=0——排序由 cache coherency 保证而非 Order 字段。如果你在 bare-metal 固件中手动配置 MMU 页表的 MAIR 寄存器,务必将设备寄存器区域的 AttrIdx 指向 Device 编码——错误地映射为 Normal Non-Cacheable 将只施加 ReqOrder(同地址保证),但不同寄存器间的写入不保证到达顺序,可能导致设备接收到错误的编程序列。

3.5 Request Retry 与 Protocol Credit 机制

当目标节点资源不足时,CHI 不会阻塞 REQ 通道,而是使用 Request Retry + Protocol Credit 机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
图 3.5 — Retry 流程

[Requester] [Completer]
│ │
│ REQ {AllowRetry=YES, PCrdType=0} │
│───────────────────────────────────►│ (Completer 的 Buffer 满)
│ │
│ RSP: RetryAck {PCrdType=2} │
│◄───────────────────────────────────│
│ │
│ ... WAIT ... │ (Buffer 可用)
│ │
│ RSP: PCrdGrant {PCrdType=2} │
│◄───────────────────────────────────│
│ │
│ REQ {AllowRetry=NO, PCrdType=2} │
│───────────────────────────────────►│ (重发请求, 被保证接受)

PCrdType 由实现方定义(例如不同的 Credit 类对应 Read Buffer 和 Write Buffer)。


四、分布式虚拟内存 (DVM) 操作

4.1 DVM 操作机制

在多核系统中,每个 CPU 都有独立的 TLB、指令缓存和分支预测器。当某个 CPU 修改页表(例如 munmap 释放一段虚拟地址、或 mprotect 改变权限)后,其他 CPU 的 TLB 中可能仍缓存着旧的虚拟地址映射。DVM (Distributed Virtual Memory) 就是 CHI 协议中负责跨 CPU 广播 TLB/ICache/Branch Predictor 无效化的机制。

DVM 事务由一个专用的 MN (Miscellaneous Node) 节点集中调度,而非交由 HN-F 处理——这避免了 Home Node 同时管理缓存一致性和 DVM 排序的复杂度。

DVM 两段传输的原因:一条 DVM 操作需要传输的信息量(操作类型、VMID、ASID、地址、TLBI 级别等)超过单个 Flit Addr 字段的承载能力。CHI 因此要求所有 DVM 操作分两段发送:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
图 4.1 — DVM 事务两阶段结构

[RN-0 (Initiator)] [MN] [RN-1 (Target)]
│ │ │
Phase 1 — Initiator → MN: │
│ TXREQ: DVMOp {Addr=op_type, VMID, addr_hi} │
│───────────────────────────► │
│ RXRSP: DBIDResp ◄ │ │
│ │ │
│ TXDAT: {addr_lo} │ │
│───────────────────────────► │
│ │ │
Phase 2 — MN → Targets (broadcast Snoop): │
│ │ TXSNP: SnpDVMOp Part1 │
│ │ {Addr=op_type+addr_hi} │
│ │──────────────────────────►│
│ │ TXSNP: SnpDVMOp Part2 │
│ │ {Addr=addr_lo} │
│ │ SnoopResp ◄────────│
│ Completion ◄───────│ │

Phase 1 的 Part1 走 REQ 通道,携带操作属性(是什么操作、哪个 VMID 下的)和地址高位;Part2 走 DAT 通道,携带地址低位。MN 拼接两段后,在 Phase 2 中通过 SNP 通道以 Part1+Part2 的同样分拆方式广播到所有目标 RN。Part1 用 Addr[3]=0 标识、Part2 用 Addr[3]=1 标识,且 Part2 可以先于 Part1 到达目标——接收方硬件必须缓存并等齐两段后再执行。

4.2 DVM Non-Sync vs DVM Sync

类型 行为 软件触发源
DVM Non-Sync 收到后立即 Ack,后延执行 TLBI / IC IALLU / BP IALL
DVM Sync 必须等所有在它之前的 Non-Sync 全部执行完毕 DSB 指令

DVM Sync 流程:

  1. RN 发出 DVM Sync 到 MN。
  2. MN 向所有 RN-F / RN-D 广播 Sync Snoop。
  3. 每个目标 RN 确保其所有待执行 DVM 操作完成。
  4. 每个 RN 回复 SnoopResp。
  5. MN 汇总后向发起 RN 发 Completion。

软件视角 — 最大的实际意义:Arm 处理器的一条 DSB ISH 指令在 CHI 系统上会触发 DVM Sync。如果你在内核中调用 flush_tlb_all(),最终会转化为一条 TLBI + DSB ISH 的序列。理解 DVM Sync 的时序对于调试 “TLB 无效化后读到 stale data” 这类问题至关重要——如果 DVM Sync 的 Completion 还没返回,后续的页表修改访问可能使用旧的 TLB 映射。

4.3 DVM 操作的完整事务示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
图 4.2 — TLB 无效化 + DVM Sync 完整时序

[RN-0] [MN] [RN-1]
│ │ │
│ TLBI DVM Req ───► │
│ DBIDResp ◄───────│ │
│ Write Data ─────► │
│ │ SnpDVMOp ──────►
│ │ SnpDVMOp Part2 ►
│ │ SnoopResp ◄─────│ (ACK, 未执行)
│ Completion ◄─────│ │
│ │ │
│ DVM Sync Req ───► │
│ DBIDResp ◄───────│ │
│ Write Data ─────► │
│ │ SnpDVMOp(Sync) ►│
│ │ │ ← 等待所有 DVM 执行完毕
│ │ SnoopResp ◄─────│ (确认执行完毕)
│ Sync Complete ◄──│ │

五、性能优化特性(选读)

5.1 Cache Stashing — 将 I/O 数据直接注入目标 CPU 缓存

传统 DMA 写入流程中,以网卡接收数据包为例:网卡通过 DMA 将数据写入 DDR 内存 → 产生中断通知 CPU → CPU 从内存地址读取数据 → 发生 Cache Miss → 总线穿透至 DDR 控制器取回缓存行(上百个时钟周期延迟)→ CPU 方能开始处理。在 100G/200G 高吞吐网卡场景下,这个”先落内存、再等 CPU 捞”的往返延迟直接导致丢包。

Cache Stashing(CHI-B 引入)的设计意图是消除这段延迟:既然 I/O 设备在发起 DMA 时已经知道目标数据最终由哪个 CPU 核心消费,就让数据在写入内存的同时、甚至先于内存写入,直接注入该 CPU 的缓存中。 CPU 后续访问该地址时将命中本地缓存,DDR 往返延迟被剪除。

5.1.1 带写数据的 Stash — WriteUniqueStash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
图 5.1 — WriteUniqueStash 事务流程(SN-F 不参与)

[RN-I (DMA Device)] [HN-F] [RN-F (Target CPU)]
│ │ │
│ TXREQ: WriteUniqueStash│ │
│ {StashNID=RN-F} │ │
│───────────────────────►│ │
│ │ TXSNP: SnpMakeInvalidStash │
│ │───────────────────────────►│
│ │ RXSNP: SnoopResp ◄────│
│ │ │
│ TXDAT: WriteData │ │
│───────────────────────►│ │
│ │ TXDAT: WriteData │
│ │ (routed to stash target) │
│ │───────────────────────────►│
│ │ RXRSP: Comp ◄────│
│ RXRSP: Comp ◄──│ │
  1. RN-I 发出 WriteUniqueStashStashNID 字段携带目标 RN-F 的 Node ID——此字段告诉 HN-F:”写操作完成后,将数据安装到该节点缓存中”。
  2. HN-F 向目标 RN-F 发出 SnpMakeInvalidStash Snoop,要求无效化该地址的旧缓存行(如果存在),为新数据腾出缓存空间。
  3. RN-F 返回 SnoopResp,确认缓存行已无效或本就不存在。
  4. 步骤 4 与步骤 2-3 可并行——RN-I 不等 Snoop 完成即可通过 TXDAT 向 HN-F 发送写数据。
  5. HN-F 将写数据转发给 RN-F。数据进入 RN-F 的本地缓存——此时 RN-F 未执行任何读指令,数据已被硬件强行安装。
  6. RN-F 返回 Comp 确认收到数据。
  7. HN-F 向 RN-I 返回 Comp,事务结束。

在整个流程中 SN-F(内存控制器)不参与——数据是否同步写入内存取决于 HN-F 的缓存替换策略,写入内存的操作被推迟到该缓存行后续被 HN-F 逐出时。如果 CPU 在数据被逐出前就已经消费了它,DDR 访问被完全消除。

5.1.2 无写数据的 Stash — StashOnceShared / StashOnceUnique

5.1.1 的场景是 I/O 设备手里有数据、顺便塞进 CPU 缓存。但还有另一种情况:设备手里没有数据,它只想帮 CPU 做一次”缓存预热”——预测 CPU 即将访问地址 X,提前替它把 X 的数据从 DDR 拉进 CPU 缓存。

此时发起 StashOnceUniqueStashOnceShared,Request Flit 不含写数据。HN-F 收到后需要自己去 DDR 取数据,再安装到目标 CPU 缓存。因此 SN-F 必须参与

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
图 5.2 — StashOnceUnique 事务流程

[RN-I] [HN-F] [RN-F (Target)] [SN-F]
│ │ │ │
│① StashOnceUnique │ │ │
│ {StashNID=RN-F} │ │ │
│─────────────────►│ │ │
│ │ │ │
│ │②a ReadNoSnp │ │
│ │─────────────────────────────────────────────►│
│ │ │ │
│ │②b SnpStashUnique │ │
│ │────────────────────────►│ │
│ │ │ │
│ │ ③a CompData ◄─────────────────────────│
│ │ (cache line) │ │
│ │ │ │
│ │ ③b SnpResp_I_Read │ │
│ │ {DataPull=1, DBID=Y} │ │
│ │◄────────────────────────│ │
│ │ │ │
│ │④ CompData_UC │ │
│ │ {TxnID=Y, data} │ │
│ │────────────────────────►│ ← 数据安装到 RN-F 缓存
│ │ │ │
│ │ ⑤ CompAck ◄───── │ │
│ ⑥ Comp ◄────────│ │ │

① RN-I 发出不带写数据的 Stash 请求,StashNID 指向目标 CPU。

②a 和 ②b 同时启动

  • ②a — HN-F 向 SN-F 发 ReadNoSnp,从 DDR 拉取该缓存行的最新数据。因为 HN-F 自己没有这份数据。
  • ②b — HN-F 向目标 RN-F 发 SnpStashUnique Snoop,通知它”有一份数据要装进你的缓存,旧数据请无效化”。

③a 和 ③b 可以以任意顺序到达 HN-F

  • ③a — SN-F 将缓存行数据以 CompData 返回。
  • ③b — RN-F 在 Snoop 响应中设 DataPull=1,附带一个 DBID(相当于说”我接受 Stash,数据直接发给我,TxnID 用 Y”)。这就是 DataPull——RN-F 不必另发 Read 请求,Snoop 响应本身等同于隐式读请求。如果 RN-F 设 DataPull=0,则它需要后续自己发 Read 事务,多走一次总线往返。

④ HN-F 将缓存行以 CompData_UC 发给 RN-F(TxnID 使用 ③b 中 RN-F 给的 DBID=Y)。数据被安装到 RN-F 的本地缓存。

⑤ RN-F 发 CompAck 给 HN-F。⑥ HN-F 发 Comp 给 RN-I。

核心要点StashOnceUnique 安装后缓存行为 Unique 状态(目标 CPU 可直接写入),StashOnceShared 对应 Shared 状态(只读共享)。DataPull 仅在 HN-F 未通过 DoNotDataPull 字段禁止时可用,且只适用于 Stash Snoop——普通一致性 Snoop 不能使用此机制。

四种 Stash 事务类型:

事务 用途 带写数据? SN-F 参与? 目标缓存状态
WriteUniquePtlStash 部分行写入 + Stash Unique
WriteUniqueFullStash 整行写入 + Stash Unique
StashOnceShared 读缓存预热 ✅ (拉数据) Shared
StashOnceUnique 写缓存预热 ✅ (拉数据) Unique

软件视角:高性能网卡驱动(DPDK 等)在配置 DMA 描述符或 PCIe 控制器属性时,可通过寄存器指定 “Stash Enable” 和 “Stash Target Core ID”,硬件据此填充 CHI Flit 的 StashNIDStashLPID 字段。对于不指定 Stash Target 的请求(StashNIDValid=0),HN-F 本身可作为 Stash 目标,选择性地将数据分配给其系统缓存——这是一种”不精确”但无额外硬件配置开销的折中方案。

5.2 I/O Deallocation — 读取同时清除缓存污染

当 I/O 设备读取一块内存后,这块数据对 I/O 设备自身已经没有后续用途。如果该缓存行恰好驻留在某个 CPU 的缓存中(且可能处于 Dirty 状态),传统的 ReadOnce 会保持 CPU 缓存中的副本不变——CPU 的缓存被一份自己不再需要的数据占据,这是缓存污染。

I/O Deallocation 为此提供两个带 Invalidation Hint 的读事务,在读取的同时通知 HN-F 无效化 CPU 端的副本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
图 5.3 — ReadOnceCleanInvalid 事务流程

[RN-I] [HN-F] [RN-F (holds Dirty)] [SN-F]
│ │ │ │
│① ReadOnceCleanInv │ │ │
│───────────────────►│ │ │
│ │② SnpUnique │ │
│ │ (Invalidate + Get Data) │ │
│ │────────────────────────►│ │
│ │ │③ Invalidate local copy│
│ │ ④ SnpRespData ◄───────│ (return Dirty data) │
│ │ │ │
│ │⑤ WriteBack ────────────────────────────────────►│
│ │ (Dirty data to DDR) │ │
│ │ │ │
│ ⑥ CompData ◄───── │ │ │
│ (data returned │ │ │
│ to I/O device) │ │ │

① RN-I 发出 ReadOnceCleanInvalid——它需要数据,但同时要求 HN-F 将 CPU 端的副本无效化。

② HN-F 向持有该缓存行的 RN-F 发出 SnpUnique Snoop。Snoop 的目的有两个:获取最新数据(可能是 Dirty),并要求 RN-F 无效化本地副本。

③ RN-F 无效化自己的缓存行。

④ RN-F 通过 SnpRespData 将数据(Dirty 状态)返回给 HN-F。

⑤ HN-F 将 Dirty 数据写回 SN-F(DDR 内存)。因为 CleanInvalid 要求保证数据持久化。

⑥ HN-F 将数据转发给 RN-I。此时 CPU 缓存中该行已被无效化——数据被 I/O 设备消费后不会继续占据 CPU 缓存空间。

ReadOnceMakeInvalid 流程相同,但步骤 ⑤ 被跳过——Dirty 数据直接丢弃、不写回内存。这适用于数据即将被 I/O 设备覆写的场景(例如接收新的网络数据包到同一缓冲区),但使用它必须确保该数据不会被其他使用者再次读取,否则造成数据丢失。

软件视角:NVMe 驱动的 read completion path 中,bio page 作为 DMA target buffer 接收数据。如果该 page 在 CPU 缓存中恰好 Dirty(上一个用户写过但未 evict),ReadOnceCleanInvalid 会在 DMA 数据到达前先写回旧 Dirty 数据。在 coherent DMA 系统中硬件自动处理;在 non-coherent 系统中驱动必须在 DMA 之前通过 dma_map_single(DMA_FROM_DEVICE) 的底层 CMO 操作(如 DC CIVAC)显式清空。

5.3 Direct Memory Transfer (DMT) — 读数据绕行 Home Node

CHI-A 中任何读事务的返回数据都必须经过 HN-F 中转:RN → HN → SN → HN → RN。HN-F 在这条路径上是纯中转节点——它不消费数据,但数据必须穿过它,增加一级 NoC 路由延迟。

DMT(CHI-B)允许 SN-F 将读数据绕过 HN-F、直接发往原始 Requester

1
2
3
4
5
6
7
8
9
10
11
12
13
图 5.4 — DMT 事务流程(对比传统路径)

传统路径:
RN-F ────► HN-F ────► SN-F
▲ │
└─────────────────────┘ (数据经 HN-F 中转, 多一级路由)

DMT 路径:
RN-F ────► HN-F ────► SN-F
▲ │
└─────────────────────┘ (数据直达 RN-F, 绕过 HN-F)

HN-F ◄── CompAck (仅确认, 不走数据)

DMT 的核心机制依靠两组新增字段:

① HN-F 向 SN-F 发 ReadNoSnp 时,携带 ReturnNID(填入原始 RN-F 的 Node ID)和 ReturnTxnID(填入原始 Request 的 TxnID)。

② SN-F 返回数据时,CompDataTgtID 设为 ReturnNID(直接路由到 RN-F),TxnID 设为 ReturnTxnID(RN-F 据此匹配到原始请求)。同时 HomeNID 填入 HN-F 的 Node ID,DBID 填入 HN-F 发出的 ReadNoSnp 的 TxnID。

③ RN-F 收到数据后,用 HomeNID 作为 TgtIDDBID 作为 TxnID,向 HN-F 发出 CompAck。HN-F 据此释放对该事务的跟踪。

优化变体(CHI-B):当 ReadNoSnpOrder=0x01 时,SN-F 额外发送 ReadReceipt 给 HN-F。HN-F 收到 ReadReceipt(而非等到 CompAck)即可释放事务跟踪——进一步缩短 HN-F 的资源占用窗口。

不适用 DMT 的事务:Exclusive Access、Order≠0ExpCompAck=0ReadNoSnp/ReadOnce

5.4 Direct Cache Transfer (DCT) — 缓存间数据直通

DCT 是 DMT 在 Snoop 场景中的等价机制。当 CPU A 发起读请求、数据恰好在 CPU B 的缓存中时,传统路径要求 CPU B 将 Snoop 数据先发给 HN-F、再由 HN-F 转给 CPU A。DCT 允许 CPU B 绕过 HN-F,直接将缓存行发给 CPU A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
图 5.5 — DCT 事务流程 (RetToSrc=0)

[RN-F1 (Requester)] [HN-F] [RN-F2 (Snoopee, holds data)]
│ │ │
│① ReadNotSharedDirty │ │
│ {TxnID=A} │ │
│───────────────────────►│ │
│ │② SnpNotSharedDirtyFwd │
│ │ {TxnID=B, │
│ │ FwdNID=1, ← RN-F1 NodeID│
│ │ FwdTxnID=A, ← 原始 TxnID │
│ │ RetToSrc=0} │
│ │─────────────────────────────►│
│ │ │
│ │ ③ SnpRespFwded │
│ │◄─────────────────────────────│
│ │ │
│ ④ CompData ═════════════════════════════════ (Data bypasses HN-F!)
│ {TxnID=A, │ │
│ HomeNID=3, ← HN-F ID │ │
│ DBID=B, ← Snoop TxnID │
│ RESP=SC} │ │
│ │ │
│⑤ CompAck {TxnID=B} │ │
│───────────────────────►│ │

① RN-F1 发起 ReadNotSharedDirty 请求。

② HN-F 缓存未命中,向持有该行的 RN-F2 发出 Forwarding SnoopSnpNotSharedDirtyFwd)。Snoop Flit 中 FwdNID 指向原始 Requester(RN-F1),FwdTxnID 填入原始事务的 TxnID——这两个字段告诉 RN-F2:”数据别给我,直接发给 Node 1”。

③ RN-F2 向 HN-F 回复 SnpRespFwded,告知 Snoop 执行结果和 FwdState(发给 Requester 的缓存状态)。

④ RN-F2 直接向 RN-F1 发送 CompData,绕过 HN-F。数据包的 TxnID=FwdTxnID=A(让 RN-F1 匹配原始请求)、HomeNID=3(让 RN-F1 知道该向谁发 CompAck)、DBID=B(Snoop 的 TxnID,用于 CompAck)。

⑤ RN-F1 向 HN-F 发 CompAck,完成事务。

RetToSrc 字段控制数据是否同时写回 HN-F:RetToSrc=0 时数据仅给 RN-F1(HN-F 不缓存);RetToSrc=1 时数据同时发给 HN-F 一份,后续对同一地址的读可能命中 HN-F 缓存而无需再次 Snoop。

软件视角:DMT 和 DCT 对生产者-消费者模式直接生效。CPU A 写入一块数据(缓存中 UD 态)→ CPU A 通知 CPU B → CPU B 发起 ReadShared → HN-F 向 CPU A 发 Forwarding Snoop → CPU A 通过 DCT 直接将缓存行发给 CPU B。数据从未离开缓存层,延迟从经 HN-F 中转的 ~50ns 降至 cache-to-cache 的 ~20ns。这正是 futex / sem_post 等同步原语能在多核间快速完成的硬件基础。

5.5 PrefetchTgt — 绕过 HN-F 的预取提示

PrefetchTgt 是一种特殊的事务:Requester 预测自己即将通过正常路径(RN → HN → SN)请求地址 X 的数据,于是提前向 SN-F 直接发送一个预取提示。SN-F 可据此提前从 DDR 取出数据并暂存在 local buffer 中。当后续 ReadNoSnp(经 HN-F 的正常路径)到达时,SN-F 直接命中 buffer 中的数据,DDR 读取延迟被消除。

1
2
3
4
5
6
7
8
9
10
11
12
13
图 5.6 — PrefetchTgt + DMT 联合优化

[CPU (RN-F)] [HN-F] [DDR Controller (SN-F)]
│ │ │
│① PrefetchTgt ──────────────────────────────────►│
│ {TxnID=0} │ │ ② Pre-fetch from DDR
│ (no response) │ │ buffer the data
│ │ │
│③ ReadOnce ──────────►│ │
│ │④ ReadNoSnp (DMT) ───────►│
│ │ {ReturnNID=CPU} │ ⑤ Buffer hit!
│ │ │ DDR access ≈ 0
│ ⑥ CompData (DMT) ◄═══════════════════════════ (direct to CPU)

① RN-F 直接向 SN-F 发 PrefetchTgt,TxnID 固定为 0(该事务不被追踪、无响应)。RN-F 需要 HN SAM 而非仅 RN SAM——因为它必须知道 SN-F 的 Node ID 才能绕开 HN-F。

② SN-F 可选择忽略该 hint,或从 DDR 取数据并暂存。

③-④ CPU 随后通过正常路径(经 HN-F,可结合 DMT 优化)发出读请求。

⑤-⑥ SN-F 从 buffer 中命中数据,通过 DMT 直接返回给 CPU。

PrefetchTgt 的生效判断:DAT 通道的 DataSource 字段指示返回数据是否来自预取 buffer(0x6 = 受益于 PrefetchTgt,0x7 = 未受益)。RN-F 据此统计命中率,动态决定是否继续发送 PrefetchTgt。

软件视角madvise(MADV_WILLNEED) 或硬件 stride prefetcher 的 pattern 检测可能在 CHI 层生成 PrefetchTgt 事务。驱动工程师不应依赖 PrefetchTgt 的语义保证——它是 hint,SN-F 可以随时丢弃。需要确定性预取时使用 Cache Maintenance 或软件 prefetch 指令。

5.6 Direct Write Transfer (DWT) — 写数据直通

CHI-E 引入的 DWT 是 DMT 的写操作等价物(RN → HN → SN 变为 RN → HN 发 WriteNoSnp 添加 DoDWT=1 到 SN → SN 直接接收 RN 的数据)。这用于 WriteUnique/WriteNoSnp 场景,但不支持 Ordered Write Observation。


六、原子操作与 RAS 特性(选读)

6.1 硬件原子事务 — 从自旋重试到单次完成

在 AXI / CHI-A 时代,多核系统实现原子读写的方式是 Exclusive AccessLDREX / STREX 指令对)。这带来两个性能瓶颈:

  1. 三次总线往返:CPU 先发 LDREX 从内存拉数据 → CPU 在寄存器中修改 → CPU 发 STREX 尝试写回。如果中间有其他写入者,STREX 失败 → CPU 必须重试整个序列。
  2. 数据锁定窗口长:从 LDREXSTREX 之间,被读取的地址需要被总线监视(Exclusive Monitor 跟踪),其他写入者被阻塞或被要求重试。CPU 间的高竞争会拉长这个窗口,导致级联重试。

CHI-B 的原子事务将 RMW 序列直接编码为一条 CHI 协议消息,交由 HN 或 SN 中的 ALU 在数据所在位置就地执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
图 6.1 — Atomic Transaction flow

[Requester (CPU)] [HN (with ALU)] [SN (DDR)]
│ │ │
│① TXREQ: AtomicOp │ │
│ {opcode, operand, addr}│ │
│─────────────────────────►│ │
│ │② HN checks cache: │
│ │ Hit → RMW in HN │
│ │ Miss → forward to │
│ │ SN (if ALU) or │
│ │ fetch first │
│ │ │
│ ③ CompData │ │
│ (original val, opt) │ │
│◄─────────────────────────│ │
│ │ │
│④ Done. Single bus round- │ │
│ trip. No retry loop. │ │

① CPU 发出一条 AtomicOp 请求,Flit 中携带操作码、操作数和目标地址。无需事先 LDREX 拉数据——操作码和操作数随 Request 一起提交。

② HN(若含 ALU)在缓存命中时就地执行 RMW。若未命中,HN 将 AtomicOp 转发至 SN(若 SN 含 ALU),由 SN 在内存控制器端执行。操作由硬件保证原子性——无需锁,不存在 “另一 CPU 插入写操作” 的时间窗口。

③ 根据操作类型,HN 可选择通过 CompData 返回操作前的原始值。

④ 与 Exclusive Access 的关键区别:AtomicOp 是一次事务、一次总线往返。Exclusive Access 需要两次(LDREX + STREX),且 STREX 可能失败触发重试。高竞争下 Exclusive 吞吐可降至接近零;AtomicOp 的吞吐恒定。

CHI-B 的原子事务通过 BROADCASTATOMIC 配置引脚控制。当该引脚为低时,Requester 不得生成 AtomicOp——必须退回到 Exclusive Access 模式。这保证了与不支持 ALU 的下游 SN 的向后兼容。

软件视角:Armv8.1 LSE(Large System Extensions)引入 LDADDSTADDCASSWP 等硬件原子指令。在 CHI-B+ 系统上,Linux 内核的 atomic_add_return() / atomic_cmpxchg() 被编译器直接映射为 LSE 指令(如 stadd),硬件层面转化为单次 AtomicOp 事务。而在老旧系统(或不启用 LSE 时),内核必须用 LDXR/STXR 指令对实现——伴随自旋重试循环和 cache line bouncing。判断方法:/proc/cpuinfoFeatures 字段若包含 atomics,则 LSE 可用。

6.2 Data Poisoning — 让错误延后、不立即崩溃

考虑一个场景:DDR 控制器报告某 64 字节缓存行发生了不可纠正的多 bit ECC 错误。传统做法是立即触发数据 Abort——但如果该缓存行对应的页面实际已不再被任何进程使用(例如刚被 munmap 释放、尚未被覆盖),那么这次 Abort 是不必要的,进程本应正常结束。

Arm RAS 规范为此定义了 Data Poisoning 机制:标记数据已损坏,但不立即报错;错误跟随数据在系统中传播,直到数据被真正”消费”时才触发异常

CHI 中的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
图 6.2 — Data Poisoning propagation

[DDR Controller] [HN-F (system cache)] [CPU (RN-F)]
│ │ │
│① ECC multi-bit error │ │
│ detected for line X │ │
│ │ │
│ TXDAT: CompData │ │
│ {Data=line X, │ │
│ Poison=0b0001_ │ │
│ bit0 → 64b corrupted} │ │
│─────────────────────────►│ │
│ │② Poison stored alongside │
│ │ cache line in HN-F. │
│ │ No error raised yet. │
│ │ │
│ │③ Later, CPU reads line X: │
│ │ TXDAT: CompData │
│ │ {Poison=0b0001} │
│ │───────────────────────────►│
│ │ │
│ │ │④ Poison stored in CPU cache.
│ │ │ Still no error.
│ │ │
│ │ │⑤ CPU executes ADD on
│ │ │ the corrupted 64b chunk
│ │ │ → Data is CONSUMED
│ │ │ → Exception raised!

① DDR 控制器在读取时发现不可纠正 ECC 错误。不是立即发 Error Response——而是用 CompData 正常返回数据,同时在 DAT Flit 的 Poison 字段中标记损坏位置。

② HN-F 将缓存行连带 Poison 位一起存入系统缓存。此时系统继续正常运行——没有异常、没有 Abort。

③-④ 后续 CPU 读取该行时,Poison 位随数据一起进入 CPU 本地缓存。错误仍未触发。

⑤ 只有当 CPU 实际使用这段数据时(ALU 运算、作为指针解引用、或写入到不支持 Poison 的外设),才触发异常。如果该页面在 ⑤ 之前被回收释放(且从未被使用),则 Poison 随缓存行被逐出而自然消失——一次可能的崩溃被避免了。

Poison 粒度:每 64-bit 数据对应 1-bit Poison 标记。若 256-bit DAT 通道,Poison 字段即为 4-bit。

DataCheck 是另一套独立的保护机制:每 8-bit 数据对应 1-bit 奇偶校验,用于 interconnect 内部各节点被动检测数据损坏——它是发现错误的手段,Poison 是传播错误的手段。两者在 CHI-B 中可互操作转换。

6.3 Trace Tag — 贯穿事务链的 1-bit 追踪标记

调试一个 64 核 SoC 上的 cache coherency 竞态问题时,总线 trace 工具会抓到海量事务——每秒数百万条 Flit。你只关心一条特定的 MakeUnique 事务以及由此触发的所有 Snoop、Snoop 响应和数据流。TraceTag 就是为这个需求设计的:在所有相关 Flit 上打一个贯穿事务链的标记位,让 trace 工具只过滤标记位为 1 的 Flit

TraceTag 宽度仅 1 bit,但出现在 全部四个通道上——REQ、RSP、SNP、DAT。一旦某个 Flit 的 TraceTag 被设置为 1,规则只一条:

该事务触发的所有后续 Flit也必须将 TraceTag 设为 1,直到整个事务链结束。

这意味着一个初始 Request 被打标后,由此衍生的 Snoop(HN-F → 其他 CPU)、下级 ReadNoSnp(HN-F → SN-F)、所有 CompData、所有 CompAck —— 全部携带 TraceTag=1。

TraceTag 可以由两个位置设置:

设置位置 方式 范围
Requester (RN) 初始 Request Flit 中 TraceTag=1 本事务及所有衍生事务
Interconnect 中间节点 硬件 watchpoint 匹配特定地址/Opcode 从被命中节点起的所有下游 Flit

第二种方式尤为重要:interconnect 可被编程为”凡是在 HN-F 处对地址 0x8000 的 MakeUnique 请求,一律设 TraceTag=1”。初始 Request 从 RN 到 HN-F 时 TraceTag 是 0——但在 HN-F 命中 watchpoint 后,HN-F 发出的所有下游 Flit(Snoop、下级读)都会被标记。

软件视角:CoreSight ETM 和总线 trace 解码器可利用 TraceTag 作为过滤条件,只捕获被标记的事务链。BSP 工程师可在 bootrom 阶段通过 interconnect 的编程接口设置 watchpoint(地址 + Opcode 组合),无需修改任何 CPU 端代码即可追踪特定内存区域的访问行为。


七、总结与学习路径建议

对芯片软件开发人员的实践建议

  1. 理解 Node ID 分配:SoC 启动代码中,SAM 的配置通常由 BootROM 或 TF-A 完成。修改地址映射时,务必确认 SAM 一致性。
  2. 掌握 DBID/TxnID 生命周期:这是调试 CHI 协议级错误(如未释放 DBID、TxnID 重映射冲突)的基础。
  3. 理解 Coherency 状态机:UD/UDP/SD/UC/UCE/SC/I 这七个状态决定了你的 DMA buffer 是否需要 cache maintenance。在 coherent DMA 系统中,硬件管理状态转换;在 non-coherent 系统中,你需要用 CMO (Cache Maintenance Operations) 显式管理。
  4. 区分 Request Order 和 Endpoint Order:对于设备驱动中需要保证写入顺序的场景(例如 NVMe SQ Tail Doorbell),确保硬件 bridge 支持对应的 Order 语义。
  5. 关注 MTE 和 RME:对于机密计算和内存安全敏感的现代应用,理解 MTE 的 Tag 传播机制和 RME 的 PAS 分离是未来 SoC 开发的必备知识。

学习资源

资源 用途
AMBA 5 CHI Architecture Specification (Issue A-G) 协议层完整参考
AMBA AXI and ACE Protocol Specification CHI 的前身理解
Arm Architecture Reference Manual (Armv8-A / Armv9-A) 指令与内存模型映射到 CHI
Arm Community (community.arm.com) 实践问题讨论
Introduction to AMBA CHI Protocol Training (Arm 官方培训) 结构化学习

核心要点

  1. 四个通道、四类节点:REQ(请求)/ RSP(响应)/ SNP(Snoop)/ DAT(数据);RN-F → HN-F → SN-F 构成一次完整事务的典型路径。MN 单独处理 DVM 操作。

  2. 读事务路径RN → HN → SN → HN → RN。DMT 让 SN 的数据绕过 HN 直接到 RN。DCT 让 Snoop 数据绕过 HN 直接到 Requestor。

  3. 写事务必须先拿 DBIDResp:写请求发出后,Requester 必须收到 Completer 的 DBIDResp(写入数据缓冲区的令牌),才能通过 DAT 通道发送写数据。DBID 同时也是 CompAck 的 TxnID 来源。

  4. CompAck 的用途是锁 Snoop、不是锁数据ExpCompAck=1 的目的是在 Coherent 事务完成前阻止 HN-F 对该地址发出 Snoop——防止 Snoop 比 CompData 更早到达 RN-F 导致状态机崩溃。Non-Coherent 事务不需要。

  5. Order 字段只对 Non-Cacheable 事务生效Order=1 保同一地址顺序,Order=2 保同一端点所有事务顺序。Coherent 事务的顺序由缓存状态机 + CompAck 保证,不走 Order 字段。

  6. DVM 是 TLB shootdown 的硬件承载层:CPU 修改页表后广播 TLB 无效化给其他核,通过 MN 分两段传输(属性 + 地址),以 DVM Sync 保证所有核完成无效化后才返回。DSB ISH 触发 DVM Sync。

  7. Cache Stashing 分两种:带写数据时 SN-F 不参与(WriteUniqueStash),无写数据时 HN-F 从 SN-F 拉数据再装进 RN-F(StashOnce*)。DataPull 让 RN-F 在 Snoop 响应中隐式发起读请求,省一次总线往返。

  8. AtomicOp 替代 LDREX/STREX 的一往返优势:CHI-B 将 RMW 操作直接提交给 HN/SN 的 ALU 就地执行,从两次往返 + 可能重试变为一次往返 + 保证完成。BROADCASTATOMIC 引脚控制是否启用。

  9. Poison 是传播、不是响应:ECC 发现不可纠正错误时,在 DAT Flit 的 Poison 字段中标记损坏的 64-bit 块,数据继续传递直到被 ALU 消费时才触发异常。这避免了 munmap 后尚未使用的脏页引发不必要的 Abort。

  10. TraceTag 贯穿事务链:1-bit 标记在四个通道上传播,一个 Request 被打标后所有衍生 Flit 也必须打标。Interconnect 可通过 watchpoint 按地址+Opcode 匹配实现零侵入的事务追踪。


八、中英文术语对照表

英文 中文 说明
CHI (Coherent Hub Interface) 一致性集线器接口 AMBA 互联协议
Requester 发起节点 事务发起方
Subordinate / Completer 目标节点 / 完成节点 事务响应方
RN-F (Fully Coherent Request Node) 全一致性请求节点 带缓存的 CPU / GPU 等
RN-I (I/O Coherent Request Node) I/O 一致性请求节点 无缓存,如 DMA 引擎
RN-D (I/O Coherent DVM Request Node) I/O 一致性 DVM 请求节点 支持 DVM 的 I/O 节点
HN-F (Fully Coherent Home Node) 全一致性 Home 节点 管理 coherent 地址空间
HN-I (Non-Coherent Home Node) 非一致性 Home 节点 管理 I/O 地址空间
SN-F (Fully Coherent Subordinate Node) 全一致性从节点 内存控制器
MN (Miscellaneous Node) 混合节点 处理 DVM 事务
SAM (System Address Map) 系统地址映射表 物理地址 → Node ID
Flit (Flow Control Unit) 流控单元 CHI 协议消息基本单位
DBID (Data Buffer ID) 数据缓冲区 ID 写数据分配 / CompAck 标识
TxnID (Transaction ID) 事务 ID SrcID 下的唯一事务标识
CompAck (Completion Acknowledgment) 完成确认 事务完成后的 ACK
DVM (Distributed Virtual Memory) 分布式虚拟内存 TLB/ICache 维护操作
DMT (Direct Memory Transfer) 直接内存传输 读数据绕行 HN
DCT (Direct Cache Transfer) 直接缓存传输 Snoop 数据直通
DWT (Direct Write Transfer) 直接写传输 写数据绕行 HN
CAH (CopyAtHome) Home 处副本 HN-F 持有副本时的传输优化
MPAM (Memory System Resource Partitioning and Monitoring) 内存系统资源分区与监控 缓存/带宽隔离
MTE (Memory Tagging Extension) 内存标签扩展 运行时内存安全检查
RME (Realm Management Extension) Realm 管理扩展 机密计算安全扩展
MEC (Memory Encryption Context) 内存加密上下文 Realm 级加密隔离
PAS (Physical Address Space) 物理地址空间 RME 安全状态标识
PoPA (Point of Physical Aliasing) 物理别名点 PAS 合并点
RAS (Reliability, Availability, Serviceability) 可靠性/可用性/可维护性 错误检测与恢复
PBHA (Page-Based Hardware Attributes) 基于页表的硬件属性 页表传播的 QoS/缓存属性
DataCheck 数据校验 按字节奇偶校验
PCrd (Protocol Credit) 协议信用 Retry 机制中的资源令牌

文档版本: v1.0 | 基于: Arm “Learn the architecture - Introducing AMBA CHI” Issue 01, June 2025 | 适用: CHI Issue A-G