Setting up a Virtual Workstation in OpenShift with VFIO Passthrough

Feb 27, 2023

25 min read

This guide explains how to configure OpenShift as a workstation with GPU PCI passthrough using Container Native Virtualization (CNV) on a single OpenShift node (SNO). This setup delivers near-native performance for GPU-intensive applications while leveraging Kubernetes orchestration capabilities.

Key Benefits:

  • Run containerized workloads and virtual machines on the same hardware

  • Use a single GPU for both Kubernetes pods and VMs by switching driver binding

  • Achieve near-native performance for gaming and professional applications in VMs

  • Maintain Kubernetes/OpenShift flexibility for other workloads

In testing, this configuration successfully ran Microsoft Flight Simulator in a Windows VM with performance comparable to a bare metal Windows installation.

Hardware Used:

Component

Specification

CPU

AMD Ryzen 9 3950X (16-Core, 32-Threads)

Memory

64GB DDR4 3200MHz

GPU

Nvidia RTX 3080 FE 10GB

Storage

2x 2TB NVMe Disks (VM storage)
1x 500GB SSD Disk (OpenShift root system)

Network

10Gbase-CX4 Mellanox Ethernet

Similar configurations with Intel CPUs should work with minor adjustments noted throughout this guide.

Installing OpenShift SNO

Before installation, be sure to back up any existing partition data.

Backup Existing System Partitions

The OpenShift assisted installer formats the first 512 bytes of any disk with a bootable partition. Back up and remove any existing partition tables you want to preserve.

OpenShift Installation

Note

You can use the OpenShift web UI installer in Red Hat Hybrid Cloud Console. This guides you through installation with the Assisted Installer service:

https://console.redhat.com/openshift/assisted-installer/clusters

This also provides an automated way to install multiple Operators from Day 0.

Relevant Operators for this setup:
  • Logical Volume Manager Storage

  • NMState

  • Node Feature Discovery

  • NVIDIA GPU

  • OpenShift Virtualization

After backing up existing file systems and removing bootable partitions, proceed with the OpenShift Single Node installation.

CoreOS (the underlying operating system) requires an entire disk for installation:

  1. 500GB SSD for the OpenShift operating system

  2. Two 2TB NVMe disks for persistent volumes as LVM Physical volumes in the same Volume Group

  3. This setup enables flexible VM storage management while keeping the system installation separate

 1#!/bin/bash
 2
 3OCP_VERSION=latest-4.10
 4
 5curl -k https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz > oc.tar.gz
 6tar zxf oc.tar.gz
 7chmod +x oc && mv oc ~/.local/bin/
 8
 9curl -k https://mirror.openshift.com/pub/openshift-v4/clients/ocp/$OCP_VERSION/openshift-install-linux.tar.gz > openshift-install-linux.tar.gz
