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

在支持 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 HOST BRIDGE,例如上图可以从 “/sys/bus/cxl/devices/prot1” 里获得 CXL HOST BRIDGE 相关信息,另外为了更好的让开发者对 CXL HOST BRIDGE 有更好的了解,接下来结合 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_port” 数据结构对 CXL HOST Bridge 进行描述. CXL HOST Bridge 在 Linux CXL 框架里,与 CXL SWITCH、CXL Endpoint 都使用同样的数据结构,在 CXL HOST Bridge 里,各成员的含义:

  • dev: dev 成员使用 device 概念描述 CXL HOST Bridge
  • uport_dev: 用于指向对应的 PCIe HOST Bridge
  • host_bridge: 指向 CXL HOST Bridge 对应的 PCIe HOST Bridge
  • id: CXL HOST Bridge 的 ID 标识
  • dports: 用于维护挂载 CXL HOST Bridge 上的 CXL RootPort
  • endpoints: 用于维护挂载 CXL HOST Bridge 下所有的 CXL Endpoint
  • regions: 用于维护 CXL HOST Bridge 管理的所有 CXL Region
  • parent_dport: 指向 CXL ROOT 上对应的 CXL Dport
  • nr_dports: 记录 CXL HOST Bridge 维护的 CXL RootPort 数量

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

Linux CXL 框架将 CXL 硬件抽象为上图的逻辑拓扑,其中 CXL HOST BRIDGE 位于 CXL ROOT 之下的位置,可以从图中看到 CXL HOST BRIDGE 名为 “port1”. 当系统通过 ACPI0017 ACPI 设备获得 CXL ROOT 信息之后,其会继续通过 ACPI0016 ACPI 设备获得 CXL HOST BRIDGE 信息. 在 CXL 框架里 CXL HOST BRIDGE 与其他组件之间的联系如下:

  • port1: 表示 CXL HOST Bridge/CXL ROOT Complex
  • port1.dev->parent: 指向 CXL ROOT
  • port1.parent_dport: 指向 CXL ROOT 维护的 CXL DPORT
  • port1.dports: 维护挂接到 CXL HOST BRDIGE 上的 CXL ROOT PORT
  • port1.uport_dev: 指向对应的 PCIe HOST Bridge
  • root->dports: CXL ROOT 可以维护多个 CXL HOST BRIDGE,当 CXL HOST BRIDGE 只有一个 CXL ROOT.

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

Linux CXL 框架是尽可能将 CXL 硬件抽象为软件可以维护和管理的数据结构,从图中可以看出 CXL ROOT 对应 ACPI0017 ACPI 设备,也就抽象为一条独立的 CXL BUS. 另外 CXL HOST Bridge(ACPI0016) 抽象为 CXL PORT1,因此 CXL PORT1 和 CXL ROOT 之间同一定的数据结构描述 CXL 硬件上 ACPI0017 与 CXL HOST BRIDGE 的关系,这里使用一个 CXL ROOT 的 CXL DPORT 描述 CXL HOST BRIDGE 与 ACPI0017 之间的关系,而 CXL PORT1 的 PARENT DPORT 则指向该 CXL DPORT,那么从 CXL 框架层面就抽象出 ACPI0017 与 ACPI0016(CXL HOST BRDIGE) 之间的关系.

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

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

  • port: 该字段描述 CXL HOST BRIDGE 的名字 “port1”.
  • host: 该字段描述所属的 PCIe HOST Bridge
  • depth: 该字段描述离 CXL ROOT 的距离
  • nr_dports: 该字段描述 CXL ROOT 有几个 CXL HOST Bridge
  • dports: 该字段描述具体的 CXL RootPort
    • dport: 该字段描述 CXL RootPort 对应的 BDF
    • id: 该字段为 DPORT 提供唯一的 ID

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

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

  • decoder1.0: CXL HOST Bridge 使用的 HDM-0 解码器,负责地址转译
  • decoder1.1: CXL HOST Bridge 使用的 HDM-1 解码器,负责地址转译
  • decoder1.2: CXL HOST Bridge 使用的 HDM-2 解码器,负责地址转译
  • decoder1.3: CXL HOST Bridge 使用的 HDM-3 解码器,负责地址转译
  • decoders_committed: 指示解码器是否已成功配置
  • devtype: 指示 CXL HOST Bridge 的类型
  • dport0: 指向 CXL HOST Bridge 的第一个 CXL RootPort
  • dport1: 指向 CXL HOST Bridge 的第二个 CXL RootPort
  • modalias: 设备的别名信息或设备 ID
  • parent_dport: 指向 “port1” 在 “root0” 的 CXL DPORT
  • port2: 指向 CXL SWITCH
  • subsystem: 设备所属子系统的信息
  • uevent: 提供设备的事件通知
  • uport: 指向其上游端口,这里指向 ACPI0016

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

