Exception Level Switch

Pi 剛開機後預設會跑在 EL2 上,我們希望將 kernel 跑在 EL1,shell 跑在 EL0,用以區隔 user privilege 與 kernel privilege。

ARM 的硬體設計:

  • sp 這個暫存器會被當作是該 exception level sp 的別稱 (SP_ELx)。
  • Exception Vector Table 會被切開: VBAR_ELx
  • 僅有在觸發或返回 exception 時會轉換到不同 exception level
  • exception level 可以用來控管 System Registers 的權限,暫存器尾數的 ELx 表示至少需要在 ELx 才能進行存取
  • exception level 對 memory 的權限需要另外設定 (Lab5),目前都是關閉 MMU,使用 physical address

初始化

首先需要對 System Registers 做一些基礎設定:

  • SCTLR_EL1 (System Control Register (EL1))
    • 把 MMU 關掉
  • CPACR_EL1 (Architectural Feature Access Control Register)
    • 設定 FPEN (參考)
  • HCR_EL2
    • 設定為 AArch64
  • VBAR_EL1 (Vector Base Address Register (EL1))
    • 設定 EL1 的 Vector Table

我將一些需要初始化的系統暫存器的值放在 include/sysreg.h 方便之後做調整。

 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
#ifndef __SYSREGS_H__
#define __SYSREGS_H__

// SCTLR_EL1, System Control Register (EL1)

#define SCTLR_VALUE_MMU_DISABLED 0

// HCR_EL2, Hypervisor Configuration Register

#define HCR_EL2_RW_AARCH64  (1 << 31)
#define HCR_EL2_VALUE       (HCR_EL2_RW_AARCH64)

// SPSR_EL2, Saved Program Status Register (EL2)

#define SPSR_EL2_MASK_ALL   (0b1111 << 6)
#define SPSR_EL2_EL1h       (0b0101 << 0)
#define SPSR_EL2_VALUE      (SPSR_EL2_MASK_ALL | SPSR_EL2_EL1h)

// SPSR_EL1, Saved Program Status Register (EL1)

#define SPSR_EL1_MASK       (0b0000 << 6)
#define SPSR_EL1_EL0        (0b0000 << 0)
#define SPSR_EL1_VALUE      (SPSR_EL1_MASK | SPSR_EL1_EL0)

// CPACR_EL1, Architectural Feature Access Control Register

#define CPACR_EL1_FPEN      (0b11 << 20)
#define CPACR_EL1_VALUE     (CPACR_EL1_FPEN)

#endif

start.S

從 EL2 回到 EL1 的技巧是先設定 spsr_el2elr_el2 並執行 eret。從 EL1 回到 EL0 也是相同技巧。

目前先將 EL1 的 stack 設在 0x60000,EL0 的 stack 設在 0x40000。並設定 EL1 的 Exception Vector Table

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "sysregs.h"

.section ".text.boot"

.global _start

_start:
    // get cpu id
    mrs     x1, MPIDR_EL1
    and     x1, x1, #3
    cbz     x1, 2f
    // if cpu_id > 0, stop
1:
    wfe
    b       1b
    // if cpu_id == 0
2:
    // set stack pointer
    ldr     x1, =_start
    mov     sp, x1

    // clear bss
    ldr     x1, =__bss_start
    ldr     x2, =__bss_size
3:  cbz     x2, 4f
    str     xzr, [x1], #8
    sub     x2, x2, #1
    cbnz    x2, 3b
4:
    // disable MMU
    ldr     x1, =SCTLR_VALUE_MMU_DISABLED
    msr     sctlr_el1, x1

    // make el0, el1 can use Floating point and Advanced SIMD
    ldr     x1, =CPACR_EL1_VALUE
    msr     CPACR_EL1, x1

    // set AArch64 for el2
    ldr     x1, =HCR_EL2_VALUE
    msr     hcr_el2, x1

    // mask all interrupt, and set interrupt level to el1h
    ldr     x1, =SPSR_EL2_VALUE
    msr     spsr_el2, x1

    adr     x1, el1_start
    msr     elr_el2, x1

    eret

el1_start:
    // set stack pointer
    ldr     x1, =0x60000
    mov     sp, x1

    // load exception_table to VBAR_EL1
    adr     x1, exception_table
    msr     vbar_el1, x1

    // mask all interrupt, and set exception level to el0
    ldr     x1, =SPSR_EL1_VALUE
    msr     spsr_el1, x1

    adr     x1, el0_start
    msr     elr_el1, x1

    eret

el0_start:
    // set stack pointer
    ldr     x1, =0x40000
    mov     sp, x1

    // jump to main function in C
    bl      main
    // halt this core if return
    b       1b

EL1 訪問 Floating Point 及 Advanced SIMD

發現當執行 floating point 相關的指令時 (e.g. ucvtf) 就會進到 Synchronous Exception,查了一下資料才發現要清 CPACR_EL1.FPEN

https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/cpacr_el1#FPEN

延伸閱讀

Learn the Architechture - Privilege and Exception levels

comments powered by Disqus