BIOS追code之SEC phase

刚涉足BIOS这个领域,只是随遇而安的应届生,什么都不懂,前辈很厉害,可以一起探讨,资料都是什么QQ群或者百度找的,侵删
在uefi_artisan这位博主关于源码分析的博客下,有着这么一个想法,如果用C语言(伪)代码描述整个BIOS 的执行流程,那么流程会是这样的:

main()
{
 
    SecStartup (  SizeOfRam,  TempRamBase,  *BootFirmwareVolume)
    {
       // Update the base address and length of Pei temporary memory
        PeiCore (&SecCoreData, mPeiSecPlatformInformationPpi);    
    } 
    
    PeiCore()
    {
	PeiDispatcher (SecCoreData, &PrivateData);
    }
	
    DxeMain(VOID *HobStart)
    {	
        CoreDispatcher ();
        ~~~~~~~~~~~~~~~~~~
    }
	
    gBds->Entry (gBds);
}

————————————————
原文链接:https://blog.csdn.net/robinsongsog/article/details/51177600

综述

SEC(Security Phase)阶段是平台初始化的第一个阶段,计算机系统加电后首先进入这个阶段。

SEC阶段的功能:

SEC作为整个系统的起点,可能会遇到各种异常,就需要设置IDT,有了中断描述符表接收异常,就能让系统遭遇意外情况时不至于崩溃,同时它还为PEI阶段的代码设置临时内存的基地址和长度,并传给PEI,还需要找到PEI代码的入口点,将控制权移交过去,并且处理临时内存。

UEFI系统开机或重启后首先进入SEC阶段,SEC阶段系统执行以下四种任务:

  1. 接收并处理系统启动和重启信号,系统加电信号、系统重启信号、系统运行过程中的异常信号。
  2. 初始化临时存储区域:系统运行在SEC阶段时,仅CPU和CPU内部资源被初始化,而各种外部设备和内存都没有被初始化,主要是出了最初的汇编代码,SEC阶段还有C代码。因此系统需要一部分临时内存用于代码和数据的存储,一般称为临时RAM,临时RAM只能位于CPU内部(CPU和CPU内部的资源最先被初始化)。最常用的临时RAM是Cache,通过将Cache设置为no-eviction模式,(noeviction:不删除策略,不淘汰,如果内存已满,添加数据是报错的),来把其当成内存使用(此时读取命中则返回Cache中的数据,读取缺失并不会向主存发出缺失事件;写命中时写入Cache,写缺失时也不会向主存发出缺失事件),这种技术称为CAR(Cache As RAM)。
  3. SEC阶段是可信系统的根:作为系统启动的第一部分,只有SEC能被系统信任,以后的各个阶段才有被信任的基础。因此,大部分情况下SEC再转交控制权给PEI前可以验证PEI是否可信。
  4. 传递系统参数给下一阶段:SEC阶段的一切工作都是为PEI阶段做准备的,最重要把系统的控制权转交给PEI,并将SEC阶段的运行信息汇报给PEI。SEC通过将以下信息作为参数传递给PEI的入口程序来向PEI汇报信息:
    • 系统当前状态,PEI根据状态值判断系统当前的健康情况。
    • 可启动固件(Boot Firmware Volume)的地址和大小,PEI据此判断可用硬件。
    • 临时RAM区域的地址和大小。
    • 栈的地址和大小。

SEC阶段执行流程及流程图

无论是在操作系统点击了重启还是直接按下电脑的重启电源键,计算机本质上是给CPU发送了一个ResetVector 信号,也就是重置,那么CPU就会从SEC阶段把固件的代码再跑一遍,这跟关机再开机不同,

sec阶段执行流程
以临时RAM初始化为界,SEC的执行又分为两大部分:临时RAM生效之前称为Reset
Vector阶段,临时RAM生效后调用SEC人口函数从而进人SEC功能区。
其中Reset Vector的执行流程如下:
进入固件接口。
1. 从实模式转换到32位平坦模式。
2. 定位固件中的BFV(Boot Firmware Volume)。
3. 定位BFV中的SEC影响。
4. 如果是64位系统,则从32位模式转换至64位模式。
5. 调用SEC入口函数。
整体流程图:

Reset Vector :刷新缓存并进入ROM中的主初始化程序
Switch to Protected Moudle :转换到非分页平面模型保护模式
Initialze MTRRs for BSP:将不同内存范围的缓存状态设置为已知状态
Microcode Path Update:为所有现有的cpu执行微码补丁更新
Initialize No-Eviction Mode NEM:建立临时内存
Various early BSP/AP interactions一系列包含一些固定延迟事件的标准步骤:
Hand-off to PEI entry point

