QEMUのRaspberry Pi2モデルで割り込みを動かしてみました。
やっとできました。
Raspberry Pi2 の Interrupt Routingを把握しないとハマります。
ソースコード:https://github.com/eggman/raspberrypi/tree/master/qemu-raspi2/int01
動作例
まずQEMUを使った割り込み動作のトレースです。
qemu-system-arm -M raspi2 -m 128 -serial mon:stdio -nographic -kernel kernel.elf -d in_asm
IN: kernel_main
0x00008174: ebffffbe bl #0x8074
----------------
IN:
0x000080b8: e59ff018 ldr pc, [pc, #0x18]
----------------
IN:
0x00008064: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, sb, sl, fp, ip, lr}
0x00008068: eb000044 bl #0x8180
----------------
IN: c_irq_handler
0x00008180: e92d4070 push {r4, r5, r6, lr}
0x00008184: ebffffbe bl #0x8084
----------------
IN:
0x00008084: f10c0080 cpsid i
----------------
IN:
0x00008088: e12fff1e bx lr
----------------
IN: c_irq_handler
0x00008188: e3a04a0b mov r4, #0xb000
- 割り込みが発生すると、0x000080b8 のIRQ vectorにジャンプします。
- レジスタをスタックにPUSHしてから、C言語で記述した c_irq_handler()にジャンプします。
Raspberry Pi2 の Interrupt Routing
Raspberry Pi2 は、独自の割り込みコントロール回路を持っており、その割り込み信号の配線がどうつながっているかがわかっていないといけません。
-
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf
の 3.2 Interrupt routing に記載されている図を理解してください。
この図です。
今回説明する UART の割り込みについて説明します。
- UARTの割り込み信号線は、図の一番左側の「64 irqs」の1本に該当します。
- 「64 irqs」が接続されているarm_controlという回路が、「https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf 」の 7 Interrupts の割り込みコントローラです。ただし、Raspberry Pi2では64本のみが対象です。
- UARTの割り込み番号は57番なので、IRQ_ENABLE2(0x3F00B214)のbit25を1に設定すると、図のirq_nの信号線にUARTの割り込み信号が反映されます。
- このirq_nの信号線は図のIRQ routingという回路に接続されています。この回路はhttps://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf で仕様が説明されています。
- irq_n を、CPUのCORE3のnIRQに接続したい場合は、GPU interrupts routing(0x4000000C)に0x3を設定します。
- これでUARTの信号線がCPUに配線されました。
コードにすると実質2行です。
#define IRQ_ENABLE2 (*(volatile unsigned int *) 0x3F00B214)
#define GPU_INTERRUPTS_ROUTING (*(volatile unsigned int *) 0x4000000C)
// enable UART interrupt.
IRQ_ENABLE2 = 1<<25;
// IRQ routeing to CORE3.
GPU_INTERRUPTS_ROUTING = 0x03;
CPUの割り込み有効と無効
ARMv6以降であれば、CPUの割り込みを有効にする命令 CPSIE、CPUの割り込みを無効にする命令 CPSIDに対応しているので、アセンブラで関数を作成しました。
.globl enable_irq
enable_irq:
cpsie i
bx lr
.globl disable_irq
disable_irq:
cpsid i
bx lr
ベクタテーブルの設定
ベクタテーブルはアセンブラで記述します。
ベクタテーブルは32バイトアラインを指定して配置します。
.balign 32
vector:
ldr pc, reset_handler
ldr pc, undefined_handler
ldr pc, swi_handler
ldr pc, prefetch_handler
ldr pc, data_handler
ldr pc, unused_handler
ldr pc, irq_handler
ldr pc, fiq_handler
reset_handler: .word reset
undefined_handler: .word io_halt
swi_handler: .word io_halt
prefetch_handler: .word io_halt
data_handler: .word io_halt
unused_handler: .word io_halt
irq_handler: .word irq
fiq_handler: .word io_halt
ARMv7以降であれば、ベクタテーブルはVBARで指定した場所に配置できます。
// set vector address.
ldr r0, =vector
mcR P15, 0, r0, c12, c0, 0
IRQモードのスタックポインタ設定
アセンブラで記述します。
// save cpsr.
mrs r0, cpsr
// setup sp in IRQ mode.
bic r1, r0, #0x1f
orr r1, r1, #0x12
msr cpsr_c,r1
mov sp,#0x4000
// restore cpsr.
msr cpsr_c, r0
// setup the stack in SVC mode.
mov sp, #0x8000
割り込みハンドラ
アセンブラの割り込みハンドラ
- レジスタをスタックにPUSHして退避してからCの割り込みハンドラを呼びます。
- Cの割り込みハンドラから戻ったら、レジスタをスタックから復帰します。
- IRQの場合は、LR-4のアドレスに戻るので、pcにLR-4を設定します。
- SPSRも復帰させるので、subの変わりにsubsを使います。
irq:
push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
bl c_irq_handler
pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
subs pc, lr, #4
- Cの割り込みハンドラでは、まず、CPUの割り込みを無効にします。
- 割り込み要因を確認して、UARTだと判定したら、UARTの割り込み処理をおこないます。
- UARTの割り込みを無効にします。
- この例では、受信割り込みのみ許可しているので、データを読み込んで、割り込みをクリアしています。
- その上で文字列を表示します。
- UARTの割り込みを有効にします。
- CPUの割り込みを有効にします。
割り込みを再度有効にすることで、繰り返し割り込みが処理できます。
void c_irq_handler(void)
{
disable_irq();
if (IRQ_PEND2 & (1 << 25)) {
IRQ_DISABLE2 = 1 << 25;
uart_putc((char) DR); // read for clear tx interrupt.
uart_puts(" c_irq_handler\n");
IRQ_ENABLE2 = 1 << 25;
}
enable_irq();
return;
}
メモ
- プログラムがコア0で動いていると思ったら、コア2で動いたせいで、割り込みが入らなくて、だいぶハマりました。(コア0で動かしていれば、ここまではまらなかった気がしますけど...)
- 結局、割り込みがどういう経路で入っているか、QEMUのソースを読んで、UARTから順番にたどっていったら、割り込みがどこで止まっているかが分かりました。
- その後、引用した図の意味が、やっと理解できるようになりました。
QEMUの解析
割り込みの解析した箇所を紹介しときます。
UARTで、割り込みはがセットされる箇所。
static void pl011_update(PL011State *s)
{
uint32_t flags;
flags = s->int_level & s->int_enabled;
trace_pl011_irq_state(flags != 0);
qemu_set_irq(s->irq, flags != 0);
}
UARTとGPU側の割り込み回路の接続。
sysbus_connect_irq(s->uart0, 0,
qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ,
INTERRUPT_UART));
GPU側の割り込み回路
static void bcm2835_ic_update(BCM2835ICState *s)
{
bool set = false;
if (s->fiq_enable) {
if (s->fiq_select >= GPU_IRQS) {
/* ARM IRQ */
set = extract32(s->arm_irq_level, s->fiq_select - GPU_IRQS, 1);
} else {
set = extract64(s->gpu_irq_level, s->fiq_select, 1);
}
}
qemu_set_irq(s->fiq, set);
set = (s->gpu_irq_level & s->gpu_irq_enable)
|| (s->arm_irq_level & s->arm_irq_enable);
qemu_set_irq(s->irq, set);
}
このirqがgpu-irqとしてARM側の割り込み回路に接続
sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 0,
qdev_get_gpio_in_named(DEVICE(&s->control), "gpu-irq", 0));
ARM側の割り込み回路
static void bcm2836_control_update(BCM2836ControlState *s)
{
int i, j;
/* reset pending IRQs/FIQs */
for (i = 0; i < BCM2836_NCORES; i++) {
s->irqsrc[i] = s->fiqsrc[i] = 0;
}
/* apply routing logic, update status regs */
if (s->gpu_irq) {
assert(s->route_gpu_irq < BCM2836_NCORES);
s->irqsrc[s->route_gpu_irq] |= (uint32_t)1 << IRQ_GPU;
}
if (s->gpu_fiq) {
assert(s->route_gpu_fiq < BCM2836_NCORES);
s->fiqsrc[s->route_gpu_fiq] |= (uint32_t)1 << IRQ_GPU;
}
for (i = 0; i < BCM2836_NCORES; i++) {
/* handle local timer interrupts for this core */
if (s->timerirqs[i]) {
assert(s->timerirqs[i] < (1 << (IRQ_CNTVIRQ + 1))); /* sane mask? */
for (j = 0; j <= IRQ_CNTVIRQ; j++) {
if ((s->timerirqs[i] & (1 << j)) != 0) {
/* local interrupt j is set */
deliver_local(s, i, j, s->timercontrol[i], j);
}
}
}
/* handle mailboxes for this core */
for (j = 0; j < BCM2836_MBPERCORE; j++) {
if (s->mailboxes[i * BCM2836_MBPERCORE + j] != 0) {
/* mailbox j is set */
deliver_local(s, i, j + IRQ_MAILBOX0, s->mailboxcontrol[i], j);
}
}
}
/* call set_irq appropriately for each output */
for (i = 0; i < BCM2836_NCORES; i++) {
qemu_set_irq(s->irq[i], s->irqsrc[i] != 0);
qemu_set_irq(s->fiq[i], s->fiqsrc[i] != 0);
}
}