I recently decided to amplify my VFIO experience by experimenting with passing my primary display adapter to a VM in proxmox. Previously I had just run tasksel on the proxmox host itself to install a GUI. I wanted better separation from the server side of proxmox and the client side. I also wanted to be able to distro-hop while maintaining the proxmox backend.
Initially I tried following my guide for passing through a secondary graphics card but ran into a snag. It did not work with my primary card and kept outputting these errors:
device vfio-pci,host=09:00.0,id=hostdev0,bus=pci.4,addr=0x0: Failed to mmap 0000:09:00.0 BAR 1. Performance may be slow
After much digging I finally found this post which explained I needed to unbind a few things for it to work properly:
echo 0 > /sys/class/vtconsole/vtcon0/bind echo 0 > /sys/class/vtconsole/vtcon1/bind echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/unbind
After more searching I found this post on reddit which had a nifty script for automating this when VM startup is desired. I tweaked it a bit to suit my needs.
Find your IDs for GPU by doing lspci and looking for your adapter. Find the IDs by running lspci -n -s <GPU location discovered with lspci>. Lastly VMID is the promxox ID for the VM you wish to start.
#!/bin/sh #Script to launch Linux desktop #Adapted from from https://www.reddit.com/r/VFIO/comments/abfjs8/cant_seem_to_get_vfio_working_with_qemu/?utm_medium=android_app&utm_source=share GPU=09:00 GPU_ID="10de 1c82" GPU_AUDIO="10de 0fb9" VMID=116 # Remove the framebuffer and console echo 0 > /sys/class/vtconsole/vtcon0/bind echo 0 > /sys/class/vtconsole/vtcon1/bind echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/unbind # Unload the Kernel Modules that use the GPU modprobe -r nvidia_drm modprobe -r nvidia_modeset modprobe -r nvidia modprobe -r snd_hda_intel # Load the vfio kernel module modprobe vfio modprobe vfio_iommu_type1 modprobe vfio-pci #Assign card to vfio-pci echo -n "${GPU_ID}" > /sys/bus/pci/drivers/vfio-pci/new_id echo -n "${GPU_AUDIO}" > /sys/bus/pci/drivers/vfio-pci/new_id #Start desktop sudo qm start $VMID #Wait here until the VM is turned off while [ "$(qm status $VMID)" != "status: stopped" ] do sleep 5 done #Reassign primary graphics card back to host echo -n "0000:${GPU}.0" > /sys/bus/pci/drivers/vfio-pci/unbind echo -n "0000:${GPU}.1" > /sys/bus/pci/drivers/vfio-pci/unbind echo -n "${GPU_ID}" > /sys/bus/pci/drivers/vfio-pci/remove_id echo -n "${GPU_AUDIO}" > /sys/bus/pci/drivers/vfio-pci/remove_id rmmod vfio-pci modprobe nvidia modprobe nvidia_drm modprobe nvidia_modeset modprobe snd_hda_intel sleep 1 echo -n "0000:${GPU}.0" > /sys/bus/pci/drivers/nvidia/bind echo -n "0000:${GPU}.1" > /sys/bus/pci/drivers/snd_hda_intel/bind sleep 1 echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/bind echo 1 > /sys/class/vtconsole/vtcon0/bind echo 1 > /sys/class/vtconsole/vtcon1/bind
With my primary adapter passed through I realized I also want other things passed through, mainly USB. I tried Proxmox’s USB device passthrough options but it doesn’t work well with USB audio (stutters and choppy.) I wanted to pass through my whole USB controller to the VM.
This didn’t work as well as I had planned due to IOMMU groups. A great explanation of IOMMU groups can be found here. I had to figure out which of my USB controllers were in which IOMMU group to see if I could pass the whole thing through or not (some of them were in the same IOMMU group as SATA & network controllers, which I did not want to pass through to the VM.)
Fortunately I was able to discover which USB controllers I could safely pass through first by running lspci to see the device ID, then running find to see which IOMMU group it was in, then checking against lspci to see what other devices were in that group. The whole group comes over together when you pass through to a VM.
First determine the IDs of your USB controllers
lspci | grep USB 01:00.0 USB controller: Advanced Micro Devices, Inc. [AMD] Device 43ba (rev 02) 08:00.0 USB controller: Renesas Technology Corp. uPD720201 USB 3.0 Host Controller (rev 03) 0a:00.3 USB controller: Advanced Micro Devices, Inc. [AMD] Device 145c 43:00.3 USB controller: Advanced Micro Devices, Inc. [AMD] Device 145c
Next get which IOMMU group these devices belong to
find /sys/kernel/iommu_groups/ -type l|sort -h|grep '01:00.0\|08:00.0\|0a:00.3\|43:00.3' /sys/kernel/iommu_groups/14/devices/0000:01:00.0 /sys/kernel/iommu_groups/15/devices/0000:08:00.0 /sys/kernel/iommu_groups/19/devices/0000:0a:00.3 /sys/kernel/iommu_groups/37/devices/0000:43:00.3
Then see what other devices use the same IOMMU group (the group is the number after /sys/kernel/iommu_groups/)
find /sys/kernel/iommu_groups/ -type l|sort -h | grep '/14\|/15\|/19\|/37' /sys/kernel/iommu_groups/14/devices/0000:01:00.0 /sys/kernel/iommu_groups/14/devices/0000:01:00.1 /sys/kernel/iommu_groups/14/devices/0000:01:00.2 /sys/kernel/iommu_groups/14/devices/0000:02:00.0 /sys/kernel/iommu_groups/14/devices/0000:02:04.0 /sys/kernel/iommu_groups/14/devices/0000:02:05.0 /sys/kernel/iommu_groups/14/devices/0000:02:06.0 /sys/kernel/iommu_groups/14/devices/0000:02:07.0 /sys/kernel/iommu_groups/14/devices/0000:04:00.0 /sys/kernel/iommu_groups/14/devices/0000:05:00.0 /sys/kernel/iommu_groups/14/devices/0000:06:00.0 /sys/kernel/iommu_groups/15/devices/0000:08:00.0 /sys/kernel/iommu_groups/19/devices/0000:0a:00.3 /sys/kernel/iommu_groups/37/devices/0000:43:00.3
As you can see one of my USB controllers (01:00.0) has a whole bunch of stuff in its IOMMU group, so I don’t want to use it lest I bring all those other things into the VM with it. The other three, though, are isolated in their groups and thus are perfect for passthrough.
In my case I passed through 0a:00.3 & 43:00.3 as 08:00.0 is a PCI card I want passed through to my Windows VM. This passed through about 2/3 of the USB ports on my system to my guest VM.