ATF(Arm Trusted Firmware)/TF-A Chapter 05 BL2

写在最前

二级boot的实现方案有很多,比如bl2,u-boot、uboot-spl, uefi。

bl2的主要职责就是将后续固件如u-boot。kernel,SCP或者其他异构核的固件,加载到ram中。

对于一个ARMv8架构的Core,比如A35,A53,A7x来说,大部分SoC设计厂商会使用ARM推荐的启动方式:ARMv8 AARCH64,Single Core Boot。

这样,Cold boot是,EL3,ARMv8 Aarch64,加载bl2,可以选择的组合有:

  • aarch32,EL1(PL0,non-secure),这种方式跳转到BL31需要smc 返回到Boot ROM(BL1)
  • aarch32,EL3(PL1,secure), 这种方式跳转到BL32可以直接通过跳转指令进入BL32
  • aarch64,EL1,这种方式跳转到BL31需要smc 返回到Boot ROM(BL1)
  • aarch64,EL3,这种方式跳转到BL32可以直接通过跳转指令进入BL31

每种bl2启动方式都有自己的特点,我们在讲完整个启动阶段(即启动kernel为止的启动阶段)后在分析。

 

5.1. 正文开始

在前一阶段,主CPU核运行bl1代码。在Bl2上,同样只有主cpu核运行bl2代码(通过platform_is_primary_cpu()函数识别主cpu核)。Bl1将系统的控制权交给bl2(跳转到BL2_BASE)。Bl2调用plat_get_bl_image_load_info()从外部flash获取要加载的image list(通过plat_get_bl_image_load_info() )。加载完成后跳转到下一个image运行( plat_get_next_bl_params())

BL2的运行分为两种情况,回忆在build BL1时,编译选项BL2_AT_EL3指出是否BL2也运行在EL3上,在EL1和EL3上的entrypoint初始化代码会有区别。我们分别分析这两种情况。默认BL2_AT_EL3=0

bl2运行在哪个异常等级(EL),和整个系统的启动流程,boot阶段的设计密不可分,bootrom的功能也会影响到能否支持BL2_AT_EL3。

在低等级的异常上,不具备访问高等级异常的权限,假如设计的armv8 ARCH64的启动流程是:

bootrom --->  bl2  ---> bl31 -->u-boot -->klernel  

如果bl2不运行在EL3,那么进入bl31执行的唯一方式是smc,启动到bl2阶段,只有bootrom(BL1)在el3上执行过,且el3的VBAR只在bootrom(BL1),所以需要bootrom的smc handler支持execuate和auth。当然,这些ATF已经帮你做好了,加上自己的平台实现函数,编进去就可以。

如果固化的bootrom没有这个功能,就需要bl2在EL3上运行,以便启动bl31。

一般这种情况下,启动阶段对应的异常等级如下表

启动阶段异常等级安全说明
bl1EL3S默认从core支持的最高EL
bl2EL3S运行在EL3,方便直接跳转到bl31
bl31EL3S需要EL3,需要支持PSCI和smc 不同的client,core上下电,访问所有地址空间
bl32EL1Strust-os
bl33EL2NS在ns的el2上执行,把ns-el1腾出来给kernel,方便兼容64位和32位的kernel
kernelEL1NS 

 

 

5.2. Build bl2

Make PLAT=fvp COLD_BOOT_SINGLE_CPU=1 SPD=tspd bl2 CROSS_COMPILE=<path-to-aarch64-gcc>/bin/aarch64-linux-gnu-
  
  

Bl2入口地址:bl2_entrypoint 分别在bl2/aarch64/bl2_entrypoint.S和bl2/aarch64/bl2_el3_entrypoint.S)

Ld文件:bl2.ld.s和bl2_el3.ld.S

在编译bl2时,会在./build/<PLAT>/<DEBUG>bl2/目录下下生成最终的ld文件bl2*.ld。

 

5.3. BL2启动流程

5.3.1 arch初始化

对于AArch64:

  1. BL2执行normal world和后续阶段所需的最小架构初始化。
  2. 通过清零CPACR.FPEN位,使EL1和EL0可以访问SIMD寄存器。

5.3.2 platform初始化

对于ARM fvp,BL2 执行下列的初始化步骤:

  1. 初始化console(PL101).(尽管在bl1时已经初始化过一次)
  2. 初始化和配置储存设备驱动,用于加载后续的bl。
  3. 使能MMU ,map the memory,访问权限. (尽管在bl1时已经初始化过一次)
  4. 平台安全设置,相关组件(寄存器,外设,地址等)的访问控制
  5. 为BL3阶段的image保留内存空间。
  6. 为BL3阶段的image定义可用内存地址范围。
  7. 如果BL1使用 TB_FW_CONFIG dynamic configuration file(保存在arg0) , 解析配置参数

5.3. image load

BL2通过查找image list的方式加载image,并且将这个list传递给下一个bl 镜像。(bl1也是这么做的)。

平台实现方法提供的可加载image list还可以包含动态配置文件。这个配置文件可以根据需要在bl2_plat_handle_post_image_load()函数中进行解析。 通过更新此函数中的相应ep信息,可以将这些配置文件作为参数传递给下一个Boot Loader阶段。

5.3.4 SCP_BL2 (System Control Processor Firmware) image load

某些系统具有单独的系统控制处理器(SCP),用于电源,时钟,复位和系统控制。 BL2将可选的SCP_BL2镜像从平台存储设备加载到特定的安全内存区域。 SCP_BL2的后续处理是特定于具体平台的,需要自行实现。 例如,Arm Juno ,BL2先把SCP_BL2加载到trust sram,再使用Boot Over MHU (BOM) 协议,把SCP_BL2加载到 SCP的内部RAM之后,SCP运行SCP_BL2,并给AP发出signals,通知BL2继续执行。

5.3.5 Load EL3 software

BL2从平台存储设备加载EL3 runtime software 到trusted SRAM.如果内存空间不够或者镜像不存在去,则assert停止运行

5.3.6 AArch64 BL32 (Secure-EL1 Payload) image load

BL2将可选的BL32镜像从平台存储设备加载到特定于平台的安全存储区域。BL32镜像在安全世界中执行。BL2依靠BL31将控制权限传递给BL32(如果存在)。 因此,BL2也会使用BL32镜像的entrypoint。 用于进入BL32的Saved Processor Status Register(SPSR)的值不是由BL2确定的,它由BL31内的Secure-EL1 Payload Dispatcher(SPD)初始化,SPD负责管理与BL32的交互。此信息将传递给BL31。

5.3.7 BL33 (Non-trusted Firmware) image load

BL2将BL33镜像(e.g. UEFI or other test or boot software)从平台存储设备加载到由平台定义的非安全内存中。

一旦安全状态初始化完成,BL2依靠EL3 Runtime Software将控制权传递给BL33。 因此,BL2使用正常世界的镜像入口和保存程序状态寄存器(SPSR)填充平台指定的存储区域。entrypoint是BL33镜像的加载地址。 SPSR按照PSCI PDD中的规定确定(PSCI 5.13节)。 此信息将传递给EL3runtime software。

5.3.8 AArch64 BL31 (EL3 Runtime Software) execution

BL2执行继续如下:

  1. BL2通过产生SMC异常将控制权传递回BL1,并给BL1提供BL31入口点。 SMC异常由BL1 阶段 install的 SMC exception handler来处理。
  2. BL1关闭MMU并刷Cache。清SCTLR_EL3.M / I / C位,将D-cache刷新到point of coherency并使TLB无效。
  3. BL1在EL3的指定入口地址将控制权传递给BL31。
    1.  Bl2 at EL3 说明

一些平台的BOOT ROM是non-TF-A的,并且下一阶段的boot直接运行在EL3上。这些bl2atEL1的平台上,TF-A BL1浪费了内存资源,因为这样做的唯一目的是确保在S-EL1上运行TF-A BL2。为了避免这种浪费,ARM提供一个特殊模式使BL2能够在EL3上执行,允许非TF-A Boot ROM加载并直接跳转到BL2。使用BL2_AT_EL3=1使能该模式。这种模式的主要区别是:

  1. BL2将包含reset代码和mailbox机制,以区分cold boot和warm boot。BL2在EL3上运行,进行EL3所需的arch初始化(和在bl1 arch初始化部分工作重复)。
  2. BL2不再接收来自BL1的meminfo信息。该信息可以通过Boot ROM传递,也可以在BL2镜像内部传递。
  3. 由于BL2在EL3上执行,BL2直接跳转到下一个镜像,而不是调用RUN_IMAGE 的SMC调用。

我们假设平台支持3种不同类型的BootROM:

  1. Boot ROM总是跳转到相同的复位地址上运行,这种情况下,对于cold boot和warm boot,我们需要保留BL2常驻部分的代码在内存中,这段内存不能被其他镜像回收。可以通过链接脚本(bl2_el3.ld.S)的symbols __TEXT_RESIDENT_START__ 和 __TEXT_RESIDENT_END__ 来配置这段内存(主要是bl2_el3_entrypoint.o和.text.asm.的代码。
  2. 平台存在可识别BootROM跳转地址的机制。平台代码可以在cold boot时使用psci_warmboot_entrypoint对跳转地址进行编程。
  3. 平台存在PROGRAMMABLE_RESET_ADDRESS功能对复位地址进行编程的机制。平台代码可以在cold boot期间使用psci_warmboot_entrypoint对复位地址进行编程,绕过Boot ROM进行warm boot。

在最后两种情况下,BL2的任何代码都不需要驻留在内存中。在前两种情况下,我们希望Boot ROM能够区分Cold boot和Warm boot,以避免在warm boot期间再次加载BL2。

FVP平台可以直接测试此功能,将镜像直接加载到内存中,并更改系统reset时候的跳转地址。例如:

-C cluster0.cpu0.RVBAR=0x4022000 –data cluster0.cpu0=bl2.bin@0x4022000
  
  

通过这种配置,FVP就像前面描述第一种情况,其中Boot ROM总是跳转到同一地址。 为简化起见,在这种情况下,BL32加载到DRAM中,以避免其他镜像回收BL2内存。

5.4 代码目录

 

bl2/aarch32和bl2/aarch64。

BL2 at EL1使用的链接脚本是bl2.ld.S

BL2 at EL3使用的链接脚本是bl2_el3.ld.S

5.5 Bl2 at EL1

5.5.1 bl2.ld.s

代码很简单:

 

15行,BL2_BASE,BL2_LIMIT都在arm-def.h中定义。注意,在BL1中使用了BL2_BASE,BL2_LIMIT,所以修改此值后,所有的bl 都需要重新编译。

22行,PAGE_SIZE在include/lib/xlat_tables/xlat_tables_defs.h中定义

入口地址:bl2_entrypoint

 

给.image_pasrser_lib_desc保留run-able地址

85行定位RW起始地址

Stack。

 BSS段

Xlat_table库代码。Bl1.ld.s也是如此

5.5.2 Bl2_entrypoint

EL1上的bl2相对简单,bl2/aarch64/bl2_entrypoint.S:

首先保存bl1 用到的x0~x3寄存器,说明今后bl1还需要运行。

5.5.2.1 异常初始化

31行,设置S-EL1 vector base address :early_exceptions。

在common/aarch64/early_exceptions.S中,定义各种异常的入口函数,调用平台实现函数plat_xxx处理异常:

early_exceptions不会处理任何异常,都会panic

清D-cache

bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 35~65行),使能SError异常;D-Cache,stack pointer and data access alignment checks;使bl2使用ram空间的d-cache无效。

inv_dcache_range 在\lib\aarch64\cache_helpers.S,line 12,bl1中介绍过,这里略

初始化BSS和COHERENT_MEM

bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 73~87行),初始化bss段和COHERENT_MEM(目前没用到)。

建立C stack

bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S,97行),跳转plat_set_my_stack执行:分配栈,当启用MMU时,其内存将标记为Normal-IS-WBWA。启用MMU后没有读取过时栈内存的风险,因为此时只有主CPU核正在运行。

Plat_set_my_stack,平台实现函数:porting guide对其描述如下:

设置当前栈指针,位于normal memory stack ,分配给当前CPU。对于bl只配分一个stack给主CPU(BL2只有主核在跑),使用UP版本代码(另一个版本是MP)栈大小由PLATFORM_STACK_SIZE指定。

PLATFORM_STACK_SIZE在plat/arm/board/fvp/include/platform_def.h中定义:

支持trust boot情况下,验签需要递归实现,所以栈大一点。分别是4K和1K

通用的实现(weak函数)plat/common/aarch64/platform_up_stack.S(UP) 和plat/common/aarch64/platform_mp_stack.S(MP)

我们看UP:

两个宏,第一个get_up_stack(include/commonaarch64/asm_macro.S,120行):

第二个platform_normal_stacks:

Declare_stack 在(include/commonaarch64/asm_macro_common.S,92行):

将栈放到.section tzfw_normal_stacks段(对应到bl2.ld.s ,99行)

针对Stack的安全措施

