ARM深入理解-AArch64启动过程

简介: ARM深入理解-AArch64启动过程
  • 1 AArch64启动过程
  • 2 初始化异常向量表
  • 3 初始化寄存器
  • 4 配置MMU和Cache
  • 5 使能NEON和浮点
  • 6 改变异常级别


1 AArch64启动过程


AArch64启动过程,其实就是初始化必要的硬件环境。大概有以下内容:

  1. 初始化异常向量表
  2. 初始化寄存器
  3. 配置MMU和Cache
  4. 使能NEON和浮点
  5. 改变异常级别


2 初始化异常向量表


参见ARM架构体系透视5.2-初始化异常-AArch64和ARM架构体系透视5.3-初始化异常-AArch32


3 初始化寄存器

涉及的寄存器:

  • 通用目的寄存器
  • 堆栈指针寄存器
  • 系统控制寄存器


3.1 初始化通用目的寄存器

为什么需要初始化寄存器?

ARM processors use some non-reset flip-flops. This can cause X-propagation issues in simulations. Register initialization helps reduce the possibility of the issue.

翻译成大白话就是:

上电复位时,寄存器的值不确定。需要初始化到确定状态。

下面是一个初始化通用寄存器为0的示例:

MOV X0, XZR
MOV X1, XZR
MOV X2, XZR
MOV X3, XZR
MOV X4, XZR
MOV X5, XZR
MOV X6, XZR
MOV X7, XZR
MOV X8, XZR
MOV X9, XZR
MOV X10, XZR
MOV X11, XZR
MOV X12, XZR
MOV X13, XZR
MOV X14, XZR
MOV X15, XZR
MOV X16, XZR
MOV X17, XZR
MOV X18, XZR
MOV X19, XZR
MOV X20, XZR
MOV X21, XZR
MOV X22, XZR
MOV X23, XZR
MOV X24, XZR
MOV X25, XZR
MOV X26, XZR
MOV X27, XZR
MOV X28, XZR
MOV X29, XZR
MOV X30, XZR

如果处理器实现了NEON和FP扩展,还需要初始化相应的浮点寄存器。

MSR CPTR_EL3, XZR
MSR CPTR_EL2, XZR
FMOV D0, XZR
FMOV D1, XZR
FMOV D2, XZR
FMOV D3, XZR
FMOV D4, XZR
FMOV D5, XZR
FMOV D6, XZR
FMOV D7, XZR
FMOV D8, XZR
FMOV D9, XZR
FMOV D10, XZR
FMOV D11, XZR
FMOV D12, XZR
FMOV D13, XZR
FMOV D14, XZR
FMOV D15, XZR
FMOV D16, XZR
FMOV D17, XZR
FMOV D18, XZR
FMOV D19, XZR
FMOV D20, XZR

3.2 初始化堆栈指针寄存器

某些指令可以隐含地使用堆栈指针寄存器(SP),比如pushpop。在使用之前,必须进行初始化。

多核系统中,堆栈指针必须指向不同的内存地址,避免覆盖彼此的堆栈区域。如果使用了不同异常级别的SP寄存器,必须全部初始化。

下面的示例,展示了如何初始化当前异常级上的SP寄存器。假设SP指向的堆栈位于stack_top,堆栈大小是CPU_STACK_SIZE字节。

ADR X1, stack_top
ADD X1, X1, #4
MRS X2, MPIDR_EL1
AND X2, X2, #0xFF   // X2 == CPU编号
MOV X3, #CPU_STACK_SIZE
MUL X3, X2, X3      // 为每一个CPU核创建独立的堆栈空间
SUB X1, X1, X3
MOV SP, X1

3.3 初始化系统控制寄存器

某些系统寄存器上电时不会复位。因此,必须在使用它们之前,根据软件需求初始化这些寄存器。下面的示例展示了如何初始化HCR_EL2SCTLR_EL2SCTLR_EL1这些系统控制寄存器。