在 Linux 内核启动过程中,CXL ROOT 初始化通过 “subsys_initcall” 时机调用 ‘cxl_acpi_init’ 函数完成,其初始化如图分成以下几个核心逻辑:

  • A: 系统在启动过程中调用 cxl_acpi_init 函数,其会调用 bus_for_each_dev 函数再次遍历 ACPI 总线,然后通过 add_host_bridge_dport 函数找到 ACPI0016 设备,该 ACPI 设备记录了 CXL HOST BRIDGE 相关信息.
  • B: 找到 ACPI0016 ACPI 设备之后,接着调用 cxl_get_chbs 从 APCI 中获得 CHBS 表,里面记录了 CXL 一些硬件信息,例如 CXL HOST Bridge 的 UID,以及 CXL 版本号,从 CHBS 表可以获得 CXL 是否支持 CXL 2.0 信息.
  • C: 系统从 ACPI0016 设备里获得 CXL HOST Bridge 对应的 PCI BUS 信息,例如这里获得 CXL HOST Bridge 对应 “pci0000:0c” 总线,并找到 “pci0000:0c” 对应的 PCI 设备. 接着系统找到 CXL ROOT,并创建一个 CXL DPORT,将其挂载 CXL ROOT 的 DPORT 集合里,并通过 XARRAY 将 “pci0000:0c” 与 DPORT 进行绑定,处理完毕之后 CXL ROOT 与 CXL HOST Bridge 建立了联系.
  • D: 系统准备好 CXL ROOT DECODER 之后,其调用 add_host_bridge_uport 再次遍历 ACPI 总线找到 ACPI0016 设备. 接下来为创建 CXL PORT 做准备,先找到 ACPI0016 对应的 PCIe 设备 “pci0000:0c”, 再次解析 CHBS 表,并将 “pci0000:0c” 插入到 “cxl_root_bus” 链表,该链表维护了系统所有的 CXL 总线.
  • E: 准备好数据之后,接着调用 devm_cxl_add_port 函数创建 CXL PORT,此时 CXL HOST Bridge 使用 “port1”,并建立以下联系:
    • parent: “port1” 的 parent 指向 “root0”, 这样与 CXL ROOT 建立映射
    • uport_dev: 指向了 “pci0000:0c”, 这样与 PCIe 总线 “0000:0c” 建立联系
    • host_bridge: 指向 “pci0000:0c”, 这样说明其是非 NVDIM 桥
    • id: 将 CXL HOST Bridge 的 CXL PORT ID 设为 1,那么 port1 就是 CXL HOST Bridge.
    • dev->bus: 指向 cxl_bus_type, 指明 port1 隶属于 CXL BUS
    • dev->type: 指向 cxl_port_type, 指明 port1 在 CXL BUS 上是一个 PORT
  • F: 创建了 CXL PORT 之后,将其作为 Linux 通用设备加入到系统,将 CXL PORT 的名字设置为 port1, 接着调用 device_add 函数加载 port1 对应的驱动,其对应的驱动是 “cxl_port”, 但由于其类型是 CXL_DEVICE_PORT,因此不会加载驱动. 最后对 port1 设备进行初始化,初始化完毕之后可以在系统 “/sys/bus/cxl/devices” 目录下看到 port1 目录.

CXL HOST BRIDGE WITH CEDT

