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 (段、总线、设备、功能) 有效(参见下面的 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 在触发阶段实际包含了哪些操作。

CXL 错误类型从 ACPI 6.5 开始支持(如果存在 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(位 0 用 param3,位 1 用 param1 和 param2),以便为正在注入的错误签名添加更多信息。实际传递的数据是这样的:

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 = 处理器,2 = 内存,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)

一个 CXL 错误注入示例,其中 $dport_dev=0000:e0:01.1

# 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 注入。

注入过程包括设置一些特殊的内存控制器触发器,它将在下一次写入目标地址时注入错误。但是硬件会阻止任何 SGX enclave 之外的软件访问 enclave 页面(甚至是 BIOS SMM 模式)。

可以使用以下序列:
  1. 确定 enclave 页面的物理地址

  2. 使用“notrigger=1”模式进行注入(这将设置注入地址,但不会实际注入)

  3. 进入 enclave

  4. 将数据存储到与步骤 1 中的物理地址匹配的虚拟地址

  5. 对该虚拟地址执行 CLFLUSH

  6. 自旋延迟 250ms

  7. 从该虚拟地址读取。这将触发错误

有关 EINJ 的更多信息,请参阅 ACPI 规范 4.0 版本第 17.5 节和 ACPI 5.0 版本第 18.6 节。