图片无法显示,请右键点击新窗口打开图片

在支持 CXL 拓扑的 Intel 硬件平台,Intel 使用 ACPI 维护了硬件相关的信息,CXL 相关的硬件信息被维护在 ACPI0017 节点上,Linux 在初始化过程中,可以通过 ACPI 提供的硬件信息构建 CXL 框架. 如图,CXL 基于 PCIe 硬件进行构建,并且 CXL 硬件独占了一条独立的 PCIe 总线. CXL 硬件总线硬件拓扑主要由以下几个部分组成:

  • ACPI.CXL: ACPI0017 CXL Root Object, 由系统固件在 ACPI 树的 _SB 下实现的虚拟软件实体,表示 CXL 早期发现表 CEDT 的存在. 在系统初始化 CXL 架构时,最先通过 ACPI 树找到 ACPI.CXL 设备,该设备作为 CXL 树的根节点.
  • CXL Host Bridge: ACPI0016 CXL Host Bridge Object, 由系统固件在 ACPI 树的 _SB(系统总线)下实现的虚拟软件实体,由操作系统使用。每个 ACPI0016 对象代表单个 CXL 根复合体。由于 CXL 树的根(CXL 根复合体)是平台特定的,并且不会通过 PCI BAR 显示,系统固件负责生成一个对象来表示 CXL 主桥所代表的一组 CXL 根端口。每个 HB 由 ACPI/SB 设备树顶端下的唯一 ACPI0016 对象表示. 系统固件将在操作系统的代表下实现一些 ACPI 方法. 这不是 CXL 主桥设备应支持的 ACPI 方法的详尽列表.
  • CXL Root Complex: 平台特定的 CXL 根端口,相当于 PCIe 根复合体。总线编号分配由系统固件负责,并通过 CXL 主桥 ACPI 命名空间设备公开.
  • CXL Root Port: 通过一个或多个 Flexbus 通道连接到 CXL 内存设备或 CXL 交换机端口的 CXL 硬件连接,相当于 PCIe 根端口.
  • CXL Switch: 提供 CXL 根端口扩展到多个下行交换机端口,允许更多的 CXL 内存设备以可扩展的方式连接。每个交换机有一组 HDM 解码器,负责上游交换机端口的 HPA 解码。系统固件、UEFI 和操作系统负责编程这些 HDM 解码器,以涵盖所有连接的下游设备和交换机
  • HDM Decoder: HDM 解码器通过系统固件为已知的 CXL 易失性容量设置,通过 UEFI 和操作系统驱动程序为已知的 CXL 持久性容量设置,并由操作系统为热插拔的 CXL 易失性和持久性内存容量设置。这些寄存器确定将主机物理地址(HPA)范围映射到设备暴露的设备物理地址(DPA)范围。HDM 解码器存在于所有上游 CXL 交换机端口以及每个 CXL 根复合体中。这些 HDM 解码器还需要编程以涵盖所有下游设备的 HDM 解码器编程。CXL 根复合体中的 HDM 解码器决定内存事务的目标根端口。同样,上游端口中的 HDM 解码器决定目标下游端口
  • CXL Type3 Device: 可以通过一个或多个 Flexbus 通道连接到 CXL 根端口或 CXL 交换机下行端口。设备使用标准 PCIe 机制映射到 MMCFG 和 MMIO 区域. 类型 3 特定内存设备命令 MMIO 邮箱接口用于管理和配置设备.

以上的组件是 CXL 硬件拓扑必备的硬件组件,除了图中描述的,还包括 CEDT、CHBS、CFMWS、以及 CDAT 等组件,只是这些组件不是实际的硬件,而是 CXL 相关的信息表. Linux CXL 框架基于以上信息进行构建,构建之后可以在 “/sys/bus/cxl” 目录下获得 CXL 框架相关的信息:

图片无法显示,请右键点击新窗口打开图片

CXL 框架是 CXL 在 Linux 内核的软件框架,向上为用户空间提供访问 CXL 各种硬件的接口和服务,向下提供访问 CXL 硬件的实际方法. CXL 框架将不同的 CXL 硬件使用不同的数据结构进行抽象,以便满足不同的系统需求。本节重点介绍 CXL 框架里的 CXL SWITCH,例如上图可以从 “/sys/bus/cxl/devices/root0/port2/endpoint3” 里获得 CXL ENDPOINT 相关信息,另外为了更好的让开发者对 CXL ENDPOINT 有更好的了解,接下来结合 BiscuitOS 实践场景进行讲解,因此需要按如下命令进行环境部署:

