天天看點

armv8 boot 分析系列文章目錄前言一、 u-boot在彙編啟動階段對系統的一些初始化二、 開啟位址無關後的重定位位址操作三、 進入_main之前系統寄存器初始化和從核的引導

系列文章目錄

文章目錄

  • 系列文章目錄
  • 前言
  • 一、 u-boot在彙編啟動階段對系統的一些初始化
  • 二、 開啟位址無關後的重定位位址操作
  • 三、 進入_main之前系統寄存器初始化和從核的引導

前言

原文連結:https://blog.csdn.net/maybeYoc/article/details/122960357

一、 u-boot在彙編啟動階段對系統的一些初始化

當cpu交由u-boot接管進入u-boot後,首先會到_start符号處開始執行初始化,并在此期間完成一些必要的系統寄存器相關的初始化,包括儲存boot參數,進行位址無關fixed,系統寄存器複位,底層平台相關初始化等,啟動代碼位于arch/arm/cpu/armv8/start.S,入口位址為_start。

從_start開始,u-boot會根據board定義做一些平台化相關的初始化工作或者是儲存一些重要寄存器資訊,代碼如下:

/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 *************************************************************************/

.globl	_start
_start: ------------------------------------------------------------------------ (1)
#if defined(CONFIG_LINUX_KERNEL_IMAGE_HEADER) ---------------------------------- (2)
#include <asm/boot0-linux-kernel-header.h>
#elif defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) -------------------------------- (3)
/*
 * Various SoCs need something special and SoC-specific up front in
 * order to boot, allow them to set that in their boot0.h file and then
 * use it here.
 */
#include <asm/arch/boot0.h>
#else
	b	reset ----------------------------------------------------------------- (4)
#endif

	.align 3

.globl	_TEXT_BASE ------------------------------------------------------------ (5)
_TEXT_BASE:
	.quad	CONFIG_SYS_TEXT_BASE

/*
 * These are defined in the linker script.
 */
.globl	_end_ofs -------------------------------------------------------------- (5)
_end_ofs:
	.quad	_end - _start

.globl	_bss_start_ofs
_bss_start_ofs:
	.quad	__bss_start - _start

.globl	_bss_end_ofs
_bss_end_ofs:
	.quad	__bss_end - _start

reset:
	/* Allow the board to save important registers */
	b	save_boot_params ----------------------------------------------------- (6)
.globl	save_boot_params_ret
save_boot_params_ret:

... /* 此處省略無關代碼,待分析到時再展開代碼 */
...
WEAK(save_boot_params)
	b	save_boot_params_ret	/* back to my caller */
ENDPROC(save_boot_params)

#endif

           

(1)_start段标記為全局可見并在連結腳本中被聲明為入口位址,表示u-boot的入口位址為_start;

(2) 首先進入入口後有兩種可配置情況,一種就是定義了LINUX_KERNEL_IMAGE_HEADER,

boot0-linux-kernel-header.h展開部分後如下:

.macro	le64sym, sym
	.long	\sym\()_lo32
	.long	\sym\()_hi32
	.endm

.globl _start
_start:
	/*
	 * DO NOT MODIFY. Image header expected by Linux boot-loaders.
	 */
	b	reset				/* branch to kernel start, magic */
	.long	0				/* reserved */
	le64sym	_kernel_offset_le		/* Image load offset from start of RAM, little-endian */
	le64sym	_kernel_size_le			/* Effective size of kernel image, little-endian */
	le64sym	_kernel_flags_le		/* Informative flags, little-endian */
	.quad	0				/* reserved */
	.quad	0				/* reserved */
	.quad	0				/* reserved */
	.ascii	"ARM\x64"			/* Magic number */
	.long	0				/* reserved */
           

此處将與在連結腳本中定義的LINUX_KERNEL_IMAGE_HEADER對應起來,為u-boot頭部添加一個類似與Linux arm64 的Image頭部,首先是起始8位元組,如果沒有定義efi相關的功能則是一個跳轉指令,跳轉到reset段繼續執行啟動流程,其他如連結腳本中解釋一緻;