Initialize No-Eviction Mode NEM::在发现平台上的内存之前,在cpu缓存中建立一个数据区域,以便在初始化早期使用基于堆栈的编程语言
Various early BSP/AP interactions:一系列包含一些固定延迟事件的标准步骤,如:发送INIT IPI到所有ap,发送Start-up IPI (SIPI)到所有ap,从ap收集BIST数据
关于BP启动AP的相关知识:https://blog.csdn.net/mengkevin/article/details/72875899

Resect Vector部分

代码体现:最先开始运行的代码文件为UefiCpuPkg\ResetVector\Vtf0\Ia16\ResetVectorVtf0.asm

 BITS    16 //该伪指令说明程序是以16位的方式运行
 ALIGN  16//按照 16 个字节的倍数对齐下一个符号,空隙默认用0 来填充
	 ;
	 ;当页表在VTF0中时,将图像大小调整为4k。如果VTF0图像内置了页表,那么我们需要确保VTF0的末尾是页表结束位置上方的4k。
	 ;这是必需的,以便当VTF0位于固件设备中0x100000000 (4GB)以下时,页表将是4k对齐的。
     ;
%ifdef ALIGN_TOP_TO_4K_FOR_PAGING
TIMES (0x1000 - ($ - EndOfPageTables) - 0x20) DB 0
%endif
    ;
    ;应用程序处理器入口点
	;GenFv生成在4k边界上对齐的代码,该边界将跳转到此位置。(0xffffffe0)这允许使用本地APIC启动IPI来唤醒应用程序处理器。
	;
applicationProcessorEntryPoint:
	jmp     EarlyApInitReal16
ALIGN   8
    DD      0

EarlyBspInitReal16

通过jmp跳转到UefiCpuPkg\ResetVector\Vtf0\Ia16\Init16.asm下的EarlyBspInitReal16:位置

    ;
	; DI(destination index)是目的变址寄存器,用做隐含的目的串地址,默认在ES中;ES叫做额外的段寄存器. 它通常跟DI一起用来做指针使用.
	; BP是基指针, 通常BP用来保存使用局部变量的地址.
	;
EarlyBspInitReal16:
    mov     di, 'BP'
    jmp     short Main16
;下面的函数说明
;OneTimeCall是宏,用于模拟call指令,与OneTimeCallRet 配合使用,因为在Reset Vector部分还没有RAM可用
;* %macro是定义多行宏,定义一个名为OneTimeCall 的宏,接收一个参数(nasm汇编)*/
/*   %macro  宏名  参数名列表
          宏体
      %endmacro  */
      
%macro  OneTimeCall 1        
    jmp     %1
%1 %+ OneTimerCallReturn:
%endmacro

%macro  OneTimeCallRet 1
    jmp     %1 %+ OneTimerCallReturn
%endmacro

那么OneTimeCall EarlyInit16相当于:
jmp EarlyInit16
EarlyInit16OneTimerCallReturn: /
需要注意的是%+可以连接字符串 /

Main16

通过jmp跳转到UefiCpuPkg\ResetVector\Vtf0\Main.asm下的Main16:位置

BITS    16
Main16:
    ;
	;OneTimeCall是宏,用于模拟call指令,与OneTimeCallRet 配合使用,因为在Reset Vector部分还没有RAM可用
	;
    OneTimeCall EarlyInit16
    
    ;
	; 将处理器从16位实模式转换为32位平面模式
	; 平面模式:直接用一个地址寄存器来线性访问4G的内存.32位的CPU最多可以寻址4GB的内存空间,如果物理内存大于4GB,超出的部分CPU是无法寻址到的。
    ; 保护模式: 在这种状态下, 一切程序都可以用线性地址(不分段)访问自己所拥有的4G的内存空间, 但是不能访问其他程序的空间. 
    ; 实模式:寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。
	;
	OneTimeCall TransitionFromReal16To32BitFlat
    
BITS    32

    ;
    ; 定位固件中的BFV
    ; FV:固件卷,指在FD上一个连续的部分,我们可以把它看成一个逻辑设备,因为我们代码真正操作的是FV,而非FD。
    ; FFS的概念也是以FV的形式存在,它描述了FV中的文件组织方式。FV之于FD,类似于thread之于package。
    ;
    OneTimeCall Flat32SearchForBfvBase
    
    ;
    ; EBP - BFV的开始
    ; EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
    ;

    ;
    ; 搜索SEC入口点
    ; 定位BFV中的SEC映像
    ;
    OneTimeCall Flat32SearchForSecEntryPoint
    
    ;
    ; ESI - SEC Core entry point
    ; ESI称为源变址寄存器,通常存放 要处理的数据的内存地址。
    ; EBP - Start of BFV
    ; esi存放了SEC的入口地址,EBP存放了BFV起始地址
    ;

%ifdef ARCH_IA32

    ;
    ; 将EAX初始值恢复到EAX寄存器
    ; ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
    ;
    mov     eax, esp
    ;
    ; 跳到32-bit sec的入口点
    jmp     esi
%else
    ;
    ; 将处理器从32位平面模式转换为64位平面模式
    ;
    OneTimeCall Transition32FlatTo64Flat
