Open62

Virtioについて

junyaUjunyaU

Virtioは仮想環境内で最適化されたデバイスI/O操作をするための準仮想化フレームワーク。
ホストOSとゲストOSの間で共有されたメモリ上にキューを置き、そのキューを利用してデータの入出力を行う。このキューのことをvirtqueuesと呼ぶ。

 lspci                   
05ee:00:00.0 SCSI storage controller: Red Hat, Inc. Virtio console (rev 01)
07a7:00:00.0 System peripheral: Red Hat, Inc. Virtio file system (rev 01)
fd77:00:00.0 3D controller: Microsoft Corporation Device 008e

ワイのwsl環境にもしっかりvirtioデバイスがいた

junyaUjunyaU

ちなみに準仮想化は、ゲストOSが物理ハードウェアへのアクセスを仮想化レイヤを介して行うのではなく、最適化されたインターフェースを通してアクセスすることで、オーバーヘッドを削減しパフォーマンス向上を実現するためのもの。
ここでいう「最適化されたインターフェース」とは、Virtioにおけるvirtqueuesのことを指す。(正確にはVirtio自体がインターフェースだと思うけど)

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/virtualization_host_configuration_and_guest_installation_guide/chap-virtualization_host_configuration_and_guest_installation_guide-para_virtualized_drivers

準仮想化ドライバーはゲストのパフォーマンスを向上し、ゲスト I/O レイテンシーを下げ、ベアメタルレベルまでスループットを増加させます。I/O の高いタスクとアプリケーションを実行する完全に仮想化されたゲストには、準仮想化ドライバーを使用することが推奨されます。

junyaUjunyaU

Virtioを用いた準仮想化デバイスドライバの一例として、virtio_netがある。
これは、Virtioの上に構築されたネットワークデバイスのドライバである。

virtio_netを用いたパケット送信の大まかな流れ

  1. ゲストOSのデバイスドライバから、virtqueuesにパケットを送信する
  2. KVMに制御が移る
  3. KVMがqemu(Virtioのバックエンドを担う)のタスクをスケジューリングする
  4. Virtioのバックエンドがvirtqueuesからパケットを取得し、I/Oのエミュレートをする
junyaUjunyaU

完全仮想化は、エミュレートされたデバイスを通してゲストOSとハイパーバイザの通信を行う。
それに対して準仮想化は、ゲストOSとハイパーバイザ間で直接やり取りを行うからエミュレーションステップがいらない。だから準仮想化のほうがパフォーマンスが良い。

junyaUjunyaU

kvmとqemuの役割の違いがちゃんとまだ分かってないな

qemuの引数にkvmを使うかどうかの引数があった
-enable-kvm

junyaUjunyaU

仮想化について

仮想化は一つのコンピューターリソースを分割して複数に見せたり、複数のコンピューターリソースを一つに見せたりするような技術。仮想マシン上でosを動かせるような環境をシステム仮想マシンと言ったりする。

完全仮想化

完全仮想化はハードウェア環境をソフトウェアで完全に再現することによって、ゲストOSに手を加えることなくそのまま仮想環境に乗せることができる。ゲストOSは疑似的なハードウェア環境を物理ハードウェア環境と勘違いして動作をする。

利点

ゲストOSに手を加えなくてもそのまま動かせる。(互換性がある)

欠点

ハイパーバイザとゲストOSのやり取りが、エミュレートされたハードウェアデバイスを介して行われるため、いちいちエミュレートしたデバイスを経由してそれをハイパーバイザが解釈する必要がある。これによってパフォーマンスのオーバーヘッドが大きくなる。

準仮想化

準仮想化は、ゲストOSが仮想環境で動くことを前提とし、ゲストOSとハイパーバイザが独自の通信方法で直接やり取りを行う仮想化技術。

利点

ハイパーバイザとゲストOSのやり取りにエミュレートされたハードウェアデバイスを挟まないので、オーバーヘッドの削減が見込める。

