高级平台错误接口在 Linux 平台上的应用(4)
- UID
- 1066743
|
高级平台错误接口在 Linux 平台上的应用(4)
HEST (Hardware Error Source Table)在 HEST 中定义了很多硬件相关的错误源和错误类型。定义这些硬件错误源的目的在于标准化软硬件错误接口的实现。有了 HEST,当发生特定类型的硬件错误,如 PCI-E 设备产生了一个 UC 类型的错误时,BIOS/FIRMWARE 有统一的方法更新特定的寄存器和内部状态,软件有统一的方法去处理和解析错误。HEST 中定义了很多硬件错误源,如 MCE, NMI, PCI-E,GHES 等等。
图 5. HEST 的基本组成结构这其中最为特殊也是最为重要的硬件错误源类型就是 GHES(Generic Hardware Error Source)。从字面上理解,GHES 是一个通用硬件错误源,换言之,任何类型的硬件错误都可以使用 GHES 来定义,而无需使用之前提到的特定硬件错误源,如 MCE 等。而事实上,当前无论是软件还是 BIOS/FIRMWARE 的实现,基本上都是只使用 GHES 来实现 HEST 的功能,至于其他特定的硬件错误源,基本上都没有使用(PCI-E AER 的部分代码检测了 PCI-E 类型的硬件错误源)。在 FFM 使能的情况下,一般而言,所有 CE 类型的错误通过 SCI 中断报告给 OS,然后 OS 在 HEST/GHES 中查表,检测并处理可能的硬件错误;所有 UC 和 Fatal 类型的错误通过 NMI 报告给 OS,然后 OS 在 NMI 的 handler 中查表,检测并处理可能的硬件错误。这些规定并不是硬性要求的,平台设计者完全可以根据需要使用 NMI 来处理所有的错误类型,包括 CE, UC 和 Fatal 类型的错误,也可以只使用 NMI 来处理 UC 和 Fatal 类型的错误,而使用轮询的方式来处理 CE 类型的错误。
以 CE 类型的错误通过 SCI 中断处理,UC 和 Fatal 类型的错误通过 NMI 处理为例,Linux 中的处理逻辑如下所示:
清单 7. 初始化 GHES 的错误分类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
| 在 drivers/acpi/apei/ghes.c 中:
static int __devinit ghes_probe(struct platform_device *ghes_dev)
{
……
switch (generic->notify.type) {
……
case ACPI_HEST_NOTIFY_SCI:
mutex_lock(&ghes_list_mutex);
if (list_empty(&ghes_sci))
register_acpi_hed_notifier(&ghes_notifier_sci);
list_add_rcu(&ghes->list, &ghes_sci);
mutex_unlock(&ghes_list_mutex);
break;
case ACPI_HEST_NOTIFY_NMI:
len = ghes_esource_prealloc_size(generic);
ghes_estatus_pool_expand(len);
mutex_lock(&ghes_list_mutex);
if (list_empty(&ghes_nmi))
register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0,
"ghes");
list_add_rcu(&ghes->list, &ghes_nmi);
mutex_unlock(&ghes_list_mutex);
break; ……
}
……
}
|
从上述代码中可以看到,SCI 的 notifier 挂在了 HED 设备(Hardware Error Device)的通知链上,当硬件发生了 CE 类型的错误时,HED 设备(HED 设备在 ACPI 中的 device id 为 PNP0C33)会执行如下代码:
清单 8. CE 类型错误的处理过程1
2
3
4
5
6
7
8
9
10
| 在 drivers/acpi/hed.c 中
/*
* SCI to report hardware error is forwarded to the listeners of HED,
* it is used by HEST Generic Hardware Error Source with notify type
* SCI.
*/
static void acpi_hed_notify(struct acpi_device *device, u32 event)
{
blocking_notifier_call_chain(&acpi_hed_notify_list, 0, NULL);
}
|
这样一来,之前注册在 HED 设备通知链上的 ghes_notifier_sci 就可以得到调用,从而完成后继的查 HEST/GHES 表以及处理错误等工作;对于 NMI 的处理也是类似,当硬件发生了 UC/Fatal 类型的错误产生 NMI 时,Linux 会在 NMI 的 handler 里面做类似的工作,这其中就包括有 HEST/GHES 注册的回调函数。
清单 9. UC/Fatal 类型错误的处理过程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 在 arch/x86/kernel/nmi.c 中
static notrace __kprobes void default_do_nmi(struct pt_regs *regs)
{
unsigned char reason = 0;
int handled;
bool b2b = false;
/*
* CPU-specific NMI must be processed before non-CPU-specific
* NMI, otherwise we may lose it, because the CPU-specific
* NMI can not be detected/processed on other CPUs.
*/
……
……
handled = nmi_handle(NMI_LOCAL, regs, b2b);
……
}
|
nmi_handle 的实现如下所示。它主要的作用就是遍历挂在 nmi_desc[NMI_LOCAL | NMI_UNKNOWN] 链表上的回调函数完成对 NMI 的处理。这个新的处理机制是从 Linux 3.1 内核引入的,在此之前,NMI 一直使用 notifier 通知链的方式完成类似的工作,有兴趣的读者可以通过 commit c9126b2 进行更深入的阅读。
清单 10. 遍历 NMI handler1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| 在 arch/x86/kernel/nmi.c 中
static int notrace __kprobes nmi_handle(unsigned int type,
struct pt_regs *regs, bool b2b)
{
struct nmi_desc *desc = nmi_to_desc(type);
struct nmiaction *a;
int handled=0;
rcu_read_lock();
/*
* NMIs are edge-triggered, which means if you have enough
* of them concurrently, you can lose some because only one
* can be latched at any given time. Walk the whole list
* to handle those situations.
*/
list_for_each_entry_rcu(a, &desc->head, list)
handled += a->handler(type, regs);
rcu_read_unlock();
/* return total number of NMI events handled */
return handled;
}
|
值得注意的是,由于 NMI context 的特殊性,譬如关中断,在 x86_64 上使用特殊的栈帧等,因此对于 HEST/GHES 而言,如果处于 NMI context,处理要格外小心。譬如不可以调用 printk 进行打印输出(printk 在调用时会持有一把锁,如果在 NMI 中调用 printk 可能会因为 NMI 抢占本 CPU 上其他中断处理中的 printk 操作而造成死锁),因而也不可以在 NMI 中使用其他加锁操作,而要使用无锁操作(lock-less)。这些细节和 APEI 本身没有直接关联,有兴趣的读者可以通过阅读相关的代码自行分析。以下以 GHES 中 NMI 类型的错误处理为例来说明整个 GHES 的处理过程。
在介绍 GHES 的处理逻辑之前,有必要先了解一下 HEST/GHES 的数据结构定义,以便于理解。
图 6. HEST/GHES 的数据结构图示从上表可以看到,GHES 基本上是一个塔形的数据结构。一层结构层叠在另一层结构之上。需要注意的是,GHES Error Data 并没有直接保存在 GHES 的数据结构当中,而是通过 GAS 中包含的地址指针间接引用。每一个 GHES Header 都包含着一份属于自己的 GHES Error Data,在每一个 GHES Error Data 当中,可能有一项或者多项 GHES Error Data Entry。
1
| 下表是一个通过 acpidump/acpixtract/iasl 实际解析出来的 HEST 表。
|
注:
acpidump/acpixtract/iasl 等工具是用来完成 ACPI 表解析的一整套工具集。 |
|
|
|
|
|