LoginSignup
5
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-21

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 は、独自の割り込みコントロール回路を持っており、その割り込み信号の配線がどうつながっているかがわかっていないといけません。

この図です。

スクリーンショット 2018-05-21 20.53.13.png

今回説明する 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で、割り込みはがセットされる箇所。

hw/char/pl011.c
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側の割り込み回路の接続。

hw/arm/bcm2835_peripherals.c
    sysbus_connect_irq(s->uart0, 0,
        qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ,
                               INTERRUPT_UART));

GPU側の割り込み回路

hw/intc/bcm2835_ic.c
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側の割り込み回路に接続

hw/arm/bcm2836.c
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 0,
        qdev_get_gpio_in_named(DEVICE(&s->control), "gpu-irq", 0));

ARM側の割り込み回路

w/intc/bcm2836_control.c
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);
    }
}
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2