BITS    64
    ;
    ; 一些值是在32位模式下计算的。对于这些值,确保64位寄存器的上高32位为零。
    ;
    mov     rax, 0x00000000ffffffff
    and     rsi, rax
    and     rbp, rax
    and     rsp, rax
    ;
    ; RSI - SEC Core entry point
    ; RBP - Start of BFV
    ;

    ;
    ; 将初始EAX值恢复到RAX寄存器
    ;
    mov     rax, rsp

    ;
    ;  跳到64-bit sec的入口点
    ;
    jmp     rsi 
%endif

进入SEC功能区

工作内容:进入功能区后,首先利用CAR技术初始化栈,初始化IDT,初始化EFI_SEC_PEI_HAND_OFF,将控制权交给PEI,并将EFI_SEC_PEI_HAND_OFF传递给PEI.这个结构体很重要!!!

CAR (Cache ASRAM),在Cashe上开辟一段空间作为内存使用(此时内存尚未初始化,相关C语言运行需要内存和栈的空间) ;
IDT = Interrupt Descriptor Table 中断描述表,,记录了0~255的中断号和调用函数之间的关系。结构体如下所述:
下面是EFI_SEC_PEI_HAND_OFF,这个是UEFI当中在SEC阶段最重要的一个数据结构,将环境从以汇编语言执行转向C语言执行。

#define SEC_IDT_ENTRY_COUNT  34
typedef struct _SEC_IDT_TABLE {
  //
  // 在IDT之前保留8个字节来存储EFI_PEI_SERVICES**,因为IDT基址应该是8字节对齐。
  // 注意:对于IA32,只有IDT前面的4个字节用于存储EFI_PEI_SERVICES**
  //
  UINT64            PeiService;
  UINT64            IdtTable[SEC_IDT_ENTRY_COUNT];
} SEC_IDT_TABLE;

**

EFI_SEC_PEI_HAND_OFF!!!★★★★★

**

/// EFI_SEC_PEI_HAND_OFF结构体保存有关PEI核操作环境的信息,例如临时RAM位置的大小、堆栈位置和BFV位置。
typedef struct  _EFI_SEC_PEI_HAND_OFF {
  /// 数据结构的大小
  UINT16  DataSize;
  /// **指向BFV的第一个字节,PEI Dispatcher应该搜索PEI模块。**
   VOID    *BootFirmwareVolumeBase;
  /// BFV的大小,以字节为单位。
  UINTN   BootFirmwareVolumeSize;
  /// 指向临时RAM的第一个字节。
   VOID    *TemporaryRamBase;
  /// 临时RAM的大小,以字节为单位。
  UINTN   TemporaryRamSize;
  /// 指向PEI可以使用的临时RAM的第一个字节。PeiTemporaryRamBase和PeiTemporaryRamSize所描述的区域不能超出TemporaryRamBase和TemporaryRamSize所描述的区域。
  /// 这个区域不应该与StackBase和StackSize返回的区域重叠。
  VOID    *PeiTemporaryRamBase;
  ///PEI使用的可用临时RAM的大小,以字节为单位。
   UINTN   PeiTemporaryRamSize;
  ///指向堆栈的第一个字节。这可能是由TemporaryRamBase和TemporaryRamSize描述的内存的一部分,也可能是一个完全独立的区域。
  VOID    *StackBase;
  /// 堆栈的大小,以字节为单位。
  UINTN   StackSize;
} EFI_SEC_PEI_HAND_OFF;

OVMF下的SEC功能区执行过程

不同硬件平台,SEC代码会有不同实现方式,但大致过程相似。下面以OVMF为例,介绍SEC功能区执行过程。
打开文件edk2\OvmfPkg\Sec\X64\SecEntry.nasm

ASM_PFX(_ModuleEntryPoint):
    ;
    ; 用初始堆栈值填充临时RAM。
    ; The loop below will seed the heap as well, but that's harmless.
    ;
    mov     rax, (FixedPcdGet32 (PcdInitValueInTempStack) << 32) | FixedPcdGet32 (PcdInitValueInTempStack)
                                                              
    mov     rdi, FixedPcdGet32 (PcdOvmfSecPeiTempRamBase)     ; 相对于ES, qword来存储基址
    mov     rcx, FixedPcdGet32 (PcdOvmfSecPeiTempRamSize) / 8 ; qword从基地计数存储
    cld
    rep stosq

    ;
    ; 加载基于PCDs的临时RAM堆栈
    ; 临时 RAM 已经初始化,设置栈地址。PcdOvmfSecPeiTempRamBase 和 PcdOvmfSecPeiTempRamSize在 OvmfPkgIa32X64 . fdf 中定义
    ; 分别为 0x010000 和 0x008000
    %define SEC_TOP_OF_STACK (FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) + \
                          FixedPcdGet32 (PcdOvmfSecPeiTempRamSize))
    mov     rsp, SEC_TOP_OF_STACK
    nop

    ;
    ; 设置参数并调用SecCoreStartupWithStack
    ; rcx: BootFirmwareVolumePtr
    ; rdx: TopOfCurrentStack
    ;
    mov     rcx, rbp ; BFV 首地址,rbp 为传参数
    mov     rdx, rsp ; 栈起始地址
    sub     rsp, 0x20
    call    ASM_PFX(SecCoreStartupWithStack) ; 此时栈已可用,故可使用 call 指令
    ;或者call    ASM_PFX(SecStartup ) ; 
