PCI Passthrough para Máquinas Virtuales con VFIO

El PCI passthrough permite asignar dispositivos de hardware físico directamente a máquinas virtuales KVM, ofreciendo rendimiento nativo sin virtualización de la GPU u otros dispositivos PCIe. Esta guía cubre la configuración de IOMMU, el binding con el driver VFIO, el GPU passthrough completo, el ajuste de rendimiento y la resolución de problemas comunes.

Requisitos Previos

  • CPU con soporte de virtualización: Intel VT-x + VT-d, o AMD-V + AMD-Vi/IOMMU
  • BIOS/UEFI con IOMMU habilitado (buscar "VT-d", "AMD IOMMU", o "SR-IOV")
  • Linux con kernel 4.1+ (soporte VFIO)
  • KVM/QEMU instalado
  • El dispositivo a pasar debe estar en un grupo IOMMU propio o con todos los dispositivos del grupo
# Verificar soporte de virtualización
egrep -c '(vmx|svm)' /proc/cpuinfo

# Verificar soporte IOMMU de la CPU
dmesg | grep -E "IOMMU|DMAR"

# Verificar la versión del kernel
uname -r

Activación de IOMMU

# Editar la configuración de GRUB
nano /etc/default/grub

# Para Intel VT-d, añadir a GRUB_CMDLINE_LINUX:
GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt"

# Para AMD-Vi, añadir:
GRUB_CMDLINE_LINUX="amd_iommu=on iommu=pt"

# iommu=pt (passthrough mode) mejora el rendimiento de los dispositivos
# que NO usan passthrough

# Actualizar GRUB
update-grub   # Ubuntu/Debian
grub2-mkconfig -o /boot/grub2/grub.cfg  # CentOS/Rocky

# Reiniciar el servidor
reboot

# Verificar que IOMMU está activo después del reinicio
dmesg | grep -E "IOMMU enabled|Adding to iommu"
cat /proc/cmdline | grep iommu

Verificar los grupos IOMMU:

# Script para listar todos los grupos IOMMU y sus dispositivos
cat << 'EOF' > /usr/local/bin/list-iommu-groups.sh
#!/bin/bash
# Listar grupos IOMMU con sus dispositivos

shopt -s nullglob
for g in /sys/kernel/iommu_groups/*; do
    echo "Grupo IOMMU ${g##*/}:"
    for d in $g/devices/*; do
        echo "  $(lspci -nns ${d##*/})"
    done
done | sort -V
EOF
chmod +x /usr/local/bin/list-iommu-groups.sh

/usr/local/bin/list-iommu-groups.sh

Identificación de Dispositivos PCIe

# Listar todos los dispositivos PCIe con sus IDs
lspci -nn

# Buscar la GPU (o el dispositivo a pasar)
lspci -nn | grep -E "VGA|3D|NVIDIA|AMD|Radeon|Display"

# Salida típica:
# 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102 [GeForce RTX 3090] [10de:2204] (rev a1)
# 01:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio [10de:1aef] (rev a1)

# IMPORTANTE: Para GPU passthrough necesitas AMBOS dispositivos
# La GPU (VGA) y el audio de la GPU deben estar en el mismo grupo IOMMU

