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
大部分教程都是用OVMF来做示例,OVMF中第一个运行的UEFI模块是SEC。但AARCH64中的SEC是这个ArmPlatformPriPeiCore。所以在edk2的AARCH64示例中,ArmPlatformPriPeiCore是第一个运行的模块。
我们用UEFITool NE 打开QEMU_EFI.fd,可以看到如下图
PEIM | 功能 |
---|---|
PlatformPei | 初始化SOC平台相关的代码 |
MemoryInit | 初始化内存 |
CpuPei | 初始化ARM cortex a57 cluster |
PcdPeim | 提供动态Pcd |
PeiVariablePei | 没用到 |
DxeIplPei | 提供加载DXE的功能 |
从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中。
ArmPlatformPrePeiCore非常简单,主要初始化CPU,设置栈指针, 初始化PEI阶段需要的参数SecCoreData最后跳转到PEI core中去。
函数调用栈如下:
_ModuleEntryPoint
_SetupPrimaryCoreStack
_PrepareArguments
CEntryPoint
PrimaryMain
(PeiCoreEntryPoint)(&SecCoreData, PpiList);
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)
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);
}
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阶段的执行了