CXL Early Discovery Table(CEDT) 是 CXL 架构中的一个重要数据结构,CEDT 是 ACPI 规范的一部分,为不同的厂商的 CXL 实现提供了统一的发现和初始化机制. CEDT 使操作系统能够在启动过程的早期阶段(即在解析完整的 ACPI 命名空间之前) 发现系统中的 CXL 主机桥及其寄存器位置, 操作系统可以提前获取必要的信息,从而对 CXL HOST Bridge 进行预初始化和发现其相关资源. CEDT 大致包括以下几个步骤:

  • 定位 CXL HOST BRIDGE: 提供系统中所有 CXL HOST BRIDGE 的位置信息
  • 寄存器映射: 提供 CXL 主机桥寄存器的物理地址映射信息
  • 预初始化支持: 为操作系统提供足够的信息,以便在启动早期对 CXL HOST BRIDGE 进行配置,例如设置 CXL.cache 和 CXL.mem 的初始状态
  • 兼容性支持: 确保符合 ACPI 规范的 CXL 系统能够被操作系统正确识别和初始化

CEDT 是一个 ACPI 表,其结构包括一个表头和一个或多个条目。以下是 CEDT 的基本结构:

  • Signature: 标识表的类型,固定为 “CEDT”
  • Length: 表的总长度
  • Revision: 表的版本号
  • Checksum: 用于验证表的完整性
  • OEM ID: 标识表的原始设备制造商(OEM)
  • OEM Table ID: OEM 定义的表标识符
  • OEM Revision: OEM 定义的版本号
  • Creator ID: 标识创建表的工具或厂商
  • Creator Revision: 创建工具的版本号

CEDT Structure 是一个通用的数据结构,用于描述 CEDT 表中各种条目的类型和格式. CEDT Structure 是 CEDT 表的核心组成部分,它定义了表中每个条目的基本布局,并为操作系统提供了一种标准化的方式来解析和处理这些条目. CEDT Structure 条目主要包括两种条目:

  • CHBS(CXL Host Bridge Structure)
    • Type: 0x01
    • Length: 根据 CHBS 的具体字段确定
    • Data: 包含 CHBS 的特定字段,例如 UID、Base Address、Host Bridge ID 等
  • CFMWS(CXL Fixed Memory Window Structure)
    • Type: 0x02
    • Length: 根据 CFMWS 的具体字段确定
    • Data: 包含 CFMWS 的特定字段,例如 Base Address、Window Size、Interleave Granularity

CHBS(CXL Host Bridge Structure)) 是 CEDT 中的一个关键数据结构,用于描述系统中每个 CXL HOST BRIDGE 的基本信息和配置. CHBS 的主要作用是为操作系统提供必要的信息,以便在系统启动的早期阶段发现和初始化 CXL 主桥. CHBS 是一个标准化的数据结构,其字段布局如下:

  • Type: 操作系统通过该字段识别条目的类型, 0 代表 CHBS
  • Length: 指示 CHBS 条目的总长度(以字节为单位). 操作系统通过该字段确定条目的边界
  • UID(Unique Identifier): 为每个 CXL 主桥分配一个唯一标识符. 操作系统通过 UID 区分不同的主桥
  • CXL Version: 指示主机桥支持的 CXL 规范版本,操作系统根据该字段确定如何配置和访问主桥
    • 00h: 表示主机桥符合 CXL 1.1 规范
    • 01h: 表示主机桥符合 CXL 2.0 规范。
  • Reserved: 用于未来扩展
  • Base: 操作系统通过该地址访问主机桥的寄存器,进行配置和控制
    • 如果 CXL Version = 0,表示 CXL 1.1 下游端口 RCRB(Root Complex Register Block)的基地址
    • 如果 CXL Version = 1,表示 CXL 2.0 CHBCR(CXL Host Bridge Configuration Register)的基地址
  • Length: 指示寄存器块的大小
    • 如果 CXL Version = 0,固定为 8 KB(2000h)
    • 如果 CXL Version = 1,固定为 64 KB(10000h)

通过这些信息,操作系统可以在启动早期对 CXL 主桥进行初始化和配置,从而确保 CXL 设备能够被正确识别和使用. 在支持多个 CXL 主机桥的系统中,每个主机桥都会有一个对应的 CHBS 条目. 操作系统通过解析这些条目,可以识别每个主机桥的位置和功能,并对其进行初始化.