//
//IDTR, GDTR, LDTR描述符的字节打包结构
//
typedef struct {
  UINT16  Limit;
  UINTN   Base;
} IA32_DESCRIPTOR;

IA32下的Sec入口函数

根据最后的call指令找到跳转到SecCoreStartupWithStack函数,在edk2\OvmfPkg\Sec\SecMain.c中:这是在Ovmf平台下的下的,在IA32平台下有与文章开头一致的入口函数在IA32FamilyCpuPkg\SecCore\SecMain.c中,具体函数内容为:

/**
  SEC的C语言阶段的入口点。SEC汇编代码初始化一些临时内存并建立堆栈后,控制被转移到这个函数。

  @param SizeOfRam           可用的临时内存的大小。
  @param TempRamBase         临时内存的基址
  @param BootFirmwareVolume  BFV的基本地址。
**/
VOID
EFIAPI
SecStartup (
  IN UINT32                   SizeOfRam,
  IN UINT32                   TempRamBase,
  IN VOID                     *BootFirmwareVolume
  )
{
  EFI_SEC_PEI_HAND_OFF        SecCoreData;
  IA32_DESCRIPTOR             IdtDescriptor;
  SEC_IDT_TABLE               IdtTableInStack;
  UINT32                      Index;
  UINT32                      PeiStackSize;
  EFI_STATUS                  Status;

  //
  // Report Status Code to indicate entering SEC core
  // 报告状态码指示进入SEC核心
  // 如果状态代码类型已启用,则报告带有最小参数的状态代码。
  // 如果由type指定的状态码类型在PcdReportStatusCodeProperyMask中启用,就调用ReportStatusCode()传入类型和值。
  //
  REPORT_STATUS_CODE (
    EFI_PROGRESS_CODE,
    EFI_SOFTWARE_SEC | EFI_SW_SEC_PC_ENTRY_POINT
    );
  //
  //PcdPeiTemporaryRamStackSize的值是指定临时RAM中的堆栈大小。0表示临时ramsize的一半。
  //
  PeiStackSize = PcdGet32 (PcdPeiTemporaryRamStackSize);//|0|UINT32|0x10001003
  if (PeiStackSize == 0) {
    PeiStackSize = (SizeOfRam >> 1);
  }
  ASSERT (PeiStackSize < SizeOfRam);
  
  //
  // Process all libraries constructor function linked to SecCore.
  // 处理所有链接到SecCore的库构造函数。
  // 为模块的所有依赖库调用库构造函数的自动生成函数。一旦建立了堆栈,SEC核心必须调用这个函数。
  //
  ProcessLibraryConstructorList ();

  //
  // Initialize floating point operating environment to be compliant with UEFI spec.
  // 初始化浮点操作环境以符合UEFI规范。初始化浮点寄存器
  // 这个函数将浮点控制字初始化为0x027F(所有异常都被屏蔽,双精度,四舍五入到最接近),
  // 多媒体扩展控制字(如果支持)初始化为0x1F80(所有异常都被屏蔽,四舍五入到最接近,对于被屏蔽的下流,flush为零)。
  //
  InitializeFloatingPointUnits ();

  
  // |-------------------|---->
  // |Idt Table          |
  // |-------------------|
  // |PeiService Pointer |    PeiStackSize
  // |-------------------|
  // |                   |
  // |      Stack        |
  // |-------------------|---->
  // |                   |
  // |                   |
  // |      Heap         |    PeiTemporayRamSize
  // |                   |
  // |                   |
  // |-------------------|---->  TempRamBase
  //关于堆栈的相关知识:https://blog.csdn.net/myqq1418/article/details/81584761
  //
  //  初始化IDT
  //  在IDT之前保留8个字节来存储EFI_PEI_SERVICES**,因为IDT基址应该是8字节对齐。
  //  注意:对于IA32,只有IDT前面的4个字节用于存储EFI_PEI_SERVICES**
  //
  IdtTableInStack.PeiService = 0;
  for (Index = 0; Index < SEC_IDT_ENTRY_COUNT; Index ++) {
 // mIdtEntryTemplate	IA32_IDT_GATE_DESCRIPTOR	IDT 中断门描述符
 // IDT里的描述符就是描述中断处理程序的数据结构
    CopyMem ((VOID*)&IdtTableInStack.IdtTable[Index], (VOID*)&mIdtEntryTemplate, sizeof (UINT64));
  }
  //IdtDescriptor  IA32_DESCRIPTOR  IDTR, GDTR, LDTR描述符的字节打包结构
  //描述符是存储描述信息的数据结构。GDT/LDT里描述符就是描述段地址和门的描述符。
  IdtDescriptor.Base  = (UINTN) &IdtTableInStack.IdtTable;
  IdtDescriptor.Limit = (UINT16)(sizeof (IdtTableInStack.IdtTable) - 1);
  //
  // 写入当前中断描述符表寄存器(GDTR)描述符。
  // 写入当前IDTR描述符并在IDTR中返回它。此功能仅适用于IA-32和X64。
  // 如果Idtr为NULL,则ASSERT()。
  //
  AsmWriteIdtr (&IdtDescriptor);

  //
  // Setup the default exception handlers
  // 初始化调试代理。
  // 该功能用于为SMM代码的源代码调试设置调试环境。
  // 如果InitFlag为DEBUG_AGENT_INIT_SMM,则会覆盖IDT表项,初始化调试端口。它将从GUIDed HOB获得调试代理邮箱,
  // 如果它存在,调试代理将把它复制到SMM空间的本地邮箱中。它将覆盖IDT表项并初始化调试端口。Context将为空。
  // 如果“InitFlag”为“DEBUG_AGENT_INIT_ENTER_SMI”,则调试代理将保存调试寄存器并在SMM空间中获取本地邮箱。Context将为空。
  // 当“InitFlag”为“DEBUG_AGENT_INIT_EXIT_SMI”时,调试代理将恢复调试寄存器。Context将为空。
  //
  Status = InitializeCpuExceptionHandlers (NULL);
  ASSERT_EFI_ERROR (Status);

  //
  // Update the base address and length of Pei temporary memory
  // 初始化SecCoreData,将临时的RAM地址,栈地址、BFV地址赋值给SecCoreData
  //
  SecCoreData.DataSize               = (UINT16) sizeof (EFI_SEC_PEI_HAND_OFF);
  SecCoreData.BootFirmwareVolumeBase = BootFirmwareVolume;
  SecCoreData.BootFirmwareVolumeSize = (UINTN)(0x100000000ULL - (UINTN) BootFirmwareVolume);
  SecCoreData.TemporaryRamBase       = (VOID*)(UINTN) TempRamBase;
  SecCoreData.TemporaryRamSize       = SizeOfRam;
  SecCoreData.PeiTemporaryRamBase    = SecCoreData.TemporaryRamBase;
  SecCoreData.PeiTemporaryRamSize    = SizeOfRam - PeiStackSize;
  SecCoreData.StackBase              = (VOID*)(UINTN)(TempRamBase + SecCoreData.PeiTemporaryRamSize);
  SecCoreData.StackSize              = PeiStackSize;

  //
  // Initialize Debug Agent to support source level debug in SEC/PEI phases before memory ready.
  // 在内存准备好之前,初始化调试代理以支持SEC/PEI阶段的源级调试。
  //
  InitializeDebugAgent (DEBUG_AGENT_INIT_PREMEM_SEC, &SecCoreData, SecStartupPhase2);

}