# Ver el grupo IOMMU de la GPU
IOMMU_GROUP=$(ls /sys/bus/pci/devices/0000:01:00.0/iommu_group/devices/)
echo "Grupo IOMMU de la GPU:"
for dev in $IOMMU_GROUP; do
    lspci -nns ${dev##*/}
done

# Anotar los IDs de vendor:device para la configuración VFIO
# Ejemplo: 10de:2204 y 10de:1aef para la GPU NVIDIA

Configuración de VFIO

# Instalar VFIO y herramientas de KVM
apt install -y qemu-kvm libvirt-daemon-system virt-manager

# Cargar el módulo VFIO
modprobe vfio
modprobe vfio-pci
modprobe vfio_iommu_type1

# Hacer permanente la carga de módulos
echo "vfio" >> /etc/modules-load.d/vfio.conf
echo "vfio-pci" >> /etc/modules-load.d/vfio.conf
echo "vfio_iommu_type1" >> /etc/modules-load.d/vfio.conf

# Configurar VFIO para que tome control de los dispositivos ANTES que el driver nativo
# Usando los IDs del paso anterior (10de:2204 y 10de:1aef para NVIDIA)
cat << 'EOF' > /etc/modprobe.d/vfio.conf
# Asignar los dispositivos PCI al driver VFIO en lugar del driver nativo
# Formato: options vfio-pci ids=VENDOR:DEVICE[,VENDOR:DEVICE]
options vfio-pci ids=10de:2204,10de:1aef
EOF

# Para NVIDIA: evitar que el driver noveau/nvidia cargue antes que VFIO
cat << 'EOF' >> /etc/modprobe.d/vfio.conf
# Desactivar los drivers de NVIDIA para que VFIO tenga prioridad
softdep nvidia pre: vfio-pci
softdep nouveau pre: vfio-pci
EOF

# En Ubuntu, actualizar initramfs
update-initramfs -u

# Reiniciar el servidor
reboot

# Verificar que VFIO tiene el control de los dispositivos
lspci -nnk | grep -A3 "NVIDIA"
# Debe mostrar: Kernel driver in use: vfio-pci

GPU Passthrough

Para pasar una GPU completa a una VM:

# Verificar que VFIO tiene control de la GPU
lspci -k | grep -E "NVIDIA|vfio"

# Ver el dispositivo desde el directorio de VFIO
ls /dev/vfio/

# Añadir el usuario libvirt al grupo de acceso a VFIO
usermod -aG kvm,libvirt usuario_admin

# Crear una VM con virt-install para GPU passthrough
virt-install \
    --name vm-gpu \
    --memory 16384 \
    --vcpus 8 \
    --os-variant win10 \
    --disk path=/var/lib/libvirt/images/vm-gpu.qcow2,size=100,format=qcow2 \
    --cdrom /ruta/windows.iso \
    --graphics spice \
    --video qxl \
    --host-device 01:00.0 \     # GPU
    --host-device 01:00.1 \     # Audio de la GPU
    --boot uefi

Configurar el XML de la VM para passthrough óptimo:

<!-- virsh edit vm-gpu -->
<!-- Añadir en el bloque <features>: -->
<features>
  <acpi/>
  <apic/>
  <!-- Ocultar la virtualización KVM de la GPU (necesario para algunos drivers) -->
  <kvm>
    <hidden state='on'/>
  </kvm>
  <!-- Hypervisor ID - necesario para NVIDIA -->
  <hyperv mode='passthrough'>
    <relaxed state='on'/>
    <vapic state='on'/>
    <spinlocks state='on' retries='8191'/>
    <vendor_id state='on' value='1234567890ab'/>
  </hyperv>
</features>

<!-- Configuración del dispositivo PCI en el XML: -->
<hostdev mode='subsystem' type='pci' managed='yes'>
  <driver name='vfio'/>
  <source>
    <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
  </source>
  <address type='pci' domain='0x0000' bus='0x07' slot='0x00' function='0x0'/>
</hostdev>

Configuración de la VM con Passthrough

# Configurar hugepages para mejor rendimiento de memoria en la VM
# Reservar 16 GB (para una VM de 16 GB)
echo 8192 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# Hacer permanente
echo "vm.nr_hugepages = 8192" >> /etc/sysctl.conf
sysctl -p

# Añadir hugepages a la configuración de la VM
virsh edit vm-gpu
# Añadir en <memoryBacking>:
<memoryBacking>
  <hugepages>
    <page size='2' unit='M'/>
  </hugepages>
</memoryBacking>

<!-- Configurar la política de NUMA para mejor rendimiento -->
<numatune>
  <memory mode='strict' nodeset='0'/>
</numatune>

<!-- CPU pinning: asignar cores físicos específicos a la VM -->
<cputune>
  <vcpupin vcpu='0' cpuset='2'/>
  <vcpupin vcpu='1' cpuset='3'/>
  <vcpupin vcpu='2' cpuset='4'/>
  <vcpupin vcpu='3' cpuset='5'/>
  <!-- Cores del host reservados para el SO -->
  <emulatorpin cpuset='0-1'/>
</cputune>

Aislamiento de CPU y Ajuste de Rendimiento

# Aislar CPUs del sistema operativo host para la VM
# Editar parámetros del kernel en GRUB
# GRUB_CMDLINE_LINUX="... isolcpus=2-9 nohz_full=2-9 rcu_nocbs=2-9"

# Verificar el aislamiento de CPUs
cat /sys/devices/system/cpu/isolated

# Configurar el governor de CPU para rendimiento máximo
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
    echo "performance" > $cpu
done

# Verificar el governor activo
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# Activar/Desactivar MSR para la VM (Intel)
echo 1 > /sys/bus/pci/devices/0000:01:00.0/enable

Solución de Problemas

# IOMMU no está activo después del reinicio
dmesg | grep -i iommu
# Si no aparece nada, verificar en BIOS/UEFI que VT-d/AMD-Vi está habilitado

# Error "Device or resource busy" al asignar a VFIO
# El driver nativo está cargado antes que VFIO
lspci -k | grep -A2 "0000:01:00.0"
# Si muestra un driver diferente a vfio-pci:
echo "0000:01:00.0" > /sys/bus/pci/devices/0000:01:00.0/driver/unbind
echo "10de 2204" > /sys/bus/pci/drivers/vfio-pci/new_id

# Error en la VM: "VFIO: Failed to open /dev/vfio/iommu_group"
ls -la /dev/vfio/
# Verificar permisos
chmod 666 /dev/vfio/*

# La GPU no es detectada en la VM (Error 43 en Windows)
# Añadir vendor_id y ocultar KVM:
virsh edit vm-gpu
# Añadir <vendor_id state='on' value='1234567890ab'/> en <hyperv>

# Verificar que el grupo IOMMU es correcto
/usr/local/bin/list-iommu-groups.sh | grep -B1 -A5 "01:00"

# Ver los logs de QEMU/KVM
journalctl -u libvirtd -f
virsh dumpxml vm-gpu | grep hostdev

Conclusión

El PCI passthrough con VFIO transforma KVM en una plataforma de virtualización de rendimiento nativo para dispositivos GPU, tarjetas de captura de video, o cualquier hardware PCIe que requiera acceso directo. La configuración es compleja pero el resultado es una VM con rendimiento de hardware real, ideal para servidores de IA/ML, estaciones de trabajo remotas con GPU, o la consolidación de workloads especializados en hardware compartido. El éxito depende principalmente de la correcta configuración de IOMMU y de que el dispositivo esté en un grupo IOMMU propio o con dispositivos que también se vayan a pasar.