1. AARCH64编译环境搭建
git clone https:
git clone https:
git clone https:
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,可以看到如下图
- ArmPlatformPrePeiCore,充当的SEC core的模块。从0x1000存放
- PeiCore,PEI Core的代码
- 7个PEIM
PEIM | 功能 |
---|
PlatformPei | 初始化SOC平台相关的代码 | MemoryInit | 初始化内存 | CpuPei | 初始化ARM cortex a57 cluster | PcdPeim | 提供动态Pcd | PeiVariablePei | 没用到 | DxeIplPei | 提供加载DXE的功能 | - 最后是一个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)
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))
cmp x0, #1
b.eq _SetupPrimaryCoreStack
_SetupPrimaryCoreStackz中主要配置了一下栈寄存器SP,然后跳转到_PrepareArguments
_SetupPrimaryCoreStack:
mov sp, x1
...
b _PrepareArguments
_PrepareArguments从PCD里拿到PEI CORE的entry,然后传给CEntryPoint,后面就是C代码了
_PrepareArguments:
MOV64 (x2, FixedPcdGet64(PcdFvBaseAddress))
ldr x1, [x2, #8]
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
)
{
ArmDisableDataCache ();
ArmInvalidateInstructionCache ();
ArmEnableInstructionCache ();
InvalidateDataCacheRange (
(VOID *)(UINTN)PcdGet64 (PcdCPUCoresStackBase),
PcdGet32 (PcdCPUCorePrimaryStackSize)
);
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
)
{
...
CreatePpiList (&PpiListSize, &PpiList);
ArmGicEnableDistributor (PcdGet64 (PcdGicDistributorBase));
...
TemporaryRamBase = (UINTN)PcdGet64 (PcdCPUCoresStackBase) + PpiListSize;
TemporaryRamSize = (UINTN)PcdGet32 (PcdCPUCorePrimaryStackSize) - PpiListSize;
SecCoreData.DataSize = sizeof (EFI_SEC_PEI_HAND_OFF);
SecCoreData.BootFirmwareVolumeBase = (VOID *)(UINTN)PcdGet64 (PcdFvBaseAddress);
SecCoreData.BootFirmwareVolumeSize = PcdGet32 (PcdFvSize);
SecCoreData.TemporaryRamBase = (VOID *)TemporaryRamBase;
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;
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阶段的执行了
|