# 切换到 BiscuitOS 项目目录
cd BiscuitOS
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig

  [*] DIY BiscuitOS/Broiler Hardware  --->
      <*> Intel Q35
      [*] CXL: Compute Express Link
            CXL Hardware Topology (CXL2.0: x1 VCS + x1 Type DDR)  --->
  [*] package --->
      [*] CXL Tools: cxl/ndctl/daxctl  --->
  

# 配置完毕保存,然后进行部署
make
# 安装必备工具
cd BiscuitOS/output/linux-6.10-x86_64/package/cxl-tools-default
# 第一次执行如下命令
make prepare
make download
make tar
make configure
# 运行 BiscuitOS
make build

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 运行之后,使用 “lspci-common -vt” 根据查看 PCIe 树的拓扑结构,其中 Device 8086:0d93 所在的子树正好是上上图描述的 CXL 硬件拓扑,在该 PCIe 拓扑里与 CXL 拓扑的对应关系:

  • BDF: 0000:0C: pci000c 是 CXL 独占的 PCIe 总线,且 pci000c 为 CXL Host Bridge
  • BDF: 0000:0C:00.1: 该设备是 CXL RootPort,直接挂接在 CXL RootComplex 上.
  • BDF: 0000:0C:01.0: 该设备是 CXL RootPort,直接挂接在 CXL RootComplex 上.
  • BDF: 0000:0D: CXL RootPort 下的一条 PCIe BUS(BUS-D)
  • BDF: 0000:0D:00.0: 该设备是 CXL SWITCH 的上游端口,直接连接到 BUS-D 上.
  • BDF: 0000:0E: CXL SWITCH 内部的 PCIe BUS(BUS-E)
  • BDF: 0000:0E:00.0: CXL SWITCH 下游端口 0,直接连接到 BUS-E 上.
  • BDF: 0000:0E:01.0: CXL SWITCH 下游端口 1,直接连接到 BUS-E 上.
  • BDF: 0000:0E:02.0: CXL SWITCH 下游端口 2,直接连接到 BUS-E 上.
  • BDF: 0000:0E:03.0: CXL SWITCH 下游端口 3,直接连接到 BUS-E 上.
  • BDF: 0000:0F: CXL SWITCH 下游端口 0 下的一条 PCIe BUS(BUS-F)
  • BDF: 0000:0F:00.0: CXL Endpoint 设备,连接 CXL Type3 设备(8086:0D93)

图片无法显示,请右键点击新窗口打开图片

Linux CXL 框架使用 “struct cxl_memdev”、”struct cxl_dev_state” 和 “struct cxl_memdev_state” 描述一个 CXL Type3 设备,”struct cxl_memdev” 用于描述一个 CXL Type3 内存设备,”struct cxl_dev_state” 用于描述 CXL Type3 设备状态信息,而 “struct cxl_memdev_state” 则描述 CXL Type3 内存设备状态信息.

图片无法显示,请右键点击新窗口打开图片

为了更好理解三个数据结构之间关系,可以使用设备驱动模式, CXL 使用 “struct cxl_memdev” 描述 CXL Type3 内存设备的设备端,因此其数据结构内部包含了 “device” 和 “cdev” 两个成员,用于在系统总线端创建 CXL Type3 设备. 而 “struct cxl_dev_state” 则表示驱动端,并且其表示通用的 CXL Type1、Type2 和 Type3 设备,而 “struct cxl_memdev_state” 则表示 CXL Type3 驱动端.

图片无法显示,请右键点击新窗口打开图片

Linux CXL 框架使用 “struct cxl_port” 数据结构对 CXL ENDPOINT 进行描述. CXL ENDPOINT 在 Linux CXL 框架里,与 CXL SWITCH、CXL SWITCH 都使用同样的数据结构,在 CXL ENDPOINT 里,各成员的含义:

  • dev: dev 成员使用 device 概念描述 CXL ENDPOINT
  • uport_dev: 用于指向对应的 struct cxl_memdev mem0
  • host_bridge: 指向 CXL ENDPOINT 对应的 PCIe HOST Bridge
  • id: CXL ENDPOINT 的 ID 标识
  • dports: 用于维护挂载 CXL ENDPOINT 上的 CXL Type1/Type2/Type3 设备
  • regions: 用于维护 CXL ENDPOINT 管理的所有 CXL Region
  • parent_dport: 指向 CXL SWITCH 上对应的 CXL Dport

图片无法显示,请右键点击新窗口打开图片

Linux CXL 框架将 CXL 硬件抽象为上图的逻辑拓扑,其中 CXL ENDPOINT 为 ENDPOINT3,其介于 CXL SWITCH DPORT 之下. 在 CXL 框架里 CXL ENDPOINT 与其他组件之间的联系如下:

  • port2: 表示 CXL SWITCH
  • endpint3: 表示 CXL ENDPOINT
  • endpoint3.dev->parent: 指向 CXL SWITCH
  • endpoint3.parent_dport: 指向挂接在 CXL SWITCH 的下游端口
  • endpoint3.uport_dev: 指向 CXL Type3 设备

图片无法显示,请右键点击新窗口打开图片

Linux CXL 框架是尽可能将 CXL 硬件抽象为软件可以维护和管理的数据结构,从图中可以看出 Linux 使用一个 CXL PORT 对应 CXL ENDPOINT,ENDPOINT3 的 UPORT_DEV 映射 CXL Type3 设备 mem0, ENDPOINT3 的 PARENT DPORT 指向 CXL SWITCH 的下游端口.

图片无法显示,请右键点击新窗口打开图片

当 Linux 构建完 CXL 框架之后,可以在用户态使用 “cxl list -v” 命令查看 CXL 框架拓扑结构,对于 CXL ENDPOINT 部分,其位于拓扑结构的 endpoint3 内部,其各字段的含义如下:

  • endpoint: 该字段描述 CXL ENDPOINT 的名字 “endpoint3”.
  • host: 该字段描述 CXL ENDPOINT 映射的 CXL Type3 设备
  • depth: 该字段描述离 CXL ROOT 的距离
  • memdev: 该字段描述具体的 CXL Type3 设备
    • memdev: 该字段描述 CXL Type3 设备的名字
    • ramsize: 该字段描述了 CXL Type3 设备的容量大小
    • host: 字段描述 CXL Type3 设备对应的 PCIE BDF

图片无法显示,请右键点击新窗口打开图片

另外, CXL 框架构建完毕之后,可以在 “/sys/bus/cxl” 目录下查看 CXL 框架信息,其中 “devices/port2/” 目录下提供给用户空间的接口,以及 CXL SWTICH 与其他组件之间的关系,各节点和目录的含义如下:

  • CDAT: Coherent Device Attribute Table(一致性设备属性表), 包含设备的属性和性能信息,例如内存类型、延迟、带宽等.
  • devtype: 设备类型, 通常为 cxl_port,表示这是一个 CXL 端口设备.
  • decoder3.X: 表示该端口上的 CXL 解码器,用于管理内存映射和地址转换, 用于配置和管理 CXL 设备的内存资源
  • modalias: 设备模块别名, 通常用于自动加载相应的内核模块
  • parent_dport: 表示 CXL ENDPOINT 使用的 CXL SWITCH 下游端口
  • subsystem: 设备所属的子系统
  • uevent: 用于触发用户空间事件的文件
  • uport: 上游端口(Upstream Port), 表示连接到 CXL Endpoint
  • decoders_committed: 表示已提交的 CXL 解码器数量
  • driver: 设备绑定的驱动程序.

图片无法显示,请右键点击新窗口打开图片

在 Linux 内核启动过程中,CXL ENDPOINT 初始化在创建完 CXL HOST BRIDGE 之后,此时 CXL 子系统会再次遍历所有的 CXL DEVICE,其中包括 CXL ENDPOINT 对应的 “0000:0F:00.0” PCIE 设备,因此初始化的起点为 cxl_pci_probe, 其核心逻辑如下:

  • PCIE ENDPOINT INIT: cxl_pci_probe 函数针对 “0000:0F:00.0” PCIE 设备进行初始化,这部分初始化是作为一个 PCIE 设备的角度进行的,包括获得 DVSEC 各寄存器的位置,以及初始化 MAILBOX 等,例如调用 cxl_await_media_ready 检查设备是否已经准备 OK,接着为 PCIE 设备分配 IRQ. 该节点还调用 cxl_mem_create_range_info 函数获得 CXL Type3 设备的 DPA 信息.
  • CXL MEMDEV INIT: PCIE 设备初始化完毕之后,接下来为 CXL Type3 设备创建并初始化 CXL MEMDEV 设备,该设备名字为 “mem0”, 其作为 CXL ENDPOINT 的设备端
  • CDEV INIT: 该阶段会为 CXL MEMDEV 创建字符设备 “/dev/cxl/memX”, 并调用 device_add 函数初始化字符串设备.
  • CXL MEMDEV DRIVER PROBE: 初始化字符串设备 “/dev/cxl/memX” 的时候,会调用对应的驱动程序,这时会调用 cxl_mem_probe 函数初始化 memX 设备,具体包括在 debugfs 目录下添加 “inject_poison” 和 “clear_poison” 接口. 另外调用 devm_cxl_enumerate_ports 函数遍历所有的 CXL PORT 设备,将 CXL EP 添加到各 CXL PORT 的 endpoint XARRAY 数组.
  • CREATE CXL ENDPOINT: 接下来调用 devm_cxl_add_endpoint 函数创建 CXL PORT,用于描述 CXL ENDPOINT,此时起名字为 endpoint3.
  • CXL CDEV INIT: 当 CXL MEMDEV 的 CDEV 创建完毕后,为其提供 CDEV OPS cxl_memdev_ioctl,用于用户进程向 CXL MEMDEV 发送 IOCTL 请求,最后便是对 “0000:0F:00.0” PCIe 设备做一些初始化.

