Create a Gaming HVM

Original forum link
https://forum.qubes-os.org/t/19000
Original poster
neowutran
Editors
apparatus, deeplow, gonzalo-bulnes, LinuxUser, system, taradiddles
Created at
2023-06-02 05:19:43
Last wiki edit
2025-01-17 09:26:13
Revisions
41 revisions
Posts count
309
Likes count
153
Tags
configuration, dom0, hvm, version-r41

Hardware

To have an 'HVM' for gaming, you must have

IOMMU Group

Goal

What

The goal of this step if to retrieve the default IOMMU Group (https://www.kernel.org/doc/html/latest/driver-api/vfio.html#groups-devices-and-iommus) of your hardware.

Why

It can help understanding potential issue with your setup (what devices live in the same IOMMU group as your GPU) / finding potential workaround. If you feel lucky, skip this step.

How

You can't see your IOMMU Group when you are using Xen (the information is hidden from dom0).

#!/bin/bash
shopt -s nullglob
for g in /sys/kernel/iommu_groups/*; do
 echo "IOMMU Group ${g##*/}:"
 for d in $g/devices/*; do
  echo -e "\t$(lspci -nns ${d##*/})"
done
done

GRUB modification

You must hide your secondary GPU from dom0. To do that, you have to modify the GRUB. In a dom0 Terminal, type:

qvm-pci

Then find the devices id for your secondary GPU. In my case, it is dom0:0a_00.0. Edit /etc/default/grub, and add the PCI hiding.

GRUB_CMDLINE_LINUX="... rd.qubes.hide_pci=0a:00.0 "

then regenerate the grub

grub2-mkconfig -o /boot/grub2/grub.cfg

If you are using UEFI and Qubes OS 4.1 or earlier, the file to override with grub2-mkconfig is /boot/efi/EFI/qubes/grub.cfg.

Note: if after this step when you reboot the computer you get stuck in the QubesOS startup that means you are trying to use the GPU you just hid. Check your BIOS options. Also check the cables, BIOS have some GPU priority based on the type of cable. For example, DisplayPort can be favoured over HDMI.

Once you have rebooted, in dom0, type sudo lspci -vvn, you should see "Kernel driver in use: pciback" for the GPU you just hid.

Configuring the parameter "max-ram-below-4g"

Since the release of this qubes version of xen: 4.17.2-8 (R4.2, 2024-01-03), no additional configuration is required.

Remove any existing "max-ram-below-4g" workaround

If you are using an older version

Why do we need to do that ?

github.com/QubesOS/qubes-issues/issues/4321

Copy-paste of the comment:

This is caused by the default TOLUD (Top of Low Usable DRAM) of 3.75G provided by qemu not being large enough to accommodate the larger BARs that a graphics card typically has. The code to pass a custom max-ram-below-4g value to the qemu command line does exist in the libxl_dm.c file of xen, but there is no functionality in libvirt to add this parameter. It is possible to manually add this parameter to the qemu commandline by doing the following in a dom0 terminal.


( "max-ram-below-4g" is not related to the amount of ram you can pass to the VM. It is not related to VRAM either. It is related to WHAT is available in the memory space for 32 bits system and WHAT is stored in the memory space for 64 bits system (usable ram is only a part of what need to be mapped in the memory space) )

Finding the correct value for this parameter

Below, we set the "max-ram-below-4g" parameter to "3.5G".

