QEMUのRaspberry Pi 3モデルで割り込みを動かしてみました。
QEMUのRaspberry Pi 2モデルで割り込み ができたので、 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で指定する。 つまり3つある。
- 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モデルで割り込み で説明しています。
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ページくらいです。