InitializeDebugAgent()->SecStartupPhase2()

在最后一行代码处,函数在MdeModulePkg\Library\DebugAgentLibNull\DebugAgentLibNull.c中,具体内容为:

/**
  初始化调试代理。

  此函数用于设置调试环境,以支持源代码级的调试。
  如果某些调试代理库实例有一些私人数据保存在堆栈中,这个函数必须在此模式工作不返回给调用者,
  然后调用者需要结束后所有其他逻辑InitializeDebugAgent()成一个函数并将其传递到InitializeDebugAgent () .
  InitializeDebugAgent()负责调用传入函数结束时InitializeDebugAgent()。

  如果参数函数不为空,调试代理库实例将通过在上下文中传递参数来调用它。

  如果Function()为空,调试代理库实例将在安装调试环境后返回。

  @param[in] InitFlag     Init flag is used to decide the initialize process.
  @param[in] Context      Context needed according to InitFlag; it was optional.
  @param[in] Function     Continue function called by debug agent library; it was
                          optional.
**/
VOID
EFIAPI
InitializeDebugAgent (
  IN UINT32                InitFlag,
  IN VOID                  *Context, OPTIONAL
  IN DEBUG_AGENT_CONTINUE  Function  OPTIONAL
  )
{
  if (Function != NULL) {
    Function (Context);
  }
}

SecStartupPhase2()-> PeiCore ()

紧接着跳转到SecStartupPhase2()函数;先看一下涉及到的数据结构