For some GPU this value need to be "2G" (discovered here: https://forum.qubes-os.org/t/quick-howto-gpu-passthrough-with-lots-of-ram/21156). It is not currently well understood why the value need to be exactly "2G" or exactly "3.5G", or maybe some other values for other GPU/configuration we never saw yet. ( https://github.com/QubesOS/qubes-issues/issues/4321#issuecomment-1206000974 ) More investigation is required to understand what is going on with this parameter.

Current best guess is to run this command in dom0: lspci -vvs GPU_IDENTIFIER | grep Region , for example: lspci -vvs 0a:00.0 | grep Region. if the max value of [size=XXXX] is 256MB, try 3.5G for max-ram-below-4g. If the max value is bigger, try 2G for max-ram-below-4g.

Update: I think I discovered the reason (https://github.com/QubesOS/qubes-issues/issues/4321#issuecomment-1828226780 https://lists.xenproject.org/archives/html/xen-devel/2023-12/msg00133.html ). If you want and have the skills required to compile the xen package, try to apply this patch Fix guest memory corruption caused by hvmloader by neowutran · Pull Request #172 · QubesOS/qubes-vmm-xen · GitHub and confirm it if work as expected. With this patch, the part about "patching stubdom-linux-rootfs.gz" is not needed.

Patching stubdom-linux-rootfs.gz

I modified the original code to: - make it works with Qubes R4.1/R4.2 - removed one of the original limitations by restricting the modification to VM with a name starting with "gpu_" - Added a way to modify per vm the value for "max-ram-below-4g". Ex, if you want specifically to use 2G for "max-ram-below-4g", name the vm "gpu_2G_YOURNAME", if you want specifically to use 3.5G for "max-ram-below-4g", name the vm "gpu_3n5G_YOURNAME"

mkdir stubroot
cp /usr/libexec/xen/boot/qemu-stubdom-linux-rootfs stubroot/qemu-stubdom-linux-rootfs.gz
cd stubroot
gunzip qemu-stubdom-linux-rootfs.gz
cpio -i -d -H newc --no-absolute-filenames < qemu-stubdom-linux-rootfs
rm qemu-stubdom-linux-rootfs
nano init

Before the line

# $dm_args and $kernel are separated with \n to allow for spaces in arguments

add:

vm_name=$(xenstore-read "/local/domain/$domid/name")
if [ $(echo "$vm_name" | grep -iEc '^gpu_' ) -eq 1 ]; then
 max_ram_below_4g=$(echo "$vm_name" | grep -iEo "_([0-9]*(n[0-9]*)?)._" | cut -d '_' -f 2)
 if [[ -z "$max_ram_below_4g" ]];
 then
  max_ram_below_4g="3.5G"
 fi
 max_ram_below_4g=$(echo "$max_ram_below_4g" | sed 's/n/./g')
 dm_args=$(echo "$dm_args" | sed -n '1h;2,$H;${g;s/\(-machine\nxenfv\)/\1,max-ram-below-4g='"$max_ram_below_4g"'/g;p}')
fi

Then execute:

find . -print0 | cpio --null -ov \
--format=newc | gzip -9 > ../qemu-stubdom-linux-rootfs
sudo mv ../qemu-stubdom-linux-rootfs /usr/libexec/xen/boot/

Note that this will apply the change to the HVM with a name starting with "gpu_". So you need to name your gaming HVM "gpu_SOMETHING".

Alternatively, the following dom0 script "patch_stubdom.sh" does all the previous steps:

#!/bin/bash 

patch_rootfs(){

 filename=${1?Filename is required}

 cd ~/

 sudo rm -R "patched_$filename"
 mkdir "patched_$filename"

 cp /usr/libexec/xen/boot/$filename "patched_$filename/$filename.gz"
 cp /usr/libexec/xen/boot/$filename "$filename.original"

 cd patched_$filename
 gunzip $filename.gz
 cpio -i -d -H newc --no-absolute-filenames < "$filename"
 sudo rm $filename

 grep -i "max-ram-below-4g" init && echo "!!ERROR!! The thing is already patched ! EXITING ! " >&2 && exit

patch_string=$(cat <<'EOF'


vm_name=$(xenstore-read "/local/domain/$domid/name")
if [ $(echo "$vm_name" | grep -iEc '^gpu_' ) -eq 1 ]; then
 max_ram_below_4g=$(echo "$vm_name" | grep -iEo "_([0-9]*(n[0-9]*)?)._" | cut -d '_' -f 2)
 if [[ -z "$max_ram_below_4g" ]];
 then
  max_ram_below_4g="3.5G"
 fi
 max_ram_below_4g=$(echo "$max_ram_below_4g" | sed 's/n/./g')
 dm_args=$(echo "$dm_args" | sed -n '1h;2,$H;${g;s/\\(-machine\\nxenfv\\)/\\1,max-ram-below-4g='"$max_ram_below_4g"'/g;p}')
fi
\# $dm_args and $kernel

EOF
)

awk -v r="$patch_string" '{gsub(/^# \$dm_args and \$kernel/,r)}1' init > init2
cp init /tmp/init_$filename
mv init2 init
chmod +x init

 find . -print0 | cpio --null -ov \
--format=newc | gzip -9 > ../$filename.patched
 sudo cp ../$filename.patched /usr/libexec/xen/boot/$filename

 cd ~/


}

grep -i "max-ram-below-4g" /usr/share/qubes/templates/libvirt/xen.xml && "!!ERROR!! xen.xml is patched ! EXITING ! " && exit
patch_rootfs "qemu-stubdom-linux-rootfs"
patch_rootfs "qemu-stubdom-linux-full-rootfs"

echo "stubdom have been patched."
OUTDATED - DO NOT USE - Other method: Patching xen.xml instead of stubdom

Instead of patching stubdom-linux-rootfs, you could inject the command directly inside the configuration template. It is the file "core-admin/ "templates/libvirt/xen.xml" in the "qubes-core-admin" repository. In dom0 this file is in "/usr/share/qubes/templates/libvirt/xen.xml"

See below the part that have been modified to add the needed "max-ram-below-4g" option.

<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
<emulator
 {% if vm.features.check_with_template('linux-stubdom', True) %}
 type="stubdom-linux"
 {% else %}
 type="stubdom"
 {% endif %}
 {% if vm.netvm %}
 {% if vm.features.check_with_template('linux-stubdom', True) %}
 cmdline="-qubes-net:client_ip={{ vm.ip -}}
 ,dns_0={{ vm.dns[0] -}}
 ,dns_1={{ vm.dns[1] -}}
 ,gw={{ vm.netvm.gateway -}}
 ,netmask={{ vm.netmask }} -machine xenfv,max-ram-below-4g=3.5G"
 {% else %}
 cmdline="-net lwip,client_ip={{ vm.ip -}}
 ,server_ip={{ vm.dns[1] -}}
 ,dns={{ vm.dns[0] -}}
 ,gw={{ vm.netvm.gateway -}}
 ,netmask={{ vm.netmask }} -machine xenfv,max-ram-below-4g=3.5G"
 {% endif %}
 {% else %}
 cmdline="-machine xenfv,max-ram-below-4g=3.5G"
 {% endif %}

A better patch for xen.xml is available here: https://github.com/QubesOS/qubes-issues/issues/4321#issuecomment-1551203532

I haven’t personnally tested this alternative but it should work and some users reported that it work. This method is less tested than patching stubdom-linux-rootfs, so I recommend patching stubdom-linux-rootfs.

Preparing the guest

As of 2023, I recommend using a Linux guest instead of a window guest.

Windows

Install a window VM, you can use this qvm-create-windows-qube

Linux

Create a new standalone Qube based on the template of your choice.

You must run the kernel provided by the guest distribution, because we will use some non-default kernel module for the GPU driver. Just follow the doc: managing-vm-kernel.

Install the GPU drivers you need.

And automated way to do that is proposed here: https://forum.qubes-os.org/t/create-a-gaming-hvm/19000#p-86347-automating-most-of-that-24

Pass the GPU

In qubes settings for the HVM, go to the 'devices' tab, pass the ID corresponding to your GPU.

qvm-pci attach gpu_gaming_archlinux dom0:0a_00.0 --persistent

You may or may not need to add the option "permissive" or "no-strict-reset". You may or may not need to passthrough additional devices depending on the result of the IOMMU script https://forum.qubes-os.org/t/create-a-gaming-hvm/19000#p-86347-iommu-group-2

Some word about the security implication of thoses parameters.

qvm-pci attach gpu_gaming_archlinux dom0:0a_00.0 -o permissive=True -o no-strict-reset=True --persistent

Starting the guest

This is where you will have a lot of issues to debug. Prepare for intense suffering

For Linux guests, run 'sudo dmesg' to have all the kernel log indicating you if there is a issue with your GPU driver. For some hardware, the MSI calls won't work. You can work around that using for example pci=nomsi or NVreg_EnableMSI=0 or something else. Check your drivers options. Check if alternative drivers exist (amdgpu, nvidia, nouveau, nvidia-open, using drivers from the official website, ...). Check multiple kernel version.

For nvidia GPU, I recommand using "nvidia-open" drivers instead of "nvidia"

Some links that could help you to debug the issues you will have

For windows guests you will probably have the same issues but it will be harder to debug. I recommend using the drivers from Windows Update instead of the official drivers from the website of the constructor.

Some things that may be useful for debugging:

Issues with the drivers could be related to 'qubes-vmm-xen-stubdom-linux', 'qubes-vmm-xen', and the Linux kernel you will be using.

[Case: No dedicated screen, mouse or keyboard] VirtualGL and vulkan

In some cases, it will just work out of the box (application will automatically detect the GPU and will be able to use it without modification.

In some cases, an additionnal layer is required: using VirtualGL, mentionned here: https://forum.qubes-os.org/t/seamless-gpu-passthrough-on-qubes-os-with-virtualgl/20265 In that case you may need to specify a new variable LD_LIBRARY_PATH=/usr/lib

You may have issue with how the mouse is handled by the game. AFAIK, no one tried to solve this issue.

[Case: Dedicated Screen, mouse and keyboard] Linux guest --- Integration with QubesOS

Xorg

Now Xorg. From XKCD:

image

Things you need to install:

In my case, it is:

archlinux version:

pacman -S xorg i3

debian version:

apt install xserver-xorg-input-kbd xserver-xorg-input-libinput xserver-xorg-input-mouse i3

Then create a XORG configuration file for your GPU and screen. My file named 'xorg.conf':

Section "ServerLayout"
Identifier "Passthrough Layout"
Screen 0 "Passthrough Screen" Absolute 0 0
EndSection

Section "Device"
Identifier  "Passthrough GPU"
# name of the driver to use. Can be "amdgpu", "nvidia", or something else
Driver      "driver"
Option "Coolbits" "4"
# The BusID value will change after each qube reboot. 
BusID       "PCI:0::0"
EndSection

Section "Monitor"
Identifier "Passthrough monitor"
EndSection

Section "Screen"
Identifier "Passthrough Screen"
Device     "Passthrough GPU"
Monitor    "Passthrough Monitor"
EndSection

We can't know what is the correct BusID before the qube is started. And it change after each reboot. So let's write a script --- named "xorgX1.sh" --- that update this configuration file with the correct value, then start a binary on the Xorg X screen n°1.

#!/bin/bash
binary=${1:?binary required}

# Find the correct BusID of the AMD GPU, then set it in the Xorg configuration file
lspci | grep "VGA" | grep -E "NVIDIA" && sed -i 's/^Driver .*/Driver "nvidia"/g' /opt/xorg.conf
lspci | grep "VGA" | grep -E "AMD/ATI" && sed -i 's/^Driver .*/Driver "amdgpu"/g' /opt/xorg.conf
pci=$(lspci | grep "VGA" | grep -E "NVIDIA|AMD/ATI" | cut -d " " -f 1 | cut -d ":" -f 2 | cut -d "." -f 1 | cut -d "0" -f 2)
sed -i 's/"PCI:[^"]*"/"PCI:0:'$pci':0"/g' /opt/xorg.conf

# Start the Xorg server for the X screen number 1.
# The X screen n__0 is already used for QubesOS integration
sudo startx "$binary" -- :1 -config /opt/xorg.conf
Deprecated: old way of doing it

!/bin/bash

binary=${1:?binary required}

Find the correct BusID of the AMD GPU, then set it in the Xorg configuration file

pci=$(lspci | grep "VGA" | grep -E "NVIDIA|AMD/ATI" | cut -d " " -f 1 | cut -d ":" -f 2 | cut -d "." -f 1 | cut -d "0" -f 2) sed -i "s/PCI:0:[0-9]:0/PCI:0:$pci:0/g" /home/user/AOC.conf

Pulseaudio setup

sudo killall pulseaudio sudo sed -i "s/load-module module-vchan-sink./load-module module-vchan-sink domid=$(qubesdb-read -w /qubes-audio-domain-xid)/" /etc/pulse/qubes-default.pa sudo rm /home/user/.pulse/client.conf start-pulseaudio-with-vchan sleep 5 && sudo chmod -R 777 /root/ & sleep 5 && sudo chmod -R 777 /root/ & sleep 5 && sudo cp /root/.pulse/client.conf /home/user/.pulse/client.conf && sudo chown -R user:user /home/user/.pulse/client.conf &

Start the Xorg server for the X screen number 1.

The X screen n°0 is already used for QubesOS integration

sudo startx "$binary" -- :1 -config /home/user/AOC.conf

Audio

#!/bin/bash
sleep 5 && sudo setxkbmap -display :1 fr & 
/bin/sudo -u user PULSE_SERVER=unix:/run/user/1000/pulse/native bash -c 'sudo xhost + local:;/usr/bin/i3'

And launch it:

sudo ./xorgX1.sh ./i3.sh
Deprecated: old way of doing it
sudo pacman -Rdd qubes-vm-pulseaudio pulseaudio
sudo pacman -S pipewire-{jack,alsa,pulse} pipewire-qubes

pipewire|690x168

Deprecated: old way of doing it2

So you need to configure pulseaudio for Xorg multiseat. The archlinux documentation explain that very well: Xorg multiseat Use the option without system-mode deamon and adapt it to qube: Add the following line to /etc/pulse/qubes-default.pa

load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1

Then add this config for root:

mkdir /root/.pulse
echo "default-server = 127.0.0.1" > /root/.pulse/client.conf

The sound was buggy/laggy on my computer. So tried to find a workaround by playing with pulseaudio settings. It was more or less random tries, so I can't really explain it: In /etc/pulse/daemon.conf add the following lines:

default-fragments = 60
default-fragment-size-msec = 1
high-priority = no
realtime-scheduling = no
nice-level = 18

In /etc/pulse/qubes-default.pa change

load-module module-udev-detect

to

load-module module-udev-detect tsched=0

You can launch you favorite Windows Manager like that

sudo ./xorgX1.sh /usr/bin/i3

Automating most of that

I trying to write a script to automate all the previous steps. It is available here: https://git.sr.ht/~yukikoo/gpu_template

Please be carefull and read the code, not much tests have been done

Issues and fixes

In one case in a setup with Intel IGPU + Nvidia DGPU, dom0 xorg crashed. Solved the case by adding a Xorg configuration to explicitly use Intel:

https://forum.qubes-os.org/t/gpu-passthrough-lightdm-service-wont-start/22220/20

References


This document was migrated from the qubes-community project
Contributors