(3) 第二種可能的配置,就是定義了ENABLE_ARM_SOC_BOOT0_HOOK配置,此處的頭檔案根據不同board會引用到不同頭檔案,如瑞芯微的最終會引用到如下部分代碼頭檔案:

#ifdef CONFIG_SPL_BUILD
	/*
	 * We need to add 4 bytes of space for the 'RK33' at the
	 * beginning of the executable.	 However, as we want to keep
	 * this generic and make it applicable to builds that are like
	 * the RK3368 (TPL needs this, SPL doesn't) or the RK3399 (no
	 * TPL, but extra space needed in the SPL), we simply insert
	 * a branch-to-next-instruction-word with the expectation that
	 * the first one may be overwritten, if this is the first stage
	 * contained in the final image created with mkimage)...
	 */
	b 1f	 /* if overwritten, entry-address is at the next word */
1:
#endif
#if CONFIG_IS_ENABLED(ROCKCHIP_EARLYRETURN_TO_BROM)
	adr     r3, entry_counter
	ldr	r0, [r3]
	cmp	r0, #1           /* check if entry_counter == 1 */
	beq	reset            /* regular bootup */
	add     r0, #1
	str	r0, [r3]         /* increment the entry_counter in memory */
	mov     r0, #0           /* return 0 to the BROM to signal 'OK' */
	bx	lr               /* return control to the BROM */
entry_counter:
	.word   0
#endif

#if (defined(CONFIG_SPL_BUILD) || defined(CONFIG_ARM64))
	/* U-Boot proper of armv7 do not need this */
	b reset
#endif

#if !defined(CONFIG_ARM64)
	/*
	 * For armv7, the addr '_start' will used as vector start address
	 * and write to VBAR register, which needs to aligned to 0x20.
	 */
	.align(5), 0x0
_start:
	ARM_VECTORS
#endif

#if !defined(CONFIG_TPL_BUILD) && defined(CONFIG_SPL_BUILD) && \
	(CONFIG_ROCKCHIP_SPL_RESERVE_IRAM > 0)
	.space CONFIG_ROCKCHIP_SPL_RESERVE_IRAM	/* space for the ATF data */
#endif
           

因為有些裝置boot到u-boot之前已經有安全固件了,是以此時控制權交給u-boot時,其實是可能有一些傳遞參數資訊的要求,比如這裡瑞芯微晶片通過bootrom boot到tpl後,後續在完成tpl初始化後會将控制權再交還給bootrom固件,由bootrom固件繼續加載spl,是以這裡在進行u-boot流程之前儲存了bootrom的傳回位址,以便後續瑞芯微闆級軟體使用。定義有可能也是arm32的模式,是以還可能在入口位址處儲存異常向量表;

(4)如果對應board沒有上述兩種需求,那麼_start段則是一條最簡單的跳轉指令b reset跳轉到reset處繼續啟動流程初始化;

(5)在_start到reset之間,有一個.align 3用于8位元組對齊,因為可能在讀取常量位址之前各自平台做了自己代碼邏輯導緻目前位址并不是8位元組對齊的,這裡不管是否對齊都強制對齊了一下,之後還儲存了一些常量資訊,其中包括_TEXT_BASE儲存了連結位址,用于在啟動位址無關功能時進行對運作時位址的偏移計算,其他幾個偏移值目前未使用;

(6)save_boot_params用于儲存一些board相關的重要寄存器,此處定義為了一個弱函數,為直接跳轉回save_boot_params_ret繼續往下執行,如果某些board需要儲存寄存器參數則可以在自己的lowlevel.S檔案中實作此函數。一般由atf,bl2或者rom跳轉到spl或u-boot時廠商可能需要在兩個固件之間傳遞參數,比如由bl2在寄存器x0,x1,x2中分别存入了一些固件的位址資訊,那麼u-boot則可以在早期通過此函數儲存這些資訊,并在後續某個時機中使用。

二、 開啟位址無關後的重定位位址操作

在由save_boot_params跳轉回save_boot_params_ret後啟動流程繼續往下執行來到下面代碼段:

save_boot_params_ret:

#if CONFIG_POSITION_INDEPENDENT
	/* Verify that we're 4K aligned.  */
	adr	x0, _start
	ands	x0, x0, #0xfff --------------------------------------------------------- (1)
	b.eq	1f
0:
	/*
	 * FATAL, can't continue.
	 * U-Boot needs to be loaded at a 4K aligned address.
	 *
	 * We use ADRP and ADD to load some symbol addresses during startup.
	 * The ADD uses an absolute (non pc-relative) lo12 relocation
	 * thus requiring 4K alignment.
	 */
	wfi
	b	0b
1:

	/*
	 * Fix .rela.dyn relocations. This allows U-Boot to be loaded to and
	 * executed at a different address than it was linked at.
	 */
pie_fixup:
	adr	x0, _start		/* x0 <- Runtime value of _start */
	ldr	x1, _TEXT_BASE		/* x1 <- Linked value of _start */
	subs	x9, x0, x1		/* x9 <- Run-vs-link offset */
	beq	pie_fixup_done ------------------------------------------------------------- (2)
	adrp    x2, __rel_dyn_start     /* x2 <- Runtime &__rel_dyn_start */ ----------- (3)
	add     x2, x2, #:lo12:__rel_dyn_start
	adrp    x3, __rel_dyn_end       /* x3 <- Runtime &__rel_dyn_end */
	add     x3, x3, #:lo12:__rel_dyn_end
pie_fix_loop: ---------------------------------------------------------------------- (4)
	ldp	x0, x1, [x2], #16	/* (x0, x1) <- (Link location, fixup) */
	ldr	x4, [x2], #8		/* x4 <- addend */
	cmp	w1, #1027		/* relative fixup? */
	bne	pie_skip_reloc
	/* relative fix: store addend plus offset at dest location */
	add	x0, x0, x9
	add	x4, x4, x9
	str	x4, [x0]
pie_skip_reloc:
	cmp	x2, x3
	b.lo	pie_fix_loop
pie_fixup_done:
#endif
           
ldp	x0, x1, [x2], #16	/* (x0, x1) <- (Link location, fixup) */先從X2取值, 低8位元組給x0, 高8 位元組給x1, 然後 x2 自加16,也就是先指派,再加的
ldr	x4, [x2], #8		/* x4 <- addend */
           

此部分的功能主要是在定義POSITION_INDEPENDENT後,進行位址無關的相對位址修複,以此保證後續在跳入c語言部分時可正常執行,一般不定義此配置則是繼續往下執行boot流程。

(1) 正如在連結腳本中說的,位址無關功能最基本需要保證加載位址是4K對齊的,經過一些測試發現某些情況需要64K對齊,這裡不展開說明。此處使用adr指令擷取_start的運作時位址并檢驗是否是4K對齊,如果是則繼續往下執行,如果不是則調用wfi指令挂死在此處。wfi為等待中斷指令,隻有在接收到中斷事件是才會喚醒cpu繼續往下執行。(補充一個知識點:wfi指令隻能被中斷喚醒,wfe指令可以被sev指令喚醒也可以被中斷喚醒)

(2) 通過對運作時位址和加載位址相減得到一個位址偏移值,如果偏移值等于0,說明加載位址和運作時位址是一緻的不需要進行位址修複,則直接跳pie_fixup_done繼續下面流程初始化,否則就進入位址修複邏輯。

(3) 首先需要說明為什麼不使用adr指令而是adrp指令,adr指令是一個小範圍讀相對pc指針位址記憶體的指令,可以使用adr說明讀取的位址一定是離pc指針很近的位置,而當讀取__rel_dyn_start這種并不能确定實際位址在哪裡的位址時則隻能使用大範圍讀位址指令的adrp指令,不過adrp指令是以頁為機關進行讀取的,是以add x2, x2, #:lo12:__rel_dyn_start的作用就是将__rel_dyn_start位址的頁内偏移讀取出來并與頁對齊的運作位址相加得到實際的運作位址。(此操作指令在Linux上被封裝為adr_l指令)

