“Error 43” in GPU passthrough w/ QEMU/kvm (NVIDIA)

表題のとおり。

Twitter でうめいていたら色々 advice をもらいかけたのですが色々試した上でのアレなので現状をまとめようと思った

preliminary

  • ホスト環境は Intel P55 chipset と Core i7 860(Lynnfield,Nehalem な世代のアーキテクチャ)という今やかなり古くなってしまった環境
  • もともとメインの Windows 環境にしていたがいまは Desktop 用途には止めて server 用途送り。Gentoo/Linux が入ってる。
  • MSI GTX 1060 6G OC(ショートなやつ)を入手した
  • QEMU/kvm で passthrough させれば UEFI 環境の上で Windows を boot させて遊べると思った
  • libvirt 4.5.0-r1,QEMU 2.12.1,Linux 4.14.83-gentoo

経緯

まず Linux の kernel cmdline を次のようにした

/vmlinuz-4.14.83-gentoo root=/dev/sda5 ro rootflags=subvol=root rootfstype=btrfs quiet console=null intel_iommu=on iommu=pt cgroup_enable=memory swapaccount=1 vfio-pci.ids=10de:1c03,10de:10f1,8086:3b56,8086:3b34 vfio_iommu_type1.allow_unsafe_interrupts=1 threadirqs default_hugepagesz=1G hugepagesz=1G hugepages=4 video=nvidiafb:off,vesa:off

vfi-pci.ids で指定しているのは GPU,GPU audio,on-board audio,on-board USB hubである。iommu も有効化した。 また,kernel config で nvidiafb や vesafb,efifb は無効化してしまっているため framebuffer は誰も掴んでいない。 そもそもホストマシンが古いせいで BIOS としてしか起動できてないですが……。

次に,GPU の vbios を用意する。本来は対象の GPU を一度 secondary GPU にしてから /sys interface とかから読むとキレイな ROM が出せるっぽい。 私の環境では諸事情で primary GPU のままにしか出来ていないので,Windows の GPU-Z というソフトウェアで有名な techpowerup のサイトから探した。https://www.techpowerup.com/vgabios/

techpowerup の BIOS collection にあるものは primary GPU として起動されたものから吸い出されたものばかりらしい。https://github.com/Matoking/NVIDIA-vBIOS-VFIO-Patcher/blob/master/nvidia_vbios_vfio_patcher.py を使って patch することで解決。

この patch された ROM は /var/lib/libvirt/vbios/patched-bios.bin として配置しておく。

その後次のコマンドで virt-install をしてみた。

#!/bin/sh

virt-install \
    --name Windows \
    --boot uefi \
    --machine pc \
    --features kvm_hidden=on \
    --host-device 01:00.0 --host-device 01:00.1 --host-device 00:1b.0 --host-device 00:1a.0 \
    --ram 6114 \
    --cpu host \
    --vcpus 4 \
    --disk pool=default,size=128,format=qcow2 \
    --disk /mnt/exports/share/Windows.iso,device=cdrom \
    --network type=bridge,source=br0,model=e1000 \
    --os-type=windows \
    --os-variant=win10

私の環境では X などが存在しないし色々あって SPICE のような VNC は使いづらい。なので,virt-install したところでどこにも何も表示されない

# virsh destroy Windows
# virsh edit Windows

そこで一度 VM を停止し domain を編集する。

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>

QEMU のコマンドオプションを直接追加するためにまず XML の名前空間を変更。

  <features>
    <hyperv>
      ...
      <vendor_id state='on' value='0123456789ab'/>

QEMU の -cpu に hv_vendor_id=0123456789ab というオプションが追加されるようにする。

  <cpu mode='host-passthrough' check='none'>
    <topology sockets='1' cores='2' threads='2'/>
    <feature policy="disable" name="hypervisor"/>

QEMU の -cpu に -hypervisor というオプションを追加する。これで Windows のタスクマネージャーなども vCPU と認識しなくなる。 これは cpuid 命令の結果に出てくる文字列を一部隠すっぽい。

    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
      </source>
      <rom bar='on' file='/var/lib/libvirt/vbios/patched-bios.bin'/>
      <address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
    </hostdev>

