Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions qemu/tests/cfg/emulated_amd_iommu.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
- emulated_amd_iommu:
type = qemu_pci_passthrough
start_vm = no
only Linux
only x86_64
only HostCpuVendor.amd
pcie_extra_root_port = 0
vt_ulimit_nofile = 65536
usb_devices = ""
auto_cpu_model = "no"
cpu_model = "host"
machine_type = "q35"
machine_type_extra_params = "kernel-irqchip=split"
extra_params = " -global kvm-pit.lost_tick_policy=discard"
vcpu_sockets = 1
vcpu_dies = 1
vcpu_threads = 1
mem = 8G
mode = "x2apic"
variants:
- no_passthrough:
required_qemu = [8.2.90,)
# TODO Add passthrough variant
variants:
- vcpu_512:
smp_fixed = 512
vcpu_maxcpus = 512
vcpu_cores = 512
- vcpu_288:
smp_fixed = 288
vcpu_maxcpus = 288
vcpu_cores = 288
- vcpu_256:
smp_fixed = 256
vcpu_maxcpus = 256
vcpu_cores = 256
- vcpu_254:
smp_fixed = 254
vcpu_maxcpus = 254
vcpu_cores = 254
- vcpu_128:
smp_fixed = 128
vcpu_maxcpus = 128
vcpu_cores = 128
- vcpu_64:
smp_fixed = 64
vcpu_maxcpus = 64
vcpu_cores = 64
variants:
- x2apic_emul_amd_iommu:
cpu_model_flags += ",+x2apic"
extra_params += " -device amd-iommu,intremap=on,xtsup=on"
kvm_probe_module_parameters = "avic=0"
- x2avic_emul_amd_iommu:
cpu_model_flags += ",+x2apic"
extra_params += " -device amd-iommu,intremap=on,xtsup=on"
kvm_probe_module_parameters = "avic=1"
65 changes: 65 additions & 0 deletions qemu/tests/cfg/iommu_accelerated_guest_mode.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
- iommu_accelerated_guest_mode:
type = qemu_pci_passthrough
start_vm = no
only Linux
only x86_64
only HostCpuVendor.amd
pcie_extra_root_port = 0
vt_ulimit_nofile = 65536
usb_devices = ""
auto_cpu_model = "no"
cpu_model = "host"
machine_type = "q35"
machine_type_extra_params = "kernel-irqchip=split"
extra_params = " -global kvm-pit.lost_tick_policy=discard"
vcpu_sockets = 1
vcpu_dies = 1
vcpu_threads = 1
mem = 8G
mode = "x2apic"
kvm_probe_module_parameters = "avic=1"
# Update pci_device with host PCI device/s for passthrough to the guest
# enabling IOMMU functionality validation. eg. pci_device = "0000:01:00.0"
# Note: Without a PCI device input, the guest will still boot, but the
# test won't exercise the host IOMMU.
#pci_device = ""
variants:
- vcpu_512:
smp_fixed = 512
vcpu_maxcpus = 512
vcpu_cores = 512
- vcpu_288:
smp_fixed = 288
vcpu_maxcpus = 288
vcpu_cores = 288
- vcpu_256:
smp_fixed = 256
vcpu_maxcpus = 256
vcpu_cores = 256
- vcpu_254:
smp_fixed = 254
vcpu_maxcpus = 254
vcpu_cores = 254
- vcpu_128:
smp_fixed = 128
vcpu_maxcpus = 128
vcpu_cores = 128
- vcpu_64:
smp_fixed = 64
vcpu_maxcpus = 64
vcpu_cores = 64
variants:
- avic:
mode = "apic"
cpu_model_flags += ",-x2apic"
no vcpu_512, vcpu_288, vcpu_256
- x2avic_ext_apic_id:
cpu_model_flags += ",+x2apic"
cpu_model_flags += ",kvm-msi-ext-dest-id=on"
- x2avic_emul_intel_iommu:
intel_iommu = yes
iommu_eim = on
iommu_intremap = on
guest_iommu_option = pt
iommu_caching_mode = on
cpu_model_flags += ",+x2apic"
69 changes: 69 additions & 0 deletions qemu/tests/cfg/iommu_guest_mode.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
- iommu_guest_mode:
type = qemu_pci_passthrough
start_vm = no
only Linux
only x86_64
only HostCpuVendor.amd
pcie_extra_root_port = 0
vt_ulimit_nofile = 65536
usb_devices = ""
auto_cpu_model = "no"
cpu_model = "host"
machine_type = "q35"
machine_type_extra_params = "kernel-irqchip=split"
extra_params = ""
vcpu_sockets = 1
vcpu_dies = 1
vcpu_threads = 1
mem = 8G
mode = "x2apic"
kvm_probe_module_parameters = "avic=0"
# Update pci_device with host PCI device/s for passthrough to the guest
# enabling IOMMU functionality validation. eg. pci_device = "0000:01:00.0"
# Note: Without a PCI device input, the guest will still boot, but the
# test won't exercise the host IOMMU.
#pci_device = ""
variants:
- vcpu_1024:
smp_fixed = 1024
vcpu_maxcpus = 1024
vcpu_cores = 1024
- vcpu_512:
smp_fixed = 512
vcpu_maxcpus = 512
vcpu_cores = 512
- vcpu_288:
smp_fixed = 288
vcpu_maxcpus = 288
vcpu_cores = 288
- vcpu_256:
smp_fixed = 256
vcpu_maxcpus = 256
vcpu_cores = 256
- vcpu_254:
smp_fixed = 254
vcpu_maxcpus = 254
vcpu_cores = 254
- vcpu_128:
smp_fixed = 128
vcpu_maxcpus = 128
vcpu_cores = 128
- vcpu_64:
smp_fixed = 64
vcpu_maxcpus = 64
vcpu_cores = 64
variants:
- apic:
mode = "apic"
cpu_model_flags += ",-x2apic"
no vcpu_1024, vcpu_512, vcpu_288, vcpu_256
- x2apic_ext_apic_id:
cpu_model_flags += ",+x2apic"
cpu_model_flags += ",kvm-msi-ext-dest-id=on"
- x2apic_emul_intel_iommu:
intel_iommu = yes
iommu_eim = on
iommu_intremap = on
guest_iommu_option = pt
iommu_caching_mode = on
cpu_model_flags += ",+x2apic"
19 changes: 19 additions & 0 deletions qemu/tests/cfg/vfio_pci_passthrough.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
- vfio_pci_passthrough:
type = qemu_pci_passthrough
start_vm = no
only Linux
only x86_64
only HostCpuVendor.amd
pcie_extra_root_port = 0
vt_ulimit_nofile = 65536
usb_devices = ""
auto_cpu_model = "no"
cpu_model = "host"
machine_type = "q35"
extra_params = ""
mem = 8G
# Update pci_device with host PCI device/s for passthrough to the guest
# enabling IOMMU functionality validation. eg. pci_device = "0000:01:00.0"
# Note: Without a PCI device input, the guest will still boot, but the
# test won't exercise the host IOMMU.
#pci_device = ""
176 changes: 176 additions & 0 deletions qemu/tests/qemu_pci_passthrough.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""
Guest boot sanity test with passthrough device in different mode
- Apic, X2apic, Avic, X2avic
"""

from avocado.utils import dmesg, linux_modules, pci, process
from virttest import env_process


def run(test, params, env): # pylint: disable=R0915
"""
Guest boot sanity test with passthrough device
Steps:
1. Launch a guest with a vfio pci passthrough device
2. Verify if vfio pci passthrough of device to guest is successful
and guest boots in expected mode - apic, avic, x2apic and x2avic.