CFMWS(CXL Fixed Memory Window Structure) 是 CEDT 中的一个关键数据结构,用于描述系统中预定义的固定内存窗口(Fixed Memory Window), 也就是 CXL 占用系统物理地址空间的区域. 这些内存窗口用于映射 CXL 设备的内存资源,并在系统启动时由 BIOS/UEFI 配置. CFMWS 的主要作用是为操作系统提供内存窗口的基地址、大小、交错方式(Interleave)以及访问限制等信息,从而确保操作系统能够正确映射和管理 CXL 设备的内存资源. CFMWS 的作用:

  • 定义内存窗口: 描述系统中预定义的内存窗口的基地址和大小
  • 支持内存交错: 定义内存窗口的交错方式(Interleave),允许多个 CXL 设备共享一个内存窗口
  • 访问限制: 定义内存窗口的访问限制和属性(例如是否支持设备一致性、是否支持持久内存等)
  • QoS 支持: 定义与内存窗口关联的 QoS(服务质量)限制组(QTG ID),用于管理内存访问的带宽和延迟

CFMWS 的结构分为多个字段,以下是每个字段的详细说明:

  • Type: 操作系统通过该字段识别条目的类型, 固定为 1,表示这是一个 CFMWS 条目
  • Record Length: 指示 CFMWS 条目的总长度
  • Base HPA: 内存窗口的基地址(Host Physical Address),必须是 256 MB 对齐的地址
  • Window Size: 内存窗口的总大小,必须是 NIW * 256 MB 的倍数
  • Encoded Number of Interleave Ways(ENIW): 编码的交错方式数量(ENIW),用于确定交错目标列表的长度
  • Interleave Arithmetic: 定义交错算法, 00h(标准模运算) AND 01h(模运算结合 XOR)
  • Host Bridge Interleave Granularity(HBIG): 定义每个交错目标解码的连续字节数
  • Window Restrictions: 定义内存窗口的限制属性
    • Bit[0]: 设备一致性(Device Coherent)
    • Bit[1]: 主机一致性(Host-only Coherent)
    • Bit[2]: 易失性内存(Volatile)
    • Bit[3]: 持久内存(Persistent)
    • Bit[4]: 固定设备配置(Fixed Device Configuration)
    • Bit[5]: 支持回写无效(Back-Invalidate)
  • QTG ID: 与内存窗口关联的 QoS 限制组 ID, 通过该字段管理内存访问的带宽和延迟
  • Interleave Target List: 包含所有参与交错的 CXL 主机桥或非 CXL 域的 UID

Linux 在启动过程中,cxl_acpi_probe 逻辑负责对 CXL HOST Bridge 的初始化,其中包含了从 CEDT 中获得 CHBS 和 CFMWS 信息,以此构建 CXL HOST Bridge 数据结构,其逻辑如下:

  • A: Linux 使用 bus_for_each_dev 遍历 ACPI 树找到 ACPI0016 节点,首先获得 CXL HOST Bridge 的 UUID,然后从该节点获得 CEDT 表,并从中读取 CHBS 表,核心逻辑位于 cxl_get_chbs_iter,其包括获得 CXL 的版本号,以及 CXL Host Bridge 的寄存器基地址. Linux 再次调用 acpi_pci_find_root 函数,结合获得 UUID 可以找到最终的 CXL HOST Bridge.
  • B: 在获得 CHBS 之后,其也就获得 CFMWS 数据结构信息,于是调用 acpi_table_parse_cedt 函数继续解析 CFMWS,其主要包括的 CXL 在系统物理地址空间映射的范围,调用 alloc_cxl_resource 函数分配对应的物理区域. 另外根据 CFMWS 的信息构建 ROOT Decoder,其主要负责 CXL 的 HPA 与 DPA 之间的映射关系.

通过上面的分析,开发者大概理清楚 CXL HOST BRIDGE 与 CEDT 之间的关系,有了 CEDT 提供的信息之后,开发者可以根据需求进行使用,接下来通过一个实践案例展示如何使用 CEDT,案例在 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 CEDT: CXL Early Discovery Table  --->

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

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

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

BiscuitOS 运行之后,直接运行 RunBiscuitOS.sh 脚本,脚本里包含了实践案例运行的所有命令,可以看到案例获得 CEDT 表之后,从中解析出 SIGNATURE 和 OEM ID 字段.

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

实践案例由一个内核模块构成,函数在 20 行调用 acpi_get_table 函数在 ACPI 中找到 CEDT 表,然后接下 CEDT 表. 以上只是一个简单的案例,开发者可以基于这个案例从 CEDT 中获得更多的信息,例如读取 CHBS 和 CFMWS 数据等.

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

CXL HOST BRIDGE WITH HDM

HDM 解码器(HDM Decoder) 是 CXL 设备中的一个硬件模块,负责将主机的物理地址(HPA)映射到设备内存的地址(Device Physical Address, DPA). 在 CXL 中可以同时存在多个 CXL HOST BRIDGE,那么 CXL HOST BRIDGE 如何找到 HPA 对应的 DPA 呢?

CXL 硬件提供了用于描述映射 HPA 区域的寄存器,包括 HAP 区域的基地址和长度寄存器,寄存器位于 PCIe 的配置空间,因此 CXL 硬件中属于 PCIe 设备的才具有该寄存器,由于 CXL HOST BRIDGE 并不是 PCIe 设备,因此 HDM Decoder 落到了 CXL Root Complex 的 CXL RootPort 设备上. 在一个系统里,可能同时存在多个 CXL HOST BRIDGE,系统针对一个映射 CXL 的 HPA 时,如何找到对应的 DPA 呢? 接下来进行详细分析:

当系统访问了 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.

CXL HOST BRIDGE 在软件层面还维护了多个 CXL HDM DECODER,其服务于 CXL REGION 的地址编码,当创建一个 CXL REGION 之后,CXL HOST BRIDGE 会为其提供一个 HDM DECODER 映射地址转译,只不过这些 HDM DECODER 是软件层面的,以便开发者可以从 “/sys/bus/cxl/devices/port1/decoder1.X” 接口更好了解 HPA 与 DPA 映射关系,每个目录下节点的含义如下:

  • devtype: 通常为 cxl_decoder,表示这是一个 CXL 解码器设备, 用于标识设备的类型
  • subsystem: 表示该设备属于平台设备子系统, 标识设备在设备树中的位置
  • interleave_granularity: 表示内存交错的粒度,通常以字节为单位(例如 256 表示 256 字节)
  • interleave_ways: 表示参与内存交错的设备数量(例如 2 表示 2 个设备交错)
  • target_list: 列出参与内存交错的 CXL 设备的标识符(例如 0000:01:00.0 0000:02:00.0)
  • target_type: 表示目标设备的类型(例如 cxl_mem 表示 CXL 内存设备)
  • uevent: 用于触发用户空间事件的文件
  • locked: 表示解码器是否被锁定(0 表示未锁定,1 表示锁定), 防止解码器的配置被意外修改
  • region: 指向与该解码器关联的内存区域的 sysfs 路径, 标识解码器管理的内存区域
  • size: 表示解码器管理的内存区域的大小,通常以字节为单位
  • start: 表示解码器管理的内存区域的起始地址,通常以字节为单位
  • modalias: 设备的模块别名,通常用于自动加载相应的内核模块, 帮助系统自动加载与该设备匹配的内核驱动

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

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

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 CXL HOST BRIDGE  --->

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

# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-BUS-TRAVERSE-HOST-BRIDGE-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 HOST BRIDGE “pci0000:0c”. 接下来分析源码:

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

实践案例使用一个内核模块,模块首先在 40 行调用 acpi_bus_for_each_dev 函数遍历 ACPI 树,每当遍历一个节点时调用函数 match_acpi_dev,该函数首先在 23 行对遍历到的 ACPI 节点进行过滤,找到 “ACPI0016” ACPI 节点. 当找到 ACPI 节点之后,其在 27 行使用 acpi_pci_find_root 函数基于 ACPI 节点提供的 UUID 找到对应的 PCIE 根节点,然后在 29 行从 PCIE 根节点获得对应的 PCIE HOST BRIDGE,此时也就是 CXL HOST BRIDGE. 案例通过简明的代码展示了内核查找 CXL HOST BRIDGE 的整个逻辑.

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