(4) 在對位址修複分析時,首先需要了解一個elf動态庫重定位的知識點,先來看一個結構體定義:

typedef struct {
        Elf64_Addr      r_offset;
        Elf64_Xword     r_info;
        Elf64_Sxword    r_addend;
} Elf64_Rela;
           

當對動态庫進行位址重定向時首先會去查找rela.dyn段中的資訊,此段中每一組資訊就是上面結構體定義的資訊,對于64位系統的elf則是24位元組為一個表,r_offset儲存需要重定位作用的位址位置,r_info描述此表重定位類型此類型特定于處理器,如arm64位則是:

/* AArch64 relocs */
#define R_AARCH64_NONE		0	/* No relocation */
#define R_AARCH64_RELATIVE	1027	/* Adjust by program base */
           

r_addend是一個常量加數,用于計算存儲在可重定位字段中的值。

對表進行重定位有以下公式:

重定位值 = *(r_offset + 實際偏移值) = 實際偏移值 + r_addend。

根據此公式則可以重定向每一個需要重定向的位址值。

是以在此處同樣是如此,x0儲存r_offset值,x1儲存r_info值,x4儲存r_addend,并通過x1與R_AARCH64_RELATIVE比較看是否屬于aarch64相對位址類型,如果不是則不是需要重定向的一組表則跳到pie_skip_reloc判斷是否完成所有rela.dyn段重定向,如果是則繼續往下執行啟動流程初始化,如果不是則跳回pie_fix_loop繼續下一組表的重定向。當判斷屬于aarch64相對位址類型時進入重定向邏輯,首先需要知道x9是運作位址減去連結位址得到的偏移位址,那麼實際運作位址也等于連結位址加上x9,是以add x0, x0, x9,add x4, x4, x9,x0是需要修複的重定位運作位址,x4是實際運作時需要附加的常量值,根據上面的公式,将x4這個由附加常量值加偏移值得到的運作時附加常量值寫入到x0這個運作時重定向位址即可完成整個重定向修複功能。此處沒有實際代碼或者流程圖展示,邏輯比較繞,大緻邏輯就是根據重定向段中每24個位元組組成的一個表讀取出實際需要進行重定向的位址,将這個位址的運作位址找出來并往這個位址寫入附加常量值加偏移值即可完成一次重定向。在完成段中所有重定向後此時位址已經被修複好了,後續調用任何絕對位址引用的指令也不會出問題了。當然這個操作是耗時的,一般也不會有board會開啟此功能。

三、 進入_main之前系統寄存器初始化和從核的引導

在完成位址無關fixup後,u-boot開始對一些系統寄存器進行初始化

第一段代碼如下:

pie_fixup_done:
#endif

#ifdef CONFIG_SYS_RESET_SCTRL
	bl reset_sctrl --------------------------------------------------------------------- (1)
#endif

#if defined(CONFIG_ARMV8_SPL_EXCEPTION_VECTORS) || !defined(CONFIG_SPL_BUILD) ---------- (2)
.macro	set_vbar, regname, reg
	msr	\regname, \reg
.endm
	adr	x0, vectors
#else
.macro	set_vbar, regname, reg
.endm
#endif
	/*
	 * Could be EL3/EL2/EL1, Initial State:
	 * Little Endian, MMU Disabled, i/dCache Disabled
	 */
	switch_el x1, 3f, 2f, 1f ---------------------------------------------------------- (3)
3:	set_vbar vbar_el3, x0
	mrs	x0, scr_el3
	orr	x0, x0, #0xf			/* SCR_EL3.NS|IRQ|FIQ|EA */
	msr	scr_el3, x0
	msr	cptr_el3, xzr			/* Enable FP/SIMD */
	b	0f
2:	mrs	x1, hcr_el2
	tbnz	x1, #34, 1f			/* HCR_EL2.E2H */
	set_vbar vbar_el2, x0
	mov	x0, #0x33ff
	msr	cptr_el2, x0			/* Enable FP/SIMD */
	b	0f