:param test: QEMU test object.
:param params: Dictionary with test parameters:
- pci_device: Pci device to passthrough. Default: ""
- mode: Defines guest modes - [apic, x2apic]. Default: x2apic.
- kvm_probe_module_parameters: To enable/disable avic on host.
Possible values ["avic=1", "avic=0"]
- login_timeout: VM login timeout in seconds. Default: 240).
:param env: Dictionary with test environment.
:raises: cancel if
1. At start of test, KVM module is not loaded.
2. KVM, msr, vfio_pci is not built or loadable.
3. pci_device != ""
a. vfio_pci is not built or loadable.
b. Interrupt remapping is not enabled on host. Required for
passthrough.
c. Pci device/s not found on host system.
4. kvm_probe_module_parameters = "avic=1"
a. Host doesnot support expected mode - avic or x2avic.
b. Host doesnot have expected mode enabled for guest - avic
or x2avic.
fails if
1. Pci device cannot be bind to vfio_pci module for passthrough.
(pci_device != "")
2. Unable to login to guest within login timeout.
"""
kvm_probe_module_parameters = params.get("kvm_probe_module_parameters", "")
login_timeout = int(params.get("login_timeout", 240))
pci_device = params.get("pci_device", "")
mode = params.get("mode", "x2apic")
driver_list = []
session = None
vm = None

try:

def check_avic_support():
"""
Check if system supports avic.
"""
cmd = "rdmsr -p 0 0xc00110dd --bitfield 13:13"
out = process.run(cmd, sudo=True, shell=True).stdout_text.strip()
if out == "0":
test.cancel("System doesnot support avic")

def check_x2avic_support():
"""
check if system supports x2avic.
"""
cmd = "rdmsr -p 0 0xc00110dd --bitfield 18:18"
out = process.run(cmd, sudo=True, shell=True).stdout_text.strip()
if out == "0":
test.cancel("System doesnot support x2avic")

def verify_avic_enablement(mode):
"""
Check AVIC and x2AVIC status in dmesg logs diff.

