首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

高级平台错误接口在 Linux 平台上的应用(3)

高级平台错误接口在 Linux 平台上的应用(3)

在整个过程当中,关键点是从 GET_TRIGGER_ERROR_ACTION_TABLE 动作完成后得到的 trigger 表的基地址。这个 trigger 表的基地址作为入口参数被 __einj_error_trigger 函数调用,最终完成错误触发。简单来说,__einj_error_trigger 需要完成两件事:
  • 根据 GET_TRIGGER_ERROR_ACTION_TABLE 返回的 trigger 表的地址(本质上是一个复合结构)进行相应的 IO 资源分配(这里的 IO 资源主要由 GAS 提供)
  • 调用 ACPI_EINJ_TRIGGER_ERROR 动作完成错误触发。
图 3. EINJ 返回的 trigger 地址展开什么是 FFM(Firmware First Mode)在 x86 平台上,当硬件发生错误时,根据系统的配置情况,硬件可以直接把错误信息发送给 CPU,然后交由系统软件处理;也可以交给 BIOS/FIRMWARE 先行处理,然后由 BIOS/FIRMWARE 来决定是否需要将这个错误交给 CPU 进行进一步处理,如果需要 CPU 的参与,又以何种方式进行。这后一种操作方式就是所谓的 FFM。由于 BIOS/FIRMWARE 是平台相关的,因此 BIOS/FIRMWARE 相比 OS 而言更加清楚硬件平台的配置情况,甚至包含各种必须的 workaround,定制和优化。这样一来,在 FFM 使能的情况下,BIOS/FIRMWARE 利用这一优势,可以有针对性的对发生的硬件错误进行分析,处理以及分发,也可以更加准确的记录错误的现场信息。这样一来,不但对硬件错误可以做出更准确,更复杂的处理,而且可以降低 OS 的复杂性和冗余度。
FFM 模式的实现要点在于 SMI 中断的使用。举例来说,如果 FFM 被禁用,硬件产生错误时发送 NMI 中断给 CPU,然后交由 OS 处理;如果 FFM 使能,硬件产生错误时不会发送 NMI 中断,而是触发 SMI 中断。这个中断会使 CPU 进入 SMM 模式,在 SMM 模式中,BIOS/FIRMWARE 会对错误进行初步处理,清除硬件的错误状态,然后根据需要在结束 SMI 中断处理后产生合适的中断发送给 CPU,例如 SCI 或者 NMI,来通知 CPU 进行下一步处理工作。如果 BIOS/FIRMWARE 不需要 CPU 做进一步的处理,那么在退出 SMM 模式后,就不会产生新的中断,可能只需要 CPU 在轮询时检查某些状态寄存器即可。需要指出的是,并不是所有的错误源都适用于 FFM,譬如 Machine Check Exception 就不能使用 FFM,而只能通过 MCE 直接报告给 CPU 进行处理。
图 4. FFM 的处理过程FFM 模式的开启需要硬件,BIOS/FIRMWARE 和软件三者的配合。首先硬件的物理连接要确保可以使用 FFM,其次 BIOS/FIRMWARE 要有对应的处理逻辑并且通过 ACPI 将必要的表项导出供系统软件使用,最后系统软件需要根据情况开启或者禁用 FFM。这三者缺一不可。硬件平台是否支持 FFM 需要查看相关的硬件手册。BIOS/FIRMWARE 是否支持可以通过检查 BIOS 的配置选项,有些 BIOS 会缺省隐藏这一选项,在这种情况下需要通过 BIOS 的提供厂商来确定 FFM 是否被支持。在软件层面,需要通过调用 ACPI 的 _OSC 方法来查询并开启 FFM 模式。在这个 _OSC 调用中,其核心是两个特定的 UUID。
第一个 UUID 定义在 ACPI 规范中(ACPI 4.0a 规范 6.2.10.2 227 页)。通过调用 _OSC 方法并使用这个 UUID 做为参数,可以判断当前系统是否支持 FFM。
清单 4. FFM 模式的初始状态检查
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
在 drivers/acpi/bus.c 中
static u8 sb_uuid_str[] = "0811B06E-4A27-44F9-8D60-3CBBC22E7B48";
static void acpi_bus_osc_support(void)
{
        u32 capbuf[2];
        struct acpi_osc_context context = {
               .uuid_str = sb_uuid_str,
                .rev = 1,
                .cap.length = 8,
                .cap.pointer = capbuf,
        };
        acpi_handle handle;
        ……
        ……
        if (!ghes_disable)
                capbuf[OSC_SUPPORT_TYPE] |= OSC_SB_APEI_SUPPORT;
        if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle)))
                return;
        if (ACPI_SUCCESS(acpi_run_osc(handle, &context))) {
                u32 *capbuf_ret = context.ret.pointer;
                if (context.ret.length > OSC_SUPPORT_TYPE)
                        osc_sb_apei_support_acked =
                                capbuf_ret[OSC_SUPPORT_TYPE]&
                                    OSC_SB_APEI_SUPPORT;
                kfree(context.ret.pointer);
        }
}




