hero4hire
Debian 13

Pass a GPU through to a VM

Reserve a discrete GPU for VFIO on a Debian host and attach it to a libvirt VM from the Cockpit console.

linuxUnverified

Overview

This guide explains how to reserve a discrete GPU for VFIO on a Debian 13 (trixie) host and attach it to a virtual machine managed through Cockpit.

The GPU has to be claimed by vfio-pci at boot, before any ordinary driver touches it. That is a host-level change requiring a reboot, so the host is configured first and the VM comes second.

Before you start

Before you pass a GPU through, ensure:

  • Debian 13 is installed and booted in UEFI mode. The steps assume the 6.12 kernel trixie ships.
  • Cockpit and libvirt are working — see Set up Cockpit.
  • The host has a second display adapter — an integrated GPU or a spare card — driving its console. A GPU handed to a VM stops being available to the host, so it cannot be the one showing the host's own display.
  • Virtualisation and IOMMU are enabled in firmware: SVM Mode and IOMMU on AMD, VT-x and VT-d on Intel. Set the primary display to the integrated GPU while you are in the BIOS setup.

The worked examples come from an AMD Ryzen 7 7800X3D host whose integrated Raphael GPU drives the console and whose GeForce RTX 3060 is passed through. Your addresses and device IDs will differ; every command that produces them is shown.

Pass a GPU through to a VM

Reserve the GPU for VFIO

  1. Confirm the IOMMU is active.

    bash
    ls /sys/class/iommu/

    An AMD host prints ivhd0; an Intel host prints dmar0. An empty directory means the IOMMU is off in firmware — go back to the BIOS setup, because nothing below works without it.

  2. Identify the GPU's PCI functions and device IDs.

    bash
    lspci -nnk | grep -EA3 'VGA|3D controller|Audio device'

    A graphics card is at least two PCI functions — the display controller and an HDMI/DisplayPort audio controller — and both have to go to the guest:

    01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060] [10de:2487] (rev a1)
    	Kernel driver in use: nouveau
    	Kernel modules: nvidiafb, nouveau
    01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)
    	Kernel driver in use: snd_hda_intel
    	Kernel modules: snd_hda_intel

    Note both [vendor:device] IDs — here 10de:2487 and 10de:228b — and the driver names on the Kernel modules: lines. Both are used in step 4.

  3. Confirm the GPU sits in an IOMMU group of its own.

    bash
    for d in /sys/kernel/iommu_groups/*/devices/*; do
      n=${d#*/iommu_groups/}
      echo "group ${n%%/*}: $(lspci -nns "${d##*/}")"
    done | sort -V

    Find the lines for your card. The group must hold the card's own functions and nothing else:

    group 12: 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060] [10de:2487] (rev a1)
    group 12: 01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)

    An IOMMU group is the smallest unit that can be isolated, so an NVMe drive or USB controller sharing the group would have to be passed through too. Move the card to a different PCIe slot and re-check rather than reaching for an ACS override kernel patch, which defeats the isolation the group exists to provide.

  4. Bind the card to vfio-pci at module load time.

    Substitute your IDs from step 2 into ids=, and add a softdep line for every driver named on the card's Kernel modules: lines:

    bash
    sudo tee /etc/modprobe.d/vfio.conf <<'EOF'
    options vfio-pci ids=10de:2487,10de:228b
    softdep nouveau pre: vfio-pci
    softdep nvidiafb pre: vfio-pci
    softdep snd_hda_intel pre: vfio-pci
    EOF

    The softdep lines are the part people leave out, and leaving them out fails quietly. ids= only tells vfio-pci which devices to claim when it loads — whichever driver reaches a device first keeps it. snd_hda_intel loads early for the host's onboard audio and takes the GPU's audio function on the way past, leaving the display controller on vfio-pci and the audio function on snd_hda_intel. libvirt will try to wrest that function back when the VM starts, and fails to when the host has the device open — so the passthrough works until the day it does not.

  5. Add the VFIO modules to the initramfs.

    bash
    printf 'vfio\nvfio_iommu_type1\nvfio_pci\n' | sudo tee -a /etc/initramfs-tools/modules

    A softdep can only prefer a module that exists in the same context, so these have to be in the initramfs alongside the drivers they are racing. Older guides also list vfio_virqfd; that module was folded into the VFIO core in Linux 6.2 and does not exist on Debian 13's kernel, so leave it out.

  6. Turn on IOMMU passthrough mode on the kernel command line.

    Edit /etc/default/grub and extend GRUB_CMDLINE_LINUX_DEFAULT:

    bash
    GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"

    On an Intel host, use intel_iommu=on iommu=pt instead. iommu=pt puts devices the host still owns into passthrough mode, keeping their DMA off the slower translated path.

    Do not add vfio-pci.ids= here as well. It does the same job as the options line in step 4 and leaves two places to edit next time the card changes.

  7. Rebuild the initramfs and the boot configuration.

    bash
    sudo update-initramfs -u -k all
    sudo update-grub
  8. Reboot, then confirm the whole card landed on vfio-pci.

    bash
    sudo reboot

    Once it is back, substituting your card's bus address:

    bash
    lspci -nnk -s 01:00.

    Every function must report vfio-pci:

    01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060] [10de:2487] (rev a1)
    	Kernel driver in use: vfio-pci
    	Kernel modules: nvidiafb, nouveau
    01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)
    	Kernel driver in use: vfio-pci
    	Kernel modules: snd_hda_intel

    If a function still shows its ordinary driver, the softdep for that driver is missing or misspelled — the name in vfio.conf must match the Kernel modules: line exactly, underscores and all.

Attach the GPU to a VM

  1. Create the VM from Virtual machinesCreate VM.

    Fill in the name, installation source, storage, and memory, then choose Create and edit. Do not tick Immediately start VM: the firmware cannot be changed once the guest has booted, and the next step has to happen first.

  2. Switch the firmware to UEFI.

    On the VM's Overview, click the Firmware value — it reads BIOS on a new VM — select UEFI, and save. This is what the ovmf package provides, and it is a one-way door: after the first boot the field is fixed.

  3. Add the GPU under Host devicesAdd host device.

    Set Type to PCI, tick every function of the card — the display controller and its audio controller both — and click Add. The VM must be shut off. Leaving the audio controller behind costs the guest its HDMI and DisplayPort audio, and if that function is still held by a host driver the VM fails to start with a group is not viable error, because VFIO hands over an IOMMU group whole or not at all.

  4. Start the VM and install the guest operating system.

    libvirt detaches the devices from the host and hands them to QEMU as it starts. If the VM refuses to start with an error about a device being in use, re-check step 8 of the previous sub-task.

  5. Confirm the guest sees the card.

    In a Linux guest:

    bash
    lspci -nn | grep -i vga

    The card appears with the same vendor and device IDs it had on the host. In a Windows guest, look under Display adapters in Device Manager.

  6. Install the vendor's GPU driver inside the guest.

    Real display output now comes from the card's own ports. Cockpit's built-in console keeps showing QEMU's emulated display adapter, which is expected and not a sign the passthrough failed — plug a monitor into the card to see the guest's actual output.

    GeForce cards no longer need the old <kvm><hidden state='on'/></kvm> domain workaround: NVIDIA has supported virtualisation on GeForce since driver 465.89. If you meet the "Code 43" error, the guest driver predates that release.

See also

On this page