BitVisorのvmm.driver.pciの指定の仕方

## 概要

2014年11月のBitVisor Summit 3の発表を聞かれた方はご存知のことと思いますが、BitbucketにあるBitVisorの最新版では、BitVisor 1.4までとは準パススルー型ドライバーの有効・無効等を指定する方法が変わり、vmm.driver.pciを使って指定するようになりました。本稿では、その指定方法について述べます。

## 背景

BitVisorの設定は、make configで編集する.configファイルと、バイナリーに埋め込まれるdefconfigファイル、または、boot/loginかboot/login-simpleのプログラムで使用するbitvisor.confで行います。.configファイルの設定は、コンパイル時に解釈され、モジュールそのもののリンクの有無、あるいは、マクロ定義によって反映されます。defconfigファイル、または、bitvisor.confファイルの設定は、コード上でconfig構造体として参照され、反映されます。

BitVisor 1.4までは、各準パススルー型ドライバー自身がconfig構造体を参照し、有効にされているかどうかをチェックして、ドライバーの登録を行っていました。この方法には以下のような欠点がありました。

- 同じデバイスが複数存在している場合、同じデバイスすべてに対してドライバーの有効・無効を設定することはできたが、特定のひとつに対してドライバーの有効・無効を設定する手段がなかった (例えば複数のIntel NICが搭載されているとして、特定のひとつを対象とできない等)
- 特定のデバイスに対して通常とは異なるドライバーを割り当てることができなかった (新規デバイスIDを持つNICとか、PCIデバイス隠蔽用の特殊なドライバーを割り当てるとか)
- ドライバーの有効・無効の設定のみで、通信内容やアクセス内容に対する処理が固定されていた (ATA/AHCIストレージは暗号化、NICはVPN、等)

これらの欠点を解消するべく導入されたのが、vmm.driver.pciです。各ドライバーがconfig構造体を参照していた部分を改め、PCIデバイスに対する共通の処理においてどのドライバーを適用するかを決定します。単なる0, 1ではなく、文字列による指定で、デバイスIDやPCIバスアドレスによるデバイス指定ができるようになった他、ドライバーにどの機能を使わせるかのオプション引数を渡すこともできます。

## 関連研究

BitVisor 1.4までにも、PCIデバイス隠蔽機能は実装されていました。このPCIデバイス隠蔽機能は、任意のデバイスに対して適用できるものであり、vmm.driver.pci_concealに、デバイスを特定する文字列を指定することにより、適用できるようになっていました。vmm.driver.pciに指定する文字列は、このvmm.driver.pci_concealで指定できていたものを発展させた形式となっています。

Windows 95、および、Windows 98以降のMicrosoft Windowsにおいては、Device Managerと呼ばれるインターフェイスから、PCに接続されているデバイスの一覧と、そのデバイスにどのデバイスドライバーが使用されているかを確認できる他、別のデバイスドライバーに切り替えることができます。デバイスのIDやアドレス等の詳細な情報がレジストリと呼ばれるデータベースに格納されており、デバイスの検出時にプラグアンドプレイにより自動的にレジストリにエントリーが作成され、Device Managerはそのエントリーを変更することによって、デバイスドライバーを変更する機能を実現しているものとみられます。BitVisorでは、レジストリのようなデータベースは保持しておらず、config構造体に記述された内容に基づいてすべてを決定します。

## 指定方法

### 概要

vmm.driver.pciの指定は、以下の3項目を並べたものをひとかたまりとして、ひとつのドライバーを指定し、複数のかたまりをandを挟んで連結し、最後に,区切りでひとつの文字列に連結したものになります。

1. デバイスの指定 (省略可能)
- ドライバーの指定
- ドライバーオプションの指定 (省略可能)

例えば、PCIのアドレス00:1f.2のデバイスにahciドライバーを、PCIのアドレス02:00.0のデバイスにrtl8169ドライバーをVPN接続として割り当てたいとします。この例では、まず、かたまりをふたつ作ります。ひとつはahciドライバーを指定するものです。

1. デバイスの指定: PCIのアドレス00:1f.2
- ドライバーの指定: ahci
- ドライバーオプションの指定: なし

ふたつ目はrtl8169ドライバーを指定するものです。

1. デバイスの指定: PCIのアドレス02:00.0
- ドライバーの指定: rtl8169
- ドライバーオプションの指定: VPN接続

