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

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

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

简介长久以来,工作在 x86 平台上的硬件使用多样的方式向上层软件报告硬件错误,有的通过 PCI-E 总线传递错误消息,有的需要读写特定的寄存器组来得到错误信息,还有的通过产生特定的中断或者异常来报告错误状态。在这些各式各样方法的背后,是硬件设计人员和软件开发人员耗费大量的时间用来定义接口以及接口实现。这样做的直接后果是增加了太多不必要的开销。因此,一个统一高效的接口无疑是非常有必要的,APEI(Advanced Platform Error Interfaces)的出现,正是为了解决这一长久存在的问题。新的 APEI 规范统一了软硬件之间的接口,降低了软硬件开发人员的开发复杂度。不但如此,新的 APEI 接口更加灵活,便于扩展。譬如说,APEI 的规范定义中大量使用了 UEFI 中已有的结构定义,这样做极大提高了 APEI 和未来的 UEFI BIOS 的兼容性。APEI 作为 RAS 体系结构中的一个重要组成部分,其作用是显而易见的,因此,熟悉并学习使用 APEI 对于构建整个系统的 RAS 体系结构是非常重要且必要的。
APEI 的结构组成并不复杂,简单而言,就是 4 张表。他们分别是:
BOOT Error Record Table (BERT)
Error Record Serialization Table (ERST)
Error Injection Table (EINJ)
Hardware Error Source Table (HEST)
以下将对这 4 张表的功能以及在 Linux 中的实现分别加以介绍。
BERT (Boot Error Record Table)  BERT 表如同其名称定义所描述的一样,主要用来记录在启动过程中出现的错误。系统的启动过程分为多个阶段,如果硬件平台在上电自检时发现某一个 CPU 出现异常无法启动,那么可以禁止激活这个 CPU,让其他 CPU 继续启动。这种机制称为 FRB(Fault Resilient Booting);如果在 OS 未接管平台的控制权限之前 firmware(如 BIOS 或者 UEFI)检测到错误,导致系统无法继续启动,可以通过 BIOS/FIRMWARE 将这种类型的错误写入到特定的存储位置。这样一来,在下一次的正常启动过程中,OS 可以通过特定的方法将之前保存的错误读取出来分析并处理。这就是 BERT 的主要用途。不过,也有可能是在系统运行过程中 firmware 检测到了致命错误,以至于 firmware 决定不通知 OS 而是直接启动(想想 CPU 风扇突然坏了,瞬间过热,如果不立刻重启会烧毁 CPU),在重启前 firmware 可以记录下相关的错误信息以便之后分析出错原因。在目前阶段,BERT 的用途还没有完全定下来,并且只有 BIOS/FIRMWARE 才有能力对 BERT 执行写入操作;对于 OS 而言,BERT 仅仅是一个只读的表。到目前为止,还没有一款 BIOS 提供对 BERT 的正式支持,因此,相关的代码也没有在 Linux 中实现。这也是到目前为止 APEI 体系中中唯一还没有在 Linux 中实现的一个模块。
  在 x86 平台的发展过程中,BERT 并不是第一种,也不会是最后一种用来记录硬件错误的方法,在过去乃至现在的很长一段时间内,BIOS/FIRMWARE 都是把特定的硬件错误记录到 BMC(Baseband Management Controller)中,再通过相应的管理程序进行错误解析。BERT 出现的意义在于希望采用一种统一的接口来记录特定类型的硬件错误(主要是一些致命的),从而简化 BIOS/FIRMWARE 和 OS 的实现。
ERST (Error Record Serialization Table)ERST 本质上是一个用来永久存储错误的抽象接口。软件可以通过 ERST 将各种错误信息保存到 ERST 中,再由 ERST 写入到可用于永久存储的物理介质中。ERST 并没有一个严格的定义来界定什么是“错误”,换言之,软件可以保存任何信息到 ERST 中,只要软件认为是有意义,有价值的信息就可以。这里用来存储的介质也未必就一定是 flash 或者 NVRAM 等常见的永久存储介质,它也可以是网络存储,如 NFS。用户不需要关心存储介质的类型,也不必关心具体的存储位置,只要确保使用 ERST 提供的标准读写接口,就可以方便的将错误信息进行保存和读取。这也是为什么说 ERST 是一个抽象接口的原因。
ERST 的主要作用就是用来存储各种硬件或者平台相关的错误,错误类型包括 Corrected Error(CE),Uncorrected Recoverable Error(UCR),以及 Uncorrected Non-Recoverable Error,或者说 Fatal Error。换言之,只要是软件可以记录的错误,都可以将其保存到 ERST 当中。加上之前谈到的 BERT 表,这样一来,无论系统运行在哪个阶段,当出现硬件或平台相关的错误时,通过 APEI 接口,都有办法将错误保存下来。这样一来就可以在之后通过适当的方法将错误读取出来进行分析,从而加快定位产生错误的原因并加以解决。
flash 的擦除操作
flash/NVRAM 这些永久存储介质不同于普通磁盘(SSD 磁盘除外)和内存,写入操作只能将数据 bit 位从 1 写为 0,当数据 bit 位变为 0 后,不能通过写入操作再变为 1。因此需要通过一个擦除操作将数据 bit 位从 0 变为 1 后才能执行新的写入。

