SecCoreStartupWithStack

这个是第一个C语言环境的函数,主要处理以下事务:

  • 为存储解压服务的信息预留内存buffer
  • 初始化IDT
  • ProcessLibraryConstructorList:初始化构造函数
  • InitializeFloatingPointUnits
  • 屏蔽8259
  • 初始化APIC
  • 关闭APIC中断
  • 初始化Debug Agent

为存储解压服务的信息预留内存buffer

  //
  // To ensure SMM can't be compromised on S3 resume, we must force re-init of
  // the BaseExtractGuidedSectionLib. Since this is before library contructors
  // are called, we must use a loop rather than SetMem.
  //
  Table = (UINT8 *)(UINTN)FixedPcdGet64 (PcdGuidedExtractHandlerTableAddress);
  for (Index = 0;
       Index < FixedPcdGet32 (PcdGuidedExtractHandlerTableSize);
       ++Index)
  {
    Table[Index] = 0;
  }

为存储解压函数信息准备内存buffer

  • 起始地址(PcdGuidedExtractHandlerTableAddress):0x807000
  • 终止地址:0x807FFF
  • 大小(PcdGuidedExtractHandlerTableSize):0x1000

BaseExtractGuidedSectionLib这个library提供注册解压服务的服务(ExtractGuidedSectionRegisterHandlers),所谓的注册就是将解压服务的信息:

  • GUID
  • EXTRACT_GUIDED_SECTION_GET_INFO_HANDLER:获取压缩文件的信息函数
  • EXTRACT_GUIDED_SECTION_DECODE_HANDLER:解压压缩文件的函数

存入到上述特意预留测地址处,方便后续调用。

目前OVMF默认使用的压缩和解压算法是Lzma,因此后续函数会去注册gLzmaCustomDecompressGuid

初始化IDT

初始化流程如下:

  • 按照8字节的门描述符结构,初始化34个如此结构的buffer,总大小为8*34
  • 将申请的buffer地址和大小信息,存入IdtDescriptor
  • 将IdtDescriptor地址存入idt寄存器
  //
  // Initialize IDT - Since this is before library constructors are called,
  // we use a loop rather than CopyMem.
  //
  IdtTableInStack.PeiService = NULL;

  for (Index = 0; Index < SEC_IDT_ENTRY_COUNT; Index++) {
    //
    // Declare the local variables that actually move the data elements as
    // volatile to prevent the optimizer from replacing this function with
    // the intrinsic memcpy()
    //
    CONST UINT8     *Src;
    volatile UINT8  *Dst;
    UINTN           Byte;

    Src = (CONST UINT8 *)&mIdtEntryTemplate;
    Dst = (volatile UINT8 *)&IdtTableInStack.IdtTable[Index];
    for (Byte = 0; Byte < sizeof (mIdtEntryTemplate); Byte++) {
      Dst[Byte] = Src[Byte];
    }
  }

#define SEC_IDT_ENTRY_COUNT  34

从上述代码可以看到,系统为IDT随机分配一段8*34大小的buffer,并将其按每八个字节的结构(mIdtEntryTemplate)初始化,一共有34个(SEC_IDT_ENTRY_COUNT)这样的结构

从下面mIdtEntryTemplate的结构,我们可以看到,这里实际存放的是interrupt或者exception函数的地址,当触发了某种中断后,根据其中断号码,去相应的位置调用其对应的函数。

//
// Template of an IDT entry pointing to 10:FFFFFFE4h.
//
IA32_IDT_GATE_DESCRIPTOR  mIdtEntryTemplate = {
  {                                      // Bits
    0xffe4,                              // OffsetLow
    0x10,                                // Selector
    0x0,                                 // Reserved_0
    IA32_IDT_GATE_TYPE_INTERRUPT_32,     // GateType
    0xffff                               // OffsetHigh
  }
};

#define IA32_IDT_GATE_TYPE_INTERRUPT_32  0x8E

///
/// Byte packed structure for an IA-32 Interrupt Gate Descriptor.
///
typedef union {
  struct {
    UINT32    OffsetLow  : 16; ///< Offset bits 15..0.
    UINT32    Selector   : 16; ///< Selector.
    UINT32    Reserved_0 : 8;  ///< Reserved.
    UINT32    GateType   : 8;  ///< Gate Type.  See #defines above.
    UINT32    OffsetHigh : 16; ///< Offset bits 31..16.
  } Bits;
  UINT64    Uint64;
} IA32_IDT_GATE_DESCRIPTOR;

从windbg中可以读取出其内容如下:

0081fedc a8 c4 10 00 00 8e fc ff  ........
0081fee4 b9 c4 10 00 00 8e fc ff  ........
0081feec ca c4 10 00 00 8e fc ff  ........
0081fef4 db c4 10 00 00 8e fc ff  ........
0081fefc ec c4 10 00 00 8e fc ff  ........
0081ff04 fd c4 10 00 00 8e fc ff  ........
0081ff0c 0e c5 10 00 00 8e fc ff  ........
0081ff14 1f c5 10 00 00 8e fc ff  ........
0081ff1c 30 c5 10 00 00 8e fc ff  0.......
0081ff24 41 c5 10 00 00 8e fc ff  A.......
0081ff2c 52 c5 10 00 00 8e fc ff  R.......
0081ff34 63 c5 10 00 00 8e fc ff  c.......
0081ff3c 74 c5 10 00 00 8e fc ff  t.......
0081ff44 85 c5 10 00 00 8e fc ff  ........
0081ff4c 96 c5 10 00 00 8e fc ff  ........
0081ff54 a7 c5 10 00 00 8e fc ff  ........
0081ff5c b8 c5 10 00 00 8e fc ff  ........
0081ff64 c9 c5 10 00 00 8e fc ff  ........
0081ff6c da c5 10 00 00 8e fc ff  ........
0081ff74 eb c5 10 00 00 8e fc ff  ........
0081ff7c e4 ff 10 00 00 8e ff ff  ........
0081ff84 e4 ff 10 00 00 8e ff ff  ........
0081ff8c e4 ff 10 00 00 8e ff ff  ........
0081ff94 e4 ff 10 00 00 8e ff ff  ........
0081ff9c e4 ff 10 00 00 8e ff ff  ........
0081ffa4 e4 ff 10 00 00 8e ff ff  ........
0081ffac e4 ff 10 00 00 8e ff ff  ........
0081ffb4 e4 ff 10 00 00 8e ff ff  ........
0081ffbc e4 ff 10 00 00 8e ff ff  ........
0081ffc4 e4 ff 10 00 00 8e ff ff  ........
0081ffcc e4 ff 10 00 00 8e ff ff  ........
0081ffd4 e4 ff 10 00 00 8e ff ff  ........

从上述内容可以看出,此时的IDT已经存放了21个interrupt或者exception函数。其中我们使用的的Source Debug功能,就在其中。

AsmWriteIdtr

将IDT的信息加载到idt寄存器中

  IdtDescriptor.Base  = (UINTN)&IdtTableInStack.IdtTable;
  IdtDescriptor.Limit = (UINT16)(sizeof (IdtTableInStack.IdtTable) - 1);

  AsmWriteIdtr (&IdtDescriptor);
/**
  Writes the current Interrupt Descriptor Table Register(IDTR) descriptor.

  Writes the current IDTR descriptor and returns it in Idtr. This function is
  only available on IA-32 and x64.

  If Idtr is NULL, then ASSERT().

  @param  Idtr  The pointer to a IDTR descriptor.

**/
VOID
EFIAPI
AsmWriteIdtr (
  IN      CONST IA32_DESCRIPTOR  *Idtr
  )
{
  ASSERT (Idtr != NULL);
  InternalX86WriteIdtr (Idtr);
}

/**
  Writes the current Interrupt Descriptor Table Register(GDTR) descriptor.

  Writes the current IDTR descriptor and returns it in Idtr. This function is
  only available on IA-32 and x64.

  @param  Idtr  The pointer to a IDTR descriptor.

**/
VOID
EFIAPI
InternalX86WriteIdtr (
  IN      CONST IA32_DESCRIPTOR  *Idtr
  )
{
  _asm {
    mov     eax, Idtr
    pushfd
    cli
    lidt    fword ptr [eax]
    popfd
  }
}

补充:IDT

在计算机系统中,IDT(Interrupt Descriptor Table,中断描述符表) 是x86架构(以及兼容架构)中负责管理中断异常处理的核心数据结构,相当于系统的“中断导航系统”。

  • 中断:外部硬件触发的 “请求信号”(如键盘输入、鼠标点击、硬盘 I/O 完成、定时器计时结束)
  • 异常:CPU 内部检测到的 “软件错误”(如除零错误、非法指令、内存访问越界、调试断点)。

门描述符

IDT是一个包含256个条目的数组,每个条目是门描述符(Gate Descriptor),其结构如下:

字节偏移 字段 位数 功能说明
0-1 偏移量(低16位) 16 处理程序入口地址的低16位(与高16位拼接为32位完整地址)
2-3 段选择子 16 指向GDT/LDT中“处理程序所在代码段”的索引,确保处理程序在合法内存空间执行
4 零填充 8 固定为0,用于内存对齐
5 类型与权限 8 4位“类型字段”(区分门类型)+ 2位“DPL(描述符特权级)”+ 2位保留位
6-7 偏移量(高16位) 16 处理程序入口地址的高16位

