x86架构—MCA


  • 1. 概述
    • 1.1. 不可纠正的MCE(uncorrected machine-check error)
    • 1.2. 可纠正的MCE(corrected machine-check error)
    • 1.3. 额外功能
  • 2. Machine Check MSR
    • 2.1. Machine-Check Global Control MSRs
      • 2.1.1. IA32_MCG_CAP MSR
      • 2.1.2. IA32_MCG_STATUS MSR
      • 2.1.3. IA32_MCG_CTL MSR
      • 2.1.4. IA32_MCG_EXT_CTL MSR
      • 2.1.5. Enabling Local Machine Check
    • 2.2. 错误报告寄存器组(Error-Reporting Register Banks)
      • 2.2.1. IA32_MCi_CTL MSRs
      • 2.2.2. IA32_MCi_STATUS MSRS
      • 2.2.3. IA32_MCi_ADDR MSRs
      • 2.2.4. IA32_MCi_MISC MSRs
      • 2.2.5. IA32_MCi_CTL2 MSRs
  • 3. CMCI
  • 4. MCA的初始化
  • 5. MSR的读写
  • 6. 参考

1. 概述

Intel从奔腾4开始的CPU中增加了一种机制,称为MCA——Machine Check Architecture,它用来检测硬件(这里的Machine表示的就是硬件)错误,比如系统总线错误、ECC错误、奇偶校验错误、缓存错误、TLB错误等等。不仅硬件故障会引起MCE,不恰当的BIOS配置、firmware bug、软件bug也有可能引起MCE。

这套系统通过一定数量的MSR(Model Specific Register)来实现,这些MSR分为两个部分,一部分用来进行设置,另一部分用来描述发生的硬件错误

1.1. 不可纠正的MCE(uncorrected machine-check error)

当CPU检测到不可纠正的MCE(Machine Check Error)时,就会触发#MCMachine Check Exception, 中断号是十进制18),通常软件注册相关的函数来处理#MC,在这个函数中会通过读取MSR来收集MCE的错误信息,但是不被允许重启处理器

  • 当然由于发生的MCE可能是非常致命的,CPU直接重启了,没有办法完成MCE处理函数

  • 甚至有可能在MCE处理函数中又触发了不可纠正的MCE,也会导致系统直接重启

1.2. 可纠正的MCE(corrected machine-check error)

从CPUID的DisplayFamily_DisplayModel06H_1AH开始, CPU可以报告可纠正的机器检查错误信息, 并为软件提供可编程中断来响应MC错误, 称为可纠正机器检查错误中断(CMCI).

CPU检测到可纠正的MCE,当可纠正的MCE数量超过一定的阈值时,会触发CMCI(Corrected Machine Check Error Interrupt),此时软件可以捕捉到该中断并进行相应的处理。

CMCI是在MCA之后才加入的,算是对MCA的一个增强,在此之前软件只能通过轮询可纠正MCE相关的MSR才能实现相关的操作。

Corrected machine-check error interrupt (CMCI) 是MCA的增强特性。在原来的芯片里面,都是使用一种叫做threshold-based error reporting的机制来处理corrected error. 但是threshold-based error reporting需要系统软件周期性的轮询检测硬件的corrected MC errors,造成CPU的浪费。 CMCI 提供了一种机制,当corrected error发生侧次数到达阀值的时候,就会发送一个信号给本地的CPU来通知系统软件。

当然,系统软件可以通过IA32_MCi_CTL2 MSRs来控制该特性的开关

发现硬件错误时触发的异常(exception),中断号是18,异常的类型是abort:

1.3. 额外功能

支持机器检查架构CMCI英特尔64处理器还可以支持额外的增强功能,即支持从某些不可纠正可恢复机器检查错误中进行软件恢复

2. Machine Check MSR

上图基本包含了MCA相关的所有MSR。

它分为左右两个部分,左边的是全局的寄存器,右边表示的是多组寄存器。

i表示的是各个组的Index。这里的组有一个称呼是Error Reporting Register Bank。

MCA通过若干Bank的MSR寄存器来表示各种类型的MCE。

下面简单介绍一下这些寄存器。

2.1. Machine-Check Global Control MSRs

机器检查全局控制MSR包括IA32_MCG_CAPIA32_MCG_STATUS,以及可选的IA32_MCG_CTLIA32_MCG_EXT_CTL