然而很多 BIOS/FIRMWARE 在实现时并未使用这个标准的 UUID,而是为了兼容 Windows 系统,使用了微软私有的 WHEA 中定义的一个 UUID 来完成这一功能。为了能让 Linux 系统使用这一强大功能,Linux 也不得不“伪装”成 Windows 来完成检测,如下所示:
清单 5. FFM 模式初始状态的附加检查
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
在 drivers/acpi/apei/apei-base.c 中
int apei_osc_setup(void)
{
       static u8 whea_uuid_str[] = "ed855e0c-6c90-47bf-a62a-26de0fc5ad5c";
        acpi_handle handle;
        u32 capbuf[3];
        struct acpi_osc_context context = {
                .uuid_str       = whea_uuid_str,
                .rev            = 1,
                .cap.length     = sizeof(capbuf),
                .cap.pointer    = capbuf,
        };

        capbuf[OSC_QUERY_TYPE] = OSC_QUERY_ENABLE;
        capbuf[OSC_SUPPORT_TYPE] = 1;
        capbuf[OSC_CONTROL_TYPE] = 0;

        if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle))
            || ACPI_FAILURE(acpi_run_osc(handle, &context)))
                return -EIO;
        else {
                kfree(context.ret.pointer);
                return 0;
        }
}




由于 WHEA 是微软的私有协议,因此除了 UUID 本身以外,我们并不知道其内部结构是如何定义的,因此如何传递参数给这个 _OSC 方法是不甚清楚的。这也是为什么在内核中会出现这样的 patch(commit id: b3b46d7)。
清单 6. FFM 模式最终使能状态判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在 drivers/acpi/apei/ghes.c 中
static int __init ghes_init(void)
{
……
……
        rc = apei_osc_setup();
        if (rc == 0 && osc_sb_apei_support_acked)
                pr_info(GHES_PFX "APEI firmware first mode is "
                        "enabled by APEI bit and WHEA _OSC.\n");
        else if (rc == 0 && !osc_sb_apei_support_acked)
                pr_info(GHES_PFX "APEI firmware first mode is "
                        "enabled by WHEA _OSC.\n");
        else if (rc && osc_sb_apei_support_acked)
                pr_info(GHES_PFX "APEI firmware first mode is "
                        "enabled by APEI bit.\n");
        else
                pr_info(GHES_PFX "Failed to enable APEI "
                        "firmware first mode.\n");
……
……
}




通过这两种方式,最终可以完成对 FFM 的支持。由上述代码可以看到,FFM 和 GHES 有很大的联系。这是因为在 FFM 使能的模式下,无论是硬件自身发生的错误,还是通过 EINJ 触发产生的错误,都会被记录在 HEST/GHES 中,下文将具体介绍 HEST/GHES 的功能和作用。
返回列表