0%

arm ras 方案调研

华为 hisi 1620

APEI

UEFI
ACPI Platform Error Interfaces

Provides a standard way to convey error info From Firmware to OS

BERT

Boot Error Record Table
Record fatal errors, then report it in the second boot
记录启动过程中的关键错误信息, 在下一次启动时报告错误.

  • 在 OS 未接管平台的控制权限之前 firmware(如 BIOS 或者 UEFI)检测到错误,导致系统无法继续启动,可以通过 BIOS/FIRMWARE 将这种类型的错误写入到特定的存储位置。这样一来,在下一次的正常启动过程中,OS 可以通过特定的方法将之前保存的错误读取出来分析并处理。
  • 系统运行过程中 firmware 检测到了致命错误,以至于 firmware 决定不通知 OS 而是直接重启(如 CPU 风扇突然坏了,瞬间过热,如果不立刻重启会烧毁 CPU),在重启前 firmware 可以记录下相关的错误信息以便之后分析出错原因

注:只有 BIOS/FIRMWARE 才有能力对 BERT 执行写入操作;对于 OS 而言,BERT 仅仅是一个只读的表。BERT 出现的意义在于希望采用一种统一的接口来记录特定类型的硬件错误(主要是一些致命的),从而简化 BIOS/FIRMWARE 和 OS 的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-+ ApeiEntryPoint(ImageHandle, SystemTable)
\ -|+ if SetupData.EnRasSupport
"通过协议的 GUID 查找对应的协议, 查找acpi 表格协议"
\ -+ gBS->LocateProtocol(&gEfiAcpiTableProtocolGuid, NULL, &mAcpiTableProtocol);
"查找 ACPI 标准数据表协议, 操作系统和驱动程序可以获取系统硬件配置信息,进行系统初始化和配置,以及支持电源管理等功能。"
| -+ gBS->LocateProtocol(&gEfiAcpiSdtProtocolGuid, NULL, &mAcpiSdtProtocol);
| -+ gBS->AllocatePool (EfiReservedMemoryType, sizeof (APEI_TRUSTED_FIRMWARE_STRUCTURE), (VOID**)&mApeiTrustedfirmwareData) "分配内存池"
| - gBS->SetMem (mApeiTrustedfirmwareData, sizeof (APEI_TRUSTED_FIRMWARE_STRUCTURE), 0)); "memset 0"
| -+ OemInitBertTable (ImageHandle);
\ - BERT_CONTEXT Context;
| -+ BertHeaderCreator (&Context, BOOT_ERROR_REGION_SIZE); "Bert 表分配内存"
\ - Context->BertHeader = AllocateZeroPool (sizeof (EFI_ACPI_6_0_BOOT_ERROR_RECORD_TABLE_HEADER));
| - Context->Block = AllocateReservedZeroPool (ErrorBlockSize);
"构造header 包含上图的 OEMID OEM_table_id creator_id等如 EFI_ACPI_ARM_OEM_REVISION EFI_ACPI_ARM_CREATOR_ID"
| - *Context->BertHeader = (EFI_ACPI_6_0_BOOT_ERROR_RECORD_TABLE_HEADER) {
ARM_ACPI_HEADER(
EFI_ACPI_6_0_BOOT_ERROR_RECORD_TABLE_SIGNATURE,
EFI_ACPI_6_0_BOOT_ERROR_RECORD_TABLE_HEADER,
EFI_ACPI_6_0_BOOT_ERROR_RECORD_TABLE_REVISION
),
| - ErrorBlockInitial (Context.Block, EFI_ACPI_6_2_ERROR_SEVERITY_NONE);
| -+ BertSetAcpiTable (&Context); "初始化 Bert Table"
"ACPI 表格是存储着系统硬件配置信息和固件与操作系统通信信息的数据结构。在 UEFI 中,
有时需要向 UEFI 固件中添加自定义的 ACPI 表格,
以便在操作系统启动时提供特定的系统配置信息,或支持特定的硬件功能"
\ -+ mAcpiTableProtocol->InstallAcpiTable ( "通过Acpi 表格协议安装 Bert 表"
mAcpiTableProtocol,
Bert,
Bert->Header.Length,
&AcpiTableHandle);

linux driver

drivers/acpi/apei/bert.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-+ bert_init
\ -+ acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab); "获取bert table"
| - region_len = bert_tab->region_length; "获取 bert_table length"
| - apei_resources_init(&bert_resources); "初始化 apei_resources 结构体"
"向bert_resources 下添加IOMEM 类型的资源,以便在发生错误时能够正确地处理这些资源。 "
| - apei_resources_add(&bert_resources, bert_tab->address, region_len, true);
| -+ apei_resources_request(&bert_resources, "APEI BERT");
| -+ boot_error_region = ioremap_cache(bert_tab->address, region_len);
| -|+ if boot_error_region
\ -+ bert_print_all(boot_error_region, region_len);
\ - "Error records from previous boot"
| -+ cper_estatus_print(KERN_INFO HW_ERR, estatus);
\ - " %s event severity: %s", severity "打印log错误级别"
| -+ foreach section
\ -+ cper_estatus_print_section(newpfx, gdata, sec_no)
\ - severity = gdata->error_severity;
| - printk("%s""Error %d, type: %s\n", pfx, sec_no, cper_severity_str(severity));
| - printk("%s""fru_id: %pUl\n", pfx, gdata->fru_id); "fru_id fru_text等, 见上图的表结构"
| - printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text);
...