2.1.1. IA32_MCG_CAP MSR

这个MSR描述了当前CPU处理MCA的能力,机器检查体系结构的信息, 具体每个位的作用如下所示:

BIT0-7:表示的是特定CPU支持可用的硬件单元错误报告库的个数(hardware unit error-reporting banks);

BIT8:1表示IA32_MCG_CTL有效,如果是0的话表示无效,读取该IA32_MCG_CTL这个MSR可能发生Exception(至少在UEFI下是这样);

BIT9:1表示IA32_MCG_EXT_CTL有效,反之无效,这个与BIT8的作用类似;

BIT10:1表示支持CMCI,但是CMCI是否能用还需要通过IA32_MCi_CTL2这个MSR的BIT30来使能

BIT11:1表示IA32_MCi_STATUS这个MSR的BIT56-55是保留的,BIT54-53是用来上报Threshold-based Error状态的;

BIT16-23:表示存在的Extended Machine Check State寄存器的个数;

BIT24:1表示CPU支持Software Error Recovery;

BIT25:1表示CPU支持增强版的MCA

BIT26:1表示支持更多的错误记录(需要UEFI、ACPI的支持);

BIT27:1表示支持Local Machine Check Exception;

2.1.2. IA32_MCG_STATUS MSR

该MSR记录了MCE发生时CPU的状态,主要的BIT位介绍如下:

  • Bit 0: Restart IP Valid. 表示程序的执行是否可以在被异常中断的指令处重新开始。
  • Bit 1: Error IP Valid. 表示被中断的指令是否与MCE错误直接相关。
  • Bit 2: Machine Check In Progress. 表示 machine check 正在进行中。
  • bit 3: 设置后说明生成本地machine-check exception. 这表示当前的机器检查事件仅传递给此逻辑处理器。

2.1.3. IA32_MCG_CTL MSR

这个寄存器的存在依赖于IA32_MCG_CAP这个MSR的BIT8。

这个寄存器主要用来Disable(写1)或者Enable(写全0)MCA功能

2.1.4. IA32_MCG_EXT_CTL MSR

这个寄存器同样依赖于IA32_MCA_CAP这个MSR,这次依赖的是BIT9。

该MSR的BIT位说明如下图所示:

目前有就BIT0有用,用来Disable(写1)或者Enable(写0)LMCE,这个LMCE的功能就是使硬件能够将某些MCE发送给单个的逻辑处理器

2.1.5. Enabling Local Machine Check

LMCE的预期用途需要平台软件和系统软件的正确配置。 平台软件可以通过设置IA32_FEATURE_CONTROL MSR(MSR地址3AH)中的位20(LMCE_ON)来打开LMCE。

系统软件必须确保在尝试设置IA32_MCG_EXT_CTL.LMCE_EN(位0)之前设置IA32_FEATURE_CONTROL.Lock(位0)和IA32_FEATURE_CONTROL.LMCE_ON(位20)。

当系统软件启用LMCE时,硬件将确定是否只能将特定错误传递给单个逻辑处理器。 软件不应假设硬件可以选择作为LMCE提供的错误类型。

2.2. 错误报告寄存器组(Error-Reporting Register Banks)

以上都是全局的MSR.

每个错误报告寄存器库可以包含IA32_MCi_CTL,IA32_MCi_STATUS,IA32_MCi_ADDR和IA32_MCi_MISC MSR。报告库的数量由IA32_MCG_CAP MSR(地址0179H)的位[7:0]表示。第一个错误报告寄存器(IA32_MC0_CTL)始终从地址400H开始

有关Pentium 4,Intel Atom和Intel Xeon处理器中错误报告寄存器的地址,请参阅“英特尔?64和IA-32架构软件开发人员手册”第4卷第2章“特定于型号的寄存器(MSR)”。以及错误报告寄存器P6系列处理器的地址。

这些寄存器的第一个是IA32_MC0_CTL,它的地址一般都是400H。

之后接着的是IA32_MC0_STATUS,IA32_MC0_ADDR,IA32_MC0_MISC,但是在之后并不是IA32_MC0_CTL2,而是IA32_MC1_CTL;对于IA32_MCi_CTL2来说,它的地址跟上面的这些不在一起,第一个IA32_MC0_CTL2是在280H,之后是IA32_MC1_CTL2在281H,以此类推。

