在支持 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 RootPort,例如上图可以从 “/sys/bus/cxl/devices/root0/port1” 里获得 CXL RootPort 相关信息,另外为了更好的让开发者对 CXL RootPort 有更好的了解,接下来结合 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_dport” 描述一个 CXL RootPort, CXL RootPort 直接挂载在 CXL Host Bridge 下,其向上接 CXL HOST Bridge,向下则可以接 CXL SWITCH 或者 CXL RCiEP(Root Complex Integrated Endpoint). 在 CXL 架构里,”struct cxl_dport” 数据结构中各成员的含义:
- dport_dev: 指向 PCIe RootComplex 对应的 CXL RootPort 设备
- reg_map: 包含组件和 RAS 相关的寄存器映射.
- port_id: CXL RootPort ID 编号
- rcrb: CXL RootComplex Register Block 数据
- rch: 下游是 RCiEP 还是 VCH(Virtual CXL Swith)
- port: 上游所属的 CXL Host Bridge.
- regs: 下游端口解析后的寄存器块
- coord: 访问坐标,包含带宽和延迟性能属性
- link_latency: 计算出的 PCIe 下游延迟
Linux CXL 框架将 CXL 硬件抽象为上图的逻辑拓扑,其中 CXL RootPort 位于 CXL Host Bridge 构成的 “CXL RootComplex” 里,由 CXL Host Bridge 的 dports XARRAY 进行维护,以此形成一个 CXL Host Bridge 下多个 CXL RootPort,而一个 CXL RootPort 只属于一个 CXL Host Bridge 的逻辑关系. 在 CXL 框架里 CXL RootPort 与其他组件之间的连续如下:
- dport_dev: 指向 PCIe 总线上 CXL RootComplex 的 RootPort.
- dport->port: 指向所属的 CXL PORT,这里对应 CXL HOST Bridge 的 Port1
- port1->dports: CXL RootComplex 维护的所有 CXL RootPort
- port1: 表示 CXL HOST Bridge/CXL ROOT Complex
- port2.parent_dport: 指向 CXL SWITCH 挂接的 CXL RootPort
Linux CXL 框架是尽可能将 CXL 硬件抽象为软件可以维护和管理的数据结构,从图中可以看出 Linux 使用一个 CXL DPORT 对应 CXL RootPort,然后 PORT1 的 DPORT 维护了多个 DPORT,对应 CXL HOST BRIDGE 维护多个 CXL ROOTPORT 一对多映射关系,另外 CXL DPORT 的 PORT 指向 PORT1,描述了 CXL RootPort 与 CXL HOST BRIDGE 的一一映射关系, PORT2 的 PDPD 对应 CXL SWITCH 上游端口与 CXL RootPort 的一一对应关系.
当 Linux 构建完 CXL 框架之后,可以在用户态使用 “cxl list -v” 命令查看 CXL 框架拓扑结构,对于 CXL RootPort 部分,其位于拓扑结构的 Port1 内部,其各字段的含义如下:
- port: port1 说明 CXL RootPort 位于 CXL HOST BRIDGE 内部
- host: 表明 CXL HOST BRIDGE 的 UUID
- depth: 表明 CXL HOST BRIDGE 在拓扑的深度
- nr_dports: 表明 CXL HOST BRIDGE 具有 CXL RootPort 的数量
- dports: 由于描述 CXL HOST BRIDGE 维护的所有 CXL RootPort
- dport: 描述 CXL RootPort 的 BDF
- id: 描述 CXL RootPort 的 ID
另外, CXL 框架构建完毕之后,可以在 “/sys/bus/cxl” 目录下查看 CXL 框架信息,其中 “devices/port1/dportX” 目录下提供给用户空间的接口,以及 CXL RootPort 与其他组件之间的关系,各节点和目录的含义如下:
- 0000:0c:00.0:pcie001: 表示该 PCIe Root Port 的子设备或功能
- 0000:0c:00.0:pcie010: 表示该 PCIe Root Port 的子设备或功能
- 0000:0d:00.0: 表示连接到该 Root Port 的下游 PCIe 设备
- ari_enabled: ARI(Alternative Routing-ID Interpretation)是否启用, ARI 是一种 PCIe 特性,用于扩展设备的 Function Number
- broken_parity_status: 设备的奇偶校验状态是否损坏
- class: 设备的类别代码, 用于标识设备的类型
- config: 设备的配置空间, 用于读取或修改设备的配置寄存器
- consistent_dma_mask_bits: 设备支持的 DMA 掩码位数, 用于配置设备的 DMA 能力
- current_link_speed: 当前链路速度, 用于监控链路的性能
- current_link_width: 当前链路宽度, 用于监控链路的性能
- d3cold_allowed: 是否允许设备进入 D3cold 状态, 用于管理设备的电源状态
- device: 设备的设备 ID, 用于标识设备的型号
- dma_mask_bits: 设备的 DMA 掩码位数, 用于配置设备的 DMA 能力
- driver: 设备绑定的驱动程序
- driver_override: 驱动程序的覆盖设置
- enable: 设备的启用状态
- irq: 设备的中断号
- link: 设备的链路状态
- local_cpulist: 设备关联的 CPU 列表
- local_cpus: 设备关联的 CPU 掩码
- max_link_speed: 最大链路速度
- max_link_width: 最大链路宽度
- modalias: 设备模块别名
- msi_bus: 设备是否支持 MSI(Message Signaled Interrupts)
- msi_irqs: 设备的 MSI 中断号
- numa_node: 设备所属的 NUMA 节点
- pci_bus: 设备所属的 PCI 总线
- power: 设备的电源管理目录
- power_state: 设备的电源状态
- remove: 移除设备
- rescan: 重新扫描设备
- resource: 设备的资源分配
- resource0: 设备的资源 0
- revision: 设备的修订版本。
- secondary_bus_number: 设备的次级总线号, 表示设备下游的总线号
- subordinate_bus_number: 设备的从属总线号, 表示设备下游的总线范围
- subsystem: 设备所属的子系统
- subsystem_device: 子系统的设备 ID
- subsystem_vendor: 子系统的厂商 ID
- uevent: 用户事件, 通常用于通知用户空间设备的状态变化
- vendor: 设备的厂商 ID
在 Linux 内核启动过程中,CXL RootPort 初始化在 CXL 子系统遍历 CXL HOST BRIDGE 所有 DPORT 时完成,其初始化如图分成以下几个核心逻辑:
- RESCAN CXL HOST BRIDGE: CXL 子系统已经完成基本的初始化,创建了 CXL ROOT 和 CXL HOST Bridge 组件,接下来其调用 cxl_bus_rescan 函数重新遍历位于 CXL BUS 上的设备,其中会遍历完 CXL HOST BRIDGE 之后,接下来遍历 CXL HOST BRIDGE 维护的 CXL RootPort,因此调用函数 devm_cxl_port_enumerate_dports,由于 CXL HOST BRIDGE 是一条 PCIe 总线,另外 CXL RootPort 也是 PCIe 设备,因此最终调用到 pci_walk_bus 函数进行遍历.
- PARSE CXL ROOT PORT: 当遍历 CXL RootPort 对应的 PCIe 设备之后,从 CXL RootPort 配置空间的 DVSEC 寄存器位置,然后从中获得组件寄存器(Component Register) 的信息,另外从 PCIe Capability 区域获得 CXL RootPort 的连接状态和端口 ID
- BUILD CXL DPORT: 当获得足够的信息之后,系统为 CXL RootPort 创建 CXL DPORT,其调用 devm_cxl_add_dport 函数实现 CXL DPORT 的创建,并将 DPORT 挂接到 CXL HOST BRIDGE 的 “Port1” 上.
CXL ROOTPORT 本身是一个 PCIe 设备,因此可以使用 “lspci-common -vt” 查看 CXL 硬件拓扑结构,然后可以找到两个 CXL RootPort,接着使用 “lspci-common -s BDF” 查看 CXL RootPort 的配置空间. 从该配置空间可以获得 CXL RootPort 很多有用信息,例如其 BAR 映射的 MMIO 范围,以及连接状态和端口号等信息.
CXL 在 CXL RootPort 的 PCI Express Extended Capability 区域提供了 DVSEC(Designated Vendor-Specific), 其是 PCIe 和 CXL 规范中定义的一种机制,用于允许厂商在标准 PCIe/CXL 配置空间中添加厂商特定的扩展功能. DVSEC 提供了一种标准化的方式来扩展设备的配置空间,同时保持与标准规范的兼容性.
对于 CXL RootPort,PCIe DVSEC 主要提供了 Register Locator DVSEC 配置,它是一个关键的结构,用于定位和描述 CXL 设备中的寄存器块(Register Blocks). 它的主要作用是为操作系统或系统软件提供一种标准化的方式来发现和访问 CXL 设备中的特定寄存器区域.
通过 Register Locator DVSEC 可以获得 CXL Subsystem Component Register 在 CXL RootPort Bar 空间的位置,上图是 CXL 标志的组件寄存器布局, CXL RootPort 从这些寄存器里获得相应的信息,以及控制相应的行为. 接下来通过一个实践案例带各位开发者介绍 CXL RootPort 的 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: Register Locator(REGLOC) --->
# 配置完毕保存,然后进行部署
make
# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-DVSEC-REGLOC-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build
当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本,脚本里包含了实践案例运行的所有命令,可以看出案例解析出 CXL RootPort 对应的 DVSEC 所在 BAR 位置,接下来分析源码:
实践案例由一个内核模块构成,其在 77 行调用 bus_for_each_dev 函数遍历 PCIE 总线,然后通过 BiscuitOS_match_pci 过滤函数找到 CXL RootPort 对应的 PCIe 设备, 函数接着在 32 行调用 pci_find_dvsec_capability 函数从 PCIe RootPort 的 “PCI Express Extended Capability” 里找到 “CXL Register Locator DVSEC”. 接着在 38 行调用 pci_read_config_dword 函数从该 DVSEC 中读取 PCI_DVSEC_HEADER1 寄存器.
从 PCI_DVSEC_HEADER1 寄存器里可以获得整个 REGBLOCK 的数量,接着函数在 46 行遍历里所有 REGBLOCK.
函数依次从 Register Block X 开始读取寄存器的值,模块接着在 51-52 行获得 Register Block X 的 Register Offset Low 和 Register Offset High 寄存器,并在 55 行获得 Register Offset Low 的 [0:2] 字段获得寄存器所在 BAR 的位置,然后从 [16:31] 字段以及 Register OFfset High 寄存器获得寄存器块在 BAR 里面的偏移. 最后 55 行从 [8:15] 字段可以获得寄存器块的类型.
HDM 解码器(HDM Decoder) 是 CXL 设备中的一个硬件模块,负责将主机的物理地址(HPA)映射到设备内存的地址(Device Physical Address, DPA). 在 CXL 中可以同时存在多个 CXL RootPort,那么 CXL 如何找到 HPA 对应的 DPA 呢?
CXL 硬件提供了用于描述映射 HPA 区域的寄存器,包括 HAP 区域的基地址和长度寄存器,寄存器位于 PCIe 的配置空间,因此 CXL 硬件中属于 PCIe 设备的才具有该寄存器,由于 CXL RootPort 是 PCIe 设备,因此 HDM Decoder 落到了 CXL RootPort 的配置空间
在 CXL RootPort 的 PCI Express Extended Capability 区域存在 Register Locator DVSEC, 根据其可以获得 CXL RootPort Component Registers 在其 BAR 空间的位置.
在获得 Component Registers 之后,找到 CXL.cache and CXL.mem Register 区域,依次遍历所有的 CXL Capability 条目,然后找到 CXL HDM Decoder Capability 条目.
CXL HDM Decoder Capability 包含了 CXL RootPort 将所有的 HDM Decoder,每个 HDM Decoder 包含基础的 4 个寄存器,分别是 CXL HDM Decoder X Base Low Register、CXL HDM Decoder X Base High Register、CXL HDM Decoder X Size Low Register 和 CXL 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: HOST BRIDGE DECODER --->
# 配置完毕保存,然后进行部署
make
# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-HDM-HOST-BRIDGE-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build
当 BiscuitOS 运行之后,直接运行 RunBiscuitOS.sh 脚本,脚本包含了实践案例运行的所有命令,案例遍历了 CXL HOST Bridge 的所有 Decoder,这些 Decoder 来自 CXL RootPort, 其从 CXL RootPort HDM Decoder 里读出了 HPA 的范围. 接下来分析一下源码:
实践案例由一个内核模块构成,其在 54 行调用 bus_find_device 遍历 CXL 总线,然后通过 traverse_cxl_port 函数遍历找到 CXL Port1,也就是 CXL Host Bridge. 接着在 31 行从 Port1 里获得 CXL HDM 数据结构,并在 34 行使用 FOR 循环遍历所有的 CXL HDM Decoder,每遍历一个 HDM Decoder 时,从 CXL HDM Decoder X Base Low Register、CXL HDM Decoder X Base High Register、CXL HDM Decoder X Size Low Register 和 CXL HDM Decoder X Size High Register 寄存器中获得 HPA 映射的值,这些寄存器来自 CXL RootPort.
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 ROOT PORT --->
# 配置完毕保存,然后进行部署
make
# 切换到实践案例所在目录
cd output/linux-6.10-x86_64/package/BiscuitOS-CXL-BUS-TRAVERSE-ROOTPORT-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 的所有 RootPort,并将其 BDF 打印出来,接着使用 “lspci-common -vt” 命令查看 PCIe 的硬件拓扑,可以看到 PCI0000C 总线下直接挂接了两个 RootPort,分别是 “0000:0c:00.0” 和 “0000:0c:01.0”. 接下来分析源码:
实践案例由一个内核模块构成,其在 41 行调用 bus_find_device 函数遍历 CXL BUS 上的所有设备,每遍历一个设备就会调用 traverse_cxl_dport 函数. 在 traverse_cxl_dport 函数里,函数通过 25-30 行逻辑找到了 CXL HOST BRIDGE 对应的 PORT1,在 32 行直接使用 xa_for_each 函数遍历所有的 CXL DPORT,CXL DPORT 的 dport_dev 指向 CXL RootPort PCIe 设备.