MSR HCR_EL2, XZR
LDR X1, =0x30C50838
MSR SCTLR_EL2, X1
MSR SCTLR_EL1, X1

这儿,只是个简单的示例,需要初始化的系统寄存器有许多。理论上,应该初始化所有没有reset值的系统寄存器。但是,某些寄存器是实现时定义的reset值,这依赖于某个处理器的具体实现。关于通用系统控制寄存器,请参考ARMv8架构官方手册以及CPU相关厂商的技术参考手册(TRM)。


4 配置MMU和Cache


MMUcache配置包含以下内容:

  • 清除、失效Cache
  • 设置MMU
  • 使能MMUCache

4.1 清除、失效Cache

reset之后,高速缓存Cache中的内容都是非法的。ARMv8-A处理器复位后,硬件自动失效所有Cache,所以,软件无需处理。但是,在某些情况下,仍然需要清除、失效D-Cache,比如某个Core核心掉电的过程中。

下面的示例,展示了如何在EL3,通过循环调用DC CISW指令,清除、失效L1D-Cache。可以作为参考,修改其它等级上的Cache或者其它Cache操作。

// 禁止L1-Cache
    MRS X0, SCTLR_EL3           // 读取SCTLR_EL3
    BIC X0, X0, #(0x1 << 2)     // 禁止D-Cache
    MSR SCTLR_EL3, X0           // 写SCTLR_EL3
    // 使D-Cache无效,以使代码通用
    // 首先,计算Cache大小,然后循环遍历每个set + way
    MOV X0, #0x0                // X0 = Cache level 
    MSR CSSELR_EL1, X0          // 0x0是L1-Dcache;0x2是L2-Dcache.
    MRS X4, CCSIDR_EL1          // 读取Cache大小的标识
    AND X1, X4, #0x7
    ADD X1, X1, #0x4            // X1 = Cache Line大小
    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.

4.2 设置MMU

ARMv8-A处理器使用VMSAv8-64虚拟内存架构执行下面的操作:

  • 物理地址到虚拟地址的转换
  • 确定内存属性,检查访问权限

地址转换由地址页表定义,并由MMU管理。每个异常级都有一个专用的地址页表。地址页表必须在使能MMU之前建立好。

VMSAv8-64使用64位地址描述符,描述地址页表中的每一项。它支持:

  • 最高48位输入、输出地址
  • 三种颗粒度:4K16K64K
  • 最高4级地址页表遍历

更多细节,请参考ARMv8架构官方手册中的AArch64 Virtual Memory System Architecture部分内容。

下面两个例子,构建一个EL3转换页表,颗粒度大小为4K,内存大小为4G

  • 0-1G配置为正常的可缓存内存
  • 1-4G配置为Device-nGnRnE内存

页表包含5122M大小的Level2内存块和31G大小的Level1内存块。

示例1:首先,初始化页表控制寄存器。然后,循环使用store指令构建页表,很方便移植。

/* 初始化转换表控制寄存器 */
    LDR X1, =0x3520         // 4G空间、4K颗粒度 
                            // 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必须是4K对齐的地址
    MSR TTBR0_EL3, X0
    /**
     * 1. 使用store指令,循环往页表所在内存写入表项
     * 2. 建立Level-1转换表
     * 3. 第1项指向Level-2页表(level2_pagetable)
     */
    LDR X1, = level2_pagetable  // 必须是4K对齐的地址
    LDR X2, =0xFFFFF000
    AND X2, X1, X2              // NSTable=0 APTable=0 XNTable=0 PXNTable=0.
    ORR X2, X2, 0x3
    STR X2, [X0], #8 
    /* 第2项是1G大小的内存块(0x40000000 ~ 0x7FFFFFFF)*/
    LDR X2, =0x40000741         // Executable Inner and Outer Shareable.
    STR X2, [X0], #8            // R/W at all ELs secure memory
                                // AttrIdx=000 Device-nGnRnE.
    /* 第3项是1G大小的内存块(0x80000000 ~ 0xBFFFFFFF)*/
    LDR X2, =0x80000741
    STR X2, [X0], #8
    /* 第4项是1G大小的内存块(0xC0000000 ~ 0xFFFFFFFF)*/
    LDR X2, =0xC0000741
    STR X2, [X0], #8 
    // 建立Level-2转换表
    LDR X0, =level2_pagetable   // level2_pagetable基地址
    LDR X2, =0x0000074D         // Executable Inner and Outer Shareable.
                                // R/W at all ELs secure memory.
                                // AttrIdx=011 Normal Cacheable.
    MOV X4, #512                // Level-2表项个数512
    LDR X5, =0x00200000         // 每次增加2M地址空间