2.2.1. IA32_MCi_CTL MSRs

IA32_MCi_CTL MSR控制#MC的信号,以发现由特定硬件单元(或硬件单元组)产生的错误。

每个Bank的CTL的作用是用来控制在发生哪些MCA的时候来触发#MC

这里的64个BIT位,设置某个BIT位就会使对应BIT位的MCA类型在发生触发#MC

2.2.2. IA32_MCi_STATUS MSRS

这类MSR的作用就是显示MCE信息:

注意只有当VAL这个BIT位(BIT63)为1时才表示发生了对应这个Bank的MCE。当MCE发生了,软件需要给这个VAL位写0来清零(如果有可能的话,因为对于不可纠正的MCE可能软件会 来不及写),不能往这位写1,会出现Exception。

BIT0-15,BIT16-31:这个两个部分都表示MCE的错误类型,前者是通用的,后者是跟CPU有关的;

BIT58:1表示IA32_MCi_ADDR这个MSR是有效的,反之无效;

BIT59:1表示IA32_MCi_MISC这个MSR是有效的,反之无效;这两个BIT是因为不同MCE错误并不是都需要ADDR和MSIC这样的MSR;

BIT60:这个位于IA32_MCi_CTL中的位是对应的,那边使能了,这里就是1;

BIT61:表示MCE是不可纠正的;

BIT62:表示发生了二次的MCE,这个时候到底这个Bank表示的是哪一次的MCE信息,需要根据一定的规则来确定:

其它寄存器不介绍了, 详细看手册

2.2.3. IA32_MCi_ADDR MSRs

这个地址指向内存中导致MCE的代码或者数据

注意这个地址在不同的内存模型下可以是偏移地址,虚拟地址和物理地址中的一种,这个需要MISC这个MSR来确定,下面会讲到。

这个MSR也可以手动清零,写1会出错。

2.2.4. IA32_MCi_MISC MSRs

这个寄存器的BIT位说明如下:

这里的Address Mode说明如下:

2.2.5. IA32_MCi_CTL2 MSRs

这个寄存器就是为CMCI使用的,BIT位说明如下:

一个是用于使能CMCI,另一个是用来设置CMCI的阈值。

除了上述的MSR之外,在IA32_MCG_CAP这个MSR的说明中还提到过它的BIT16-23还提到了额外的MSR,它们称为Extended Machine Check State,这些MSR的描述如下:

上图实际上只展示了非64位CPU的MSR,还有一个64位CPU的MSR,这里就不再多说。

需要注意,实际上上面的这些寄存器并不需要自己一个个去对比和解析,Intel提供了一个工具叫做MCE Decoder,可以用来解析MCE

另外在Intel的开发者手册中有专门的一个章节解析MCE错误:《CHAPTER 16 INTERPRETING MACHINE-CHECK ERROR CODES》。

3. CMCI

前面以及提到,CMCI是后期加入到MCA的一种机制,它将错误上报的阈值操作从原始的软件轮询变成了硬件中断触发

一个CPU是否支持CMCI需要查看IA32_MCG_CAP的BIT10,如果该位是1就表示支持。

另外CMCI默认是关闭的,需要通过IA32_MCi_CTL2的BIT30来打开,并设置BIT0-14的阈值,注意 每个Bank都要设置!!!

设置的时候首先写1IA32_MCi_CTL2BIT30,再读取这个值,如果值变成了1,说明CMCI使能了,否则就是CPU不支持CMCI;之后再写阈值到BIT0-14,如果读出来的值是0,表示不支持阈值,否则就是成功设置了阈值。

CMCI是通过Local ACPI来实现的,具体的示意图如下:

Local ACPI Table中有专门处理CMCI的寄存器,称为LVT CMCI Register (FEE0 02F0H):

BIT0-7:中断向量;

BIT8-10:Delivery Mode,比如SMI,NMI等;

BIT12:Delivery Status,0表示没有中断,1表示中断正在发生;

BIT17:Interrupt Mask,0表示接收中断,1表示屏蔽中断;

关于CMCI的初始化和CMCI处理函数的实现,手册上有部分的介绍,不过没有什么源代码可以借鉴,这个不展开了。

4. MCA的初始化