///
/// PEIM用来描述PEI Foundation可用服务的数据结构。
/// 
typedef struct {
  ///
  /// 这个字段是一组标记,描述这个导入的表条目的特征。
  /// 所有标记都定义为EFI_PEI_PPI_DESCRIPTOR_***,也可以组合为一个标记。
  /// Flags描述了PPI的特征
  UINTN     Flags;
  ///
  /// 命名接口的EFI_GUID的地址。
  /// Guid是PPI的名字。
  EFI_GUID  *Guid;
  ///
  /// 指向PPI的指针。它包含安装服务所需的信息。
  /// Ppi是service的实体,这个是PPI的真正意义。
  VOID      *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;
/**
  在InitializeDebugAgent()结束时调用调用者提供的函数。

  SEC的C语言阶段的入口点。SEC汇编代码初始化一些临时内存并建立堆栈后,
  控制被转移到这个函数。

  @param[in] Context   初始化debugagent()的第一个输入参数。
**/
VOID
NORETURN
EFIAPI
SecStartupPhase2(
  IN VOID                     *Context
  )
{
  EFI_SEC_PEI_HAND_OFF        *SecCoreData;
  EFI_PEI_PPI_DESCRIPTOR      *PpiList;
  UINT32                      Index;
  EFI_PEI_PPI_DESCRIPTOR      *AllSecPpiList;
  EFI_PEI_CORE_ENTRY_POINT    PeiCoreEntryPoint;

  SecCoreData   = (EFI_SEC_PEI_HAND_OFF *) Context;
  AllSecPpiList = (EFI_PEI_PPI_DESCRIPTOR *) SecCoreData->PeiTemporaryRamBase;
  //
  // Find Pei Core entry point. It will report SEC and Pei Core debug information if remote debug is enabled.
  // 找到Pei核心入口点。如果启用了远程调试,它将报告SEC和Pei核心调试信息。
  //
  FindAndReportEntryPoints ((EFI_FIRMWARE_VOLUME_HEADER *) SecCoreData->BootFirmwareVolumeBase, &PeiCoreEntryPoint);
  if (PeiCoreEntryPoint == NULL)
  {
    CpuDeadLoop ();
  }
  //
  // 在进入PeiCore之前执行特定平台的初始化。
  //
  PpiList = SecPlatformMain (SecCoreData);
  if (PpiList != NULL) {
    //
    // 从终端PPI中移除终端标志
    //
    CopyMem (AllSecPpiList, mPeiSecPlatformInformationPpi, sizeof (mPeiSecPlatformInformationPpi));
    Index = sizeof (mPeiSecPlatformInformationPpi) / sizeof (EFI_PEI_PPI_DESCRIPTOR) - 1;
    AllSecPpiList[Index].Flags = AllSecPpiList[Index].Flags & (~EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);

    //
    // 向平台添加额外的PPI列表
    //
    Index += 1;
    while (((PpiList->Flags & EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST) != EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST)) {
      CopyMem (&AllSecPpiList[Index], PpiList, sizeof (EFI_PEI_PPI_DESCRIPTOR));
      Index++;
      PpiList++;
    }

    //
    // 添加终端PPI
    //
    CopyMem (&AllSecPpiList[Index ++], PpiList, sizeof (EFI_PEI_PPI_DESCRIPTOR));

    //
    // 将PpiList设置为总PPI
    //
    PpiList = AllSecPpiList;

    //
    // 调整PEI TEMP RAM范围。
    //
    ASSERT (SecCoreData->PeiTemporaryRamSize > Index * sizeof (EFI_PEI_PPI_DESCRIPTOR));
    SecCoreData->PeiTemporaryRamBase = (VOID *)((UINTN) SecCoreData->PeiTemporaryRamBase + Index * sizeof (EFI_PEI_PPI_DESCRIPTOR));
    SecCoreData->PeiTemporaryRamSize = SecCoreData->PeiTemporaryRamSize - Index * sizeof (EFI_PEI_PPI_DESCRIPTOR);
  } else {
    //
    // 没有添加PPI, PpiList直接指向通用PPI列表。
    //
    PpiList = &mPeiSecPlatformInformationPpi[0];
  }

  DEBUG ((
    DEBUG_INFO,
    "%a() Stack Base: 0x%p, Stack Size: 0x%x\n",
    __FUNCTION__,
    SecCoreData->StackBase,
    (UINT32) SecCoreData->StackSize
    ));

  //
  // 报告状态代码,指示转移到PEI核心
  //
  REPORT_STATUS_CODE (
    EFI_PROGRESS_CODE,
    EFI_SOFTWARE_SEC | EFI_SW_SEC_PC_HANDOFF_TO_NEXT
    );

  //
  // 将控制转移到PEI核心
  //
  ASSERT (PeiCoreEntryPoint != NULL);
  (*PeiCoreEntryPoint) (SecCoreData, PpiList);

  //
  // Should not come here.
  //
  UNREACHABLE ();
}

子函数具体说明之SecPlatformMain()

关于Windows架构的一些知识可以戳https://blog.csdn.net/freeking101/article/details/102752048

/**
  A developer supplied function to perform platform specific operations.

    开发人员提供了执行平台特定操作的函数。
    SecPlatformMain()是开发人员提供的函数,用于执行适合于给定平台的任何操作。
    在SEC core将控制权移交给PEI core之前,它会被调用。
    平台开发人员可以修改传递给PEI核心的SecCoreData。
    它返回一个平台特定的PPI列表,平台希望传递给PEI core。
    通用的SEC core模块将合并该名单,加入到传递给PEI core的最终名单中。

  @param[in,out] SecCoreData          与传递给PEI core的参数相同。它可以被这个函数覆盖。

  @return  平台特定的PPI列表将被传递给PEI core,如果不需要这种平台特定的PPI列表,则为NULL。
**/
EFI_PEI_PPI_DESCRIPTOR *
EFIAPI
SecPlatformMain (
  IN OUT   EFI_SEC_PEI_HAND_OFF        *SecCoreData
  )
{
  EFI_PEI_PPI_DESCRIPTOR      *PpiList;

  DEBUG((DEBUG_INFO, "SecPlatformMain\n"));
  DEBUG((DEBUG_INFO, "BootFirmwareVolumeBase - 0x%x\n", SecCoreData->BootFirmwareVolumeBase));
  DEBUG((DEBUG_INFO, "BootFirmwareVolumeSize - 0x%x\n", SecCoreData->BootFirmwareVolumeSize));
  DEBUG((DEBUG_INFO, "TemporaryRamBase       - 0x%x\n", SecCoreData->TemporaryRamBase));
  DEBUG((DEBUG_INFO, "TemporaryRamSize       - 0x%x\n", SecCoreData->TemporaryRamSize));
  DEBUG((DEBUG_INFO, "PeiTemporaryRamBase    - 0x%x\n", SecCoreData->PeiTemporaryRamBase));
  DEBUG((DEBUG_INFO, "PeiTemporaryRamSize    - 0x%x\n", SecCoreData->PeiTemporaryRamSize));
  DEBUG((DEBUG_INFO, "StackBase              - 0x%x\n", SecCoreData->StackBase));
  DEBUG((DEBUG_INFO, "StackSize              - 0x%x\n", SecCoreData->StackSize));

  // 初始化本地APIC定时器。
  // 初始化并使能本地APIC定时器。
  InitializeApicTimer (0, (UINT32) -1, TRUE, 5);
  //
  // 使用堆中间作为临时缓冲区,它将被调用者复制。
  // 不要使用Stack,因为它会导致PeiCore对Stack的计算错误
  //
  PpiList = (VOID *)((UINTN)SecCoreData->PeiTemporaryRamBase + (UINTN)SecCoreData->PeiTemporaryRamSize/2);
  CopyMem (PpiList, mPeiSecPlatformPpi, sizeof(mPeiSecPlatformPpi));

  //
  // TopOfTemporaryRamPpi补丁
  //
  PpiList[0].Ppi = (VOID *)((UINTN)SecCoreData->TemporaryRamBase + SecCoreData->TemporaryRamSize);

  return PpiList;
}

子函数说明之FindAndReportEntryPoints()

/**
找到并返回PEI核心入口点。
它还可以查找SEC和PEI核心文件的调试信息。如果启用了远程调试,它将报告它们。
  
  @param   BootFirmwareVolumePtr    指向BFV。
  @param   PeiCoreEntryPoint        PEI核心的入口.
**/
VOID
EFIAPI
FindAndReportEntryPoints (
  IN  EFI_FIRMWARE_VOLUME_HEADER       *BootFirmwareVolumePtr,
  OUT EFI_PEI_CORE_ENTRY_POINT         *PeiCoreEntryPoint
  )
{
  EFI_STATUS                       Status;
  EFI_PHYSICAL_ADDRESS             SecCoreImageBase;
  EFI_PHYSICAL_ADDRESS             PeiCoreImageBase;
  PE_COFF_LOADER_IMAGE_CONTEXT     ImageContext;

  //
  // 查找SEC核心和PEI核心Image base
  //
  Status = FindImageBase (BootFirmwareVolumePtr, &SecCoreImageBase, &PeiCoreImageBase);
  ASSERT_EFI_ERROR (Status);

  ZeroMem ((VOID *) &ImageContext, sizeof (PE_COFF_LOADER_IMAGE_CONTEXT));
  //
  // 当启用远程调试时,报告SEC核心调试信息
  //
  ImageContext.ImageAddress = SecCoreImageBase;
  ImageContext.PdbPointer = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageContext.ImageAddress);
  PeCoffLoaderRelocateImageExtraAction (&ImageContext);

  //
  // 当开启远程调试时,上报PEI核心调试信息
  //
  ImageContext.ImageAddress = PeiCoreImageBase;
  ImageContext.PdbPointer = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageContext.ImageAddress);
  PeCoffLoaderRelocateImageExtraAction (&ImageContext);

  //
  // 找到PEI核心入口点
  //
  Status = PeCoffLoaderGetEntryPoint ((VOID *) (UINTN) PeiCoreImageBase, (VOID**) PeiCoreEntryPoint);
  if (EFI_ERROR (Status)) {
    *PeiCoreEntryPoint = 0;
  }
  return;
}

