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

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,可以看到如下图
在这里插入图片描述

  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地址。
在这里插入图片描述
然后同样看一下0x1000地址的数据。又是一条跳转指令0x14000d16。解析出来就是b pc+0x3458,当前pc是0x1000,因此他就跳转到了0x4458。
在这里插入图片描述
那么0x4458存放的是什么东西?
首先通过反汇编ArmPlatformPrePeiCore.debug,可以得到0x3458是ArmPlatformPrePeiCore的_ModuleEntryPoint
在这里插入图片描述
然后我们查看QEMU_EFI.fd的0x4458的地址存放的数据,就是对应_ModuleEntryPoint的第一条指令。我们知道ArmPlatformPrePeiCore是从0x1000存放的,因此实际上0x4458就是ArmPlatformPrePeiCore的_ModuleEntryPoint。而ArmPlatformPrePeiCore编译出来的代码是位置无关代码,所以通过0地址和0x1000地址的两次跳转,最终就跳转到ArmPlatformPrePeiCore的_ModuleEntryPoint中。
在这里插入图片描述

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阶段的执行了

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值