loop:
    STR X2, [X0], #8            // 每项占据2个WORD大小
    ADD X2, X2, X5
    SUBS X4, X4, #1
    BNE loop

示例2:在编译时,创建一个section作为转换页表。该方法对于模拟来说,速度很快。使用GNU汇编语法编写。上面示例1中,初始化转换表控制寄存器的代码仍然需要。

/* 定义一个宏,存放小端字节序的64位值 */
.macro PUT_64B high, low
.word \low
.word \high
.endm
/* 创建一个表项,指向下一级的页表 */
.macro TABLE_ENTRY PA, ATTR
PUT_64B \ATTR, (\PA) + 0x3
.endm
/* 创建一个表项,指向一个1G大小的内存块 */
.macro BLOCK_1GB PA, ATTR_HI, ATTR_LO
PUT_64B \ATTR_HI, ((\PA) & 0xC0000000) | \ATTR_LO | 0x1
.endm
/* 创建一个表项,指向一个2M大小的内存块 */
.macro BLOCK_2MB PA, ATTR_HI, ATTR_LO
PUT_64B \ATTR_HI, ((\PA) & 0xFFE00000) | \ATTR_LO | 0x1
.endm
.align 12               // 按照2^12大小对齐,也就是满足4K大小的颗粒度
ttb0_base:
    TABLE_ENTRY level2_pagetable, 0
    BLOCK_1GB 0x40000000, 0, 0x740
    BLOCK_1GB 0x80000000, 0, 0x740
    BLOCK_1GB 0xC0000000, 0, 0x740
.align 12               // 按照2^12大小对齐,也就是满足4K大小的颗粒度
level2_pagetable:
    .set ADDR, 0x000    // 当前page的地址
    .rept 0x200
    BLOCK_2MB (ADDR << 20), 0, 0x74C
    .set ADDR, ADDR+2
    .endr

4.3 使能MMUcache


在使能MMUcache之前,必须先初始化它们。所有的ARMv8-A处理器要求,为了让MMUCache支持硬件一致性(hardware coherency),必须先设置SMPEN标志位。

下面的示例,展示了如何设置SMPEN标志位,并使能MMUCache的方法。

/* 位于CPUECTLR寄存器中 */
MRS X0, S3_1_C15_C2_1
ORR X0, X0, #(0x1 << 6)     // SMP标志位
MSR S3_1_C15_C2_1, X0
/* 使能MMU和Cache */
MRS X0, SCTLR_EL3
ORR X0, X0, #(0x1 << 2)     // C标志位(D-Cache)
ORR X0, X0, #(0x1 << 12)    // I标志位(I-Cache)
ORR X0, X0, #0x1            // M标志位(MMU)
MSR SCTLR_EL3, X0
DSB SY
ISB


5 使能NEON和浮点


AArch64状态下,不需要使能对NEONFP寄存器的访问。但是,仍然可以捕获对NEONFP寄存器的访问。

下面的示例,展示了如何在所有异常级下,禁止捕获对NEONFP寄存器的访问。

/* 在EL3和EL2,禁止捕获对这些寄存器的访问 */
MSR CPTR_EL3, XZR
MSR CPTR_EL3, XZR
/* 在EL1和EL0,禁止捕获对这些寄存器的访问 */
MOV X1, #(0x3 << 20)    // 设置FPEN,禁止EL1对这些寄存器的访问
MSR CPACR_EL1, X1
ISB