欠点

互換性がない。仮想環境でしか動かない。

junyaUjunyaU

なんとなくkvmとqemuの動作がつかめてきた

junyaUjunyaU

Linux KVMについて

KVM(Kernel-based Virtual Machine)はLinuxカーネルを仮想マシンの実行環境(要するにハイパーバイザ)として提供するためのカーネルモジュールである。これはカーネルモジュールなのでOSのメモリ管理やプロセススケジューリングなどを積極的に活用して、効率的な仮想化環境を提供できる。
KVMはCPUの仮想化支援機能を用いて仮想マシンを作ることができる。この仮想マシンには仮想CPUが搭載されている。通常、ソフトウェアでエミュレートしたCPUを使うと、実際のCPUで一つの命令で済む場合でも、何十もの命令に置き換えられてしまう(エミュレートして実際のCPUが実行するから)ので、性能が著しく低下する。
しかし仮想化支援機能によって、仮想マシン内の命令実行がホストマシンの物理CPUに直接移譲され命令が実行される。

すなわち、KVMを使う最大の利点はCPUの仮想化支援技術を利用して、効率的な命令実行ができることにある。

QEMUについて

QEMUは仮想マシンソフトウェア(ユーザーアプリ)であり、CPU、メモリ、I/Oデバイスなどのハードウェアコンポーネントをエミュレートすることができる。
普段のOS開発でお世話になってるやつ。これ単体で仮想マシンとして動かすことはできるが、先ほど述べたようにソフトウェアでエミュレートしたCPUはとても効率が悪い。

どのように協調するのか

KVMによって仮想CPUを提供し、QEMUによってメモリやI/Oデバイスを提供するようにすることで、CPUの仮想化支援技術を用いてCPUの命令を効率よく実行しつつ、QEMUによってエミュレートされたメモリやI/Oデバイスを使用できる。


雑なイメージ

junyaUjunyaU

qemuに-enable-kvmオプションを付けて実行するとkvmがCPUのエミュレーションを行ってくれる。

junyaUjunyaU

差を比べたいから自作OSで軽いベンチマークとってみようかな

junyaUjunyaU
qemu-system-x86_64: warning: host doesn't support requested feature: CPUID.80000001H:ECX.svm [bit 2]

kvmと実行したら警告が出た。pcの仮想化設定が有効になってないのかな

junyaUjunyaU

  1. ゲストOSの準仮想化(Virtio)デバイスドライバがVirtio queuesにパケットを送信する。
  2. 送信後にデバイスドライバがKVM(ハイパーバイザ)に処理を渡す(ここでVMExitが発生)
  3. KVMはQEMUのタスクをスケジューリングする
  4. QEMUは、Virtio queuesからメッセージを受け取りI/Oデバイス(ネットワークデバイス)のエミュレートをして、Virtio queuesにパケットを送ったり送らなかったりする
  5. ゲストOSに制御が戻り、Virtio queuesに格納されているデータを処理する
junyaUjunyaU

プロセッサが、仮想マシンの実行からハイパーバイザの実行に切り替わることをVMExitという。
このVMExitは、仮想マシンの実行におけるオーバーヘッドになる。
完全仮想化のデバイスが遅い理由はここにあり、エミュレートされたデバイスのハードウェアレジスタに対するアクセスが検知されるたびにVMExitが走るらしい。
準仮想化デバイスの場合、ゲストOSがキューにメッセージを送ってKVMにトラップするときぐらいしか使われないみたい。VMExitの抑制になるから結果的に早くなるのかな。

junyaUjunyaU

Virtioは準仮想化デバイスとはいえ、ハイパーバイザ上でなければ実現できないような挙動には依存しない。

junyaUjunyaU

virtqueueは以下の領域を持つ。

ディスクリプタエリア

ドライバが各処理要求のディスクリプタチェーンのアドレスや長さを書き込む場所

ドライバエリア

仮想デバイスに処理してほしいディスクリプタチェーンの先頭を書き込むリングバッファ。

デバイスエリア

デバイスが処理を完了したディスクリプタチェーンの先頭が書き込まれるリングバッファ。ドライバはここから処理要求の結果を見る。

junyaUjunyaU

ドライバがデバイスを初期化する流れ

  1. Reset the device.
  2. Set the ACKNOWLEDGE status bit: the guest OS has noticed the device.
  3. Set the DRIVER status bit: the guest OS knows how to drive the device.
  4. Read device feature bits, and write the subset of feature bits understood by the OS and driver to the device. During this step the driver MAY read (but MUST NOT write) the device-specific configuration fields to check that it can support the device before accepting it.
  5. Set the FEATURES_OK status bit. The driver MUST NOT accept new feature bits after this step.
  6. Re-read device status to ensure the FEATURES_OK bit is still set: otherwise, the device does not support our subset of features and the device is unusable.
  7. Perform device-specific setup, including discovery of virtqueues for the device, optional per-bus setup, reading and possibly writing the device’s virtio configuration space, and population of virtqueues.
  8. Set the DRIVER_OK status bit. At this point the device is “live”.
junyaUjunyaU

それぞれのstatus

ACKNOWLEDGE (1)

Indicates that the guest OS has found the device and recognized it as a valid virtio device.

DRIVER (2)

Indicates that the guest OS knows how to drive the device. Note: There could be a significant (or infinite) delay before setting this bit. For example, under Linux, drivers can be loadable modules.

FAILED (128)

Indicates that something went wrong in the guest, and it has given up on the device. This could be an internal error, or the driver didn’t like the device for some reason, or even a fatal error during device operation.

FEATURES_OK (8)

Indicates that the driver has acknowledged all the features it understands, and feature negotiation is complete.

DRIVER_OK (4)

Indicates that the driver is set up and ready to drive the device.

DEVICE_NEEDS_RESET (64)

Indicates that the device has experienced an error from which it can’t recover.

junyaUjunyaU

PCIかMMIOからアクセスできそう。PCIのスキャンは既に実装してた気がするからPCIのほうからアクセスしてみよう

junyaUjunyaU

とりあえず検出したpciデバイス表示させたいから先にlspsiを実装する

junyaUjunyaU

VirtIOデバイスをQEMUに追加する場合、それぞれのデバイスに対応するディスクイメージが必要

junyaUjunyaU

これでvirtio-blkが認識されるようになった

qemu-system-x86_64 -m 1G \
    -drive if=pflash,format=raw,file=OVMF_CODE.fd \
    -drive if=pflash,format=raw,file=OVMF_VARS.fd \
    -drive if=ide,index=0,media=disk,format=raw,file=$disk_img \
    -device nec-usb-xhci,id=xhci \
    -device usb-kbd \
    -drive if=none,id=drive-virtio-disk0,format=raw,file=$storage_img \
    -device virtio-blk-pci,drive=drive-virtio-disk0,id=virtio-disk0 \
    -monitor stdio -S -gdb tcp::12345
junyaUjunyaU

lspciコマンドを雑に作った。検出できていないデバイス2個あるけどVirtioの実装一通り終わったら直そう

junyaUjunyaU

Virtioで定義されている整数データ型
u8, u16, u32, u64 : 符号なし整数
le16, le32, le64 : リトルエンディアンの符号なし整数
be16, be32, be64 : ビッグエンディアンの符号なし整数

junyaUjunyaU

Virtqueuesについて

The mechanism for bulk data transport on virtio devices is pretentiously called a virtqueue. Each device can have zero or more virtqueues.
Driver makes requests available to device by adding an available buffer to the queue, i.e., adding a buffer describing the request to a virtqueue, and optionally triggering a driver event, i.e., sending an available buffer notification to the device.
Device executes the requests and - when complete - adds a used buffer to the queue, i.e., lets the driver know by marking the buffer as used. Device can then trigger a device event, i.e., send a used buffer notification to the driver.
Device reports the number of bytes it has written to memory for each buffer it uses. This is referred to as “used length”.
Device is not generally required to use buffers in the same order in which they have been made available by the driver.
Some devices always use descriptors in the same order in which they have been made available. These devices can offer the VIRTIO_F_IN_ORDER feature. If negotiated, this knowledge might allow optimizations or simplify driver and/or device code.

junyaUjunyaU

バージョン2.7以前の名称 → 現在の名称

  • Descriptor Table → Descriptor Area
  • Available Ring → Driver Area
  • Used Ring → Device Area
junyaUjunyaU

VirtioのPCIデバイスレイアウト

設定方法

  • I/O領域やメモリ領域を介して、デバイスの設定が行われる。これらの領域は、Virtio Structure PCI Capabilitiesによって指定される。
  • デバイス構成内のすべてのフィールドはリトルエンディアン。
  • 64ビットフィールドは、二つの32ビットフィールドとして扱われる。

ドライバがアクセスするには

  • 8ビットフィールドに対して、8ビット幅のアクセス。
  • 16ビットフィールドには、16ビット幅でアラインしてアクセス。
  • 32ビットおよび64ビットのフィールドに対しては、32ビット幅でアラインされたアクセス。
  • 64ビットのフィールドは、ドライバが高い32ビット部分と低い32ビット部分をそれぞれ独立してアクセスできる。

Virtio Structure PCI Capabilities

Virtio Structure PCI Capabilitiesは、VirtioのPCIデバイスにおけるPCI Capabilitiesの領域を指す。
PCI CapabilitiesはPCIコンフィギュレーション空間における拡張領域。0x00から0x3FはPCIデバイス共通の設定が含まれており、0x40以降が拡張領域(これがPCI Capabilities)となる。

Virtioデバイスのコンフィギュレーションレイアウトは以下の構造体たちで構成されている。

  • Common configuration
  • Notifications
  • ISR Status
  • Device-specific configuration (optional)
  • PCI configuration access

これらの構造体は、BAR(Base Address Register)か VIRTIO_PCI_CAP_PCI_CFGというPCIコンフィギュレーション空間内の特別なフィールドからアクセスできる。

struct virtio_pci_cap { 
        u8 cap_vndr;    /* Generic PCI field: PCI_CAP_ID_VNDR */ 
        u8 cap_next;    /* Generic PCI field: next ptr. */ 
        u8 cap_len;     /* Generic PCI field: capability length */ 
        u8 cfg_type;    /* Identifies the structure. */ 
        u8 bar;         /* Where to find it. */ 
        u8 id;          /* Multiple capabilities of the same type */ 
        u8 padding[2];  /* Pad to full dword. */ 
        le32 offset;    /* Offset within bar. */ 
        le32 length;    /* Length of the structure, in bytes. */ 
};

PCI Capabilitiesに格納されているvirtio_pci_capのbar&offsetから該当のbarとそのオフセットを見つける→ barの特定オフセットにcfg_typeに応じた構造体が格納されている。
PCI Capabilitiesからvirtio_pci_cap を探すときは、cap_nextを走査するイメージ。

junyaUjunyaU

Common configuration

struct virtio_pci_common_cfg { 
        /* About the whole device. */ 
        le32 device_feature_select;     /* read-write */ 
        le32 device_feature;            /* read-only for driver */ 
        le32 driver_feature_select;     /* read-write */ 
        le32 driver_feature;            /* read-write */ 
        le16 config_msix_vector;        /* read-write */ 
        le16 num_queues;                /* read-only for driver */ 
        u8 device_status;               /* read-write */ 
        u8 config_generation;           /* read-only for driver */ 
 
        /* About a specific virtqueue. */ 
        le16 queue_select;              /* read-write */ 
        le16 queue_size;                /* read-write */ 
        le16 queue_msix_vector;         /* read-write */ 
        le16 queue_enable;              /* read-write */ 
        le16 queue_notify_off;          /* read-only for driver */ 
        le64 queue_desc;                /* read-write */ 
        le64 queue_driver;              /* read-write */ 
        le64 queue_device;              /* read-write */ 
        le16 queue_notify_data;         /* read-only for driver */ 
        le16 queue_reset;               /* read-write */ 
};

ドライバは以下のフィールドに書き込んではいけない。

  • device_feature
  • num_queues
  • config_generation
  • queue_notify_off
  • queue_notify_data

queue_enable で virtqueue を有効にする前に、他の virtqueue フィールドを設定しなければならない。

device_status に 0 を書き込んだ後、ドライバはデバイスを再初期化する前に device_status の読み込みが 0 を返すのを待たなければならない。

ドライバは queue_enable に 0 を書き込んではいけない。

junyaUjunyaU

Notifications

Notificationsは、VIRTIO_PCI_CAP_NOTIFY_CFGを使用して見つける必要がある。

struct virtio_pci_notify_cap { 
        struct virtio_pci_cap cap; 
        le32 notify_off_multiplier; /* Multiplier for queue_notify_off. */ 
};

virtqueue の BAR 内のキュー通知アドレスの求め方

cap.offset + queue_notify_off * notify_off_multiplier
junyaUjunyaU

ISR status

Virtio PCIデバイスにおいて割り込みステータスを管理するための特定の機能を提供する。このCapabilityにより提供されるISR(Interrupt Status Register)は、ドライバがデバイスからの割り込みを適切に処理するのに必要な情報を提供する。

PCIコンフィギュレーション空間内の特定のオフセットに配置される。このオフセットは、virtio_pci_cap 構造体の offset フィールドを通じて特定される。この構造体はbar フィールドによって指定されたBARを介してアクセスされる場合が多い。

ISRの構造

Bits 0 1 2 to 31
Purpose Queue Interrupt Device Configuration Interrupt Reserved

ドライバが割り込みを受信したときにどこからの割り込みかを判断するためにISRステータスレジスタを読みだす感じか。

junyaUjunyaU

MSIとMSI-Xの違いをちゃんと理解していない(拡張版ってことしか知らん)

junyaUjunyaU

virtioの仕様としてサポートしているのがINTxとMSI-X。
仕様書にMSIの言及は特になし。

If MSI-X capability is disabled:
Read the ISR Status field, which will reset it to zero.
If the lower bit is set: look through all virtqueues for the device, to see if any progress has been made by the device which requires servicing.
If the second lower bit is set: re-examine the configuration space to see what changed.
If MSI-X capability is enabled:
Look through all virtqueues mapped to that MSI-X vector for the device, to see if any progress has been made by the device which requires servicing.
If the MSI-X vector is equal to config_msix_vector, re-examine the configuration space to see what changed.

あ、でも上記の仕様書の記載からMSIも使えそうな雰囲気が感じ取れる。

やっぱむりっぽい。MSI-Xの対応しなきゃかあ...めんどい

junyaUjunyaU

MSIについて

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/kernel/introduction-to-message-signaled-interrupts

Trigger Mode

割り込み信号がどのように発生するかを定義する。

  • Edge Triggered : 割り込み信号のエッジ(信号の変化)が割り込みを発生させる。信号が低から高に変わる瞬間(立ち上がりエッジ)や、高から低に変わる瞬間(立ち下がりエッジ)に割り込みがトリガーされる。
  • Level Triggered : 割り込み信号のレベル(信号の状態)が割り込みを発生させる。信号が特定のレベル(高か低)に保持されている間割り込みがアクティブになる。

Delivery Mode

CPUに割り込みがどのように配信されるかを指定する。

  • Fixed : 固定配信モード。指定された特定のCPUコアに対して割り込みを配送する。このモードを使用することが一般的。
  • Lowest Priority : 最低優先度配信モード。MSIでは使わない。
  • SMI, NMI, INIT, ExtINT : MSIでは使わない。
junyaUjunyaU

MSI

https://www.intel.com/content/www/us/en/docs/programmable/683140/22-1-5-0-0/msi.html

Capability

  • Message AddressとMessage Dataは、Header部分の直後に配置されている。

MSI-X

https://www.intel.com/content/www/us/en/docs/programmable/683140/22-1-5-0-0/msi-x.html

Capability

  • Message AddressとMessage Dataは、BARで指定されるMMIO領域に配置されている。

MSI-X Table


各割り込みについての詳細設定を格納するテーブル。

  • Vector Control : 割り込みのコントロールオプション。割り込みのマスクとかも含まれていて個別の割り込みを動的に無効/有効にすることができる。
  • Message Data : 割り込みに関連するデータ(割り込みベクタとか)が含まれる。
  • Message Address : 割り込みが送信されるプロセッサのローカルAPICのアドレスなどを指定する。

MSI-X PBA Table


MSI-X PBA(Pending Bit Array) Tableはアクティブながらまだ処理されてない割り込みを追跡するためのテーブル。
ドライバや割り込みハンドラが割り込みを効率的に処理するために、どの割り込みがペンディング状態にあるかをすぐに確認できる。

共通している構造

Message Address Register

Message Data Register

junyaUjunyaU

大まかなMSI-Xの設定手順

  1. PCIデバイスを検出する
  2. Capability Pointerの取得
  3. MSI-X Capabilityの探索
  4. MSI-X Capabilityエントリから、MSI-X TableとPBA Tableのアドレス取得
  5. MSI-X Tableの設定
  6. 割り込みの有効化

って感じか。意外とそこまで複雑ではなさそうだった


MSI-X Tableの格納されているBARとそのオフセットを取れた

PBA TableのほうはBARとオフセットともにゼロだった。これは、virtio_blkでは使わないということかな?

junyaUjunyaU

msi_x_capabilityの設定コード書いてみた。動くかな。

void write_msi_x_capability(const device& dev,
                            const msi_x_capability& msix_cap,
                            uint64_t msg_addr,
                            uint32_t msg_data)
{
	uint64_t table_bar = read_base_address_register(dev, msix_cap.table_bar);

	auto* msix_table =
			reinterpret_cast<msix_table_entry*>(table_bar + msix_cap.table_offset);

	msix_table->msg_addr = msg_addr & 0xffffffffU;
	msix_table->msg_upper_addr = msg_addr >> 32;
	msix_table->msg_data = msg_data;

	// Memory barrier to ensure writes are not reordered
	asm volatile("mfence" ::: "memory");
}
junyaUjunyaU

BARのオフセットとインデックスの取得方法を間違えている可能性がある

junyaUjunyaU

見逃している設定があった。

The maximum table size is 2048 entries. Each 16-byte entry is divided in 4 fields as shown in the figure below. The MSI-X table can be accessed on any BAR configured. The base address of the MSI-X table must be aligned to a 4 KB boundary.

どうやらMSI-X tableのアドレスは4KiB境界アラインする必要があるらしい。

junyaUjunyaU

長らく詰まっていたMSI-Xようやく機能した...

junyaUjunyaU

動かなかった原因

Specifies the memory address offset for the MSI-X Table relative to the BAR base address value of the BAR number specified in MSI-X Table BAR Indicator,[2:0] above. The address is extended by appending 3 zeros to create quad-word alignment.

オフセットを8バイト境界にアラインするために、左に3ビットシフトする必要があった。

uint64_t bar_addr = read_base_address_register(dev, msix_cap.table.bits.bar);
bar_addr &= 0xffff'ffff'ffff'f000U;
bar_addr += msix_cap.table.bits.offset << 3;
auto* table_entry = reinterpret_cast<msix_table_entry*>(bar_addr);