手册上有一个伪代码可供参考

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
40
41
42
43
44
45
46
47
48
49
50
51
IF CPU supports MCE
THEN
    IF CPU supports MCA
    THEN
        IF (IA32_MCG_CAP.MCG_CTL_P = 1)
        (* IA32_MCG_CTL register is present *)
        THEN
            IA32_MCG_CTL ← FFFFFFFFFFFFFFFFH;
            (* enables all MCA features *)
        FI
        IF (IA32_MCG_CAP.MCG_LMCE_P = 1 and IA32_FEATURE_CONTROL.LOCK = 1 and IA32_FEATURE_CONTROL.LMCE_ON= 1)
        (* IA32_MCG_EXT_CTL register is present and platform has enabled LMCE to permit system software to use LMCE *)
        THEN
            IA32_MCG_EXT_CTL ← IA32_MCG_EXT_CTL | 01H;
            (* System software enables LMCE capability for hardware to signal MCE to a single logical processor*)
        FI
        (* Determine number of error-reporting banks supported *)
        COUNT← IA32_MCG_CAP.Count;
        MAX_BANK_NUMBER ← COUNT - 1;
        IF (Processor Family is 6H and Processor EXTMODEL:MODEL is less than 1AH)
        THEN
            (* Enable logging of all errors except for MC0_CTL register *)
            FOR error-reporting banks (1 through MAX_BANK_NUMBER)
                DO
                    IA32_MCi_CTL ← 0FFFFFFFFFFFFFFFFH;
                OD
        ELSE
            (* Enable logging of all errors including MC0_CTL register *)
            FOR error-reporting banks (0 through MAX_BANK_NUMBER)
                DO
                    IA32_MCi_CTL ← 0FFFFFFFFFFFFFFFFH;
                OD
        FI
        (* BIOS clears all errors only on power-on reset *)
        IF (BIOS detects Power-on reset)
        THEN
            FOR error-reporting banks (0 through MAX_BANK_NUMBER)
                DO
                    IA32_MCi_STATUS ← 0;
                OD
        ELSE
            FOR error-reporting banks (0 through MAX_BANK_NUMBER)
                DO
                    (Optional for BIOS and OS) Log valid errors
                    (OS only) IA32_MCi_STATUS ← 0;
                OD
        FI
    FI
    Setup the Machine Check Exception (#MC) handler for vector 18 in IDT
    Set the MCE bit (bit 6) in CR4 register to enable Machine-Check Exceptions
FI

5. MSR的读写

x86平台读写MSR有专门的指令,分别是rdmsr和wrmsr。下面是MSR读写的一个基本实现:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
  Returns a 64-bit Machine Specific Register(MSR).
  Reads and returns the 64-bit MSR specified by Index. No parameter checking is
  performed on Index, and some Index values may cause CPU exceptions. The
  caller must either guarantee that Index is valid, or the caller must set up
  exception handlers to catch the exceptions. This function is only available
  on IA-32 and X64.
  @param  Index The 32-bit MSR index to read.
  @return The value of the MSR identified by Index.
**/
UINT64
EFIAPI
AsmReadMsr64 (
  IN      UINT32                    Index
  )
{
  UINT32 LowData;
  UINT32 HighData;
 
  __asm__ __volatile__ (
    "rdmsr"
    : "=a" (LowData),   // %0
      "=d" (HighData)   // %1
    : "c"  (Index)      // %2
    );
   
  return (((UINT64)HighData) << 32) | LowData;
}
 
/**
  Writes a 64-bit value to a Machine Specific Register(MSR), and returns the
  value.
  Writes the 64-bit value specified by Value to the MSR specified by Index. The
  64-bit value written to the MSR is returned. No parameter checking is
  performed on Index or Value, and some of these may cause CPU exceptions. The
  caller must either guarantee that Index and Value are valid, or the caller
  must establish proper exception handlers. This function is only available on
  IA-32 and X64.
  @param  Index The 32-bit MSR index to write.
  @param  Value The 64-bit value to write to the MSR.
  @return Value
**/
UINT64
EFIAPI
AsmWriteMsr64 (
  IN      UINT32                    Index,
  IN      UINT64                    Value
  )
{
  UINT32 LowData;
  UINT32 HighData;
 
  LowData  = (UINT32)(Value);
  HighData = (UINT32)(Value >> 32);
 
  __asm__ __volatile__ (
    "wrmsr"
    :
    : "c" (Index),
      "a" (LowData),
      "d" (HighData)
    );
   
  return Value;
}