ERST 是一个抽象接口,因此其提供的操作方法也都是抽象出来的动作行为。简单来说,ERST 提供了 3 种基本操作行为:读取(read),写入(write)和擦除(erase)。读取和写入很容易理解,而擦除操作主要是针对 flash,NVRAM 这些永久存储介质使用的。下面以写入操作为例来描述一下 ERST 的操作过程:
  • OS 需要初始化 ERST 指定格式的错误记录,包括一个特定的标记“ER”用来识别是否为 ERST 的记录项。值得说明的是 ERST 的写入操作为了可以在任何环境下都能使用,包括实时环境,其加锁操作使用的不是普通的 spinlock,而是 raw_spinlock 以确保其原子性。                        清单 1. ERST 的写入过程
    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
    31
    32
    33
    34
    35
    36
    在 drivers/acpi/apei/erst.c 中
    int erst_write(const struct cper_record_header *record)
    {
            int rc;
            unsigned long flags;
            struct cper_record_header *rcd_erange;

            if (erst_disable)
                    return -ENODEV;

            if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE))
                    return -EINVAL;

            if (erst_erange.attr & ERST_RANGE_NVRAM) {
                    if (!raw_spin_trylock_irqsave(&erst_lock, flags))
                            return -EBUSY;
                    rc = __erst_write_to_nvram(record);
                    raw_spin_unlock_irqrestore(&erst_lock, flags);
                    return rc;
            }

            if (record->record_length > erst_erange.size)
                    return -EINVAL;

            if (!raw_spin_trylock_irqsave(&erst_lock, flags))
                    return -EBUSY;
            memcpy(erst_erange.vaddr, record, record->record_length);
            rcd_erange = erst_erange.vaddr;
            /* signature for serialization system */
            memcpy(&rcd_erange->persistence_information, "ER", 2);

            rc = __erst_write_to_storage(0);
            raw_spin_unlock_irqrestore(&erst_lock, flags);

            return rc;
    }




  • 通过调用内嵌的 __erst_write_to_storage 函数,继续执行 APEI 规范中写入操作规定的的一系列 ACTION 完成最终的写入。这些动作的顺序依次为:                                               
    1
    2
    3
    4
    5
    6
    ACPI_ERST_BEGIN_WRITE,
    ACPI_ERST_SET_RECORD_OFFSET,
    ACPI_ERST_EXECUTE_OPERATION,
    ACPI_ERST_CHECK_BUSY_STATUS,
    ACPI_ERST_GET_COMMAND_STATUS,
    ACPI_ERST_END




    清单 2. ERST 写入操作的内部命令序列
    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    在 drivers/acpi/apei/erst.c 中
    static int __erst_write_to_storage(u64 offset)
    {
            struct apei_exec_context ctx;
            u64 timeout = FIRMWARE_TIMEOUT;
            u64 val;
            int rc;

            erst_exec_ctx_init(&ctx);
           rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_WRITE);
            if (rc)
                    return rc;
            apei_exec_ctx_set_input(&ctx, offset);
            rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
            if (rc)
                    return rc;
            rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
            if (rc)
                    return rc;
            for (;;) {
                    rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
                    if (rc)
                            return rc;
                    val = apei_exec_ctx_get_output(&ctx);
                    if (!val)
                            break;
                    if (erst_timedout(&timeout, SPIN_UNIT))
                            return -EIO;
            }
            rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
            if (rc)
                    return rc;
            val = apei_exec_ctx_get_output(&ctx);
            rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
            if (rc)
                    return rc;

            return erst_errno(val);
    }




返回列表