无论是bl1还是bl31都会走到el3_entrypoint_common, 只是传递过来的参数不一致。
#if !RESET_TO_BL31
/* ---------------------------------------------------------------------
* For !RESET_TO_BL31 systems, only the primary CPU ever reaches
* bl31_entrypoint() during the cold boot flow, so the cold/warm boot
* and primary/secondary CPU logic should not be executed in this case.
*
* Also, assume that the previous bootloader has already initialised the
* SCTLR_EL3, including the endianness, and has initialised the memory.
* ---------------------------------------------------------------------
*/
el3_entrypoint_common \
_init_sctlr=0 \
_warm_boot_mailbox=0 \
_secondary_cold_boot=0 \
_init_memory=0 \
_init_c_runtime=1 \
_exception_vectors=runtime_exceptions \
_pie_fixup_size=BL31_LIMIT - BL31_BASE
#else
/* ---------------------------------------------------------------------
* For RESET_TO_BL31 systems which have a programmable reset address,
* bl31_entrypoint() is executed only on the cold boot path so we can
* skip the warm boot mailbox mechanism.
* ---------------------------------------------------------------------
*/
el3_entrypoint_common \
_init_sctlr=1 \
_warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS \
_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU \
_init_memory=1 \
_init_c_runtime=1 \
_exception_vectors=runtime_exceptions \
_pie_fixup_size=BL31_LIMIT - BL31_BASE
/* ---------------------------------------------------------------------
* For RESET_TO_BL31 systems, BL31 is the first bootloader to run so
* there's no argument to relay from a previous bootloader. Zero the
* arguments passed to the platform layer to reflect that.
* ---------------------------------------------------------------------
*/
mov x20, 0
mov x21, 0
mov x22, 0
mov x23, 0
#endif /* RESET_TO_BL31 */
A. 功能分析
bl31_entrypoint.S
el3_entrypoint_common, 这个宏对于整个系统的启动,起着承上启下的作用,在这个阶段,处理器处于安全模式。
针对这个宏的参数说明:
1. _init_sctlr:指定是否初始化寄存器SCTLR(System Control Register)。
2. _warm_boot_mailbox: 冷启动和热启动分支处理,PROGRAMMABLE_RESET_ADDRESS 宏表示系统的复位地址,为0可能是冷启动(运行程序中会进一步判断),不为0则是热启动。
3. _secondary_cold_boot:指定是否为单个 CPU 进行冷启动,如果是 COLD_BOOT_SINGLE_CPU 则表示是单个 CPU 进行冷启动。
4. _warm_boot_mailbox_init_memory:指定是否初始化内存。
5. _init_c_runtime:指定是否初始化 C 运行时。
6. _exception_vectors:指定异常向量的入口点,runtime_exceptions。
7. _pie_fixup_size:指定 PIE 修复大小,在本例中是 0。
下面针对每一个参数执行的代码进行具体的分析,分析顺序为代码工作流程:
1. _init_sctlr
.if \_init_sctlr
/* -------------------------------------------------------------
* This is the initialisation of SCTLR_EL3 and so must ensure
* that all fields are explicitly set rather than relying on hw.
* Some fields reset to an IMPLEMENTATION DEFINED value and
* others are architecturally UNKNOWN on reset.
*
* SCTLR.EE: Set the CPU endianness before doing anything that
* might involve memory reads or writes. Set to zero to select
* Little Endian.
*
* SCTLR_EL3.WXN: For the EL3 translation regime, this field can
* force all memory regions that are writeable to be treated as
* XN (Execute-never). Set to zero so that this control has no
* effect on memory access permissions.
*
* SCTLR_EL3.SA: Set to zero to disable Stack Alignment check.
*
* SCTLR_EL3.A: Set to zero to disable Alignment fault checking.
*
* SCTLR.DSSBS: Set to zero to disable speculation store bypass
* safe behaviour upon exception entry to EL3.
* -------------------------------------------------------------
*/
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 /* _init_sctlr */
mov_imm x0, (SCTLR_RESET_VAL & ~(SCTLR_EE_BIT | SCTLR_WXN_BIT | SCTLR_SA_BIT | SCTLR_A_BIT | SCTLR_DSSBS_BIT)):
计算 SCTLR_EL3 寄存器的新值。通过对 SCTLR_RESET_VAL 应用位掩码,我们清除了 SCTLR_EE_BIT(字节序)、SCTLR_WXN_BIT(写时不执行)、SCTLR_SA_BIT(堆栈对齐检查)、SCTLR_A_BIT(对齐错误检查)
和 SCTLR_DSSBS_BIT(投机存储器旁路安全行为)位。结果值存储在寄存器 x0 中。
投机存储操作是指CPU在不等待先前内存操作完成的情况下投机存储数据到内存的操作。这样做是为了通过重叠内存操作来提高性能。然而,如果存在安全漏洞,投机存储操作可能会绕过安全检查,导致信息泄露或其他安全问题。
为了防止这种情况,在最新的CPU架构中引入了“投机存储器旁路安全行为”位。当SCTLR_DSSBS_BIT被设置时,CPU将在执行投机存储操作之前等待先前的内存操作完成,以确保投机存储操作以安全的方式执行。
当这个位被清除时,CPU可能会在不等待的情况下执行投机存储操作,从而导致安全漏洞。
还需要注意的是上电时,SCTLR_RESET_VAL 在arch.h中被赋了初始值。
msr sctlr_el3, x0:
将新值(寄存器 x0 中的值)写入 SCTLR_EL3 寄存器。这将更新处理器在 EL3 异常级别的配置,如前面提到的字节序、写时不执行、堆栈对齐检查、对齐错误检查和投机存储器旁路安全行为。
isb: 指令同步屏障 (Instruction Synchronization Barrier)。
这个指令确保在执行此屏障之前的所有指令已完成,以及在执行此屏障之后的所有指令还未开始执行。
这里使用 ISB 的目的是确保对 SCTLR_EL3 寄存器的更改在继续执行后续指令之前生效。这样可以防止在新配置生效之前执行与之相关的指令。
2. _warm_boot_mailbox
.if \_warm_boot_mailbox
/* -------------------------------------------------------------
* This code will be executed for both warm and cold resets.
* Now is the time to distinguish between the two.
* Query the platform entrypoint address and if it is not zero
* then it means it is a warm boot so jump to this address.
* -------------------------------------------------------------
*/
bl plat_get_my_entrypoint
cbz (Compare and Branch on Zero)一种条件分支指令。它用于在寄存器上进行比较,如果该寄存器的值为零,则跳转到指定的标签或地址。
cbz x0, do_cold_boot -- 如果x0为0,那就跳到 do_cold_boot
br x0 -- 如果x0不为0,那就跳到x0的地址
do_cold_boot:
.endif /* _warm_boot_mailbox */
_warm_boot_mailbox =!PROGRAMMABLE_RESET_ADDRESS, PROGRAMMABLE_RESET_ADDRESS表示系统的复位地址,
如果该值为零,则表示系统可能是冷启动,执行_warm_boot_mailbox的代码;如果不为零,则表示系统是热启动。
如果 PROGRAMMABLE_RESET_ADDRESS =0,_warm_boot_mailbox 宏被定义,系统有可能是冷启动,但还需进一步判断,即执行以下操作:
调用 plat_get_my_entrypoint 函数以查询平台的入口点地址, 这个函数有各自厂家实现。
如果平台入口点地址不为零,则表示系统是热启动,因此控制将转移到该地址,开始热启动的处理。
如果平台入口点地址为零,则表示系统是冷启动,因此控制将转移到 do_cold_boot 标签,开始冷启动的处理。
3. _pie_fixup_size: 修复全局描述符表
当系统进入冷启动后,如果启用了多处理器支持,则有可能需要修复一次全局描述符表。
通常情况下,Arm 系统是不启用 PIE的。PIE 是一种特殊的内存分配方式,用于在程序运行时为可执行文件分配随机的内存位置。
这对于防止恶意软件的攻击非常有用,但是它带来了一些性能开销。因此,通常情况下,Arm 系统不会启用 PIE。
在安全性要求较高的场景,例如嵌入式系统或者涉及到支付或私密信息的系统中,PIE 可能会被启用。在这种情况下,下面这段代码就有用了。
.if \_pie_fixup_size
#if ENABLE_PIE -- f1上面没有enable
/*
* ------------------------------------------------------------
* If PIE is enabled fixup the Global descriptor Table only
* once during primary core cold boot path.
*
* Compile time base address, required for fixup, is calculated
* using "pie_fixup" label present within first page.
* ------------------------------------------------------------
*/
pie_fixup: 定义 pie_fixup 标签,该标签在编译时用于计算基址
ldr x0, =pie_fixup --- ldr 指令将 X0 寄存器加载为 pie_fixup 标签的地址
and x0, x0, #~(PAGE_SIZE_MASK) --- and 指令对 X0 寄存器的值进行掩码操作,以便将其与 PAGE_SIZE_MASK 取按位与
mov_imm x1, \_pie_fixup_size --- mov_imm 指令将_pie_fixup_size加载到 X1 寄存器中。
add x1, x1, x0 --- add 指令将 X1 寄存器的值与 X0 寄存器的值相加,得到修复 GDT 的结束地址
bl fixup_gdt_reloc --- 调用 fixup_gdt_reloc 函数,该函数将修复 GDT
#endif /* ENABLE_PIE */
.endif /* _pie_fixup_size */
如果启用了多处理器支持,则 COLD_BOOT_SINGLE_CPU 的值为 0,如果没有启用多处理器支持,则 COLD_BOOT_SINGLE_CPU 的值为 1。
如果是热启动,则不需要修复全局描述符表(GDT),因为它已经被修复过一次了。在热启动中,内存布局和系统状态都是保持不变的,因此不需要再次修复。
相反,在冷启动中,内存布局和系统状态都是全新的,因此需要修复一次 GDT。
4. 设置异常向量
/* ---------------------------------------------------------------------
* Set the exception vectors.
* ---------------------------------------------------------------------
*/
参数传进来的 runtime_exceptions, bl1、bl2、bl31都有自己的函数。
adr x0, \_exception_vectors -- 将异常向量表的地址存储到寄存器 x0 中
msr vbar_el3, x0 -- 将寄存器 x0 的值存储到 vbar_el3 寄存器中,该寄存器是 EL3 的异常向量表寄存器(Exception Vector Base Address Register)
isb
/* ---------------------------------------------------------------------
* It is a cold boot.
* Perform any processor specific actions upon reset e.g. cache, TLB
* invalidations etc.
* ---------------------------------------------------------------------
*/
bl reset_handler
补充初始化EL3寄存器(_init_sctlr=1时已经设置过一部分)
el3_arch_init_common --- el3_arch_init_common宏与el3_entrypoint_common宏定义在同一个文件
5. 主CPU或辅助CPU的冷启动(参数:_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU)
f1上不走这里,_secondary_cold_boot为0
.if \_secondary_cold_boot
/* -------------------------------------------------------------
* Check if this is a primary or secondary CPU cold boot.
* The primary CPU will set up the platform while the
* secondaries are placed in a platform-specific state until the
* primary CPU performs the necessary actions to bring them out
* of that state and allows entry into the OS.
* -------------------------------------------------------------
*/
bl plat_is_my_cpu_primary -- 需要的话要厂商自己实现来确定是否是主core
cbnz w0, do_primary_cold_boot
/* This is a cold boot on a secondary CPU */
bl plat_secondary_cold_boot_setup
/* plat_secondary_cold_boot_setup() is not supposed to return */
bl el3_panic
do_primary_cold_boot:
.endif /* _secondary_cold_boot */
首先使用 plat_is_my_cpu_primary 函数来检查处理器是否为主处理器。
如果是主处理器(返回非0),则跳转到 do_primary_cold_boot 标号,程序继续执行。
否则,表示它是次要处理器的冷启动,将调用 plat_secondary_cold_boot_setup 函数来初始化辅助cpu,
如果这个函数执行不出现错误,会执行辅助cpu后期操作,不再返回。
如果返回,则说明系统出现错误,调用 el3_panic 函数抛出异常错误。
6. 主CPU初始化内存(参数:_init_memory=1)
/* ---------------------------------------------------------------------
* Initialize memory now. Secondary CPU initialization won't get to this
* point.
* ---------------------------------------------------------------------
*/
.if \_init_memory
bl platform_mem_init
.endif /* _init_memory */
platform_mem_init函数的实现与具体的硬件平台有关。
每个硬件平台的内存构造和资源可能不同,因此 platform_mem_init函数的实现也可能不同。
例如,在一个平台上,platform_mem_init 函数可能需要配置内存分配池,并将某些内存区域映射到系统存储器中,
而在另一个平台上,platform_mem_init 函数可能仅需要配置缓存和数据换出。
7. 初始化C运行时环境(参数:_init_c_runtime=1)
/* ---------------------------------------------------------------------
* Init C runtime environment:
* - Zero-initialise the NOBITS sections. There are 2 of them:
* - the .bss section;
* - the coherent memory section (if any).
* - Relocate the data section from ROM to RAM, if required.
* ---------------------------------------------------------------------
*/
.if \_init_c_runtime
#if defined(IMAGE_BL31) || (defined(IMAGE_BL2) && BL2_AT_EL3 && BL2_INV_DCACHE)
/* -------------------------------------------------------------
* Invalidate the RW memory used by the BL31 image. This
* includes the data and NOBITS sections. This is done to
* safeguard against possible corruption of this memory by
* dirty cache lines in a system cache as a result of use by
* an earlier boot loader stage.
* -------------------------------------------------------------
*/
adrp x0, __RW_START__
add x0, x0, :lo12:__RW_START__
adrp x1, __RW_END__
add x1, x1, :lo12:__RW_END__
sub x1, x1, x0
bl inv_dcache_range
#if defined(IMAGE_BL31) && SEPARATE_NOBITS_REGION
adrp x0, __NOBITS_START__
add x0, x0, :lo12:__NOBITS_START__
adrp x1, __NOBITS_END__
add x1, x1, :lo12:__NOBITS_END__
sub x1, x1, x0
bl inv_dcache_range
#endif
#endif
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
#if USE_COHERENT_MEM
adrp x0, __COHERENT_RAM_START__
add x0, x0, :lo12:__COHERENT_RAM_START__
adrp x1, __COHERENT_RAM_END_UNALIGNED__
add x1, x1, :lo12: __COHERENT_RAM_END_UNALIGNED__
sub x1, x1, x0
bl zeromem
#endif
#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
#endif
.endif /* _init_c_runtime */
这段代码主要负责初始化 C 运行时环境,具体包括以下操作:
零初始化 NOBITS 区段:这里有两个这样的区段,一个是 .bss 区段,另一个是一致性内存区段(如果有的话,我们这里没有)。
如果需要(我们不需要),将数据区段从 ROM 拷贝到 RAM。
我们再次逐段分析代码:
首先,根据条件编译,代码会检查是否需要执行这些操作,例如在 BL31 镜像或者 BL2 镜像的特定条件下。
接下来,代码会检查是否启用了 PIE (Position Independent Executable)。
PIE 是一种可执行文件格式,可以让代码在内存中的任何位置运行,而不需要重新编译。
根据 PIE 的启用情况,代码会计算数据区段的起始和结束地址。
然后,代码会调用 inv_dcache_range 函数,以确保在系统缓存中没有残留的脏缓存行,防止之前的引导阶段对当前阶段的内存造成污染。
接下来,代码会计算 .bss 区段的起始和结束地址,然后调用 zeromem 函数将 .bss 区段的内容置零。.bss 区段通常包含了程序中未初始化的全局变量。
如果启用了一致性内存,代码会计算一致性内存区段的起始和结束地址,并调用 zeromem 函数将这部分内存置零。
接着,对于 BL1 映像或满足特定条件的 BL2 映像,代码会计算数据区段在 RAM 和 ROM 中的起始和结束地址。然后调用 memcpy16 函数,将数据区段从 ROM 拷贝到 RAM。
这个过程是为了确保在 RAM 中可以访问并修改数据区段的内容。
这段代码在启动过程的早期执行,为 TF-A 的后续阶段提供了一个正确初始化的 C 运行时环境。这有助于确保程序在运行时可以正确地访问全局变量和一致性内存。
8. 设置堆栈指针寄存器SP_EL0
/* ---------------------------------------------------------------------
* Use SP_EL0 for the C runtime stack.
* ---------------------------------------------------------------------
*/
msr spsel, #0
执行msr spsel, #0指令的目的是将SPSel(栈指针选择器)寄存器的值初始化为0。
在ARMv8-A架构中,SPSel寄存器用于选择当前异常级别下要使用的栈指针寄存器。
当SPSel的值为0时,处理器使用SP_ELx寄存器(如SP_EL1、SP_EL2、SP_EL3)作为栈指针;
当SPSel的值为1时,处理器使用SP_EL0寄存器作为栈指针。
需要注意的是,SPSel寄存器仅在EL1下有效,用于在EL1和EL0之间切换栈指针。在EL2和EL3中,SPSel寄存器始终为0。
通过执行msr spsel, #0指令,确保了处理器在EL1下使用SP_EL1寄存器作为栈指针,而不是SP_EL0寄存器。
在EL3下(例如,在TF-A中),由于SPSel寄存器始终为0,处理器会使用SP_EL3寄存器作为栈指针。
msr spsel, #0确保了在TF-A的执行过程中,栈空间与当前的异常级别和特权级别相匹配。
9.
/* ---------------------------------------------------------------------
* Allocate a stack whose memory will be marked as Normal-IS-WBWA when
* the MMU is enabled. There is no risk of reading stale stack memory
* after enabling the MMU as only the primary CPU is running at the
* moment.
* ---------------------------------------------------------------------
*/
目的是为当前CPU分配一个栈内存。栈内存在启用MMU(内存管理单元)后将被标记为Normal-IS-WBWA。
这里的Normal-IS-WBWA表示内存类型是Normal,具有Inner Shareable属性(IS,内部可共享),并采用写回(Write-Back)和写分配(Write-Allocate)的缓存策略
bl plat_set_my_stack 这个由厂商实现,./open_atf/plat/common/aarch64/platform_mp_stack.S
1.
func plat_set_my_stack
mov x9, x30 -- x30即LR,是返回地址寄存器,保存LR到x9中,为后续的返回做准备。
bl plat_get_my_stack
mov sp, x0
ret x9 -- 返回函数调用
endfunc plat_set_my_stack
2.
/* -----------------------------------------------------
* uintptr_t plat_get_my_stack ()
*
* For the current CPU, this function returns the stack
* pointer for a stack allocated in device memory.
* -----------------------------------------------------
*/ 从section
func plat_get_my_stack
mov x10, x30 -- 保存LR到x9中,为下面的返回做准备
get_my_mp_stack platform_normal_stacks, PLATFORM_STACK_SIZE --- PLATFORM_STACK_SIZE定义在platform_def.h中为0x4000
ret x10
endfunc plat_get_my_stack
【问题:】 platform_normal_stacks 是什么东西,哪里来的呢?
declare_stack platform_normal_stacks, tzfw_normal_stacks, PLATFORM_STACK_SIZE, PLATFORM_CORE_COUNT, CACHE_WRITEBACK_GRANULE
通过上面的宏汇编,会在编译的时候在section:tzfw_normal_stacks 中预留一段内存,用来作为各个cpu的堆栈空间。
declare_stack:
.macro declare_stack _name, _section, _size, _count, _align=DEFAULT_STACK_ALIGN -- ./open_atf/include/common/asm_macros_common.S
count_tz \_align, 0
.if (\_align - (1 << TZ_COUNT))
.error "Incorrect stack alignment specified (Must be a power of 2)."
.endif
.if ((\_size & ((1 << TZ_COUNT) - 1)) <> 0)
.error "Stack size not correctly aligned"
.endif
.section \_section, "aw", %nobits
.align TZ_COUNT
\_name: -- 根据前面传进来的名字,定义一个变量,并给它分配size * count的内存空间,放在tzfw_normal_stacks section里面。
.space ((\_count) * (\_size)), 0
.endm
1.
tzfw_normal_stacks: 是一个section的名字,也就是在编译的时候会预留一段地址,专门用于堆栈中,size为PLATFORM_STACK_SIZE * core的数量,因为每个core的sp是独立的。
#if !(defined(IMAGE_BL31) && RECLAIM_INIT_CODE)
#define STACK_SECTION \
stacks (NOLOAD) : { \
__STACKS_START__ = .; \
*(tzfw_normal_stacks) \
__STACKS_END__ = .; \
}
#endif
2.
PLATFORM_STACK_SIZE: 每个堆栈的大小
PLATFORM_CORE_COUNT: cpu的个数
总结:
从上面的分析看出,创建了一个变量 platform_normal_stacks,给它申请了 PLATFORM_STACK_SIZE * PLATFORM_CORE_COUNT大小的内存空间,并放在section:tzfw_normal_stacks里面。
专门给各个cpu做堆栈空间使用。
3. 函数 get_my_mp_stack --- ./open_atf/include/arch/aarch64/asm_macros.S
/*
* This macro calculates the base address of the current CPU's MP stack
* using the plat_my_core_pos() index, the name of the stack storage
* and the size of each stack
* Out: X0 = physical address of stack base
* Clobber: X30, X1, X2
*/ -- 通过调用 plat_my_core_pos 函数获取当前CPU的索引,然后使用该索引、存储堆栈的名称和每个堆栈的大小计算堆栈的基地址
.macro get_my_mp_stack _name, _size
bl plat_my_core_pos -- 获取当前CPU的索引,应该是0、1、2、3、4、5、6....
adrp x2, (\_name + \_size) -- 使用adrp指令计算指向堆栈存储区域的高位地址部分,并将其加载到寄存器x2中。
add x2, x2, :lo12:(\_name + \_size) -- 使用add指令合并高位地址和低位地址,以获得完整的地址。
mov x1, #\_size -- 将堆栈的大小存储到寄存器x1中。
madd x0, x0, x1, x2 -- 使用madd指令计算当前CPU的MP堆栈的基地址,并将其存储到寄存器x0中, x0 = x0 * x1 + x2
在ARMv8架构中,madd指令是一种乘加指令,用于执行两个数的乘法操作,并将结果与第三个数相加。
madd指令是“Multiply-Add”的缩写,其一般形式和作用如下:
madd Xd, Xn, Xm, Xa
Xd:目的寄存器,用于存储操作的最终结果。
Xn, Xm:参与乘法操作的两个源寄存器。
Xa:在乘法结果基础上进行累加的源寄存器。
指令的操作可以表示为:
Xd = (Xn * Xm) + Xa
.endm
4. 函数 plat_my_core_pos --- atf/plat/vendor/franklin/aarch64/plat_helpers.S
func plat_my_core_pos
mrs x0, mpidr_el1 --- 从MPIDR_EL1(多处理器仿真器ID寄存器)中读取当前CPU的ID,并将其存储在寄存器x0中
lsr x0, x0, #MPIDR_AFF1_SHIFT
and x0, x0, #MPIDR_AFFLVL_MASK
ret
endfunc plat_my_core_pos
1569



被折叠的 条评论
为什么被折叠?



