马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
一、ATF 源码下载链接
1. ARM Trusted Firmware (ATF) 官方 GitHub 仓库
- GitHub 地址: https://github.com/ARM-software/arm-trusted-firmware
- 这是 ATF 的官方源码仓库,包罗最新的代码、文档和示例。
下载方式:
- 使用 Git 克隆仓库:
- git clone https://github.com/ARM-software/arm-trusted-firmware.git
复制代码 - 大概直接下载 ZIP 文件:在 GitHub 页面上点击 "Code" 按钮,然后选择 "Download ZIP"。
2. ATF 官方文档
- 文档地址: Trusted Firmware-A Documentation — Trusted Firmware-A 2.12.0 documentation
- 这里提供了具体的 ATF 使用指南、开发文档和 API 参考。
3. ATF 支持的平台
ATF 支持多种 ARM 平台,包罗:
- ARMv8-A 架构的处理器(如 Cortex-A53, Cortex-A57, Cortex-A72 等)。
- 特定的 SoC 平台(如 NXP i.MX8, TI AM65x, Raspberry Pi 4 等)。
在 ATF 源码中,plat/ 目次下包罗了针对不同平台的实现。
4. ATF 的主要功能
- 安全启动:实现从 BL1(Boot Loader Stage 1)到 BL31(Runtime Firmware)的启动流程。
- 可信实行环境(TEE):与 OP-TEE 等 TEE 实现集成。
- 电源管理:支持 PSCI(Power State Coordination Interface)。
- 异常处理:实现 ARMv8-A 的异常级别(EL3, EL2, EL1)管理。
5. ATF 的编译和使用
ATF 的编译通常需要交叉编译工具链(如 aarch64-none-elf-gcc)。以下是一个简朴的编译示例:
安装依赖:
- sudo apt-get install build-essential gcc-aarch64-linux-gnu
复制代码 编译 ATF:
- make CROSS_COMPILE=aarch64-none-elf- PLAT=<platform> all
复制代码
- 将 <platform> 更换为具体的平台名称(如 fvp、qemu、rpi4 等)。
天生镜像:
编译完成后,天生的镜像文件(如 bl1.bin、fip.bin)会位于 build/<platform>/release/ 目次下。
二、BL1 启动流程
BL1是系统启动的第一阶段,其主要目的是初始化系统环境和启动第二阶段镜像BL2。借用网友的图,总体流程:仅供参考
展示一下,现实代码中的 el3_entrypoint_common 函数:
其入口函数为bl1_entrypoint,它是由bl1/ bl1.ld.S通过ENTRY标号定义的:
- OUTPUT_FORMAT(PLATFORM_LINKER_FORMAT)
- OUTPUT_ARCH(PLATFORM_LINKER_ARCH)
- ENTRY(bl1_entrypoint)
复制代码 它先初始化EL3环境,实行平台相关的初始化流程,然后加载下一阶段镜像、为其预备符合的参数,末了跳转到下一阶段镜像入口处运行。由于bl1的流程比较简朴,接下来我们直接进入各个环节的具体实现吧。
(1)el3_entrypoint_common
该函数是所有在EL3下实行镜像共享的,如BL1和BL31都会通过该函数初始化系统状态。该函数主要初始化系统的初始状态,实行一些必要的fixup操作,以及初始化c运行时环境和设置运行时的栈指针,为后续代码跳转到c语言实行预备条件
(2)bl_setup
该函数主要实行一些平台相关的操作,如对于qemu平台会实行串口初始化、内存结构配置、MMU设置和data cache使能操作
(3)bl_main
该函数主要用于bl2镜像加载以及跳转前的预备流程,如获取镜像参数、加载镜像内容、安全启动验签、bl2镜像跳转预备以及world switch上下文初始化等
(4)el3_exit
该流程实行现实的上下文切换流程,包罗保存当前EL3上下文以及跳转到bl2入口地址实行等。接下来我们将对以上流程举行更加具体的分析
除了由硬件提供默认值的寄存器外,其它寄存器的值都处于不确定状态,因此在启动流程的初始阶段必须要先初始化这些寄存器,以将系统带到一个确定的运行状态。接下来需要设置cpu的异常处理程序和c运行时环境,为代码跳转到c语言做预备。末了,则需要加载BL2镜像,预备下一阶段启动所需的参数和跳转设置,并最终跳转到BL2的入口函数中实行。
三、el3_entrypoint_common流程分析
该宏由所有需要在EL3下实行的镜像共享,如BL1和BL31都会入口处调用该函数,只是传入的参数有所区别。其主要完成的功能如下:
(1)初始化sctlr_el3寄存器,以初始化系统控制参数
(2)判断当前启动方式是冷启动照旧热启动,并实行相应的处理
(3)pie相关的处理
(4)设置异常向量表
(5)特定cpu相关的reset处理
(6)架构相关el3的初始化
(7)冷启动时secondary cpu的处理
(8)c运行环境初始化
(9)初始化运行栈
3.1 sctlr_el3初始化
其代码流程如下:
- .if \_init_sctlr
- mov_imm x0, (SCTLR_RESET_VAL & ~(SCTLR_EE_BIT | SCTLR_WXN_BIT \
- | SCTLR_SA_BIT | SCTLR_A_BIT | SCTLR_DSSBS_BIT))
- msr sctlr_el3, x0
- isb
- .endif
复制代码 (1)sctlr_el3是EL3异常等级的控制寄存器,它控制了一些系统的重要行为,因此必须要在起始阶段就将其初始化到确定的状态
(2)这里主要设置了系统巨细端(SCTLR_EE_BIT)、禁用了对齐错误(SCTLR_A_BIT)和栈对齐错误(SCTLR_SA_BIT)检查,以及禁止可写内存的实行权限(SCTLR_WXN_BIT)等
而其它的值都沿用SCTLR_RESET_VAL的定义;
3.2 冷热启动处理
其代码如下:
- .if \_warm_boot_mailbox
- bl plat_get_my_entrypoint
- cbz x0, do_cold_boot
- br x0
- do_cold_boot:
- .endif
复制代码 冷启动和热启动的最大区别就是冷启动需要实行完整的系统初始化流程,而热启动由于在启动前保存了相关状态,因此可以跳过这些阶段,从而加速启动速度。因此,这段代码就很好理解了,它先通过plat_get_my_entrypoint从特定平台获取热启动地址,若地址获取乐成,则直接跳转到该地址实行热启动流程。若地址获取失败,该函数会返回0,此时表明本次启动是冷启动,因此急需实行冷启动流程;
3.3 pie处理
我们知道代码实行过程中大概需要跳转到某个位置,大概操作某个地址的数据,而在二进制代码中这些位置都需要通过地址来表示。因此,对于平凡程序我们需要将其加载到与链接地址类似的位置实行,否则这些寻址操作就会失败。pie(地址无关可实行文件)就是为相识决该题目的,它的基本思路如下:
(1)程序中的函数调用和数据读写,若其可以转换为相对寻址的,则将其用相对寻址方式更换绝对地址。如armv8的adr指令,通过pc + offset的方式寻址,即以pc值为基地址,以offset为偏移量,从而计算得到新的地址。固然,这种寻址方式有一定的限制,如跳转范围有限等
(2)若该地址不能转换为相对寻址,则将其放到一个独立的段global descriptor table(GDT)中,并在镜像启动时通过现实加载地址调解这些地址值
因此,pie的实现需要编译和加载的共同配合完成,在构建时添加如下编译选项:
(1)编译时添加选项-fpie
(2)链接时添加选项-pie
在加载时需要对GDT表中的内容举行调解,这部分代码即是用于这一目的,其代码如下:
- pie_fixup:
- ldr x0, =pie_fixup
- and x0, x0, #~(PAGE_SIZE_MASK)
- mov_imm x1, \_pie_fixup_size
- add x1, x1, x0
- bl fixup_gdt_reloc
复制代码 具体的重定位流程位于fixup_gdt_reloc函数中,各位可以自行分析:
路径:lib/aarch64/misc_helpers.S +499
3.4 设置异常向量表
这部分代码比较简朴,就是将bl1的异常向量表设置到el3的向量表基地址寄存器中,其代码如下:
- adr x0, \_exception_vectors
- msr vbar_el3, x0
复制代码 bl1异常向量表的定义位于bl1/aarch64/bl1_exceptions.S,从该异常向量表的定义我们可看到bl1只支持SMC异常的处理,其它的异常都是不合法的:func smc_handler64
3.5 reset_handler
该函数用于实行特定cpu相关的reset处理函数,这些处理函数在定义时会被放到一个特别的段中,在实行reset_handler函数时就从该段中查找操作函数的函数指针,并实行相应的回调函数。以cortex-a53 为例,其cpu ops的定义流程如下:
- lib/cpus/aarch64/cortex_a53.S:
- declare_cpu_ops cortex_a53, CORTEX_A53_MIDR, \
- cortex_a53_reset_func, \
- cortex_a53_core_pwr_dwn, \
- cortex_a53_cluster_pwr_dwn
- include/lib/cpus/aarch64/cpu_macros.S:
- .macro declare_cpu_ops _name:req, _midr:req, _resetfunc:req, \
- _power_down_ops:vararg
- declare_cpu_ops_base \_name, \_midr, \_resetfunc, 0, 0, 0, \
- \_power_down_ops
- .endm
- include/lib/cpus/aarch64/cpu_macros.S:
- .macro declare_cpu_ops_base _name:req, _midr:req, _resetfunc:req, \
- _extra1:req, _extra2:req, _e_handler:req, _power_down_ops:vararg
- .section cpu_ops, "a"
- .align 3
- .type cpu_ops_\_name, %object
- .quad \_midr
- #if defined(IMAGE_AT_EL3)
- .quad \_resetfunc
- #endif
- .quad \_extra1
- .quad \_extra2
- .quad \_e_handler
- …
- .endm
复制代码 此中cpu_ops段的地址定义在链接脚本头文件include/common/bl_common.ld.h中,即其位于__CPU_OPS_START__到__CPU_OPS_END__之间。
- #define CPU_OPS \
- . = ALIGN(STRUCT_ALIGN); \
- __CPU_OPS_START__ = .; \
- KEEP(*(cpu_ops)) \
- __CPU_OPS_END__ = .;
复制代码 reset_handler的流程比较简朴,就是查找__CPU_OPS_START__到__CPU_OPS_END__之间的cpu_ops结构体,并调用其reset_func回调函数,具体流程不再赘述。对于cortex-a53 平台,其reset函数定义如下,该流程主要是实行一些cpu相关的errata操作,以及使能SMP位(lib/cpus/aarch64/cortex_a53.S)。
- func cortex_a53_reset_func
- mov x19, x30
- bl cpu_get_rev_var
- mov x18, x0
- #if ERRATA_A53_826319
- mov x0, x18
- bl errata_a53_826319_wa
- #endif
- #if ERRATA_A53_836870
- mov x0, x18
- bl a53_disable_non_temporal_hint
- #endif
- #if ERRATA_A53_855873
- mov x0, x18
- bl errata_a53_855873_wa
- #endif
- mrs x0, CORTEX_A53_ECTLR_EL1
- orr x0, x0, #CORTEX_A53_ECTLR_SMP_BIT
- msr CORTEX_A53_ECTLR_EL1, x0
- isb
- ret x19
- endfunc cortex_a53_reset_func
复制代码 3.6 架构相关el3的初始化
该流程主要实行一些系统寄存器相关的配置,以设置系统的状态。其aarch64架构流程如下(include/arch/aarch64/el3_common_macro.S):
- .macro el3_arch_init_common
- mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT) (1)
- mrs x0, sctlr_el3
- orr x0, x0, x1
- msr sctlr_el3, x0
- isb
- #ifdef IMAGE_BL31
- bl init_cpu_data_ptr
- #endif /* IMAGE_BL31 */
- mov_imm x0, ((SCR_RESET_VAL | SCR_EA_BIT | SCR_SIF_BIT) \
- & ~(SCR_TWE_BIT | SCR_TWI_BIT | SCR_SMD_BIT)) (2)
- #if CTX_INCLUDE_PAUTH_REGS
- orr x0, x0, #(SCR_API_BIT | SCR_APK_BIT) (3)
- #endif
- msr scr_el3, x0
- mov_imm x0, ((MDCR_EL3_RESET_VAL | MDCR_SDD_BIT | \
- MDCR_SPD32(MDCR_SPD32_DISABLE) | MDCR_SCCD_BIT | \
- MDCR_MCCD_BIT) & ~(MDCR_SPME_BIT | MDCR_TDOSA_BIT | \
- MDCR_TDA_BIT | MDCR_TPM_BIT)) (4)
- msr mdcr_el3, x0
- mov_imm x0, ((PMCR_EL0_RESET_VAL | PMCR_EL0_LP_BIT | \
- PMCR_EL0_LC_BIT | PMCR_EL0_DP_BIT) & \
- ~(PMCR_EL0_X_BIT | PMCR_EL0_D_BIT)) (5)
- msr pmcr_el0, x0
- msr daifclr, #DAIF_ABT_BIT (6)
- mov_imm x0, (CPTR_EL3_RESET_VAL & ~(TCPAC_BIT | TTA_BIT | TFP_BIT)) (7)
- msr cptr_el3, x0
- mrs x0, id_aa64pfr0_el1
- ubfx x0, x0, #ID_AA64PFR0_DIT_SHIFT, #ID_AA64PFR0_DIT_LENGTH (8)
- cmp x0, #ID_AA64PFR0_DIT_SUPPORTED
- bne 1f
- mov x0, #DIT_BIT
- msr DIT, x0
- 1:
- .endm
复制代码 (1)使能指令cache、对齐错误和栈对齐错误检查
(2)secure寄存器相关设置,主要用于设置某些操作是否路由到EL3实行,如设置SCR_EA_BIT会将所有异常等级下的external abort和serror异常路由到EL3处理,扫除SCR_TWE_BIT则不会使得低于EL3等级的WFE指令不会路由到EL3处理。其它位的含义基本类似,具体定义可检察armv8 spec
(3)它用于使能指针署名特性PAC。由于armv8虚拟地址没有完全使用,如对于48位虚拟地址,其高16位是空闲的,完全可以用于存储一些其它信息。因此arm支持了指针署名技能,它通过密钥和署名算法对指针举行署名,并将截断后的署名保存到虚拟地址的高位,在使用该指针时则对高署名举行验证,以确保其没有被篡改。它主要是用来保护栈中数据的安全性,防御ROP/JOP攻击。
(4)mdcr_el3寄存器用于设置debug和performance monitor相关的功能
(5)用于设置performance monitor配置,如一些性能变乱计数器的行为
(6)用于使能serror异常,今后bl1将能接收serror异常,并处理smc调用
(7)设置一些特定变乱是否要陷入EL3
(8)设置DIT特性,若使能了DIT,则DIT相关的指令实行时间与数据不相关。由于侧信道攻击可以利用某些敏感指令(如加解密指令)实行时间、功耗等的不同,来推测出数据内容,因此猜测该功能是用于防止侧信道攻击的
3.7 secondary cpu的处理
由于启动代码不支持并发,因此在smp系统中只有一个cpu(primary cpu)实行启动流程,而其它cpu(secondary cpu)需要将自身设置为一个安全的状态,待primary cpu启动完成后再通过spintable或psci等方式来启动它们。其流程如下:
- bl plat_is_my_cpu_primary (1)
- cbnz w0, do_primary_cold_boot (2)
- bl plat_secondary_cold_boot_setup (3)
- bl el3_panic
- do_primary_cold_boot:
复制代码 (1)当前cpu是否为primary cpu
(2)若其为primary cpu,继续实行cold boot流程
(3)若其为secondary cpu,实行平台定义的secondary cpu启动设置函数
这个和uboot 启动CPU和kernel 启动CPU 机制一样,先让primary cpu启动,初始化一定后,设置secondary cpu 的启动函数指针。
3.8 内存初始化
该函数实行平台相关的内存初始化函数platform_mem_init.
3.9 c运行环境初始化
c语言运行需要依赖于bss段和栈,因此在跳转到c函数之前需要下设置它们。而且由于bl1的镜像一般被烧写在rom中,因此需要将其可写数据段从rom重定位到ram中。以下为其主要代码实现:
- adrp x0, __RW_START__
- add x0, x0, :lo12:__RW_START__ (1)
- adrp x1, __RW_END__
- add x1, x1, :lo12:__RW_END__
- sub x1, x1, x0 (2)
- bl inv_dcache_range (3)
- …
- adrp x0, __BSS_START__
- add x0, x0, :lo12:__BSS_START__
- adrp x1, __BSS_END__
- add x1, x1, :lo12:__BSS_END__
- sub x1, x1, x0
- bl zeromem (4)
- …
- #if defined(IMAGE_BL1) || (defined(IMAGE_BL2) && BL2_AT_EL3 && BL2_IN_XIP_MEM)
- adrp x0, __DATA_RAM_START__
- add x0, x0, :lo12:__DATA_RAM_START__
- adrp x1, __DATA_ROM_START__
- add x1, x1, :lo12:__DATA_ROM_START__
- adrp x2, __DATA_RAM_END__
- add x2, x2, :lo12:__DATA_RAM_END__
- sub x2, x2, x0
- bl memcpy16 (5)
- #endif
复制代码 (1)计算数据段的起始地址,由于adrp指令加载的地址值会将低bit mask掉,使其4k对齐。因此需要加上其低12位的数据,以规复其原始值
(2)计算该段地址的长度
(3)失效这段sram内存的dcache
(4)获取bss段的起止地址,并计算其长度,然后清零该段内存的数据
(5)获取bl1可读写数据段在rom中的地址,以及其将要被重定位的ram地址,计算数据长度,并实行重定位操作
3.10 运行栈设置
C语言的函数调用返回地址,上层栈指针地址,局部变量以及参数通报都大概需要用到栈。本函数通过设置运行时栈指针为跳转到c语言实行做末了的预备,其代码如下:
- msr spsel, #0 (1)
- bl plat_set_my_stack (2)
- #if STACK_PROTECTOR_ENABLED
- .if \_init_c_runtime
- bl update_stack_protector_canary (3)
- .endif
- #endif
复制代码 (1)使用sp_el0作为栈指针寄存器
(2)设置运行时栈,该函数会获取一个定义好的栈指针,并将其设置到当前栈指针寄存器sp中
(3)在栈顶设置一个canary值,用于检测栈溢出
四、bl_setup流程分析
4.1 bl1_early_platform_setup函数
以qemu平台的实现为例,其代码如下:plat/qemu/common/qemu_bl1_setup.c
- void bl1_early_platform_setup(void)
- {
- qemu_console_init(); (1)
- bl1_tzram_layout.total_base = BL_RAM_BASE; (2)
- bl1_tzram_layout.total_size = BL_RAM_SIZE;
- }
复制代码 (1)控制台初始化
(2)设置secure sram内存的地址范围
4.2 bl1_plat_arch_setup函数
以qemu平台为例,其代码如下:
- void bl1_plat_arch_setup(void)
- {
- QEMU_CONFIGURE_BL1_MMU(bl1_tzram_layout.total_base,
- bl1_tzram_layout.total_size,
- BL_CODE_BASE, BL1_CODE_END,
- BL1_RO_DATA_BASE, BL1_RO_DATA_END,
- BL_COHERENT_RAM_BASE, BL_COHERENT_RAM_END);
- }
复制代码 该函数用于为所有bl1需要访问的地址建立MMU页表,并且使能dcache。bl1中物理地址和虚拟地址映射的地址值是相等的,之所以要开启MMU主要是为了开启dcache,以加速后面BL2镜像加载的速度
五、bl_main流程分析
5.1 bl1的架构设置
bl1的aarch64架构设置函数如下:
- void bl1_arch_setup(void)
- {
- write_scr_el3(read_scr_el3() | SCR_RW_BIT);
- }
复制代码 该函数的作用为将下一个异常等级的实行状态设置为EL3;
5.2 secure boot初始化
secure boot用于校验镜像的合法性,它通常需要一个包罗镜像署名信息的镜像头。署名信息可在打包时完成,一般包罗计算镜像的hash值,然后使用非对称算法(如RSA或ECDSA)对该hash值实行署名操作,并将署名信息保存到镜像头中。在系统启动时,需要校验该署名是否合法,若不合法表明镜像被破坏或被更换了,因此系统需要停止启动流程。
auth_mod_init函数用于初始化署名验证所需的模块,其代码如下:
- void auth_mod_init(void)
- {
- assert(cot_desc_ptr != NULL);
- crypto_mod_init(); (1)
- img_parser_init(); (2)
- }
复制代码 (1)初始化署名验证所需的密码库
(2)初始化获取镜像署名信息模块
5.3 bl1的平台初始化
qemu平台的初始化实现接口如下:
- void bl1_platform_setup(void)
- {
- plat_qemu_io_setup();
- }
复制代码 该函数用于初始化qemu大概使用的镜像加载驱动初始化。
5.4 获取下一阶段镜像id
Bl1的下一阶段镜像通常为BL2,以下是通用的镜像id获取函数:
- unsigned int bl1_plat_get_next_image_id(void)
- {
- return BL2_IMAGE_ID;
- }
复制代码 5.5 bl2镜像加载
镜像加载流程包罗了镜像从storage中的加载以及镜像合法性验签两部分,由于secure boot计划在后面专门花一篇文章来介绍,因此这里只介绍镜像的加载流程。其代码主要流程如下:
- desc = bl1_plat_get_image_desc(BL2_IMAGE_ID); (1)
- info = &desc->image_info;
- err = bl1_plat_handle_pre_image_load(BL2_IMAGE_ID); (2)
- if (err != 0) {
- ERROR("Failure in pre image load handling of BL2 (%d)\n", err);
- plat_error_handler(err);
- }
- err = load_auth_image(BL2_IMAGE_ID, info); (3)
- if (err != 0) {
- ERROR("Failed to load BL2 firmware.\n");
- plat_error_handler(err);
- }
- err = bl1_plat_handle_post_image_load(BL2_IMAGE_ID); (4)
- if (err != 0) {
- ERROR("Failure in post image load handling of BL2 (%d)\n", err);
- plat_error_handler(err);
- }
复制代码 它主要包罗以下几部分内容:
(1)获取待加载镜像描述信息
在atf中,镜像描述信息主要包罗镜像id、镜像加载器使用的信息image_info和镜像跳转时使用的信息ep_info,其结构如下:
bl1_plat_get_image_desc用于获取bl2镜像的信息。
(2)加载之前的处理
它由平台函数bl1_plat_handle_pre_image_load处理,qemu平台未对其做任何处理
(3)从storage中加载镜像
它会根据先前获取到的bl2镜像描述信息,从storage中将镜像数据加载到给定地址上。qemu支持fip和semihosting类型的加载方式
(4)加载之后的处理
它主要用于设置bl1向bl2通报的参数,上面结构体中的args即用于该目的,它一共包罗8个参数,在bl1跳转到bl2之前会分别被设置到x0 – x7寄存器中。bl1只需通过x1寄存器向bl2传送其可用的secure内存region即可。以下为其代码主体流程:
- image_desc = bl1_plat_get_image_desc(BL2_IMAGE_ID);
- ep_info = &image_desc->ep_info; (a)
- bl1_secram_layout = bl1_plat_sec_mem_layout(); (b)
- bl2_secram_layout = (meminfo_t *) bl1_secram_layout->total_base;
- bl1_calc_bl2_mem_layout(bl1_secram_layout, bl2_secram_layout); (c)
- ep_info->args.arg1 = (uintptr_t)bl2_secram_layout;
复制代码 a 获取bl2的ep信息
b 获取bl1的secure内存region
c 将总的内存减去bl1已使用的sram内存,作为bl2的可用内存
d 将bl2的可用内存信息保存到参数通报信息中
5.6 下一阶段镜像启动预备流程
在atf中定义了一个异常等级切换相关的cpu context结构体,该结构体包罗了切换时所需的所有的信息,如gp寄存器的值,el1、el2系统寄存器以及el3状态的值等。由于armv8包罗secure和non secure两种安全状态,因此在el3中为这两种状态分别保留了一份独立的上下文信息,我们在实行上下文切换预备工作时,现实上就是填充对应security状态的结构体内容。以下是该结构体的定义:
- typedef struct cpu_context {
- gp_regs_t gpregs_ctx;
- el3_state_t el3state_ctx;
- el1_sysregs_t el1_sysregs_ctx;
- #if CTX_INCLUDE_EL2_REGS
- el2_sysregs_t el2_sysregs_ctx;
- #endif
- #if CTX_INCLUDE_FPREGS
- fp_regs_t fpregs_ctx;
- #endif
- cve_2018_3639_t cve_2018_3639_ctx;
- #if CTX_INCLUDE_PAUTH_REGS
- pauth_t pauth_ctx;
- #endif
- } cpu_context_t;
复制代码 bl1_prepare_next_image的主要工作就是初始化primary cpu的cpu_context上下文,并填充该结构体的相关信息,其主要流程如下:
- desc = bl1_plat_get_image_desc(image_id);
- next_bl_ep = &desc->ep_info; (1)
- security_state = GET_SECURITY_STATE(next_bl_ep->h.attr); (2)
- if (cm_get_context(security_state) == NULL)
- cm_set_context(&bl1_cpu_context[security_state], security_state); (3)
- if ((security_state != SECURE) && (el_implemented(2) != EL_IMPL_NONE)) { (4)
- mode = MODE_EL2;
- }
- next_bl_ep->spsr = (uint32_t)SPSR_64((uint64_t) mode,
- (uint64_t)MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS); (5)
- bl1_plat_set_ep_info(image_id, next_bl_ep);
- cm_init_my_context(next_bl_ep); (6)
- cm_prepare_el3_exit(security_state); (7)
- desc->state = IMAGE_STATE_EXECUTED;
复制代码 (1)获取bl2的ep信息
(2)从bl2的ep信息中获取其security状态
(3)若context内存未分配,则为其分配内存
(4)默认的下一阶段镜像异常等级为其支持的最高等级,即若支持el2,则下一异常等级为EL2
(5)计算spsr的值,即异常等级为step 4计算的值,栈指针使用sp_elx,关闭所有DAIF异常
(6)该函数为待切换异常等级初始化上下文,如scr_el3,scr_el3,pc,spsr以及参数通报寄存器x0 – x7的值
(7)将context中参数设置到现实的寄存器中
5.7 console_flush函数
在退出bl1之前将串口中的数据全部刷新掉。
六、el3_exit流程分析
路径:lib/el3_runtime/aarch64/context.S
该函数实行现实的异常等级切换流程,包罗设置scr_el3,spsr_el3,elr_el3寄存器,以及实行eret指令跳转到elr_el3设定的bl2入口函数处实行。其定义如下:
- func el3_exit
- mov x17, sp (1)
- msr spsel, #MODE_SP_ELX (2)
- str x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP] (3)
- ldr x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
- ldp x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3] (4)
- msr scr_el3, x18
- msr spsr_el3, x16
- msr elr_el3, x17 (5)
- #if IMAGE_BL31
- ldp x19, x20, [sp, #CTX_EL3STATE_OFFSET + CTX_CPTR_EL3]
- msr cptr_el3, x19
- ands x19, x19, #CPTR_EZ_BIT
- beq sve_not_enabled
- isb
- msr S3_6_C1_C2_0, x20 /* zcr_el3 */
- sve_not_enabled:
- #endif
- #if IMAGE_BL31 && DYNAMIC_WORKAROUND_CVE_2018_3639
- ldr x17, [sp, #CTX_CVE_2018_3639_OFFSET + CTX_CVE_2018_3639_DISABLE]
- cbz x17, 1f
- blr x17
- 1:
- #endif
- restore_ptw_el1_sys_regs
- bl restore_gp_pmcr_pauth_regs (6)
- ldr x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
- #if IMAGE_BL31 && RAS_EXTENSION
- esb
- #else
- dsb sy
- #endif
- #ifdef IMAGE_BL31
- str xzr, [sp, #CTX_EL3STATE_OFFSET + CTX_IS_IN_EL3]
- #endif
- exception_return (7)
- endfunc el3_exit
复制代码 (1)将sp_el0栈指针暂存到x17寄存器中
(2)将栈指针切换到sp_el3,此中sp_el3指向前面context的el3state_ctx指针,即它被用于保存el3的上下文
(3)将sp_el0的值保存的el3 context中
(4)从el3 context中加载scr_el3、spsr_el3和elr_el3寄存器的值
(5)设置scr_el3、spsr_el3和elr_el3寄存器
(6)规复gp寄存器等寄存器的值
(7)实行eret指令,今后cpu将离开bl1跳转到bl2的入口处实行了
exception_return 的实现:路径include/arch/aarch64/asm_macros.S
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |