天天看點

ARMV8_AARCH64裸機啟動流程分析

本篇針對于ARMV8架構下64位模式的裸機啟動代碼流程分析,重點介紹裸機啟動代碼bootcode.s。

重點了解ARM處理器從上電到跳轉到main函數的過程!!!

1、寄存器初始化

首先我們需要初始化相關寄存器:

  • 通用寄存器;
  • SP寄存器(stack pointer);
  • System control registers.(控制寄存器)。

1.1、初始化通用寄存器

ARM處理器使用一些非複位觸發器。這可能會導緻模拟中出現X傳播問題。寄存器初始化有助于減少問題的可能性。寄存器的低32位用w來表示。

注:由于X狀态僅存在于硬體模拟中,是以在矽晶片上不需要此初始化。

對寄存器的初始化代碼如代碼1-1所示:

/*code1-1: Initialize the stack pointer.*/
MOV  X0, XZR
…………………
MOV  X30, XZR
           
  • 1
  • 2
  • 3
  • 4

如果處理器實作NEON和FP擴充功能,浮點寄存器也必須初始化。

/*code1-2: Floating-point registers initialization*/
MSR CPTR_EL3, XZR 
MSR CPTR_EL2, XZR 
FMOV D0, XZR 
FMOV D1, XZR
。。。。。。。。
FMOV D31, XZR
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1.2、初始化SP寄存器

堆棧指針(sp)寄存器隐式地用于某些指令中,例如push和pop。在使用它之前,必須使用正确的值進行初始化。

在MPCore系統中,不同的堆棧指針必須指向不同的記憶體位址,以避免覆寫堆棧區域。如果使用不同異常級别的SP,則必須初始化它們。

示例1-3顯示了為目前異常級别初始化SP,SP指向的堆棧位于堆棧頂部,堆棧大小CPU_STACK_SIZE。

/*code1-3: Initialize the stack pointer.*/
ADR X1, stack_top 
ADD X1, X1, #4 
MRS X2, MPIDR_EL1 
AND X2, X2, #0xFF  // X2 == CPU number. 
MOV X3, #CPU_STACK_SIZE 
MUL X3, X2, X3 // Create separated stack spaces 
SUB X1, X1, X3 // for each processor
MOV SP, X1
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.3、初始化系統控制寄存器

某些系統控制寄存器沒有複位值。是以,在使用寄存器之前,必須根據軟體要求對其進行初始化。

代碼1-4展示了在複位之後如何初始化HCR_EL2,SCTLR_EL1和SCTLR_EL1。

/*code1-4:Example 5-9 System control registers initialization*/
MSR HCR_EL2, XZR
LDR X1, =0x30C50838 
MSR SCTLR_EL2, X1 
MSR SCTLR_EL1, X1
           
  • 1
  • 2
  • 3
  • 4
  • 5

本例不包括所有需要初始化的系統寄存器。理論上,必須初始化所有沒有定義重置值的系統寄存器。但是,根據特定處理器的實作,某些寄存器的值已經提前定義好了,更多的細節見ARMV8手冊。寄存器具體需要配置什麼值,請看armV8手冊,根據自己的需要去配置。

2、異常初始化

異常初始化要求:

  • 設定異常向量表
  • 異步異常路由和屏蔽配置

2.1、設定異常向量表

在AArch64中,複位向量不再是異常向量表的一部分,它有專門的配置輸入引腳和複位向量寄存器。其他異常向量存儲在向量表中。

複位向量

在AArch64中,處理器從IMPLEMENTAION-DEFINED的位址開始執行,該位址由硬體輸入引腳RVBARADDR定義,可由RVBAR_EL3寄存器讀取。必須在此位址放置引導代碼(bootcode)。

異常向量表

每個異常級别都有專用的向量表:

  • VBAR_EL3.
  • VBAR_EL2.
  • VBAR_EL1.

AArch64中的向量表與AArch32中的向量表不同。AArch64模式下的向量表包含16個入口位址。每個入口位址的大小為128B,最多包含32條指令。向量表必須放置在2KB對齊的位址。通過初始化VBAR_ELn寄存器來設定位址。關于更多的異常向量表的細節,請檢視《ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile》手冊。

下表展示了異常向量表的結構圖:

ARMV8_AARCH64裸機啟動流程分析