门描述符类型有以下三种:

门类型 32位类型字段(二进制) 64位类型字段(十六进制) 核心行为特性 典型应用场景
中断门 1110B(0xE) 0x8E(DPL=0)/0xCE(DPL=3) 1. 自动关闭IF标志(IF=0),禁止可屏蔽中断嵌套,避免硬件冲突;
2. 特权级切换时强制换栈(如用户态→内核态);
3. 处理后通过IRETD/IRETQ返回
硬件中断(键盘、定时器、网卡)、高优先级异常(NMI)
陷阱门 1111B(0xF) 0x8F(DPL=0)/0xCF(DPL=3) 1. 保持IF标志(IF不变),允许中断嵌套;
2. 仅当CPL>DPL时换栈(如用户态触发内核系统调用);
3. 支持主动软件调用
系统调用(Linux int 0x80)、调试异常(断点、单步)、软件错误(溢出)
任务门 1001B(0x9) 不支持(废弃) 1. 不跳转处理程序,触发任务切换(加载TSS上下文);
2. 偏移量无效,仅用段选择子指向TSS;
3. 自动保存原任务状态
32位模式多任务调度(现代系统极少用,被线程调度替代)

ProcessLibraryConstructorList

此函数是自动生成的,用于处理该SecMain中引用的构造函数库,其位于:Build\Ovmf3264\NOOPT_VS2019\IA32\OvmfPkg\Sec\SecMain\DEBUG\AutoGen.c。函数本体如下

VOID
EFIAPI
ProcessLibraryConstructorList (
  VOID
  )
{
  RETURN_STATUS  Status;

  Status = BaseDebugLibSerialPortConstructor ();
  ASSERT_RETURN_ERROR (Status);

  Status = AcpiTimerLibConstructor ();
  ASSERT_RETURN_ERROR (Status);

  Status = LzmaDecompressLibConstructor ();
  ASSERT_RETURN_ERROR (Status);

}

从上述函数中,我们可以看到主要处理以下事务:

  • BaseDebugLibSerialPortConstructor:初始化串口
  • AcpiTimerLibConstructor:初始化ACPI用到的IO space
  • LzmaDecompressLibConstructor:注册解压服务

BaseDebugLibSerialPortConstructor

这个构造函数用于初始化串口,其原理和ResetVector module中实现的原理一样。

/**
  The constructor function initialize the Serial Port Library

  @retval EFI_SUCCESS   The constructor always returns RETURN_SUCCESS.

**/
RETURN_STATUS
EFIAPI
BaseDebugLibSerialPortConstructor (
  VOID
  )
{
  return SerialPortInitialize ();
}
// ---------------------------------------------
// UART Register Offsets
// ---------------------------------------------
#define BAUD_LOW_OFFSET    0x00
#define BAUD_HIGH_OFFSET   0x01
#define IER_OFFSET         0x01
#define LCR_SHADOW_OFFSET  0x01
#define FCR_SHADOW_OFFSET  0x02
#define IR_CONTROL_OFFSET  0x02
#define FCR_OFFSET         0x02
#define EIR_OFFSET         0x02
#define BSR_OFFSET         0x03
#define LCR_OFFSET         0x03
#define MCR_OFFSET         0x04
#define LSR_OFFSET         0x05
#define MSR_OFFSET         0x06

// ---------------------------------------------
// UART Register Bit Defines
// ---------------------------------------------
#define LSR_TXRDY  0x20
#define LSR_RXDA   0x01
#define DLAB       0x01
#define MCR_DTRC   0x01
#define MCR_RTS    0x02
#define MSR_CTS    0x10
#define MSR_DSR    0x20
#define MSR_RI     0x40
#define MSR_DCD    0x80

// ---------------------------------------------
// UART Settings
// ---------------------------------------------
UINT16  gUartBase = 0x3F8;
UINTN   gBps      = 115200;
UINT8   gData     = 8;
UINT8   gStop     = 1;
UINT8   gParity   = 0;
UINT8   gBreakSet = 0;

/**
  Initialize the serial device hardware.

  If no initialization is required, then return RETURN_SUCCESS.
  If the serial device was successfully initialized, then return RETURN_SUCCESS.
  If the serial device could not be initialized, then return RETURN_DEVICE_ERROR.

  @retval RETURN_SUCCESS        The serial device was initialized.
  @retval RETURN_DEVICE_ERROR   The serial device could not be initialized.

**/
RETURN_STATUS
EFIAPI
SerialPortInitialize (
  VOID
  )
{
  UINTN  Divisor;
  UINT8  OutputData;
  UINT8  Data;

  //
  // Map 5..8 to 0..3
  //
  Data = (UINT8)(gData - (UINT8)5);

  //
  // Calculate divisor for baud generator
  //
  Divisor = 115200 / gBps;

  //
  // Set communications format
  //
  OutputData = (UINT8)((DLAB << 7) | (gBreakSet << 6) | (gParity << 3) | (gStop << 2) | Data);
  IoWrite8 (gUartBase + LCR_OFFSET, OutputData);

  //
  // Configure baud rate
  //
  IoWrite8 (gUartBase + BAUD_HIGH_OFFSET, (UINT8)(Divisor >> 8));
  IoWrite8 (gUartBase + BAUD_LOW_OFFSET, (UINT8)(Divisor & 0xff));

  //
  // Switch back to bank 0
  //
  OutputData = (UINT8)((gBreakSet << 6) | (gParity << 3) | (gStop << 2) | Data);
  IoWrite8 (gUartBase + LCR_OFFSET, OutputData);

  return RETURN_SUCCESS;
}

AcpiTimerLibConstructor

这个构造函数,SEC阶段使用的库实例是OvmfPkg\Library\AcpiTimerLib\BaseRomAcpiTimerLib.inf,用来:

  • decode ACPI base address
  • 使能ACPI
  • 获取ACPI timer的地址
/**
  The constructor function enables ACPI IO space.

  If ACPI I/O space not enabled, this function will enable it.
  It will always return RETURN_SUCCESS.

  @retval EFI_SUCCESS   The constructor always returns RETURN_SUCCESS.

**/
RETURN_STATUS
EFIAPI
AcpiTimerLibConstructor (
  VOID
  )
{
  UINT16  HostBridgeDevId;
  UINTN   Pmba;
  UINT32  PmbaAndVal;
  UINT32  PmbaOrVal;
  UINTN   AcpiCtlReg;
  UINT8   AcpiEnBit;

  //
  // Query Host Bridge DID to determine platform type
  //
  HostBridgeDevId = PciRead16 (OVMF_HOSTBRIDGE_DID);
  switch (HostBridgeDevId) {
    case INTEL_82441_DEVICE_ID:
      Pmba       = POWER_MGMT_REGISTER_PIIX4 (PIIX4_PMBA);
      PmbaAndVal = ~(UINT32)PIIX4_PMBA_MASK;
      PmbaOrVal  = PIIX4_PMBA_VALUE;
      AcpiCtlReg = POWER_MGMT_REGISTER_PIIX4 (PIIX4_PMREGMISC);
      AcpiEnBit  = PIIX4_PMREGMISC_PMIOSE;
      break;
    case INTEL_Q35_MCH_DEVICE_ID:
      Pmba       = POWER_MGMT_REGISTER_Q35 (ICH9_PMBASE);
      PmbaAndVal = ~(UINT32)ICH9_PMBASE_MASK;
      PmbaOrVal  = ICH9_PMBASE_VALUE;
      AcpiCtlReg = POWER_MGMT_REGISTER_Q35 (ICH9_ACPI_CNTL);
      AcpiEnBit  = ICH9_ACPI_CNTL_ACPI_EN;
      break;
    case CLOUDHV_DEVICE_ID:
      return RETURN_SUCCESS;
    default:
      DEBUG ((
        DEBUG_ERROR,
        "%a: Unknown Host Bridge Device ID: 0x%04x\n",
        __func__,
        HostBridgeDevId
        ));
      ASSERT (FALSE);
      return RETURN_UNSUPPORTED;
  }

  //
  // Check to see if the Power Management Base Address is already enabled
  //
  if ((PciRead8 (AcpiCtlReg) & AcpiEnBit) == 0) {
    //
    // If the Power Management Base Address is not programmed,
    // then program it now.
    //
    PciAndThenOr32 (Pmba, PmbaAndVal, PmbaOrVal);

    //
    // Enable PMBA I/O port decodes
    //
    PciOr8 (AcpiCtlReg, AcpiEnBit);
  }

  return RETURN_SUCCESS;
}

OVMF支持两个硬件平台:I440FX和Q35。本文采用的是Q35。需要用到以下datasheet

从代码,我们可以直接看到其主要配置以下寄存器

  • ICH9_PMBASE(0x40):填入ICH9的PmBase(0x600)
  • ICH9_ACPI_CNTL(0x44):将bit 7置1,启用ACPI