PeiCoreEntryPoin这个值就是一个64位的虚拟地址,这个地址是Pei阶段的Entry point函数的入口地址,然后通过(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable); 这段代码跳转到Pei阶段。函数位于MdePkg/Library/PeiCoreEntryPoint/PeiCoreEntryPoint.c:

/**
  PE/COFF图像的入口点为PEI核心。
  这个函数是PEI Foundation的入口点,它允许SEC阶段传递关于堆栈、临时RAM和引导固件卷的信息。
  此外,它还允许SEC阶段以一个或多个ppi的形式传递服务和数据,以供PEI阶段使用。
  从SEC传递到PEI Foundation的额外PPIs数量没有限制。作为初始化阶段的一部分,
  PEI Foundation将把这些sec托管的PPIs添加到其PPI数据库中,
  这样PEI Foundation和任何模块都可以利用这些早期PPIs中的相关服务调用and/or代码。
  这个函数需要调用ProcessModuleEntryPointList(),并将上下文参数设置为NULL。ProcessModuleEntryPoint()永远不会返回。
  PEI核心负责在PEI服务表和PEI核心本身的文件句柄建立之后调用ProcessLibraryConstructorList()。
如果ProcessModuleEntryPointList()返回,则ASSERT()并停止系统。

  @param SecCoreData  指向一个包含PEI核心操作环境信息的数据结构,
                      例如临时RAM的大小和位置,堆栈位置和BFV位置
                      
  @param PpiList      指向PEI核心最初要安装的一个或多个PPI描述符的列表。
                      空的PPI列表由单个描述符组成,其结束标记为EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST。
                      作为初始化阶段的一部分,PEI Foundation将把这些sec托管的PPIs添加到其PPI数据库中,
                      这样PEI Foundation和任何模块都可以利用这些早期PPIs中的相关服务调用和/或代码。

**/
VOID
EFIAPI 
_ModuleEntryPoint(
  IN CONST  EFI_SEC_PEI_HAND_OFF    *SecCoreData,
  IN CONST  EFI_PEI_PPI_DESCRIPTOR  *PpiList
)
{
  ProcessModuleEntryPointList (SecCoreData, PpiList, NULL);
  
  //
  // Should never return
  //
  ASSERT(FALSE);
  CpuDeadLoop ();  
}

