This page looks best with JavaScript enabled

树莓派OS#0x02 处理器初始化

 ·  ☕ 6 min read

本文概览

Exception Level

https://developer.arm.com/docs/ddi0488/d/programmers-model/armv8-architecture-concepts/exception-levels

在 ARMv8 处理器中,处理器的运行总是发生在 4 个异常级别之中的一个。异常级别决定了权限级别(privilege level),每个异常级别(ELn)都有对应的权限级别(PLn)。n 的值越大,权限也就越大。

ARM Exception Levels

通常会按照如下规则来配置运行软件的异常级别:

  • EL0: Normal user applications
  • EL1: Operating system kernel typically described as privileged
  • EL2: Hypervisor
  • EL3: Low-level firmware, including the Secure Monitor
Exception Level
EL0 EL0 的权限是最小的,运行在 EL0 的程序只能访问通用寄存器,大多数系统寄存器都不能访问,也不能修改某些系统配置,比如虚拟内存映射等。
EL1 EL1 通常是操作系统运行的级别,EL1 在 EL0 的基础上还能访问系统寄存器,也能够修改系统配置。
EL2 EL2 提供了处理器虚拟化支持。
EL3 EL3 提供了切换 Scurity 状态的能力。

获得当前 EL

https://developer.arm.com/docs/ddi0595/g/aarch64-system-registers/currentel

可以通过系统寄存器 CurrentEL 获取处理器当前的 Exception Level

AArch64 架构下 CurrentEL 用 64 位表示,其中第 [3:2] 位表示 EL,[63:4] 和 [1:0] 都是保留位。

EL Meaning
0b00 EL0
0b01 EL1
0b10 EL2
0b11 EL3

通过下面的代码能够获得当前的 EL:

1
2
3
4
5
6
7
// Move the contents of a PSR to a general-purpose register
//  CurrentEL 值写入通用寄存器 x0
mrs x0, CurrentEL

// Logical Shift Right
//  Xt 值右移 2 
lsr x0, x0, #2

比如获取到的值是 0b0100, 因为 [1:0] 2位是保留位,总是为0,所以右移 2 位就得到了上表中的 0b01 EL。

所以能创建如下的方法去获取当前 EL:

1
2
3
4
5
.global get_el
get_el:
    mrs x0, CurrentEL
    lsr x0, x0, #2
    ret // 返回到 X30 寄存器

切换 EL

在了解异常级别的切换前,需要先了解 ARM 架构的异常。

ARM Excepion

在通常的操作系统概念中,经常提到中断:一种中断软件执行流程的机制。在 ARM 体系中,中断实际是通过异常体现的。异常是一种要求异常处理程序采取某些措施以确保系统平稳运行的条件或系统事件。每个异常类型都有对应的异常处理程序。处理完异常后,异常处理程序将为内核做好准备(比如: 恢复处理器状态、恢复异常发生时的指令地址),以恢复发生异常之前的操作。

  • ARM 架构下有如下3种类型的异常:

    1. Interrupts: 中断。这里的中断是指硬件层面的中断。通常由外部硬件发起中断。
    2. Aborts: 中止。这个就是软件层面的异常了。比如代码中访问了不存在的内存地址,进行了除0操作(指令执行错误)等问题。
    3. Reset: 复位。复位被视为实现最高异常级别的特殊变量。这是引发异常时 ARM 处理器跳转到的指令的位置。 该向量使用 IMPLEMENTATION DEFINED 地址。 RVBAR_ELn 包含此变量的地址,其中 n 是已实现的最高异常级别的编号。所有内核都有一个复位输入,并在复位后立即执行复位异常。它是最高优先级的异常,无法屏蔽。处理器上电后,此异常用于在内核上执行初始化的代码。
  • 除了运行时程序出现问题产生异常外,也有一些指令能够产生异常,通常执行这些指令是为了从运行于更高权限级别的软件中请求服务:

    • svc: Supervisor Call 指令使用户模式程序可以请求 OS 服务
    • hvc: Hypervisor Call 指令使来宾 OS 可以请求系统管理程序服务。
    • smc: 通过 Secure monitor Call 指令,普通世界可以请求安全世界服务。(security model 参考)

    如果异常是由于在 EL0 执行指令而生成的,则会将其视为 EL1 的异常。如果是在任何其他 EL 执行指令导致生成异常,则异常级别保持不变。

异常处理

程序只能通过「获取异常」或「从异常返回」去切换异常级别

也就是说程序只有在处理异常的过程中,才能切换异常级别。

  • 当处理器从较高的异常级别移至较低的异常级别时,执行状态(Execution State)可以保持不变,也可以从 AArch64 切换到 AArch32
  • 相反,当从较低的异常级别移到较高的异常级别时,执行状态可以保持不变或从 AArch32 切换到 AArch64

异常流程

当应用程序发生异常时,处理器会到一个异常表去查找分派代码然后执行分派代码(异常表包含每种异常类型对应的分派代码),分派代码选择并调用适当的函数来处理异常。分派代码执行完成后,返回到高级别的处理程序,该处理程序执行 ERET 指令返回到应用程序。

相关寄存器

当处理异常时,会涉及几个寄存器的操作:

  1. 当前正在执行的指令地址(PC 寄存器)会被存储在 ELR_ELn(Exception link register) 中
  2. 当前处理器的状态(PSTATE)被存储在 SPSR_ELn(Saved Program Status Register)
  3. 异常处理程序被执行。异常处理程序可以修改 ELR 和 SPSR
  4. 异常处理程序执行 eret 指令。这个指令会从 SPSR_Eln 寄存器恢复处理器的状态,并且恢复 ELE_Eln 中储存的指令的执行​