图片无法显示,请右键点击新窗口打开图片

图片无法显示,请右键点击新窗口打开图片

CXL ENDPOINT 本身是 PCIe 设备,因此可以使用 “lspci-common -vt” 查看 CXL 硬件拓扑结构,然后可以找到 CXL ENDPOINT BDF 为 “0000:0F:00.0”. 接着使用 “lspci-common -s BDF” 查看 CXL ENDPOINT 的配置空间. 从该配置空间可以获得 CXL ENDPOINT 很多有用信息,例如其 BAR 映射的 MMIO 范围,以及连接状态和端口号等信息.

图片无法显示,请右键点击新窗口打开图片

CXL 在 CXL ENDPOINT 的 PCI Express Extended Capability 区域提供了 DVSEC(Designated Vendor-Specific), 其是 PCIe 和 CXL 规范中定义的一种机制,用于允许厂商在标准 PCIe/CXL 配置空间中添加厂商特定的扩展功能. DVSEC 提供了一种标准化的方式来扩展设备的配置空间,同时保持与标准规范的兼容性.

图片无法显示,请右键点击新窗口打开图片 图片无法显示,请右键点击新窗口打开图片

对于 CXL ENDPOINT,PCIe DVSEC 主要提供了 Register Locator DVSEC 配置,它是一个关键的结构,用于定位和描述 CXL 设备中的寄存器块(Register Blocks). 它的主要作用是为操作系统或系统软件提供一种标准化的方式来发现和访问 CXL 设备中的特定寄存器区域.

图片无法显示,请右键点击新窗口打开图片

通过 Register Locator DVSEC 可以获得 CXL Subsystem Component Register 在 CXL ENDPOINT Bar 空间的位置,上图是 CXL 标志的组件寄存器布局, CXL ENDPOINT 从这些寄存器里获得 HDM DECODER 相关信息,以及控制相应的行为.

图片无法显示,请右键点击新窗口打开图片

对于 CXL ENDPOINT,PCIe DVSEC 提供的另外一个配置是 PCIe DVSEC for CXL Device, 其主要用于提供 CXL Type3 设备的属性信息,以及一致性等控制设备的寄存器,也可以获得 CXL Type3 设备容量信息. 接下来通过一个实践案例带各位开发者介绍 CXL ENDPOINT 的 DVSEC 使用逻辑, 其在 BiscuitOS 上的部署逻辑如下:

# 切换到 BiscuitOS 项目目录
cd /BiscuitOS
# 选择开发环境,如果已经选择过可以跳过,这里与 linux 6.10 X86 为例
make linux-6.10-x86_64_defconfig
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig

  [*] DIY BiscuitOS/Broiler Hardware  --->
      [*] CXL: Compute Express Link
            CXL Hardware Topology (CXL2.0: x1 VCS + x1 Type DDR)  --->
  [*] Package  --->
      [*] HETEROGENEOUS MEMORY MANAGEMENT
          [*] CXL DVSEC: PCIe DVSEC for CXL Device  --->

# 配置完毕保存,然后进行部署
make

# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-DVSEC-PCIE-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本,脚本里包含了实践案例运行的所有命令,可以看出案例解析出 CXL ENDPOINT 对应的 PCIe DVSEC 里获得 CXL Type3 设备的容量信息,接下来分析源码:

图片无法显示,请右键点击新窗口打开图片

实践案例由一个内核模块构成,首先在 69 行调用 bus_for_each_dev 函数遍历 PCIE 总线,然后通过 BiscuitOS_match_pci 过滤函数找到 CXL ENDPOINT 对应的 PCIe 设备, 函数接着在 33 行调用 pci_find_dvsec_capability 函数从 PCIe ENDPOINT 的 “PCI Express Extended Capability” 里找到 “PCIe DVSEC for CXL Device”. 接着在 39 行调用 pci_read_config_word 函数从该 DVSEC 中读取 CXL_DVSEC_CAP_OFFSET, 寄存器.

图片无法显示,请右键点击新窗口打开图片

从 CXL_DVSEC_CAP_OFFSET 寄存器里获得 HDM DECODER 的数量. 然后从 CXL_DVSEC_RANGE_SIZE 寄存器中获得 CXL 设备的容量信息, 接着从 CXL_DVSEC_RANGE_BASE 寄存器里获得设备 HPA 基地址信息.

图片无法显示,请右键点击新窗口打开图片