:param mode: Whether apic or x2apic.
"""

# Check for the "avic enabled" in dmesg
if not dmesg.check_kernel_logs("AVIC enabled"):
test.cancel("AVIC not enabled after loading kvm_amd with avic=1")

# Check for the "x2avic enabled" only if the test is "x2apic"
if (not dmesg.check_kernel_logs("x2AVIC enabled")) and (mode == "x2apic"):
test.cancel("x2AVIC not enabled after loading kvm_amd with avic=1.")

def prepare_pci_passthrough():
"""
Validate IOMMU, Interrupt Remapping, and vfio-pci module availability
"""

# Check if interrupt remapping is enabled on system
if not dmesg.check_kernel_logs("AMD-Vi: Interrupt remapping enabled"):
test.cancel("IOMMU interrupt remaping is not enabled")

# Check and load vfio-pci module
if not linux_modules.configure_module("vfio-pci", "CONFIG_VFIO_PCI"):
test.cancel("vfio-pci module can not be configured.")

def guest_system_details(session):
"""
Collect guest system details

:param session: active guest login session
"""
test.log.debug("Debug: %s", session.cmd_output("cat /etc/os-release"))
test.log.debug("Debug: %s", session.cmd_output("uname -a"))
test.log.debug("Debug: %s", session.cmd_output("ls /boot/"))
test.log.debug("Debug: %s", session.cmd_output("lspci -k"))
test.log.debug("Debug: %s", session.cmd_output("lscpu"))
test.log.debug("Debug: %s", session.cmd_output("lsblk"))
test.log.debug("Debug: %s", session.cmd_output("df -h"))
test.log.debug("Debug: %s", session.cmd_output("dmesg"))

# Check system support for avic or x2avic
if kvm_probe_module_parameters == "avic=1":
if not linux_modules.configure_module("msr", "CONFIG_X86_MSR"):
test.cancel("msr module can not be configured.")
if mode == "apic":
check_avic_support()
if mode == "x2apic":
check_x2avic_support()
# Validate dmesg for avic and x2avic enablement
verify_avic_enablement(mode)

# Passthrough device/s and validate if passthrough is successful
if pci_device != "":
# Perform pre-checks and prereq enablements before pci passthrough
prepare_pci_passthrough()

# Prepare for pci passthrough
for i in range(len(pci_device.split(" "))):
# Check if device input is valid
if pci_device.split(" ")[i] not in pci.get_pci_addresses():
test.cancel("Please provide valid pci device input.")

driver_list.append(pci.get_driver(pci_device.split(" ")[i]))
pci.attach_driver(pci_device.split(" ")[i], "vfio-pci")
params["extra_params"] += (
f" -device vfio-pci,host={pci_device.split(' ')[i]}"
)

Comment on lines +125 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Pre-compute the device list and use split() instead of split(" ").

pci_device.split(" ") is called 5 times per loop iteration, and split(" ") will produce empty strings if there are consecutive spaces. Split once before the loop and iterate over the result.

♻️ Proposed refactor
         # Passthrough device/s and validate if passthrough is successful
-        if pci_device != "":
+        devices = pci_device.split()
+        if devices:
             # Perform pre-checks and prereq enablements before pci passthrough
             prepare_pci_passthrough()
 
             # Prepare for pci passthrough
-            for i in range(len(pci_device.split(" "))):
+            for dev in devices:
                 # Check if device input is valid
-                if pci_device.split(" ")[i] not in pci.get_pci_addresses():
+                if dev not in pci.get_pci_addresses():
                     test.cancel("Please provide valid pci device input.")
 
-                driver_list.append(pci.get_driver(pci_device.split(" ")[i]))
-                pci.attach_driver(pci_device.split(" ")[i], "vfio-pci")
-                params[
-                    "extra_params"
-                ] += f" -device vfio-pci,host={pci_device.split(' ')[i]}"
+                driver_list.append(pci.get_driver(dev))
+                pci.attach_driver(dev, "vfio-pci")
+                params["extra_params"] += f" -device vfio-pci,host={dev}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@qemu/tests/qemu_pci_passthrough.py` around lines 125 - 141, Pre-compute the
device list to avoid repeated splitting and use split() (not split(" ")) to
avoid empty entries: create a local variable like devices = pci_device.split()
before the loop, then iterate for device in devices; inside the loop replace
every pci_device.split(" ")[i] with device when calling
prepare_pci_passthrough(), pci.get_pci_addresses(),
driver_list.append(pci.get_driver(device)), pci.attach_driver(device,
"vfio-pci") and when appending to params["extra_params"] so the checks
(pci.get_pci_addresses()) and updates (driver_list, pci.attach_driver,
params["extra_params"]) operate on the precomputed list and avoid redundant
splits.

params["start_vm"] = "yes"
vm = env.get_vm(params["main_vm"])
try:
env_process.preprocess_vm(test, params, env, params.get("main_vm"))
vm.verify_alive()
except Exception as e:
test.fail(f"Failed to create VM: {str(e)}")
try:
session = vm.wait_for_login(timeout=login_timeout)
except Exception as e:
test.fail(f"Failed to login VM: {str(e)}")
Comment on lines +144 to +152

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check if the file imports avocado exceptions
echo "=== Checking imports in qemu_pci_passthrough.py ==="
cat -n qemu/tests/qemu_pci_passthrough.py | head -50

echo -e "\n=== Searching for avocado imports across codebase ==="
rg -n "from avocado" --type py | head -20

echo -e "\n=== Searching for avocado exception handling patterns ==="
rg -n "except.*TestCancel|except.*TestError|except.*TestFail" --type py | head -20

echo -e "\n=== Checking if avocado is installed or referenced ==="
rg -n "import avocado" --type py | head -10

Repository: autotest/tp-qemu

Length of output: 1714


🌐 Web query:

avocado test framework TestCancel TestError TestFail exception hierarchy

💡 Result:

In Avocado (the avocado-framework test runner), the core test exception hierarchy is:

Exception
└─ avocado.core.exceptions.TestBaseException   (status = "FAIL")
   ├─ avocado.TestError / avocado.core.exceptions.TestError    (status = "ERROR")
   ├─ avocado.TestCancel / avocado.core.exceptions.TestCancel  (status = "CANCEL")
   └─ avocado.TestFail / avocado.core.exceptions.TestFail      (status = "FAIL")
        └─ also inherits from AssertionError

Key points:

  • TestBaseException is the parent of all Avocado test exceptions and inherits from Python Exception. [1]
  • TestError and TestCancel inherit from TestBaseException. [1], [2]
  • TestFail inherits from both TestBaseException and AssertionError (for unittest-style failure compatibility). [1], [2]
  • These exceptions are exposed in the public avocado namespace as avocado.TestFail, avocado.TestError, avocado.TestCancel. [2], [3]

Sources
[1] Avocado core API docs (avocado.core.exceptions): (avocado-framework.readthedocs.io)
[2] Avocado Test API docs (avocado.TestFail/TestError/TestCancel): (avocado-framework.readthedocs.io)
[3] Avocado 52.0 LTS release notes (public availability + behavior equivalence): (avocado-framework.readthedocs.io)

Citations:


Broad except Exception will swallow avocado framework exceptions (TestCancel, TestError, TestFail).

In avocado, these framework exceptions inherit from Exception and are used to classify test results (cancellation, error, failure). Catching bare Exception here converts them to test.fail() calls, misclassifying test outcomes. Catch and re-raise framework exceptions before the broad exception handler.

Proposed fix
+        from avocado.core.exceptions import TestCancel, TestError, TestFail
         try:
             env_process.preprocess_vm(test, params, env, params.get("main_vm"))
             vm.verify_alive()
+        except (TestCancel, TestError, TestFail):
+            raise
         except Exception as e:
             test.fail(f"Failed to create VM: {str(e)}")
         try:
             session = vm.wait_for_login(timeout=login_timeout)
+        except (TestCancel, TestError, TestFail):
+            raise
         except Exception as e:
             test.fail(f"Failed to login VM: {str(e)}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@qemu/tests/qemu_pci_passthrough.py` around lines 144 - 152, The code
currently uses broad except Exception blocks around env_process.preprocess_vm /
vm.verify_alive and vm.wait_for_login which will swallow avocado framework
exceptions (TestCancel, TestError, TestFail); update both try/except blocks to
first except and re-raise these framework exceptions (TestCancel, TestError,
TestFail) and then handle other exceptions by calling test.fail with the
original error message so framework outcomes are preserved; reference the
existing symbols env_process.preprocess_vm, vm.verify_alive, vm.wait_for_login,
and test.fail when making the change.

vm.verify_kernel_crash()

# Collect guest system details
guest_system_details(session)
except ValueError as e:
test.fail(f"{e}")
finally:
if session:
session.close()
if vm:
vm.destroy()
try:
if pci_device != "":
for i in range(len(pci_device.split(" "))):
if pci_device.split(" ")[i] not in pci.get_pci_addresses():
break
if driver_list[i] is None:
cur_driver = pci.get_driver(pci_device.split(" ")[i])
if cur_driver is not None:
pci.unbind(cur_driver, pci_device.split(" ")[i])
else:
pci.attach_driver(pci_device.split(" ")[i], driver_list[i])
except ValueError as e:
Comment on lines +165 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix cleanup: original driver not restored; loop breaks early; may leave devices bound to vfio.

  • Using break skips remaining devices.
  • Restoring logic runs only when driver_list[i] is None, which is inverted, and can call attach_driver(..., None).

Apply this diff to correctly unbind vfio and restore original drivers:

-            if pci_device != "":
-                for i in range(len(pci_device.split(" "))):
-                    if pci_device.split(" ")[i] not in pci.get_pci_addresses():
-                        break
-                    if driver_list[i] is None:
-                        cur_driver = pci.get_driver(pci_device.split(" ")[i])
-                        if cur_driver is not None:
-                            pci.unbind(cur_driver, pci_device.split(" ")[i])
-                        else:
-                            pci.attach_driver(pci_device.split(" ")[i], driver_list[i])
+            if pci_device:
+                devices = [d for d in pci_device.split() if d]
+                for idx, dev in enumerate(devices):
+                    if dev not in pci.get_pci_addresses():
+                        continue
+                    cur_driver = pci.get_driver(dev)
+                    if cur_driver == "vfio-pci":
+                        pci.unbind("vfio-pci", dev)
+                    orig_driver = driver_list[idx] if idx < len(driver_list) else None
+                    if orig_driver:
+                        pci.attach_driver(dev, orig_driver)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if pci_device != "":
for i in range(len(pci_device.split(" "))):
if pci_device.split(" ")[i] not in pci.get_pci_addresses():
break
if driver_list[i] is None:
cur_driver = pci.get_driver(pci_device.split(" ")[i])
if cur_driver is not None:
pci.unbind(cur_driver, pci_device.split(" ")[i])
else:
pci.attach_driver(pci_device.split(" ")[i], driver_list[i])
except ValueError as e:
if pci_device:
devices = [d for d in pci_device.split() if d]
for idx, dev in enumerate(devices):
if dev not in pci.get_pci_addresses():
continue
cur_driver = pci.get_driver(dev)
if cur_driver == "vfio-pci":
pci.unbind("vfio-pci", dev)
orig_driver = driver_list[idx] if idx < len(driver_list) else None
if orig_driver:
pci.attach_driver(dev, orig_driver)
except ValueError as e:
🤖 Prompt for AI Agents
In qemu/tests/qemu_pci_passthrough.py around lines 163-173, the rollback loop
breaks early, repeatedly calls split(" ") and inverts the restore logic (can
call attach_driver with None); fix by precomputing addresses =
pci_device.split(" "), iterate with for i, addr in enumerate(addresses) (no
break — use continue), skip addresses not present via if addr not in
pci.get_pci_addresses(): continue, and on cleanup for each present addr: if
driver_list[i] is not None call pci.attach_driver(addr, driver_list[i]) to
restore the original driver, else obtain cur_driver = pci.get_driver(addr) and
if cur_driver is not None call pci.unbind(cur_driver, addr) to unbind any
temporary driver (this avoids attaching None and ensures all devices are
processed).

test.fail(f"Failed to reset devices after test: Reason {e}")