外部記憶装置

外付け記憶装置

Mewz on libkrun - その6 Mewz 追加実装(virtio-console)

Mewz (WebAssembly x Unikernel) を libkrun で動かしてみた - 外部記憶装置 の詳細を記すシリーズ

目次

Virtio Console (virtio-console) について

docs.oasis-open.org

virtio-console はコンソール入出力向けの virtio デバイスである。 一般的に、シリアル経由で行われるコンソール入出力を置き換えるために用いられる。

libkrun においても、コンソールデバイスとして標準で用いられる。 シリアルの場合 x86_64 や aarch64 でそれぞれ対応するデバイスが異なり、ドライバを複数用意する必要がある。 virtio-console を用いることで、そういったデバイスドライバを用意する必要が無くなる。

Mewz においては今のところ x86_64 を対象としているため、IO Port 経由でシリアルデバイスを操作することでコンソールの入出力を行っている。 libkrun は virtio-console を用いることが想定されているため、これについても追加実装を行い対応する。

基本機能の実装

virtio-console は Virtqueue を送受信用で計2つ用意し、他の virtio デバイスと同じ手順で初期化すれば基本的な機能は利用できる。 コンソール出力については transmitq(port0)、入力については receiveq(port0) で扱い、Mewz 側でこれに対応するだけで良い。

QEMU においては、-device virtio-serial -device virtconsole,chardev=my_console -chardev socket,id=my_console,host=127.0.0.1,port=3334,server,nowait をオプションとして与え、別ターミナルで telnet localhost 3334 を実行すれば telnet 経由で virtio-console の入出力を扱うことができる。

VIRTIO_CONSOLE_F_MULTIPORT

libkrun では、VIRTIO_CONSOLE_F_MULTIPORT と呼ばれる拡張機能を前提としているため、上述の単純な virtio-console ドライバでは動作しない。 この拡張はその名の通り、複数 virtio-console ポートを扱うためのものであり、Virtqueue 初期化後に各ポートの初期化処理を行う必要がある。 これらの制御は専用の Virtqueue である control receiveqcontrol transmitqを介して行われる。

VIRTIO_CONSOLE_F_MULTIPORT feature を利用する場合、control キューでは以下のフォーマットに従い制御通信を行う。

struct virtio_console_control { 
        le32 id;    /* Port number */ 
        le16 event; /* The kind of control event */ 
        le16 value; /* Extra information for the event */ 
};

id はポート番号、event は制御用コード、valueステータスコード等を含める。 ポートの初期化は以下の流れに従う。

Virtqueue の初期化後、VIRTIO_CONSOLE_DEVICE_READY を libkrun(Device) に対して通知することでデバイスの初期化が完了する。

その後、Device からポートの追加要求(VIRTIO_CONSOLE_DEVICE_ADD) を受け取る。id で指定されたポートの準備が完了した後、成功を示す value=1 を持つVIRTIO_CONSOLE_PORT_READYメッセージで返答する。

ポートをコンソールの入出力として利用する場合、Device は VIRTIO_CONSOLE_CONSOLE_PORT により Driver に通知を行う。Driver はこれを受けて VIRTIO_CONSOLE_PORT_OPENvalue=1 を入れて返答する。※なお、QEMU においては VIRTIO_CONSOLE_PORT_OPEN を返答しなくとも動くようではあるが、Virtio の仕様上は "MUST" となっているため必ず返答する必要がある。

その後、Device 側から VIRTIO_CONSOLE_PORT_OPEN を受け取ると、該当ポートは既に利用可能な状態であるため、Driver はコンソールの入出力を対応する Virtqueue に流せば良い。

Mewz への実装

drivers/virtio/console.zig に Multiport 対応のドライバを実装した。

github.com