流程图 示意图

由于异常处理程序可以修改 ELR_ELnSPSR_ELn 寄存器,所以异常处理程序能够间接的修改 EL 等参数,达到切换 EL 的目的。

切换到 EL1

回到树莓派OS这边,通电之后,处理器默认是在最高级别的 EL 运行的,也就是 EL3。现在我希望树莓派OS 启动之后切换到 EL1 执行(如同 Linux 和 Windows 都是在EL1运行)。通过配置一些系统寄存器,然后调用 eret 指令触发处理器去执行状态重读取就能达成目的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
master:
    ldr    x0, =SCTLR_VALUE_MMU_DISABLED
    msr    sctlr_el1, x0

    ldr    x0, =HCR_VALUE
    msr    hcr_el2, x0

    ldr    x0, =SCR_VALUE
    msr    scr_el3, x0

    ldr    x0, =SPSR_VALUE
    msr    spsr_el3, x0

    adr    x0, el1_entry
    msr    elr_el3, x0

    eret

配置 SCTLR_EL1

System Control Register

sctlr_eln 寄存器被用来配置处理器的不同参数。存在 sctlr_el1、 sctlr_el2 和 sctlr_el3 分别对应 EL1、 EL2 和 EL3 的寄存器。

sctlr_el1 寄存器能够配置 EL0 和 EL1 级别的内存等配置。通过修改 sctlr_el1 某些位的值能达到配置处理器在 EL0 和 EL1 级别运行时的行为。

1
2
ldr    x0, =SCTLR_VALUE_MMU_DISABLED
msr    sctlr_el1, x0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#define SCTLR_RESERVED                  (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11)
// 保留位赋值1
#define SCTLR_EE_LITTLE_ENDIAN          (0 << 25)
// EL1 采用小端字节序
#define SCTLR_EOE_LITTLE_ENDIAN         (0 << 24)
// EL0 采用小端字节序
#define SCTLR_I_CACHE_DISABLED          (0 << 12)
// 禁用指令缓存
#define SCTLR_D_CACHE_DISABLED          (0 << 2)
// 禁用数据缓存
#define SCTLR_MMU_DISABLED              (0 << 0)
// 禁用内存管理单元

#define SCTLR_VALUE_MMU_DISABLED	(SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED |    SCTLR_MMU_DISABLED)

上述代码执行之后的布局

配置 HCR_EL2

Hypervisor Configuration Register

HCR_EL2 寄存器提供了虚拟化的配置,包括定义是否将各种操作限制在EL2中。因为只有 EL2 支持 Hypervisor,所以只存在一个 HCR_EL2 寄存器。

1
2
3
#define HCR_RW	    			(1 << 31)
// EL1 的 Execution State 为 AArch64
#define HCR_VALUE			HCR_RW
1
2
    ldr    x0, =HCR_VALUE
    msr    hcr_el2, x0

配置 SCR_EL3

Secure Configuration Register

SCR_EL3 寄存器定义当前安全状态的配置:

  • EL0,EL1 和 EL2 的安全状态为 SecureNon-Secure
  • EL2 的 Execution State
1
2
3
4
5
6
#define SCR_RESERVED	    		(3 << 4)
#define SCR_RW				(1 << 10)
// EL2 的Execution State 为 AArch64
#define SCR_NS				(1 << 0)
// 安全状态为 Non-secure
#define SCR_VALUE	    	    	(SCR_RESERVED | SCR_RW | SCR_NS)
1
2
ldr    x0, =SCR_VALUE
msr    scr_el3, x0

配置 SPSR_EL3

Saved Program Status Register

EL3 发生异常时,SPSR_EL3 寄存器用来保存处理器的状态。这里因为树莓派OS启动之后是在 EL3 运行,所以通过修改 SPSR_EL3 的值来修改处理器的运行状态。

7 << 6 意味着将 [8:7] 3 个位都置 1:

1
#define SPSR_MASK_ALL 			(7 << 6)
  • bit[8]: SError interrupt mask
  • bit[7]: IRQ interrupt mask
  • bit[6]: FIQ interrupt mask

设置 Exception level 和 selected Stack Pointer 为 EL1h:

1
2
#define SPSR_EL1h			(5 << 0)
#define SPSR_VALUE			(SPSR_MASK_ALL | SPSR_EL1h)
含义
0b0000 EL0t
0b0100 EL1t
0b0101 (5 << 0) EL1h
0b1000 EL2t
0b1001 EL2h
0b1100 EL3t
0b1101 EL3h
1
2
ldr    x0, =SPSR_VALUE
msr    spsr_el3, x0

将 SPSR_EL3 的第 [3:0] 3 个位, 置为 EL1h 后,在执行 eret 指令后,处理器的状态会从 SPSR_EL3 中恢复,也就让处理器的 EL 切换到了 EL1。

配置 ELR_EL3

Exception Link Register (EL3)

在 EL3 进行异常处理时,ELR_EL3 寄存器将用来保留要返回的地址。

1
2
3
4
adr    x0, el1_entry
//  el1_entry label 地址存到 x0
msr    elr_el3, x0
//  x0 值存到 elr_el3

先将 el1_entry 符号地址存到 elr_el3,在执行 eret 指令之后,处理器就将从 elr_el3 寄存器读取符号去恢复执行,也就间接的让处理器执行 el1_entry


通过配置上述系统寄存器,然后调用 eret 触发处理器的执行状态的重恢复,就能将异常级别从 EL3 切换到 EL1 了。


Yang
WRITTEN BY
Yang
Developer