Headless Intel IGD Passthrough (Haswell)

I recently struggled for several weeks trying to pass through the Intel HD 4600 on my i5-4690k to a Windows 10 VM without another graphics device attached, so I’m writing this for anyone else who might be stuck. There was no 1 particular guide I followed, as none of them seemed to cover everything.

My goal was to have the machine running Arch Linux, as a headless server, serving things such as this website as well as act as a NAS. That was the easy part, just install Arch Linux, enable SSH, unplug monitor/mouse/keyboard, install software, done. But then I wanted the same machine, while booted into Arch Linux, to be able to boot into Windows, to run graphical software such as Autodesk Inventor, Photoshop, and Multisim. While I did have things like X11 forwarding set up on Arch, there was no access to graphical acceleration and Multisim was the only application that actually worked in WINE. I needed Windows, but if I just set up a basic VM with an emulated graphics card, the resolution would be limited and most of the apps still wouldn’t work. I needed a direct connection with a graphics card. Luckily, that’s possible using PCI passthrough. Normally, you pass through an external GPU, but I wanted to pass through the Intel Integrated Graphics.

Let’s look at the hardware:

  • i5-4690k
  • ASRock Z97E-ITX/ac
  • Kingston 16GB DDR3L-1333
  • MyDigitalSSD SBXe 500GB NVMe SSD
  • WD Red 10TB 5400RPM
  • PicoPSU 160w

And now the software configuration:

  • Host OS: Arch Linux
  • Kernel: linux-vfio-lts (4.19 at the time of writing)
  • qemu 4.0 and the latest libvirt
  • SSH and ThinLinc for remote access
  • Guest OS: Windows 10 Enterprise LTSC 2019
  • Latest updates installed

First thing is to enable virtualization and IOMMU. Both of these options were available in the BIOS. Then, just follow the generic VFIO PCI passthrough setup, I like to use the Arch Wiki’s page on it. We can’t follow the while guide, just get to the point where you give the vfio-pci driver the PCI IDs and blacklist i915. Also install and prepare libvirt according to the guide. Additionally, a few kernel parameters need to be added, so that we make sure absolutely no drivers map some memory to the GPU. Many guides on IGD passthrough recommend video:efifb=off,vesafb=off but I found it necessary to also have simplefb=off added to that list. This is what had me stuck for the longest time; all of my VMs would display basic text (through simplefb) perfectly fine, but as soon as they tried to display anything else simplefb would stay attached and crash the GPU. Don’t forget to disable simplefb.

At this point I actually got it to work exactly once, but never again. It turns out that vesafb was still claiming GPU memory (reported by /proc/iomem) even though I disabled it in grub. The solution was to also add nomodeset and nofb to the kernel command line, and possibly also from commenting out a line in the grub configuration file (you can see my full configuration file here). Basically, it seems that Linux really wants to use the primary GPU, so we have to be very specific.

Finally, set up a VM in virt-manager, in my case I installed Windows 10 LTSC 2019 using the QXL video driver without the GPU passed through. I was able to get various Linux ISOs such as Ubuntu 19.04 to boot at 1080p just fine. Some critical setting include using SeaBIOS (no OVMF support) and the chipset must be i440fx. Q35 with Intel iGPU passthrough does not work yet. After the install, remove all virtual video devices that you may have used for the install, and add the PCI devices for the iGPU and optionally the Intel HD Audio device (though I haven’t gotten it to work yet). Look at the XML and make sure that the iGPU is on PCI bus 0x02 in the VM. If it’s not 0x02, it will not work. There may be another device, such as the emulated sound card or possibly a storage driver using the bus. Remove the offender, and then just re-add it after adding the iGPU.

At this point, boot up the VM and watch the screen, if you get lucky it will boot up and render some graphics. With Windows 10 it initially booted at 800×600 with 256 colors, but after logging in and waiting a few minutes Windows loaded some old Intel driver and got the GPU running at my monitor’s resolution. Just in case, I installed the latest Intel driver from their website, as some others have reported issues with the built in driver. After a reboot, I was able to get 1080p 144Hz output from the GPU with no issues. I tested various 3D applications and games and all ran as expected for an Intel HD graphics card. To this day I have not encountered a single BSOD.

I hope this explanation can help you figure out IGD passthrough, or at least it was an interesting read. You can find my libvirt XML and kernel parameters in my vfio config repo.

Articles and resources I used when figuring this out:

Leave a Reply

Your email address will not be published. Required fields are marked *