通过查询ICH9 datasheet,我们可以找到上述操作的定义

  • ICH9_PMBASE(ACPI Base Address Register)

    Offset Address: 40h–43h

    Sets base address for ACPI I/O registers, GPIO registers and TCO I/O registers. These registers can be mapped anywhere in the 64-K I/O space on 128-byte boundaries.

    Bit Description
    31:16 Reserved
    15:7 Base Address — R/W. This field provides 128 bytes of I/O space for ACPI, GPIO, and
    TCO logic. This is placed on a 128-byte boundary
    6:1 Reserved
    0 Resource Type Indicator (RTE) — RO. Hardwired to 1 to indicate I/O space.
  • ICH9_ACPI_CNTL(ACPI Control Register

    在这里插入图片描述

LzmaDecompressLibConstructor

这个函数主要是为了将解压服务,注册到之前预留好的buffer处(0x807000 ~ 0x807FFFF)

/**
  Register LzmaDecompress and LzmaDecompressGetInfo handlers with LzmaCustomerDecompressGuid.

  @retval  RETURN_SUCCESS            Register successfully.
  @retval  RETURN_OUT_OF_RESOURCES   No enough memory to store this handler.
**/
EFI_STATUS
EFIAPI
LzmaDecompressLibConstructor (
  VOID
  )
{
  return ExtractGuidedSectionRegisterHandlers (
           &gLzmaCustomDecompressGuid,
           LzmaGuidedSectionGetInfo,
           LzmaGuidedSectionExtraction
           );
}
/**
  Registers handlers of type EXTRACT_GUIDED_SECTION_GET_INFO_HANDLER and EXTRACT_GUIDED_SECTION_DECODE_HANDLER
  for a specific GUID section type.

  Registers the handlers specified by GetInfoHandler and DecodeHandler with the GUID specified by SectionGuid.
  If the GUID value specified by SectionGuid has already been registered, then return RETURN_ALREADY_STARTED.
  If there are not enough resources available to register the handlers  then RETURN_OUT_OF_RESOURCES is returned.

  If SectionGuid is NULL, then ASSERT().
  If GetInfoHandler is NULL, then ASSERT().
  If DecodeHandler is NULL, then ASSERT().

  @param[in]  SectionGuid    A pointer to the GUID associated with the the handlers
                             of the GUIDed section type being registered.
  @param[in]  GetInfoHandler The pointer to a function that examines a GUIDed section and returns the
                             size of the decoded buffer and the size of an optional scratch buffer
                             required to actually decode the data in a GUIDed section.
  @param[in]  DecodeHandler  The pointer to a function that decodes a GUIDed section into a caller
                             allocated output buffer.

  @retval  RETURN_SUCCESS           The handlers were registered.
  @retval  RETURN_OUT_OF_RESOURCES  There are not enough resources available to register the handlers.

**/
RETURN_STATUS
EFIAPI
ExtractGuidedSectionRegisterHandlers (
  IN CONST  GUID                                     *SectionGuid,
  IN        EXTRACT_GUIDED_SECTION_GET_INFO_HANDLER  GetInfoHandler,
  IN        EXTRACT_GUIDED_SECTION_DECODE_HANDLER    DecodeHandler
  )
{
  UINT32                               Index;
  RETURN_STATUS                        Status;
  EXTRACT_GUIDED_SECTION_HANDLER_INFO  *HandlerInfo;

  //
  // Check input parameter
  //
  ASSERT (SectionGuid != NULL);
  ASSERT (GetInfoHandler != NULL);
  ASSERT (DecodeHandler != NULL);

  //
  // Get the registered handler information
  //
  Status = GetExtractGuidedSectionHandlerInfo (&HandlerInfo);
  if (RETURN_ERROR (Status)) {
    return Status;
  }

  //
  // Search the match registered GetInfo handler for the input guided section.
  //
  ASSERT (HandlerInfo != NULL);
  for (Index = 0; Index < HandlerInfo->NumberOfExtractHandler; Index++) {
    if (CompareGuid (HandlerInfo->ExtractHandlerGuidTable + Index, SectionGuid)) {
      //
      // If the guided handler has been registered before, only update its handler.
      //
      HandlerInfo->ExtractDecodeHandlerTable[Index]  = DecodeHandler;
      HandlerInfo->ExtractGetInfoHandlerTable[Index] = GetInfoHandler;
      return RETURN_SUCCESS;
    }
  }

  //
  // Check the global table is enough to contain new Handler.
  //
  if (HandlerInfo->NumberOfExtractHandler >= PcdGet32 (PcdMaximumGuidedExtractHandler)) {
    return RETURN_OUT_OF_RESOURCES;
  }

  //
  // Register new Handler and guid value.
  //
  CopyGuid (HandlerInfo->ExtractHandlerGuidTable + HandlerInfo->NumberOfExtractHandler, SectionGuid);
  HandlerInfo->ExtractDecodeHandlerTable[HandlerInfo->NumberOfExtractHandler]    = DecodeHandler;
  HandlerInfo->ExtractGetInfoHandlerTable[HandlerInfo->NumberOfExtractHandler++] = GetInfoHandler;

  return RETURN_SUCCESS;
}

从上述代码,我们可以看到处理流程如下:

  • 获取当前解压库的信息:地址,包含的解压服务数目等

  • 判断这次需要注册的解压服务,是否已经存在(通过GUID判断),如果存在,则更新解压服务

  • 如果是新的解压服务,那么就将其GUIID,解压函数(DecodeHandler),获取压缩文件信息函数(GetInfoHandler),记录到解压库中。从下面的结构体,可以看出。这几个信息,是分组存放的。

    typedef struct {
      UINT32                                     Signature;
      UINT32                                     NumberOfExtractHandler;
      GUID                                       *ExtractHandlerGuidTable;
      EXTRACT_GUIDED_SECTION_DECODE_HANDLER      *ExtractDecodeHandlerTable;
      EXTRACT_GUIDED_SECTION_GET_INFO_HANDLER    *ExtractGetInfoHandlerTable;
    } EXTRACT_GUIDED_SECTION_HANDLER_INFO;
    

要想理解其分组规则,则需要看下GetExtractGuidedSectionHandlerInfo这个函数:

这个除了负责,获取当前解压服务库的信息。还负责初始化,解压服务库。

/**
  HandlerInfo table address is set by PcdGuidedExtractHandlerTableAddress, which is used to store
  the registered guid and Handler list. When it is initialized, it will be directly returned.
  Or, HandlerInfo table will be initialized in this function.

  @param[in, out]  InfoPointer   The pointer to the handler information structure.

  @retval  RETURN_SUCCESS            HandlerInfo table can be used to store guid and function tables.
  @retval  RETURN_OUT_OF_RESOURCES   HandlerInfo table address is not writable.
**/
RETURN_STATUS
GetExtractGuidedSectionHandlerInfo (
  IN OUT EXTRACT_GUIDED_SECTION_HANDLER_INFO  **InfoPointer
  )
{
  EXTRACT_GUIDED_SECTION_HANDLER_INFO  *HandlerInfo;

  //
  // Set the available memory address to handler info.
  //
  HandlerInfo = (EXTRACT_GUIDED_SECTION_HANDLER_INFO *)(VOID *)(UINTN)PcdGet64 (PcdGuidedExtractHandlerTableAddress);
  if (HandlerInfo == NULL) {
    *InfoPointer = NULL;
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // First check whether the handler information structure is initialized.
  //
  if (HandlerInfo->Signature == EXTRACT_HANDLER_INFO_SIGNATURE) {
    //
    // The handler information has been initialized and is returned.
    //
    *InfoPointer = HandlerInfo;
    return RETURN_SUCCESS;
  }

  //
  // Try to initialize the handler information structure
  //
  HandlerInfo->Signature = EXTRACT_HANDLER_INFO_SIGNATURE;
  if (HandlerInfo->Signature != EXTRACT_HANDLER_INFO_SIGNATURE) {
    //
    // The handler information structure was not writeable because the memory is not ready.
    //
    *InfoPointer = NULL;
    return RETURN_OUT_OF_RESOURCES;
  }

  //
  // Init HandlerInfo structure
  //
  HandlerInfo->NumberOfExtractHandler    = 0;
  HandlerInfo->ExtractHandlerGuidTable   = (GUID *)(HandlerInfo + 1);
  HandlerInfo->ExtractDecodeHandlerTable = (EXTRACT_GUIDED_SECTION_DECODE_HANDLER *)(
                                                                                     (UINT8 *)HandlerInfo->ExtractHandlerGuidTable +
                                                                                     PcdGet32 (PcdMaximumGuidedExtractHandler) * sizeof (GUID)
                                                                                     );
  HandlerInfo->ExtractGetInfoHandlerTable = (EXTRACT_GUIDED_SECTION_GET_INFO_HANDLER *)(
                                                                                        (UINT8 *)HandlerInfo->ExtractDecodeHandlerTable +
                                                                                        PcdGet32 (PcdMaximumGuidedExtractHandler) *
                                                                                        sizeof (EXTRACT_GUIDED_SECTION_DECODE_HANDLER)
                                                                                        );
  *InfoPointer = HandlerInfo;
  return RETURN_SUCCESS;
}

从上述代码可以看到,初始化了EXTRACT_GUIDED_SECTION_HANDLER_INFO

  • Signature:EXTRACT_HANDLER_INFO_SIGNATURE
  • NumberOfExtractHandler:0
  • ExtractHandlerGuidTable:起始地址
  • ExtractDecodeHandlerTable:起始地址
  • ExtractGetInfoHandlerTable:起始地址

InitializeFloatingPointUnits

这个是调用汇编完成的。文件位于:UefiCpuPkg\Library\BaseUefiCpuLib\X64\InitializeFpu.nasm

SECTION .rodata

;
; Float control word initial value:
; all exceptions masked, double-precision, round-to-nearest
;
mFpuControlWord: DW 0x27F
;
; Multimedia-extensions control word:
; all exceptions masked, round-to-nearest, flush to zero for masked underflow
;
mMmxControlWord: DD 0x1F80

    SECTION .text

;
; Initializes floating point units for requirement of UEFI specification.
;
; This function initializes floating-point control word to 0x027F (all exceptions
; masked,double-precision, round-to-nearest) and multimedia-extensions control word
; (if supported) to 0x1F80 (all exceptions masked, round-to-nearest, flush to zero
; for masked underflow).
;
global ASM_PFX(InitializeFloatingPointUnits)
ASM_PFX(InitializeFloatingPointUnits):

    push    ebx

    ;
    ; Initialize floating point units
    ;
    finit
    fldcw   [mFpuControlWord]

    ;
    ; Use CpuId instructuion (CPUID.01H:EDX.SSE[bit 25] = 1) to test
    ; whether the processor supports SSE instruction.
    ;
    mov     eax, 1
    cpuid
    bt      edx, 25
    jnc     Done

    ;
    ; Set OSFXSR bit 9 in CR4
    ;
    mov     eax, cr4
    or      eax, BIT9
    mov     cr4, eax

    ;
    ; The processor should support SSE instruction and we can use
    ; ldmxcsr instruction
    ;
    ldmxcsr [mMmxControlWord]
Done:
    pop     ebx

    ret

push ebx

ebx是非易失性寄存器,在被调用函数中,如果要使用的化,需要将其先保存起来。调用结束后,再还原。后面的cpuid指令会使用ebx,因此,这里先将其压入栈。

非易失性寄存器

在汇编语言和处理器架构中,非易失性寄存器(Non-Volatile Registers,又称“被调用者保存寄存器” Callee-Saved Registers) 是一类特殊的寄存器:在函数调用过程中,被调用者(子函数)必须保护其原始值——如果子函数修改了这类寄存器,必须在返回调用者前恢复其原值,确保调用者后续使用时寄存器的值不受影响。

简单来说,非易失性寄存器的“非易失”体现在:其值不会因函数调用而“丢失”,调用者无需担心子函数会破坏它存储的重要数据。

1. 核心规则:谁来负责保护?

非易失性寄存器的核心约定由调用约定(Calling Convention) 定义(如 x86 的 cdeclstdcall,或 UEFI 规范的调用约定),规则明确:

  • 调用者(Caller):调用子函数前,无需提前保存非易失性寄存器的值(因为子函数会负责保护)。
  • 被调用者(Callee,子函数):如果子函数需要使用非易失性寄存器(如修改其值),必须先将寄存器的原始值保存到栈中,在函数返回前再从栈中恢复原值。

与之相对的是易失性寄存器(Volatile Registers,又称“调用者保存寄存器” Caller-Saved Registers):子函数可以随意修改这类寄存器,无需恢复;若调用者需要保留其值,必须在调用子函数前自行保存到栈中。

2. x86 架构中的非易失性寄存器(32位与64位)

不同架构的非易失性寄存器列表不同,以常见的 x86 架构为例:

架构 非易失性寄存器(Callee-Saved) 易失性寄存器(Caller-Saved)
x86 32位 ebxebpesiediesp eaxecxedx
x86 64位 rbxrbprsprsirdir12~r15 raxrcxrdxrsirdir8~r11

注:esp/rsp(栈指针)是特殊的非易失性寄存器——子函数可以修改它(如分配栈空间),但返回时必须恢复为调用前的值(否则调用者无法正确访问栈数据)。

3. 为什么需要非易失性寄存器?

非易失性寄存器的设计是为了简化函数协作,避免寄存器使用冲突:

  • 对于调用者:无需频繁保存/恢复所有寄存器,只需关注易失性寄存器即可,减少代码冗余。
  • 对于被调用者:明确需要保护的寄存器范围,确保自身逻辑不破坏调用者的关键数据(如循环变量、指针地址等)。

例如,调用者在调用子函数前,可能在 ebx 中存储了一个重要的数组基地址。由于 ebx 是非易失性寄存器,子函数若要使用 ebx,必须先 push ebx 保存原值,使用后再 pop ebx 恢复——这样调用者后续访问数组时,ebx 的值依然正确。

finit

finit 是 x86 架构中用于初始化浮点运算单元(FPU, Floating-Point Unit)的汇编指令,全称为 FPU Initialize。它的核心作用是将 FPU 重置为一个已知的初始状态,确保浮点运算从一个统一的基准开始,避免前序操作残留的状态影响后续计算。

finit 指令的具体功能

finit 会对 FPU 的多个关键寄存器和状态进行初始化,具体包括:

1. 控制字(FCW, FPU Control Word)

控制字是 16 位寄存器,用于配置 FPU 的运算模式(如舍入方式、精度)和异常屏蔽(如除零、无效操作等异常是否触发中断)。
finit 会将 FCW 初始化为默认值 0x037F,具体含义:

  • bit 0~5(异常屏蔽位):全部为 1,表示屏蔽所有 FPU 异常(无效操作、除零、溢出、下溢、精度丢失、非规格化数),即异常发生时不触发中断,而是由 FPU 自行处理(如返回 NaN、无穷大等)。
  • bit 8~9(精度控制)11b,表示默认使用扩展精度(80位) 运算(x87 FPU 的原生精度)。
  • bit 10~11(舍入方式)00b,表示默认使用四舍五入(Round to Nearest)模式。
  • bit 12(无穷大模式)0,表示使用** projective 无穷大**(即 +∞ 和 -∞ 视为不同值)。
2. 状态字(FSW, FPU Status Word)

状态字是 16 位寄存器,用于记录 FPU 的当前状态(如是否发生异常、寄存器栈状态等)。
finit 会将 FSW 清零(0x0000),表示:

  • 无任何异常标志(所有异常位为 0)。
  • 寄存器栈指针(TOP)复位为 0(指向栈顶寄存器 ST0)。
  • 忙标志(BUSY)为 0(FPU 处于空闲状态)。
3. 标记字(FTW, FPU Tag Word)

标记字是 16 位寄存器,用于标记 8 个浮点寄存器(ST0~ST7)的状态(如是否为空、是否为零、是否为无效值等)。
finit 会将 FTW 设为 0xFFFF,表示所有浮点寄存器均为空(Tag = 11b,即 “empty”)。

4. 指令指针(FIP)和数据指针(FDP)
  • FIP 记录最后一条执行的 FPU 指令的地址,FDP 记录最后一条 FPU 指令操作的数据地址。
  • finit 会将这两个指针重置为 0(实模式下)或特定的段选择子(保护模式下),具体值取决于处理器模式。

finit 的使用场景

finit 通常用于浮点运算开始前的初始化,确保 FPU 处于可预测的状态。例如:

  • 程序启动时,初始化 FPU 以避免 BIOS/固件残留的状态影响。
  • 函数调用中,若函数使用 FPU,可能在入口处用 finit 重置状态,避免调用者的 FPU 状态干扰。

在之前的 UEFI 初始化代码中,finit 先将 FPU 重置为默认状态,再通过 fldcw 加载自定义控制字(0x27F),正是因为默认状态(如精度模式)可能不符合 UEFI 规范,需要先清零再配置。

注意事项

  • finit 是 x87 FPU 的指令,适用于传统浮点运算;现代处理器的 SSE/AVX 扩展有独立的初始化方式(如 ldmxcsr)。
  • finit 的默认配置(如扩展精度、四舍五入)可能不符合特定场景需求(如需要双精度、截断舍入),因此实际使用中常需在 finit 后用 fldcw 加载自定义控制字。

总结

finit 是 FPU 的“重置按钮”,通过初始化控制字、状态字、标记字等关键组件,确保浮点运算从一个已知的基准状态开始,是浮点程序设计中保证一致性和可靠性的基础指令。

fldcw [mFpuControlWord]

配置FPU 控制寄存器(FCW),这里将其配置成0x27F,开启了相应的功能

FCW(Floating-Point Control Word,浮点控制字)是 x86 架构中 x87 浮点运算单元(FPU)的 16 位控制寄存器,用于配置浮点运算的核心规则,包括异常处理、运算精度、舍入方式等。以下是其详细位结构与功能:

位范围(bit) 名称 / 缩写 功能描述
0 IM(Invalid Operation Mask) 无效操作异常屏蔽位: - 1:屏蔽无效操作异常(如 0/0、√负数等),不触发中断; - 0:允许异常,触发中断。
1 DM(Denormal Operand Mask) 非规格化数异常屏蔽位: - 1:屏蔽非规格化数(过小的浮点数)异常; - 0:允许异常,触发中断。
2 ZM(Zero Divide Mask) 除零异常屏蔽位: - 1:屏蔽除零异常(如 x/0); - 0:允许异常,触发中断。
3 OM(Overflow Mask) 溢出异常屏蔽位: - 1:屏蔽运算结果溢出(超出可表示范围)异常; - 0:允许异常,触发中断。
4 UM(Underflow Mask) 下溢异常屏蔽位: - 1:屏蔽运算结果下溢(小于最小可表示正数)异常; - 0:允许异常,触发中断。
5 PM(Precision Mask) 精度丢失异常屏蔽位: - 1:屏蔽运算结果精度丢失(如四舍五入导致的误差)异常; - 0:允许异常,触发中断。
6~7 保留位(Reserved) 未定义功能,必须为 0(否则可能导致处理器行为异常)。
8~9 PC(Precision Control) 精度控制位:指定 FPU 运算的默认精度(影响加法、乘法等运算的结果精度)。
10~11 RC(Rounding Control) 舍入控制位:指定浮点运算的舍入方式(影响结果的取整规则)。
12 IC(Infinity Control) 无穷大控制位:指定 FPU 对无穷大(±∞)的处理模式。
13~15 保留位(Reserved) 未定义功能,必须为 0

cpuid

CPUID 是 x86/x86-64 架构中一条用于查询处理器信息的汇编指令,全称为 “CPU Identification”。它允许软件获取处理器的厂商信息、型号、支持的指令集(如SSE、AVX)、缓存配置等关键特性,是系统初始化、硬件检测、软件优化的核心指令。

CPUID 指令的基本原理

CPUID 指令的工作机制是**“输入-输出”模式**:

  • 输入:执行前,通过 eax 寄存器设置“功能号”(Function Number),指定需要查询的信息类型;
  • 输出:执行后,处理器将查询结果返回至 eaxebxecxedx 四个寄存器中,不同功能号对应不同的返回值格式。

使用步骤

  1. 设置功能号:将需要查询的功能号存入 eax 寄存器(如 mov eax, 1 表示查询处理器特性)。
  2. 执行指令:通过 cpuid 指令触发查询。
  3. 读取结果:从 eaxebxecxedx 寄存器中获取返回信息。

示例代码框架:

mov     eax, <功能号>  ; 设置要查询的功能
cpuid                 ; 执行CPUID查询
; 结果存放在 eax, ebx, ecx, edx 中

功能号分类与常用功能

CPUID 的功能号分为基本功能号0x00 ~ 0x0F)和扩展功能号0x80000000 ~ 0x8000000F 及以上),分别对应不同的查询范围。以下是常用功能号的说明:

1. 基本功能号 0:查询厂商信息与最大基本功能号
  • 输入eax = 0
  • 输出
    • eax:返回最大基本功能号(即处理器支持的最高基本功能号,如 0x0F 表示支持到功能号 0x0F);
    • ebxedxecx:组合成 12 字节的厂商 ID 字符串(按 ebx + edx + ecx 顺序拼接)。

示例

  • Intel 处理器返回 ebx="Genu", edx="ineI", ecx="ntel" → 拼接为 "GenuineIntel"
  • AMD 处理器返回 ebx="Auth", edx="enti", ecx="cAMD" → 拼接为 "AuthenticAMD"
2. 基本功能号 1:查询处理器特性与版本
  • 输入eax = 1
  • 输出
    • eax:包含处理器家族(Family)、型号(Model)、步进(Stepping)等版本信息;
    • ebx:包含缓存配置、处理器数量等信息;
    • edx:包含标准特性标志位(每一位对应一种指令集或功能是否支持);
    • ecx:包含扩展特性标志位(较新的指令集,如SSE4、AVX等)。

关键标志位(edx)

  • bit 23:MMX 指令集支持;
  • bit 25:SSE 指令集支持(之前代码中通过 bt edx, 25 检查SSE支持);
  • bit 26:SSE2 指令集支持;
  • bit 15:CR4 寄存器中的 OSFXSR 位是否可用(用于SSE状态保存)。

