Virtual I/O Device(以降VirtIO)は仮想マシン向けに提供されるI/Oフレームワークです.
ホストマシン上でソフトウェアとして動かし, ゲストマシンには物理デバイスの様に見える準仮想化デバイスとして作り仮想マシンの動作速度の向上を図るものです.
(動作速度向上については他の記事をあたってもらうとして)
そのVirtIOデバイスを作るにあたり必要な情報をまとめて備忘録として残します.
VirtIOデバイス
VirtIOの仕様は以下のリンクから読めます.
Virtual I/O Device (VIRTIO) Version 1.1
VirtIOではゲストマシンとのやり取りについて規定することで様々なデバイスに変身するための汎用性があります.
ネットワークカード(NIC)やブロックデバイス, コンソールデバイスなど様々なデバイスに適用することができます.
VirtIOデバイスはゲストマシンとの接続方法として仕様では以下の3種類があります.
- PCI
- MMIO
- Channel I/O
今回は組み込みに適したMMIOについて深掘りしました.
VirtIOとMMIO
MMIOはメモリマップドI/Oの略ですが, CPUのアドレス空間上にメインメモリやI/Oデバイスが存在する方式です.
VirtIOデバイスがMMIOを用いるためには明示的にゲストマシンへ教える必要があるので, その方法としてはデバイスツリーを用います.
サンプルを以下に示します.
メモリの範囲とサイズを指定し, 割り込みについても記述しています.
// EXAMPLE: virtio_block device taking 512 bytes at 0x1e000, interrupt 42.
virtio_block@1e000 {
compatible = "virtio,mmio";
reg = <0x1e000 0x200>;
interrupts = <42>;
}
デバイスツリーとして組み込むことでLinuxはVirtIOデバイスを認識することができます.
認識されると起動時にデバイスやドライバの初期化がなされデバイスとして/dev/*に現れます.
VirtIOデバイスのレジスタレイアウト
MMIOデバイスとしてのレジスタレイアウトは4.2.2 MMIO Device Register Layoutにあります.
すべてのレジスタは4バイトで構成され, リトルエンディアンです.
オフセット | R/W | 名前 | 値 | メモ |
0x000 | R | MagicValue | 0x74726976 | マジックナンバーでありasciiで”virt”を意味する. この数値をゲスト側が確認してVirtIOデバイスであることを確認する |
0x004 | R | Version | 0x2 | VirtIOデバイスの仕様バージョン Legacyデバイスは0x1を指定 |
0x008 | R | DeviceID | デバイスの種類に応じて設定 0x0であればNIC, 0x1であればブロックデバイスなど | |
0x00C | R | VendorID | デバイスのベンダーID 特に規定は書かれていない | |
0x010 | R | DeviceFeatures | VirtIOデバイスが対応している機能のビットを立ててゲスト側に示す (DeviceFeaturesは合計64ビットありDeviceFeaturesSelによって0~31と32~63を切り替えて読み取ることができる) | |
0x014 | W | DeviceFeaturesSel | 0x0 or 0x1 | DeviceFeaturesSel=0のときDeviceFeatures=0~31 DeviceFeaturesSel=1のときDeviceFeatures=32~63 (DeviceFeaturesSelは最下位ビットのみ有効) |
0x020 | W | DriverFeatures | 有効にする機能に対応したビットを立ててデバイスへ指定する (DeviceFeaturesと同様合計64ビットある) | |
0x024 | W | DriverFeaturesSel | DriverFeaturesSel=0のときDriverFeatures=0~31 DriverFeaturesSel=1のときDriverFeatures=32~63 (DriverFeaturesSelは最下位ビットのみ有効) | |
0x030 | W | QueueSel | 操作するVirtqueueを選択 デバイスの種類によってVirtqueueを使用する個数は異なる (ブロックデバイスは1つ) | |
0x034 | R | QueueNumMax | Virtqueue内で使用可能な最大の要素数 | |
0x038 | W | QueueNum | 最大の要素数を受けて使用する要素数を指定する | |
0x044 | RW | QueueReady | 0x0 or 0x1 | Virtqueueの準備が完了したかを表す. 0x1で完了を示し使用可能である. |
0x050 | W | QueueNotify | Virtqueueに処理すべき新しいバッファがあることを デバイスへ通知する. | |
0x060 | R | InterruptStatus | デバイスが割り込みをした原因のビットを返す ビット0 : 少なくとも1つのアクティブなVirtqueueを使用したことを示す. ビット1 : デバイスの構成が変更された | |
0x064 | W | InterruptACK | 割り込み原因に対処したことをデバイスに通知する | |
0x070 | RW | Status | 現在のデバイスの状態を示す 0以外を書き込むとドライバの進行状況として設定される 0を書き込むとデバイスはリセットする ビット0 : ACKNOWLEDGE このビットによりOS側からVirtIOデバイスと認識される ビット1 : DRIVER デバイスを制御する意思があることを示す ビット2 : DRIVER_OK DRIVERがセットされたことに対しての応答 ビット3 : FEATURES_OK OSとデバイスが機能についてネゴシエーション完了 ビット4 : DEVICE_NEEDS_RESET デバイスに回復不可能なエラーが発生した ビット5 : FAILED OS側で問題が発生しデバイスの使用を断念した 2.1 Device Status Field | |
0x080 0x084 | W | QueueDescLow QueueDescHigh | メモリ アドレス | VirtqueueのDescriptor領域の物理アドレス 64ビット幅なので2つの領域を使用 |
0x090 0x094 | W | QueueDriverLow QueueDriverHigh | メモリ アドレス | VirtqueueのDriver領域の物理アドレス (Available Ringに対応) 64ビット幅なので2つの領域を使用 |
0x0a0 0x0a4 | W | QueueDeviceLow QueueDeviceHigh | メモリ アドレス | VirtqueueのDescriptor領域の物理アドレス (Used Ringに対応) 64ビット幅なので2つの領域を使用 |
0x0fc | R | ConfigGeneration | デバイスの構成のバージョンを表す. (Configを変更した場合に変更される?) | |
0x100+ | RW | Config | デバイスによって自由に使用可能な空間 |
Virtqueue
VirtIOではVirtqueueと呼ばれるリングキューを用いてデバイスとゲストでやり取りを行う.
NICでは最低でも2つ, ブロックデバイスでは1つのVirtqueueを使用する.
VirtqueueにはAvailable RingとUsed Ringと呼ばれるリングキューとDescriptor Tableと呼ばれる処理対象を示すバッファが存在する.
リングキューはキュー内のエントリーの先頭を示すidxと状況を示すフラグを持っている.
idxを元にring[]を辿るとDescriptor Tableのidxが得られる.
得られた値をもとにDescriptor Tableへアクセスすることで処理内容を得ることができる.
Descriptor Tableの要素には以下の内容が含まれている.
- addr : バッファのメモリアドレス
- len : バッファの長さ
- flags
VRING_DESC_F_NEXT(0x1) : nextフィールドは有効
VRING_DESC_F_WRITE(0x2) : このバッファは書き込み専用(逆は読み取り専用) - next : 次のDescriptor要素のid
Descriptor Tableは一つのエントリーだけで完結するものから複数のエントリーを使うものもある.
(例えばブロックデバイスは3つ使用している)
複数使用している場合にはVRING_DESC_F_NEXTフラグが立っており, nextフィールドに有効な次のエントリー番号が書き込まれている.
VRING_DESC_F_WRITEはバッファのメモリアドレスが読み取りか書き込みかどうかを示す.
lenはそのバッファの読み書きサイズを示す.
まとめ
VirtIOデバイスのフレームワークについて基礎知識から最低限理解すべきVirtqueueについて
経験を踏まえて解説しました.
またMMIOデバイスとして扱うためのレジスタについても解説しました.
VirtIOの仕様書はあるものの体系的な解説は少なくVirtqueueの理解が難しく感じました.
参考文献
[转]virtio之vring 2022/08/24
Virtqueueについての構造について解説されている.
virtio-queue 2022/08/24
Virtqueueについてどういう流れで読み書きしてるか解説されている.
コメント