Linux CXL 平台配置:BIOS/EFI 期望、UEFI_MEMORY_SP 与解码器编程规范
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文基于 Linux 内核官方文档 BIOS/EFI Configuration,系统讲解 CXL(Compute Express Link)平台在 BIOS/EFI 阶段需要完成哪些静态配置、Linux 对固件的硬性期望是什么,以及内存区域对齐、内存空洞(Memory Hole)与 HDM 解码器(HDM Decoder)编程中的常见陷阱。读完本文,你将掌握:如何使用uefisettings检查并设置EFI_MEMORY_SP位、为什么 CXL 内存区域应做 2GB 对齐、遇到内存空洞时如何用多个解码器与多个 CFMWS 条目正确描述,以及 Linux 源码中这些期望的真实落点,适用于 CXL 内存设备集成工程师与平台固件开发者。
启动阶段的职责划分:固件完成静态配置,Linux 完成运行时配置
CXL 设备的配置被人为地分成两个阶段:BIOS/EFI 负责静态信息的描述,Linux 内核负责运行时的设备配置。在启动阶段,大致的流程如下(原文档给出的高层视图):
- Bootloader 启动 BIOS/EFI;
- BIOS/EFI 执行早期设备探测(early device probe),确定静态配置;
- BIOS/EFI 生成 ACPI 表(如 CEDT、SRAT、HMAT 等),向操作系统描述静态配置;
- BIOS/EFI 建立系统内存映射(EFI Memory Map、x86 上的 E820 等);
- BIOS/EFI 调用
start_kernel,进入 Linux 早期启动(Early Boot)流程。
其中绝大部分工作集中在ACPI 表的生成和静态内存映射的构建上。这些表的字段级说明见仓库内的 ACPI Tables 文档 及其子文档 CEDT、SRAT。
原文档特别提示:平台厂商应仔细阅读该节内容,因为其中包含关于物理内存区域大小与对齐、内存空洞、HDM 交织(interleave)的建议,以及 Linux 对使用这些特性的 HDM 解码器的具体期望。
Linux 对 BIOS/EFI 软件的核心期望
内核文档明确列出了 Linux 对固件的边界要求:
- 必须提供:BIOS/EFI 必须构建足够的 ACPI 表(CEDT、SRAT、HMAT 等)和平台特定配置(HPA 空间、host-bridge 交织配置等),使 Linux CXL 驱动能够在运行时自行配置 CXL 拓扑(fabric)中的设备。
- 不要求:HDM 解码器与交换端口(switch port)的编程不需要由固件完成,可以推迟给 CXL 驱动,并依据管理员策略(例如 udev 规则)在用户态触发配置。
- 应避免:个别平台可能因为硬件 quirk 需要预先编程并锁定 HDM 解码器(典型例子即文档提到的Zen5 地址转换问题)。这不是正常的、"被期望的"配置路径,应尽可能避免。从源码看,这一 quirk 在 Linux 中的对应物是 drivers/cxl/cxl.h 中的区域标志
CXL_REGION_F_NORMALIZED_ADDRESSING,其注释明确写道:"Indicate Normalized Addressing. Use it to disable SPA conversion if HPA != SPA and an address translation callback handler does not exist.Flag is needed by AMD Zen5 platforms.",即当 HPA 与 SPA 不一致且没有地址转换回调时,用该标志禁用 SPA 转换。 - 平台厂商义务:如果平台希望预先配置这些资源、以便在不带 CXL 驱动支持的情况下也能把内存带起来,厂商应当用现有 CXL 驱动测试自己的配置,并在需要 RAS 等特性时为其"自动配置"提供驱动支持。
- 副作用警告:需要启动时编程和/或锁定 CXL fabric 组件的平台,可能会使设备热插拔(hot-plug)等特性无法工作。
换句话说,Linux 的设计哲学是:固件只描述"有什么",驱动决定"怎么用"。任何绕过这一分工的静态锁定方案,都可能直接牺牲热插拔、运行时重配置等能力。
使用 uefisettings 检查与配置 UEFI 设置
如果平台支持 UEFI 变量读取接口,可以用uefisettings命令读写 EFI 设置。两个关键前提:
- 修改在下一次重启后才生效;
- kexec 不构成一次足够(sufficient)的重启,kexec 后修改不会生效。
EFI_MEMORY_SP 位:CXL 内存归属的关键开关
原文档特别指出一个重要的配置项:EFI_MEMORY_SP(Specific Purpose)位。
- 当该位被置位时,它告诉 Linux:将该内存区域的管理权交给驱动(在 CXL 场景下即 CXL 驱动),而不是直接交给页分配器;
- 否则,该内存会被视为"普通内存",并在
__init阶段直接暴露给页分配器(page allocator)。
源码印证:EFI_MEMORY_SP 如何映射到 E820
在 x86 内核中,这一机制的实现位于 arch/x86/platform/efi/efi.c 的 EFI 内存映射导入逻辑:遍历 EFI 内存描述符时,若某段EFI_CONVENTIONAL_MEMORY带有EFI_MEMORY_SP属性且软保留(soft reserve)机制已启用,则把它标记为E820_TYPE_SOFT_RESERVED,而不是E820_TYPE_RAM:
for_each_efi_memory_desc(md) { ... switch (md->type) { case EFI_LOADER_CODE: case EFI_LOADER_DATA: case EFI_BOOT_SERVICES_CODE: case EFI_BOOT_SERVICES_DATA: case EFI_CONVENTIONAL_MEMORY: if (efi_soft_reserve_enabled() && (md->attribute & EFI_MEMORY_SP)) e820_type = E820_TYPE_SOFT_RESERVED; else if (md->attribute & EFI_MEMORY_WB) e820_type = E820_TYPE_RAM; ...其中EFI_MEMORY_SP的定义在 arch/x86/boot/compressed/efi.h(0x0000000000040000,注释为 "soft reserved")。内核源码注释同时说明了逃生开关:如果平台固件错误地设置了该位,可以用efi=nosoftreserve内核参数关闭内核对EFI_MEMORY_SP的处理,让内存回到普通 RAM 路径。这是排查"CXL 内存为何没被 CXL 驱动接管"类问题的第一条线索。
uefisettings 使用示例
查看平台身份(BIOS 厂商、版本、产品名称等):
uefisettings identify bios_vendor: xxx bios_version: xxx bios_release: xxx bios_date: xxx product_name: xxx product_family: xxx product_version: xxx在部分 AMD 平台上,EFI_MEMORY_SP位是通过名为CXL Memory Attribute的固件字段设置的;你的平台可能使用其他名称。查询示例:
uefisettings get "CXL Memory Attribute" selector: xxx ... question: Question { name: "CXL Memory Attribute", answer: "Enabled", ... }物理内存映射:区域大小与对齐要求
内存块必须"统一大小且统一对齐"
截至Linux v6.14,热插拔内存(hotplug memory)子系统要求内存区域在大小和对齐上保持统一。CXL 规范允许小至 256MB 的内存区域,但热插拔内存支持的块大小与对齐是架构定义的:
- Linux 内存块(memory block)可以小至128MB,且按 2 的幂次增长;
- ARM:默认块大小和对齐为 128MB 或 256MB;
- x86:默认块大小为 256MB,并且随着系统容量增长(最高到 64GB 规模)增长到2GB。
文档据此给出明确的工程建议:
为了获得跨内核版本的最佳支持,平台厂商应将 CXL 内存放置在 2GB 对齐的基地址处,且区域大小也应 2GB 对齐。这同时有助于避免创建数以千计的内存设备(每个 block 一个 memory device)。
内存空洞(Memory Holes):最易踩坑的布局
内存映射中的空洞非常棘手。原文档用一个 4GB 设备为例:设备基地址0x100000000,但中间夹着一个空洞,内存图如下:
--------------------- | 0x100000000 | | CXL | | 0x1BFFFFFFF | --------------------- | 0x1C0000000 | | MEMORY HOLE | | 0x1FFFFFFFF | --------------------- | 0x200000000 | | CXL CONT. | | 0x23FFFFFFF | ---------------------这里存在两个必须同时考虑的问题:
- 解码器编程(decoder programming);
- 内存块对齐(memory block alignment)。
如果目标架构要求 2GB 统一大小且对齐的内存块,那么截至 v6.14,Linux 能够映射的容量只有0x100000000-0x180000000这一段(因为空洞截断了第一个 2GB 对齐窗口);其余容量会成为"搁浅"(stranded)容量——因为它们不构成 2GB 对齐的长度,无法被热插拔内存系统接纳。
如果架构与内存配置允许1GB 内存块,则该内存图是被支持的。此时正确的做法是把空洞两侧分别描述为 CEDT 中多个 CFMWS条目,并配备与之匹配的解码器。
原文档给出的通用规则:
- 可以用(并且应该用)多个解码器来管理这种内存空洞,但空洞切出的每一段都应按合理的块大小对齐(对齐粒度越大越好);
- 如果你打算在内存映射中保留空洞,就应预期:每段连续的 host 物理内存对应一个解码器;
- 截至 v6.14,Linux 已经支持由单个 HDM 解码器描述、但被内存空洞分隔开的多段物理内存区域的热插拔。
解码器编程:翻译点、交织与多介质
如果 BIOS/EFI 打算把解码器静态编程好,原文档强调:有几条建议并不是规范(specification)的强制要求,但 Linux 不保证在这些建议之外提供支持。
翻译点(Translation Point)
按照 CXL 规范,唯一负责把 Host Physical Address(HPA)翻译成 Device Physical Address(DPA)的解码器是端点解码器(Endpoint Decoder)。fabric 中其余所有解码器(root decoder、host bridge 解码器、交换端口解码器)都只负责路由访问而不翻译地址。
文档引用了 CXL Specification 3.1 的 8.2.4.20 节("CXL HDM Decoder Capability Structure")及其两条实现说明(Host Bridge/Upstream Switch Port Decoder Flow、Device Decoder Logic)作为依据。基于此,Linux 做一个很强的假设:CPU 与端点之间的所有解码器,其编程的地址范围都必须是父解码器范围的子集。
历史教训:由于架构、ACPI、PCI 与 CXL 规范在"责任交接"上存在模糊地带,一些早期采纳平台曾在内存控制器或 host bridge 处做地址翻译。这种配置需要平台特定的驱动扩展,虽被支持但不获官方背书。文档的措辞非常直接:强烈建议不要这样做;否则平台需要自行实现驱动支持。Linux 源码中对这一类平台的兼容点即上文提到的CXL_REGION_F_NORMALIZED_ADDRESSING(见 drivers/cxl/cxl.h)。
交织(Interleave)与配置灵活性
CEDT 中 CFMWS(CXL Fixed Memory Window Structure)条目的组织方式决定了用户态可用的解码器编程自由度:
- 跨 host-bridge 交织:必须在 CEDT 中给出一个 CFMWS 条目,并列出参与交织的目标 host bridge(每个 host bridge 背后可能挂多个设备);
- host-bridge 内部交织:如果该 CFMWS 覆盖了该 host bridge 背后设备的全部容量,则只需一个条目;
- 希望给用户更多自由度(允许用户编程 root 以下的解码器)时,可以考虑在 CEDT 中提供多个 CFMWS 条目,例如:
- 一个覆盖所有可交织 host bridge 的 CFMWS 条目;
- 一个覆盖单个 host bridge 上所有设备的 CFMWS 条目;
- 为每个设备各一个 CFMWS 条目。
平台可以三者全加,也可以根据 BIOS 设置项切换模式。对每个 CFMWS 条目,Linux 期望在 SRAT 中找到对应内存区域的描述,以便确定早期启动/init 阶段应预留多少NUMA node。
这里有一个版本敏感的注意事项:截至 v6.14,即使找不到匹配的 SRAT 条目,Linux 也会为每个 CEDT CFMWS 条目创建一个 NUMA node;但这一行为未来不保证,平台应当避免依赖这种"无 SRAT 对应"的配置。从源码结构看,这一行为对应 drivers/cxl/acpi.c 中解析 CFMWS 时的 NUMA 关联逻辑(如通过phys_to_target_node(cfmws->base_hpa)将窗口基地址映射到目标 node,并打印 "decode range: node: %d range ..." 调试信息)。
解码器层面的内存空洞处理
如果 CXL 内存之间夹杂空洞,建议用多个解码器分别覆盖各段区域,而不是让解码器覆盖整个范围、指望 Linux 来处理重叠。对上文示例(单个设备直连 host bridge),Linux 期望的解码器编程方式是:
----------------------- ----------------------- | root-decoder-0 | | root-decoder-1 | | base: 0x100000000 | | base: 0x200000000 | | size: 0xC0000000 | | size: 0x40000000 | ----------------------- ----------------------- | | ----------------------- ----------------------- | HB-decoder-0 | | HB-decoder-1 | | base: 0x100000000 | | base: 0x200000000 | | size: 0xC0000000 | | size: 0x40000000 | ----------------------- ----------------------- | | ----------------------- ----------------------- | ep-decoder-0 | | ep-decoder-1 | | base: 0x100000000 | | base: 0x200000000 | | size: 0xC0000000 | | size: 0x40000000 | ----------------------- -----------------------即:root、host bridge、endpoint 三层解码器每一层都按空洞切成两个解码器,且 CEDT 中相应地用两个 CFMWS 条目描述这两个 root 解码器。文档同时声明:Linux 对"奇怪的内存空洞情况"不提供任何支持保证。
多介质设备(Multi-Media Devices)
CEDT 的 CFMWS 字段中有特殊的限制位(restriction bits),描述该内存区域允许volatile(易失性)或 persistent(持久性)内存(或两者皆可)。若平台意图支持以下场景之一:
- 单一设备携带多种介质;或
- 把持久内存设备当普通内存使用;
平台可以创建多个 CEDT CFMWS 条目描述同一段内存,让用户在配置方式上保有灵活性。原文档指出:Linux 目前在该领域没有强约束,但这属于"可以这样描述"的合法模式。
小结:平台集成自查清单
综合 bios-and-efi.rst 的全文,CXL 平台固件侧的自查清单可以归纳为:
| 检查项 | Linux 期望 |
|---|---|
| ACPI 表 | 提供 CEDT、SRAT、HMAT 等完整静态描述,运行时配置交给 CXL 驱动 |
| HDM 解码器 | 不必预编程/预锁定;预锁定会牺牲热插拔等特性(Zen5 类 quirk 除外且需规避) |
| EFI_MEMORY_SP | 需要驱动管理的 CXL 区域应置位;可用uefisettings检查,efi=nosoftreserve作为 x86 逃生开关 |
| 区域对齐 | CXL 内存基地址与区域大小建议 2GB 对齐,避免海量 memory device 与搁浅容量 |
| 内存空洞 | 每段连续 HPA 用一个解码器 + 一个 CFMWS 条目描述,两侧空洞分别建模 |
| 地址翻译 | 只允许 Endpoint Decoder 做 HPA→DPA 翻译;中间层解码器范围必须是父解码器子集 |
| 交织 | 跨 host-bridge 交织的 CFMWS 必须列出全部目标 host bridge;CFMWS 应有 SRAT 对应描述 |
配合仓库内的 CEDT 字段文档、SRAT 文档 与 CXL 平台文档总索引,以及 drivers/cxl 驱动源码(acpi.c 负责 ACPI/CEDT 解析、core/ 实现区域与解码器运行时逻辑),即可从固件配置一路追踪到内核侧的完整行为链路。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考