Skip to content

PCI Passthrough (vt-d, vt-x) Multi-OS Workstation with qemu/kvm on AsRock Z87 Exterme 9/ac

Introduction

This article describes how to get passthrough working using the AsRock Z87 Extreme 9/ac. Main hardware passed through is two NVidia Quaddro and two USB-3 Cards.

Overview

This workstation build is running two virtualized 64-bit guest operating systems, each with full GPU support via native drivers The dom-0 is another 64-bit host system which using the i915 graphics.

Guest operating systems are: Windows10 via OVMF and Debian Linux via seabios which demonstrates use of both firmwares. The host operating system is Ubuntu-Gnome variant of the 16.04 LTS for ease of setup.

Patching the kernel is required with this motherboard because of the separation of the iommu groups. Kernel patches are provided via github. Patching the kernel introduces security issues !

Ressources

Hardware overview

The system has been successfully built with the setup described below. Other Nvidia PCIe cards than the Quadro can be used but might require more effort to get to work properly. Eventhough the AsRockZ87 board has a great many USB connectors plus USB headers on the motherboard, it actually only has two separatable USB controllers. It gets better: the first has 14x USB 3.0 & 2x USB 2.0 and the second has 4x USB 2.0, so assigning the first to a VM rids you of quite a lot of USB ports. Additionally getting usable USB controllers is tricky, esp. the very common VLA/VIA chipsets will not run stable on the host OS. The chipsets i found usable are mentioned in the list below.

  • AsRock Z87 Extreme 9/ac Motherboard
  • Intel i7-4770, hyperthreaded quad core @ 3.4 GHz
  • 32GB/DDR3 RAM (4x8GB)
  • Samsung SSD 850 EVO 500GB
  • Samsung SSD 840 EVO 250GB
  • PNY Nvidia Quadro PNY Quadro 2000K
  • HP Nvidia Quadro PNY Quadro 4000
  • Alcor chipset USB 3.0 PCIe controller
  • PCIe USB 3.0 and 3.1 controllers (
  • 1 PS/2 Keyboard. Had issues accessing BIOS/bootloaders via USB keyboard.
  • 2 USB Keyboards and 3 USB Mouses for the host, guest OSes.
  • Corsair AX760 modular PSU
  • Monitor with multiple input channel support, one used to view each system.

Setup Walkthrough

Verify Hardware Support

Make sure your Intel Processor supports vt-d before embarking on this journey. This can be checked via UEFI Setup under Advanced -> Chipset Configuration -> VT-d Capability -> Supported.

AsRock UEFI Setup

Boot into it pressing F2. If you're having trouble with USB, connect a PS/2 keyboard.

  • OC Tweaker -> Multi Core Enhancement -> Disabled
  • Advanced -> CPU Configuration -> Intel Hyper Threading Technology -> Enabled
  • Advanced -> Chipset Configuration -> Primary Graphics Adapter -> Onboard
  • Advanced -> Chipset Configuration -> Wt-d -> Enabled
  • Advanced -> Chipset Configuration -> PCIE2 Link Speed -> Auto
  • Advanced -> ACPI Configuration -> Suspend to RAM -> Auto
  • Advanced -> ACPI Configuration -> Check Ready Bit -> Enabled
  • Advanced -> ACPI Configuration -> ACPI HPET Table -> Enabled
  • Advanced -> ACPI Configuration -> set rest of the list to -> Disabled
  • Advanced -> USB Configuration -> USB Controller -> Enabled
  • Advanced -> USB Configuration -> Intel USB3.0 Mode -> Disabled (for now)
  • Advanced -> USB Configuration -> Legacy USB Support -> Disabled

Install and update host OS

Grab the official 64bit iso for Ubuntu-gnome 16.04 LTS and perform a full install.

Keeping the GPUs display ports disconnected during install/setup should keep video on the monitor you're using. If you have multiple drives, disabling them for the install process is recommended as the Ubuntu installer might automatically install GRUB on a wrong disk (and fail doing so, bricking your install).

From this point on let's asume that the install succeeded, internet is available and all operations are run as root.

Run an update

# Update the fresh install
apt-get update
apt-get upgrade
reboot

Patch and install new kernel

We'll be using the git method of building the kernel. For more info see Ubuntu Kernel Git Guide. The ACS override patch introduces security issues, you can read more about them here. You can find the patches here github.

The Intel i915 driver patch might fail, it looks like this section of code is being actively developed on so the patch becomes out of sync quickly. If that happens, you'll have to complete the patch by hand by checking the content of the .rej files, locate the right spot in the source and inject those changes.

Before proceding uncomment the deb-src entries in /etc/apt/sources.list for xenial and xenial-updates sources.

# add the deb-src sources to apt
apt-get update

# grab dependencies for building the kernel
apt-get build-dep linux-image-`uname -r`

# install git and fakeroot
apt-get install git fakeroot

# pull kernel sources (this will take a while)
git clone git://kernel.ubuntu.com/ubuntu/ubuntu-xenial.git

# patch the kernel using the patches from the repo
patch -p1 < deb-kvm-passthrough/override_for_missing_acs_capabilities.patch
patch -p1 < deb-kvm-passthrough/deb-kvm-passthrough/i915_317.patch

# you might have to do the chmod
chmod a+x debian/rules
chmod a+x debian/scripts/*
chmod a+x debian/scripts/misc/*

# create configs (for all archs), answer all with Y, then Exit
fakeroot debian/rules editconfigs

# modify the kernel tag so we can verify the proper kernel with `uname -a` later
# in the root dir edit the first line of debian.master/changelog, append a tag to
# the version number (something like +qemu). If you don't to this the new kernel
# still should have a + appended at the end of it's name.
#
vim debian.master/changelog
# For example, change
# linux (4.4.0-31.50) xenial; urgency=low
# to
# linux (4.4.0-31.50+qemu) xenial; urgency=low

# clean
fakeroot debian/rules clean

# (this will take a while, but use multiple processor if available))
fakeroot debian/rules binary-headers binary-generic

If everything went fine, install new kernel. Kernel version 4.4.0-31.50 with git commit id 2a1e175b928ae worked for me.

# deb packages will be built into .. from the source tree
cd ..
dpkg -i linux-image-4.4.0-31-generic_4.4.0-31.50_amd64.deb
dpkg -i linux-headers-4.4.0-31-generic_4.4.0-31.50_amd64.deb

Install QEMU, shadow NVIDIA cards and enable kernel patches

apt-get install qemu-kvm libvirt-bin virtinst bridge-utils

Add virtualization modules to your /etc/modules

pci_stub
vfio
vfio_iommu_type1
vfio_pci
kvm
kvm_intel

To blacklist the noveau driver add the following to /etc/modprobe.d/blacklist.conf

# Disable nouveau for passthrough
blacklist nouveau
blacklist lbm-nouveau
alias nouveau off
alias lbm-nouveau off

Update the commandline options in /etc/default/grub to the line below. These GRUB options will enable iommu support, fix i915 VGA graphics issues and apply the acs_override patch options to separate our IOMMU groups into individual chunks. The Z87 board unfortunately groups its PCIe ports so this options are neccesary but unsecure. You can see this if you don't apply the kernel patch.

GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on i915.enable_hd_vgaarb=1 pcie_acs_override=downstream"

After that update grub and reboot to boot into the new kernel with the patches enabled.

update-grub
reboot

Detect hardware ids and assign them to pci_stub

After the reboot create a directory called /vm and copy the following script to /vm/check_iommu.sh.

#!/bin/bash
for iommu_group in $(find /sys/kernel/iommu_groups/ -maxdepth 1 -mindepth 1 -type d); do
        echo "IOMMU group $(basename "$iommu_group")";
        for device in $(ls -1 "$iommu_group"/devices/); do
                echo -n $'\t'; lspci -nns "$device";
        done;
done

Run the script, and check if the GPUs are in separate iommu-groups. Without the pcie_acs_override patch and kernel boot-option the groups would not be separated and look like shown below (not listing full output).

...
IOMMU group 1
        00:01.0 PCI bridge [0604]: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor PCI Express x16 Controller [8086:0c01] (rev 06)
        01:00.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
        02:08.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
        02:09.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
        02:10.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
        03:00.0 VGA compatible controller [0300]: NVIDIA Corporation GF100GL [Quadro 4000] [10de:06dd] (rev a3)
        03:00.1 Audio device [0403]: NVIDIA Corporation GF100 High Definition Audio Controller [10de:0be5] (rev a1)
        04:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK107GL [Quadro K2000] [10de:0ffe] (rev a1)
        04:00.1 Audio device [0403]: NVIDIA Corporation GK107 HDMI Audio Controller [10de:0e1b] (rev a1)
        05:00.0 USB controller [0c03]: ASMedia Technology Inc. ASM1142 USB 3.1 Host Controller [1b21:1242]
IOMMU group 2
        00:02.0 VGA compatible controller [0300]: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller [8086:0412] (rev 06)
...

If the patch is working, it should instead list the GPUs and other components in their separate iommu groups like shown below:

...
IOMMU group 1
    00:01.0 PCI bridge [0604]: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor PCI Express x16 Controller [8086:0c01] (rev 06)
IOMMU group 2
    00:02.0 VGA compatible controller [0300]: Intel Corporation Xeon E3-1200 v3/4th Gen Core Processor Integrated Graphics Controller [8086:0412] (rev 06)
...

IOMMU group 12
    00:1d.0 USB controller [0c03]: Intel Corporation 8 Series/C220 Series Chipset Family USB EHCI #1 [8086:8c26] (rev 05)

...
IOMMU group 14
    01:00.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
IOMMU group 15
    02:08.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
IOMMU group 16
    02:09.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
IOMMU group 17
    02:10.0 PCI bridge [0604]: PLX Technology, Inc. PEX 8747 48-Lane, 5-Port PCI Express Gen 3 (8.0 GT/s) Switch [10b5:8747] (rev ba)
IOMMU group 18
    03:00.0 VGA compatible controller [0300]: NVIDIA Corporation GF100GL [Quadro 4000] [10de:06dd] (rev a3)
    03:00.1 Audio device [0403]: NVIDIA Corporation GF100 High Definition Audio Controller [10de:0be5] (rev a1)
IOMMU group 19
    04:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK107GL [Quadro K2000] [10de:0ffe] (rev a1)
    04:00.1 Audio device [0403]: NVIDIA Corporation GK107 HDMI Audio Controller [10de:0e1b] (rev a1)
IOMMU group 20
    05:00.0 USB controller [0c03]: ASMedia Technology Inc. ASM1142 USB 3.1 Host Controller [1b21:1242]
...

Edit /etc/initramfs-tools/modules to assign the devices to the pci_stub module, basically locking them from being used by other modules. Note these are the vendor-ids.

pci_stub ids=10de:06dd,10de:0be5,10de:0ffe,10de:0e1b,1b21:1242,8086:8c26

Generate the new ramfs

update-initramfs -u
reboot

If after reboot the iommu groups are not separated, check if the patched kernel has actually been loaded and the boot options hav been used.

This can be done by checking uname -a as well as dmesg | grep acs_override.

Create guest OS FS images, installation ISOs and gather BIOS/UEFI firmware roms.

Install seabios via apt-get install seabios if it's not installed already. OVMF is being developed by Tianocore. However we want the split image version from Gerd Hoffmant's Fedora OVMF builds. See more about this at the OVMF split image support blog. Grab the x86_64 ovmf image, extract it from the rpm archive and copy it to /usr/share/edk2.git/.

Next create linux and windows disk images using the qcow2 format. The options are for performance optimization.

qemu-img create -f qcow2 -o preallocation=metadata,compat=1.1,lazy_refcounts=on linux4.img 120G
qemu-img create -f qcow2 -o preallocation=metadata,compat=1.1,lazy_refcounts=on win10-v1.img 120G

First create two files as config files for the script below with device id's called vfio-pic-win10.cfg and vfio-pic-linux.cfg. The files will contain the ids of the devices to pass through:

vfio-pic-win10.cfg might look something like this:

0000:03:00.0
0000:03:00.1
0000:05:00.0

This will pass through the Quadro 4000 GPU as well as the HDMI Audio as seen by running check_iommu. The 05:00.0 is a USB controller card. Beware of passing through your motherboards usb controller unless you want to, because as mentioned earlier most of the usb ports will be assigned to the VM leaving you stranded on the host OS.

Next create two vm-scripts following snipplet below. Match the device id of PCIe to pass through as well as rom locations and disks for each os. Kudos to all the folks developing various versions of this script.

A boot iso image for each respective system will be needed, as well as the seabios for Linux and ovmf for Windows10.

#!/bin/bash

configfile=vfio-pci-win10.cfg

vfiobind() {
    dev="$1"
        vendor=$(cat /sys/bus/pci/devices/$dev/vendor)
        device=$(cat /sys/bus/pci/devices/$dev/device)
        if [ -e /sys/bus/pci/devices/$dev/driver ]; then
          echo "Unbinding dev: $dev"
          echo $dev > /sys/bus/pci/devices/$dev/driver/unbind
        fi
        echo "Assigning vendor: $vendor device: $device to vfio-pci driver"
        echo $vendor $device > /sys/bus/pci/drivers/vfio-pci/new_id
}

modprobe vfio-pci

cat $configfile | while read line;do
    echo $line | grep ^# >/dev/null 2>&1 && continue
    vfiobind $line
done

# QEMU name and PID
O="-name windows-10-pro"
O="$O -pidfile /tmp/windows-10-pro.pid"

# Processor
O="$O -cpu host,hv_time,kvm=off"
O="$O -smp 4,sockets=1,cores=2,threads=2"
O="$O -enable-kvm"
O="$O -M q35 -m 8192"
#
O="$O -device ioh3420,bus=pcie.0,addr=1c.0,multifunction=on,port=1,chassis=1,id=root.1"

# BIOS / UEFI
# For Linux
# O="$O -bios /usr/share/seabios/bios.bin"
# For Windows, use edk2
cp /usr/share/edk2.git/ovmf-x64/OVMF_VARS-pure-efi.fd /tmp/my_vars.fd
O="$O -drive if=pflash,format=raw,readonly,file=/usr/share/edk2.git/ovmf-x64/OVMF_CODE-pure-efi.fd"
O="$O -drive if=pflash,format=raw,file=/tmp/my_vars.fd"

## GFX (nvidia quadro 4k)
O="$O -device vfio-pci,host=03:00.0,bus=root.1,addr=00.0,multifunction=on,x-vga=on"
O="$O -device vfio-pci,host=03:00.1,bus=root.1,addr=00.1"

# USB
O="$O -device vfio-pci,host=76:0.0,bus=pcie.0"

# SCSI drive for Windows10
O="$O -drive id=disk0,if=none,cache=writeback,format=qcow2,file=win10-v1.qcow2"
O="$O -device driver=virtio-scsi-pci,id=scsi0"
O="$O -device scsi-hd,drive=disk0"
# IDE drive for Linux
# O="$O -drive file=linux-deb-v1.qcow2,id=disk,format=qcow2,if=none"
# O="$O -device ide-hd,bus=ide.0,drive=disk"

# VirtIO drivers CD
O="$O -drive id=virtiocd,if=none,format=raw,file=virtio-win.iso"
O="$O -device driver=ide-cd,bus=ide.0,drive=virtiocd"

# OS media
O="$O -drive file=Windows10-64.iso,id=isocd,format=raw,if=none"
O="$O -device ide-cd,bus=ide.1,drive=isocd"
# O="$O -drive file=debian-8.5.0-amd64-xfce-CD-1.iso,id=isocd,format=raw,if=none"
# O="$O -device ide-cd,bus=ide.1,drive=isocd"

# Network
O="$O -netdev user,id=network0"
O="$O -device e1000,netdev=network0"
# Provide local folder as a samba share (install samba on host OS)
# O="$O -smb /usr/vm/drivers"
# Redirect ssh -p 5555 user@localhost to ssh port of VM
# O="$O -redir tcp:5555::22"

# Disable/Enable Console
#O="$O -vga none"
O="$O -boot menu=on"

# Turn on debugging
#O="$O -debugcon file:debug_w10_install.log -global isa-debugcon.iobase=0x402"

sudo qemu-system-x86_64 $O
exit 0

Running the script should yield a black QEMU console window or the boot-screen display of the iso attached. The qemu console can be closed by pressing ALT-2 and typing the quit command.

Debian/Linux guest installation (via seabios)

In general the guest OS installation is performed by disabling the passthrough GPU VGA display and using QEMUs emulation of vga, keyboard and mouse in order to navigate around driver issues, followed by installing native graphics and USB drivers and turning off emulation afterwards. To do this remove x-vga=on from the GPU device line and make sure you don't have the -vga none option in the script. The respective lines for the example script above would be:

# disable x-vga output on card to install OS using stock vga output and drivers
O="$O -device vfio-pci,host=03:00.0,bus=root.1,addr=00.0,multifunction=on"

# use seabios
O="$O -bios /usr/share/seabios/bios.bin"

# Commenting this should give you a vga output window. Disable for install.
#O="$O -vga none"

Given the image was empty the bios should boot using the iso image. Perform an installation from the iso and reboot the VM. After reboot and hopefully a system update the native NVIDIA drivers can be installed.

This step depends on the respective linux-distro but any automatic install usually works out of the box. After the native graphics driver has been installed it's time to blacklist the nouveau driver on the guest os. This is done in the same way as described for the host OS by adding the module to blacklist.conf.

Poweroff the system: Connect your HDMI/DVI/DP cable and put the x-vga=on line back to the script. This should enable the graphics adapters output. Upon next boot you should see something on the output of the graphics card.

Check if the graphics driver is being used by running the nvidia-settings tool.

Windows10 guest installation (via OVMF/UEFI)

Windows installation is run in the same way as the linux installation with the difference that we're using the OVMF image. In order for Windows10 to pickup your 'scsi' drive you'll need to provide the virt-io drivers to the installer. Download the virtio drivers for windows by following the link found here and configure the script to point to its proper path as an attached CD-ROM image. During the Windows10 installation procedure you can select to load the virtio driver from cdrom.

Once Windows10 is installed, install Nvidia drivers, and proceed in the same way as for the Linux install.

Providing guests with access to host filesystem

Use the -smb option in the host VM config. You'll also need to install samba: apt-get install samba.

O="$O -smb /point/to/folder"

On the Linux guest install cifs-utils if it's a debian derivate. The following should do

mount.cifs //10.0.2.4/qemu /mnt/data -o user=qemu

On the Windows guest, just use the file explorer and point to //10.0.2.4/qemu.

Changelog

  • 2016-07-31 First Draft Version
  • 2019-04-25 Updated Formatting
  • 2019-07-13 Updated Intro