barのアドレスを4KIBアラインしたものと、オフセットを3bit左にシフトしたものを足すことで、MSI-X Tableのアドレスを得ることができた。もっとしっかり仕様書読むべきだったと反省。

junyaUjunyaU

Virtio Structure PCI Capabilitiesはどこから取れるんだろうか。

junyaUjunyaU

0x34オフセットからcapability listを走査する。

junyaUjunyaU


走査してみた。cap_idが9のものがvirtioのcapability。ちなみに17(0x11)はMSI-X用のCapabilityである。

junyaUjunyaU

Capability読み込んでみたけどbar以降がちゃんと読み込めてない気がする

junyaUjunyaU

PCI Configuration AccessのCap(config_type 5)だけが正しく取得できていない

junyaUjunyaU

問題なかった。仕様書見返したらPCI Configuration AccessのCapに至ってはそういう仕様だった。

The VIRTIO_PCI_CAP_PCI_CFG capability creates an alternative (and likely suboptimal) access method to the common configuration, notification, ISR and device-specific configuration regions.
The capability is immediately followed by an additional field like so:
struct virtio_pci_cfg_cap {
struct virtio_pci_cap cap;
u8 pci_cfg_data[4]; /* Data for BAR access. */
};
The fields cap.bar, cap.length, cap.offset and pci_cfg_data are read-write (RW) for the driver.
To access a device region, the driver writes into the capability structure (ie. within the PCI configuration space) as follows:
The driver sets the BAR to access by writing to cap.bar.
The driver sets the size of the access by writing 1, 2 or 4 to cap.length.
The driver sets the offset within the BAR by writing to cap.offset.
At that point, pci_cfg_data will provide a window of size cap.length into the given cap.bar at offset cap.offset.

↑ これを要約すると

VIRTIO_PCI_CAP_PCI_CFGのケイパビリティはvirtio_pci_capを拡張した以下のような構造になっている。

struct virtio_pci_cfg_cap { 
        struct virtio_pci_cap cap; 
        u8 pci_cfg_data[4]; /* Data for BAR access. */ 
};

それぞれのフィールド

  • cap.bar : デバイスのアクセスしたいBARインデックスを書き込む。
  • cap.length : アクセスするデータのサイズを決める。1~4 byte。
  • cap.offset : BAR内でアクセスするデータの開始位置。
  • pci_cfg_data : 上の三つの設定が終わった後にこのフィールドにアクセスすると指定した位置の読み書きが可能なデータウィンドウとして機能する。

他のVirtio Structure PCI Capabilitiesの構造体はのbarやlength,offsetなのは読み取り専用なのに対して、VIRTIO_PCI_CAP_PCI_CFGにおいては書き込み用のフィールドになる。

junyaUjunyaU

自分のosではvirtio_pci_capを以下のように定義することにした。

struct virtio_pci_cap {
	union {
		uint32_t data;

		struct {
			uint8_t cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
			uint8_t cap_next; /* Generic PCI field: next ptr. */
			uint8_t cap_len;  /* Generic PCI field: capability length */
			uint8_t cfg_type; /* Identifies the structure. */
		} __attribute__((packed)) fields;
	} first_dword;

	union {
		uint32_t data;

		struct {
			uint8_t bar;		/* Where to find it. */
			uint8_t id;			/* Multiple capabilities of the same type */
			uint8_t padding[2]; /* Pad to full dword. */
		} __attribute__((packed)) fields;
	} second_dword;

	uint32_t offset; /* Offset within bar. */
	uint32_t length; /* Length of the structure, in bytes. */

	virtio_pci_cap* next;
} __attribute__((packed));

ロードしやすいようにunionでまとめて、アクセスしやすいように数珠つなぎにした。

junyaUjunyaU

登録されてるcapabilityを全て検出することができた。

次は、それぞれのcapabilityに設定を登録していく必要がある。