ERST

Error Record Serialization Table
Provides details necessary to communicate with on-board persistent storage for error recording

提供必要的详细信息, 协同存储错误记录

ERST 本质上是一个用来永久存储错误的抽象接口软件可以通过 ERST 表将各种错误信息保存到 ERST 中,再由 ERST 写入到可用于永久存储的物理介质中。ERST 并没有一个严格的定义来界定什么是“错误”,换言之,软件可以保存任何信息到 ERST 中,只要软件认为是有意义,有价值的信息就可以.
物理介质未必一定是 flash 或 NVRAM,可以是网络存储或者其他。

ERST 的主要作用就是用来存储各种硬件或者平台相关的错误,错误类型包括:

  • Corrected Error(CE)
  • Uncorrected Recoverable Error(UCR)
  • Uncorrected Non-Recoverable Error,或者说 Fatal Error。

换言之,只要是软件可以记录的错误,都可以将其保存到 ERST 当中。加上之前谈到的 BERT 表,这样一来,无论系统运行在哪个阶段,当出现硬件或平台相关的错误时,通过 APEI 接口,都有办法将错误保存下来。这样一来就可以在之后通过适当的方法将错误读取出来进行分析,从而加快定位产生错误的原因并加以解决。

EINJ

Error Injection Table
Provides a generic Interface which OSPM can inject hardware Errors to the platform without requiring platform Specific software.

提供通用接口方便 os 向硬件注入错误.

GHES of HEST

Generic Hardware Error Source - GHES
Hardware Error Source Table - HEST

HOW to get trigger: Notification Structure
WHERE are the error records:

Error Status Address
(GAS : Generic Address Structure)

HOW to release records’ mem:
Read Ack Register

在 HEST 中定义了很多硬件相关的错误源和错误类型。定义这些硬件错误源的目的在于标准化软硬件错误接口的实现。有了 HEST,当发生特定类型的硬件错误,如 PCI-E 设备产生了一个 Uncorrected Recoverable 类型的错误时,BIOS/FIRMWARE 有统一的方法更新特定的寄存器和内部状态,软件有统一的方法去处理和解析错误。HEST 中定义了很多硬件错误源,如 MCE、PCI-E、GHES 等等。

其中最为特殊也是最为重要的硬件错误源类型就是 GHES (Generic Hardware Error Source)。GHES 是一个通用硬件错误源,换言之,任何类型的硬件错误都可以使用 GHES 来定义,而无需使用之前提到的特定硬件错误源,如内存控制器错误等。

当前无论是软件还是 BIOS/FIRMWARE 的实现,基本上都是只使用 GHES 来实现 HEST 的功能,至于其他特定的硬件错误源,基本上都没有使用(PCI-E AER 的部分代码检测了 PCI-E 类型的硬件错误源)。

在 FFM (Firmware-First handling) 使能的情况下,一般而言,所有 CE 类型的错误通过 SCI 中断报告给 OS,然后 OS 在 HEST/GHES 中查表,检测并处理可能的硬件错误;所有 UC 和 Fatal 类型的错误通过 NMI 报告给 OS,然后 OS 在 NMI 的 handler 中查表,检测并处理可能的硬件错误。这些规定并不是硬性要求的,平台设计者完全可以根据需要使用 NMI 来处理所有的错误类型,包括 CE, UC 和 Fatal 类型的错误,也可以只使用 NMI 来处理 UC 和 Fatal 类型的错误,而使用轮询的方式来处理 CE 类型的错误。

