3.4. ハードウェア割り込み
virtio-netデバイスの初期化に成功しましたが、まだ割り込みを捕捉するための設定が出来ていないため、デバイスにパケットが届いても何も起きません。ここではデバイスからの通知であるハードウェア割り込みを扱えるようにします。
Note
RISC-Vでは、デバイスからの外部割り込みを「PLIC(Platform-Level Interrupt Controller)」で扱います。PLICは複数の割り込み元をまとめてCPU(hart)へ配送するコントローラで、x86でいうI/O APICに相当します。タイマ割り込みやソフトウェア割り込みはPLICを経由せず、CPUコアごとに直接扱われます。
PLICの設定
PLIC関連のコードはkernel/plic.cにあります。既にuartとvirtio-diskの割り込みを捕捉できるようにするためのコードがあるので、これを参考にしてvirtio-netの割り込みも受け付けるようにします。
📝 kernel/plic.c
void
plicinit(void)
{
// set desired IRQ priorities non-zero (otherwise disabled).
*(uint32 *)(PLIC + UART0_IRQ * 4) = 1;
*(uint32 *)(PLIC + VIRTIO0_IRQ * 4) = 1;
+ *(uint32 *)(PLIC + VIRTIO1_IRQ * 4) = 1;
}
void
plicinithart(void)
{
int hart = cpuid();
// set enable bits for this hart's S-mode
// for the uart and virtio disk.
- *(uint32 *)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ);
+ *(uint32 *)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ) | (1 << VIRTIO1_IRQ);
// set this hart's S-mode priority threshold to 0.
*(uint32 *)PLIC_SPRIORITY(hart) = 0;
}
Note
plicinit()では割り込みの優先度を設定し、plicinithart()では有効にする割り込みを設定します。
割り込みハンドラの登録と呼び出し
virtio-netドライバは、初期化時にintr_register()で自身の割り込みハンドラ(virtio_net_isr())を登録しています。3.1で用意したintr.cはスタブのままだったので、ハンドラの登録(intr_register())と、割り込み番号に応じた登録済みハンドラの呼び出し(intr_dispatch())を実装します。
📝 kernel/net/platform/xv6-riscv/intr.c
-/* stub: to be implemented in the software interrupt step */
+#include <stdio.h>
#include "platform.h"
+#include "util.h"
+
+struct irq_entry {
+ struct irq_entry *next;
+ unsigned int irq;
+ intr_isr_t isr;
+ int flags;
+ void *arg;
+};
+
+/*
+ * NOTE: if you want to add/delete the entries after intr_run(),
+ * you need to protect these lists with a mutex.
+ */
+static struct irq_entry *irqs;
+
int
intr_register(unsigned int irq, intr_isr_t isr, int flags, void *arg)
{
+ struct irq_entry *entry;
+
+ for (entry = irqs; entry; entry = entry->next) {
+ if (entry->irq == irq) {
+ if (entry->flags ^ INTR_IRQ_SHARED || flags ^ INTR_IRQ_SHARED) {
+ errorf("conflicts with already registered IRQs, irq=%u", irq);
+ return -1;
+ }
+ }
+ }
+ entry = memory_alloc(sizeof(*entry));
+ if (!entry) {
+ errorf("memory_alloc() failure");
+ return -1;
+ }
+ entry->irq = irq;
+ entry->isr = isr;
+ entry->flags = flags;
+ entry->arg = arg;
+ entry->next = irqs;
+ irqs = entry;
+ infof("success, irq=%u", irq);
return 0;
}
int
intr_raise(unsigned int irq)
{
return 0;
}
+/*
+ * NOTE: called from devintr() in kernel/trap.c
+ */
+void
+intr_dispatch(unsigned int irq)
+{
+ struct irq_entry *entry;
+
+ for (entry = irqs; entry; entry = entry->next) {
+ if (entry->irq == irq) {
+ entry->isr(entry->irq, entry->arg);
+ }
+ }
+}
+
int
intr_init(void)
{
return 0;
}
...
intr_dispatch()はxv6側の割り込み処理から呼び出すため、kernel/defs.hにプロトタイプ宣言を追加します。
📝 kernel/defs.h
...
// net/net.c
int net_init(void);
int net_run(void);
+// net/platform/xv6-riscv/intr.c
+void intr_dispatch(unsigned int);
+
// net/platform/xv6-riscv/driver/virtio_net.c
struct net_device* virtio_net_init(void);
...
割り込みの捕捉
割り込み関連のコードはkernel/trap.cにあります。devintr()の中にuartとvirtio-diskの割り込みを捕捉するコードがあるので、これを参考にしてvirtio-netの割り込みを捕捉したらintr_dispatch()を呼び出すようにします。
📝 kernel/trap.c
...
int
devintr()
{
...
if (irq == UART0_IRQ) {
uartintr();
} else if (irq == VIRTIO0_IRQ) {
virtio_disk_intr();
+ } else if (irq == VIRTIO1_IRQ) {
+ intr_dispatch(irq);
} else if (irq) {
printk("unexpected interrupt irq=%d\n", irq);
}
...
}
動作確認
再ビルドした後、make qemuを実行してxv6を起動します。起動したら、開発環境で別のシェルを開き192.0.2.2に対してpingを実行します。
$ ping -c 3 192.0.2.2
割り込みの設定が正しく行えていれば、virtio-netデバイスからの割り込みを捕捉してパケットを受信できるようになっているはずです。xv6のコンソールに受信したフレームの内容が出力されます。
10:05:00.101 [D] virtio_net_input: dev=net0, type=0x0806, len=42 (kernel/net/platform/xv6-riscv/driver/virtio_net.c:315)
src: 16:2a:af:19:b5:91
dst: ff:ff:ff:ff:ff:ff
type: 0x0806 (ARP)
10:05:00.102 [D] net_input: dev=net0, type=0x0806, len=28 (kernel/net/net.c:232)
+------+-------------------------------------------------+------------------+
| 0000 | 00 01 08 00 06 04 00 01 16 2a af 19 b5 91 c0 00 | .........*...... |
| 0010 | 02 01 00 00 00 00 00 00 c0 00 02 02 | ............ |
+------+-------------------------------------------------+------------------+
Note
pingはホスト側がARPで192.0.2.2のMACアドレスを解決できず失敗したままで大丈夫です。ARPを処理するモジュールはまだ移植していないため、受信したフレーム(ARP要求)はnet_input()まで届いた後、対応するプロトコルが見つからず破棄されます。ここでは「割り込み経由でパケットを受信できたこと」が確認できれば成功です。