passthrough 対象の GPU の bus や slot が書いてある <hostdev> のセクションに <rom ... という形で patch 済み ROM の path を指定する。

  <qemu:commandline>
    <qemu:arg value='-set'/>
    <qemu:arg value='device.hostdev0.x-vga=on'/>
    <qemu:arg value='-set'/>
    <qemu:arg value='device.hostdev0.multifunction=on'/>
  </qemu:commandline>

passthrough する GPU について x-vga=on と multifunction=on も足しておく。

この後

virsh start Windows
でインストール作業を再開。既に passthrough は成功しており,画面には Windows の installer が表示され USB hub も passthrough しているのでこのマシンに挿したキーボードとマウスで操作も出来る

Windows install のあと

https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.isoをダウンロード,マウントしたり空のディスクを追加して bus を scsi に設定したりしておく。

    <controller type='scsi' index='0' model='virtio-scsi'/>
というコントローラを追加しておくと virtio-scsi が使われる。 その後,再起動して VM の構成変更を反映したのち,ダウンロードした iso から virtio driver や qemu-guest agent をインストールしておく。

qemu-guest agent を追加するときは

    <channel type='unix'>
      <source mode='bind' path='/var/lib/libvirt/qemu/win10.agent'/>
      <target type='virtio' name='org.qemu.guest_agent.0'/>
      <address type='virtio-serial' controller='0' bus='0' port='1'/>
    </channel>
という風に channel を追加しておくと,
virsh reboot --mode agent Windows
virsh shutdown --mode agent Windows
という風に virsh から reboot や shutdown をしても guest OS がちゃんと正しく終了して安全。

経過

その後 NVIDIA の公式 driver をインストールしても error 43 が出てくる。画面表示は出来ているため,NVIDIA の driver の仮想化検知にひっかかったものと思われるがそれを騙す設定は既にしてあるので不明。

また, デバイスマネージャーから GPU のデバイスインスタンスパスを調べた

これはこの後に,regedit.exe を用いて, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\PCI\調べた GPU のデバイスインスタンスパス\Device Parameters\Interrupt Management\MessageSignaledInterruptProperties に DWORD 値の subkey を MSISupported という名前で追加し値を 1 に設定するというのに用いた。

これによって INTx ではなく MSI/MSI-X での割り込みを使うようになったはずだが,デバイスマネージャーで表示を“リソース(種類別)”にし割り込みを確認しても GTX 1060 の割り込みの番号が正の値で表示されているため INTx の割り込みにしかなっていない。これも不明。

cf. https://forums.guru3d.com/threads/windows-line-based-vs-message-signaled-based-interrupts.378044/

<clock offset='localtime'> セクションから hypervclock を取り除く,virtio driver を使っている device を全て QEMU の emulation に戻す,qemu-guest agent を止めるなどしてみたがこれもイマイチ効果がない。

そんなわけで,IOMMU は効いていて passthrough も出来てるのに NVIDIA の driver のせいで今一歩うまくいかないのでだんだん NVIDIA がキライになってきた。

現在

QEMU でエミュレーションする chipset を pc-i440fx にした上で hyperv 関連や virtio 関連を徹底的に排除した設定で Windows Install をしてみたが,途中で止まる。

libvirt XML 全文

virtio driver を使っている版でとりあえず今の VM の設定を以下に示す。

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
  <name>Windows</name>
  <uuid>2cfc8437-f5db-4fb0-8c81-b207983ad1ac</uuid>
  <memory unit='KiB'>4194304</memory>
  <currentMemory unit='KiB'>4194304</currentMemory>
  <vcpu placement='static'>4</vcpu>
  <os>
    <type arch='x86_64' machine='pc-q35-2.12'>hvm</type>
    <loader readonly='yes' type='pflash'>/usr/share/edk2-ovmf/OVMF_CODE.fd</loader>
    <nvram>/var/lib/libvirt/qemu/nvram/Windows_VARS.fd</nvram>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic eoi='on'/>
    <hyperv>
      <relaxed state='on'/>
      <vapic state='on'/>
      <spinlocks state='on' retries='8191'/>
      <vendor_id state='on' value='0123456789ab'/>
    </hyperv>
    <kvm>
      <hidden state='on'/>
    </kvm>
    <vmport state='off'/>
  </features>
  <cpu mode='host-passthrough' check='none'>
    <topology sockets='1' cores='2' threads='2'/>
    <feature policy="disable" name="hypervisor"/>
  </cpu>
  <clock offset='localtime'>
    <timer name='rtc' tickpolicy='catchup' track='guest'/>
    <timer name='pit' tickpolicy='delay'/>
    <timer name='hpet' present='no'/>
  </clock>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <pm>
    <suspend-to-mem enabled='no'/>
    <suspend-to-disk enabled='no'/>
  </pm>
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/Windows.qcow2'/>
      <target dev='sda' bus='scsi'/>
      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <source file='/usr/share/drivers/windows/virtio-win-0.1.141.iso'/>
      <target dev='sdc' bus='scsi'/>
      <readonly/>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
    <controller type='scsi' index='0' model='virtio-scsi'>
      <address type='pci' domain='0x0000' bus='0x09' slot='0x00' function='0x0'/>
    </controller>
    <controller type='sata' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
    </controller>
    <controller type='pci' index='0' model='pcie-root'/>
    <controller type='pci' index='1' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='1' port='0x10'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0' multifunction='on'/>
    </controller>
    <controller type='pci' index='2' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='2' port='0x11'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x1'/>
    </controller>
    <controller type='pci' index='3' model='pcie-to-pci-bridge'>
      <model name='pcie-pci-bridge'/>
      <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
    </controller>
    <controller type='pci' index='4' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='4' port='0x12'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x2'/>
    </controller>
    <controller type='pci' index='5' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='5' port='0x13'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x3'/>
    </controller>
    <controller type='pci' index='6' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='6' port='0x14'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x4'/>
    </controller>
    <controller type='pci' index='7' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='7' port='0x15'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x5'/>
    </controller>
    <controller type='pci' index='8' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='8' port='0x16'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x6'/>
    </controller>
    <controller type='pci' index='9' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='9' port='0x17'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x7'/>
    </controller>
    <controller type='pci' index='10' model='pcie-root-port'>
      <model name='pcie-root-port'/>
      <target chassis='10' port='0x8'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
    </controller>
    <controller type='virtio-serial' index='0'>
      <address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
    </controller>
    <controller type='usb' index='0' model='qemu-xhci'>
      <address type='pci' domain='0x0000' bus='0x0a' slot='0x00' function='0x0'/>
    </controller>
    <interface type='bridge'>
      <mac address='52:54:00:06:80:2b'/>
      <source bridge='br0'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target type='isa-serial' port='0'>
        <model name='isa-serial'/>
      </target>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <channel type='unix'>
      <source mode='bind' path='/var/lib/libvirt/qemu/win10.agent'/>
      <target type='virtio' name='org.qemu.guest_agent.0'/>
      <address type='virtio-serial' controller='0' bus='0' port='1'/>
    </channel>
    <input type='mouse' bus='ps2'/>
    <input type='keyboard' bus='ps2'/>
    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
      </source>
      <rom bar='on' file='/var/lib/libvirt/vbios/patched-bios.bin'/>
      <address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
    </hostdev>
    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
      </source>
      <address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0'/>
    </hostdev>
    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x00' slot='0x1b' function='0x0'/>
      </source>
      <address type='pci' domain='0x0000' bus='0x07' slot='0x00' function='0x0'/>
    </hostdev>
    <hostdev mode='subsystem' type='pci' managed='yes'>
      <source>
        <address domain='0x0000' bus='0x00' slot='0x1a' function='0x0'/>
      </source>
      <address type='pci' domain='0x0000' bus='0x03' slot='0x02' function='0x0'/>
    </hostdev>
    <memballoon model='none'/>
  </devices>
  <qemu:commandline>
    <qemu:arg value='-set'/>
    <qemu:arg value='device.hostdev0.x-vga=on'/>
    <qemu:arg value='-set'/>
    <qemu:arg value='device.hostdev0.multifunction=on'/>
  </qemu:commandline>
</domain>