1:	set_vbar vbar_el1, x0
	mov	x0, #3 << 20
	msr	cpacr_el1, x0			/* Enable FP/SIMD */
0:

#ifdef COUNTER_FREQUENCY -------------------------------------------------------------- (4)
	branch_if_not_highest_el x0, 4f
	ldr	x0, =COUNTER_FREQUENCY
	msr	cntfrq_el0, x0			/* Initialize CNTFRQ */
#endif

4:	isb ------------------------------------------------------------------------------- (5)

...
...
#ifdef CONFIG_SYS_RESET_SCTRL
reset_sctrl:
	switch_el x1, 3f, 2f, 1f
3:
	mrs	x0, sctlr_el3
	b	0f
2:
	mrs	x0, sctlr_el2
	b	0f
1:
	mrs	x0, sctlr_el1

0:
	ldr	x1, =0xfdfffffa
	and	x0, x0, x1

	switch_el x1, 6f, 5f, 4f
6:
	msr	sctlr_el3, x0
	b	7f
5:
	msr	sctlr_el2, x0
	b	7f
4:
	msr	sctlr_el1, x0

7:
	dsb	sy
	isb
	b	__asm_invalidate_tlb_all ----------------------------------------------------- (6)
	ret
#endif
           

(1)一般情況下此功能不需要使用,但是一些由其他固件引導啟動的u-boot,board希望系統行為能按照自己預期行為執行而不受上一級加載器的影響,是以使用CONFIG_SYS_RESET_SCTRL來決定是否重置系統控制寄存器,包括保證處理器處于小端,關閉data cache,關閉mmu。其中switch_el是一個宏,用于讀取目前所處的異常級别,根據所處異常級别調用對應的系統控制寄存器。某些時候u-boot的加載并不是一定在el3級别,當存在atf等時,el3由atf控制,atf會将u-boot的運作級别切換到el2,以便保證自己的控制級别,是以u-boot通過switch_el來選擇自己能夠控制的系統寄存器。

(2)定義設定異常向量表的宏,将異常向量表的位址寫入/reg設定的系統寄存器即可完成異常向量表的設定,這裡u-boot是需要設定異常向量表的,而spl預設是不需要設定異常向量表的,畢竟spl隻是一個加載器隻會運作一次,不過當定義了CONFIG_ARMV8_SPL_EXCEPTION_VECTORS時可以為spl也設定一個異常向量表。

(3)同樣的使用switch_el來跳轉到對應級别的路徑上去執行,在進行系統寄存器設定時,因為在這之前已經由SYS_RESET_SCTRL或者board自己保證處理器處于小端,mmu關,i-cache和d-cache處于關閉狀态了,是以這裡直接進行對應級别系統寄存器設定,首先是跳轉到對應表設定對應級别的異常向量表。接着會有如下三種情況:

當處于EL3時,會設定安全配置系統寄存器(scr_el3),會将低四位bit設定為0xf,表示設定處理器處于非安全模式,任何級别的實體irq中斷,實體fiq,異常abort中斷,異常SError中斷都将被路由到el3級别。後續這些設定将在啟動Linux時被修改,這些設定僅用于在u-boot階段。接着将cptr_el3清零,使用xzr是可以快速操作寄存器為零。這裡保證任何級别下通路SIMD和floating-point指令不會導緻觸發異常陷入el3。

當處于EL2時,首先根據HCR_EL2.E2H判斷系統是一個虛拟機管理器還是主機系統,當E2H = 0時,表示系統處于主機系統隻需要做el3一樣的操作配置SIMD和FP指令不會陷入el2即可。

當系統處于EL1時,則什麼也不需要操作隻需要配置SIMD和FP指令不會陷入el1。

(4)u-boot在啟動時系統的時鐘頻率不一定配置了,是以當在include/configs/xxxxxx.h中定義了COUNTER_FREQUENCY的頻率值時,說明需要在此處配置系統時鐘,是以根據宏 branch_if_not_highest_el判斷當系統不處于EL3時則需要設定系統的時鐘工作頻率cntfrq_el0,後續Linux或者u-boot根據讀出的這個值計算出系統每納秒的滴答數進而供軟體擷取時間流逝值。

(5)isb指令用于確定上述操作指令被正确真正的執行了,屬于同步指令的一種。

(6)在進行系統控制器複位時,dsb sy,isb,__asm_invalidate_tlb_all三個操作在這裡的意義是,因為對處理器的小端,mmu,d-cache進行了複位,是以這裡必須通過dsb和isb確定資料和指令全部執行和寫入,這裡進行了mmu和cache關閉操作,那麼如果有緩存的tlb在這個時候這些緩存的tlb資料就是無效的,這裡對可能緩存的tlb進行全部無效化,確定後續任何可能的mmu開啟操作不會使用到這些無用的tlb條目而導緻系統異常。

第二段代碼如下:

4:	isb

	/*
	 * Enable SMPEN bit for coherency.
	 * This register is not architectural but at the moment
	 * this bit should be set for A53/A57/A72.
	 */
#ifdef CONFIG_ARMV8_SET_SMPEN -------------------------------------------------------- (1)
	switch_el x1, 3f, 1f, 1f
3:
	mrs     x0, S3_1_c15_c2_1               /* cpuectlr_el1 */
	orr     x0, x0, #0x40
	msr     S3_1_c15_c2_1, x0
	isb
1:
#endif

	/* Apply ARM core specific erratas */
	bl	apply_core_errata ------------------------------------------------------------ (2)

	/*
	 * Cache/BPB/TLB Invalidate
	 * i-cache is invalidated before enabled in icache_enable()
	 * tlb is invalidated before mmu is enabled in dcache_enable()
	 * d-cache is invalidated before enabled in dcache_enable()
	 */

	/* Processor specific initialization */
	bl	lowlevel_init ---------------------------------------------------------------- (3)
...
...
WEAK(apply_core_errata)
...
...
WEAK(lowlevel_init)
	mov	x29, lr			/* Save LR */

#if defined(CONFIG_GICV2) || defined(CONFIG_GICV3) ----------------------------------- (4)
	branch_if_slave x0, 1f
	ldr	x0, =GICD_BASE
	bl	gic_init_secure
1:
#if defined(CONFIG_GICV3)
	ldr	x0, =GICR_BASE
	bl	gic_init_secure_percpu
#elif defined(CONFIG_GICV2)
	ldr	x0, =GICD_BASE
	ldr	x1, =GICC_BASE
	bl	gic_init_secure_percpu
#endif
#endif

#ifdef CONFIG_ARMV8_MULTIENTRY ------------------------------------------------------ (5)
	branch_if_master x0, x1, 2f

	/*
	 * Slave should wait for master clearing spin table.
	 * This sync prevent salves observing incorrect
	 * value of spin table and jumping to wrong place.
	 */
#if defined(CONFIG_GICV2) || defined(CONFIG_GICV3)
#ifdef CONFIG_GICV2
	ldr	x0, =GICC_BASE
#endif
	bl	gic_wait_for_interrupt
#endif

	/*
	 * All slaves will enter EL2 and optionally EL1.
	 */
	adr	x4, lowlevel_in_el2
	ldr	x5, =ES_TO_AARCH64
	bl	armv8_switch_to_el2

lowlevel_in_el2:
#ifdef CONFIG_ARMV8_SWITCH_TO_EL1
	adr	x4, lowlevel_in_el1
	ldr	x5, =ES_TO_AARCH64
	bl	armv8_switch_to_el1

lowlevel_in_el1:
#endif

#endif /* CONFIG_ARMV8_MULTIENTRY */

2:
	mov	lr, x29			/* Restore LR */
	ret
ENDPROC(lowlevel_init)
           