6 改变异常级别


ARMv8-A架构新引入了4个异常级别:EL0EL1EL2EL3

有时候,在测试用例中,必须在这些异常级之间进行切换。陷入异常或从异常返回时,处理器都会改变异常级。对于异常级的更多细节,请参考ARMv8架构官方手册中的Exception levels部分内容。

6.1 EL3EL0AArch64

reset之后,处理器会进入EL3。此时,较低的异常级EL的控制寄存器和异常状态没有明确。为了进入较低异常级,必须初始化相关执行状态和控制寄存器。然后,调用ERET指令,执行一个伪造的异常返回。

下面的示例,展示了如何从EL3切换到非安全EL0的过程:

/* 在进入EL2之前,初始化SCTLR_EL2和HCR_EL2寄存器,保存相关值 */
    MSR SCTLR_EL2, XZR
    MSR HCR_EL2, XZR
    /* 确定EL2执行状态 */
    MRS X0, SCR_EL3
    ORR X0, X0, #(1<<10)    // RW EL2执行状态是AArch64AArch64.
    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:
    /* 在进入EL1时,初始化SCTLR_EL1寄存器 */
    MSR SCTLR_EL1, XZR 
    /* 确定EL1执行状态 */
    MRS X0, HCR_EL2
    ORR X0, X0, #(1<<31)    // RW=1 EL1执行状态是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:
    /* 确定EL0执行状态 */
    MOV X0, #0b00000        // DAIF=0000 M[4:0]=00000 EL0t.
    MSR SPSR_EL1, X0
    ADR x0, el0_entry       // el1_entry指向`EL0`代码的第一条指令 
    MSR ELR_EL1, X0
    ERET
el0_entry:
    /* EL0代码 */

6.2 AArch64 EL2AArch32 EL1

有时候,可能在不同的异常级上混合执行状态。比如,较高异常级使用AArch64状态,而较低异常级被允许运行在AArch64AArch32状态下。因此,有可能从较高异常的AArch64状态,切换到较低异常级的AArch32状态。

下面的示例,展示了如何从AArch64 EL2切换到AArch32 EL1

/* 在进入EL1时,初始化SCTLR_EL1寄存器 */
    MSR SCTLR_EL1, XZR 
    MRS X0, HCR_EL2
    BIC X0, X0, #(1<<31)    // RW=0 EL1执行状态是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代码 */
相关文章
|
存储 人工智能
ARM深入理解-5.2-通往内核的大门(异常向量表_AArch64)
ARM深入理解-5.2-通往内核的大门(异常向量表_AArch64)
|
算法 C语言
嵌入式ARM设计编程(四) ARM启动过程控制
嵌入式ARM设计编程(四) ARM启动过程控制
159 0
嵌入式ARM设计编程(四) ARM启动过程控制
|
Ubuntu
使用debootstrap构建制作aarch64/arm64 Debian rootfs文件系统
使用debootstrap构建制作aarch64/arm64 Debian rootfs文件系统
1668 0
|
存储 Linux 编译器
Linux内核在arm上的启动过程
Linux内核在arm上的启动过程
135 0
Linux内核在arm上的启动过程
从openjdk.java.net获取OpenJDK8源码并编译(amd64/aarch64/arm64)
从openjdk.java.net获取OpenJDK8源码并编译(amd64/aarch64/arm64)
335 0
|
Android开发
eclipse ARM/AARCH64版本下载
eclipse ARM/AARCH64版本下载
269 0
开源:OpenJDK8 AARCH64(ARM)
开源:OpenJDK8 AARCH64(ARM)
475 0
ARM多核处理器启动过程分析【转】
转自:http://blog.csdn.net/qianlong4526888/article/details/27695173 版权声明:本文为博主原创文章,未经博主允许不得转载。       说明: 该流程图按照代码执行时间顺序划分为4部分: 1.
1537 0