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

在支持 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 Root,例如上图可以从 “/sys/bus/cxl/devices/root0” 里获得 CXL Root 相关信息,另外为了更好的让开发者对 CXL Root 有更好的了解,接下来结合 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_root” 描述一条独立 CXL 总线的根节点,Linux 以该数据结构为起点维护 CXL 框架,其 “struct cxl_port” 成员 port 描述了 CXL Root 与其他组件之间的关系,”struct cxl_root_ops” 成员 ops 则提供了 CXL ROOT 的特殊操作.

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

Linux CXL 框架将 CXL 硬件抽象为上图的逻辑拓扑,其中 CXL ROOT 位于顶层的位置,可以从图中看到 CXL ROOT 名为 “root0”, Linux 在启动过程中会从 ACPI0017 里获得 CXL BUS 相关信息,并基于该信息构建 CXL 框架,CXL 框架使用 CXL ROOT 抽象描述一条独立的 CXL BUS,换句话如何系统中存在两条独立的 CXL BUS,那么 Linux CXL 框架会使用两个 CXL ROOT 分别描述两条 CXL BUS. 在 CXL 框架里 CXL ROOT 与其他组件之间的联系如下:

  • root.port->uport_dev: 指向 ACPI0017 ACPI 设备,以此指明该 CXL BUS 的根节点
  • root.port->dev.parent: 指向 ACPI0017 ACPI 设备,表示在 DEVICE 逻辑上父节点
  • root.port->dport: CXL ROOT BUS,BUS 上可以接 CXL HOST Bridge
  • root.port->dport[CXL HOST BRIDGE]: 指向该 CXL HOST Bridge PCIe 设备
  • port1: 表示 CXL HOST Bridge/CXL ROOT Complex
  • port1.parent_dport: 指向其所属的 CXL HOST Bridge
  • port1.dev->parent: 指向 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 ROOT 部分,其位于拓扑结构的顶端,其各字段的含义如下:

  • bus: 该字段描述 CXL BUS 的编号,在有的系统中可能同时存在多条 CXL BUS
  • provider: 该字段描述 CXL 拓扑信息来源,在 Intel 平台有 ACPI 提供
  • nr_dports: 该字段描述 CXL ROOT 有几个 CXL HOST Bridge
  • dports: 该字段描述具体的 CXL DPORT 信息
    • dport: 该字段描述 CXL DPORT 指向的 CXL HOST Bridge
    • alias: 该字段描述了 CXL DPORT 的别名,这里指向 ACPI0016:00
    • id: 该字段为 DPORT 提供唯一的 ID

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

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

  • decoder0.0: CXL ROOT 使用的根 HDM 解码器,负责地址转译
  • decoders_committed: 指示解码器是否已成功配置
  • devtype: 指示 CXL ROOT 的类型
  • dport12: 指向 CXL ROOT 的 CXL HOST Bridge
  • modalias: 设备的别名信息或设备 ID
  • nvdimm-bridge0: 指向支持的 PMEM HOST Bridge
  • port1: 指向 CXL HOST Bridge/CXL ROOT Complex
  • subsystem: 设备所属子系统的信息
  • uevent: 提供设备的事件通知
  • uport: 指向其上游端口,这里指向 ACPI0017

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

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

  • PORT ACPI0017: 系统启动过程中调用 cxl_acpi_init 函数,其传入 ACPI 设备 “ACPI0017”, 其包含了 CXL 硬件拓扑信息,其对 ACPI 设备使能之后,调用 “devm_cxl_add_root” 函数初始化 CXL ROOT.
  • CREATE NEW PORT: 由于 CXL ROOT 本质上是一个特殊的 CXL PORT,因此核心还是调用 cxl_port_alloc 创建一个新的 CXL PORT. 由于 CXL ROOT 不存在 CXL PARENT DPORT,那么直接调用 kzalloc 分配一段内存给 cxl_root, 然后对 cxl_root 内部包含的 port 进行初始化.
  • BIND TO ACPI.CXL: 在初始化 CXL ROOT 内部的 CXL PORT 时,先分配一个唯一的 ID,然后将 CXL PORT 通过 uport_dev 指向 ACPI0017,然后从 CXL PORT 的 DEVICE 链路,将其 PARENT 指向 ACPI0017,这样从通用的 DEVICE 链路和私有 CXL BUS 链路上都与 ACPI0017 进行深度绑定.
  • EMULATE DPORT TO HOST BRIDGE: 由于 CXL ROOT 本质上是 CXL PORT,那么其维护了三颗 XARRAY,其用于维护 PCIe BUS 的关系,其中 “port->dport” 就用来模拟 CXL ROOT 与 CXL HOST Bridge 之间的上下级连接关系. 由于 CXL ROOT 只有 DPORT 没有 ENDPOINT 和 REGION,因此另外两个 XARRAY 不使用.
  • ADD TO BUS CXL BUS: CXL ROOT 的 CXL PORT 初始化完毕之后,将其 DEVICE/BUS 设置为 cxl_bus_type, 同理将 DEVICE/TYPE 设置为 cxl_port_type, 那么在遍历 CXL BUS 时,可以将 CXL ROOT 从 CXL BUS 中隔离出来.
  • BUILD CXL ROOT root0: 此时为 CXL ROOT 设置名字为 “root0”, 并将 CXL ROOT 设备加入到 SYSFS 架构里,最后为其在 SYSFS 上创建 uport 和 parent_dport 节点.
  • CXL ROOT OPS: 此时为 CXL ROOT 提供 cxl_root_ops 特殊处理函数,其内部包含了 “qos_class” 的处理方法.
CXL ROOT Practice

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

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 FRAME: CXL ROOT  --->

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

# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-FRAME-CXL-ROOT-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”.

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

硬件拓扑分析完毕之后,插入模块,模块插入之后,可以看到找到了 CXL ROOT,然后输出了 CXL ROOT 的 Uport Device 以及 Parent Device 的信息,此时可以看到 CXL ROOT 的上游是 ACPI0017. 接下来分析源码:

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

模块插入时会调用 “BiscuitOS_init” 函数,函数会在 38 行调用 bus_find_device() 函数遍历 “cxl_bus_type” CXL BUS 软总线,每遍历 CXL BUS 上的一个设备时,会调用 traverse_cxl_bus 函数. 在该函数里,22 行通过名字找到 CXL ROOT,然后在 26 行通过 dev 找到对应的 CXL PORT,这里调用 container_of 是因此 struct cxl_port 数据结构的 dev 成员是实例而不是指针,因此可以使用这个函数获得 CXL PORT,同理 CXL ROOT 的 port 是实例而不是指针,因此可以通过 CXL PORT 获得对应的 CXL ROOT. 因此可以通过上面的代码逻辑获得 CXL ROOT.

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