(1)首先解釋S3_1_c15_c2_1,有一些架構定義系統寄存器是不能被編譯器識别的,如果要通路則隻能直接通過如下定義的形式被ARM彙編器識别并編譯成二進制:S3__ 。這裡使用S3_1_C15_C2_0,則不需要将編碼交給彙程式設計式,如果直接使用對應的CPUACTLR_EL1則必須把它的編碼交給彙程式設計式,而由于arm彙程式設計式并不識别此系統器進而指導編譯報錯退出。這裡是當定義了CONFIG_ARMV8_SET_SMPEN并處于EL3級别時設定cpuectlr_el1的SMPEN位用于開啟多核之間的資料一緻性功能,當然這個功能是因為設計的問題導緻隻是一些a系列處理器需要設定,如a53,a57,a72。

(2)因為一些基于armv8架構設計的處理器本身會存在一些bug,是以這裡對特定處理器進行勘誤設定,相當于打更新檔的意思,感興趣的可以去看看每個a系列有哪些勘誤需要被設定。

(3)低平台初始化,這裡可以在彙編階段對對應平台board進行初始化,lowlevel_init是一個弱符号,可以由廠商自己實作,這裡分析armv8的标準lowlevel_init進行的初始化。(4)中詳述。

(4)首先是對GIC的一些初始化,同樣的需要根據使用的gic版本在Kconfig中選中CONFIG_GICV2或者CONFIG_GICV3,當定義了這兩個宏其中一個時就會對gic進行相應初始化。

當定義的是GICV2時,則需要在include/configs/xxxxx.h中定義GICD_BASE和GICC_BASE,分别說明GIC的分發器基位址和cpu接口寄存器基位址。

當定義的是GICV3時,則需要在include/configs/xxxxx.h中定義GICD_BASE,說明GIC的主分發器基位址,因為cpu接口在使用gicv3時可通過armv8拓展的系統寄存器接口通路,是以這裡不用定義cpu接口的基位址,gicv3使用系統寄存器通路cpu接口的原因,主要是因為cpu對gic cpu接口寄存器的通路是頻繁的,為了少一些對系統總線的通路,直接在v3中使用系統寄存器通路,大大提高了cpu接口的讀寫速度,提高整體性能。

當是boot cpu執行時則會調用gic_init_secure對應安全組等相關的初始化,具體代碼在arch/arm/lib/gic_64.S中,這裡不展開隻說明具體做了哪些事情:

如果是gicv3則主要是對分發器的初始化:激活group0,激活非安全的group1,激活安全的group1,激活安全和非安全的親和性路由(綁定中斷到某些cpu功能)。讀取支援的最大spi中斷數量,并配置。

如果是gicv2則首先對分發器進行初始化,激活group0和group1,和讀取spi的支援數并配置。接着初始化cpu接口寄存器,激活group0和group1,并配置允許非安全模式下對GICC_PMR的通路,GICC_PMR是中斷優先級的配置,在linux上沒有對優先級有過多使用,隻有一次性配置。

當是從核cpu執行到這裡時則會根據branch_if_slave跳轉到1标号處進行percpu的gic初始化,branch_if_slave的實作是讀取cpu各自的辨別符寄存器mpidr_el1擷取自己的簇号進行區分的,細節可以檢視此系統寄存器的描述。

同樣的對percpu的gic初始化是gic_init_secure_percpu,意思就是每個cpu都要進行相應的設定,比如gicv3會對每個cpu的辨別進行識别,以便精确ppi中斷的分發,是以會将每個cpu的mpidr_el1值寫入對應的每個cpu的重分發器裡,是以稱為percpu初始化,這也是gicv3能支援到128個cpu的原因,而gicv2隻能支援8個cpu核心,具體實作可看arch/arm/lib/gic_64.S。

(這裡說一個之前遇到的bug,在進行bring up時,linux無法産生任何中斷,包括核間中斷,後續發現就是u-boot中這段初始化沒有調用,導緻沒有配置中斷在非安全狀态下路由到哪個group,是以即便在el1或者el0産生了中斷,中斷也被路由到了group0,而linux的gic設定為:隻處理非安全狀态下的group1中斷,是以linux無法産生中斷)