2-1代碼示例展示了在複位之後如何初始化VBAR_EL3, VBAR_EL2, and VBAR_EL1。

/*code:2-1 Vector Base Address registers*/
// Initialize VBAR_EL3. 
LDR X1, = vector_table_el3 
MSR VBAR_EL3, X1 
LDR X1, = vector_table_el2 
MSR VBAR_EL2, X1 
LDR X1, = vector_table_el1 
MSR VBAR_EL1, X1
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

示例2-2展示了AArch64中的典型異常向量表代碼。

/*code2-2: Typical exception vector table code.*/ 
.balign 0x800 
Vector_table_el3: 
curr_el_sp0_sync: // The exception handler for the synchronous // exception from the current EL using SP0. 
.balign 0x80 
curr_el_sp0_irq: // The exception handler for the IRQ exception // from the current EL using SP0.
.balign 0x80 
curr_el_sp0_fiq: // The exception handler for the FIQ exception // from the current EL using SP0.
 .balign 0x80 
curr_el_sp0_serror: // The exception handler for the system error // exception from the current EL using SP0. 
.balign 0x80 
curr_el_spx_sync: // The exception handler for the synchronous // exception from the current EL using the // current SP. 
.balign 0x80 
curr_el_spx_irq: // The exception handler for IRQ exception // from the current EL using the current SP.
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.2、啟用異步異常

異步異常包括SError、IRQ和FIQ,它們在複位後預設被屏蔽。是以,如果要啟用SError、IRQ和FIQ的話,必須設定路由規則并清除屏蔽設定。

要啟用中斷,還必須初始化外部中斷,以将中斷傳遞給處理器,但本文檔中沒有介紹中斷的相關知識。有關中斷知識可以去官網下載下傳GIC相關文檔。

Asynchronous exceptions routing(異步異常路由)

異步異常路由确定用于處理同步異常的異常級别。

要将異步異常路由到EL3,必須設定SCR_EL3.{EA,IRQ,FIQ}。

下面2-3的例子展示了如何把SError、IRQ和FIQ異步異常事件路由到EL3。

code2-3: SError, IRQ and FIQ routing enablement in EL3
MRS X0, SCR_EL3
ORR X0, X0, #(1<<3)  // The EA bit.
ORR X0, X0, #(1<<1)  //The IRQ bit.
ORR X0, X0, #(1<<2)  // The FIQ bit.
MSR SCR_EL3, X0
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果你想把異步異常路由到EL2,你必須設定HCR_EL2.{AMO,FMO,IMO},并且清除SCR_EL3.{EA,IRQ,FIQ}.

下面2-4的例子展示了如何把SError、IRQ和FIQ異步異常事件路由到EL2。

MRS X0, HCR_EL2 
ORR X0, X0, #(1<<5) // The AMO bit.
ORR X0, X0, #(1<<4) // The IMO bit.
ORR X0, X0, #(1<<3) // The FMO bit.
MSR HCR_EL2, X0
           
  • 1
  • 2
  • 3
  • 4
  • 5

如果中斷沒有路由到EL2和EL3,那麼就預設路由到EL1。

Asynchronous exceptions mask(異步異常屏蔽)

異步異常是否被屏蔽取決于以下因素:

  • 中斷路由到的目标異常級别;
  • PSTATE{A,I,F}的值

當目标異常級别低于目前異常級别時,無論PSTATE.{A,I,F}值如何,異步異常都會被隐式屏蔽。

當目标異常級别與目前異常級别相同時,如果PSTATE.{A,I,F}為1,則會屏蔽異步異常。

當目标異常級别高于目前異常級别且目标異常級别為EL2或EL3時,不管PSTATE.{A,I,F}值是多少,都會執行異步異常。

當目标異常級别高于目前異常級别且目标異常級别為EL1時,如果PSTATE.{A,I,F}為1,則會屏蔽異步異常。

示例2-5顯示了如何在PSTATE中清除SError、IRQ和FIQ屏蔽位。

// code2-5:Enable SError, IRQ and FIQ 
MSR DAIFClr, #0x7
           
  • 1
  • 2

3、配置MMU和Cache

配置MMU和Cache需要以下步驟:

  • 清理并使緩存無效
  • 設定MMU
  • 使能MMU和Cache

3.1、清理并使緩存無效