这个函数又调用ProcessModuleEntryPointList (SecCoreData, PpiList, NULL);函数,这个函数位于AutoGen.c中,这个文件是脚本生成的,在这个函数中调用了PeiCore入口函数,这样就连接了PEI阶段。

VOID
EFIAPI
ProcessModuleEntryPointList (
  IN CONST  EFI_SEC_PEI_HAND_OFF    *SecCoreData,
  IN CONST  EFI_PEI_PPI_DESCRIPTOR  *PpiList,
  IN VOID                           *Context
  )
{
  PeiCore (SecCoreData, PpiList, Context);                                                                                                                      
}
  • 9
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
RX 580 8G 2304 大地之神 BIOS是指AMD的显卡型号RX 580 8G 2304,其中2304代表该显卡有2304个流处理器(即图形计算单元)。而"大地之神"则是该显卡的非官方命名,可能是由于该显卡强大的性能和稳定性而得名。 BIOS是显卡上的一块固态存储芯片,用于存储显卡的基本输入输出系统(BIOS)或固件。显卡的BIOS包含了与显卡硬件相关的信息和设置,如时钟频率、电压、功耗限制等。在显卡硬件初始化过程中,BIOS会加载并操作这些设置,以确保显卡正常运行。 对于RX 580 8G 2304 大地之神 BIOS,可能包含某些专为该型号显卡优化的设置。这些设置可能会通过提高显卡性能、改善功耗管理、优化散热等方面来提供更好的使用体验。在使用显卡时,用户可以通过刷新或更新BIOS来改变显卡的参数和设置,以满足个人需求或适应特定的计算任务。 需要注意的是,更新显卡的BIOS对硬件来说是一个敏感的操作,需要非常小心。若不正确地更新了显卡的BIOS,可能会导致显卡无法启动或出现其他不同寻常的问题。因此,在更新显卡BIOS之前,建议详细阅读相关的更新说明和指南,并确保按照正确的步骤来操作。 总之,RX 580 8G 2304 大地之神 BIOS是指该型号显卡的固件程序,用于存储显卡的硬件设置和参数。更新BIOS可能会提供更好的性能和体验,但在操作之前需要谨慎,并遵循官方的指南。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值