HDM 解码器(HDM Decoder) 是 CXL 设备中的一个硬件模块,负责将主机的物理地址(HPA)映射到设备内存的地址(Device Physical Address, DPA). 在 CXL ENDPOINT 如何将 HPA 最终映射到 DPA 呢?

CXL 硬件提供了用于描述映射 HPA 区域的寄存器,包括 HAP 区域的基地址和长度寄存器,寄存器位于 PCIe 的配置空间,因此 CXL 硬件中属于 PCIe 设备的才具有该寄存器,由于 CXL ENDPOINT 是 PCIe 设备,因此 HDM Decoder 落到了 CXL ENDPOINT 的配置空间

在 CXL ENDPOINT 的 PCI Express Extended Capability 区域存在 Register Locator DVSEC, 根据其可以获得 CXL ENDPOINT Component Registers 在其 BAR 空间的位置.

在获得 Component Registers 之后,找到 CXL.cache and CXL.mem Register 区域,依次遍历所有的 CXL Capability 条目,然后找到 CXL HDM Decoder Capability 条目.

CXL HDM Decoder Capability 包含了 CXL ENDPOINT 所有的 HDM Decoder,每个 HDM Decoder 包含基础的 4 个寄存器,分别是 CXL HDM Decoder X Base Low RegisterCXL HDM Decoder X Base High RegisterCXL HDM Decoder X Size Low RegisterCXL HDM Decoder X Size High Register. 系统将映射 HPA 区域的值填入该区域之后,也就代表该 HDM Decoder 用于解码这段 HPA 区域.

当系统访问了 CXL 映射的 HPA 物理地址时,硬件会自动检查 HPA 来自 CXL,然后依次遍历所有的 CXL HOST BRIDGE,每当遍历一个 CXL HOST BRIDGE 时,其会遍历所有的 CXL ROOT PORT 的 HDM DECODER 寄存器,从中获得 HDM DECODER 映射的 HPA 范围,确认被访问的 HPA 是否落在该 HDM DECODER 上,如果不落在则继续遍历下一个 HDM DECODER,如果遍历完一个 CXL HOST BRIDGE 所有 CXL ROOT PORT 没有匹配到 HAP,那么则遍历下一个 HOST BRIDGE. 如果在遍历过程中匹配到一个 HDM DECODER,那么就确认 CXL HOST BRIDGE.

确认 CXL HOST BRIDGE 之后,也就确认了 CXL ROOT PORT,那么接下来查看 CXL SWITCH 的 HDM DECODER,硬件会自动遍历 CXL SWITCH 所有下游端口维护的 HDM DECODER,然后找到 HPA 落到的 CXL SWITCH DOWNSTREAM PORT.

由于 PCIe 里 PCIe SWITCH DOWNSTREAM PORT 与 PCIe ENDPOINT 是端到端连接的,因此硬件继续遍历 CXL ENDPOINT 维护的所有 HDM DECODER,然后找到映射 HPA 的 HDM DECODER,其会计算该 HDM DECODER 是第几个,然后计算前几个 HDM DECODER 维护的 HPA 范围,以此计算 DPA 的偏移,由于 DPA 对软件是透明的,计算出 HPA 偏移之后,也就可以计算 HPA 对应的 DPA. 接下来通过一个实践案例介绍 HDM Decoder 的查找逻辑, 其在 BiscuitOS 上的部署逻辑如下:

# 切换到 BiscuitOS 项目目录
cd /BiscuitOS
# 选择开发环境,如果已经选择过可以跳过,这里与 linux 6.10 X86 为例
make linux-6.10-x86_64_defconfig
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig

  [*] DIY BiscuitOS/Broiler Hardware  --->
      [*] CXL: Compute Express Link
            CXL Hardware Topology (CXL2.0: x1 VCS + x1 Type DDR)  --->
  [*] Package  --->
      [*] HETEROGENEOUS MEMORY MANAGEMENT
          [*] CXL HDM: ENDPOINT DECODER  --->

# 配置完毕保存,然后进行部署
make

# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-HDM-ENDPOINT-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 运行之后,直接运行 RunBiscuitOS.sh 脚本,脚本包含了实践案例运行的所有命令,案例遍历了 CXL ENDPOINT 的所有 Decoder, 其从 CXL ENDPOINT HDM Decoder 里读出了 HPA 的范围. 接下来分析一下源码:

图片无法显示,请右键点击新窗口打开图片

实践案例由一个内核模块构成,其在 54 行调用 bus_find_device 遍历 CXL 总线,然后通过 traverse_cxl_port 函数遍历找到 endpoint3,也就是 CXL ENDPOINT. 接着在 31 行从 endpoint3 里获得 CXL HDM 数据结构,并在 34 行使用 FOR 循环遍历所有的 CXL HDM Decoder,每遍历一个 HDM Decoder 时,从 CXL HDM Decoder X Base Low RegisterCXL HDM Decoder X Base High RegisterCXL HDM Decoder X Size Low RegisterCXL HDM Decoder X Size High Register 寄存器中获得 HPA 映射的值,这些寄存器来自 CXL ENDPOINT.

