aarch64系统级体系架构之异常级别

aarch64系统级体系架构之异常级别

  • 1.简述
  • 2.树莓派启动深度解析
  • 3.不同异常级别需要注意的问题

1.简述

系统的异常级别对于arm芯片来说非常的重要,对于操作系统层面上来说,理解芯片的体系架构,将很容易的进入状态,随心所欲的去玩转芯片,对于做应用来说,熟悉芯片的体系架构,可以解决非常棘手的问题,比如系统的安全还有就是实时性响应问题。比如我们的手机指纹加密数据,实际上是在安全模式下的,此时对于运行在非安全模式下的操作系统,其实是获取不到指纹的数据的,只是处理安全模式下发送过来的结果,类似的还有支付安全。

对于armv8异常级别,其实是一个很大的话题,但是深入理解之后,就会发现这时一个很有意思的东西。看过盗梦空间的电影都知道,梦有好多层,但是那一层是真实的,那一层是梦境,真实到梦境如何切换,梦境到真实如何切换,这真的是不识庐山真面目,只缘生在此山中

本文简单介绍一下树莓派启动的异常级别,如何从不同的exception level进行切换,同时启动的时候如何指定exception level,大体上去理解异常级别。

 

获得当前 EL

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

ELMeaning
0b00 EL0
0b01 EL1
0b10 EL2
0b11 EL3

 

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

// 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:

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

切换 EL

  • 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 执行指令导致生成异常,则异常级别保持不变。

 

相关寄存器

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

  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_ELn 和 SPSR_ELn 寄存器,所以异常处理程序能够间接的修改 EL 等参数,达到切换 EL 的目的。

切换到 EL1

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

复制代码
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 级别运行时的行为。

ldr    x0, =SCTLR_VALUE_MMU_DISABLED
msr    sctlr_el1, x0

 

复制代码
#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 寄存器。

#define HCR_RW                    (1 << 31)
// EL1 的 Execution State 为 AArch64
#define HCR_VALUE            HCR_RW

 

   ldr    x0, =HCR_VALUE
   msr    hcr_el2, x0

 

配置 SCR_EL3

Secure Configuration Register

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

  • EL0,EL1 和 EL2 的安全状态为 Secure 或 Non-Secure
  • EL2 的 Execution State
#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)

 

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:

 

#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:

#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

 

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 寄存器将用来保留要返回的地址。

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 了。

2.树莓派启动深度解析

树莓派的启动流程,我想简单叙述一下,就是上电之后,启动了GPU,然后通过GPU去启动arm的核,然后就是读取配置文件,设置ddr等等。如果sd卡里有kernel8.img文件,那这个就是Linux内核执行的程序。此时,Linux就执行起来了。

对于rt-thread来说,情况是一样的,可以在config.txt里写下如此的文件

kernel=kernel8.img
kernel_addr=0x80000
enable_uart=1

这就告诉树莓派,需要启动的固件名字是kernel8.img,入口地址0x80000

其实这并不是芯片上电后执行的第一个程序,还运行了一个叫start.elf的文件,该文件会加载kernel8.img。通过测试得知,树莓派其实在kernel8.img的入口的第一条指令是在el2下的。关于el3,el2,el1,el0可以看下面的图进行理解。

 

 

应用程序运行在EL0上,此时可以访问的寄存器很有限,比如我们安卓手机安装的app,其实都是运行在EL0的。而EL1是运行Kernel的,比如Linux的或者是rt-thread。

到了EL2就是提供了虚拟化的实现,这一层涉及到虚拟化,在服务器上用的比较多。

然后就是EL3,这个比较厉害,权限比较大,基本上可以访问所有寄存器,而且电源管理,也在里面。另外这个就类似于一个电梯,打通了安全与非安全的通道。

树莓派启动内核在EL2里面,那么我们知道操作系统运行在EL1的非安全模式下,安全模式是对于安全应用场景的,这里不做考虑,但是如果要访问GIC的组,一般是在安全模式。

目的就是从EL2->EL1。

复制代码
 // enable AArch64 in EL1
    mov     x0, #(1 << 31)          // AArch64
    orr     x0, x0, #(1 << 1)       // SWIO hardwired on Pi3
    msr     hcr_el2, x0
    mrs     x0, hcr_el2

    // change execution level to EL1
    mov     x2, #0x3c4
    msr     spsr_el2, x2        // 1111000100
    adr     x2, .L__in_el1
    msr     elr_el2, x2
    eret                        // exception return. from EL2. continue from .L__in_el1
复制代码

主要就是使能el1在64位模式下运行,然后配置系统从EL2->>EL1,采用的是eret指令,该指令会将pc指针指向elr_el2对应的地址。

果我们想要树莓派在el3上运行,可以采用MVC指令进行模式切换。

svc,hvc,smc指令切换,对EL1~3有三种不同的中断向量。

不想进行指令切换,最简单的办法,就是编译一个链接地址为0的固件,在config.txt中写下

armstub=kernel8.img

此时,系统从el3运行,并且起始地址为0。和芯片上电执行第一条指令模式类似。

3.不同异常级别需要注意的问题

既然涉及到异常级别,那就不得不说一下使用异常级别需要注意的问题了。安全和非安全这是物理隔离的,但是异常级别却是需要进行切换的。比如我们从非安全到安全,是不能直接切换过去的,需要借助el3这个电梯,可以借助这个过去。

然后就是你在el1上访问某些寄存器的时候,突然系统hard fault,这时就要看aarch64的芯片手册了,看这个寄存器是在那个异常级别下可以访问的。有些寄存器在不对应的异常级别,读为零,写无效。比如GIC的某些寄存器。

异常级别对于芯片的体系架构非常重要,做底层开发,离不开体系架构知识,做上层开发理解芯片体系架构更好,设计更加符合芯片设计的产品,做更加性能优化的产品是非常好的。

posted on   tycoon3  阅读(3096)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2021-03-10 vxlan tso
2021-03-10 tcpdump 报文超过 MTU tso
2021-03-10 tcpdump vxlan
2021-03-10 网卡性能之多队网卡性能之多队列技术列技术

导航

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示