四:CXL事务层

四:CXL事务层
3.0 CXL事务层本章说明 CXL 事务层Transaction Layer的三个协议分支CXL.io、CXL.cache和CXL.mem。其中CXL.io是所有实现都必须支持的基础协议主要承担发现、枚举、错误上报和管理功能CXL.cache与CXL.mem则按设备类型和负载模型选择性实现。事务层定义了事务类型、包格式、排序规则、信用返回以及典型事务流。3.1 CXL.ioCXL.io设备必须同时支持 CXL 1.1 和 CXL 2.0 两种运行模式具体由 Alternate Protocol Negotiation 的协商结果决定。当链路工作在 CXL 1.1 模式时CXL.io端点以 PCIe RCiEP 的形式对软件暴露当工作在 CXL 2.0 模式时则以 PCI Express Endpoint 的形式对软件暴露。参与 CXL 协议的CXL.io端点功能不得生成INTx消息。只有由 Non-CXL Function Map DVSEC 标记为不参与CXL.cache或CXL.mem的非 CXL 功能才允许在不推荐的前提下使用INTx。对于 MLD 组件包含非 CXL 功能在内的所有CXL.io端点功能都不允许生成INTx。3.1.2 CXL 电源管理 VDM 格式CXL 电源管理消息通过 PCIe Vendor Defined Type 0 报文发送携带 4DW 数据负载包含PMREQ、PMRSP、PMGO、RESETPREP、AGENT_INFO和CREDIT_RTN等逻辑操作码。其关键特性如下Fmt/Type指示为带数据消息路由方式为Local-Terminate at Receiver。Message Code固定为 Vendor Defined Type 0。Vendor ID固定为1E98h。消息头第 15 字节中的VDM Code固定为CXL PM Message (68h)。若接收端收到带Poison的 PM VDM应直接丢弃并按 advisory non-fatal error 处理。若 PMU 不能理解 PM VDM 负载内容也应静默丢弃不上报不可纠正错误。CXL 电源管理消息包格式CXL 电源管理消息数据负载字段定义AGENT_INFO目前仅定义Index 0其他Index均保留。设备在把 credit 返还给 Host 时必须把TARGET_AGENT_ID视为保留字段。PMREQ 字段定义Payload字段定义3.1.2.1 Credit 与 PM 初始化PM 信用和初始化是链路本地行为。上游端口 PMUPower Management Unit 需要先通过下游端口 PMU 发送的PM2IP.CREDIT_RTN和PM2IP.AGENT_INFO完成初始化然后才能发起IP2PM消息。GPF消息不消耗 credit接收方也不应对GPF返回CREDIT_RTN。上图描述的是 CXL 链路两端 PMUPower Management Unit在上电后如何先建立消息信用值credit再交换电源管理能力信息最后完成 PM 初始化。上游端口 PMU 必须满足以下要求在发起任何IP2PM消息前必须先收到一个PM2IP.CREDIT_RTN。必须从收到的第一条PM2IP消息中提取TARGET_AGENT_ID并将其作为后续消息中的PM_AGENT_ID。必须能在不依赖其他 PM 消息的情况下独立接收并处理CREDIT_RTN。至少要支持 1 个 PM credit该 credit 足以吸收一个带 128bit 负载的PM2IP消息。应尽快把 credit 返还给下游端口 PMU规范建议不要扣留超过10us。流程可以分成两段看1. Credit Initialization先把“可发消息额度”建起来两端分别是Downstream Port (e.g. Host) PMUUpstream Port (e.g. CXL Device) PMU开始时两边都是TX.Credit 0这表示两边一开始都不能随便发送 PM 消息必须先由对端“返还 credit”建立发送额度流程如下Downstream Port 先发PM2IP.CREDIT_RTN(TARGET_AGENT_ID, NUM_CREDITS1)到 Upstream Port含义给 Upstream Port 指定的 PM_AGENT_ID 分配 1 个发送 credit2.Upstream Port 收到后CXL PM_AGENT_ID TARGET_AGENT_IDTX.Credit 1也就是 Upstream Port 现在可以发 1 条 PM 事务了3.Upstream Port 再回IP2PM.CREDIT_RTN(NUM_CREDITS2)给 Downstream Port4.Downstream Port 收到后TX.Credit 2到这里双方都具备了后续 PM 初始化所需的基本发送能力。这一步的本质是先互相发“信用额度”解决一开始双方都没法发控制消息的问题。2. PM Initialization交换能力信息并完成初始化有了 credit 之后开始真正的 PM 初始化。1.Downstream Port 发PM2IP.AGENT_INFO(Req, Index0, CAPABILITY_VECTOR)这表示 Downstream Port 向 Upstream Port 查询/传递某个 PM Agent 的能力信息Index0 表示从第一个 capability 项开始。2.Downstream Port 发完这条消息后自己的 TX.Credit 从 2 - 1因为发出一条消息会消耗 1 个发送 credit3.Upstream Port 返回IP2PM.AGENT_INFO(Rsp, Index0, CAPABILITY_VECTOR)即返回 capability vector 响应。4.图中红色虚线回环表示如果 capability 信息不止一项AGENT_INFO Req/Rsp 可能会按 index 多轮交互直到能力信息交换完整5.Downstream Port 随后发PM2IP.CREDIT_RTN(NUM_CREDITS1)给 Upstream Port 补回 1 个 credit所以 Upstream PortTX.Credit 16.Upstream Port 再发IP2PM.CREDIT_RTN(NUM_CREDITS1)给 Downstream Port 补回 1 个 credit所以 Downstream PortTX.Credit 27.最终两边都置位Power Management Initialization Complete 1表示credit 已建立capability 信息已交换完成PM 初始化完成可以进入后续正常的电源管理交互这张图的核心含义CXL 电源管理交互要先由 Downstream Port 和 Upstream Port 完成 credit bootstrap再通过 AGENT_INFO 交换 PM capability最后双方都把 Power Management Initialization Complete 置 1。CXL 电源管理初始化过程由 Downstream Port PMU 与 Upstream Port PMU 共同完成。该过程分为两个阶段Credit Initialization 和 PM Initialization。只有在两个阶段均完成后双方才能进入正常的电源管理消息交互状态。梳理简化流程如下1 Credit Initialization初始化开始时Downstream Port PMU 和 Upstream Port PMU 的发送信用值均为 0即Downstream Port TX.Credit 0Upstream Port TX.Credit 0在该状态下双方均不具备发送后续 PM 事务的能力因此需要首先完成 credit 初始化。(1) Downstream Port 向 Upstream Port 发送PM2IP.CREDIT_RTN(TARGET_AGENT_ID, NUM_CREDITS1)该消息用于向 Upstream Port 指定的 PM Agent 返回 1 个发送 credit。(2) Upstream Port 接收该消息后将 CXL PM_AGENT_ID 设置为 TARGET_AGENT_ID将自身 TX.Credit 更新为 1此时Upstream Port 获得发送 1 条 PM 消息的能力。(3) Upstream Port 向 Downstream Port 返回IP2PM.CREDIT_RTN(NUM_CREDITS2)(4) Downstream Port 接收后将自身 TX.Credit 更新为 2。至此双方完成 credit 初始化并具备进行后续 PM 初始化交互所需的基本发送能力。2. PM Initialization在 credit 初始化完成后进入 PM Initialization 阶段用于交换 PM Agent 能力信息并完成初始化。(1) Downstream Port 向 Upstream Port 发送PM2IP.AGENT_INFO(Req, Index0, CAPABILITY_VECTOR)该请求用于获取或交换 PM Agent 的 capability vectorIndex0 表示从第一个 capability 项开始。(2) Downstream Port 发出该请求后其 TX.Credit 由 2 变为 1。(3) Upstream Port 响应IP2PM.AGENT_INFO(Rsp, Index0, CAPABILITY_VECTOR)该响应返回对应的 capability vector 信息。(4) 若 capability vector 包含多个索引项则 Downstream Port 与 Upstream Port 可基于不同 Index 进行多轮 AGENT_INFO 请求/响应交互直至所有能力信息交换完成。(5) 在能力交换过程中Downstream Port 向 Upstream Port 发送PM2IP.CREDIT_RTN(NUM_CREDITS1)Upstream Port 接收后其 TX.Credit 更新为 1。(6) 随后Upstream Port 向 Downstream Port 发送IP2PM.CREDIT_RTN(NUM_CREDITS1)Downstream Port 接收后其 TX.Credit 更新为 2。(7) 当能力交换和 credit 调整全部完成后双方均置位Power Management Initialization Complete 13. 结果当 Downstream Port 与 Upstream Port 的 Power Management Initialization Complete 均置位后表示PM credit 初始化完成PM Agent capability 信息交换完成电源管理初始化流程完成链路可以进入正常的电源管理事务交互状态3.1.3 CXL 错误 VDM 格式CXL Error Message 也是 PCIe Vendor Defined Type 0 消息但不带数据负载。目前该类消息只定义了Memory Error Firmware Notification (MEFN)。该消息始终由设备发起路由到 Root Complex。其关键特性如下Fmt/Type为不带数据的消息格式。路由方式为Routed to Root Complex。Vendor ID 1E98h。VDM Code CXL Error Message (00h)。Byte 14[3:0]表示Firmware Interrupt Vector编码由 Host 自定义。CXL MEFN 消息包格式3.1.4 CXL 所需的可选 PCIe 特性规范要求部分在 PCIe 中属于“可选”的能力在 CXL 中必须支持。CXL 所需的可选 PCIe 特性3.1.5 错误传播Error Propagation设备检测到的CXL.cache和CXL.mem错误通过CXL.io流向上游端口传播并记录到 PCIe AER 寄存器中表现为 Correctable 或 Uncorrectable Internal Error。3.1.6 ATS 中的内存类型指示某些内存区域只能经由CXL.io访问而不能走CXL.cache。这些区域由 Host 决定例如在 x86 系统里Host 可能只允许对UC类型内存通过CXL.io发起访问。Host 通过 ATS Completion 上的附加指示把这一信息通知设备。由 CXL 设备发出的 ATS 请求必须设置Source-CXL位。不是所有主机内存都允许设备通过 CXL.cache 做一致性访问。有些内存区域Host 明确规定只能按 I/O 语义访问也就是走 CXL.io不能走 CXL.cache。可以拆开理解1. 谁决定一块内存能不能走 CXL.cache由 Host 决定不是设备自己决定Host 会根据这块内存的属性告诉设备这块地址适合什么访问方式2. 为什么有些区域不能走 CXL.cache因为 CXL.cache 是一致性缓存访问前提是这块内存允许被缓存Host 愿意把它纳入一致性域但有些内存区域不满足这个条件比如MMIO 区域强顺序、不可缓存区域特定平台限制区域这些区域如果让设备通过 CXL.cache 去访问就可能破坏语义。3. x86 里的 UC 是什么意思UC Uncacheable即不可缓存内存类型在 x86 里如果某段内存被 Host 标成 UCCPU 自己都不会按普通 cacheable memory 来处理那设备通常也不能通过 CXL.cache 去缓存访问它只能通过 CXL.io 用 I/O 事务方式访问所以这句话里的例子是在 x86 系统中Host 可能规定对 UC 类型内存只能发 CXL.io不能发 CXL.cache。4. Host 怎么把这个限制告诉设备这里提到通过 ATS Completion 上的附加指示通知设备也就是说设备先发 ATS Request问 Host某个设备要访问的地址对应的主机侧翻译/属性是什么Host 返回 ATS Completion在这个 Completion 里除了地址翻译结果之外还会带额外提示这块地址能不能走 CXL.cache是否必须走 CXL.io所以 ATS 在这里不只是“地址翻译”还承担了访问类型约束传递的作用5. 为什么 Source-CXL 位必须置位这句由 CXL 设备发出的 ATS 请求必须设置 Source-CXL 位意思是Host 需要知道这次 ATS 请求来自一个 CXL 设备而不是普通 PCIe 设备因为CXL 设备既可能发 CXL.io也可能发 CXL.cacheHost 需要根据“请求源是 CXL 设备”来决定返回哪些 CXL 相关属性和限制信息所以 Source-CXL1 的作用是告诉 Host这不是普通 PCIe ATS 请求而是来自 CXL 设备的地址转换请求请按 CXL 规则返回访问属性。总结设备即使拿到了某段主机地址也不代表一定能用 CXL.cache 去访问Host 会通过 ATS Completion 告诉设备这段地址是否只允许走 CXL.io而 CXL 设备发 ATS 请求时必须带上 Source-CXL 标识。时序流程CXL Device 发起 ATS Request请求对目标地址进行地址转换Source-CXL 位必须置 1Host ATS Logic 接收 ATS Request识别该请求来自 CXL Device查询目标地址对应的主机侧地址属性Host Memory Attribute / Access Policy Logic 判断目标地址属性判断该地址是否允许通过 CXL.cache 访问若该地址属于受限区域例如 x86 中的 UC 类型内存则标记为仅允许 CXL.io 访问Host 返回 ATS Completion返回地址翻译结果同时返回附加访问属性指示信息CXL Device 接收 ATS Completion获取翻译后的地址信息获取该地址允许的访问方式CXL Device 根据 Host 返回属性选择后续访问协议若允许 CXL.cache则可发起 CXL.cache 访问若仅允许 CXL.io则后续只能发起 CXL.io 访问不得对该区域发起 CXL.cache 请求总结地址是否允许通过 CXL.cache 访问由 Host 决定Host 通过 ATS Completion 的附加指示将该限制通知给 CXL DeviceCXL Device 必须依据该返回属性选择访问路径包格式在标准 PCIe ATS 64-bit Request 格式里CXL 复用了一个原本保留的 bit作为 CXL Src 指示位用来告诉 Host这次 ATS 请求来自支持 CXL Memory Type Indication 的 CXL 设备。这个 CXL Src 位是干什么的0b表示该 ATS 请求来自 不支持 Memory Type Indication on ATS 的 Function1b表示该 ATS 请求来自 支持 Memory Type Indication on ATS 的 Function并且所有 CXL Device Functions 都必须把该 bit 置 1所以它的作用是告诉 Host这不是普通 PCIe Function 发来的普通 ATS 请求而是一个支持 CXL ATS memory type indication 的 CXL Function 发来的请求3. 为什么需要这个位因为 Host 后续返回 ATS Translation Completion 时不只是返回地址翻译结果还可能附带该地址页是否允许通过 CXL.cache是否只能通过 CXL.io也就是说Host 要先知道请求方是不是 CXL 设备请求方能不能理解“memory type / access type”这类附加语义而 CXL Src1 就是在 ATS 请求阶段提前告诉 Host 这一点。简化流程如下设备发 ATS Request- 带上 CXL Src 1Host 知道这是 CXL 设备- Host 返回 ATS Completion- Completion 里附带访问属性例如是否只能 Issue-on-CXL.io设备据此决定后续访问方式- 走 CXL.cache 或 CXL.io总结CXL 在标准 ATS 64-bit Request 中增加了 CXL Src 指示位所有 CXL Device Function 都必须把它置 1以便 Host 在 ATS Completion 中返回与 CXL 访问类型相关的附加属性信息。Host 在 ATS Translation Completion 里返回一个 CXL 专用属性位用来告诉设备目标页后续能不能走 CXL.cache还是只能走 CXL.io。它和上图 ATS Request with CXL Src 是配套的上图里设备在 ATS Request 中声明 CXL Src 1本张图里Host 在 ATS Completion 中返回 CXL.io 指示位3.2 CXL.cache3.2.1 概述CXL.cache描述设备与 Host 之间的缓存一致性交互。协议在每个方向上都定义了三个通道Request、Response和Data。设备到主机方向为D2H主机到设备方向为H2D。D2H Request设备向 Host 发起新请求通常面向内存。D2H Response设备对 Host snoop 的响应。D2H Data设备向 Host 返回缓存线数据通常由 snoop 或 eviction 触发。H2D RequestHost 向设备发起 snoop。H2D ResponseHost 返回顺序消息、GO或WritePull。H2D DataHost 向设备返回读数据。独立通道的设计用于实现解耦并提高线级吞吐率。. CXL.cache 通道CXL.cache 在设备和 Host 之间不是单一的一条消息流而是按方向拆成 6 个独立逻辑通道。也就是设备到主机D2H主机到设备H2D每个方向再分成 3 类RequestResponseData所以总共就是 6 个 channel。为什么要拆成 6 个通道因为 CXL.cache 处理的是缓存一致性事务而一致性事务里通常会混合出现发起一个新请求对已有请求做响应传输真正的数据 cache line这三类流量语义不同、时序不同所以协议把它们逻辑分开避免混淆。可以把它理解成Request谁先发起一件事Response对某件事给出控制性答复Data真正搬运 cache line 数据3.2.2 CXL.cache 通道说明3.2.2.1 通道排序各通道原则上必须相互独立以确保 forward progress。例如对同一地址X的设备请求在 Host 收齐该地址所有 snoop 响应前可能被阻塞如果通道之间强耦合会造成死锁。但仍有一个关键排序约束Host 必须等设备观察到某地址上的GO响应后才能继续向该地址发出后续 snoop。为降低 Host 追踪GO的缓冲开销规范要求后发出的 snoop 不能越过先发出的GO。CXL.cache 通道独立性和排序约束为什么要同时存在。可以拆成两部分理解为什么各通道原则上要相互独立核心目的就是保证 forward progress避免协议交互卡死避免死锁原因在于缓存一致性事务不是“一发一回”那么简单。例如设备对地址 X 发起一个请求后Host 收到设备请求Host 可能要先向多个缓存持有者发 snoopHost 只有在收齐与地址 X 相关的所有 snoop 响应后才能决定是否给设备最终许可、数据或状态返回所以在这个过程中最开始那个设备请求虽然已经进来了但可能会被暂时阻塞。如果这时候通道之间是强耦合的比如D2H Request 卡住了连带 D2H Response 也发不出来或 H2D Response 卡住了连带 H2D Request 也发不出去就会出现这种循环等待Host 等设备返回 snoop response设备却因为另一个通道阻塞而发不出 response最终双方互相等死锁所以规范才强调各通道原则上必须相互独立。也就是Request 不该把 Response 卡住Response 不该把 Data 卡住D2H 不该因为 H2D 某个流量阻塞而完全失去前进能力为什么又必须保留一个关键排序约束虽然通道要尽量独立但完全无序也不行。这里专门提到一个关键约束Host 必须等设备观察到某地址上的 GO 响应后才能继续向该地址发出后续 snoop。这个要求的含义是GO 是 Host 对设备的一种关键控制许可它表示关于该地址的前一个一致性事务设备可以继续推进只有设备先“看到”这个 GOHost 才能对同一地址继续发后续 snoop否则会出问题设备可能还没完成前一轮状态转换Host 又对同一地址发了新的 snoop设备看到的事务顺序就乱了一致性状态可能冲突所以这里实际上要求同一地址上的 Host-Device 控制流不能乱序跨过 GO。为什么规范要求“后发出的 snoop 不能越过先发出的 GO”这是一个实现优化要求。如果规范不加这个限制那么 Host 为了保证正确性就必须对每个地址追踪已经发出的 GO判断设备是否已经观察到再决定后续 snoop 是否可以继续发送这样 Host 侧就需要很复杂的per-address 跟踪表bufferreorder 控制逻辑为减少这部分硬件开销规范直接规定后发出的 snoop 不能越过先发出的 GO。这样 Host 就可以简单很多按发送顺序保证只要 GO 还在前面没被设备观察到后面的同地址 snoop 就不能抢先到达这样就不用做很重的“GO 观察状态追踪”。用一个直观例子理解假设同一地址 X设备先发起一次对 X 的请求Host 处理后准备给设备一个 GO在设备真正看到这个 GO 之前Host 又想对 X 发一个新的 snoop如果允许这个后发 snoop 抢在 GO 前到设备设备会先看到新的 snoop但它对前一个事务的状态还没被 GO 正式放行状态机就可能进入不一致或不可判定状态所以必须保证GO(X) 先被设备观察到后续 snoop(X) 才能继续出现5. 一句话总结CXL.cache 各通道必须尽量独立以避免因一致性事务阻塞而产生死锁但对同一地址Host 仍必须保证 GO 与后续 snoop 的顺序禁止后发 snoop 越过先发 GO从而在保证正确性的同时降低 Host 的实现复杂度。3.2.2.2 通道 Crediting由于链路层 credit 可能在任意时刻用尽因此每个通道都需要独立 credit 管理。发送方每发一条消息都要消耗 credit接收方在处理完消息并释放缓冲后返还 credit。若没有可用 credit发送方必须等待CXL.cache 通道 Crediting3.2.3 CXL.cache 字段说明3.2.3.1 D2H RequestD2H Request关键字段如下NT1表示该行建议放入 LRU 位置NT0表示默认行为由 Host 实现定义。3.2.3.2 D2H Response3.2.3.3 D2H DataByte Enable虽然逻辑上属于 data header但实际以额外 data chunk 的形式传输仅在不全为 1 时发送。3.2.3.4 H2D Request3.2.3.5 H2D ResponseRSP_PRE 编码含义如下MESI 编码如下3.2.3.6 H2D Data