QEMUのRaspberry Pi 3モデルで割り込み

QEMUのRaspberry Pi 3モデルで割り込みを動かしてみました。

[QEMUのRaspberry Pi 2モデルで割り込み](https://qiita.com/eggman/items/ae26394b70533be020da) ができたので、 Raspberry Pi 3 aarch64 で試してみました。

QEMU 2.12を使いました。

ソースコード : https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/int01

# 割り込み動作トレース


QEMUを使ったト割り込み動作のトレースです。

```
$ make runasm
qemu-system-aarch64 -M raspi3 -m 128 -serial mon:stdio -nographic -kernel kernel.elf -d in_asm
...
IN: 
0x0008088c:  d65f03c0  ret      

----------------
IN: kernel_main
0x000818d4:  d503207f  wfi      

----------------
IN: 
0x00081080:  a9bf07e0  stp      x0, x1, [sp, #-0x10]!
0x00081084:  a9bf0fe2  stp      x2, x3, [sp, #-0x10]!
0x00081088:  a9bf17e4  stp      x4, x5, [sp, #-0x10]!
0x0008108c:  a9bf1fe6  stp      x6, x7, [sp, #-0x10]!
0x00081090:  a9bf27e8  stp      x8, x9, [sp, #-0x10]!

```
プログラムを起動して、キーを入力すると0x00081080 にあるIRQの割り込みベクタにジャンプします。



# 例外ベクタテーブル

aarch64の例外ベクタテーブルの構造

| Address | Exception Type | Description |
|:--|:--|:--|
| VBAR_ELn + 0x000  | Synchronous  | Current EL with SP0  |
| +0x080  | IRQ/vIRQ  | 同上  |
| +0x100  | FIQ/vFRQ  | 同上  |
| +0x180  | SError/vSError  | 同上  |
| +0x200  | Synchronous  | Current EL with SPx  |
| +0x280  | IRQ/vIRQ  | 同上  |
| +0x300  | FIQ/vFRQ  | 同上  |
| +0x380  | SError/vSError  | 同上  |
| +0x400  | Synchronous  | Lower EL using AArch64  |
| +0x480  | IRQ/vIRQ  | 同上  |
| +0x500  | FIQ/vFRQ  | 同上  |
| +0x580  | SError/vSError  | 同上  |
| +0x600  | Synchronous  | Lower EL using AArch32  |
| +0x680  | IRQ/vIRQ  | 同上  |
| +0x700  | FIQ/vFRQ  | 同上  |
| +0x780  | SError/vSError  | 同上  |

* 例外ベクタテーブルの一つのテーブルのサイズは128(0x80)バイト
* 例外ベクタテーブルのサイズは2048(0x800)バイト。
* 例外ベクタテーブルは2048バイトアラインした場所に配置する。
* 例外ベクタテーブルの場所はそれぞれVBAR_EL3, VBAR_EL2、VBAR_EL1で指定する。　つまり３つある。
* with SP0 と　with SPxの違いはSPが切り替わらないプロセッサモードと、SPが切り替わるモードでベクタが異なる。
* Lower EL..はよくわからない。

アセンブラで例外ベクタテーブルを記述

aarch64では例外ベクタテーブルの中に、ハンドラ処理を記述することができます。

```
.balign 2048
vector:
.balign 128
    b hang
.balign 128
    stp   x0,  x1,  [sp, #-16]!
    stp   x2,  x3,  [sp, #-16]!
    stp   x4,  x5,  [sp, #-16]!
    stp   x6,  x7,  [sp, #-16]!
    stp   x8,  x9,  [sp, #-16]!
    stp   x10, x11, [sp, #-16]!
    stp   x12, x13, [sp, #-16]!
    stp   x14, x15, [sp, #-16]!
    stp   x16, x17, [sp, #-16]!
    stp   x18, x19, [sp, #-16]!
    // call c handler.
    bl    c_irq_handler

    ldp   x18, x19, [sp], #16
    ldp   x16, x17, [sp], #16
    ldp   x14, x15, [sp], #16
    ldp   x12, x13, [sp], #16
    ldp   x10, x11, [sp], #16
    ldp   x8,  x9,  [sp], #16
    ldp   x6,  x7,  [sp], #16
    ldp   x4,  x5,  [sp], #16
    ldp   x2,  x3,  [sp], #16
    ldp   x0,  x1,  [sp], #16
    eret
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
.balign 128
    b hang
```

ベクターテーブルの位置を設定するレジスタVBAR_EL1レジスタにアドレスを設定する。

```
    // set vector address in EL1.
    ldr x0, =vector
    msr vbar_el1, x0 
```

# 割り込みの有効・無効

aach64では、割り込み有効にdaifclrレジスタ、割り込み無効にdaifetを使います。

IRQは1ビット目が対応するので、2をオペランドに渡しています。

```
.globl enable_irq
enable_irq:
    msr   daifclr, #2
    ret

.globl disable_irq
disable_irq:
    msr   daifset, #2
    ret

```

# 割り込みのルーティングについて

以前の記事 [QEMUのRaspberry Pi 2モデルで割り込み](https://qiita.com/eggman/items/ae26394b70533be020da)　で説明しています。

UARTの受信割みをCORE0にルーティングする設定をしました。

```
#define UART0_IMSC ((volatile unsigned int*)(0x3F201038))
#define GPU_INTERRUPTS_ROUTING ((volatile uint32_t *)(0x4000000C))
#define CORE0_INTERRUPT_SOURCE ((volatile uint32_t *)(0x40000060))

    // enable UART RX interrupt.
    *UART0_IMSC = 1 << 4;

    // UART interrupt routing.
    *IRQ_ENABLE2 = 1 << 25;

    // IRQ routeing to CORE0.
    *GPU_INTERRUPTS_ROUTING = 0x00;
```

# 割り込みハンドラ

割り込みハンドラはアセンブラとCで記述しました。 割り込みハンドラのアセンブラ部分は、例外テーブルのところで提示しました。

割り込みハンドラのC言語部分です。

割り込みハンドラの処理内容

* まずCPUの割り込みを無効にする
* 割り込み要因を上位の割り込みステータスから順々に確認しUARTからの割り込みだと判定する。
* UART受信割り込みと判明したら、割り込み要因をクリアのために、UARTのデータレジスタをリードする。
* 割り込みを要因をクリアしたので、CPUの割り込みを有効にする。
* デバッグのために文字列を出力

```
void c_irq_handler(void)
{   
    char c;
    disable_irq();
    // check inteerupt source
    if (*CORE0_INTERRUPT_SOURCE & (1 << 8)) {
        if (*IRQ_PEND2 & (1 << 25)) {
            if (*UART0_MIS & (1 << 4)) { 
                c = (unsigned char) *UART0_DR; // read for clear tx interrupt.
                enable_irq();
                uart_putc(c);
                uart_puts(" c_irq_handler\n");
                return;
            }
        }
    }
    enable_irq();
    return;
}
```

# 割り込みでよくわからないところ

* SPが切り替わる、SP切り替わらないの動作を確認してみた

```
SP_EL1 を 0x70000
SP     を 0x80000
に設定した時のスタックポインタ

EL2h の時のスタックポインタ
7FFF0
7FF30
割り込みでスタックポインタが切り替わらない

EL1t の時のスタックポインタ
7FFF0  main
6FF40  handler
割り込みでスタックポインタが切り替わる。
```

* SPが切り替わるモード　とSPが切り替わらないモードがあるが、どういいう意図で使い分ければ良いのか分からない。
 * 特権モードがアプリにも必要なシステムの場合、EL1h(カーネル) EL1t(アプリ)という組み合わせで使う。 
 * XilinxのCortex-A53用のFreeRTOSの移植ではEL0は使わず、カーネルはEL1h, タスクはEL1tを使っている。
* EL0はIRQでEL1になるが、EL1、EL2, EL3 は割り込みでもELは同じなのか？
* 多重割り込み

# 参考情報

* https://developer.arm.com/products/architecture/a-profile/docs/100933/latest/aarch64-exception-and-interrupt-handling
 * Aarch64の割り込みに関しては、このドキュメントを読むのが良いです。PDFで20ページくらいです。