linux kernel ghes 处理

Ghes_probe 函数中,根据 HEST 表中传递的检测错误类型,查看相关 kernel 配置选项是否支持。包括 arm 相关的 SEA 错误,NMI,本地中断。
调用 ghes_new 函数,初始化 struct ghes 结构。初始化 ghes 结构,映射表中 Error Status Address。为存放错误信息数据申请内存。

根据上报错误方式,注册不同的处理流程,包括如下:
(1)轮询方式,根据表中传递的 poll_interval 时间,创建定时器。在定时器处理函数 ghes_poll_func 中,调用 ghes_proc。在这个函数中:
A、读取 GHES 结构中传递的 Error Status Address。首先将读到的 struct acpi_hest_generic_status 结构拷贝到前面申请的内存中,检测相关错误信息长度是否合法。然后将后面的错误信息拷贝。
B、如上报错误的严重级别大于 GHES_SEV_PANIC,则将错误信息打印,清除错误状态以及记录错误信息的内存块。然后进入 kernel panic。
C、调用 ghes_do_proc 函数处理错误。这个函数中,获取错误数据块中各 section 中 section_type 以及 error_severity。同时判断 fru_id 和 fru_text 字段是否有效。 1>如为内存相关错误,以下为错误类型分类
* @ HW_EVENT_ERR_CORRECTED: Corrected Error - 表示检测到 ECC 纠正的错误
* @ HW_EVENT_ERR_UNCORRECTED : 表示 ECC 无法纠正的错误,但不是致命的错误 (可能是在未使用的内存区域,或者内存控制器可以从中恢复,例如,通过重新尝试操作)
* @ HW_EVENT_ERR_DEFERRED: Deferred Error - 表示处理不紧急的不可纠正的错误。这可能是由于硬件数据中毒,系统可以继续操作,直到中毒的数据被消耗。也可以采取主动的措施,例如,offlining 页面等。
* @ HW_EVENT_ERR_FATAL: 致命错误-无法恢复的不可更正错误。
* @ HW_EVENT_ERR_INFO: 规范定义了第四种类型的错误: 信息日志。
首先获取 struct cper_sec_mem_err 数据块。调用
Ghes_edac_report_mem_error 函数,这个函数中,将错误信息中包括错误类型,错误地址,内存颗粒,错误内存位置填充到错误报告 buffer 中(struct edac_raw_error_desc)。
错误类型如下:

把错误写到 ftrace 的一个跟踪项中,最后调用 edac_raw_mc_handle_error
分别通过 edac_ce_error 处理 ECC 类型的错误以及调用 edac_ue_error 处理其他错误。
最后调用 ghes_handle_memory_failure
如果
- 错误级别为可修复级别且 CPER_SEC_ERROR_THRESHOLD_EXCEEDED 置位(表示内核中止使用这个资源)
- 错误是可恢复的类型。满足上述条件之一则调用 memory_failure_queue 函数。这个函数在检测到页面的硬件内存损坏时由硬件错误处理程序调用。它调度错误页面的恢复,包括删除页面,杀死进程等。

如为 pcie aer 错误
PCIe AER 错误需要发送到 AER 驱动程序进行报告和恢复。GHES 的严重程度与以下 AER 严重程度相对应,并需要进行以下处理:
- GHES_SEV_CORRECTABLE -> AER_CORRECTABLE — 需要由 AER 驱动报告,但不需要恢复。
- GHES_SEV_RECOVERABLE -> AER_NONFATAL
- GHES_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL
这两种情况都需要 AER 驱动报告和恢复。
GHES_SEV_PANIC 不会进行这种处理,因为内核必须将进入 panic 状态。

(2) 如果为外部中断,则通过 GHES 中传递的中断号,申请中断处理函数,在中断处理函数 ghes_irq_func 调用 ghes_proc。下面的处理流程和上面轮询一致。
(3) 同样的如果为 SCI 中断,调用 notifier_call 回调函数 ghes_notify_hed,遍历 ghes_hed 链表,分别执行 ghes_proc 函数,处理错误
(4) 如果为 NMI 中断,处理错误级别大于 GHES_SEV_PANIC,则直接 kernel panic。否则如配置 CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG,则调到工作队列处理函数 ghes_proc_in_irq 中。执行相当于下半部的处理过程。最终调用 ghes_do_proc 函数执行上述相关错误的处理。