回到bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 104~106行):

尽管默认STACK_PROTECTOR_ENABLED=0,但我们有必要分析如何保护stack的安全,有一部分攻击是通过 stack overflow的。关于stack protect,可以参考:wiki 和附录A

在\lib\stack_protector\aarch64\asm_stack_protector.S :

先看porting guide:

使用ENABLE_STACK_PROTECTOR=1启用堆栈保护。此函数返回一个随机值,这种方法称为Random canaries。 由于可预测的值会削弱保护作用,因为攻击者可以在大多数时间轻松地将正确的值写入攻击的一部分。 因此,它应该返回一个真正的随机数。

注意:为使保护有效,需要将全局数据放在比堆栈基数低的地址。 如果不这样做,攻击者就会覆盖canary,作为stack buffer overflow攻击的一部分。

Fvp平台没有random number generator,使用cntpct^ 固定值充当随机数

具体攻击方法和原理见附录A

Bl2_early_platform_setup2

回到bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 108~117行)

在bl2/aarch64/bl2_entrypoint.S, 108~117行21~25行,将x0~x3先保存在了20~23中,此时恢复出来

在porting guide中:

在MMU和d-cache关闭的情况下运行

只有主核运行这个函数,

4个参数是平台指定的由bl1传递给bl2的。

ARM 平台上,这些参数是:

arg0 - 如果存在HW_CONFIG ,则arg0为HW_CONFIG的地址

arg1 - 在bl1中设置的meminfo 结构体.

函数功能:

初始化UART(PL011,一个arm的ip) console

初始化storage abstraction layer为读取bootloader images做准备。注意,SCP_BL2 加载之后(如果存在的话),就要立即执行bl2_platform_setup .

bl2_early_platform_setup2在plat/arm/board/fvp/fvp_bl2_setup.c16行(和bl1类似,arm_bl2_setp.c也存在一个默认的弱实现plat/arm/common/arm_bl2_setup.c):

四个参数,只用前两个。回忆bl1中的Cold boot期间的动态配置,

Arg0用来保存tb_fw_config,arg1用来保存mem_layout

 bl2_early_platform_setup2首先调用arm_bl2_early_platform_setup(plat/arm/common/arm_bl2_setup.c,51行):

对比bl1中的arm_bl1_eary_platform_setup,bl2不需要重新初始化WDOG。执行了在bl1中相同的console初始化arm_console_boot_init。bl2_tzram_layout是一个meminfo_t结构体

plat_arm_io_setup则是调用io_dev_xxx 初始化相关的io。这在bl1的bl1_main->bl1_platform_setup->arm_bl1_platform_setup调用的代码相同。只是在bl2中的plat_arm_io_setup执行位置比较靠前。

arm_bl2_set_tb_cfg_addr则是给void* 的tb_fw_cfg_dtb直接赋值:

fvp_config_setup(plat/arm/board/fvp/fvp_common.c,245行),初始化平台配置供将来使用。在bl1阶段,bl1_early_platform_setup也执行过同样的代码。

具体代码TBD。

再看看其他平台在bl2_early_platform_setup2做了什么:

\plat\hisilicon\poplar\bl2_plat_setup.c

初始化console和emmc,timer

\plat\marvell\common\bl2_plat_setup.c

bl2_plat_arch_setup

回到bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 119行):调用bl2_plat_arch_setup

在plat/arm/common/arm_bl2_setup.c,139行

在porting guide中:

写的很清楚,执行非常早期的平台特定架构的初始化。初始化mmu,映射BL2 RO。105行:

注意

他也是个weak函数,但fvp平台并没有实现平台代码。

再看看其他平台做了什么:

\plat\hisilicon\poplar\bl2_plat_setup.c:

\plat\marvell\common\bl2_plat_setup.c

\plat\marvell\common\bl2_plat_setup.c

都是以平台实现的特定方式初始化mmu。

 

bl2_plat_arch_setup,与bl1中的bl1_plat_setup类似。在bl1_entrypoint执行44行的bl1_plat_arch_setup(\plat\arm\common\arm_bl1_setup.c)调用arm_bl1_plat_arch_setup,同样配置bl1 ram的mmu。ARM在各BL阶段的初始化流程和代码调用基本一致。

 

那么,下面应该是执行bl2_main了。在此之前,bl2_plat_arch_setup的136行调用的arm_setup_romlib,后面章节会详细说明。

(To be continued)

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值