(5)u-boot雖然是運作單個cpu的程式,但也允許從核cpu進入u-boot,這裡典型的例子就是使用SPIN_TABLE方式啟動從核在u-boot中自選等待進入linux。當要使用spin_table時則必須開啟CONFIG_ARMV8_MULTIENTRY 選項,用于讓從核進入u-boot進行基本初始化。首先如果是主核自然不用走從核流程則直接在這裡傳回完成lowlevel_init的調用。如果是從核并且定義了gicv2或者gicv3則首先是當從核進入u-boot後,進入gic_wait_for_interrupt使用wfi休眠,等待主核的事件喚醒,也就是smp_kick_all_cpus操作,讓從核cpu可以繼續往下執行。bl armv8_switch_to_el2因為u-boot處于el3級别時,當啟動linux時會将異常級别降低到el2或者el1來啟動linux,根據具體設定來切換,而處于el3時則會将異常級别切換到el2,是以這裡的操作是與主核一緻,從核要進入linux,首先就是要将自己的異常級别從el3切換到el2。是否真實的能切到el2還要根據自己目前的級别,如果已經是el1了自然無法切換到el2。

當然如果定義了CONFIG_ARMV8_SWITCH_TO_EL1,說明還得切換從核到el1,這裡将從核切換到el1級别去。

第三段代碼如下:

#if defined(CONFIG_ARMV8_SPIN_TABLE) && !defined(CONFIG_SPL_BUILD) --------------------- (1)
	branch_if_master x0, x1, master_cpu
	b	spin_table_secondary_jump
	/* never return */
#elif defined(CONFIG_ARMV8_MULTIENTRY)
	branch_if_master x0, x1, master_cpu

	/*
	 * Slave CPUs
	 */
slave_cpu: ----------------------------------------------------------------------------- (2)
	wfe
	ldr	x1, =CPU_RELEASE_ADDR
	ldr	x0, [x1]
	cbz	x0, slave_cpu
	br	x0			/* branch to the given address */
#endif /* CONFIG_ARMV8_MULTIENTRY */
master_cpu:
	bl	_main

... /* arch/arm/cpu/armv8/spin_table_v8.S */
ENTRY(spin_table_secondary_jump) ------------------------------------------------------ (3)
.globl spin_table_reserve_begin
spin_table_reserve_begin:
0:	wfe
	ldr	x0, spin_table_cpu_release_addr
	cbz	x0, 0b
	br	x0
.globl spin_table_cpu_release_addr
	.align	3
spin_table_cpu_release_addr:
	.quad	0
.globl spin_table_reserve_end
spin_table_reserve_end:
ENDPROC(spin_table_secondary_jump)
           

(1)首先是當定義了CONFIG_ARMV8_SPIN_TABLE啟動從核時并且不處于SPL中,spl是不需要啟動從核的。當是主核運作時直接跳轉到_main離開系統寄存器初始化,開始下一段board_f等的u-boot初始化流程,如果是從核則會跳轉到spin_table_secondary_jump(3),在這裡,從核cpu将會開始自旋,使用wfe指令休眠,并在接收到中斷或者sev事件時醒來并讀取spin_table_cpu_release_addr位址裡的值,如果是0說明并不是引導進入linux的事件發生則繼續調用wfe休眠等待喚醒,如果spin_table_cpu_release_addr位址裡不是0說明這時候已經由linux喚醒了,從核需要進入linux開始下一段旅程,則直接跳轉到指定的位址裡去繼續執行。spin_table_cpu_release_addr位址将會在u-boot加載linux的裝置樹時直接寫入到linux使用的裝置樹的對應節點中去,以便不需要由人為指定位址,linux和u-boot即可完成從核啟動的交接,這就是spin_table的工作原理。

(2)當不是spin_table啟動從核時,說明這是由使用者定義需要在u-boot或者其他地方使用從核,是以直接通過CPU_RELEASE_ADDR宏定義了一個跳轉位址的存儲位址,在使用者任何時候需要從核執行時往CPU_RELEASE_ADDR位址寫入對應要跳轉的位址後開始執行從核。

綜上在完成系統寄存器配置和從核引導啟動後,u-boot将會跳轉至_main離開系統寄存的初始化,開始u-boot本身的初始化。

繼續閱讀