关键标志位(ecx)

  • bit 0:SSE3 支持;
  • bit 9:SSE4.1 支持;
  • bit 28:AVX 支持。
3. 扩展功能号 0x80000000:查询最大扩展功能号
  • 输入eax = 0x80000000
  • 输出
    • eax:返回最大扩展功能号(如 0x80000008 表示支持到扩展功能号 0x80000008)。
4. 扩展功能号 0x80000002~0x80000004:查询处理器品牌名称
  • 输入eax = 0x800000020x800000030x80000004
  • 输出:每次执行后,eaxebxecxedx 组合成 16 字节字符串,三次执行共返回 48 字节的处理器品牌名称(如 "Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz")。
5. 扩展功能号 0x80000001:查询扩展特性
  • 输入eax = 0x80000001
  • 输出
    • edx:包含 64 位模式支持(bit 29)、NX(数据执行保护)等扩展功能标志。

CPUID 的应用场景

  1. 指令集检测:软件启动时通过 CPUID 检查处理器是否支持特定指令集(如SSE、AVX),避免执行不支持的指令导致崩溃(如之前的代码中检查SSE支持后才配置MXCSR)。
  2. 硬件信息展示:操作系统或工具(如任务管理器、lscpu 命令)通过 CPUID 获取处理器型号、核心数等信息并展示。
  3. 性能优化:根据处理器缓存大小(通过功能号 0x02 查询)调整数据块大小,或根据支持的指令集(如AVX)选择更高效的运算函数。

注意事项

  1. 寄存器修改CPUID 会覆盖 eaxebxecxedx 的值,其中 ebx 是非易失性寄存器(需被调用者保护),因此使用时需先通过 push ebx 保存原值(如之前的代码所示)。
  2. 功能号兼容性:不同处理器支持的功能号不同,需先通过功能号 00x80000000 查询最大功能号,避免使用不支持的功能号导致错误。
  3. 特权级CPUID 可在用户态(Ring 3)和内核态(Ring 0)执行,无需特殊权限。

总结

CPUID 是软件与处理器沟通的“桥梁”,通过功能号机制提供了丰富的硬件信息。其核心价值在于让软件动态适配硬件特性,无论是系统初始化(如UEFI配置FPU/SSE)、性能优化还是硬件检测,都离不开 CPUID 的支持。理解其功能号与返回值的对应关系,是掌握x86架构硬件编程的基础。

bt edx, 25

检测CPU是否支持SSE指令集

标准特性标志位

在 x86 架构中,标准特性标志位(Standard Feature Flags) 是指通过 CPUID 指令(功能号 eax=1)查询时,由 edx 寄存器返回的一组二进制位。这些位用于标识处理器支持的基础硬件功能和指令集(相对早期或核心的特性),是软件检测硬件能力的核心依据。

每一位(bit)对应一种特定功能,置位(1)表示支持该功能,清零(0)表示不支持。以下是最常用的标准特性标志位(按位索引从 0 开始):

位索引(bit) 标志名称 功能描述 关联场景 / 指令
0 FPU 处理器内置浮点运算单元(FPU)支持。 finitfadd 等浮点指令
1 VME 虚拟 8086 模式扩展支持(与 CR4.VME 配合)。 保护模式下运行 8086 程序
2 DE 调试扩展支持(如断点寄存器调试)。 int3 断点指令
3 PSE 页大小扩展支持(4MB 大页)。 内存分页管理
4 TSC 时间戳计数器(TSC)支持。 rdtsc 指令(读取时间戳)
5 MSR 模型专用寄存器(MSR)支持。 rdmsr/wrmsr 指令
6 PAE 物理地址扩展支持(突破 4GB 内存限制)。 64 位页表管理
7 MCE 机器检查异常支持(硬件错误检测)。 #MC 异常(内存 / 总线错误)
8 CX8 CMPXCHG8B 指令支持(8 字节比较并交换)。 多线程同步
9 APIC 本地 APIC(高级可编程中断控制器)支持。 多处理器中断管理
11 SEP SYSENTER/SYSEXIT 指令支持(快速系统调用)。 用户态到内核态的快速切换
12 MTRR 内存类型范围寄存器支持(控制内存缓存策略)。 优化内存访问性能
13 PGE 全局页支持(TLB 不刷新全局页)。 提升多任务切换性能
14 MCA 机器检查架构支持(扩展的硬件错误报告)。 更详细的硬件故障诊断
15 CMOV 条件移动指令支持(如 cmovzcmovn)。 优化分支预测,减少跳转开销
16 PAT 页属性表支持(更灵活的内存缓存策略配置)。 细粒度控制内存缓存行为
17 PSE-36 36 位页大小扩展支持(更大的物理地址空间)。 扩展内存寻址范围
18 PSN 处理器序列号支持(早期 Intel 处理器)。 硬件唯一标识(部分处理器移除)
19 CLFLUSH CLFLUSH 指令支持(手动刷新缓存行)。 缓存一致性控制
23 MMX MMX 多媒体指令集支持(64 位整数运算)。 多媒体数据处理(如音频 / 视频)
24 FXSR FXSAVE/FXRSTOR 指令支持(保存 / 恢复 FPU/MMX 状态)。 快速上下文切换(需 CR4.OSFXSR 配合)
25 SSE SSE 指令集支持(128 位单精度浮点运算)。 科学计算、多媒体加速(如之前代码检测的位)
26 SSE2 SSE2 指令集支持(128 位双精度浮点 / 整数运算)。 更强大的 SIMD 运算能力
27 SS 自 snooping 支持(缓存一致性维护)。 多处理器缓存同步
28 HTT 超线程技术支持(单物理核心模拟多逻辑核心)。 多线程性能优化
29 TM 温度监控支持(处理器过热检测)。 硬件热管理
30 IA64 兼容 IA64 架构(仅特定处理器)。 跨架构兼容性
31 PBE 断点使能(调试相关)。 高级调试功能

Set OSFXSR bit 9

    ;
    ; Set OSFXSR bit 9 in CR4
    ;
    mov     eax, cr4
    or      eax, BIT9
    mov     cr4, eax

启用指令FXSAVE和FXRSTOR指令

  • FXSAVE:将 FPU(浮点单元)、MMX、SSE、SSE2 等扩展单元的寄存器(如ST0 - ST7、MM0 - MM7、XMM0 - XMM7)及控制字(如 FCW、MXCSR)一次性保存到内存中;
  • FXRSTOR:从内存中恢复上述寄存器和控制字的状态

CR4寄存器

位索引(Bit) 名称(缩写) 功能描述
0 VME(Virtual-8086 Mode Extensions) 启用虚拟 8086 模式扩展:允许在保护模式下更高效地运行 8086 程序,支持虚拟中断标志(VIF)。
1 PVI(Protected-Mode Virtual Interrupts) 启用保护模式下的虚拟中断:允许操作系统模拟中断,用于虚拟化场景。
2 TSD(Time Stamp Disable) 控制 RDTSC 指令的权限: - 0:所有特权级可执行 RDTSC(读取时间戳计数器); - 1:仅 Ring 0(内核态)可执行。
3 DE(Debugging Extensions) 启用调试扩展:允许使用调试寄存器(DR4/DR5)和断点调试的扩展功能。
4 PSE(Page Size Extensions) 启用页大小扩展:支持 4MB 大页(32 位模式下),减少页表层级。
5 PAE(Physical Address Extension) 启用物理地址扩展:支持超过 4GB 的物理内存(32 位模式下最大支持 64GB),需配合 64 位页表项。
6 MCE(Machine Check Enable) 启用机器检查异常:允许处理器检测硬件错误(如内存错误、总线错误)并触发异常(#MC)。
7 PGE(Page Global Enable) 启用全局页功能:标记为 “全局” 的页表项不会因任务切换而刷新 TLB(Translation Lookaside Buffer),提升性能。
8 PCE(Performance-Monitoring Counter Enable) 控制 RDPMC 指令的权限: - 0:仅 Ring 0 可执行 RDPMC(读取性能计数器); - 1:所有特权级可执行。
9 OSFXSR(Operating System Support for FXSAVE/FXRSTOR) 启用对 FXSAVE/FXRSTOR 指令的支持:这两条指令用于保存 / 恢复 FPU、MMX、SSE 等寄存器状态,是 SSE 功能正常工作的前提(之前代码中通过 or eax, BIT9 置位,正是为了启用 SSE)。
10 OSXMMEXCPT(Operating System Support for Unmasked SIMD Floating-Point Exceptions) 启用操作系统对未屏蔽的 SIMD 浮点异常的支持:若 SSE 等 SIMD 指令产生未屏蔽异常,处理器会触发 #XM 异常(需操作系统处理)。
11 VMXE(Virtual Machine Extensions) 启用 Intel VT-x 虚拟化技术:允许处理器运行虚拟机监控程序(VMM),支持硬件辅助虚拟化。(Intel 特定)
12 SMXE(Safer Mode Extensions) 启用 Intel SMX 技术:支持安全启动和可信执行环境(如 TXT)。(Intel 特定)
13 FSGSBASE 允许用户态程序通过指令(如 WRFSBASE、WRGSBASE)直接修改 FS/GS 段寄存器的基地址,提升线程本地存储(TLS)访问效率。
14 PCIDE(Process-Context Identifiers) 启用进程上下文标识符:允许不同进程共享 TLB 条目(通过 PCID 区分),减少进程切换时的 TLB 刷新开销。
15 OSXSAVE 启用 XSave/XRestore 指令支持:扩展 FXSAVE/FXRSTOR 功能,可保存 AVX、AVX-512 等更高级 SIMD 指令的状态。
16 SMEP(Supervisor Mode Execution Prevention) 启用 supervisor 模式执行保护:禁止内核态执行用户态内存中的代码,防御某些缓冲区溢出攻击。
17 SMAP(Supervisor Mode Access Prevention) 启用 supervisor 模式访问保护:禁止内核态访问用户态内存(除非显式允许),增强内存隔离。
其他高位(18+) 保留或厂商特定功能 通常为保留位或由处理器厂商(如 Intel/AMD)定义特定功能(如 AMD 的 NX 位等)。

ldmxcsr [mMmxControlWord]

配置SSE 控制与状态寄存器(MXCSR),这里将其配置为0x1F80,开启相应的功能。

MXCSR(Media eXtension Control and Status Register,媒体扩展控制与状态寄存器)是 x86 架构中专门用于控制 SSE、SSE2 等 SIMD(单指令多数据)指令集行为的 32 位寄存器。它同时包含控制位(配置运算规则)和状态位(记录运算状态),是管理 SIMD 浮点运算的核心寄存器。以下是其详细位结构与功能:

位范围(bit) 类型 名称 / 缩写 功能描述 关键说明
0 控制位 IE(Invalid Op Mask) 无效操作异常屏蔽位: - 1:屏蔽无效操作异常(如 0/0、√负数等); - 0:允许异常(触发 #XM 中断)。 对应 SIMD 运算中最常见的非法操作检查。
1 控制位 DE(Denorm Op Mask) 非规格化数异常屏蔽位: - 1:屏蔽非规格化数(过小浮点数)异常; - 0:允许异常。 非规格化数运算速度较慢,通常屏蔽以提升性能。
2 控制位 ZE(Zero Divide Mask) 除零异常屏蔽位: - 1:屏蔽除零异常(如 x/0); - 0:允许异常。 避免除零导致程序中断。
3 控制位 OE(Overflow Mask) 溢出异常屏蔽位: - 1:屏蔽运算结果溢出(超出可表示范围)异常; - 0:允许异常。 溢出时返回 ±∞ 而非触发中断。
4 控制位 UE(Underflow Mask) 下溢异常屏蔽位: - 1:屏蔽运算结果下溢(小于最小可表示正数)异常; - 0:允许异常。 配合 FZ 位决定下溢处理方式。
5 控制位 PE(Precision Mask) 精度丢失异常屏蔽位: - 1:屏蔽运算结果精度丢失(舍入导致误差)异常; - 0:允许异常。 大多数场景不关注精度丢失,通常屏蔽。
6~7 保留位 - 未定义功能,必须为 0(否则可能导致处理器行为异常)。 硬件保留,软件不可修改。
8~11 控制位 RC(Rounding Control) 舍入方式控制位: - 0000:四舍五入(默认,向最近值舍入,相等时向偶数舍入); - 0001:向负无穷舍入(向下取整); - 0010:向正无穷舍入(向上取整); - 0011:向零舍入(截断)。 与 FPU 的 FCW 舍入方式兼容,确保浮点运算规则统一。
12 控制位 FZ(Flush to Zero) 下溢处理模式: - 1:启用 “下溢清零”—— 结果下溢时返回 0(提升性能); - 0:传统模式 —— 保留非规格化数(精度更高但速度慢)。 SSE 优化关键位,系统级程序(如 UEFI)通常启用(置为 1)。
13~15 保留位 - 未定义功能,必须为 0 硬件保留,软件不可修改。
16 状态位 IS(Invalid Op Status) 无效操作异常状态位: - 1:表示已发生无效操作异常(需软件清零); - 0:未发生。 状态位仅记录异常发生,不直接控制行为,需通过对应控制位(IE)决定是否中断。
17 状态位 DS(Denorm Op Status) 非规格化数异常状态位: - 1:表示已处理非规格化数(需软件清零); - 0:未发生。 与 DE 控制位配合,记录非规格化数的出现。
18 状态位 ZS(Zero Divide Status) 除零异常状态位: - 1:表示已发生除零(需软件清零); - 0:未发生。 记录除零事件,无论 ZE 控制位是否屏蔽。
19 状态位 OS(Overflow Status) 溢出异常状态位: - 1:表示已发生溢出(需软件清零); - 0:未发生。 记录溢出事件,无论 OE 控制位是否屏蔽。
20 状态位 US(Underflow Status) 下溢异常状态位: - 1:表示已发生下溢(需软件清零); - 0:未发生。 记录下溢事件,无论 UE 控制位是否屏蔽。
21 状态位 PS(Precision Status) 精度丢失状态位: - 1:表示已发生精度丢失(需软件清零); - 0:未发生。 记录精度丢失事件,无论 PE 控制位是否屏蔽。
22~31 保留位 - 未定义功能,必须为 0(64 位模式下部分位可能被扩展使用)。 硬件保留,软件不可修改。

Initialize SEC hand-off state

准备hand-off,传递给PEI。主要是保存以下内容:

  • 临时内存的基地址和大小
  • 栈的起始地址和大小
  • BFV的地址和大小
  //
  // Initialize SEC hand-off state
  //
  SecCoreData.DataSize = sizeof (EFI_SEC_PEI_HAND_OFF);

  SecCoreData.TemporaryRamSize = (UINTN)PcdGet32 (PcdOvmfSecPeiTempRamSize);
  SecCoreData.TemporaryRamBase = (VOID *)((UINT8 *)TopOfCurrentStack - SecCoreData.TemporaryRamSize);

  SecCoreData.PeiTemporaryRamBase = SecCoreData.TemporaryRamBase;
  SecCoreData.PeiTemporaryRamSize = SecCoreData.TemporaryRamSize >> 1;

  SecCoreData.StackBase = (UINT8 *)SecCoreData.TemporaryRamBase + SecCoreData.PeiTemporaryRamSize;
  SecCoreData.StackSize = SecCoreData.TemporaryRamSize >> 1;

  SecCoreData.BootFirmwareVolumeBase = BootFv;
  SecCoreData.BootFirmwareVolumeSize = (UINTN)BootFv->FvLength;

屏蔽8259

屏蔽8259功能

  //
  // Make sure the 8259 is masked before initializing the Debug Agent and the debug timer is enabled
  //
  IoWrite8 (0x21, 0xff);
  IoWrite8 (0xA1, 0xff);

8259 是 Intel 为 x86 架构设计的 可编程中断控制器(PIC),核心作用是整合多外部设备的中断请求,实现有序管理并高效传递给 CPU,是早期 x86 计算机(如 IBM PC/XT/AT)中断系统的核心组件。

一、核心定位与价值

早期计算机中,外设(如键盘、定时器、磁盘控制器)需通过“中断”请求 CPU 处理任务,但 CPU 仅能接收单个中断信号。8259 作为“中断管家”,解决了“多外设中断冲突”问题,让系统可同时响应多个设备的请求,是提升计算机并行处理能力的关键硬件。

二、核心功能

  1. 中断请求整合:提供 8 个中断输入引脚(IR0~IR7),可直接连接 8 个外设;通过“主从级联”(最多 9 片 8259),可扩展管理 64 个中断源。
  2. 优先级裁决:内置优先级裁决器(PR),支持“固定优先级”(默认 IR0 最高、IR7 最低)或“循环优先级”(动态调整优先级,避免某设备独占 CPU),确保高紧急度请求(如定时器中断)优先被处理。
  3. 中断屏蔽与嵌套:通过“中断屏蔽寄存器(IMR)”可单独禁止某一中断;支持“中断嵌套”——高优先级中断可抢占正在处理的低优先级中断,提升响应灵活性。
  4. 可编程配置:通过“初始化命令字(ICW1ICW4)”设置工作模式(如边沿/电平触发、单片/级联),通过“操作命令字(OCW1OCW3)”动态控制(如屏蔽中断、结束中断服务)。

三、关键工作流程

  1. 请求触发:外设通过 IR 引脚发送中断信号,8259 的“中断请求寄存器(IRR)”记录该请求。
  2. 优先级判断:优先级裁决器对比所有未屏蔽的请求,选出最高优先级。
  3. CPU 交互:8259 向 CPU 发送 INT 中断请求;CPU 回送 INTA 响应信号后,8259 传递“中断类型码”(告知 CPU 需执行哪个中断服务程序)。
  4. 服务与结束:CPU 执行中断服务程序,完成后通过 OCW2 发送“EOI(中断结束命令)”,8259 清除当前服务标记(ISR 寄存器对应位),准备响应下一个请求。

四、典型应用与现代地位

  • 早期 PC 场景:IR0 连接 8254 定时器(生成系统时钟中断)、IR1 连接键盘、IR6 连接软盘控制器,是 DOS 等早期系统实现时钟管理、外设交互的基础。
  • 现代定位:随着多核 CPU 普及,8259 因仅支持单核、中断源有限等局限,已被“高级可编程中断控制器(APIC)”取代;但现代主板仍保留 8259 兼容模式(端口 0x20/0x21、0xA0/0xA1),以支持老旧外设或系统。

综上,8259 是 x86 中断体系的“奠基组件”,其“优先级管理”“中断嵌套”等设计思想,至今仍影响着现代中断控制器的架构。

InitializeApicTimer

初始化APIC timer,初始化的顺序是

  • 设置ACPI enable bit(XAPIC_SPURIOUS_VECTOR_OFFSET)
  • 配置DCR分频值(XAPIC_TIMER_DIVIDE_CONFIGURATION_OFFSET)
  • 配置LVT定时器寄存器(XAPIC_LVT_TIMER_OFFSET)
  • 写入初始计数值(XAPIC_TIMER_INIT_COUNT_OFFSET)
/**
  Initialize the local APIC timer.

  The local APIC timer is initialized and enabled.

  @param DivideValue   The divide value for the DCR. It is one of 1,2,4,8,16,32,64,128.
                       If it is 0, then use the current divide value in the DCR.
  @param InitCount     The initial count value.
  @param PeriodicMode  If TRUE, timer mode is peridoic. Othewise, timer mode is one-shot.
  @param Vector        The timer interrupt vector number.
**/
VOID
EFIAPI
InitializeApicTimer (
  IN UINTN    DivideValue,
  IN UINT32   InitCount,
  IN BOOLEAN  PeriodicMode,
  IN UINT8    Vector
  )
{
  LOCAL_APIC_DCR        Dcr;
  LOCAL_APIC_LVT_TIMER  LvtTimer;
  UINT32                Divisor;

  //
  // Ensure local APIC is in software-enabled state.
  //
  InitializeLocalApicSoftwareEnable (TRUE);

  if (DivideValue != 0) {
    ASSERT (DivideValue <= 128);
    ASSERT (DivideValue == GetPowerOfTwo32 ((UINT32)DivideValue));
    Divisor = (UINT32)((HighBitSet32 ((UINT32)DivideValue) - 1) & 0x7);

    Dcr.Uint32            = ReadLocalApicReg (XAPIC_TIMER_DIVIDE_CONFIGURATION_OFFSET);
    Dcr.Bits.DivideValue1 = (Divisor & 0x3);
    Dcr.Bits.DivideValue2 = (Divisor >> 2);
    WriteLocalApicReg (XAPIC_TIMER_DIVIDE_CONFIGURATION_OFFSET, Dcr.Uint32);
  }

  //
  // Enable APIC timer interrupt with specified timer mode.
  //
  LvtTimer.Uint32 = ReadLocalApicReg (XAPIC_LVT_TIMER_OFFSET);
  if (PeriodicMode) {
    LvtTimer.Bits.TimerMode = 1;
  } else {
    LvtTimer.Bits.TimerMode = 0;
  }

  LvtTimer.Bits.Mask   = 0;
  LvtTimer.Bits.Vector = Vector;
  WriteLocalApicReg (XAPIC_LVT_TIMER_OFFSET, LvtTimer.Uint32);

  //
  // Program init-count register.
  //
  WriteLocalApicReg (XAPIC_TIMER_INIT_COUNT_OFFSET, InitCount);
}

InitializeLocalApicSoftwareEnable

将APIC enable位置1,启用APIC

/**
  Initialize the state of the SoftwareEnable bit in the Local APIC
  Spurious Interrupt Vector register.

  @param  Enable  If TRUE, then set SoftwareEnable to 1
                  If FALSE, then set SoftwareEnable to 0.

**/
VOID
EFIAPI
InitializeLocalApicSoftwareEnable (
  IN BOOLEAN  Enable
  )
{
  LOCAL_APIC_SVR  Svr;

  //
  // Set local APIC software-enabled bit.
  //
  Svr.Uint32 = ReadLocalApicReg (XAPIC_SPURIOUS_VECTOR_OFFSET);
  if (Enable) {
    if (Svr.Bits.SoftwareEnable == 0) {
      Svr.Bits.SoftwareEnable = 1;
      WriteLocalApicReg (XAPIC_SPURIOUS_VECTOR_OFFSET, Svr.Uint32);
    }
  } else {
    if (Svr.Bits.SoftwareEnable == 1) {
      Svr.Bits.SoftwareEnable = 0;
      WriteLocalApicReg (XAPIC_SPURIOUS_VECTOR_OFFSET, Svr.Uint32);
    }
  }
}

伪中断向量寄存器(Spurious Interrupt Vector Register, SVR)

位域范围 名称 功能与取值说明
Bits 0-7 伪中断向量(Vector) - 配置伪中断对应的中断向量号(范围0x00~0xFF) - 需与中断描述符表(IDT)中的中断门关联,通常设置为0xFF(系统预留的未定义中断向量) - 例:0xFF表示所有未明确配置的中断均由此向量处理
Bit 8 APIC 启用位(APIC Enable) - 1:启用 Local APIC(必须置 1,否则 APIC 不响应任何中断) - 0:禁用 Local APIC(默认值,初始化阶段需显式置 1)
Bit 11 EOI 广播抑制位(Suppress EOI Broadcast) - 1:禁止电平触发中断的 EOI 广播(仅 xAPIC 模式有效,避免多核间 EOI 信号干扰) - 0:允许 EOI 广播(默认值,兼容传统中断处理)
其他位(9-10、12-31) 保留位(Reserved) 硬件未定义,软件读写时需置 0(否则可能导致不可预期的行为)
//
// Spurious-Interrupt Vector Register (SVR)
//
typedef union {
  struct {
    UINT32    SpuriousVector          : 8;  ///< Spurious Vector.
    UINT32    SoftwareEnable          : 1;  ///< APIC Software Enable/Disable.
    UINT32    FocusProcessorChecking  : 1;  ///< Focus Processor Checking.
    UINT32    Reserved0               : 2;  ///< Reserved.
    UINT32    EoiBroadcastSuppression : 1;  ///< EOI-Broadcast Suppression.
    UINT32    Reserved1               : 19; ///< Reserved.
  } Bits;
  UINT32    Uint32;
} LOCAL_APIC_SVR;

配置Divide Configuration Register (DCR)

分频配置寄存器 (Divide Configuration Register, DCR) 是 Local APIC 定时器系统的关键组成部分,它决定了定时器的时钟源频率。该寄存器控制着处理器总线时钟与 APIC 定时器实际使用时钟之间的分频比率。

通过串口看到,配置的就是默认值:0

  if (DivideValue != 0) {
    ASSERT (DivideValue <= 128);
    ASSERT (DivideValue == GetPowerOfTwo32 ((UINT32)DivideValue));
    Divisor = (UINT32)((HighBitSet32 ((UINT32)DivideValue) - 1) & 0x7);

    Dcr.Uint32            = ReadLocalApicReg (XAPIC_TIMER_DIVIDE_CONFIGURATION_OFFSET);
    Dcr.Bits.DivideValue1 = (Divisor & 0x3);
    Dcr.Bits.DivideValue2 = (Divisor >> 2);
    WriteLocalApicReg (XAPIC_TIMER_DIVIDE_CONFIGURATION_OFFSET, Dcr.Uint32);
  }

Divide Configuration Register

位域 功能与陷阱
Bits 0-1 - 分频系数选择低2位000=2 分频,001=4 分频,…,111=1 分频(不分频) - 陷阱:保留值1000~1111禁止使用,可能导致定时器停摆
Bit 2 - 保留位:必须置 0,否则可能触发硬件异常(如 APIC 内部错误中断)
Bit 3 分频系数选择高1位
Bits 4-31 - 保留位:读写时需置 0,避免干扰 APIC 内部状态机
//
// Divide Configuration Register (DCR)
//
typedef union {
  struct {
    UINT32    DivideValue1 : 2;  ///< Low 2 bits of the divide value.
    UINT32    Reserved0    : 1;  ///< Always 0.
    UINT32    DivideValue2 : 1;  ///< Highest 1 bit of the divide value.
    UINT32    Reserved1    : 28; ///< Reserved.
  } Bits;
  UINT32    Uint32;
} LOCAL_APIC_DCR;

分频值编码表

二进制值 十六进制值 分频比 备注
0000 0x0 2 默认值
0001 0x1 4
0010 0x2 8
0011 0x3 16
1000 0x8 32
1001 0x9 64
1010 0xA 128
1011 0xB 1 保留,不建议使用
其他 0x4-0x7, 0xC-0xF 保留 行为未定义

配置LVT Timer Register

LVT 定时器寄存器 (LVT Timer Register) 是 Local APIC 中用于配置和管理本地 APIC 定时器中断行为的关键寄存器。它决定了定时器中断的交付方式、工作模式以及中断向量等重要参数。

  //
  // Enable APIC timer interrupt with specified timer mode.
  //
  LvtTimer.Uint32 = ReadLocalApicReg (XAPIC_LVT_TIMER_OFFSET);
  if (PeriodicMode) {
    LvtTimer.Bits.TimerMode = 1;
  } else {
    LvtTimer.Bits.TimerMode = 0;
  }

  LvtTimer.Bits.Mask   = 0;
  LvtTimer.Bits.Vector = Vector;
  WriteLocalApicReg (XAPIC_LVT_TIMER_OFFSET, LvtTimer.Uint32);

位域定义(32 位)

位域范围 名称 功能与取值说明 关键注意事项
Bits 0-7 中断向量(Vector) 配置定时器中断对应的向量号(0x00~0xFF),需与中断描述符表(IDT)中的中断门关联。 例:0x32通常用于系统时钟中断。 向量需唯一,避免与其他中断(如外设中断)冲突
Bit 12 触发模式(Trigger Mode) 固定为0(边缘触发),定时器中断不支持电平触发(硬件强制)。 写入1会被硬件忽略,不影响功能但不符合规范
Bit 16 屏蔽位(Mask) - 1:屏蔽定时器中断(不响应计数到 0 事件) - 0:启用定时器中断(计数到 0 时触发) 配置阶段需先置1(屏蔽),完成设置后再置0(启用),避免异常中断
Bits 17-18 定时器模式(Timer Mode) 定义定时器工作模式: - 00:单次模式(One-Shot) - 01:周期模式(Periodic) - 10:TSC-Deadline 模式 - 11:保留(行为未定义,禁止使用) 11为保留值,使用会导致定时器停摆或异常中断
Bit 19 交付状态(Delivery Status) 只读位: - 1:中断正在交付(处理器未响应) - 0:中断已处理或未触发 用于调试中断阻塞问题(如长期1表示处理器未响应中断)
其他位(8-11、20-31) 保留位(Reserved) 硬件未定义功能,读写时需置 0 误写非零值可能导致定时器计数紊乱或模式异常

EDK2中的定义

//
// LVT Timer Register
//
typedef union {
  struct {
    UINT32    Vector         : 8;  ///< The vector number of the interrupt being sent.
    UINT32    Reserved0      : 4;  ///< Reserved.
    UINT32    DeliveryStatus : 1;  ///< 0: Idle, 1: send pending.
    UINT32    Reserved1      : 3;  ///< Reserved.
    UINT32    Mask           : 1;  ///< 0: Not masked, 1: Masked.
    UINT32    TimerMode      : 1;  ///< 0: One-shot, 1: Periodic.
    UINT32    Reserved2      : 14; ///< Reserved.
  } Bits;
  UINT32    Uint32;
} LOCAL_APIC_LVT_TIMER;

配置初始计数寄存器

初始计数寄存器 (Initial Count Register, ICR) 是 Local APIC 定时器系统的关键组件,用于设置定时器的起始计数值。写入该寄存器会立即启动或重启定时器的递减计数过程

代码默认配置到最大:0xFFFFFFFF

  //
  // Program init-count register.
  //
  WriteLocalApicReg (XAPIC_TIMER_INIT_COUNT_OFFSET, InitCount);

DisableApicTimerInterrupt

禁用Local APIC的定时器中断,只需将其timer寄存器中的mask位置设置为1即可。

/**
  Disable the local APIC timer interrupt.
**/
VOID
EFIAPI
DisableApicTimerInterrupt (
  VOID
  )
{
  LOCAL_APIC_LVT_TIMER  LvtTimer;

  LvtTimer.Uint32    = ReadLocalApicReg (XAPIC_LVT_TIMER_OFFSET);
  LvtTimer.Bits.Mask = 1;
  WriteLocalApicReg (XAPIC_LVT_TIMER_OFFSET, LvtTimer.Uint32);
}

SecMtrrSetup

启用MTRR,Q35平台默认支持

//
// Enable MTRR early, set default type to write back.
// Needed to make sure caching is enabled,
// without this lzma decompress can be very slow.
//
STATIC
VOID
SecMtrrSetup (
  VOID
  )
{
  CPUID_VERSION_INFO_EDX           Edx;
  MSR_IA32_MTRR_DEF_TYPE_REGISTER  DefType;

  AsmCpuid (CPUID_VERSION_INFO, NULL, NULL, NULL, &Edx.Uint32);
DEBUG ((DEBUG_INFO, "[JasonStudy] Edx.Bits.MTRR = 0x%X\n", Edx.Bits.MTRR));
  if (!Edx.Bits.MTRR) {
    return;
  }

 #if defined (TDX_GUEST_SUPPORTED)
  if (CcProbe () == CcGuestTypeIntelTdx) {
    //
    // According to TDX Spec, the default MTRR type is enforced to WB
    // and CR0.CD is enforced to 0.
    // The TD guest has to disable MTRR otherwise it tries to
    // program MTRRs to disable caching. CR0.CD=1 results in the
    // unexpected #VE.
    //
    DEBUG ((DEBUG_INFO, "%a: Skip TD-Guest\n", __func__));
    return;
  }

 #endif

  DefType.Uint64    = AsmReadMsr64 (MSR_IA32_MTRR_DEF_TYPE);
  DefType.Bits.Type = MSR_IA32_MTRR_CACHE_WRITE_BACK;
  DefType.Bits.E    = 1; /* enable */
  AsmWriteMsr64 (MSR_IA32_MTRR_DEF_TYPE, DefType.Uint64);
}

InitializeDebugAgent

  //
  // Initialize Debug Agent to support source level debug in SEC/PEI phases before memory ready.
  //
  InitializeDebugAgent (DEBUG_AGENT_INIT_PREMEM_SEC, &SecCoreData, SecStartupPhase2);

这是一个跳转函数,该函数主要是初始化Debug Agent功能,用于进行Source Debug。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