天天看點

ATF之BL1跳轉到BL2的過程。0x00 Intro0x01 總體時序0x02 ERET指令0x03 ELR & SPSR指派0x04 SP堆棧寄存器0x05 跳轉

0x00 Intro

ATF(ARM Trusted Firmware)作為一個bootload,其本身最終要的作用就是load各階段的鏡像到執行位址,然後跳轉過去繼續執行。

根據以往經驗,ARM處理器跳轉到不同的鏡像可以通過直接修改PC寄存器來實作。當然除了修改PC寄存器可能還需要在跳轉之前初始化相關的環境、以及後續堆棧等。

這篇文章就是記錄ATF從BL1跳轉到BL2的過程。

0x01 總體時序

先放整體時序圖,圖中僅保留和跳轉相關的函數。入口是BL1的entrypoint,出口是通過ERET指令跳轉到BL2

ATF之BL1跳轉到BL2的過程。0x00 Intro0x01 總體時序0x02 ERET指令0x03 ELR & SPSR指派0x04 SP堆棧寄存器0x05 跳轉

0x02 ERET指令

從時序圖可以看到,從BL1執行到BL2是通過ERET指令實作的,那先看下ERET指令。

ATF之BL1跳轉到BL2的過程。0x00 Intro0x01 總體時序0x02 ERET指令0x03 ELR & SPSR指派0x04 SP堆棧寄存器0x05 跳轉

ERET指令用于異常傳回,傳回位址和處理器狀态是從目前EL(exception level)下的ELR和SPSR寄存器中恢複的。即ELR寄存器中的值就是BL1最後跳轉的目的位址,SPSR寄存器的值就是跳轉之後處理器的狀态。是以在代碼中重點關注這兩個值的初始化。

0x03 ELR & SPSR指派

BL1在初始化過程中有一個比較重要的資料結構,cpu_context。cpu_context在初始化的過程中需要把各個域都填充,ELR和SPSR寄存也存放在這個資料結構中,位于el3state_ctx域。

ATF之BL1跳轉到BL2的過程。0x00 Intro0x01 總體時序0x02 ERET指令0x03 ELR & SPSR指派0x04 SP堆棧寄存器0x05 跳轉

具體填充的語句如下:

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