APEI 错误注入¶
EINJ 提供了一种硬件错误注入机制。它对于调试和测试 APEI 和 RAS 功能非常有用。
您需要首先检查您的 BIOS 是否支持 EINJ。为此,请查找类似于以下内容的早期启动消息
ACPI: EINJ 0x000000007370A000 000150 (v01 INTEL 00000001 INTL 00000001)
这表明 BIOS 正在公开一个 EINJ 表格 - 这是执行注入的机制。
或者,在 /sys/firmware/acpi/tables 中查找 “EINJ” 文件,它是同一事物的不同表示形式。
如果以上情况不存在,并不一定意味着不支持 EINJ:在放弃之前,进入 BIOS 设置查看 BIOS 是否有启用错误注入的选项。查找名为 WHEA 或类似的东西。通常,您需要先启用 ACPI5 支持选项,才能看到 BIOS 菜单支持和公开的 APEI、EINJ ... 功能。
要使用 EINJ,请确保在内核配置中启用了以下选项
CONFIG_DEBUG_FS
CONFIG_ACPI_APEI
CONFIG_ACPI_APEI_EINJ
并且(可选)启用 CXL 协议错误注入设置
CONFIG_ACPI_APEI_EINJ_CXL
EINJ 用户界面位于 <debugfs 挂载点>/apei/einj。
以下文件属于它
available_error_type
此文件显示支持哪些错误类型
错误类型值
错误描述
0x00000001
处理器可纠正
0x00000002
处理器不可纠正的非致命错误
0x00000004
处理器不可纠正的致命错误
0x00000008
内存可纠正
0x00000010
内存不可纠正的非致命错误
0x00000020
内存不可纠正的致命错误
0x00000040
PCI Express 可纠正
0x00000080
PCI Express 不可纠正的非致命错误
0x00000100
PCI Express 不可纠正的致命错误
0x00000200
平台可纠正
0x00000400
平台不可纠正的非致命错误
0x00000800
平台不可纠正的致命错误
文件内容的格式如上所示,但只显示可用的错误类型。
error_type
设置要注入的错误类型的值。可能的错误类型在上面的 available_error_type 文件中定义。
error_inject
向此文件写入任何整数以触发错误注入。确保您已指定所有必要的错误参数,即,此写入应该是注入错误时的最后一步。
flags
在 3.13 及更高版本的内核中存在。用于指定 param{1..4} 中的哪些有效,并且固件在注入期间应使用哪些。该值是一个位掩码,如 ACPI5.0 规范中针对 SET_ERROR_TYPE_WITH_ADDRESS 数据结构所指定的那样
- 位 0
处理器 APIC 字段有效(请参见下面的 param3)。
- 位 1
内存地址和掩码有效(param1 和 param2)。
- 位 2
PCIe (seg, bus, dev, fn) 有效(请参见下面的 param4)。
如果设置为零,则会模拟旧的行为,其中注入类型仅指定一位设置,并且 param1 被复用。
param1
此文件用于设置第一个错误参数值。其效果取决于 error_type 中指定的错误类型。例如,如果错误类型是与内存相关的类型,则 param1 应该是有效的物理内存地址。[除非设置了 “flag” - 请参见上文]
param2
与上面的 param1 用法相同。例如,如果错误类型是与内存相关的类型,则 param2 应该是物理内存地址掩码。Linux 需要页面或更小的粒度,例如 0xfffffffffffff000。
param3
当 “flags” 中设置了 0x1 位时使用,用于指定 APIC ID
param4 当 “flags” 中设置了 0x4 位时使用,用于指定目标 PCIe 设备
notrigger
错误注入机制是一个两步过程。首先注入错误,然后执行一些操作来触发它。将 “notrigger” 设置为 1 会跳过触发阶段,这可能允许用户通过简单地访问作为错误注入目标的 CPU、内存位置或设备在其他上下文中引起错误。这是否真正起作用取决于 BIOS 在触发阶段实际包含的操作。
从 ACPI 6.5 版本开始支持 CXL 错误类型(假设存在 CXL 端口)。CXL 错误类型的 EINJ 用户界面位于 <debugfs 挂载点>/cxl。以下文件属于它
einj_types
提供与上面的 available_error_types 相同的功能,但适用于 CXL 错误类型
$dport_dev/einj_inject
将 CXL 错误类型注入到由 $dport_dev 表示的 CXL 端口中,其中 $dport_dev 是 CXL 端口的名称(通常是 PCIe 设备名称)。针对 CXL 2.0+ 端口的错误注入可以使用 <debugfs 挂载点>/apei/einj 下的旧接口,而 CXL 1.1/1.0 端口注入必须使用此文件。
基于 ACPI 4.0 规范的 BIOS 版本在控制错误注入位置方面选项有限。您的 BIOS 可能支持扩展(通过 param_extension=1 模块参数或启动命令行 einj.param_extension=1 启用)。这允许通过 apei/einj 中的 param1 和 param2 文件指定内存注入的地址和掩码。
基于 ACPI 5.0 规范的 BIOS 版本可以更好地控制注入的目标。对于与处理器相关的错误(类型 0x1、0x2 和 0x4),您可以将 flags 设置为 0x3(param3 用于位 0,param1 和 param2 用于位 1),以便在注入的错误签名中添加更多信息。实际传递的数据是这样的
memory_address = param1;
memory_address_range = param2;
apicid = param3;
pcie_sbdf = param4;
对于内存错误(类型 0x8、0x10 和 0x20),使用 param1 设置地址,并使用 param2 设置掩码(0x0 等效于全部为 1)。对于 PCI express 错误(类型 0x40、0x80 和 0x100),使用 param1 指定段、总线、设备和功能
31 24 23 16 15 11 10 8 7 0
+-------------------------------------------------+
| segment | bus | device | function | reserved |
+-------------------------------------------------+
无论如何,您应该明白了,如果存在疑问,只需查看 drivers/acpi/apei/einj.c 中的代码。
ACPI 5.0 BIOS 也可能允许注入特定于供应商的错误。在这种情况下,名为 vendor 的文件将包含来自 BIOS 的标识信息,希望使用供应商特定扩展的应用程序可以通过该信息判断它们是否在支持该扩展的 BIOS 上运行。所有供应商扩展的 error_type 中都设置了 0x80000000 位。文件 vendor_flags 控制 param1 和 param2 的解释 (1 = PROCESSOR, 2 = MEMORY, 4 = PCI)。有关详细信息,请参见 BIOS 供应商文档(并且如果供应商在此功能的使用方面的创造力超出我们的预期,则期望此 API 会发生更改)。
错误注入示例
# cd /sys/kernel/debug/apei/einj
# cat available_error_type # See which errors can be injected
0x00000002 Processor Uncorrectable non-fatal
0x00000008 Memory Correctable
0x00000010 Memory Uncorrectable non-fatal
# echo 0x12345000 > param1 # Set memory address for injection
# echo 0xfffffffffffff000 > param2 # Mask - anywhere in this page
# echo 0x8 > error_type # Choose correctable memory error
# echo 1 > error_inject # Inject now
您应该在 dmesg 中看到类似以下内容
[22715.830801] EDAC sbridge MC3: HANDLING MCE MEMORY ERROR
[22715.834759] EDAC sbridge MC3: CPU 0: Machine Check Event: 0 Bank 7: 8c00004000010090
[22715.834759] EDAC sbridge MC3: TSC 0
[22715.834759] EDAC sbridge MC3: ADDR 12345000 EDAC sbridge MC3: MISC 144780c86
[22715.834759] EDAC sbridge MC3: PROCESSOR 0:306e7 TIME 1422553404 SOCKET 0 APIC 0
[22716.616173] EDAC MC3: 1 CE memory read error on CPU_SrcID#0_Channel#0_DIMM#0 (channel:0 slot:0 page:0x12345 offset:0x0 grain:32 syndrome:0x0 - area:DRAM err_code:0001:0090 socket:0 channel_mask:1 rank:0)
使用 $dport_dev=0000:e0:01.1 的 CXL 错误注入示例
# cd /sys/kernel/debug/cxl/
# ls
0000:e0:01.1 0000:0c:00.0
# cat einj_types # See which errors can be injected
0x00008000 CXL.mem Protocol Correctable
0x00010000 CXL.mem Protocol Uncorrectable non-fatal
0x00020000 CXL.mem Protocol Uncorrectable fatal
# cd 0000:e0:01.1 # Navigate to dport to inject into
# echo 0x8000 > einj_inject # Inject error
注入到 SGX enclave 中的特殊注意事项
可能存在单独的 BIOS 设置选项来启用 SGX 注入。
注入过程包括设置一些特殊的内存控制器触发器,该触发器将在下次写入目标地址时注入错误。但是 h/w 会阻止 SGX enclave 之外的任何软件访问 enclave 页面(即使是 BIOS SMM 模式)。
- 可以使用以下顺序
确定 enclave 页面的物理地址
使用 “notrigger=1” 模式进行注入(这将设置注入地址,但实际上不会注入)
进入 enclave
将数据存储到与步骤 1 中的物理地址匹配的虚拟地址
对该虚拟地址执行 CLFLUSH
自旋延迟 250 毫秒
从虚拟地址读取。这将触发错误
有关 EINJ 的更多信息,请参阅 ACPI 规范版本 4.0 的第 17.5 节和 ACPI 5.0 的第 18.6 节。