图片无法显示,请右键点击新窗口打开图片

CXL 和其他行业标准的一致性互连技术使得一致性交换机、一致性内存设备或一致性加速器设备能够连接到一个或多个处理器, 包含这些设备的系统本质上是异构的,系统软件需要了解拓扑结构、设备属性和亲和性信息,以便优化资源分配. 在现代系统中,系统固件通过静态的高级配置与 ACPI 表(如系统资源亲和性表 SRAT 和异构内存属性表 HMAT) 将这些信息传递给操作系统. SRAT ACPI 表描述了系统中的各种非一致性内存访问 NUMA 域,包括主机处理器、加速器和内存. HMAT ACPI 表描述了从任何发起者(处理器或加速器)到任何内存目标的带宽和延迟. SRAT 和 HMAT 由系统固件或预启动环境构建. 在这些一致性互连技术出现之前,处理器是系统中唯一的一致性组件, 系统固件可以预期对处理器的性能属性有先验知识,并因此构建 SRAT 和 HMAT. 然而对于具有这些一致性互连技术的系统,这一假设不再成立. 通常这些系统由来自多个供应商的一致性设备和交换机构建. 让系统固件跟踪来自不同供应商的多个组件的性能属性可能是不切实际的, 系统固件更新将需要处理新类型的设备. 此外某些一致性互连技术允许动态添加或移除设备和交换机,这使得系统软件收集性能信息变得更加具有挑战性.

一致性设备属性表(Coherent Device Attribute Table: CDAT) 被引入以应对这一挑战, CDAT 是由一致性组件暴露的数据结构,描述了这些组件的性能特征. 这些组件的类型包括一致性内存设备、一致性加速器和一致性交换机. 例如 CXL 加速器可能通过 CDAT 暴露其性能特征. CDAT 描述了一致性组件的属性,并且不依赖于系统配置. CDAT 在 CXL ENDPOINT 中的作用:

  • 设备发现与拓扑管理: CDAT 表提供了 CXL Endpoint 的详细信息,包括设备类型、连接方式和拓扑结构, 系统软件通过解析 CDAT 表可以发现系统中的 CXL Endpoint 及其连接关系.
  • 性能优化: CDAT 表包含了 CXL Endpoint 的性能属性,例如带宽、延迟等. 系统软件可以根据这些信息优化数据访问路径,选择性能最优的设备进行数据访问.
  • 一致性管理: CDAT 表描述了 CXL Endpoint 的一致性属性,例如是否支持缓存一致性, 系统软件可以根据这些信息管理设备之间的缓存一致性,确保数据的一致性访问
  • 内存扩展与共享: CDAT 表提供了 CXL Endpoint 的内存映射信息,帮助系统将设备的内存映射到主机的地址空间. 系统软件可以根据这些信息优化内存分配和共享,确保高效的数据访问和传输
  • 动态设备管理: CDAT 表支持动态设备管理,允许系统在运行时添加或移除 CXL Endpoint. 系统软件可以根据 CDAT 表的信息动态调整资源分配,优化系统性能

CDAT 表由多个子结构组成,每个子结构描述了设备的不同属性和性能特征, 以下是 CDAT 表中常见的几种子结构及其作用

  • Device Scoped Memory Affinity Structure(DSMAS): 描述设备与内存之间的亲和性关系,特别是设备对内>存的访问属性和管理方式. DSMAS 提供了设备内存区域的详细信息,帮助系统软件优化内存分配、管理缓存一致性以及处理动态容量变化.
  • Device Scoped Latency and Bandwidth Information Structure(DSLBIS): 描述设备访问内存的延迟和带宽信息. 具体包括: 设备访问特定内存区域的延迟(Latency)和带宽(Bandwidth), 这些信息可以帮助系统软件了解设备与内存之间的性能特征. 操作系统可以根据 DSLBIS 的信息优化数据访问路径,选择延迟最低或带宽最高的内存区域进行数据分配, 例如在 CXL 内存扩展系统中,操作系统可以根据 DSLBIS 的信息将频繁访问的数据分配到延迟较低的内存区域.
  • Device Scoped Memory Side Cache Information Structure(DSMSCIS): 描述设备的内存侧缓存(Memory Side Cache)信息. 具体包括: 设备的内存侧缓存的容量、类型(如 L1、L2 缓存)和一致性属性, 缓存的一致性信息(例如是否支持缓存一致性协议). 操作系统可以根据 DSMSCIS 的信息管理设备的内存侧缓存,确保缓存一致性并优化缓存使用, 例如在 CXL 设备中,操作系统可以利用 DSMSCIS 的信息决定是否启用缓存一致性协议.
  • Device Scoped Initiator Structure(DSIS): 描述设备的发起者(Initiator)信息, 具体包括: 设备的发起者类型(例如,处理器、加速器等), 发起者与目标设备之间的连接方式和性能特征. 操作系统可以根据 DSIS 的信息了解设备的发起者及其性能特征,优化数据访问路径, 例如在 CXL Switch 中,操作系统可以根据 DSIS 的信息将数据访问请求分配到性能最优的发起者上.
  • Device Scoped EFI Memory Type Structure(DSEMTS): 描述设备的 EFI 内存类型信息, 具体包括: 设备的内存类型(例如 DRAM、NVDIMM 等), 内存的 EFI 类型(例如 EFI 内存映射表中的内存类型). 操作系统可以根据 DSEMTS 的信息识别设备的内存类型,并根据内存类型优化内存管理策略, 例如在 CXL 内存扩展系统中,操作系统可以根据 DSEMTS 的信息将不同类型的内存分配给不同的任务.
  • Switch Scoped Latency and Bandwidth Information Structure(SSLBIS): 描述交换机(Switch)的延迟和带宽信息, 具体包括: 交换机连接设备之间的延迟和带宽信息, 这些信息可以帮助系统软件了解交换机连接的设备之间的性能特征. 操作系统可以根据 SSLBIS 的信息优化数据在交换机连接的设备之间的传输路径,选择延迟最低或带宽最高的路径, 例如在 CXL Switch 中,操作系统可以根据 SSLBIS 的信息将数据访问请求分配到性能最优的设备上.

Device Scoped Memory Affinity Structure(DSMAS) 是 CDAT 中的一个关键子结构,用于描述设备与内存之间的亲和性关系,特别是设备对内存的访问属性和管理方式. DSMAS 提供了设备内存区域的详细信息,帮助系统软件优化内存分配、管理缓存一致性以及处理动态容量变化. DSMAS 表项结构如上图,其各成员的含义如下:

  • Flags: 描述内存区域的属性和管理方式
    • Bit2 - Non-Volatile: 如果置位,表示该内存区域是非易失性内存(Non-Volatile Memory)
    • Bit3 - Sharable: 如果清零,该内存区域仅映射到当前访问 CDAT 的主机,不与其他主机共享; 如果置位, 该内存区域可以映射到当前访问 CDAT 的主机以及一个或多个其他主机
    • Bit4 - Hardware Managed Coherency: 如果 Bit 3 为 置位(内存区域可共享),则: 清零,软件负责管理多主机之间的缓存一致性; 置位, 设备负责管理多主机之间的缓存一致性,确保每个主机对内存内容的一致性视图
    • Bit5 - Interconnect specific Dynamic Capacity Management: 如果清零,当前映射的动态容量通过 DSMAS 和 DSEMTS 结构报告, 未映射的 DPA 地址范围必须通过 DSEMTS 报告为 EfiReservedMemoryType; 如果置位, 使用特定互连机制确定当前映射的动态容量及其变化,动态容量变化不会触发 DSEMTS 更新
  • DPA Base: 描述该 DSMAS 条目关联的最低 DPA(Device Physical Address) 地址
  • DPA Length: 描述该 DSMAS 条目关联的内存区域的长度(以字节为单位)

CDAT PDF

CDAT 寄存器并不位于 CXL ENDPOINT 的 PCIe Configuration Space,也不为位于 BAR 空间,而是位于 CXL ENDPOINT 内部. 为了从 CXL ENDPOINT 内部读取 CDAT,需要使用 PCIe Doe 通信机制,以此从 CXL ENDPOINT 中获得 CDAT 信息. 接下来通过一个实践案例介绍如何访问 CXL ENDPOINT 的 CDAT,其在 BiscuitOS 上的部署逻辑如下:

# 切换到 BiscuitOS 项目目录
cd /BiscuitOS
# 选择开发环境,如果已经选择过可以跳过,这里与 linux 6.10 X86 为例
make linux-6.10-x86_64_defconfig
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig

  [*] DIY BiscuitOS/Broiler Hardware  --->
      [*] CXL: Compute Express Link
            CXL Hardware Topology (CXL2.0: x1 VCS + x1 Type DDR)  --->
  [*] Package  --->
      [*] HETEROGENEOUS MEMORY MANAGEMENT
          [*] CXL CDAT: DSMAS(Device Scoped Memory Affinity Structure)  --->

# 配置完毕保存,然后进行部署
make

# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-CDAT-DSMAS-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 运行之后,直接运行 RunBiscuitOS.sh 脚本,脚本包含了实践案例运行的所有命令,案例从 CXL ENDPOINT 中读取了 CDAT 的 DSMAS 表,然后将所有表项的内容都读了出来,可以看到其获得了 CXL ENDPOINT 的 DPA 范围. 接下来分析一下源码:

图片无法显示,请右键点击新窗口打开图片

实践案例由一个内核模块构成,其首先在 119 行调用 bus_for_each_dev 函数遍历 PCIE 总线,然后通过 BiscuitOS_match_pci 函数找到 CXL ENDPOINT “0000:0F:00.0”, 69-122 行通过 Doe 机制从 CXL ENDPOINT 读取 CDAT 表,并在 108-111 行从中找到 ACPI_CDAT_TYPE_DSMAS 表,找到之后调用 parse_cdat_dsmas 函数解析 DSMAS 表,从表里可以获得 CXL ENDPOINT 的 DPA 范围.

图片无法显示,请右键点击新窗口打开图片

图片无法显示,请右键点击新窗口打开图片

BiscuitOS 目前支持 CXL2.0 CXL ROOT 的实践,本节用于介绍如何在 BiscuitOS 上实践上图所示的 CXL 拓扑, 项目包含了一台主机,CPU 通过南桥转 CXL Host Bridge,其引入 PCI:0C 总线,直接连接到 CXL Root Complex,CXL RC 里仅包含一个 CXL RootPort,此时 CXL RootPort 的 BDF 为 “0C:00.0”. RP 通过 “BUS-D” 总线连接到 CXL SWITCH 上,SWITCH 只虚拟了一个 VCS,那么 Upstream Port(UP vPPB) 的 BDF 为 “0D:00.0”, VCS 通过 “BUS-E” 总线连接 4 个 Downstream Port(DP vPPB), 每个 DP 都连接到一个 PCIe 插槽上. 实践案例里只有 “BUS-F” 连接的 PCIe 插槽上插入一个 “CXL Type3” 设备,该设备的 BDF 为 “0F:00.0”. 接下来在 BiscuitOS 实践该场景, 参考如下命令:

# 切换到 BiscuitOS 项目目录
cd /BiscuitOS
# 选择开发环境,如果已经选择过可以跳过,这里与 linux 6.10 X86 为例
make linux-6.10-x86_64_defconfig
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig

  [*] DIY BiscuitOS/Broiler Hardware  --->
      [*] CXL: Compute Express Link
            CXL Hardware Topology (CXL2.0: x1 VCS + x1 Type DDR)  --->
  [*] Package  --->
      [*] HETEROGENEOUS MEMORY MANAGEMENT
          [*] CXL BUS: TRAVERSE ENDPOINT  --->

# 配置完毕保存,然后进行部署
make

# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-BUS-TRAVERSE-ENDPOINT-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build

图片无法显示,请右键点击新窗口打开图片

BiscuitOS 启动之后,可以使用 CXL 工具查看 CXL 的拓扑结构,拓扑与 CXL 硬件对应关系是: “root0” 对应 “CXL Root”, “port1” 对应 “CXL Host Bridge”, 其 dports 对应着 “CXL RootPort”. “port2” 对应 CXL SWITCH,其 dport 对应 CXL SWITCH 的Downstream Port, 最后 “endpoint3” 对应 “CXL Type3” 设备. 接着使用 lspci 工具查看 PCIe 的拓扑信息,可以看到 CXL 使用了 “PCI-0C”,并且 BDF 可以和之前介绍的对应上,其中 “CXL Type3” 设备的 BDF 为 “0F:00.0”.

图片无法显示,请右键点击新窗口打开图片

硬件拓扑分析完毕之后,直接运行 RunBiscuitOS.sh 脚本,脚本里包含了运行案例所需的全部命令,可以看到案例读取了 CXL SWITCH 上游端口的配置空间. 接着使用 “lspci-common -vt” 命令查看 PCIe 的硬件拓扑,可以看到 CXL ENDPOINT, 并看到 CXL MEMDEV 设备和其容量. 接下来分析源码:

图片无法显示,请右键点击新窗口打开图片

实践案例由一个内核模块构成,其在 49 行调用 bus_find_device 函数遍历 CXL BUS 上的所有设备,每遍历一个设备就会调用 traverse_cxl_port 函数. 在 traverse_cxl_port 函数里,函数通过 27-31 行逻辑找到了 CXL ENDPOINT 对应的 endpoint3,接着将调用 to_cxl_memdev 函数将 port 的 uport_dev 转换成 CXL MEMDEV,然后通过 to_cxl_memdev_state 将其继续转换成 struct cxl_memdev_state 数据结构,接下来从中获得想要的信息.

图片无法显示,请右键点击新窗口打开图片