UEFI学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)

文章目录

  • 1. AARCH64编译环境搭建
  • 2. ArmPlatformPriPeiCore
    • 2.1 QEMU_EFI.fd包含了什么
    • 2.2 QEMU virt aarch64相关
    • 2.3 从第一条指令到ArmPlatformPrePeiCore入口
    • 2.4 ArmPlatformPrePeiCore做了点什么
      • _ModuleEntryPoint
      • CEntryPoint
      • PrimaryMain

1. AARCH64编译环境搭建

git clone https://github.com/tianocore/edk2-platforms.git
git clone https://github.com/acpica/acpica.git
git clone https://github.com/tianocore/edk2.git
 
cd edk2
git submodule update --init
cd ..

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install qemu-system-aarch64

export WORKSPACE=$PWD
export PACKAGES_PATH=$WORKSPACE/edk2:$WORKSPACE/edk2-platforms
export IASL_PREFIX=$WORKSPACE/acpica/generate/unix/bin/
export GCC5_AARCH64_PREFIX=/usr/bin/aarch64-linux-gnu-

source edk2/edksetup.sh
build -a AARCH64 -t GCC5 -p edk2/ArmVirtPkg/ArmVirtQemu.dsc -b DEBUG

编译完之后会生成UEFI文件:Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/FV/QEMU_EFI.fd

运行命令如下

 qemu-system-aarch64 -M virt -cpu cortex-a57 -bios Build/ArmVirtQemu-AARCH64/DEBUG_GCC5/FV/QEMU_EFI.fd -net none -serial stdio

2. ArmPlatformPriPeiCore

大部分教程都是用OVMF来做示例,OVMF中第一个运行的UEFI模块是SEC。但AARCH64中的SEC是这个ArmPlatformPriPeiCore。所以在edk2的AARCH64示例中,ArmPlatformPriPeiCore是第一个运行的模块。

2.1 QEMU_EFI.fd包含了什么

我们用UEFITool NE 打开QEMU_EFI.fd,可以看到如下图
UEFI学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)_第1张图片

  1. ArmPlatformPrePeiCore,充当的SEC core的模块。从0x1000存放
  2. PeiCore,PEI Core的代码
  3. 7个PEIM
    PEIM 功能
    PlatformPei 初始化SOC平台相关的代码
    MemoryInit 初始化内存
    CpuPei 初始化ARM cortex a57 cluster
    PcdPeim 提供动态Pcd
    PeiVariablePei 没用到
    DxeIplPei 提供加载DXE的功能
  4. 最后是一个Volumn image,这个是一个压缩卷,里面包含PEI后面阶段的image,如DXE, BSD等等。需要在PEI中进行解压然后运行。

2.2 QEMU virt aarch64相关

  • ROM的空间是0x00000000 - 0x3FFFFFFF
  • RAM的空间在0x40000000 - 0x7FFFFFFF
  • CPU第一条指令是在0地址运行,即在ROM上
  • QEMU_EFI.fd文件存放在ROM上,即从0地址开始

2.3 从第一条指令到ArmPlatformPrePeiCore入口

从2.2中知道CPU第一条指令从0地址执行,那么QEMU_EFI.fd里的第一个word存放了什么东西?用二进制编辑器查看QEMU_EFI.fd可以看到在0地址存放了一个word:0x14000400。
在这里插入图片描述
这是一条跳转指令,根据armv8a的手册来看,这条指令是b pc+0x1000。CPU刚启动的时候,PC寄存器是0,所以这条指令会直接跳转到0x1000地址。
UEFI学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)_第2张图片
然后同样看一下0x1000地址的数据。又是一条跳转指令0x14000d16。解析出来就是b pc+0x3458,当前pc是0x1000,因此他就跳转到了0x4458。
在这里插入图片描述
那么0x4458存放的是什么东西?
首先通过反汇编ArmPlatformPrePeiCore.debug,可以得到0x3458是ArmPlatformPrePeiCore的_ModuleEntryPoint
UEFI学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)_第3张图片
然后我们查看QEMU_EFI.fd的0x4458的地址存放的数据,就是对应_ModuleEntryPoint的第一条指令。我们知道ArmPlatformPrePeiCore是从0x1000存放的,因此实际上0x4458就是ArmPlatformPrePeiCore的_ModuleEntryPoint。而ArmPlatformPrePeiCore编译出来的代码是位置无关代码,所以通过0地址和0x1000地址的两次跳转,最终就跳转到ArmPlatformPrePeiCore的_ModuleEntryPoint中。
UEFI学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)_第4张图片

2.4 ArmPlatformPrePeiCore做了点什么

ArmPlatformPrePeiCore非常简单,主要初始化CPU,设置栈指针, 初始化PEI阶段需要的参数SecCoreData最后跳转到PEI core中去。

函数调用栈如下:

_ModuleEntryPoint
	_SetupPrimaryCoreStack
		_PrepareArguments
			CEntryPoint
				PrimaryMain
					(PeiCoreEntryPoint)(&SecCoreData, PpiList);

_ModuleEntryPoint

edk2/ArmPlatformPkg/PrePeiCore/AArch64/PrePeiCoreEntryPoint.S
这里面首先调用了ArmPlatformPeiBootAction,这个函数是个空实现,实际没什么用。接着调用SetupExceptionLevel1设置EL1的环境,然后跳转到MainEntryPoint。

ASM_FUNC(_ModuleEntryPoint)
  // Do early platform specific actions
  bl    ASM_PFX(ArmPlatformPeiBootAction)
  
  EL1_OR_EL2(x0)
1:bl    ASM_PFX(SetupExceptionLevel1)
  b     ASM_PFX(MainEntryPoint)

MainEntryPoint里就读出CPU ID去配置一下栈指针,如果是primary core就设置primary 栈,如果是secondary core就设置secondary栈。后面都只讨论primary core。栈指针从FIX PCD中获取

ASM_PFX(MainEntryPoint):
  MOV64 (x1, FixedPcdGet64(PcdCPUCoresStackBase) + FixedPcdGet32(PcdCPUCorePrimaryStackSize))
  // x0 is equal to 1 if I am the primary core
  cmp   x0, #1
  b.eq   _SetupPrimaryCoreStack

_SetupPrimaryCoreStackz中主要配置了一下栈寄存器SP,然后跳转到_PrepareArguments

_SetupPrimaryCoreStack:
  mov   sp, x1
 ...
  b     _PrepareArguments

_PrepareArguments从PCD里拿到PEI CORE的entry,然后传给CEntryPoint,后面就是C代码了

_PrepareArguments:
  // The PEI Core Entry Point has been computed by GenFV and stored in the second entry of the Reset Vector
  MOV64 (x2, FixedPcdGet64(PcdFvBaseAddress))
  ldr   x1, [x2, #8]

  // Move sec startup address into a data register
  // Ensure we're jumping to FV version of the code (not boot remapped alias)
  ldr   x3, =ASM_PFX(CEntryPoint)

CEntryPoint

edk2/ArmPlatformPkg/PrePeiCore/PrePeiCore.c
CEntryPoint主要就初始化了一些ARM CPU的一些东西,关闭cache,打开VFP, 设置VBAR之类的,然后就跳转到 PrimaryMain中去了。

CEntryPoint (
  IN  UINTN                     MpId,
  IN  EFI_PEI_CORE_ENTRY_POINT  PeiCoreEntryPoint
  )
{
  //关闭所有Dcache
  ArmDisableDataCache ();
  // Invalid所有ICache
  ArmInvalidateInstructionCache ();
  // 使能ICACHE
  ArmEnableInstructionCache ();
  // 刷下栈上的Dcache
  InvalidateDataCacheRange (
    (VOID *)(UINTN)PcdGet64 (PcdCPUCoresStackBase),
    PcdGet32 (PcdCPUCorePrimaryStackSize)
    );
  //设置VBAR到PeiVectorTable --> PEI的异常向量表
  ArmWriteVBar ((UINTN)PeiVectorTable);
  //使能浮点单元
  ArmEnableVFP ();
  PrimaryMain (PeiCoreEntryPoint);
}

PrimaryMain

edk2/ArmPlatformPkg/PrePeiCore/MainMPCore.c
PrimaryMain里主要建立了SEC阶段传给PEI的PPI list,然后配置好SecCoreData结构体,跳转到PeiCore中去。

VOID
EFIAPI
PrimaryMain (
  IN  EFI_PEI_CORE_ENTRY_POINT  PeiCoreEntryPoint
  )
{
  ...
  //创建SEC阶段的PPI
  CreatePpiList (&PpiListSize, &PpiList);
  //使能GIC
  ArmGicEnableDistributor (PcdGet64 (PcdGicDistributorBase));

  ...
  //从PCD中获取TempStack的base,TempStack在永久内存初始化之前的临时内存
  TemporaryRamBase = (UINTN)PcdGet64 (PcdCPUCoresStackBase) + PpiListSize;
  //从PCD中获取TempStack的大小
  TemporaryRamSize = (UINTN)PcdGet32 (PcdCPUCorePrimaryStackSize) - PpiListSize;

  SecCoreData.DataSize               = sizeof (EFI_SEC_PEI_HAND_OFF);
  //从PCD中获取PEI的FV地址和长度并存入SecCoreData中
  SecCoreData.BootFirmwareVolumeBase = (VOID *)(UINTN)PcdGet64 (PcdFvBaseAddress);
  SecCoreData.BootFirmwareVolumeSize = PcdGet32 (PcdFvSize);
  
  SecCoreData.TemporaryRamBase       = (VOID *)TemporaryRamBase; // We run on the primary core (and so we use the first stack)
  SecCoreData.TemporaryRamSize       = TemporaryRamSize;
  SecCoreData.PeiTemporaryRamBase    = SecCoreData.TemporaryRamBase;
  SecCoreData.PeiTemporaryRamSize    = ALIGN_VALUE (SecCoreData.TemporaryRamSize / 2, CPU_STACK_ALIGNMENT);
  SecCoreData.StackBase              = (VOID *)((UINTN)SecCoreData.TemporaryRamBase + SecCoreData.PeiTemporaryRamSize);
  SecCoreData.StackSize              = (TemporaryRamBase + TemporaryRamSize) - (UINTN)SecCoreData.StackBase;

  // Jump to PEI core entry point
  PeiCoreEntryPoint (&SecCoreData, PpiList);
}

我们在PeiCore的入口的地方加入了打印,把SecCoreData dump出来如下

DataSize:48
BootFirmwareVolumeBase:00000001000
BootFirmwareVolumeSize:000001FF000
TemporaryRamBase:0004007C030
TemporaryRamSize:00000003FD0
PeiTemporaryRamBase:0004007C030
PeiTemporaryRamSize:00000001FF0
StackBase:0004007E020
StackSize:00000001FE0

至此,SEC就运行完了,后面就是PEI阶段的执行了

你可能感兴趣的:(UEFI,驱动开发)