系統複位後,cache中的内容無效。ARMv8-A處理器實作的硬體在複位後自動使所有cache失效,是以複位後不需要軟體清除。但是,在某些情況下,清理和使資料緩存無效仍然是必要的(資料cache),例如核心斷電過程。

示例3-1展示了如何使用EL3中的循環DC CISW指令清除一級資料緩存(L1 date cache)并使其失效。由此可以輕松修改其他級别緩存或其他緩存操作的代碼。

/*code3-1: Clean and invalidate L1 data cache*/
// Disable L1 Caches 
MRS X0, SCTLR_EL3 // Read SCTLR_EL3. 
BIC X0, X0, #(0x1 << 2) // Disable D Cache. 
MSR SCTLR_EL3, X0 // Write SCTLR_EL3. 
           

// Invalidate Data cache to make the code general purpose.

// Calculate the cache size first and loop through each set +

// way.

MOV X0, #0x0 // X0 = Cache level

MSR CSSELR_EL1, x0 // 0x0 for L1 Dcache 0x2 for L2 Dcache. (每個核有兩級cache,L3)

MRS X4, CCSIDR_EL1 // Read Cache Size ID.

AND X1, X4, #0x7

ADD X1, X1, #0x4 // X1 = Cache Line Size.

LDR X3, =0x7FFF

AND X2, X3, X4, LSR #13 // X2 = Cache Set Number – 1.

LDR X3, =0x3FF

AND X3, X3, X4, LSR #3 // X3 = Cache Associativity Number – 1.

CLZ W4, W3 // X4 = way position in the CISW instruction.

MOV X5, #0 // X5 = way counter way_loop.

way_loop:

MOV X6, #0 // X6 = set counter set_loop.

set_loop:

LSL X7, X5, X4

ORR X7, X0, X7 // Set way.

LSL X8, X6, X1

ORR X7, X7, X8 // Set set.

DC cisw, X7 // Clean and Invalidate cache line.

ADD X6, X6, #1 // Increment set counter.

CMP X6, X2 // Last set reached yet?

BLE set_loop // If not, iterate set_loop,

ADD X5, X5, #1 // else, next way.

CMP X5, X3 // Last way reached yet?

BLE way_loop // If not, iterate way_loop.

ARMV8_AARCH64裸機啟動流程分析
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

3.2、設定MMU

ARMv8-A處理器使用VMSAv8-64在AArch64上執行以下操作:

  • 實體位址到虛拟位址的轉換;
  • 确定記憶體屬性并檢查通路權限。

位址轉換由轉換表定義并由MMU管理。每個異常級别都有一個專用的轉換頁表。啟用MMU之前必須設定轉換表。

VMSAv8-64在轉換表中使用64位描述符格式。它支援:

  • 最多48位輸入和輸出位址;
  • 三種顆粒度:4KB,16KB,64KB;
  • 最多四個級别的位址查找。

示例3-2和示例3-3建構一個EL3頁表,其粒度為4KB,覆寫4GB記憶體空間:

  • 0~1GB記憶體被配置為正常緩存的記憶體;
  • 1~4GB記憶體配置為Device-nGnRnE記憶體。

轉換表包含512個2MB大小的level2塊和3個1GB大小的level1塊。

示例3-2首先初始化轉換表控制寄存器,然後使用循環存儲指令建構轉換表,這更易于移植。

/*3-2*/
// Initialize translation table control registers
LDR X1, =0x3520 // 4GB space 4KB granularity
// Inner-shareable.
MSR TCR_EL3, X1 // Normal Inner and Outer Cacheable.
LDR X1, =0xFF440400 // ATTR0 Device-nGnRnE ATTR1 Device.
MSR MAIR_EL3, X1 // ATTR2 Normal Non-Cacheable.
					// ATTR3 Normal Cacheable.
ADR X0, ttb0_base  // ttb0_base must be a 4KB-aligned address.
MSR TTBR0_EL3, X0
// Set up translation table entries in memory with looped store
// instructions.
// Set the level 1 translation table.
// The first entry points to level2_pagetable.
LDR X1, = level2_pagetable // Must be a 4KB align address.
LDR X2, =0xFFFFF000
AND X2, X1, X2 // NSTable=0 APTable=0 XNTable=0 PXNTable=0.
ORR X2, X2, 0x3
STR X2, [X0], #8
// The second entry is 1GB block from 0x40000000 to 0x7FFFFFFF.
LDR X2, =0x40000741 // Executable Inner and Outer Shareable.
STR X2, [X0], #8 // R/W at all ELs secure memory
// AttrIdx=000 Device-nGnRnE.
// The third entry is 1GB block from 0x80000000 to 0xBFFFFFFF.
LDR X2, =0x80000741
STR X2, [X0], #8
// The fourth entry is 1GB block from 0xC0000000 to 0xFFFFFFFF.
LDR X2, =0xC0000741
STR X2, [X0], #8
// Set level 2 translation table.
LDR X0, =level2_pagetable // Base address of level2_pagetable.
LDR X2, =0x0000074D // Executable Inner and Outer Shareable.
// R/W at all ELs secure memory.
// AttrIdx=011 Normal Cacheable.
MOV X4, #512 // Set 512 level2 block entries.
LDR X5, =0x00200000 // Increase 2MB address each time.
loop:
STR X2, [X0], #8 // Each entry occupies 2 words.
ADD X2, X2, X5
SUBS X4, X4, #1
BNE loop
             
ARMV8_AARCH64裸機啟動流程分析
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

示例3-3在編譯時建立一個段作為轉換表。這種方法對于仿真是快速的。它是用GNU彙編文法編寫的。示例5-11中初始化轉換表控制寄存器的代碼仍然是必需的。

/*code3-3*/
// Put a 64-bit value with little endianness.
.macro PUT_64B high, low
.word \low
.word \high
.endm
// Create an entry pointing to a next-level table.
.macro TABLE_ENTRY PA, ATTR
PUT_64B \ATTR, (\PA) + 0x3
.endm
// Create an entry for a 1GB block.
.macro BLOCK_1GB PA, ATTR_HI, ATTR_LO
PUT_64B \ATTR_HI, ((\PA) & 0xC0000000) | \ATTR_LO | 0x1
.endm
// Create an entry for a 2MB block.
.macro BLOCK_2MB PA, ATTR_HI, ATTR_LO
PUT_64B \ATTR_HI, ((\PA) & 0xFFE00000) | \ATTR_LO | 0x1
.endm
.align 12 // 12 for 4KB granule.
ttb0_base:
TABLE_ENTRY level2_pagetable, 0
BLOCK_1GB 0x40000000, 0, 0x740
BLOCK_1GB 0x80000000, 0, 0x740
BLOCK_1GB 0xC0000000, 0, 0x740
.align 12 // 12 for 4KB granule.
level2_pagetable:
.set ADDR, 0x000 // The current page address.
.rept 0x200
BLOCK_2MB (ADDR << 20), 0, 0x74C
.set ADDR, ADDR+2
.endr
             
ARMV8_AARCH64裸機啟動流程分析
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

3.3、啟動MMU和cache

必須先初始化MMU和cache,然後才能啟用它們。在啟用MMU和緩存以支援硬體之前,所有ARMv8-A處理器都需要設定SMPEN位一緻性。

示例3-4顯示了如何設定SMPEN位并啟用MMU和cache。

/*code3-4:*/
// It is implemented in the CPUECTLR register.
MRS X0, S3_1_C15_C2_1
ORR X0, X0, #(0x1 << 6) // The SMP bit.
MSR S3_1_C15_C2_1, X0
// Enable caches and the MMU.
MRS X0, SCTLR_EL3
ORR X0, X0, #(0x1 << 2) // The C bit (data cache).
ORR X0, X0, #(0x1 << 12) // The I bit (instruction cache).
ORR X0, X0, #0x1 // The M bit (MMU).
MSR SCTLR_EL3, X0
DSB SY
ISB
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4、啟動NEON 和浮點單元

在AArch64中,您不需要啟用對NEON和FP寄存器的通路。然而,對NEON和FP寄存器的通路仍然可能被捕獲。

示例4-1顯示了如何在所有異常級别禁用對NEON和FP寄存器的通路捕獲。

/*4-1*/
// Disable trapping of accessing in EL3 and EL2. 
MSR CPTR_EL3, XZR 
MSR CPTR_EL3, XZR 
           

// Disable access trapping in EL1 and EL0.

MOV X1, #(0x3 << 20) // FPEN disables trapping to EL1.

MSR CPACR_EL1, X1

ISB

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5、切換異常等級