libkrun については標準で port0 だけでコンソールの入出力を扱えるため、port0 のみ受け付け、それ以外のポートは拒否するように実装した。

    fn handleCtrl(self: *Self, ctrl: *VirtioConsoleControl) void {
        switch (ctrl.event) {
            .VIRTIO_CONSOLE_DEVICE_ADD => {
                // only accept port 0
                if (ctrl.id == 0) {
                    log.info.printf("virtio.console: ctrl port added (port={})\n", .{ctrl.id});
                    self.port0_added = true;
                    self.ctrlTransmit(ctrl.id, .VIRTIO_CONSOLE_PORT_READY, 1);
                } else {
                    log.warn.printf("virtio.console: ctrl port is not ready (port={})\n", .{ctrl.id});
                    self.ctrlTransmit(ctrl.id, .VIRTIO_CONSOLE_PORT_READY, 0);
                }
            },
            .VIRTIO_CONSOLE_CONSOLE_PORT => {
                if (ctrl.id != 0 or !self.port0_added) {
                    log.warn.printf("virtio.console: cannot use port{} as a console: not added\n", .{ctrl.id});
                } else {
                    self.port0_console = true;
                    log.info.print("virtio.console: port0 is specified as a console\n");
                    self.ctrlTransmit(0, .VIRTIO_CONSOLE_PORT_OPEN, 1);
                }
            },
            .VIRTIO_CONSOLE_PORT_OPEN => {
                if (ctrl.id != 0 or !self.port0_added) {
                    log.warn.printf("virtio.console: cannot open port{}: not added\n", .{ctrl.id});
                } else {
                    if (ctrl.value == 1) {
                        self.port0_opened = true;
                        log.info.print("virtio.console: port0 is opened\n");
                    } else if (ctrl.value == 0) {
                        self.port0_opened = false;
                        log.info.print("virtio.console: port0 is closed\n");
                    } else {
                        log.warn.printf("virtio.console: invalid value: {}\n", .{ctrl});
                    }
                }
            },
            .VIRTIO_CONSOLE_RESIZE => {
                log.warn.print("virtio.console: VIRTIO_CONSOLE_RESIZE is ignored\n");
            },

UART(シリアル)との共存については、virtio-console の port0 がコンソールとして利用かつ、既に OPEN な状態にある場合は port0 を利用し、それ以外はシリアルにコンソール出力を流すようにした。

github.com

他にも、virtio-console の PCI BAR が IO space mapped な場合への対応等を行い、QEMU と libkrun のどちらでも動作するようにした。

pci: Handle IO space mapped BAR · naoki9911/mewz@8495787 · GitHub

ここまでの動作確認

前回の環境を引き続き利用し、動作確認を行った。なお、 GitHub - naoki9911/mewz-on-libkrun の submodule は更新済みであり、必要に応じて pull すること。

$ cd mewz
$ zig build -Dapp-obj=wasm.o -Dlog-level=info -Denable-pci=false libkrunfw
$ cd ../libkrun/examples
$ sudo LD_LIBRARY_PATH=../lib ./chroot_vm --net=passt dummy dummy
[sudo] password for naoki:
UART: [LOG INFO]: booted with linux zero page
UART: [LOG INFO]: zeropage: e820_entries=2
UART: [LOG INFO]: E820 Entry [1] addr=0x0 size=0x9fc00 type=1
UART: [LOG INFO]: E820 Entry [2] addr=0x100000 size=0x200b0001 type=1
UART: [LOG INFO]: available memory: 144eb000 - 201b0001
UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0000000 size=0x1000 IRQ=5
UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0001000 size=0x1000 IRQ=6
UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0002000 size=0x1000 IRQ=7
UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0003000 size=0x1000 IRQ=8
UART: [LOG INFO]: virtio_mmio device detected: addr=0xd0004000 size=0x1000 IRQ=9
UART: [LOG INFO]: virtio.console: found mmio device
UART: [LOG INFO]: virtio.console: initialized mmio device
UART: [LOG INFO]: virtio.net: found mmio device
UART: [LOG INFO]: mac: 5a:94:ef:e4:c:ee
UART: [LOG INFO]: virtio.net: initialized mmio device

UART: [LOG INFO]: virtio.console: ctrl port added (port=0)
UART: [LOG INFO]: virtio.console: port0 is specified as a console
VC: [LOG INFO]: virtio.console: port0 is opened
VC: [LOG WARN]: virtio.console: VIRTIO_CONSOLE_RESIZE is ignored
VC: Hello, wasker

UART 経由の出力については UART:、virtio-console 経由の出力については VC:を先頭に付与する実装としている。 virtio-console ドライバで port0 が open となった時点で出力が virtio-console となっており、意図した通り動作していることがわかる。