uefi中Sec阶段和Pei阶段的启动分析

本文的启动分析是基于龙心3A2000+780E桥片的处理器的。

首先:

SEC(安全性)阶段:

系统开始执行第一条指令,这时的Memory没有被初始化。主要工作是建立临时的Memory,它可以是处理器的Cache,或是system Static RAM(SRAM)。另外,SEC Phase需要知道一些早期的内存被映射到的位置以及BFV(Boot Firmware Volume)的位置。

PEI(EFI前初始化)阶段:(详细的Pei阶段请看:http://www.doc88.com/p-587672390607.html)

主要工作是Memory初始化以及一些必要的CPU、Chipset等等的初始化,为加载DXE做准备,准备HOB数据。

 

Sec:

开机系统第一条指令由硬件规定死,从地址0x1fc00000地址出开始执行,由于这个地址是物理地址,cpu要从这个物理地址取指执行,就需要能访问这个地址。由于在mips体系中,32位虚拟地址空间中,kseg0(0x80000000-0x9fffffff)是cache模式,kseg1(0xa0000000-0xbfffffff)是uncache模式。这个时候cache还没有初始化,因此需要使用uncache模式访问这个地址。因此实际访问的虚拟地址就是0xbfc00000,而64位虚拟地址的时候,0x9800000000000000开始的地址是cache模式的,0x9000000000000000开始的地址是uncache模式的,因此为了在64位模式下能访问1fc00000这个地址,就需要使用0x900000001fc00000这个虚拟地址。CPU上电执行的第一个虚拟地址就是这个地址。下图是flash中存储fd文件的位置。uefi中Sec阶段和Pei阶段的启动分析

 

由于在这个地址上存储的第一条指令就是跳转指令,跳转到Sec代码的起始处开始执行。即跳转到0x980000001fc10000这个地址。这是sec阶段代码的起始地址。位于start.s中的汇编代码,在这里主要做了对cpu的初始化和初始化cpu的串口。然后开始锁cache,这是为了早点进入C环境,将Cache作为临时的内存使用,这是uefi的一个特点,当然这里也可以不锁,那就等内存初始化好之后,再跳到内存中执行。这里锁cache的好处在于,锁定cache之后,就可以建立C环境,这样内存初始化的代码就可以用C来实现。否则只能用汇编代码。我们这里采用了锁cache的方式。锁cache的代码如下,锁定了2M的空间:

 PRINTSTR("\r\nLock 2MB scache 0x9800000410000000 - 0x9800000410200000\r\n")
    dli  t0, 0x900000003ff00000
    dli  t1, 0xffffffffffe00000
    sd   t1, 0x248(t0)
    dli  t1, 0x8000000410000000
    sd   t1, 0x208(t0)
    ld   t1, 0x208(t0)  //make sure config is ok sync

    PRINTSTR("Lock Scache Done.\r\n")

锁定cache之后,就准备跳转到C环境中去执行。下面的代码就是跳转的过程,SecCoreStartupWithStack就是C环境的入口函数。位于SecMain.c中

call_centry:
    dli __a0, 0x9800000410000000 # rambase
    lui     a1, 0x1  # size
    daddu __sp, a0, a1   # stackbase
    dsubu __sp, sp, 8
    dla __v0, SecCoreStartupWithStack
    jalr _v0

    nop

跳转到VOID
EFIAPI

SecCoreStartupWithStack (UINT64 mbase, UINT64 msize)这个函数的入参mbase也就是上面的0x9800000410000000,为什么是这个地址还不确定。msize就是

 lui     a1, 0x1  # size

    daddu __sp, a0, a1   # stackbase
    dsubu __sp, sp, 8

这三条汇编执行设置的堆栈的大小0x10000(64k)的大小。这里2Mcache是如何使用的请看下面的图片

uefi中Sec阶段和Pei阶段的启动分析

这个函数中主要初始化了sec阶段要使用的数据结构,和设置上图所设置的堆栈大小和地址的分配。接下来进入到函数

SecStartupPhase2 (&SecCoreData),这里SecCoreData的数据类型就是下面的结构

typedef struct _EFI_SEC_PEI_HAND_OFF {
  UINT16  DataSize;
  VOID    *BootFirmwareVolumeBase;
  UINTN   BootFirmwareVolumeSize;
  VOID    *TemporaryRamBase;
  UINTN   TemporaryRamSize;
  VOID    *PeiTemporaryRamBase;
  UINTN   PeiTemporaryRamSize;
  VOID    *StackBase;
  UINTN   StackSize;

} EFI_SEC_PEI_HAND_OFF;

在上面的函数中已经初始化。在这个函数中主要主要的工作就是FindAndReportEntryPoint (&BootFv, &PeiCoreEntryPoint);这个函数来查找Pei阶段的入口点。PeiCoreEntryPoin这个值就是一个64位的虚拟地址,这个地址就是Pei阶段的Entry point函数的入口地址,然后通过(*PeiCoreEntryPoint) (SecCoreData, (EFI_PEI_PPI_DESCRIPTOR *)&mPrivateDispatchTable); 这段代码跳转到Pei阶段。跳转的函数位于MdePkg/Library/PeiCoreEntryPoint/PeiCoreEntryPoint.c中的VOID
EFIAPI
_ModuleEntryPoint(                                                                                                                                              
  IN CONST  EFI_SEC_PEI_HAND_OFF    *SecCoreData,
  IN CONST  EFI_PEI_PPI_DESCRIPTOR  *PpiList

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

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);                                                                                                                      
}
这样便跳转到了Pei阶段。
 

Pei:

从Sec阶段中直接查找到PeiCore的位置,并跳转到入口函数开始执行,Pei阶段的入口地址函数位于MdeModulePkg/Core/Pei/PeiMain/PeiMain.c中的PeiCore(这部分代码位于PeiCore.efi中,现在mem没有初始化,还在cache中执行,后续内存初始化之后,还会再执行一遍。)

VOID
EFIAPI
PeiCore (
  IN CONST EFI_SEC_PEI_HAND_OFF        *SecCoreDataPtr,                                                                                                         
  IN CONST EFI_PEI_PPI_DESCRIPTOR      *PpiList,
  IN VOID                              *Data
  )

这个函数有三个入参,Sec阶段执行跳转的函数只传了两个,第三个参数为NULL。

Pei阶段使用的系统一些服务的函数都位于下面这个表中。

EFI_PEI_SERVICES  gPs = {
  {
    PEI_SERVICES_SIGNATURE,                                                                                                                                                              
    PEI_SERVICES_REVISION,
    sizeof (EFI_PEI_SERVICES),
    0,
    0
  },
  PeiInstallPpi,
  PeiReInstallPpi,
  PeiLocatePpi,
  PeiNotifyPpi,

  PeiGetBootMode,
  PeiSetBootMode,

  PeiGetHobList,
  PeiCreateHob,

  PeiFfsFindNextVolume,
  PeiFfsFindNextFile,
  PeiFfsFindSectionData,

  PeiInstallPeiMemory,      
  PeiAllocatePages,
  PeiAllocatePool,
  (EFI_PEI_COPY_MEM)CopyMem,
  (EFI_PEI_SET_MEM)SetMem,

  PeiReportStatusCode,
  PeiResetSystem,

  &gPeiDefaultCpuIoPpi,
  &gPeiDefaultPciCfg2Ppi,

  PeiFfsFindFileByName,
  PeiFfsGetFileInfo,
  PeiFfsGetVolumeInfo,
  PeiRegisterForShadow,
  PeiFfsFindSectionData3,
  PeiFfsGetFileInfo2,
  PeiResetSystem2

};

Pei阶段几个专业的名词的作用:

PEI Core–T在Pei阶段负责Dispatch PeiM和提供系统服务的函数

•PEIM–Pei阶段的各个模块(这里包含四个.efi文件,这个文件是由下面的四个inf文件生成的


INF  MdeModulePkg/Universal/PCD/Pei/Pcd.inf
INF  IntelFrameworkModulePkg/Universal/StatusCode/Pei/StatusCodePei.inf
INF  MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
INF  $(PLATFORM_DIRECTORY)/PlatformPei/PlatformPei.inf)

•PPI–(Peim到Peim之间相互调用的接口函数)
•PEI Dispatcher–(寻找个Dispatch Peim的核心函数)

•PEI Services–(Pei阶段为Peim提供服务的函数)

在函数PeiCore中,前期主要做了一些初始化的操作,具体的操作如下:

 

//初始化系统服务表

CopyMem (&PrivateData.ServiceTableShadow, &gPs, sizeof (gPs));

//设置全局变量系统服务表的指针指向上面的表

SetPeiServicesTablePointer ((CONST EFI_PEI_SERVICES **)&PrivateData.Ps);
  // Initialize PEI Core Services

  InitializeMemoryServices   (&PrivateData,    SecCoreData, OldCoreData);

// init Ppi Service

InitializePpiServices      (&PrivateData,    OldCoreData);

//init SecurityService

InitializeSecurityServices (&PrivateData.Ps, OldCoreData);

InitializeDispatcherData   (&PrivateData,    OldCoreData, SecCoreData);

InitializeImageServices    (&PrivateData,    OldCoreData);

//Dispatch Peim

PeiDispatcher (SecCoreData, &PrivateData);在这个函数中开始查找PeiM并执行,我们这里一共有四个,执行的顺序是PcdPeim.efi、StatusCodePei.efi、PlatformPei.efi(初始化内存,在这里执行,内存初始化之后还要再执行一次PeiCore)、PeiCore.efi(从内存中执行)、DxeIpl.efi(在这个模块里将Pei阶段向Dxe阶段跳转的函数注册进去)

下面介绍一下Pei阶段的四个驱动的主要作用:

PcdPeim.efi主要用来注册pcd(platform configuration data)处理函数,通过其getpcd函数可以获取在编译阶段定义的全局配置值(比如file volume 要加载的位置/大小等信息);具体的实现请看PcdPeimInit这个函数。

StatusCodePei.efi函数主要完用于打印报告uefi运行状态,函数实现请看PeiStatusCodeDriverEntry函数

 

PlatformPei.efi是我们的mips平台初始化(窗口配置,ht配置,ddr配置以及将DXE从flash拷贝到ddr中);

DxeIpl.efi主要是初始化Dxe阶段需要的数据结构和注册跳转到Dxe阶段的函数DxeLoadCore。

在DxeLoadCore函数中,首先进行启动模式的判断,UEFI中是支持S3启动的。然后开始组建传给Dxe阶段需要的HOB表,然后开始查找DxeCore阶段的入口函数,这个函数是通过DxeIplFindDxeCore ()实现的,找到之后将Dxe阶段的代码load到内存的一个位置。然后通过函数HandOffToDxeCore (DxeCoreEntryPoint, HobList);将控制权交给Dxe阶段。在这个函数里面重新分配了Dxe阶段需要运行的堆栈的大小和位置。然后通过函数SwitchStack实现的指令的跳转。(这个函数在具体的平台实现是不同的)

上一篇:windows10+arch linux双系统 uefi启动


下一篇:UEFI 基础教程 (五) - PPI 初探