ARMV8具有4種異常等級:

  • EL0:使用者等級
  • EL1:作業系統核心
  • EL2:Hypervisor
  • EL3:安全世界狀态核正常世界的切換

有時,必須在測試用例中的這些異常級别之間進行切換。當發生或傳回異常時,處理器會切換異常級别。

5.1、EL3到EL0之間的切換

處理器在複位後進入EL3。未定義較低異常級别的控制寄存器和異常狀态。要進入較低的異常級别,必須初始化執行狀态和控制寄存器,然後通過執行ERET指令使用僞異常傳回。見代碼5-1:

/*code5-1*/
// Initialize SCTLR_EL2 and HCR_EL2 to save values before entering EL2.
MSR SCTLR_EL2, XZR
MSR HCR_EL2, XZR
// Determine the EL2 Execution state.
MRS X0, SCR_EL3
ORR X0, X0, #(1<<10) // RW EL2 Execution state is AArch64.
ORR X0, X0, #(1<<0) // NS EL1 is Non-secure world.
MSR SCR_EL3, x0
MOV X0, #0b01001 // DAIF=0000
MSR SPSR_EL3, X0 // M[4:0]=01001 EL2h must match SCR_EL3.RW
// Determine EL2 entry.
ADR X0, el2_entry // el2_entry points to the first instruction of
MSR ELR_EL3, X0 // EL2 code.
ERET
el2_entry:
// Initialize the SCTLR_EL1 register before entering EL1.
MSR SCTLR_EL1, XZR
// Determine the EL1 Execution state.
MRS X0, HCR_EL2
ORR X0, X0, #(1<<31) // RW=1 EL1 Execution state is AArch64.
MSR HCR_EL2, X0
MOV X0, #0b00101 // DAIF=0000
MSR SPSR_EL2, X0 // M[4:0]=00101 EL1h must match HCR_EL2.RW.
ADR X0, el1_entry // el1_entry points to the first instruction of
MSR ELR_EL2, X0 // EL1 code.
ERET
el1_entry:
// Determine the EL0 Execution state.
MOV X0, #0b00000 // DAIF=0000 M[4:0]=00000 EL0t.
MSR SPSR_EL1, X0
ADR x0, el0_entry // el1_entry points to the first instruction of
MSR ELR_EL1, X0 // EL0 code.
ERET
el0_entry:
// EL0 code here.
             
ARMV8_AARCH64裸機啟動流程分析
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

5.2、EL2到EL1之間的切換

可以在不同的異常級别混合執行狀态。當較高的異常級别使用AArch64時,較低的異常級别可以使用AArch64或AArch32。是以,可以從AArch64中較高的異常級别更改為AArch32中較低的異常級别。見代碼5-2:

/*5-2*/
// Initialize the SCTLR_EL1 register before entering EL1.
MSR SCTLR_EL1, XZR
MRS X0, HCR_EL2
BIC X0, X0, #(1<<31) // RW=0 EL1 Execution state is AArch32.
MSR HCR_EL2, X0
MOV X0, #0b10011 // DAIF=0000
MSR SPSR_EL2, X0 // M[4:0]=10011 EL1 is SVC mode must match HCR_EL2.RW.
// Determine EL1 Execution state.
ADR X0, el1_entry  // el1_entry points to the first instruction of SVC
MSR ELR_EL2, X0 // mode code.
ERET
el1_entry:
// EL1 code here.
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

備注:

在多核啟動的時候,每個核都會去執行一段公共代碼(boot.s)去判斷自己是否為CPU0核(mpidr_el1有辨別号),如果不是則進入WFI休眠狀态,如果是CPU0核測執行相應的程式。

參考文獻

《Bare-metal Boot Code for ARMv8-A Processors》

本篇針對于ARMV8架構下64位模式的裸機啟動代碼流程分析,重點介紹裸機啟動代碼bootcode.s。

重點了解ARM處理器從上電到跳轉到main函數的過程!!!

1、寄存器初始化

首先我們需要初始化相關寄存器:

  • 通用寄存器;
  • SP寄存器(stack pointer);
  • System control registers.(控制寄存器)。

1.1、初始化通用寄存器

ARM處理器使用一些非複位觸發器。這可能會導緻模拟中出現X傳播問題。寄存器初始化有助于減少問題的可能性。寄存器的低32位用w來表示。