10tar zxvf openshift-install-linux.tar.gz
11chmod +x openshift-install && mv openshift-install ~/.local/bin/
12
13curl $(openshift-install coreos print-stream-json | grep location | grep x86_64 | grep iso | cut -d\" -f4) > rhcos-live.x86_64.iso
install-config.yaml
 1# This file contains the configuration for an OpenShift cluster installation.
 2
 3apiVersion: v1
 4
 5# The base domain for the cluster.
 6baseDomain: epheo.eu
 7
 8# Configuration for the compute nodes.
 9compute:
10- name: worker
11  replicas: 0 
12
13# Configuration for the control plane nodes.
14controlPlane:
15  name: master
16  replicas: 1 
17
18# Metadata for the cluster.
19metadata:
20  name: da2
21
22# Networking configuration for the cluster.
23networking:
24  networkType: OVNKubernetes
25  clusterNetwork:
26  - cidr: 10.128.0.0/14
27    hostPrefix: 23
28  serviceNetwork:
29  - 172.30.0.0/16
30
31# Platform configuration for the cluster.
32platform:
33  none: {}
34
35# Configuration for bootstrapping the cluster.
36bootstrapInPlace:
37  installationDisk: /dev/sda
38
39# Pull secret for accessing the OpenShift registry.
40pullSecret: '{"auths":{"cloud.openshift.com":{"auth":"XXXXXXXX"}}}' 
41
42# SSH key for accessing the cluster nodes.
43sshKey: |
44  ssh-rsa AAAAB3XXXXXXXXXXXXXXXXXXXXXXXXX
Generate OpenShift Container Platform assets
mkdir ocp && cp install-config.yaml ocp
openshift-install --dir=ocp create single-node-ignition-config
Embed the ignition data into the RHCOS ISO:
alias coreos-installer='podman run --privileged --rm \
      -v /dev:/dev -v /run/udev:/run/udev -v $PWD:/data \
      -w /data quay.io/coreos/coreos-installer:release'
cp ocp/bootstrap-in-place-for-live-iso.ign iso.ign
coreos-installer iso ignition embed -fi iso.ign rhcos-live.x86_64.iso
dd if=discovery_image_sno.iso of=/dev/usbkey status=progress

After copying the ISO to a USB drive, boot your workstation from it to install OpenShift.

Installing CNV Operator

Enable Intel VT or AMD-V hardware virtualization extensions in your BIOS/UEFI settings.

cnv-resources.yaml
 1# This YAML file contains Kubernetes resources for installing the KubeVirt Hyperconverged Operator (HCO) on the OpenShift Container Platform.
 2# It creates a namespace named "openshift-cnv", an operator group named "kubevirt-hyperconverged-group" in the "openshift-cnv" namespace, and a subscription named "hco-operatorhub" in the "openshift-cnv" namespace.
 3# The subscription specifies the source, source namespace, name, starting CSV, and channel for the KubeVirt Hyperconverged Operator.
 4
 5apiVersion: v1
 6kind: Namespace
 7metadata:
 8  name: openshift-cnv
 9---
10apiVersion: operators.coreos.com/v1
11kind: OperatorGroup
12metadata:
13  name: kubevirt-hyperconverged-group
14  namespace: openshift-cnv
15spec:
16  targetNamespaces:
17    - openshift-cnv
18---
19apiVersion: operators.coreos.com/v1alpha1
20kind: Subscription
21metadata:
22  name: hco-operatorhub
23  namespace: openshift-cnv
24spec:
25  source: redhat-operators
26  sourceNamespace: openshift-marketplace
27  name: kubevirt-hyperconverged
28  startingCSV: kubevirt-hyperconverged-operator.v4.10.0
29  channel: "stable"
oc apply -f cnv-resources.yaml
Installing Virtctl client on your desktop
subscription-manager repos --enable cnv-4.10-for-rhel-8-x86_64-rpms
dnf install kubevirt-virtctl

Configuring OpenShift for GPU Passthrough

Since we’re working with a single GPU, additional configuration is required.

We’ll use MachineConfig to configure our node. In a single-node OpenShift setup, all MachineConfig changes apply to the master machineset. In multi-node clusters, these would apply to workers instead.

Setting Kernel Boot Arguments

To enable GPU passthrough, we need to pass several kernel arguments at boot time via the MachineConfigOperator:

  • amd_iommu=on: Enables IOMMU support for AMD platforms (use intel_iommu=on for Intel CPUs)

  • vga=off: Disables VGA console output during boot

  • rdblaclist=nouveau: Blacklists the Nouveau open-source NVIDIA driver

  • video=efifb:off: Disables EFI framebuffer console output

Setting Kernel Arguments at boot time.
 1variant: openshift
 2version: 4.10.0
 3metadata:
 4  name: 100-vfio
 5  labels:
 6    machineconfiguration.openshift.io/role: master
 7openshift:
 8  kernel_arguments:
 9    - amd_iommu=on
10    - vga=off
11    - rdblaclist=nouveau
12    - 'video=efifb:off'
1cd articles/openshift-workstation/machineconfig/build
2butane -d . vfio-prepare.bu -o ../vfio-prepare.yaml
3oc patch MachineConfig 100-vfio --type=merge -p ../vfio-prepare.yaml

Note

Intel CPU users: use intel_iommu=on instead of amd_iommu=on.

Installing the NVIDIA GPU Operator

The NVIDIA GPU Operator simplifies GPU management in Kubernetes environments.

Step 1: Install the Operator

Via OpenShift web console: 1. Go to OperatorsOperatorHub 2. Search for “NVIDIA GPU Operator” 3. Select the operator and click Install 4. Keep default settings and click Install

Or via CLI:

oc create -f https://raw.githubusercontent.com/NVIDIA/gpu-operator/master/deployments/git/operator-namespace.yaml
oc create -f https://raw.githubusercontent.com/NVIDIA/gpu-operator/master/deployments/git/operator-source.yaml

Step 2: Configure the ClusterPolicy

Set sandboxWorkloads.enabled to true to enable the components needed for GPU passthrough:

sandboxWorkloadsEnabled.yaml
1kind: ClusterPolicy
2metadata:
3  name: gpu-cluster-policy
4spec:
5  sandboxWorkloads:
6    defaultWorkload: container
7    enabled: true
oc patch ClusterPolicy gpu-cluster-policy --type=merge -p sandboxWorkloadsEnabled.yaml

The NVIDIA GPU Operator doesn’t officially support consumer-grade GPUs and won’t automatically bind the GPU audio device to the vfio-pci driver. We’ll handle this manually with the following machine config:

vfio-prepare.bu
 1variant: openshift
 2version: 4.10.0
 3metadata:
 4  name: 100-vfio
 5  labels:
 6    machineconfiguration.openshift.io/role: master
 7storage:
 8  files:
 9  - path: /usr/local/bin/vfio-prepare
10    mode: 0755
11    overwrite: true
12    contents:
13      local: ./vfio-prepare.sh
14  - path: /etc/modules-load.d/vfio-pci.conf
15    mode: 0644
16    overwrite: true
17    contents:
18      inline: vfio-pci
19systemd:
20  units:
21    - name: vfioprepare.service
22      enabled: true
23      contents: |
24       [Unit]
25       Description=Prepare vfio devices
26       After=ignition-firstboot-complete.service
27       Before=kubelet.service crio.service
28
29       [Service]
30       Type=oneshot
31       ExecStart=/usr/local/bin/vfio-prepare
32
33       [Install]
34       WantedBy=kubelet.service
vfio-prepare.sh
 1#!/bin/bash
 2
 3vfio_attach () {
 4  if [ -f "${path}/driver/unbind" ]; then
 5    echo $address > ${path}/driver/unbind
 6  fi
 7  echo vfio-pci > ${path}/driver_override
 8  echo $address > /sys/bus/pci/drivers/vfio-pci/bind || \
 9  echo $name > /sys/bus/pci/drivers/vfio-pci/new_id ||true
10}
11
12# 0a:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)
13address=0000:0a:00.1
14path=/sys/bus/pci/devices/0000\:0a\:00.1
15name="10de 1467"
16vfio_attach
1cd articles/openshift-workstation/machineconfig/build
2butane -d . vfio-prepare.bu -o ../vfio-prepare.yaml
3oc patch MachineConfig 100-vfio --type=merge -p ../vfio-prepare.yaml

Dynamically Switching GPU Drivers

A key advantage of this setup is using a single GPU for both container workloads and VMs without rebooting.

Use Case Scenario

  • Single NVIDIA GPU shared between container workloads and VMs

  • Container workloads require the NVIDIA kernel driver

  • VMs with GPU passthrough require the VFIO-PCI driver

  • Switching between modes without rebooting

Driver Switching Using Node Labels

The NVIDIA GPU Operator with sandbox workloads enabled lets you switch driver bindings using node labels:

For container workloads (NVIDIA driver):

# Replace 'da2' with your node name
oc label node da2 --overwrite nvidia.com/gpu.workload.config=container

For VM passthrough (VFIO-PCI driver):

# Replace 'da2' with your node name
oc label node da2 --overwrite nvidia.com/gpu.workload.config=vm-passthrough

Notes on Driver Switching

  • Driver switching takes a few minutes

  • Verify current driver with lspci -nnk | grep -A3 NVIDIA

  • Stop all GPU workloads before switching drivers

  • No reboot is usually required

  • Can be occasionally unreliable and may require a reboot

Adding GPU as a Hardware Device

First, identify the GPU’s Vendor and Product ID:

lspci -nnk |grep VGA

Then, identify the device name provided by gpu-feature-discovery:

oc get nodes da2 -ojson |jq .status.capacity |grep nvidia

Now, add the GPU to the permitted host devices:

 1kind: HyperConverged
 2metadata:
 3  name: kubevirt-hyperconverged
 4  namespace: openshift-cnv
 5spec:
 6  permittedHostDevices:
 7    pciHostDevices:
 8    - externalResourceProvider: true
 9      pciDeviceSelector: 10DE:2206
10      resourceName: nvidia.com/GA102_GEFORCE_RTX_3080
oc patch hyperconverged kubevirt-hyperconverged -n openshift-cnv --type=merge -f hyperconverged.yaml

The pciDeviceSelector specifies the vendor:device ID, while resourceName specifies the resource name in Kubernetes/OpenShift.

Passthrough USB Controllers to VMs

For a complete desktop experience, you’ll want to pass through an entire USB controller to your VM for better performance and flexibility.

Identifying a Suitable USB Controller

  1. List all USB controllers:

    lspci -nnk | grep -i usb
    

    Example output: ` 0b:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Matisse USB 3.0 Host Controller [1022:149c] `

  2. Note the PCI address (e.g., 0b:00.3) and device ID (1022:149c).

  3. Check the IOMMU group:

    find /sys/kernel/iommu_groups/ -iname "*0b:00.3*"
    # Shows which IOMMU group contains this device
    
    ls /sys/kernel/iommu_groups/27/devices/
    # Lists all devices in the same IOMMU group
    
  4. Important: For clean passthrough, the USB controller should ideally be alone in its IOMMU group. If other devices share the group, you’ll need to pass those through as well.

Adding the USB Controller as a Permitted Device

Add the controller’s Vendor and Product IDs to permitted host devices:

 1kind: HyperConverged
 2metadata:
 3  name: kubevirt-hyperconverged
 4  namespace: openshift-cnv
 5spec:
 6  permittedHostDevices:
 7    pciHostDevices:
 8      - pciDeviceSelector: 1022:149C
 9        resourceName: devices.kubevirt.io/USB3_Controller
10      - pciDeviceSelector: 8086:2723
11        resourceName: intel.com/WIFI_Controller
oc patch hyperconverged kubevirt-hyperconverged -n openshift-cnv --type=merge -f hyperconverged.yaml

Binding the USB Controller to VFIO-PCI Driver

vfio-prepare.bu
 1variant: openshift
 2version: 4.10.0
 3metadata:
 4  name: 100-vfio
 5  labels:
 6    machineconfiguration.openshift.io/role: master
 7storage:
 8  files:
 9  - path: /usr/local/bin/vfio-prepare
10    mode: 0755
11    overwrite: true
12    contents:
13      local: ./vfio-prepare.sh
14  - path: /etc/modules-load.d/vfio-pci.conf
15    mode: 0644
16    overwrite: true
17    contents:
18      inline: vfio-pci
19  - path: /etc/modprobe.d/vfio.conf
20    mode: 0644
21    overwrite: true
22    contents:
23      inline: |
24        options vfio-pci ids=8086:2723,1022:149c
25systemd:
26  units:
27    - name: vfioprepare.service
28      enabled: true
29      contents: |
30       [Unit]
31       Description=Prepare vfio devices
32       After=ignition-firstboot-complete.service
33       Before=kubelet.service crio.service
34
35       [Service]
36       Type=oneshot
37       ExecStart=/usr/local/bin/vfio-prepare
38
39       [Install]
40       WantedBy=kubelet.service
41openshift:
42  kernel_arguments:
43    - amd_iommu=on
44    - vga=off
45    - rdblaclist=nouveau
46    - 'video=efifb:off'

Create a script to unbind the USB controller from its current driver and bind it to vfio-pci:

vfio-prepare.sh
 1#!/bin/bash
 2
 3vfio_attach () {
 4  if [ -f "${path}/driver/unbind" ]; then
 5    echo $address > ${path}/driver/unbind
 6  fi
 7  echo vfio-pci > ${path}/driver_override
 8  echo $address > /sys/bus/pci/drivers/vfio-pci/bind || \
 9  echo $name > /sys/bus/pci/drivers/vfio-pci/new_id ||true
10}
11
12# 0a:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)
13address=0000:0a:00.1
14path=/sys/bus/pci/devices/0000\:0a\:00.1
15name="10de 1467"
16vfio_attach
17
18# Bind "useless" device to vfio-pci to satisfy IOMMU group
19address=0000:07:00.0
20path=/sys/bus/pci/devices/0000\:07\:00.0
21name="1043 87c0"
22vfio_attach
23
24# Unbind USB switch and handle via vfio-pci kernel driver
25address=0000:07:00.1
26path=/sys/bus/pci/devices/0000\:07\:00.1
27name="1043 87c0"
28vfio_attach
29
30# Unbind USB switch and handle via vfio-pci kernel driver
31address=0000:07:00.3
32path=/sys/bus/pci/devices/0000\:07\:00.3
33name="1022 149c"
34vfio_attach
35
36# Unbind USB switch and handle via vfio-pci kernel driver
37address=0000:0c:00.3
38path=/sys/bus/pci/devices/0000\:0c\:00.3
39name="1022 148c"
40vfio_attach
1cd articles/openshift-workstation/machineconfig/build
2butane -d . vfio-prepare.bu -o ../vfio-prepare.yaml
3oc patch MachineConfig 100-vfio --type=merge -p ../vfio-prepare.yaml

Creating VMs with GPU Passthrough

This section explains how to create VMs that can use GPU passthrough, using existing LVM Logical Volumes with UEFI boot.

Creating Persistent Volumes from LVM Disks

First, make LVM volumes available to OpenShift via Persistent Volume Claims (PVCs). This assumes you have the Local Storage Operator installed.

  1. Create a YAML file for each VM disk:

fedora_pvc.yaml
 1---
 2kind: PersistentVolumeClaim
 3apiVersion: v1
 4metadata:
 5  name: fedora35
 6spec:
 7  accessModes:
 8  - ReadWriteOnce
 9  volumeMode: Block
10  resources:
11    requests:
12      storage: 100Gi 
13  storageClassName: lvms-vg1
  1. Apply the YAML:

oc apply -f fedora35.yaml
  1. Verify the PV and PVC are bound:

oc get pv
oc get pvc -n <your-namespace>

Defining VMs with GPU Passthrough

Key configuration elements for desktop VMs with GPU passthrough:

  1. GPU Passthrough: Pass the entire physical GPU to the VM

  2. Disable Virtual VGA: Remove the emulated VGA device

  3. USB Controller Passthrough: For connecting peripherals directly

  4. UEFI Boot: For compatibility with modern OSes and GPU drivers

  5. CPU/Memory Configuration: Based on workload requirements

fedora.yaml
 1apiVersion: kubevirt.io/v1
 2kind: VirtualMachine
 3metadata:
 4  name: fedora
 5  namespace: epheo
 6spec:
 7  runStrategy: Halted
 8  template:
 9    metadata:
10      labels:
11        kubevirt.io/domain: fedora
12    spec:
13      architecture: amd64
14      domain:
15        cpu:
16          cores: 8
17          model: host-passthrough
18          sockets: 2
19          threads: 1
20        features:
21          acpi: {}
22          smm:
23            enabled: true 
24        firmware:
25          bootloader:
26            efi:
27              secureBoot: false # For Nvidia Driver...
28        devices:
29          disks:
30            - bootOrder: 1
31              disk:
32                bus: virtio
33              name: pvdisk
34            - disk:
35                bus: virtio
36              name: cloudinitdisk
37          autoattachGraphicsDevice: false
38          gpus:
39          - deviceName: nvidia.com/GA102_GEFORCE_RTX_3080
40            name: gpuvideo
41          hostDevices:
42          - deviceName: devices.kubevirt.io/USB3_Controller
43            name: usbcontroller
44          - deviceName: devices.kubevirt.io/USB3_Controller
45            name: usbcontroller2
46          - deviceName: intel.com/WIFI_Controller
47            name: wificontroller
48          interfaces:
49          - masquerade: {}
50            name: default
51          - bridge: {}
52            model: virtio
53            name: nic-0
54          networkInterfaceMultiqueue: true
55          rng: {}
56        machine:
57          type: q35
58        resources:
59          requests:
60            memory: 16G
61      hostname: fedora
62      networks:
63      - name: default
64        pod: {}
65      - multus:
66          networkName: br1
67        name: nic-0
68      terminationGracePeriodSeconds: 0
69      volumes:
70        - persistentVolumeClaim:
71            claimName: 'fedora35'
72          name: pvdisk
73        - cloudInitNoCloud:
74            userData: |-
75              #cloud-config
76              password: fedora
77              chpasswd: { expire: False }
78          name: cloudinitdisk
windows.yaml
  1apiVersion: kubevirt.io/v1
  2kind: VirtualMachine
  3metadata:
  4  annotations:
  5    vm.kubevirt.io/os: windows10
  6    vm.kubevirt.io/workload: desktop
  7  name: windows
  8spec:
  9  runStrategy: Manual
 10  template:
 11    metadata:
 12      labels:
 13        kubevirt.io/domain: windows
 14    spec:
 15      architecture: amd64
 16      domain:
 17        clock:
 18          timer:
 19            hpet:
 20              present: false
 21            hyperv: {}
 22            pit:
 23              tickPolicy: delay
 24            rtc:
 25              tickPolicy: catchup
 26          utc: {}
 27        cpu:
 28          cores: 8
 29          dedicatedCpuPlacement: true
 30          sockets: 2
 31          threads: 1
 32        devices:
 33          autoattachGraphicsDevice: false
 34          disks:
 35          - cdrom:
 36              bus: sata
 37            name: windows-guest-tools
 38          - bootOrder: 1
 39            disk:
 40              bus: virtio
 41            name: pvdisk
 42          - disk:
 43              bus: virtio
 44            name: pvdisk1
 45          gpus:
 46          - deviceName: nvidia.com/GA102_GEFORCE_RTX_3080
 47            name: gpuvideo
 48          hostDevices:
 49          - deviceName: devices.kubevirt.io/USB3_Controller
 50            name: usbcontroller
 51          - deviceName: devices.kubevirt.io/USB3_Controller
 52            name: usbcontroller2
 53          - deviceName: intel.com/WIFI_Controller
 54            name: wificontroller
 55          interfaces:
 56          - bridge: {}
 57            model: virtio
 58            name: nic-0
 59          networkInterfaceMultiqueue: true
 60          rng: {}
 61          tpm: {}
 62        features:
 63          acpi: {}
 64          apic: {}
 65          hyperv:
 66            frequencies: {}
 67            ipi: {}
 68            reenlightenment: {}
 69            relaxed: {}
 70            reset: {}
 71            runtime: {}
 72            spinlocks:
 73              spinlocks: 8191
 74            synic: {}
 75            synictimer:
 76              direct: {}
 77            tlbflush: {}
 78            vapic: {}
 79            vpindex: {}
 80          smm: {}
 81        firmware:
 82          bootloader:
 83            efi:
 84              secureBoot: true
 85        machine:
 86          type: q35
 87        memory:
 88          hugepages:
 89            pageSize: 1Gi
 90        resources:
 91          requests:
 92            memory: 32Gi
 93      evictionStrategy: None
 94      hostname: windows
 95      networks:
 96      - multus:
 97          networkName: br1
 98        name: nic-0
 99      terminationGracePeriodSeconds: 3600
100      volumes:
101      - containerDisk:
102          image: registry.redhat.io/container-native-virtualization/virtio-win-rhel9@sha256:0c536c7aba76eb9c1e75a8f2dc2bbfa017e90314d55b242599ea41f42ba4434f
103        name: windows-guest-tools
104      - name: pvdisk
105        persistentVolumeClaim:
106          claimName: windows
107      - name: pvdisk1
108        persistentVolumeClaim:
109          claimName: windowsdata

Future Improvements

Some potential future improvements to this setup:

  • Using MicroShift instead of OpenShift to reduce Control Plane footprint

  • Running Linux Desktop in containers instead of VMs

  • Implementing more efficient resource allocation with CPU pinning and huge pages

Troubleshooting

IOMMU Group Issues

Problem: VM fails to start with:

{"component":"virt-launcher","level":"error","msg":"Failed to start VirtualMachineInstance",
"reason":"virError... vfio 0000:07:00.1: group 19 is not viable
Please ensure all devices within the iommu_group are bound to their vfio bus driver."}

Diagnosis: Not all devices in the IOMMU group are bound to vfio-pci. Check:

# Check devices in the IOMMU group
ls /sys/kernel/iommu_groups/19/devices/

# Check what these devices are
lspci -nnks 07:00.0

Solution: Bind all devices in the IOMMU group to vfio-pci:

# Add to vfio-prepare.sh
echo "vfio-pci" > /sys/bus/pci/devices/0000:03:08.0/driver_override
echo "vfio-pci" > /sys/bus/pci/devices/0000:07:00.0/driver_override
echo "vfio-pci" > /sys/bus/pci/devices/0000:07:00.1/driver_override
echo "vfio-pci" > /sys/bus/pci/devices/0000:07:00.3/driver_override

# Unbind from current drivers, then bind to vfio-pci

Common Issues and Solutions

No display output after GPU passthrough:

  • Disable virtual VGA in VM spec

  • Pass through both GPU and audio device

  • Install proper GPU drivers inside VM

Performance issues in Windows VM:

  • Configure CPU pinning correctly

  • Enable huge pages for better memory performance

  • Install latest NVIDIA drivers in VM

  • Disable Windows Game Bar and overlay software

GPU driver switching fails:

  • Stop all GPU workloads before switching

  • Check GPU operator logs: oc logs -n nvidia-gpu-operator <pod-name>

  • Verify IOMMU is enabled in BIOS/UEFI

For further troubleshooting, check logs:

  • virt-handler: oc logs -n openshift-cnv virt-handler-<hash>

  • virt-launcher: oc logs -n <namespace> virt-launcher-<vm-name>-<hash>

  • nvidia-driver-daemonset: oc logs -n nvidia-gpu-operator nvidia-driver-daemonset-<hash>