ACPI ERST 设备
ACPI ERST 设备支持错误记录序列化表(ERST), 将错误记录保存到持久存储,供后续查阅或调试。
ACPI 规范 [1] 的 ACPI Platform Error Interfaces(APEI)章节, 尤其是 Error Serialization 小节,定义持久化存储错误记录的方法。
记录格式定义于 UEFI 规范 [2] 附录 N,Common Platform Error Record。
ACPI 允许直接向操作系统/客户机暴露非易失 RAM 的 NVRAM 模式, 见 GET_ERROR_LOG_ADDRESS_RANGE_ATTRIBUTES,但此设备实现非 NVRAM 模式。 多数 BIOS 也采用该模式,因为闪存内容更新需要编程操作。 撰写本文时,Linux 也仅支持非 NVRAM 模式。
背景与动机
Linux 用持久存储文件系统 pstore 在 panic 和关机时记录 dmesg 尾部等信息。 pstore 独立于 kdump,且先于它运行。主机或客户机根文件系统位于 NFS/iSCSI, 而网络软硬件故障导致 kdump 失败等情况下,pstore 可能保留事后调试信息。
pstore 常见后端为 ACPI ERST 和 UEFI。多数 BIOS 实现 ERST,但并非所有客户机都用 UEFI。 QEMU 支持 ERST 后,它就能像裸机一样成为虚拟机可用的 pstore 后端。
该支持提供一致的内核 panic 信息采集方法,适用于资源受限 microvm 到大型客户机, 尤其适用于没有 UEFI 运行时服务的直接启动环境。
Microsoft Windows 在 ERST 可用时,也用它保存部分崩溃信息 [3]。
配置与使用
创建 memory-backend-file 对象和 acpi-erst 设备,例如:
qemu ... -object memory-backend-file,id=erstnvram,mem-path=acpi-erst.backing,size=0x10000,share=on -device acpi-erst,memdev=erstnvram
正常工作要求后备对象具有以下参数:
id:用于将内存对象关联到 acpi-erst。
size:ERST 后备存储大小,必需。
mem-path:后备存储文件位置,必需。
share:必须 share=on,确保更新写入文件。
设备参数:
memdev:memory-backend-file 的对象 ID。
record_size:后备存储中记录或槽的大小,必须为不小于 4096(PAGE_SIZE)的 2 的幂。
PCI 接口
ERST 是带两个 BAR 的 PCI 设备,分别访问编程寄存器与记录交换缓冲区。
BAR0 包含 64 位 ACTION 和 VALUE 寄存器。 全部 ERST 操作及副作用都在写 ACTION 时发生,所需数据必须先写 VALUE。 读取 VALUE 仅返回其内容,该内容可能已被此前 ACTION 更新。
BAR1 包含 8KiB 记录交换缓冲区,即实现所支持的最大记录大小。
后备存储格式
存储分为固定 8KiB 的槽,每槽一个记录。槽不必全部占用,也不要求连续占用。 清除特定记录会产生空闲槽。
槽 0 保存存储头,标识 ERST 内容并支持高效访问。 根据总大小,头部可能占更多槽。8KiB 头可容纳 1021 条记录, 因此 8MiB(8KiB * 1024)存储需再用一个槽作头部。 此时槽 0、1 共同组成头部,记录从槽 2 开始。
以下示例存储小于 8MiB,大小为 8KiB 整数倍,含 N 个记录槽。 其中两条 CPER 记录已占用,其余空闲。
槽 记录
<------------------ 8KiB -------------------->
+--------------------------------------------+
0 | 存储头 |
+--------------------------------------------+
1 | 空闲/可用 |
+--------------------------------------------+
2 | CPER |
+--------------------------------------------+
3 | CPER |
+--------------------------------------------+
... | |
+--------------------------------------------+
N | 空闲/可用 |
+--------------------------------------------+
存储头包含基本信息和 CPER record_id 数组,以高效访问记录。
头部全部字段均为小端。
+--------------------------------------------+
| magic | 0x0000
+--------------------------------------------+
| record_offset | record_size | 0x0008
+--------------------------------------------+
| record_count | reserved | version | 0x0010
+--------------------------------------------+
| record_id[0] | 0x0018
+--------------------------------------------+
| record_id[1] | 0x0020
+--------------------------------------------+
| record_id[...] |
+--------------------------------------------+
| record_id[N] | 0x1FF8
+--------------------------------------------+
magic 值为 0x524F545354535245。
record_size 为 0x2000,即 8KiB。
record_offset 指向数组第一项 record_id,即 0x0018。
version 为 0x0100,表示第一版本。
record_count 为后备存储中的有效记录数。
record_id 数组保存对应槽内 CPER 的 64 位记录标识。 其在数组中的位置就是对应记录的槽索引。
例如存储小于 8MiB 时,槽 0 为头部,record_id[0] 永不包含有效 CPER ID。 槽 1 才是第一个可用槽,因此 record_id_[1] 可能包含 CPER。
全 0 或全 1 的 record_id 表示无效记录,即该槽可用。
参考资料
[1] “Advanced Configuration and Power Interface Specification”,4.0,2009 年 6 月。
[2] “Unified Extensible Firmware Interface Specification”,2.1,2008 年 10 月。
[3] “Windows Hardware Error Architecture”,尤其是“Error Record Persistence Mechanism”。