これらを実際に使用する文字列になおし、andを挟んで連結すると以下のようになります。

1. `slot=00:1f.2`
- `driver=ahci`
- `and`
- `slot=02:00.0`
- `driver=rtl8169`
- `net=vpn`

最後に、,区切りで連結します。

```
slot=00:1f.2, driver=ahci, and, slot=02:00.0, driver=rtl8169, net=vpn
```

`and`で区切られた各条件は、先頭から順にマッチングが行われます。全く同じ同じデバイス指定を複数書いても、最初のものが常に成立し、後に書かれたものは無視されます。

### デバイスの指定として指定できるもの

デバイスの指定として指定できる項目には、以下のものがあります。

```:drivers/pci_match.c
   slot=%02x:%02x.%u           (bus_no, device_no, func_no)
   class=%04x                  (class_code >> 8)
   id=%04x:%04x                (vendor_id, device_id)
   subsystem=%04x:%04x         (sub_vendor_id, sub_device_id)
   revision=%02x               (revision_id)
   rev=%02x                    (revision_id)
   programming_interface=%02x  (programming_interface)
   if=%02x                     (programming_interface)
   class_code=%06x             (class_code)
   header_type=%02x            (header_type)
   device=%s                   (device name)
   number=%d                   (number)
```

`slot`は上の例で使用したPCIアドレス指定です。`class`から`header_type`までは、それぞれ、PCI configuration spaceにある情報を表します。`device`は、ドライバー側で持っているデバイス一覧に基づいて選択する指定方法です。例えば、`device=ahci`と指定すれば、`class_code=010601`を指定したのと同じ意味になります。make configによりコンパイル対象から外されたドライバーのドライバー名は指定できません。

`number`は、そこまでの指定で限定されたデバイスのうち、何番目のものを使用するかを指定するもので、0が最初のデバイスになります。例えば、`device=rtl8169, number=0`とすれば、最初に見つかったrtl8169デバイス、という意味になります。

複数の条件を指定した場合、基本的にはandということになりますが、`number`だけは、指定する位置に意味があります。`class_code=010601, number=1, id=0000:1111`なら、2番目に見つかったclass_code=010601のデバイスが、ID 0000:1111の場合に条件が成立しますが、`id=0000:1111, number=1, class_code=010601`なら、2番目に見つかったid=0000:1111のデバイスが、class_code=010601の場合に条件が成立します。

各項目の右辺には、|記号で区切ることにより複数のor条件を指定することができます。`id=0000:1111|2222:3333`と書けば、id=0000:1111、または、id=2222:3333、という意味になります。また、ワイルドカードとして`*`および`?`が使用できます。これは一般にファイル名で使われるワイルドカードと同じ意味のもので、例えば`id=0000:*`と指定すればベンダーIDが0000のすべてのデバイスを表します。

デバイス指定を省略した場合、次のドライバー指定に基づいて、deviceにドライバー名が指定されたものとみなされます。例えば、`driver=ahci`とだけ指定されると、`device=ahci, driver=ahci`と指定されたものとみなされます。これは、BitVisor 1.4までと同じような設定にしたいときに、冗長になりそうな部分を簡略化したものです。

### ドライバーの指定

ドライバーの指定は、`driver=ドライバー名`の形式になります。ドライバー名には以下のようなものがあります。

- ahci
- ata
- bnx
- conceal
- ehci
- ieee1394
- ieee1394log
- monitor
- pro100
- pro1000
- raid
- rtl8169
- uhci
- vga_intel
- x540

上に書いたように、各ドライバーは通常デバイス一覧を持っています。ただし、concealはPCIデバイス隠蔽のための特殊なドライバーであり、デバイス一覧は持っていません。(正確には、どのデバイスにも合致しないという条件を持っていますので、もし`device=conceal`と書いたらその条件は成立しません。)

concealと同様に、monitorもデバイス一覧は持っていません。これはPCIデバイスのI/Oをモニターするためのドライバーです。

この他に、特別なドライバー指定として`driver=none`があります。これは、条件にマッチするもののドライバーは割り当てたくない、という状況において使用するものです。`and`で区切られた各条件は先頭から順にチェックされることを利用して、特定の条件を持つデバイスのみを除外し、それ以外を対象にしたい、というような条件を書く時に使用します。例えば、最初に見つかったahciデバイスはパススルーとし、それ以外を隠蔽したい、という場合には、`device=ahci, number=0, driver=none, and, device=ahci, driver=conceal`という風に書きます。

### ドライバーオプションの指定

ドライバーオプションはドライバーによって異なります。ネットワーク系のドライバーだと以下のものがあります。

- `net` (すべてのネットワークドライバー): ネットワークの処理モジュールを指定するもの。`vpn`, `ip`, `ippass`, `pass`と空文字列が指定可能。
- `tty` (bnx, pro1000, rtl8169): ログ出力に使用するかどうか。`1`が指定されたデバイスからログが出力される。ログの出力先および出力プロトコルは、vmm.tty_mac_address, vmm.tty_syslog.enable, vmm.tty_syslog.src_ipaddr, および、vmm.tty_syslog.dst_ipaddrで指定する。
- `virtio` (bnx, pro1000): Virtio-netを使用するかどうか。`1`が指定されると各デバイスがvirtio-netデバイスに化ける。`net`が空文字列または`ip`の場合、デバイスは隠蔽モードとなり、隠蔽モードに非対応の場合、または、`virtio=1`の場合はOSの通信内容がすべて破棄される。`net`がそれ以外は準パススルーモードとなり、`virtio`が指定されていない場合、または、`virtio=0`の場合はBitVisorはNICの初期化を行わず、OSによるNICの初期化により通信できるようになる。`virtio=1`の場合はBitVisorが起動直後にNICの初期化を行い、OSはvirtio-netを使用して通信できる。`bnx`はvirtio-netを使用しない準パススルーモードに対応していない。

他に、monitorドライバーもドライバーオプションを使用してモニター内容を決定します。

## コード

コード上は、BitVisor 1.4までは各ドライバーがconfig構造体を参照し、pci_register_driver()関数で登録するかどうかを決定していました。しかし現在は、各ドライバーはリンクされれば常にpci_register_driver()関数で登録を行い、実際にそのドライバーが使用されるかどうかは、PCIデバイスに対する共通の処理に委ねられます。

このため、pci_register_driver()関数に指定するstruct pci_driverが変更されました。BitVisor 1.4までは、idおよびclassメンバーにデバイスIDとクラスの条件をビットマスクと値で記述していましたが、このメンバーは削除されました。代わりにdeviceおよびdriver_optionsメンバーが追加され、どちらも文字列で柔軟な指定を実現します。

deviceメンバーは、前述の、ドライバーが持つデバイス一覧を表します。内容は前述のデバイスの指定で使用した文字列と同一 (ただし`device`や`number`は使用しない) です。例えばahciドライバーでは`"class_code=010601"`が設定されています。

driver_optionsメンバーはドライバーオプションの一覧を表します。オプション名を,で区切ったものになります。例えばpro100ドライバーには`"net"`、bnxドライバーには`"tty,net,virtio"`が設定されています。各オプションはstruct pci_deviceのdriver_optionsメンバーにchar*ポインターの配列の形で渡されます。例えばbnxドライバーに`net=vpn`を指定した場合、struct pci_deviceには、driver_options[1]に`"vpn"`が入った状態となります。指定されてない要素には`NULL`が入ります。

マッチングを行うプログラムはdrivers/pci_match.cにあります。

## 互換性

ドライバーの互換性はなくなりましたが、BitVisor 1.4までのdefconfig並びにbitvisor.confをそのまま使用可能とするための設定変換プログラムが実装されています。よって、従来の設定方法も引き続き使用可能です。しかし、新たに追加されたドライバーを使用したり、ネットワークにVPN以外を使用したりするにはvmm.driver.pciを使用しなければなりません。

設定変換プログラムはdrivers/pci_match_compat.cにあります。例えば、config構造体にvmm.driver.ata=1が指定されている場合の設定を追加する処理は以下のようになっています:

```c:drivers/pci_match_compat.c
        if (config.vmm.driver.ata)
                pci_match_add_compat ("driver=ata,and,driver=ahci,and,"
                                      "driver=raid"); 
```

## まとめ

BitVisor 1.4までの設定方法の欠点と、現在のvmm.driver.pciの指定の仕方について述べました。2014年11月のBitVisor Summit 3の発表を聞かれた方はすでにご存知の内容だったかと思いますが、<del>発表資料がアップロードされていないみたいなので、これが唯一の説明資料かも知れません :-)</del><ins>アップロードされたそうです。(2015-12-15修正)</ins>

どなたかいいドキュメントを書いてくれるとみんな喜びます。