aarch64でEL (Exception Level)を変更する

aarch64でEL (Exception Level)を変更してみました。

あまり良く分かっていないのですが、aarch64でOSをブートするためにはExceptin Levelを変更する必要があります。

# ELについて
EL : Exception Level 例外レベル
ARMv8では４つの例外レベルが定義されている。

| EL | 説明 |
|:-:|:-:|
| EL0  | アプリ  |
| EL1  | OS  |
| EL2  | ハイパーバイザ  |
| EL3  | セキュアモニタ |

分かったこと

* Aarch32の　User modeがEL0
* Aarch32のIRQ Mode, FIQ ModeがEL1
* ELの数値が下がるほど、アクセス可能なシステムレジスタが少なくなる。
* CPUはEL3で動作開始する。
* Linux起動時は事前にEL2/EL1に設定しておく。

Raspberry Pi 3Bについて

* kernel_old=1 で起動するとEL3で起動する。
* kernel_old=1 を指定しないと、EL2で起動する。

# ELを確認する。

ELの数値をレジスタx０に代入するアセンブリコード。 

```
mrs   x0, CurrentEL
```

bit3とbit2がELの値でb、it31-4とbit1とbit0は０となる。
つまり、EL3なら0xC, EL2なら0x8, EL1なら0x4, EL0なら0x0となる。(だがEL0からはこのレジスタにアクセスできない。EL0の判定はどうやるのか？)

CurrentELの仕様は、ARMv8のアーキテクチャリファレンスマニュアル C5.2.1　にある。

# ELを変更する。
ELを変更するアセンブリーコードです。


## EL3 to EL2
EL3からEL2に変更するアセンブリコード。

```
#define MODE_AARCH64_EL2H 0x9
```

```
mov   x0, #0x4b1    // RW=1, SMD=1, RES=1, NS=1
msr   scr_el3, x0
adr   x0, start_el2
msr   elr_el3, x0
mov   x0, #MODE_AARCH64_EL2H
msr   spsr_el3, x0
eret
```

EL2に変更する例外を発生

* EL３の例外リンクアドレスレジスタ elr_el3,に、EL2に変更できた際の開始アドレス（start_el2）を設定する。
* EL３のセーブドプログラムステータスレジスタ spsr_el3に例外の戻り先のモードをEL2hに設定する。

とするとeretの後にELがEL3からEL2hに変更になる。

この時に指定するモードは同じELでも２種類あるEL2hとEL2tがある。EL2tだとEL変更後スタックポインタSP0を使う。
EL2hだとEL変更後はSP_EL2を使う

elr_el3の仕様は、ARMv8のアーキテクチャリファレンスマニュアル C5.2.7　にある。
spsr_el3の仕様は、ARMv8のアーキテクチャリファレンスマニュアル C5.2.20　にある。

## EL2 to EL1

ほぼ同様です。

```
#define MODE_AARCH64_EL1H 0x5
```

```
mov   x2, #(1 << 31)      // AArch64
orr   x2, x2, #(1 << 1)   // SWIO hardwired on Pi3
msr   hcr_el2, x2
mrs   x0, hcr_el2
adr   x0, start_el1
msr   elr_el2, x0
mov   x0, #MODE_AARCH64_EL1H
msr   spsr_el2, x0
eret
```

## EL1 to EL0

ほぼ同様です。


```
#define MODE_AARCH64_EL0 0x0
```

```
adr   x0, start_el0
msr   elr_el1, x0
mov   x0, #MODE_AARCH64_EL0
msr   spsr_el1, x0
eret
```

## ERETによるEL変更

ERETでは数字が同じか小さいいELに変更できる

* EL3はEL3, EL2、EL1、EL0に変更できる。
* EL2はEL2, EL1、EL0に変更できる。
* EL1はEL1, EL0 に変更できる

数字が小さいELから大きいELに変更するためには、IRQ、FIQなどの外部割り込みを発生させるか、SVC, HVC, SMC命令を使う。


# ELを変更する実行例
コードをQEMUで実行した場合のトレース。

* 1回目のeretの後にelr_el3に設定した0x30番地にジャンプしている。
* ２回目のeretの後にelr_el2に設定した0x54番地にジャンプしている。
* ３回目のeretの後にelr_el1に設定した0x68番地にジャンプしている。


```
0x00000010:  58000441  ldr      x1, #0x98
0x00000014:  d2809622  movz     x2, #0x4b1
0x00000018:  d51e1102  msr      scr_el3, x2
0x0000001c:  d2800122  movz     x2, #0x9
0x00000020:  d51e4002  msr      spsr_el3, x2
0x00000024:  10000062  adr      x2, #0x30
0x00000028:  d51e4022  msr      elr_el3, x2
0x0000002c:  d69f03e0  eret     

0x00000030:  d2b00002  movz     x2, #0x8000, lsl #16
0x00000034:  b27f0042  orr      x2, x2, #2
0x00000038:  d51c1102  msr      hcr_el2, x2
0x0000003c:  d53c1100  mrs      x0, hcr_el2
0x00000040:  d28000a2  movz     x2, #0x5
0x00000044:  d51c4002  msr      spsr_el2, x2
0x00000048:  10000062  adr      x2, #0x54
0x0000004c:  d51c4022  msr      elr_el2, x2
0x00000050:  d69f03e0  eret     

0x00000054:  d2800002  movz     x2, #0
0x00000058:  d5184002  msr      spsr_el1, x2
0x0000005c:  10000062  adr      x2, #0x68
0x00000060:  d5184022  msr      elr_el1, x2
0x00000064:  d69f03e0  eret     

0x00000068:  9100003f  mov      sp, x1
```

このトレースはqemuの-d in_asm で出力しました。この機能は便利です。


# 参考にしたページ
* [Switch between exception level 3 to exception level 2](https://www.raspberrypi.org/forums/viewtopic.php?t=203056)
* ARMv8_Overview.pdf