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

在支持 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 HDM DECODER,例如上图可以从 “/sys/bus/cxl/devices/root0/port2/endpoint3” 里获得 CXL HDM DECODER 相关信息,另外为了更好的让开发者对 CXL HDM DECODER 有更好的了解,接下来结合 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 decoder” 描述一个 CXL HDM DECODER,基于该数据结构拓展出 struct cxl_root_decoder 数据结构,其用于描述 Root HDM Decoder, 而 struct cxl_switch_decoder 数据结构则描述 CXL SWITCH HDM Decoder,最后 struct cxl_endpoint_decoder 数据结构用于描述 CXL ENDPOINT HDM Decoder. 无论这些数据差异如何巨大,其主要目的都是解决 HPA 到 DPA 映射问题.

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

Linux CXL 框架使用 “struct cxl_decoder” 数据结构对 CXL HDM DECODER 进行描述. 其余的 CXL ROOT HDM DECODER、CXL SWITCH HDM DECODER 以及 CXL ENDPOINT DECODE 都是基于其构建,那么 CXL HDM DECODER 各成员的含义是:

  • dev: 表示该解码器对应的设备结构, 用于在内核设备模型中表示该解码器,提供设备管理功能(如设备注册、注销等)
  • id: 解码器的唯一标识符, 用于在内核中唯一标识该解码器,通常用于设备命名或调试
  • hpa_range: 表示该解码器映射的主机物理地址(HPA)范围
  • interleave_ways: 表示该解码器的交错方式数量(Interleave Ways), 描述解码器支持的交错访问方式数量,即有多少个 CXL 设备参与交错访问
  • interleave_granularity: 表示该解码器的交错粒度(Interleave Granularity), 描述解码器的交错访问粒度,即每次交错访问的数据块大小
  • target_type: 描述解码器的目标设备类型,通常用于区分不同类型的 CXL 设备
  • region: 指向与该解码器关联的 CXL 区域(Region), 描述解码器当前分配的内存区域,帮助系统软件管理解码器的内存映射和资源分配
  • flags: 描述解码器的内存类型能力和锁定状态,通常用于指示解码器的功能和状态
  • commit: 指向解码器的提交回调函数
  • reset: 指向解码器的重置回调函数

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

CXL HDM DECODER 并不是作为一个独立硬件存在,而是依附在 CXL PCIe 设备上,例如 CXL ROOT DECODER 依赖于 CEDT 的 ACPI CFMWS 构建,并挂接在 CXL ROOT 上,而 CXL SWITCH HDM SWITCH 则来自 CXL SWITCH DownStream Port,同理 CXL ENDPOINT HDM SWITCH 来自 CXL Endpoint. 除了这些 HDM DECODER 之外,CXL RootPort 也存在 HDM Decoder.

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

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

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

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

CXL HDM Decoder Capability 包含了 CXL PCIe 设备所有的 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  --->

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

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

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

当 BiscuitOS 运行之后,直接运行 RunBiscuitOS.sh 脚本,脚本包含了实践案例运行的所有命令,案例向创建一个 CXL REGION,然后遍历了 CXL ENDPOINT 的所有 Decoder, 其从 CXL ENDPOINT HDM Decoder 里读出了 HPA 的范围, 可见 CXL ENDPOINT 使用的是第一个 HDM DECODER,其映射的 HAP 是 [0xCF000000, 0XD1000000), 此时由于其他 HDM DECODER 都是 0,因此其对应的 DPA 范围是 [0x00000000, 0X20000000). 接下来分析一下源码:

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

实践案例由一个内核模块构成,其在 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.

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

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

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

  • decoders: root0: 描述 ROOT HDM DECODER
  • decoder: 描述 HDM DECODER 的名字
  • size: 描述 HDM DECODER 管理的 HPA 的大小
  • interleave_ways: 描述 HDM DECODER 维护 CXL MEMDEV 的数量
  • max_available_extent: 描述 HDM DECODER 可以维护 HPA 最大的范围
  • pmem_capable: 描述 HDM DECODER 是否支持 PMEM
  • volatile_capable: 描述 HDM DECODER 是否支持 DDR
  • accelmem_capable: 描述 HDM DECODE 是否支持访问加速
  • qos_class: QOS 类
  • nr_target: 描述 HDM DECODER 解码的 CXL HBX 数量
  • targets: 描述 HDM DECODER 解码的 CXL 组件信息
    • target: 描述 HDM DECODER 所属的 CXL 组件
    • alias: 描述 HDM DECODER 所属 CXL 组件的别名
    • poison: 描述 HDM DECODER 内是否有 POISON 区域
    • id: 描述 HDM DECODER 解码组件的 UUID

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

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

  • cap_pmem: 表示解码器是否支持持久内存 PMEM
  • cap_ram: 表示解码器是否支持易失性内存 RAM
  • cap_type2: 表示解码器是否支持 Type 2 设备(CXL 加速器)
  • cap_type3: 表示解码器是否支持 Type 3 设备(CXL 内存扩展器)
  • create_pmem_region: 用于创建持久内存区域
  • create_ram_region: 用于创建易失性内存区域
  • delete_region: 用于删除内存区域
  • devtype: 表示该设备的类型,通常为 cxl_decoder
  • interleave_granularity: 表示解码器的交错访问粒度,即每次交错访问的数据块大小
  • interleave_ways: 表示解码器支持的交错访问方式数量,即有多少个 CXL 设备参与交错访问
  • locked: 指示解码器的配置是否被锁定,防止配置被修改
  • modalias: 设备模块别名
  • qos_class: 服务质量类别, 于优化数据访问的优先级和带宽分配
  • size: 解码器映射的内存区域大小
  • start: 解码器映射的内存区域起始地址
  • subsystem: 设备所属的子系统
  • target_list: 目标设备列表
  • uevent: 用户事件

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

在 Linux 内核启动过程中,内核在调用 cxl_acpi_init 函数初始化 CXL ROOT ACPI0017 时,会从 CEDT 的 CFMWS 表中获得 CXL 硬件配置,内核由此构建 CXL ROOT HDM DECODER,其初始化如图分成以下几个核心逻辑:

  • PARSE ACPI CEDT CFMWS: 函数在初始化 CXL ROOT 时,会调用 acpi_table_parse_cedt 函数解析 CFMWS 表,从中可以获得 CXL 硬件信息,包括 CXL 映射到系统物理地址空间的访问,以及 CXL 物理区域的交织粒度和交织设备数量信息. 通过调用 alloc_cxl_resource 将映射的物理区域注册到系统物理地址空间.
  • ALLOC DECODER FOR ROOT DECODER: 调用 cxl_root_decoder_alloc 函数分配一个 HDM DECODER 作为 ROOT HDM DECODER, 进行简单的初始化.
  • BUILD ROOT DECODER DEVICE INTERFACE: 该阶段将 CXL ROOT HDM DECODER 作为一个设备添加到系统设备系统里,其在 “/sys/bus/cxl/devices/decoder0.0” 目录下创建多个节点,以及这些节点对应的处理函数.
  • INIT ROOT DECODER: 该阶段根据从 CEDT CFMWS 表中获得的信息初始化 CXL ROOT DECODER 里的多个成员,初始化完毕之后 CXL ROOT DECODER 开始负责维护 CXL HPA 到 DPA 的编解码事宜.
  • BIND CXL ROOT DECODER INTO CXL ROOT: 该阶段将初始化好的 CXL HDM DECODER 与 CXL ROOT 进行绑定.

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