注:由于X狀态僅存在于硬體模拟中,是以在矽晶片上不需要此初始化。

對寄存器的初始化代碼如代碼1-1所示:

/*code1-1: Initialize the stack pointer.*/
MOV  X0, XZR
…………………
MOV  X30, XZR
           
  • 1
  • 2
  • 3
  • 4

如果處理器實作NEON和FP擴充功能,浮點寄存器也必須初始化。

/*code1-2: Floating-point registers initialization*/
MSR CPTR_EL3, XZR 
MSR CPTR_EL2, XZR 
FMOV D0, XZR 
FMOV D1, XZR
。。。。。。。。
FMOV D31, XZR
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1.2、初始化SP寄存器

堆棧指針(sp)寄存器隐式地用于某些指令中,例如push和pop。在使用它之前,必須使用正确的值進行初始化。

在MPCore系統中,不同的堆棧指針必須指向不同的記憶體位址,以避免覆寫堆棧區域。如果使用不同異常級别的SP,則必須初始化它們。

示例1-3顯示了為目前異常級别初始化SP,SP指向的堆棧位于堆棧頂部,堆棧大小CPU_STACK_SIZE。

/*code1-3: Initialize the stack pointer.*/
ADR X1, stack_top 
ADD X1, X1, #4 
MRS X2, MPIDR_EL1 
AND X2, X2, #0xFF  // X2 == CPU number. 
MOV X3, #CPU_STACK_SIZE 
MUL X3, X2, X3 // Create separated stack spaces 
SUB X1, X1, X3 // for each processor
MOV SP, X1
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.3、初始化系統控制寄存器

某些系統控制寄存器沒有複位值。是以,在使用寄存器之前,必須根據軟體要求對其進行初始化。

代碼1-4展示了在複位之後如何初始化HCR_EL2,SCTLR_EL1和SCTLR_EL1。

/*code1-4:Example 5-9 System control registers initialization*/
MSR HCR_EL2, XZR
LDR X1, =0x30C50838 
MSR SCTLR_EL2, X1 
MSR SCTLR_EL1, X1
           
  • 1
  • 2
  • 3
  • 4
  • 5

本例不包括所有需要初始化的系統寄存器。理論上,必須初始化所有沒有定義重置值的系統寄存器。但是,根據特定處理器的實作,某些寄存器的值已經提前定義好了,更多的細節見ARMV8手冊。寄存器具體需要配置什麼值,請看armV8手冊,根據自己的需要去配置。

2、異常初始化

異常初始化要求:

  • 設定異常向量表
  • 異步異常路由和屏蔽配置

2.1、設定異常向量表

在AArch64中,複位向量不再是異常向量表的一部分,它有專門的配置輸入引腳和複位向量寄存器。其他異常向量存儲在向量表中。

複位向量

在AArch64中,處理器從IMPLEMENTAION-DEFINED的位址開始執行,該位址由硬體輸入引腳RVBARADDR定義,可由RVBAR_EL3寄存器讀取。必須在此位址放置引導代碼(bootcode)。

異常向量表

每個異常級别都有專用的向量表:

  • VBAR_EL3.
  • VBAR_EL2.
  • VBAR_EL1.

AArch64中的向量表與AArch32中的向量表不同。AArch64模式下的向量表包含16個入口位址。每個入口位址的大小為128B,最多包含32條指令。向量表必須放置在2KB對齊的位址。通過初始化VBAR_ELn寄存器來設定位址。關于更多的異常向量表的細節,請檢視《ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile》手冊。

下表展示了異常向量表的結構圖:

ARMV8_AARCH64裸機啟動流程分析

2-1代碼示例展示了在複位之後如何初始化VBAR_EL3, VBAR_EL2, and VBAR_EL1。

/*code:2-1 Vector Base Address registers*/
// Initialize VBAR_EL3. 
LDR X1, = vector_table_el3 
MSR VBAR_EL3, X1 
LDR X1, = vector_table_el2 
MSR VBAR_EL2, X1 
LDR X1, = vector_table_el1 
MSR VBAR_EL1, X1
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

示例2-2展示了AArch64中的典型異常向量表代碼。

