aarch64系統級體系架構之異常級別


aarch64系統級體系架構之異常級別

  • 1.簡述
  • 2.樹莓派啟動深度解析
  • 3.不同異常級別需要注意的問題

1.簡述

系統的異常級別對於arm芯片來說非常的重要,對於操作系統層面上來說,理解芯片的體系架構,將很容易的進入狀態,隨心所欲的去玩轉芯片,對於做應用來說,熟悉芯片的體系架構,可以解決非常棘手的問題,比如系統的安全還有就是實時性響應問題。比如我們的手機指紋加密數據,實際上是在安全模式下的,此時對於運行在非安全模式下的操作系統,其實是獲取不到指紋的數據的,只是處理安全模式下發送過來的結果,類似的還有支付安全。

對於armv8異常級別,其實是一個很大的話題,但是深入理解之后,就會發現這時一個很有意思的東西。看過盜夢空間的電影都知道,夢有好多層,但是那一層是真實的,那一層是夢境,真實到夢境如何切換,夢境到真實如何切換,這真的是不識廬山真面目,只緣生在此山中

本文簡單介紹一下樹莓派啟動的異常級別,如何從不同的exception level進行切換,同時啟動的時候如何指定exception level,大體上去理解異常級別。

 

獲得當前 EL

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

EL Meaning
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的某些寄存器。

異常級別對於芯片的體系架構非常重要,做底層開發,離不開體系架構知識,做上層開發理解芯片體系架構更好,設計更加符合芯片設計的產品,做更加性能優化的產品是非常好的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM