NetBSD virtio(4) driver とPCIデバイスID

2020年8月追記: CentOS 6以前 (RHEL 6以前) でも全く同じ問題があることに気付いてしまった。解決方法も同じ。また、machineをq35からi440FXにする方法もある気がしたのでそれも追記。

NetBSDにも[virtioドライバ](https://netbsd.gw.com/cgi-bin/man-cgi?virtio+4+NetBSD-9.0)あるから、とubuntu 18.04上のlibvirt/KVMでNetBSD VM作ろうとしたら、ディスクもNICも認識してくれなかった。昔は動いていた気がするぞ。

`vendor 1af4 product 1041 (ethernet network, revision 0x01) at pci1 dev 0 function 0 not configured`

(緑の文字で)

virtioドライバのコードを覗いてみると、[確かに](https://github.com/NetBSD/src/blob/netbsd-9/sys/dev/pci/virtio_pci.c#L185)デバイスID 0x1041はvirtio_pciドライバにマッチしない。

```sys/dev/pci/virtio-pci.c
static int
virtio_pci_match(device_t parent, cfdata_t match, void *aux)
{
	struct pci_attach_args *pa;

	pa = (struct pci_attach_args *)aux;
	switch (PCI_VENDOR(pa->pa_id)) {
	case PCI_VENDOR_QUMRANET:
		if ((PCI_PRODUCT_QUMRANET_VIRTIO_1000 <=
		     PCI_PRODUCT(pa->pa_id)) &&
		    (PCI_PRODUCT(pa->pa_id) <=
		     PCI_PRODUCT_QUMRANET_VIRTIO_103F))
			return 1;
		break;
	}

	return 0;
}
```

`PCI_PRODUCT_QUMRANET_VIRTIO_1000`と`PCI_PRODUCT_QUMRANET_VIRTIO_103F`は、それぞれ0x1000と0x103f。ここんとこのコード、NetBSDは最初っから変わっていないので、Qemu側が変わったのだろうか。

[これ](https://git.qemu.org/?p=qemu.git;a=blob;f=hw/virtio/virtio-pci.c;h=e92837c42b40ad08de8d6b2f539bb257be9aa715;hb=refs/heads/stable-2.11#l2363)じゃないのかな。

```hw/virtio/virtio-pci.c
static void virtio_net_pci_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
    VirtioPCIClass *vpciklass = VIRTIO_PCI_CLASS(klass);

    k->romfile = "efi-virtio.rom";
    k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
    k->device_id = PCI_DEVICE_ID_VIRTIO_NET;
    k->revision = VIRTIO_PCI_ABI_VERSION;
    k->class_id = PCI_CLASS_NETWORK_ETHERNET;
    set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
    dc->props = virtio_net_properties;
    vpciklass->realize = virtio_net_pci_realize;
}
```

PCI_DEVICE_ID_VIRTIO_NETは[0x1000](https://git.qemu.org/?p=qemu.git;a=blob;f=include/hw/pci/pci.h;h=8d02a0a38394db9f2553fbb3e2d8d3a9c40a82e8;hb=refs/heads/stable-2.11#l80)。0x1041はどこから出てくる?

[仕様](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html)を見てみよう。[これ](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1020002)かな。

> Any PCI device with PCI Vendor ID 0x1AF4, and PCI Device ID 0x1000 through 0x107F inclusive is a virtio device. The actual value within this range indicates which virtio device is supported by the device. The PCI Device ID is calculated by adding 0x1040 to the Virtio Device ID, as indicated in section 5. Additionally, devices MAY utilize a Transitional PCI Device ID range, 0x1000 to 0x103F depending on the device type.

なんと、NetBSDのドライバが認識する0x1000から0x103fのデバイスIDは、暫定IDだそうで。

改めてQemuのソースコードに戻ってtransitionalで検索を[かける](https://git.qemu.org/?p=qemu.git;a=blob;f=hw/virtio/virtio-pci.c;h=e92837c42b40ad08de8d6b2f539bb257be9aa715;hb=refs/heads/stable-2.11#l1599)と、

```sys/dev/pci/virtio-pci.c
static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
{
// 略
    if (legacy) {
        /* legacy and transitional */
        pci_set_word(config + PCI_SUBSYSTEM_VENDOR_ID,
                     pci_get_word(config + PCI_VENDOR_ID));
        pci_set_word(config + PCI_SUBSYSTEM_ID, virtio_bus_get_vdev_id(bus));
    } else {
        /* pure virtio-1.0 */
        pci_set_word(config + PCI_VENDOR_ID,
                     PCI_VENDOR_ID_REDHAT_QUMRANET);
        pci_set_word(config + PCI_DEVICE_ID,
                     0x1040 + virtio_bus_get_vdev_id(bus));
        pci_config_set_revision(config, 1);
    }
```

後から上書きしてた! Qemuのdevice property disable-legacyとdisable-modernで変更するらしい。

libvirt経由で起動する場合、[model属性で](https://libvirt.org/formatdomain.html#elementsVirtioTransitional)変えるらしい。`<model type="virtio-transitional"/>` (NICの場合) なら、transitional、すなわちNetBSDのvirtio(4)でも認識可能。が、この機能はLibvirt 5.2.0以降の追加で、ubuntu 18.04の4.0.0にはまだない。4.0.0の段階では、

> This device will work like a virtio-non-transitional device when plugged into a PCI Express slot, and like a virtio-transitional device otherwise; libvirt will pick one or the other based on the machine type. This is the best choice when compatibility with libvirt versions older than 5.2.0 is necessary, but it's otherwise not recommended to use it.

PCI Expressスロットではなく、PCIスロットに刺すようにすればNetBSDでも認識される。libvirtでは`<address>`要素で指定。PCI Expressはpoint-to-point型で、一つのバスに一つのデバイスのみが刺さるので、

```
    <interface type='direct'>
      <mac address='XX:XX:XX:XX:XX:XX'/>
...
      <address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/
>
    </interface>
```

という感じで、他にbus='0x04'のデバイスはない。bus='0x04'は、controller pcie-root-portのpci.4で定義されているはず。

一方PCIは単一のバスに複数のデバイスを挿せるので、例えばbus='0x00'には他にも複数のデバイスがある。

```
    <interface type='direct'>
      <mac address='XX:XX:XX:XX:XX:XX'/>
...
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/
>
    </interface>
```

のように指定すればよい。または、type要素のmachine属性を、q35からPCI Expressのないi440FXにしてもよいはず。

NetBSDのproblem reportを検索したら、[既報](https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=53372)であった。