/*code2-2: Typical exception vector table code.*/ 
.balign 0x800 
Vector_table_el3: 
curr_el_sp0_sync: // The exception handler for the synchronous // exception from the current EL using SP0. 
.balign 0x80 
curr_el_sp0_irq: // The exception handler for the IRQ exception // from the current EL using SP0.
.balign 0x80 
curr_el_sp0_fiq: // The exception handler for the FIQ exception // from the current EL using SP0.
 .balign 0x80 
curr_el_sp0_serror: // The exception handler for the system error // exception from the current EL using SP0. 
.balign 0x80 
curr_el_spx_sync: // The exception handler for the synchronous // exception from the current EL using the // current SP. 
.balign 0x80 
curr_el_spx_irq: // The exception handler for IRQ exception // from the current EL using the current SP.
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.2、啟用異步異常

異步異常包括SError、IRQ和FIQ,它們在複位後預設被屏蔽。是以,如果要啟用SError、IRQ和FIQ的話,必須設定路由規則并清除屏蔽設定。

要啟用中斷,還必須初始化外部中斷,以将中斷傳遞給處理器,但本文檔中沒有介紹中斷的相關知識。有關中斷知識可以去官網下載下傳GIC相關文檔。

Asynchronous exceptions routing(異步異常路由)

異步異常路由确定用于處理同步異常的異常級别。

要将異步異常路由到EL3,必須設定SCR_EL3.{EA,IRQ,FIQ}。

下面2-3的例子展示了如何把SError、IRQ和FIQ異步異常事件路由到EL3。

code2-3: SError, IRQ and FIQ routing enablement in EL3
MRS X0, SCR_EL3
ORR X0, X0, #(1<<3)  // The EA bit.
ORR X0, X0, #(1<<1)  //The IRQ bit.
ORR X0, X0, #(1<<2)  // The FIQ bit.
MSR SCR_EL3, X0
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果你想把異步異常路由到EL2,你必須設定HCR_EL2.{AMO,FMO,IMO},并且清除SCR_EL3.{EA,IRQ,FIQ}.

下面2-4的例子展示了如何把SError、IRQ和FIQ異步異常事件路由到EL2。

MRS X0, HCR_EL2 
ORR X0, X0, #(1<<5) // The AMO bit.
ORR X0, X0, #(1<<4) // The IMO bit.
ORR X0, X0, #(1<<3) // The FMO bit.
MSR HCR_EL2, X0
           
  • 1
  • 2
  • 3
  • 4
  • 5

如果中斷沒有路由到EL2和EL3,那麼就預設路由到EL1。

Asynchronous exceptions mask(異步異常屏蔽)

異步異常是否被屏蔽取決于以下因素:

  • 中斷路由到的目标異常級别;
  • PSTATE{A,I,F}的值

當目标異常級别低于目前異常級别時,無論PSTATE.{A,I,F}值如何,異步異常都會被隐式屏蔽。

當目标異常級别與目前異常級别相同時,如果PSTATE.{A,I,F}為1,則會屏蔽異步異常。

當目标異常級别高于目前異常級别且目标異常級别為EL2或EL3時,不管PSTATE.{A,I,F}值是多少,都會執行異步異常。

當目标異常級别高于目前異常級别且目标異常級别為EL1時,如果PSTATE.{A,I,F}為1,則會屏蔽異步異常。

示例2-5顯示了如何在PSTATE中清除SError、IRQ和FIQ屏蔽位。

// code2-5:Enable SError, IRQ and FIQ 
MSR DAIFClr, #0x7
           
  • 1
  • 2

3、配置MMU和Cache

配置MMU和Cache需要以下步驟:

  • 清理并使緩存無效
  • 設定MMU
  • 使能MMU和Cache

3.1、清理并使緩存無效

系統複位後,cache中的内容無效。ARMv8-A處理器實作的硬體在複位後自動使所有cache失效,是以複位後不需要軟體清除。但是,在某些情況下,清理和使資料緩存無效仍然是必要的(資料cache),例如核心斷電過程。

示例3-1展示了如何使用EL3中的循環DC CISW指令清除一級資料緩存(L1 date cache)并使其失效。由此可以輕松修改其他級别緩存或其他緩存操作的代碼。

/*code3-1: Clean and invalidate L1 data cache*/
// Disable L1 Caches 
MRS X0, SCTLR_EL3 // Read SCTLR_EL3. 
BIC X0, X0, #(0x1 << 2) // Disable D Cache. 
MSR SCTLR_EL3, X0 // Write SCTLR_EL3.