0x00 Intro
ATF(ARM Trusted Firmware)作為一個bootload,其本身最終要的作用就是load各階段的鏡像到執行位址,然後跳轉過去繼續執行。
根據以往經驗,ARM處理器跳轉到不同的鏡像可以通過直接修改PC寄存器來實作。當然除了修改PC寄存器可能還需要在跳轉之前初始化相關的環境、以及後續堆棧等。
這篇文章就是記錄ATF從BL1跳轉到BL2的過程。
0x01 總體時序
先放整體時序圖,圖中僅保留和跳轉相關的函數。入口是BL1的entrypoint,出口是通過ERET指令跳轉到BL2
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9U1MkJnTyIWe0JTW2p0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5YjNxEDO0QTMwIjMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
0x02 ERET指令
從時序圖可以看到,從BL1執行到BL2是通過ERET指令實作的,那先看下ERET指令。
ERET指令用于異常傳回,傳回位址和處理器狀态是從目前EL(exception level)下的ELR和SPSR寄存器中恢複的。即ELR寄存器中的值就是BL1最後跳轉的目的位址,SPSR寄存器的值就是跳轉之後處理器的狀态。是以在代碼中重點關注這兩個值的初始化。
0x03 ELR & SPSR指派
BL1在初始化過程中有一個比較重要的資料結構,cpu_context。cpu_context在初始化的過程中需要把各個域都填充,ELR和SPSR寄存也存放在這個資料結構中,位于el3state_ctx域。
具體填充的語句如下:
state = get_el3state_ctx(ctx);
write_ctx_reg(state, CTX_SCR_EL3, scr_el3);
write_ctx_reg(state, CTX_ELR_EL3, ep->pc);
write_ctx_reg(state, CTX_SPSR_EL3, ep->spsr);
先将state定位到cpu_context的el3_state域,然後依次将pc和spsr分别填到el3_state的ELR和SPSR中。那ep->pc和ep->spsr來源于何處呢?
spsr
SPSR來源于bl1_context_mgmt.c中的bl1_prepare_next_image函數:
next_bl_ep->spsr = SPSR_64(mode, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS);
pc
PC來源于BL2鏡像的描述資料結構,定義在common_def.h中,如下所示:
#define BL2_IMAGE_DESC { \
.image_id = BL2_IMAGE_ID, \
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, \
VERSION_2, image_info_t, 0), \
.image_info.image_base = BL2_BASE, \
.image_info.image_max_size = BL2_LIMIT - BL2_BASE,\
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, \
VERSION_2, entry_point_info_t, SECURE | EXECUTABLE),\
.ep_info.pc = BL2_BASE, \
}
可見ep_info.pc被初始化成了BL2_BASE。
是以ELR被初始化成了BL2_BASE, SPSR也有了值。
0x04 SP堆棧寄存器
前面看到ELR和SPSR隻是被儲存到了context_cpu中,那最終是如何設定到處理器的相關寄存器中呢?
答案是堆棧,最後通過sp指針彈出再設到相關寄存器中。sp堆棧的初始化時在context_mgmt.h的cm_set_next_context()函數中實作的。
__asm__ volatile("msr spsel, #1\n"
"mov sp, %0\n"
"msr spsel, #0\n"
: : "r" (context));
這裡有個問題,如果隻需要設sp,為啥還要設定spsel寄存器?
根據本部落格前期的文章[ARM v8 AArch64 Programmers’ model](https://blog.csdn.net/rockrockwu/article/details/103698045)可以知道,ARMv8除了可以使用目前模式下的sp,也可以使用EL0的sp。除EL0以外的模式,SP是可以選擇的,可以使用ELx_SP,也可以選擇使用EL0_SP。
這條語句的作用就是首先選擇使用EL3_SP,然後将EL3_SP指向cpu_context資料結構,最後又将sp調整為EL0_SP。
那為什麼要來回設定sp呢?不設定可以嗎?
答案是來回設定sp更合理一些。因為設定sp後,堆棧内容就變成了cpu_context了,這裡面都BL2運作需要的寄存器資料。但是這裡隻是設定了sp,到真正去使用sp中的内容還有大段代碼需要運作。這些代碼很有可能會去修改堆棧中的内容,這會導緻BL2運作環境異常。
是以這裡把el3_sp修改之後,就立即切換到el0_sp,用el0的sp來跑後面的代碼,防止el3_sp中的内容被修改。
0x05 跳轉
接着就到正式跳轉了。
mov x17, sp
msr spsel, #MODE_SP_ELX
str x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
/* ----------------------------------------------------------
* Restore SPSR_EL3, ELR_EL3 and SCR_EL3 prior to ERET
* ----------------------------------------------------------
*/
ldr x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
ldp x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
msr scr_el3, x18
msr spsr_el3, x16
msr elr_el3, x17
可以看到跳轉就是從切換sp開始,把sp切到el3_sp。同時把目前的sp儲存到cpu_context的runtime_sp中。然後通過ldp指令,把儲存在堆棧中的cpu_context資料結構中的ELR和SPSR彈出到寄存器x17和X16中。然後又從x17/x16寄存器恢複到spsr和elr中。最後通過eret指令跳轉到elr,而elr就是BL2_BASE,到此就進入BL2了。
.macro exception_return
eret
dsb nsh
isb
.endm
[1]. Arm® Architecture Reference Manual
[2]. Arm® A64 Instruction Set Architecture
[3]. Arm® Architecture Registers