summary refs log tree commit diff stats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitlab-ci.d/buildtest-template.yml16
-rw-r--r--.gitlab-ci.d/buildtest.yml4
-rw-r--r--.gitlab-ci.d/crossbuild-template.yml4
-rw-r--r--.gitlab-ci.d/custom-runners.yml2
-rw-r--r--.gitlab-ci.d/windows.yml4
-rw-r--r--.gitmodules4
-rw-r--r--MAINTAINERS3
-rw-r--r--docs/about/deprecated.rst25
-rw-r--r--docs/about/removed-features.rst25
-rw-r--r--docs/devel/kconfig.rst2
-rw-r--r--docs/devel/memory.rst7
-rw-r--r--docs/devel/testing/functional.rst3
-rw-r--r--docs/devel/testing/main.rst40
-rw-r--r--hw/arm/Kconfig3
-rw-r--r--hw/arm/virt.c5
-rw-r--r--hw/core/sysbus-fdt.c383
-rw-r--r--hw/display/bcm2835_fb.c1
-rw-r--r--hw/mips/malta.c2
-rw-r--r--hw/ppc/spapr_pci_vfio.c2
-rw-r--r--hw/s390x/s390-pci-vfio.c14
-rw-r--r--hw/vfio-user/container.c26
-rw-r--r--hw/vfio-user/container.h7
-rw-r--r--hw/vfio-user/pci.c18
-rw-r--r--hw/vfio/Kconfig16
-rw-r--r--hw/vfio/amd-xgbe.c61
-rw-r--r--hw/vfio/calxeda-xgmac.c61
-rw-r--r--hw/vfio/container.c35
-rw-r--r--hw/vfio/cpr-legacy.c14
-rw-r--r--hw/vfio/cpr.c12
-rw-r--r--hw/vfio/device.c2
-rw-r--r--hw/vfio/igd.c45
-rw-r--r--hw/vfio/iommufd.c4
-rw-r--r--hw/vfio/listener.c20
-rw-r--r--hw/vfio/meson.build3
-rw-r--r--hw/vfio/pci-quirks.c48
-rw-r--r--hw/vfio/pci.c217
-rw-r--r--hw/vfio/pci.h20
-rw-r--r--hw/vfio/platform.c716
-rw-r--r--hw/vfio/spapr.c16
-rw-r--r--hw/vfio/trace-events11
-rw-r--r--hw/vfio/vfio-region.h (renamed from include/hw/vfio/vfio-region.h)0
-rw-r--r--include/hw/display/bcm2835_fb.h1
-rw-r--r--include/hw/vfio/vfio-amd-xgbe.h46
-rw-r--r--include/hw/vfio/vfio-calxeda-xgmac.h43
-rw-r--r--include/hw/vfio/vfio-container-base.h13
-rw-r--r--include/hw/vfio/vfio-container.h7
-rw-r--r--include/hw/vfio/vfio-device.h2
-rw-r--r--include/hw/vfio/vfio-platform.h78
-rw-r--r--meson.build6
-rw-r--r--python/qemu/machine/qtest.py2
-rw-r--r--python/qemu/qmp/__init__.py3
-rw-r--r--python/qemu/qmp/error.py7
-rw-r--r--python/qemu/qmp/events.py50
-rw-r--r--python/qemu/qmp/legacy.py46
-rw-r--r--python/qemu/qmp/message.py22
-rw-r--r--python/qemu/qmp/models.py8
-rw-r--r--python/qemu/qmp/protocol.py194
-rw-r--r--python/qemu/qmp/qmp_client.py155
-rw-r--r--python/qemu/qmp/qmp_shell.py159
-rw-r--r--python/qemu/qmp/qmp_tui.py30
-rw-r--r--python/qemu/qmp/util.py143
-rw-r--r--python/tests/protocol.py10
-rwxr-xr-xscripts/checkpatch.pl3
-rw-r--r--scripts/tracetool/__init__.py9
-rw-r--r--scripts/tracetool/format/c.py3
-rw-r--r--scripts/tracetool/format/d.py3
-rw-r--r--scripts/tracetool/format/h.py17
-rw-r--r--scripts/tracetool/format/log_stap.py1
-rw-r--r--scripts/tracetool/format/simpletrace_stap.py1
-rw-r--r--scripts/tracetool/format/stap.py1
-rw-r--r--scripts/tracetool/format/ust_events_c.py1
-rw-r--r--scripts/tracetool/format/ust_events_h.py1
-rw-r--r--system/memory.c46
-rw-r--r--system/physmem.c8
-rw-r--r--tests/Makefile.include1
-rwxr-xr-xtests/functional/aarch64/test_aspeed_ast2700.py8
-rwxr-xr-xtests/functional/aarch64/test_hotplug_pci.py12
-rwxr-xr-xtests/functional/arm/test_aspeed_ast1030.py12
-rwxr-xr-xtests/functional/arm/test_aspeed_ast2500.py8
-rwxr-xr-xtests/functional/arm/test_aspeed_ast2600.py8
-rwxr-xr-xtests/functional/m68k/test_nextcube.py3
-rw-r--r--tests/functional/qemu_test/asset.py16
-rw-r--r--tests/functional/qemu_test/cmd.py67
-rw-r--r--tests/functional/qemu_test/testcase.py14
-rwxr-xr-xtests/functional/x86_64/test_memlock.py3
-rw-r--r--tests/meson.build1
-rwxr-xr-xtests/qapi-schema/test-qapi.py7
-rwxr-xr-xtests/qemu-iotests/1471
-rwxr-xr-xtests/qemu-iotests/1515
-rwxr-xr-xtests/qemu-iotests/check4
-rw-r--r--tests/qemu-iotests/testenv.py7
-rw-r--r--tests/qemu-iotests/testrunner.py9
-rw-r--r--tests/tracetool/dtrace.c32
-rw-r--r--tests/tracetool/dtrace.d10
-rw-r--r--tests/tracetool/dtrace.h45
-rw-r--r--tests/tracetool/dtrace.log-stap15
-rw-r--r--tests/tracetool/dtrace.simpletrace-stap16
-rw-r--r--tests/tracetool/dtrace.stap14
-rw-r--r--tests/tracetool/ftrace.c32
-rw-r--r--tests/tracetool/ftrace.h59
-rw-r--r--tests/tracetool/log.c32
-rw-r--r--tests/tracetool/log.h43
-rw-r--r--tests/tracetool/meson.build28
-rw-r--r--tests/tracetool/simple.c61
-rw-r--r--tests/tracetool/simple.h40
-rw-r--r--tests/tracetool/syslog.c32
-rw-r--r--tests/tracetool/syslog.h43
-rw-r--r--tests/tracetool/trace-events5
-rwxr-xr-xtests/tracetool/tracetool-test.py107
-rw-r--r--tests/tracetool/ust.c32
-rw-r--r--tests/tracetool/ust.h41
-rw-r--r--tests/tracetool/ust.ust-events-c14
-rw-r--r--tests/tracetool/ust.ust-events-h56
-rw-r--r--ui/vnc.c18
114 files changed, 1875 insertions, 2106 deletions
diff --git a/.gitlab-ci.d/buildtest-template.yml b/.gitlab-ci.d/buildtest-template.yml
index 038c3c9540..d866cb12bb 100644
--- a/.gitlab-ci.d/buildtest-template.yml
+++ b/.gitlab-ci.d/buildtest-template.yml
@@ -83,14 +83,18 @@
 
 .native_test_job_template:
   extends: .common_test_job_template
+  before_script:
+    # Prevent logs from the build job that run earlier
+    # from being duplicated in the test job artifacts
+    - rm -f build/meson-logs/*
   artifacts:
     name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
     when: always
     expire_in: 7 days
     paths:
-      - build/meson-logs/testlog.txt
+      - build/meson-logs
     reports:
-      junit: build/meson-logs/testlog.junit.xml
+      junit: build/meson-logs/*.junit.xml
 
 .functional_test_job_template:
   extends: .common_test_job_template
@@ -104,14 +108,16 @@
     when: always
     expire_in: 7 days
     paths:
-      - build/tests/results/latest/results.xml
-      - build/tests/results/latest/test-results
+      - build/meson-logs
       - build/tests/functional/*/*/*.log
     reports:
-      junit: build/tests/results/latest/results.xml
+      junit: build/meson-logs/*.junit.xml
   before_script:
     - export QEMU_TEST_ALLOW_UNTRUSTED_CODE=1
     - export QEMU_TEST_CACHE_DIR=${CI_PROJECT_DIR}/functional-cache
+    # Prevent logs from the build job that run earlier
+    # from being duplicated in the test job artifacts
+    - rm -f build/meson-logs/*
   after_script:
     - cd build
     - du -chs ${CI_PROJECT_DIR}/*-cache
diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml
index d888a60063..83c2867295 100644
--- a/.gitlab-ci.d/buildtest.yml
+++ b/.gitlab-ci.d/buildtest.yml
@@ -613,9 +613,9 @@ gcov:
     when: always
     expire_in: 2 days
     paths:
-      - build/meson-logs/testlog.txt
+      - build/meson-logs
     reports:
-      junit: build/meson-logs/testlog.junit.xml
+      junit: build/meson-logs/*.junit.xml
       coverage_report:
         coverage_format: cobertura
         path: build/coverage.xml
diff --git a/.gitlab-ci.d/crossbuild-template.yml b/.gitlab-ci.d/crossbuild-template.yml
index 303943f818..58136d06e4 100644
--- a/.gitlab-ci.d/crossbuild-template.yml
+++ b/.gitlab-ci.d/crossbuild-template.yml
@@ -128,6 +128,6 @@
     when: always
     expire_in: 7 days
     paths:
-      - build/meson-logs/testlog.txt
+      - build/meson-logs
     reports:
-      junit: build/meson-logs/testlog.junit.xml
+      junit: build/meson-logs/*.junit.xml
diff --git a/.gitlab-ci.d/custom-runners.yml b/.gitlab-ci.d/custom-runners.yml
index 1aa3c60efe..2d493f70f7 100644
--- a/.gitlab-ci.d/custom-runners.yml
+++ b/.gitlab-ci.d/custom-runners.yml
@@ -26,7 +26,7 @@
       - build/build.ninja
       - build/meson-logs
     reports:
-      junit: build/meson-logs/testlog.junit.xml
+      junit: build/meson-logs/*.junit.xml
 
 include:
   - local: '/.gitlab-ci.d/custom-runners/ubuntu-22.04-s390x.yml'
diff --git a/.gitlab-ci.d/windows.yml b/.gitlab-ci.d/windows.yml
index beac39e5bd..1e6a01bd9a 100644
--- a/.gitlab-ci.d/windows.yml
+++ b/.gitlab-ci.d/windows.yml
@@ -24,9 +24,9 @@ msys2-64bit:
     name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
     expire_in: 7 days
     paths:
-      - build/meson-logs/testlog.txt
+      - build/meson-logs
     reports:
-      junit: "build/meson-logs/testlog.junit.xml"
+      junit: build/meson-logs/*.junit.xml
   before_script:
   - Write-Output "Acquiring msys2.exe installer at $(Get-Date -Format u)"
   - If ( !(Test-Path -Path msys64\var\cache ) ) {
diff --git a/.gitmodules b/.gitmodules
index 73cae4cd4d..e27dfe8c2c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -15,7 +15,7 @@
 	url = https://gitlab.com/qemu-project/qemu-palcode.git
 [submodule "roms/u-boot"]
 	path = roms/u-boot
-	url = https://gitlab.com/qemu-project/u-boot.git
+	url = https://gitlab.com/qemu-project-mirrors/u-boot.git
 [submodule "roms/skiboot"]
 	path = roms/skiboot
 	url = https://gitlab.com/qemu-project/skiboot.git
@@ -27,7 +27,7 @@
 	url = https://gitlab.com/qemu-project/seabios-hppa.git
 [submodule "roms/u-boot-sam460ex"]
 	path = roms/u-boot-sam460ex
-	url = https://gitlab.com/qemu-project/u-boot-sam460ex.git
+	url = https://gitlab.com/qemu-project-mirrors/u-boot-sam460ex.git
 [submodule "roms/edk2"]
 	path = roms/edk2
 	url = https://gitlab.com/qemu-project/edk2.git
diff --git a/MAINTAINERS b/MAINTAINERS
index 1ae28e8804..f8cd513d8b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1575,6 +1575,7 @@ F: tests/functional/ppc64/test_tuxrun.py
 PowerNV (Non-Virtualized)
 M: Nicholas Piggin <npiggin@gmail.com>
 R: Aditya Gupta <adityag@linux.ibm.com>
+R: Glenn Miles <milesg@linux.ibm.com>
 L: qemu-ppc@nongnu.org
 S: Odd Fixes
 F: docs/system/ppc/powernv.rst
@@ -2781,6 +2782,7 @@ T: git https://github.com/philmd/qemu.git fw_cfg-next
 
 XIVE
 R: Gautam Menghani <gautam@linux.ibm.com>
+R: Glenn Miles <milesg@linux.ibm.com>
 L: qemu-ppc@nongnu.org
 S: Odd Fixes
 F: hw/*/*xive*
@@ -3560,6 +3562,7 @@ F: scripts/tracetool/
 F: scripts/qemu-trace-stap*
 F: docs/tools/qemu-trace-stap.rst
 F: docs/devel/tracing.rst
+F: tests/tracetool/
 T: git https://github.com/stefanha/qemu.git tracing
 
 Simpletrace
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index 03f7cabf73..aa300bbd50 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -454,31 +454,6 @@ Stream ``reconnect`` (since 9.2)
 The ``reconnect`` option only allows specifying second granularity timeouts,
 which is not enough for all types of use cases, use ``reconnect-ms`` instead.
 
-VFIO device options
-'''''''''''''''''''
-
-``-device vfio-calxeda-xgmac`` (since 10.0)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The vfio-calxeda-xgmac device allows to assign a host Calxeda Highbank
-10Gb XGMAC Ethernet controller device ("calxeda,hb-xgmac" compatibility
-string) to a guest. Calxeda HW has been ewasted now and there is no point
-keeping that device.
-
-``-device vfio-amd-xgbe`` (since 10.0)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The vfio-amd-xgbe device allows to assign a host AMD 10GbE controller
-to a guest ("amd,xgbe-seattle-v1a" compatibility string). AMD "Seattle"
-is not supported anymore and there is no point keeping that device.
-
-``-device vfio-platform`` (since 10.0)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The vfio-platform device allows to assign a host platform device
-to a guest in a generic manner. Integrating a new device into
-the vfio-platform infrastructure requires some adaptation at
-both kernel and qemu level. No such attempt has been done for years
-and the conclusion is that vfio-platform has not got any traction.
-PCIe passthrough shall be the mainline solution.
-
 CPU device properties
 '''''''''''''''''''''
 
diff --git a/docs/about/removed-features.rst b/docs/about/removed-features.rst
index 07ca4079d4..a5338e44c2 100644
--- a/docs/about/removed-features.rst
+++ b/docs/about/removed-features.rst
@@ -1306,6 +1306,31 @@ The corresponding upstream server project is no longer maintained.
 Users are recommended to switch to an alternative distributed block
 device driver such as RBD.
 
+VFIO devices
+------------
+
+``-device vfio-calxeda-xgmac`` (since 10.2)
+'''''''''''''''''''''''''''''''''''''''''''
+The vfio-calxeda-xgmac device allows to assign a host Calxeda Highbank
+10Gb XGMAC Ethernet controller device ("calxeda,hb-xgmac" compatibility
+string) to a guest. Calxeda HW has been ewasted now and there is no point
+keeping that device.
+
+``-device vfio-amd-xgbe`` (since 10.2)
+''''''''''''''''''''''''''''''''''''''
+The vfio-amd-xgbe device allows to assign a host AMD 10GbE controller
+to a guest ("amd,xgbe-seattle-v1a" compatibility string). AMD "Seattle"
+is not supported anymore and there is no point keeping that device.
+
+``-device vfio-platform`` (since 10.2)
+''''''''''''''''''''''''''''''''''''''
+The vfio-platform device allows to assign a host platform device
+to a guest in a generic manner. Integrating a new device into
+the vfio-platform infrastructure requires some adaptation at
+both kernel and qemu level. No such attempt has been done for years
+and the conclusion is that vfio-platform has not got any traction.
+PCIe passthrough shall be the mainline solution.
+
 Tools
 -----
 
diff --git a/docs/devel/kconfig.rst b/docs/devel/kconfig.rst
index 493b76c4fb..1d4a114a02 100644
--- a/docs/devel/kconfig.rst
+++ b/docs/devel/kconfig.rst
@@ -59,8 +59,6 @@ stanza like the following::
       config ARM_VIRT
          bool
          imply PCI_DEVICES
-         imply VFIO_AMD_XGBE
-         imply VFIO_XGMAC
          select A15MPCORE
          select ACPI
          select ARM_SMMUV3
diff --git a/docs/devel/memory.rst b/docs/devel/memory.rst
index 57fb2aec76..42d3ca29c4 100644
--- a/docs/devel/memory.rst
+++ b/docs/devel/memory.rst
@@ -158,8 +158,11 @@ ioeventfd) can be changed during the region lifecycle.  They take effect
 as soon as the region is made visible.  This can be immediately, later,
 or never.
 
-Destruction of a memory region happens automatically when the owner
-object dies.
+Destruction of a memory region happens automatically when the owner object
+dies.  When there are multiple memory regions under the same owner object,
+the memory API will guarantee all memory regions will be properly detached
+and finalized one by one.  The order in which memory regions will be
+finalized is not guaranteed.
 
 If however the memory region is part of a dynamically allocated data
 structure, you should call object_unparent() to destroy the memory region
diff --git a/docs/devel/testing/functional.rst b/docs/devel/testing/functional.rst
index 3728bab6c0..fdeaebaadc 100644
--- a/docs/devel/testing/functional.rst
+++ b/docs/devel/testing/functional.rst
@@ -312,6 +312,9 @@ The cache is populated in the ``~/.cache/qemu/download`` directory by
 default, but the location can be changed by setting the
 ``QEMU_TEST_CACHE_DIR`` environment variable.
 
+To force the test suite to re-download the cache, even if still valid,
+set the ``QEMU_TEST_REFRESH_CACHE`` environment variable.
+
 Skipping tests
 --------------
 
diff --git a/docs/devel/testing/main.rst b/docs/devel/testing/main.rst
index 2b5cb0c148..0662766b5c 100644
--- a/docs/devel/testing/main.rst
+++ b/docs/devel/testing/main.rst
@@ -178,6 +178,46 @@ parser (either fixing a bug or extending/modifying the syntax). To do this:
 
   ``qapi-schema += foo.json``
 
+The reference output can be automatically updated to match the latest QAPI
+code generator by running the tests with the QEMU_TEST_REGENERATE environment
+variable set.
+
+.. code::
+
+   QEMU_TEST_REGENERATE=1 make check-qapi-schema
+
+The resulting changes must be reviewed by the author to ensure they match
+the intended results before adding the updated reference output to the
+same commit that alters the generator code.
+
+.. _tracetool-tests:
+
+Tracetool tests
+~~~~~~~~~~~~~~~
+
+The tracetool tests validate the generated source files used for defining
+probes for various tracing backends and source formats. The test operates
+by running the tracetool program against a sample trace-events file, and
+comparing the generated output against known good reference output. The
+tests can be run with:
+
+.. code::
+
+  make check-tracetool
+
+The reference output is stored in files under tests/tracetool, and when
+the tracetool backend/format output is intentionally changed, the reference
+files need to be updated. This can be automated by setting the
+QEMU_TEST_REGENERATE=1 environment variable:
+
+.. code::
+
+   QEMU_TEST_REGENERATE=1 make check-tracetool
+
+The resulting changes must be reviewed by the author to ensure they match
+the intended results, before adding the updated reference output to the
+same commit that alters the generator code.
+
 check-block
 ~~~~~~~~~~~
 
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 2aa4b5d778..3baa6c6c74 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -5,9 +5,6 @@ config ARM_VIRT
     depends on TCG || KVM || HVF
     imply PCI_DEVICES
     imply TEST_DEVICES
-    imply VFIO_AMD_XGBE
-    imply VFIO_PLATFORM
-    imply VFIO_XGMAC
     imply TPM_TIS_SYSBUS
     imply TPM_TIS_I2C
     imply NVDIMM
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 7b3f9b1cdf..02209fadcf 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -38,8 +38,6 @@
 #include "hw/arm/primecell.h"
 #include "hw/arm/virt.h"
 #include "hw/block/flash.h"
-#include "hw/vfio/vfio-calxeda-xgmac.h"
-#include "hw/vfio/vfio-amd-xgbe.h"
 #include "hw/display/ramfb.h"
 #include "net/net.h"
 #include "system/device_tree.h"
@@ -3276,10 +3274,7 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
      * configuration of the particular instance.
      */
     mc->max_cpus = 512;
-    machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_CALXEDA_XGMAC);
-    machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_AMD_XGBE);
     machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE);
-    machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_PLATFORM);
     machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS);
     machine_class_allow_dynamic_sysbus_dev(mc, TYPE_ARM_SMMUV3);
 #ifdef CONFIG_TPM
diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c
index e80776080b..59f1d17de1 100644
--- a/hw/core/sysbus-fdt.c
+++ b/hw/core/sysbus-fdt.c
@@ -33,10 +33,6 @@
 #include "system/tpm.h"
 #include "hw/arm/smmuv3.h"
 #include "hw/platform-bus.h"
-#include "hw/vfio/vfio-platform.h"
-#include "hw/vfio/vfio-calxeda-xgmac.h"
-#include "hw/vfio/vfio-amd-xgbe.h"
-#include "hw/vfio/vfio-region.h"
 #include "hw/display/ramfb.h"
 #include "hw/uefi/var-service-api.h"
 #include "hw/arm/fdt.h"
@@ -67,380 +63,6 @@ typedef struct HostProperty {
     bool optional;
 } HostProperty;
 
-#ifdef CONFIG_LINUX
-
-/**
- * copy_properties_from_host
- *
- * copies properties listed in an array from host device tree to
- * guest device tree. If a non optional property is not found, the
- * function asserts. An optional property is ignored if not found
- * in the host device tree.
- * @props: array of HostProperty to copy
- * @nb_props: number of properties in the array
- * @host_dt: host device tree blob
- * @guest_dt: guest device tree blob
- * @node_path: host dt node path where the property is supposed to be
-              found
- * @nodename: guest node name the properties should be added to
- */
-static void copy_properties_from_host(HostProperty *props, int nb_props,
-                                      void *host_fdt, void *guest_fdt,
-                                      char *node_path, char *nodename)
-{
-    int i, prop_len;
-    const void *r;
-    Error *err = NULL;
-
-    for (i = 0; i < nb_props; i++) {
-        r = qemu_fdt_getprop(host_fdt, node_path,
-                             props[i].name,
-                             &prop_len,
-                             &err);
-        if (r) {
-            qemu_fdt_setprop(guest_fdt, nodename,
-                             props[i].name, r, prop_len);
-        } else {
-            if (props[i].optional && prop_len == -FDT_ERR_NOTFOUND) {
-                /* optional property does not exist */
-                error_free(err);
-            } else {
-                error_report_err(err);
-            }
-            if (!props[i].optional) {
-                /* mandatory property not found: bail out */
-                exit(1);
-            }
-            err = NULL;
-        }
-    }
-}
-
-/* clock properties whose values are copied/pasted from host */
-static HostProperty clock_copied_properties[] = {
-    {"compatible", false},
-    {"#clock-cells", false},
-    {"clock-frequency", true},
-    {"clock-output-names", true},
-};
-
-/**
- * fdt_build_clock_node
- *
- * Build a guest clock node, used as a dependency from a passthrough'ed
- * device. Most information are retrieved from the host clock node.
- * Also check the host clock is a fixed one.
- *
- * @host_fdt: host device tree blob from which info are retrieved
- * @guest_fdt: guest device tree blob where the clock node is added
- * @host_phandle: phandle of the clock in host device tree
- * @guest_phandle: phandle to assign to the guest node
- */
-static void fdt_build_clock_node(void *host_fdt, void *guest_fdt,
-                                uint32_t host_phandle,
-                                uint32_t guest_phandle)
-{
-    char *node_path = NULL;
-    char *nodename;
-    const void *r;
-    int ret, node_offset, prop_len, path_len = 16;
-
-    node_offset = fdt_node_offset_by_phandle(host_fdt, host_phandle);
-    if (node_offset <= 0) {
-        error_report("not able to locate clock handle %d in host device tree",
-                     host_phandle);
-        exit(1);
-    }
-    node_path = g_malloc(path_len);
-    while ((ret = fdt_get_path(host_fdt, node_offset, node_path, path_len))
-            == -FDT_ERR_NOSPACE) {
-        path_len += 16;
-        node_path = g_realloc(node_path, path_len);
-    }
-    if (ret < 0) {
-        error_report("not able to retrieve node path for clock handle %d",
-                     host_phandle);
-        exit(1);
-    }
-
-    r = qemu_fdt_getprop(host_fdt, node_path, "compatible", &prop_len,
-                         &error_fatal);
-    if (strcmp(r, "fixed-clock")) {
-        error_report("clock handle %d is not a fixed clock", host_phandle);
-        exit(1);
-    }
-
-    nodename = strrchr(node_path, '/');
-    qemu_fdt_add_subnode(guest_fdt, nodename);
-
-    copy_properties_from_host(clock_copied_properties,
-                              ARRAY_SIZE(clock_copied_properties),
-                              host_fdt, guest_fdt,
-                              node_path, nodename);
-
-    qemu_fdt_setprop_cell(guest_fdt, nodename, "phandle", guest_phandle);
-
-    g_free(node_path);
-}
-
-/**
- * sysfs_to_dt_name: convert the name found in sysfs into the node name
- * for instance e0900000.xgmac is converted into xgmac@e0900000
- * @sysfs_name: directory name in sysfs
- *
- * returns the device tree name upon success or NULL in case the sysfs name
- * does not match the expected format
- */
-static char *sysfs_to_dt_name(const char *sysfs_name)
-{
-    gchar **substrings =  g_strsplit(sysfs_name, ".", 2);
-    char *dt_name = NULL;
-
-    if (!substrings || !substrings[0] || !substrings[1]) {
-        goto out;
-    }
-    dt_name = g_strdup_printf("%s@%s", substrings[1], substrings[0]);
-out:
-    g_strfreev(substrings);
-    return dt_name;
-}
-
-/* Device Specific Code */
-
-/**
- * add_calxeda_midway_xgmac_fdt_node
- *
- * Generates a simple node with following properties:
- * compatible string, regs, interrupts, dma-coherent
- */
-static int add_calxeda_midway_xgmac_fdt_node(SysBusDevice *sbdev, void *opaque)
-{
-    PlatformBusFDTData *data = opaque;
-    PlatformBusDevice *pbus = data->pbus;
-    void *fdt = data->fdt;
-    const char *parent_node = data->pbus_node_name;
-    int compat_str_len, i;
-    char *nodename;
-    uint32_t *irq_attr, *reg_attr;
-    uint64_t mmio_base, irq_number;
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
-    VFIODevice *vbasedev = &vdev->vbasedev;
-
-    mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
-    nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node,
-                               vbasedev->name, mmio_base);
-    qemu_fdt_add_subnode(fdt, nodename);
-
-    compat_str_len = strlen(vdev->compat) + 1;
-    qemu_fdt_setprop(fdt, nodename, "compatible",
-                          vdev->compat, compat_str_len);
-
-    qemu_fdt_setprop(fdt, nodename, "dma-coherent", "", 0);
-
-    reg_attr = g_new(uint32_t, vbasedev->num_regions * 2);
-    for (i = 0; i < vbasedev->num_regions; i++) {
-        mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i);
-        reg_attr[2 * i] = cpu_to_be32(mmio_base);
-        reg_attr[2 * i + 1] = cpu_to_be32(
-                                memory_region_size(vdev->regions[i]->mem));
-    }
-    qemu_fdt_setprop(fdt, nodename, "reg", reg_attr,
-                     vbasedev->num_regions * 2 * sizeof(uint32_t));
-
-    irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3);
-    for (i = 0; i < vbasedev->num_irqs; i++) {
-        irq_number = platform_bus_get_irqn(pbus, sbdev , i)
-                         + data->irq_start;
-        irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI);
-        irq_attr[3 * i + 1] = cpu_to_be32(irq_number);
-        irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI);
-    }
-    qemu_fdt_setprop(fdt, nodename, "interrupts",
-                     irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t));
-    g_free(irq_attr);
-    g_free(reg_attr);
-    g_free(nodename);
-    return 0;
-}
-
-/* AMD xgbe properties whose values are copied/pasted from host */
-static HostProperty amd_xgbe_copied_properties[] = {
-    {"compatible", false},
-    {"dma-coherent", true},
-    {"amd,per-channel-interrupt", true},
-    {"phy-mode", false},
-    {"mac-address", true},
-    {"amd,speed-set", false},
-    {"amd,serdes-blwc", true},
-    {"amd,serdes-cdr-rate", true},
-    {"amd,serdes-pq-skew", true},
-    {"amd,serdes-tx-amp", true},
-    {"amd,serdes-dfe-tap-config", true},
-    {"amd,serdes-dfe-tap-enable", true},
-    {"clock-names", false},
-};
-
-/**
- * add_amd_xgbe_fdt_node
- *
- * Generates the combined xgbe/phy node following kernel >=4.2
- * binding documentation:
- * Documentation/devicetree/bindings/net/amd-xgbe.txt:
- * Also 2 clock nodes are created (dma and ptp)
- *
- * Asserts in case of error
- */
-static int add_amd_xgbe_fdt_node(SysBusDevice *sbdev, void *opaque)
-{
-    PlatformBusFDTData *data = opaque;
-    PlatformBusDevice *pbus = data->pbus;
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
-    VFIODevice *vbasedev = &vdev->vbasedev;
-    VFIOINTp *intp;
-    const char *parent_node = data->pbus_node_name;
-    char **node_path, *nodename, *dt_name;
-    void *guest_fdt = data->fdt, *host_fdt;
-    const void *r;
-    int i, prop_len;
-    uint32_t *irq_attr, *reg_attr;
-    const uint32_t *host_clock_phandles;
-    uint64_t mmio_base, irq_number;
-    uint32_t guest_clock_phandles[2];
-
-    host_fdt = load_device_tree_from_sysfs();
-
-    dt_name = sysfs_to_dt_name(vbasedev->name);
-    if (!dt_name) {
-        error_report("%s incorrect sysfs device name %s",
-                     __func__, vbasedev->name);
-        exit(1);
-    }
-    node_path = qemu_fdt_node_path(host_fdt, dt_name, vdev->compat,
-                                   &error_fatal);
-    if (!node_path || !node_path[0]) {
-        error_report("%s unable to retrieve node path for %s/%s",
-                     __func__, dt_name, vdev->compat);
-        exit(1);
-    }
-
-    if (node_path[1]) {
-        error_report("%s more than one node matching %s/%s!",
-                     __func__, dt_name, vdev->compat);
-        exit(1);
-    }
-
-    g_free(dt_name);
-
-    if (vbasedev->num_regions != 5) {
-        error_report("%s Does the host dt node combine XGBE/PHY?", __func__);
-        exit(1);
-    }
-
-    /* generate nodes for DMA_CLK and PTP_CLK */
-    r = qemu_fdt_getprop(host_fdt, node_path[0], "clocks",
-                         &prop_len, &error_fatal);
-    if (prop_len != 8) {
-        error_report("%s clocks property should contain 2 handles", __func__);
-        exit(1);
-    }
-    host_clock_phandles = r;
-    guest_clock_phandles[0] = qemu_fdt_alloc_phandle(guest_fdt);
-    guest_clock_phandles[1] = qemu_fdt_alloc_phandle(guest_fdt);
-
-    /**
-     * clock handles fetched from host dt are in be32 layout whereas
-     * rest of the code uses cpu layout. Also guest clock handles are
-     * in cpu layout.
-     */
-    fdt_build_clock_node(host_fdt, guest_fdt,
-                         be32_to_cpu(host_clock_phandles[0]),
-                         guest_clock_phandles[0]);
-
-    fdt_build_clock_node(host_fdt, guest_fdt,
-                         be32_to_cpu(host_clock_phandles[1]),
-                         guest_clock_phandles[1]);
-
-    /* combined XGBE/PHY node */
-    mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
-    nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node,
-                               vbasedev->name, mmio_base);
-    qemu_fdt_add_subnode(guest_fdt, nodename);
-
-    copy_properties_from_host(amd_xgbe_copied_properties,
-                       ARRAY_SIZE(amd_xgbe_copied_properties),
-                       host_fdt, guest_fdt,
-                       node_path[0], nodename);
-
-    qemu_fdt_setprop_cells(guest_fdt, nodename, "clocks",
-                           guest_clock_phandles[0],
-                           guest_clock_phandles[1]);
-
-    reg_attr = g_new(uint32_t, vbasedev->num_regions * 2);
-    for (i = 0; i < vbasedev->num_regions; i++) {
-        mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i);
-        reg_attr[2 * i] = cpu_to_be32(mmio_base);
-        reg_attr[2 * i + 1] = cpu_to_be32(
-                                memory_region_size(vdev->regions[i]->mem));
-    }
-    qemu_fdt_setprop(guest_fdt, nodename, "reg", reg_attr,
-                     vbasedev->num_regions * 2 * sizeof(uint32_t));
-
-    irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3);
-    for (i = 0; i < vbasedev->num_irqs; i++) {
-        irq_number = platform_bus_get_irqn(pbus, sbdev , i)
-                         + data->irq_start;
-        irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI);
-        irq_attr[3 * i + 1] = cpu_to_be32(irq_number);
-        /*
-          * General device interrupt and PCS auto-negotiation interrupts are
-          * level-sensitive while the 4 per-channel interrupts are edge
-          * sensitive
-          */
-        QLIST_FOREACH(intp, &vdev->intp_list, next) {
-            if (intp->pin == i) {
-                break;
-            }
-        }
-        if (intp->flags & VFIO_IRQ_INFO_AUTOMASKED) {
-            irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI);
-        } else {
-            irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_EDGE_LO_HI);
-        }
-    }
-    qemu_fdt_setprop(guest_fdt, nodename, "interrupts",
-                     irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t));
-
-    g_free(host_fdt);
-    g_strfreev(node_path);
-    g_free(irq_attr);
-    g_free(reg_attr);
-    g_free(nodename);
-    return 0;
-}
-
-/* DT compatible matching */
-static bool vfio_platform_match(SysBusDevice *sbdev,
-                                const BindingEntry *entry)
-{
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
-    const char *compat;
-    unsigned int n;
-
-    for (n = vdev->num_compat, compat = vdev->compat; n > 0;
-         n--, compat += strlen(compat) + 1) {
-        if (!strcmp(entry->compat, compat)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-#define VFIO_PLATFORM_BINDING(compat, add_fn) \
-    {TYPE_VFIO_PLATFORM, (compat), (add_fn), vfio_platform_match}
-
-#endif /* CONFIG_LINUX */
-
 #ifdef CONFIG_TPM
 /*
  * add_tpm_tis_fdt_node: Create a DT node for TPM TIS
@@ -511,11 +133,6 @@ static bool type_match(SysBusDevice *sbdev, const BindingEntry *entry)
 
 /* list of supported dynamic sysbus bindings */
 static const BindingEntry bindings[] = {
-#ifdef CONFIG_LINUX
-    TYPE_BINDING(TYPE_VFIO_CALXEDA_XGMAC, add_calxeda_midway_xgmac_fdt_node),
-    TYPE_BINDING(TYPE_VFIO_AMD_XGBE, add_amd_xgbe_fdt_node),
-    VFIO_PLATFORM_BINDING("amd,xgbe-seattle-v1a", add_amd_xgbe_fdt_node),
-#endif
 #ifdef CONFIG_TPM
     TYPE_BINDING(TYPE_TPM_TIS_SYSBUS, add_tpm_tis_fdt_node),
 #endif
diff --git a/hw/display/bcm2835_fb.c b/hw/display/bcm2835_fb.c
index 820e67ac8b..1bb2ee45a0 100644
--- a/hw/display/bcm2835_fb.c
+++ b/hw/display/bcm2835_fb.c
@@ -27,6 +27,7 @@
 #include "hw/display/bcm2835_fb.h"
 #include "hw/hw.h"
 #include "hw/irq.h"
+#include "ui/console.h"
 #include "framebuffer.h"
 #include "ui/pixel_ops.h"
 #include "hw/misc/bcm2835_mbox_defs.h"
diff --git a/hw/mips/malta.c b/hw/mips/malta.c
index 344dc8ca76..02da629b5a 100644
--- a/hw/mips/malta.c
+++ b/hw/mips/malta.c
@@ -1191,7 +1191,7 @@ void mips_malta_init(MachineState *machine)
          * In little endian mode the 32bit words in the bios are swapped,
          * a neat trick which allows bi-endian firmware.
          */
-        if (!TARGET_BIG_ENDIAN) {
+        if (!TARGET_BIG_ENDIAN && bios_size > 0) {
             uint32_t *end, *addr;
             const size_t swapsize = MIN(bios_size, 0x3e0000);
             addr = rom_ptr(FLASH_ADDRESS, swapsize);
diff --git a/hw/ppc/spapr_pci_vfio.c b/hw/ppc/spapr_pci_vfio.c
index e318d0d912..7e1c71ef59 100644
--- a/hw/ppc/spapr_pci_vfio.c
+++ b/hw/ppc/spapr_pci_vfio.c
@@ -106,7 +106,7 @@ static VFIOContainer *vfio_eeh_as_container(AddressSpace *as)
 
 out:
     vfio_address_space_put(space);
-    return container_of(bcontainer, VFIOContainer, bcontainer);
+    return VFIO_IOMMU_LEGACY(bcontainer);
 }
 
 static bool vfio_eeh_as_ok(AddressSpace *as)
diff --git a/hw/s390x/s390-pci-vfio.c b/hw/s390x/s390-pci-vfio.c
index aaf91319b4..938a551171 100644
--- a/hw/s390x/s390-pci-vfio.c
+++ b/hw/s390x/s390-pci-vfio.c
@@ -62,7 +62,7 @@ S390PCIDMACount *s390_pci_start_dma_count(S390pciState *s,
 {
     S390PCIDMACount *cnt;
     uint32_t avail;
-    VFIOPCIDevice *vpdev = container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vpdev = VFIO_PCI_BASE(pbdev->pdev);
     int id;
 
     assert(vpdev);
@@ -108,7 +108,7 @@ static void s390_pci_read_base(S390PCIBusDevice *pbdev,
 {
     struct vfio_info_cap_header *hdr;
     struct vfio_device_info_cap_zpci_base *cap;
-    VFIOPCIDevice *vpci =  container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vpci = VFIO_PCI_BASE(pbdev->pdev);
     uint64_t vfio_size;
 
     hdr = vfio_get_device_info_cap(info, VFIO_DEVICE_INFO_CAP_ZPCI_BASE);
@@ -162,7 +162,7 @@ static bool get_host_fh(S390PCIBusDevice *pbdev, struct vfio_device_info *info,
 {
     struct vfio_info_cap_header *hdr;
     struct vfio_device_info_cap_zpci_base *cap;
-    VFIOPCIDevice *vpci = container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vpci = VFIO_PCI_BASE(pbdev->pdev);
 
     hdr = vfio_get_device_info_cap(info, VFIO_DEVICE_INFO_CAP_ZPCI_BASE);
 
@@ -185,7 +185,7 @@ static void s390_pci_read_group(S390PCIBusDevice *pbdev,
     struct vfio_device_info_cap_zpci_group *cap;
     S390pciState *s = s390_get_phb();
     ClpRspQueryPciGrp *resgrp;
-    VFIOPCIDevice *vpci =  container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vpci = VFIO_PCI_BASE(pbdev->pdev);
     uint8_t start_gid = pbdev->zpci_fn.pfgid;
 
     hdr = vfio_get_device_info_cap(info, VFIO_DEVICE_INFO_CAP_ZPCI_GROUP);
@@ -264,7 +264,7 @@ static void s390_pci_read_util(S390PCIBusDevice *pbdev,
 {
     struct vfio_info_cap_header *hdr;
     struct vfio_device_info_cap_zpci_util *cap;
-    VFIOPCIDevice *vpci =  container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vpci = VFIO_PCI_BASE(pbdev->pdev);
 
     hdr = vfio_get_device_info_cap(info, VFIO_DEVICE_INFO_CAP_ZPCI_UTIL);
 
@@ -291,7 +291,7 @@ static void s390_pci_read_pfip(S390PCIBusDevice *pbdev,
 {
     struct vfio_info_cap_header *hdr;
     struct vfio_device_info_cap_zpci_pfip *cap;
-    VFIOPCIDevice *vpci =  container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vpci = VFIO_PCI_BASE(pbdev->pdev);
 
     hdr = vfio_get_device_info_cap(info, VFIO_DEVICE_INFO_CAP_ZPCI_PFIP);
 
@@ -314,7 +314,7 @@ static void s390_pci_read_pfip(S390PCIBusDevice *pbdev,
 
 static struct vfio_device_info *get_device_info(S390PCIBusDevice *pbdev)
 {
-    VFIOPCIDevice *vfio_pci = container_of(pbdev->pdev, VFIOPCIDevice, pdev);
+    VFIOPCIDevice *vfio_pci = VFIO_PCI_BASE(pbdev->pdev);
 
     return vfio_get_device_info(vfio_pci->vbasedev.fd);
 }
diff --git a/hw/vfio-user/container.c b/hw/vfio-user/container.c
index d589dd90f5..3cdbd44c1a 100644
--- a/hw/vfio-user/container.c
+++ b/hw/vfio-user/container.c
@@ -24,16 +24,14 @@
  */
 static void vfio_user_listener_begin(VFIOContainerBase *bcontainer)
 {
-    VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
-                                                 bcontainer);
+    VFIOUserContainer *container = VFIO_IOMMU_USER(bcontainer);
 
     container->proxy->async_ops = true;
 }
 
 static void vfio_user_listener_commit(VFIOContainerBase *bcontainer)
 {
-    VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
-                                            bcontainer);
+    VFIOUserContainer *container = VFIO_IOMMU_USER(bcontainer);
 
     /* wait here for any async requests sent during the transaction */
     container->proxy->async_ops = false;
@@ -44,8 +42,8 @@ static int vfio_user_dma_unmap(const VFIOContainerBase *bcontainer,
                                hwaddr iova, ram_addr_t size,
                                IOMMUTLBEntry *iotlb, bool unmap_all)
 {
-    VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
-                                            bcontainer);
+    VFIOUserContainer *container = VFIO_IOMMU_USER(bcontainer);
+
     Error *local_err = NULL;
     int ret = 0;
 
@@ -86,8 +84,8 @@ static int vfio_user_dma_map(const VFIOContainerBase *bcontainer, hwaddr iova,
                              ram_addr_t size, void *vaddr, bool readonly,
                              MemoryRegion *mrp)
 {
-    VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
-                                                bcontainer);
+    VFIOUserContainer *container = VFIO_IOMMU_USER(bcontainer);
+
     int fd = memory_region_get_fd(mrp);
     Error *local_err = NULL;
     int ret = 0;
@@ -173,8 +171,7 @@ static int vfio_user_query_dirty_bitmap(const VFIOContainerBase *bcontainer,
 
 static bool vfio_user_setup(VFIOContainerBase *bcontainer, Error **errp)
 {
-    VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
-                                                bcontainer);
+    VFIOUserContainer *container = VFIO_IOMMU_USER(bcontainer);
 
     assert(container->proxy->dma_pgsizes != 0);
     bcontainer->pgsizes = container->proxy->dma_pgsizes;
@@ -218,7 +215,7 @@ vfio_user_container_connect(AddressSpace *as, VFIODevice *vbasedev,
         goto put_space_exit;
     }
 
-    bcontainer = &container->bcontainer;
+    bcontainer = VFIO_IOMMU(container);
 
     ret = ram_block_uncoordinated_discard_disable(true);
     if (ret) {
@@ -263,7 +260,7 @@ put_space_exit:
 
 static void vfio_user_container_disconnect(VFIOUserContainer *container)
 {
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
     VFIOAddressSpace *space = bcontainer->space;
 
@@ -291,7 +288,7 @@ static bool vfio_user_device_get(VFIOUserContainer *container,
 
     vbasedev->fd = -1;
 
-    vfio_device_prepare(vbasedev, &container->bcontainer, &info);
+    vfio_device_prepare(vbasedev, VFIO_IOMMU(container), &info);
 
     return true;
 }
@@ -315,8 +312,7 @@ static bool vfio_user_device_attach(const char *name, VFIODevice *vbasedev,
 
 static void vfio_user_device_detach(VFIODevice *vbasedev)
 {
-    VFIOUserContainer *container = container_of(vbasedev->bcontainer,
-                                                VFIOUserContainer, bcontainer);
+    VFIOUserContainer *container = VFIO_IOMMU_USER(vbasedev->bcontainer);
 
     vfio_device_unprepare(vbasedev);
 
diff --git a/hw/vfio-user/container.h b/hw/vfio-user/container.h
index 2bb1fa1343..96aa6785d9 100644
--- a/hw/vfio-user/container.h
+++ b/hw/vfio-user/container.h
@@ -13,10 +13,11 @@
 #include "hw/vfio-user/proxy.h"
 
 /* MMU container sub-class for vfio-user. */
-typedef struct VFIOUserContainer {
-    VFIOContainerBase bcontainer;
+struct VFIOUserContainer {
+    VFIOContainerBase parent_obj;
+
     VFIOUserProxy *proxy;
-} VFIOUserContainer;
+};
 
 OBJECT_DECLARE_SIMPLE_TYPE(VFIOUserContainer, VFIO_IOMMU_USER);
 
diff --git a/hw/vfio-user/pci.c b/hw/vfio-user/pci.c
index be71c77729..e2c309784f 100644
--- a/hw/vfio-user/pci.c
+++ b/hw/vfio-user/pci.c
@@ -20,7 +20,8 @@
 OBJECT_DECLARE_SIMPLE_TYPE(VFIOUserPCIDevice, VFIO_USER_PCI)
 
 struct VFIOUserPCIDevice {
-    VFIOPCIDevice device;
+    VFIOPCIDevice parent_obj;
+
     SocketAddress *socket;
     bool send_queued;   /* all sends are queued */
     uint32_t wait_time; /* timeout for message replies */
@@ -64,7 +65,7 @@ static void vfio_user_msix_setup(VFIOPCIDevice *vdev)
     vdev->msix->pba_region = pba_reg;
 
     vfio_reg = vdev->bars[vdev->msix->pba_bar].mr;
-    msix_reg = &vdev->pdev.msix_pba_mmio;
+    msix_reg = &PCI_DEVICE(vdev)->msix_pba_mmio;
     memory_region_init_io(pba_reg, OBJECT(vdev), &vfio_user_pba_ops, vdev,
                           "VFIO MSIX PBA", int128_get64(msix_reg->size));
     memory_region_add_subregion_overlap(vfio_reg, vdev->msix->pba_offset,
@@ -85,7 +86,7 @@ static void vfio_user_msix_teardown(VFIOPCIDevice *vdev)
 
 static void vfio_user_dma_read(VFIOPCIDevice *vdev, VFIOUserDMARW *msg)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIOUserProxy *proxy = vdev->vbasedev.proxy;
     VFIOUserDMARW *res;
     MemTxResult r;
@@ -133,7 +134,7 @@ static void vfio_user_dma_read(VFIOPCIDevice *vdev, VFIOUserDMARW *msg)
 
 static void vfio_user_dma_write(VFIOPCIDevice *vdev, VFIOUserDMARW *msg)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIOUserProxy *proxy = vdev->vbasedev.proxy;
     MemTxResult r;
 
@@ -213,8 +214,9 @@ static void vfio_user_compute_needs_reset(VFIODevice *vbasedev)
 
 static Object *vfio_user_pci_get_object(VFIODevice *vbasedev)
 {
-    VFIOUserPCIDevice *vdev = container_of(vbasedev, VFIOUserPCIDevice,
-                                           device.vbasedev);
+    VFIOUserPCIDevice *vdev = VFIO_USER_PCI(container_of(vbasedev,
+                                                         VFIOPCIDevice,
+                                                         vbasedev));
 
     return OBJECT(vdev);
 }
@@ -406,6 +408,8 @@ static const Property vfio_user_pci_dev_properties[] = {
                        sub_vendor_id, PCI_ANY_ID),
     DEFINE_PROP_UINT32("x-pci-sub-device-id", VFIOPCIDevice,
                        sub_device_id, PCI_ANY_ID),
+    DEFINE_PROP_UINT32("x-pci-class-code", VFIOPCIDevice,
+                       class_code, PCI_ANY_ID),
     DEFINE_PROP_BOOL("x-send-queued", VFIOUserPCIDevice, send_queued, false),
     DEFINE_PROP_UINT32("x-msg-timeout", VFIOUserPCIDevice, wait_time, 5000),
     DEFINE_PROP_BOOL("x-no-posted-writes", VFIOUserPCIDevice, no_post, false),
@@ -417,7 +421,7 @@ static void vfio_user_pci_set_socket(Object *obj, Visitor *v, const char *name,
     VFIOUserPCIDevice *udev = VFIO_USER_PCI(obj);
     bool success;
 
-    if (udev->device.vbasedev.proxy) {
+    if (VFIO_PCI_BASE(udev)->vbasedev.proxy) {
         error_setg(errp, "Proxy is connected");
         return;
     }
diff --git a/hw/vfio/Kconfig b/hw/vfio/Kconfig
index 91d9023b79..27de24e4db 100644
--- a/hw/vfio/Kconfig
+++ b/hw/vfio/Kconfig
@@ -17,22 +17,6 @@ config VFIO_CCW
     select VFIO
     depends on LINUX && S390_CCW_VIRTIO
 
-config VFIO_PLATFORM
-    bool
-    default y
-    select VFIO
-    depends on LINUX && PLATFORM_BUS
-
-config VFIO_XGMAC
-    bool
-    default y
-    depends on VFIO_PLATFORM
-
-config VFIO_AMD_XGBE
-    bool
-    default y
-    depends on VFIO_PLATFORM
-
 config VFIO_AP
     bool
     default y
diff --git a/hw/vfio/amd-xgbe.c b/hw/vfio/amd-xgbe.c
deleted file mode 100644
index 58f590e385..0000000000
--- a/hw/vfio/amd-xgbe.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * AMD XGBE VFIO device
- *
- * Copyright Linaro Limited, 2015
- *
- * Authors:
- *  Eric Auger <eric.auger@linaro.org>
- *
- * This work is licensed under the terms of the GNU GPL, version 2.  See
- * the COPYING file in the top-level directory.
- *
- */
-
-#include "qemu/osdep.h"
-#include "hw/vfio/vfio-amd-xgbe.h"
-#include "migration/vmstate.h"
-#include "qemu/module.h"
-#include "qemu/error-report.h"
-
-static void amd_xgbe_realize(DeviceState *dev, Error **errp)
-{
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev);
-    VFIOAmdXgbeDeviceClass *k = VFIO_AMD_XGBE_DEVICE_GET_CLASS(dev);
-
-    warn_report("-device vfio-amd-xgbe is deprecated");
-    vdev->compat = g_strdup("amd,xgbe-seattle-v1a");
-    vdev->num_compat = 1;
-
-    k->parent_realize(dev, errp);
-}
-
-static const VMStateDescription vfio_platform_amd_xgbe_vmstate = {
-    .name = "vfio-amd-xgbe",
-    .unmigratable = 1,
-};
-
-static void vfio_amd_xgbe_class_init(ObjectClass *klass, const void *data)
-{
-    DeviceClass *dc = DEVICE_CLASS(klass);
-    VFIOAmdXgbeDeviceClass *vcxc =
-        VFIO_AMD_XGBE_DEVICE_CLASS(klass);
-    device_class_set_parent_realize(dc, amd_xgbe_realize,
-                                    &vcxc->parent_realize);
-    dc->desc = "VFIO AMD XGBE";
-    dc->vmsd = &vfio_platform_amd_xgbe_vmstate;
-}
-
-static const TypeInfo vfio_amd_xgbe_dev_info = {
-    .name = TYPE_VFIO_AMD_XGBE,
-    .parent = TYPE_VFIO_PLATFORM,
-    .instance_size = sizeof(VFIOAmdXgbeDevice),
-    .class_init = vfio_amd_xgbe_class_init,
-    .class_size = sizeof(VFIOAmdXgbeDeviceClass),
-};
-
-static void register_amd_xgbe_dev_type(void)
-{
-    type_register_static(&vfio_amd_xgbe_dev_info);
-}
-
-type_init(register_amd_xgbe_dev_type)
diff --git a/hw/vfio/calxeda-xgmac.c b/hw/vfio/calxeda-xgmac.c
deleted file mode 100644
index 03f2ff5763..0000000000
--- a/hw/vfio/calxeda-xgmac.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * calxeda xgmac VFIO device
- *
- * Copyright Linaro Limited, 2014
- *
- * Authors:
- *  Eric Auger <eric.auger@linaro.org>
- *
- * This work is licensed under the terms of the GNU GPL, version 2.  See
- * the COPYING file in the top-level directory.
- *
- */
-
-#include "qemu/osdep.h"
-#include "hw/vfio/vfio-calxeda-xgmac.h"
-#include "migration/vmstate.h"
-#include "qemu/module.h"
-#include "qemu/error-report.h"
-
-static void calxeda_xgmac_realize(DeviceState *dev, Error **errp)
-{
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev);
-    VFIOCalxedaXgmacDeviceClass *k = VFIO_CALXEDA_XGMAC_DEVICE_GET_CLASS(dev);
-
-    warn_report("-device vfio-calxeda-xgmac is deprecated");
-    vdev->compat = g_strdup("calxeda,hb-xgmac");
-    vdev->num_compat = 1;
-
-    k->parent_realize(dev, errp);
-}
-
-static const VMStateDescription vfio_platform_calxeda_xgmac_vmstate = {
-    .name = "vfio-calxeda-xgmac",
-    .unmigratable = 1,
-};
-
-static void vfio_calxeda_xgmac_class_init(ObjectClass *klass, const void *data)
-{
-    DeviceClass *dc = DEVICE_CLASS(klass);
-    VFIOCalxedaXgmacDeviceClass *vcxc =
-        VFIO_CALXEDA_XGMAC_DEVICE_CLASS(klass);
-    device_class_set_parent_realize(dc, calxeda_xgmac_realize,
-                                    &vcxc->parent_realize);
-    dc->desc = "VFIO Calxeda XGMAC";
-    dc->vmsd = &vfio_platform_calxeda_xgmac_vmstate;
-}
-
-static const TypeInfo vfio_calxeda_xgmac_dev_info = {
-    .name = TYPE_VFIO_CALXEDA_XGMAC,
-    .parent = TYPE_VFIO_PLATFORM,
-    .instance_size = sizeof(VFIOCalxedaXgmacDevice),
-    .class_init = vfio_calxeda_xgmac_class_init,
-    .class_size = sizeof(VFIOCalxedaXgmacDeviceClass),
-};
-
-static void register_calxeda_xgmac_dev_type(void)
-{
-    type_register_static(&vfio_calxeda_xgmac_dev_info);
-}
-
-type_init(register_calxeda_xgmac_dev_type)
diff --git a/hw/vfio/container.c b/hw/vfio/container.c
index 3e13feaa74..030c6d3f89 100644
--- a/hw/vfio/container.c
+++ b/hw/vfio/container.c
@@ -71,7 +71,7 @@ static int vfio_dma_unmap_bitmap(const VFIOContainer *container,
                                  hwaddr iova, ram_addr_t size,
                                  IOMMUTLBEntry *iotlb)
 {
-    const VFIOContainerBase *bcontainer = &container->bcontainer;
+    const VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     struct vfio_iommu_type1_dma_unmap *unmap;
     struct vfio_bitmap *bitmap;
     VFIOBitmap vbmap;
@@ -124,8 +124,7 @@ static int vfio_legacy_dma_unmap_one(const VFIOContainerBase *bcontainer,
                                      hwaddr iova, ram_addr_t size,
                                      IOMMUTLBEntry *iotlb)
 {
-    const VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                                  bcontainer);
+    const VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     struct vfio_iommu_type1_dma_unmap unmap = {
         .argsz = sizeof(unmap),
         .flags = 0,
@@ -213,8 +212,7 @@ static int vfio_legacy_dma_map(const VFIOContainerBase *bcontainer, hwaddr iova,
                                ram_addr_t size, void *vaddr, bool readonly,
                                MemoryRegion *mr)
 {
-    const VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                                  bcontainer);
+    const VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     struct vfio_iommu_type1_dma_map map = {
         .argsz = sizeof(map),
         .flags = VFIO_DMA_MAP_FLAG_READ,
@@ -246,8 +244,7 @@ static int
 vfio_legacy_set_dirty_page_tracking(const VFIOContainerBase *bcontainer,
                                     bool start, Error **errp)
 {
-    const VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                                  bcontainer);
+    const VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     int ret;
     struct vfio_iommu_type1_dirty_bitmap dirty = {
         .argsz = sizeof(dirty),
@@ -272,8 +269,7 @@ vfio_legacy_set_dirty_page_tracking(const VFIOContainerBase *bcontainer,
 static int vfio_legacy_query_dirty_bitmap(const VFIOContainerBase *bcontainer,
                       VFIOBitmap *vbmap, hwaddr iova, hwaddr size, Error **errp)
 {
-    const VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                                  bcontainer);
+    const VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     struct vfio_iommu_type1_dirty_bitmap *dbitmap;
     struct vfio_iommu_type1_dirty_bitmap_get *range;
     int ret;
@@ -495,7 +491,7 @@ static void vfio_get_iommu_info_migration(VFIOContainer *container,
 {
     struct vfio_info_cap_header *hdr;
     struct vfio_iommu_type1_info_cap_migration *cap_mig;
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
 
     hdr = vfio_get_iommu_info_cap(info, VFIO_IOMMU_TYPE1_INFO_CAP_MIGRATION);
     if (!hdr) {
@@ -518,8 +514,7 @@ static void vfio_get_iommu_info_migration(VFIOContainer *container,
 
 static bool vfio_legacy_setup(VFIOContainerBase *bcontainer, Error **errp)
 {
-    VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                            bcontainer);
+    VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     g_autofree struct vfio_iommu_type1_info *info = NULL;
     int ret;
 
@@ -634,7 +629,7 @@ static bool vfio_container_connect(VFIOGroup *group, AddressSpace *as,
 
     if (!cpr_is_incoming()) {
         QLIST_FOREACH(bcontainer, &space->containers, next) {
-            container = container_of(bcontainer, VFIOContainer, bcontainer);
+            container = VFIO_IOMMU_LEGACY(bcontainer);
             if (!ioctl(group->fd, VFIO_GROUP_SET_CONTAINER, &container->fd)) {
                 return vfio_container_group_add(container, group, errp);
             }
@@ -652,7 +647,7 @@ static bool vfio_container_connect(VFIOGroup *group, AddressSpace *as,
          * create the container struct and group list.
          */
         QLIST_FOREACH(bcontainer, &space->containers, next) {
-            container = container_of(bcontainer, VFIOContainer, bcontainer);
+            container = VFIO_IOMMU_LEGACY(bcontainer);
 
             if (vfio_cpr_container_match(container, group, fd)) {
                 return vfio_container_group_add(container, group, errp);
@@ -672,7 +667,7 @@ static bool vfio_container_connect(VFIOGroup *group, AddressSpace *as,
         goto fail;
     }
     new_container = true;
-    bcontainer = &container->bcontainer;
+    bcontainer = VFIO_IOMMU(container);
 
     if (!vfio_legacy_cpr_register_container(container, errp)) {
         goto fail;
@@ -735,7 +730,7 @@ fail:
 static void vfio_container_disconnect(VFIOGroup *group)
 {
     VFIOContainer *container = group->container;
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
 
     QLIST_REMOVE(group, container_next);
@@ -781,7 +776,7 @@ static VFIOGroup *vfio_group_get(int groupid, AddressSpace *as, Error **errp)
     QLIST_FOREACH(group, &vfio_group_list, next) {
         if (group->groupid == groupid) {
             /* Found it.  Now is it already in the right context? */
-            if (group->container->bcontainer.space->as == as) {
+            if (VFIO_IOMMU(group->container)->space->as == as) {
                 return group;
             } else {
                 error_setg(errp, "group %d used in multiple address spaces",
@@ -895,7 +890,7 @@ static bool vfio_device_get(VFIOGroup *group, const char *name,
         }
     }
 
-    vfio_device_prepare(vbasedev, &group->container->bcontainer, info);
+    vfio_device_prepare(vbasedev, VFIO_IOMMU(group->container), info);
 
     vbasedev->fd = fd;
     vbasedev->group = group;
@@ -1087,7 +1082,7 @@ static int vfio_legacy_pci_hot_reset(VFIODevice *vbasedev, bool single)
         /* Prep dependent devices for reset and clear our marker. */
         QLIST_FOREACH(vbasedev_iter, &group->device_list, next) {
             if (!vbasedev_iter->dev->realized ||
-                vbasedev_iter->type != VFIO_DEVICE_TYPE_PCI) {
+                !vfio_pci_from_vfio_device(vbasedev_iter)) {
                 continue;
             }
             tmp = container_of(vbasedev_iter, VFIOPCIDevice, vbasedev);
@@ -1172,7 +1167,7 @@ out:
 
         QLIST_FOREACH(vbasedev_iter, &group->device_list, next) {
             if (!vbasedev_iter->dev->realized ||
-                vbasedev_iter->type != VFIO_DEVICE_TYPE_PCI) {
+                !vfio_pci_from_vfio_device(vbasedev_iter)) {
                 continue;
             }
             tmp = container_of(vbasedev_iter, VFIOPCIDevice, vbasedev);
diff --git a/hw/vfio/cpr-legacy.c b/hw/vfio/cpr-legacy.c
index 553b203e9b..8f437194fa 100644
--- a/hw/vfio/cpr-legacy.c
+++ b/hw/vfio/cpr-legacy.c
@@ -41,8 +41,8 @@ static int vfio_legacy_cpr_dma_map(const VFIOContainerBase *bcontainer,
                                    hwaddr iova, ram_addr_t size, void *vaddr,
                                    bool readonly, MemoryRegion *mr)
 {
-    const VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                                  bcontainer);
+    const VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
+
     struct vfio_iommu_type1_dma_map map = {
         .argsz = sizeof(map),
         .flags = VFIO_DMA_MAP_FLAG_VADDR,
@@ -65,7 +65,7 @@ static void vfio_region_remap(MemoryListener *listener,
 {
     VFIOContainer *container = container_of(listener, VFIOContainer,
                                             cpr.remap_listener);
-    vfio_container_region_add(&container->bcontainer, section, true);
+    vfio_container_region_add(VFIO_IOMMU(container), section, true);
 }
 
 static bool vfio_cpr_supported(VFIOContainer *container, Error **errp)
@@ -98,7 +98,7 @@ static int vfio_container_pre_save(void *opaque)
 static int vfio_container_post_load(void *opaque, int version_id)
 {
     VFIOContainer *container = opaque;
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
     dma_map_fn saved_dma_map = vioc->dma_map;
     Error *local_err = NULL;
@@ -135,7 +135,7 @@ static int vfio_cpr_fail_notifier(NotifierWithReturn *notifier,
 {
     VFIOContainer *container =
         container_of(notifier, VFIOContainer, cpr.transfer_notifier);
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
 
     if (e->type != MIG_EVENT_PRECOPY_FAILED) {
         return 0;
@@ -167,7 +167,7 @@ static int vfio_cpr_fail_notifier(NotifierWithReturn *notifier,
 
 bool vfio_legacy_cpr_register_container(VFIOContainer *container, Error **errp)
 {
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     Error **cpr_blocker = &container->cpr.blocker;
 
     migration_add_notifier_mode(&bcontainer->cpr_reboot_notifier,
@@ -191,7 +191,7 @@ bool vfio_legacy_cpr_register_container(VFIOContainer *container, Error **errp)
 
 void vfio_legacy_cpr_unregister_container(VFIOContainer *container)
 {
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
 
     migration_remove_notifier(&bcontainer->cpr_reboot_notifier);
     migrate_del_blocker(&container->cpr.blocker);
diff --git a/hw/vfio/cpr.c b/hw/vfio/cpr.c
index a831243e02..2c71fc1e8e 100644
--- a/hw/vfio/cpr.c
+++ b/hw/vfio/cpr.c
@@ -56,7 +56,7 @@ static void vfio_cpr_claim_vectors(VFIOPCIDevice *vdev, int nr_vectors,
 {
     int i, fd;
     bool pending = false;
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     vdev->nr_vectors = nr_vectors;
     vdev->msi_vectors = g_new0(VFIOMSIVector, nr_vectors);
@@ -99,7 +99,7 @@ static void vfio_cpr_claim_vectors(VFIOPCIDevice *vdev, int nr_vectors,
 static int vfio_cpr_pci_pre_load(void *opaque)
 {
     VFIOPCIDevice *vdev = opaque;
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int size = MIN(pci_config_size(pdev), vdev->config_size);
     int i;
 
@@ -113,7 +113,7 @@ static int vfio_cpr_pci_pre_load(void *opaque)
 static int vfio_cpr_pci_post_load(void *opaque, int version_id)
 {
     VFIOPCIDevice *vdev = opaque;
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int nr_vectors;
 
     vfio_sub_page_bar_update_mappings(vdev);
@@ -173,8 +173,8 @@ const VMStateDescription vfio_cpr_pci_vmstate = {
     .post_load = vfio_cpr_pci_post_load,
     .needed = cpr_incoming_needed,
     .fields = (VMStateField[]) {
-        VMSTATE_PCI_DEVICE(pdev, VFIOPCIDevice),
-        VMSTATE_MSIX_TEST(pdev, VFIOPCIDevice, pci_msix_present),
+        VMSTATE_PCI_DEVICE(parent_obj, VFIOPCIDevice),
+        VMSTATE_MSIX_TEST(parent_obj, VFIOPCIDevice, pci_msix_present),
         VMSTATE_VFIO_INTX(intx, VFIOPCIDevice),
         VMSTATE_END_OF_LIST()
     }
@@ -214,7 +214,7 @@ static int set_irqfd_notifier_gsi(KVMState *s, EventNotifier *n,
 static int vfio_cpr_set_msi_virq(VFIOPCIDevice *vdev, Error **errp, bool enable)
 {
     const char *op = (enable ? "enable" : "disable");
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int i, nr_vectors, ret = 0;
 
     if (msix_enabled(pdev)) {
diff --git a/hw/vfio/device.c b/hw/vfio/device.c
index 52a1996dc4..08f12ac31f 100644
--- a/hw/vfio/device.c
+++ b/hw/vfio/device.c
@@ -129,7 +129,7 @@ static inline const char *action_to_str(int action)
 
 static const char *index_to_str(VFIODevice *vbasedev, int index)
 {
-    if (vbasedev->type != VFIO_DEVICE_TYPE_PCI) {
+    if (!vfio_pci_from_vfio_device(vbasedev)) {
         return NULL;
     }
 
diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c
index ee0767b0b8..4bfa2e0fcd 100644
--- a/hw/vfio/igd.c
+++ b/hw/vfio/igd.c
@@ -200,7 +200,7 @@ static bool vfio_pci_igd_opregion_detect(VFIOPCIDevice *vdev,
     }
 
     /* Hotplugging is not supported for opregion access */
-    if (vdev->pdev.qdev.hotplugged) {
+    if (DEVICE(vdev)->hotplugged) {
         warn_report("IGD device detected, but OpRegion is not supported "
                     "on hotplugged device.");
         return false;
@@ -260,11 +260,12 @@ static int vfio_pci_igd_copy(VFIOPCIDevice *vdev, PCIDevice *pdev,
 static int vfio_pci_igd_host_init(VFIOPCIDevice *vdev,
                                   struct vfio_region_info *info)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     PCIBus *bus;
     PCIDevice *host_bridge;
     int ret;
 
-    bus = pci_device_root_bus(&vdev->pdev);
+    bus = pci_device_root_bus(pdev);
     host_bridge = pci_find_device(bus, 0, PCI_DEVFN(0, 0));
 
     if (!host_bridge) {
@@ -327,13 +328,14 @@ type_init(vfio_pci_igd_register_types)
 static int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev,
                                  struct vfio_region_info *info)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     PCIDevice *lpc_bridge;
     int ret;
 
-    lpc_bridge = pci_find_device(pci_device_root_bus(&vdev->pdev),
+    lpc_bridge = pci_find_device(pci_device_root_bus(pdev),
                                  0, PCI_DEVFN(0x1f, 0));
     if (!lpc_bridge) {
-        lpc_bridge = pci_create_simple(pci_device_root_bus(&vdev->pdev),
+        lpc_bridge = pci_create_simple(pci_device_root_bus(pdev),
                                  PCI_DEVFN(0x1f, 0), "vfio-pci-igd-lpc-bridge");
     }
 
@@ -350,13 +352,14 @@ static bool vfio_pci_igd_setup_lpc_bridge(VFIOPCIDevice *vdev, Error **errp)
 {
     struct vfio_region_info *host = NULL;
     struct vfio_region_info *lpc = NULL;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     PCIDevice *lpc_bridge;
     int ret;
 
     /*
      * Copying IDs or creating new devices are not supported on hotplug
      */
-    if (vdev->pdev.qdev.hotplugged) {
+    if (DEVICE(vdev)->hotplugged) {
         error_setg(errp, "IGD LPC is not supported on hotplugged device");
         return false;
     }
@@ -366,7 +369,7 @@ static bool vfio_pci_igd_setup_lpc_bridge(VFIOPCIDevice *vdev, Error **errp)
      * can stuff host values into, so if there's already one there and it's not
      * one we can hack on, this quirk is no-go.  Sorry Q35.
      */
-    lpc_bridge = pci_find_device(pci_device_root_bus(&vdev->pdev),
+    lpc_bridge = pci_find_device(pci_device_root_bus(pdev),
                                  0, PCI_DEVFN(0x1f, 0));
     if (lpc_bridge && !object_dynamic_cast(OBJECT(lpc_bridge),
                                            "vfio-pci-igd-lpc-bridge")) {
@@ -460,7 +463,7 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr)
     int gen;
 
     if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
-        !vfio_is_vga(vdev) || nr != 0) {
+        !vfio_is_base_display(vdev) || nr != 0) {
         return;
     }
 
@@ -510,6 +513,7 @@ void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr)
 static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
 {
     struct vfio_region_info *opregion = NULL;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int ret, gen;
     uint64_t gms_size = 0;
     uint64_t *bdsm_size;
@@ -518,7 +522,7 @@ static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
     Error *err = NULL;
 
     if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
-        !vfio_is_vga(vdev)) {
+        !vfio_is_base_display(vdev)) {
         return true;
     }
 
@@ -529,21 +533,22 @@ static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
     info_report("OpRegion detected on Intel display %x.", vdev->device_id);
 
     gen = igd_gen(vdev);
-    gmch = vfio_pci_read_config(&vdev->pdev, IGD_GMCH, 4);
+    gmch = vfio_pci_read_config(pdev, IGD_GMCH, 4);
 
     /*
      * For backward compatibility, enable legacy mode when
      * - Device geneation is 6 to 9 (including both)
-     * - IGD claims VGA cycles on host
+     * - IGD exposes itself as VGA controller and claims VGA cycles on host
      * - Machine type is i440fx (pc_piix)
      * - IGD device is at guest BDF 00:02.0
      * - Not manually disabled by x-igd-legacy-mode=off
      */
     if ((vdev->igd_legacy_mode != ON_OFF_AUTO_OFF) &&
+        vfio_is_vga(vdev) &&
         (gen >= 6 && gen <= 9) &&
         !(gmch & IGD_GMCH_VGA_DISABLE) &&
         !strcmp(MACHINE_GET_CLASS(qdev_get_machine())->family, "pc_piix") &&
-        (&vdev->pdev == pci_find_device(pci_device_root_bus(&vdev->pdev),
+        (pdev == pci_find_device(pci_device_root_bus(pdev),
         0, PCI_DEVFN(0x2, 0)))) {
         /*
          * IGD legacy mode requires:
@@ -565,7 +570,7 @@ static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
          */
         ret = vfio_device_get_region_info(&vdev->vbasedev,
                                           VFIO_PCI_ROM_REGION_INDEX, &rom);
-        if ((ret || !rom->size) && !vdev->pdev.romfile) {
+        if ((ret || !rom->size) && !pdev->romfile) {
             error_setg(&err, "Device has no ROM");
             goto error;
         }
@@ -610,8 +615,8 @@ static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
      * ASLS (OpRegion address) is read-only, emulated
      * It contains HPA, guest firmware need to reprogram it with GPA.
      */
-    pci_set_long(vdev->pdev.config + IGD_ASLS, 0);
-    pci_set_long(vdev->pdev.wmask + IGD_ASLS, ~0);
+    pci_set_long(pdev->config + IGD_ASLS, 0);
+    pci_set_long(pdev->wmask + IGD_ASLS, ~0);
     pci_set_long(vdev->emulated_config_bits + IGD_ASLS, ~0);
 
     /*
@@ -625,8 +630,8 @@ static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
         }
 
         /* GMCH is read-only, emulated */
-        pci_set_long(vdev->pdev.config + IGD_GMCH, gmch);
-        pci_set_long(vdev->pdev.wmask + IGD_GMCH, 0);
+        pci_set_long(pdev->config + IGD_GMCH, gmch);
+        pci_set_long(pdev->wmask + IGD_GMCH, 0);
         pci_set_long(vdev->emulated_config_bits + IGD_GMCH, ~0);
     }
 
@@ -635,12 +640,12 @@ static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
 
         /* BDSM is read-write, emulated. BIOS needs to be able to write it */
         if (gen < 11) {
-            pci_set_long(vdev->pdev.config + IGD_BDSM, 0);
-            pci_set_long(vdev->pdev.wmask + IGD_BDSM, ~0);
+            pci_set_long(pdev->config + IGD_BDSM, 0);
+            pci_set_long(pdev->wmask + IGD_BDSM, ~0);
             pci_set_long(vdev->emulated_config_bits + IGD_BDSM, ~0);
         } else {
-            pci_set_quad(vdev->pdev.config + IGD_BDSM_GEN11, 0);
-            pci_set_quad(vdev->pdev.wmask + IGD_BDSM_GEN11, ~0);
+            pci_set_quad(pdev->config + IGD_BDSM_GEN11, 0);
+            pci_set_quad(pdev->wmask + IGD_BDSM_GEN11, ~0);
             pci_set_quad(vdev->emulated_config_bits + IGD_BDSM_GEN11, ~0);
         }
     }
diff --git a/hw/vfio/iommufd.c b/hw/vfio/iommufd.c
index 48c590b6a9..8c27222f75 100644
--- a/hw/vfio/iommufd.c
+++ b/hw/vfio/iommufd.c
@@ -737,8 +737,8 @@ iommufd_cdev_dep_get_realized_vpdev(struct vfio_pci_dependent_device *dep_dev,
     }
 
     vbasedev_tmp = iommufd_cdev_pci_find_by_devid(dep_dev->devid);
-    if (!vbasedev_tmp || !vbasedev_tmp->dev->realized ||
-        vbasedev_tmp->type != VFIO_DEVICE_TYPE_PCI) {
+    if (!vfio_pci_from_vfio_device(vbasedev_tmp) ||
+        !vbasedev_tmp->dev->realized) {
         return NULL;
     }
 
diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c
index f498e23a93..e093833165 100644
--- a/hw/vfio/listener.c
+++ b/hw/vfio/listener.c
@@ -250,8 +250,9 @@ static int vfio_ram_discard_notify_populate(RamDiscardListener *rdl,
     return 0;
 }
 
-static void vfio_ram_discard_register_listener(VFIOContainerBase *bcontainer,
-                                               MemoryRegionSection *section)
+static bool vfio_ram_discard_register_listener(VFIOContainerBase *bcontainer,
+                                               MemoryRegionSection *section,
+                                               Error **errp)
 {
     RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
     int target_page_size = qemu_target_page_size();
@@ -316,13 +317,15 @@ static void vfio_ram_discard_register_listener(VFIOContainerBase *bcontainer,
 
         if (vrdl_mappings + max_memslots - vrdl_count >
             bcontainer->dma_max_mappings) {
-            warn_report("%s: possibly running out of DMA mappings. E.g., try"
+            error_setg(errp, "%s: possibly running out of DMA mappings. E.g., try"
                         " increasing the 'block-size' of virtio-mem devies."
                         " Maximum possible DMA mappings: %d, Maximum possible"
                         " memslots: %d", __func__, bcontainer->dma_max_mappings,
                         max_memslots);
+            return false;
         }
     }
+    return true;
 }
 
 static void vfio_ram_discard_unregister_listener(VFIOContainerBase *bcontainer,
@@ -450,7 +453,7 @@ static void vfio_device_error_append(VFIODevice *vbasedev, Error **errp)
      * MMIO region mapping failures are not fatal but in this case PCI
      * peer-to-peer transactions are broken.
      */
-    if (vbasedev && vbasedev->type == VFIO_DEVICE_TYPE_PCI) {
+    if (vfio_pci_from_vfio_device(vbasedev)) {
         error_append_hint(errp, "%s: PCI peer-to-peer transactions "
                           "on BARs are not supported.\n", vbasedev->name);
     }
@@ -571,9 +574,14 @@ void vfio_container_region_add(VFIOContainerBase *bcontainer,
      */
     if (memory_region_has_ram_discard_manager(section->mr)) {
         if (!cpr_remap) {
-            vfio_ram_discard_register_listener(bcontainer, section);
+            if (!vfio_ram_discard_register_listener(bcontainer, section, &err)) {
+                goto fail;
+            }
         } else if (!vfio_cpr_ram_discard_register_listener(bcontainer,
                                                            section)) {
+            error_setg(&err,
+                       "vfio_cpr_ram_discard_register_listener for %s failed",
+                       memory_region_name(section->mr));
             goto fail;
         }
         return;
@@ -751,7 +759,7 @@ static bool vfio_section_is_vfio_pci(MemoryRegionSection *section,
     owner = memory_region_owner(section->mr);
 
     QLIST_FOREACH(vbasedev, &bcontainer->device_list, container_next) {
-        if (vbasedev->type != VFIO_DEVICE_TYPE_PCI) {
+        if (!vfio_pci_from_vfio_device(vbasedev)) {
             continue;
         }
         pcidev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
diff --git a/hw/vfio/meson.build b/hw/vfio/meson.build
index bfaf6be805..d3ed3cb7ac 100644
--- a/hw/vfio/meson.build
+++ b/hw/vfio/meson.build
@@ -13,14 +13,11 @@ vfio_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files(
   'pci.c',
 ))
 vfio_ss.add(when: 'CONFIG_VFIO_CCW', if_true: files('ccw.c'))
-vfio_ss.add(when: 'CONFIG_VFIO_PLATFORM', if_true: files('platform.c'))
 vfio_ss.add(when: 'CONFIG_VFIO_AP', if_true: files('ap.c'))
 vfio_ss.add(when: 'CONFIG_VFIO_IGD', if_true: files('igd.c'))
 
 specific_ss.add_all(when: 'CONFIG_VFIO', if_true: vfio_ss)
 
-system_ss.add(when: 'CONFIG_VFIO_XGMAC', if_true: files('calxeda-xgmac.c'))
-system_ss.add(when: 'CONFIG_VFIO_AMD_XGBE', if_true: files('amd-xgbe.c'))
 system_ss.add(when: 'CONFIG_VFIO', if_true: files(
   'cpr.c',
   'cpr-legacy.c',
diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c
index 3f002252ac..c97606dbf1 100644
--- a/hw/vfio/pci-quirks.c
+++ b/hw/vfio/pci-quirks.c
@@ -113,6 +113,7 @@ static uint64_t vfio_generic_window_quirk_data_read(void *opaque,
 {
     VFIOConfigWindowQuirk *window = opaque;
     VFIOPCIDevice *vdev = window->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint64_t data;
 
     /* Always read data reg, discard if window enabled */
@@ -120,7 +121,7 @@ static uint64_t vfio_generic_window_quirk_data_read(void *opaque,
                             addr + window->data_offset, size);
 
     if (window->window_enabled) {
-        data = vfio_pci_read_config(&vdev->pdev, window->address_val, size);
+        data = vfio_pci_read_config(pdev, window->address_val, size);
         trace_vfio_quirk_generic_window_data_read(vdev->vbasedev.name,
                                     memory_region_name(window->data_mem), data);
     }
@@ -133,9 +134,10 @@ static void vfio_generic_window_quirk_data_write(void *opaque, hwaddr addr,
 {
     VFIOConfigWindowQuirk *window = opaque;
     VFIOPCIDevice *vdev = window->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     if (window->window_enabled) {
-        vfio_pci_write_config(&vdev->pdev, window->address_val, data, size);
+        vfio_pci_write_config(pdev, window->address_val, data, size);
         trace_vfio_quirk_generic_window_data_write(vdev->vbasedev.name,
                                     memory_region_name(window->data_mem), data);
         return;
@@ -156,6 +158,7 @@ static uint64_t vfio_generic_quirk_mirror_read(void *opaque,
 {
     VFIOConfigMirrorQuirk *mirror = opaque;
     VFIOPCIDevice *vdev = mirror->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint64_t data;
 
     /* Read and discard in case the hardware cares */
@@ -163,7 +166,7 @@ static uint64_t vfio_generic_quirk_mirror_read(void *opaque,
                            addr + mirror->offset, size);
 
     addr += mirror->config_offset;
-    data = vfio_pci_read_config(&vdev->pdev, addr, size);
+    data = vfio_pci_read_config(pdev, addr, size);
     trace_vfio_quirk_generic_mirror_read(vdev->vbasedev.name,
                                          memory_region_name(mirror->mem),
                                          addr, data);
@@ -175,9 +178,10 @@ static void vfio_generic_quirk_mirror_write(void *opaque, hwaddr addr,
 {
     VFIOConfigMirrorQuirk *mirror = opaque;
     VFIOPCIDevice *vdev = mirror->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     addr += mirror->config_offset;
-    vfio_pci_write_config(&vdev->pdev, addr, data, size);
+    vfio_pci_write_config(pdev, addr, data, size);
     trace_vfio_quirk_generic_mirror_write(vdev->vbasedev.name,
                                           memory_region_name(mirror->mem),
                                           addr, data);
@@ -211,7 +215,8 @@ static uint64_t vfio_ati_3c3_quirk_read(void *opaque,
                                         hwaddr addr, unsigned size)
 {
     VFIOPCIDevice *vdev = opaque;
-    uint64_t data = vfio_pci_read_config(&vdev->pdev,
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+    uint64_t data = vfio_pci_read_config(pdev,
                                          PCI_BASE_ADDRESS_4 + 1, size);
 
     trace_vfio_quirk_ati_3c3_read(vdev->vbasedev.name, data);
@@ -563,6 +568,7 @@ static uint64_t vfio_nvidia_3d0_quirk_read(void *opaque,
 {
     VFIONvidia3d0Quirk *quirk = opaque;
     VFIOPCIDevice *vdev = quirk->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIONvidia3d0State old_state = quirk->state;
     uint64_t data = vfio_vga_read(&vdev->vga->region[QEMU_PCI_VGA_IO_HI],
                                   addr + 0x10, size);
@@ -573,7 +579,7 @@ static uint64_t vfio_nvidia_3d0_quirk_read(void *opaque,
         (quirk->offset & ~(PCI_CONFIG_SPACE_SIZE - 1)) == 0x1800) {
         uint8_t offset = quirk->offset & (PCI_CONFIG_SPACE_SIZE - 1);
 
-        data = vfio_pci_read_config(&vdev->pdev, offset, size);
+        data = vfio_pci_read_config(pdev, offset, size);
         trace_vfio_quirk_nvidia_3d0_read(vdev->vbasedev.name,
                                          offset, size, data);
     }
@@ -586,6 +592,7 @@ static void vfio_nvidia_3d0_quirk_write(void *opaque, hwaddr addr,
 {
     VFIONvidia3d0Quirk *quirk = opaque;
     VFIOPCIDevice *vdev = quirk->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIONvidia3d0State old_state = quirk->state;
 
     quirk->state = NONE;
@@ -599,7 +606,7 @@ static void vfio_nvidia_3d0_quirk_write(void *opaque, hwaddr addr,
         if ((quirk->offset & ~(PCI_CONFIG_SPACE_SIZE - 1)) == 0x1800) {
             uint8_t offset = quirk->offset & (PCI_CONFIG_SPACE_SIZE - 1);
 
-            vfio_pci_write_config(&vdev->pdev, offset, data, size);
+            vfio_pci_write_config(pdev, offset, data, size);
             trace_vfio_quirk_nvidia_3d0_write(vdev->vbasedev.name,
                                               offset, data, size);
             return;
@@ -815,7 +822,7 @@ static void vfio_nvidia_quirk_mirror_write(void *opaque, hwaddr addr,
 {
     VFIOConfigMirrorQuirk *mirror = opaque;
     VFIOPCIDevice *vdev = mirror->vdev;
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     LastDataSet *last = (LastDataSet *)&mirror->data;
 
     vfio_generic_quirk_mirror_write(opaque, addr, data, size);
@@ -1005,6 +1012,7 @@ static void vfio_rtl8168_quirk_address_write(void *opaque, hwaddr addr,
 {
     VFIOrtl8168Quirk *rtl = opaque;
     VFIOPCIDevice *vdev = rtl->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     rtl->enabled = false;
 
@@ -1013,7 +1021,7 @@ static void vfio_rtl8168_quirk_address_write(void *opaque, hwaddr addr,
         rtl->addr = (uint32_t)data;
 
         if (data & 0x80000000U) { /* Do write */
-            if (vdev->pdev.cap_present & QEMU_PCI_CAP_MSIX) {
+            if (pdev->cap_present & QEMU_PCI_CAP_MSIX) {
                 hwaddr offset = data & 0xfff;
                 uint64_t val = rtl->data;
 
@@ -1021,7 +1029,7 @@ static void vfio_rtl8168_quirk_address_write(void *opaque, hwaddr addr,
                                                     (uint16_t)offset, val);
 
                 /* Write to the proper guest MSI-X table instead */
-                memory_region_dispatch_write(&vdev->pdev.msix_table_mmio,
+                memory_region_dispatch_write(&pdev->msix_table_mmio,
                                              offset, val,
                                              size_memop(size) | MO_LE,
                                              MEMTXATTRS_UNSPECIFIED);
@@ -1049,11 +1057,12 @@ static uint64_t vfio_rtl8168_quirk_data_read(void *opaque,
 {
     VFIOrtl8168Quirk *rtl = opaque;
     VFIOPCIDevice *vdev = rtl->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint64_t data = vfio_region_read(&vdev->bars[2].region, addr + 0x70, size);
 
-    if (rtl->enabled && (vdev->pdev.cap_present & QEMU_PCI_CAP_MSIX)) {
+    if (rtl->enabled && (pdev->cap_present & QEMU_PCI_CAP_MSIX)) {
         hwaddr offset = rtl->addr & 0xfff;
-        memory_region_dispatch_read(&vdev->pdev.msix_table_mmio, offset,
+        memory_region_dispatch_read(&pdev->msix_table_mmio, offset,
                                     &data, size_memop(size) | MO_LE,
                                     MEMTXATTRS_UNSPECIFIED);
         trace_vfio_quirk_rtl8168_msix_read(vdev->vbasedev.name, offset, data);
@@ -1297,7 +1306,7 @@ static void vfio_radeon_set_gfx_only_reset(VFIOPCIDevice *vdev)
 
 static int vfio_radeon_reset(VFIOPCIDevice *vdev)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int i, ret = 0;
     uint32_t data;
 
@@ -1454,7 +1463,7 @@ static bool is_valid_std_cap_offset(uint8_t pos)
 static bool vfio_add_nv_gpudirect_cap(VFIOPCIDevice *vdev, Error **errp)
 {
     ERRP_GUARD();
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int ret, pos;
     bool c8_conflict = false, d4_conflict = false;
     uint8_t tmp;
@@ -1547,6 +1556,7 @@ static bool vfio_add_nv_gpudirect_cap(VFIOPCIDevice *vdev, Error **errp)
 static bool vfio_add_vmd_shadow_cap(VFIOPCIDevice *vdev, Error **errp)
 {
     ERRP_GUARD();
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint8_t membar_phys[16];
     int ret, pos = 0xE8;
 
@@ -1565,7 +1575,7 @@ static bool vfio_add_vmd_shadow_cap(VFIOPCIDevice *vdev, Error **errp)
         return false;
     }
 
-    ret = pci_add_capability(&vdev->pdev, PCI_CAP_ID_VNDR, pos,
+    ret = pci_add_capability(pdev, PCI_CAP_ID_VNDR, pos,
                              VMD_SHADOW_CAP_LEN, errp);
     if (ret < 0) {
         error_prepend(errp, "Failed to add VMD MEMBAR Shadow cap: ");
@@ -1574,10 +1584,10 @@ static bool vfio_add_vmd_shadow_cap(VFIOPCIDevice *vdev, Error **errp)
 
     memset(vdev->emulated_config_bits + pos, 0xFF, VMD_SHADOW_CAP_LEN);
     pos += PCI_CAP_FLAGS;
-    pci_set_byte(vdev->pdev.config + pos++, VMD_SHADOW_CAP_LEN);
-    pci_set_byte(vdev->pdev.config + pos++, VMD_SHADOW_CAP_VER);
-    pci_set_long(vdev->pdev.config + pos, 0x53484457); /* SHDW */
-    memcpy(vdev->pdev.config + pos + 4, membar_phys, 16);
+    pci_set_byte(pdev->config + pos++, VMD_SHADOW_CAP_LEN);
+    pci_set_byte(pdev->config + pos++, VMD_SHADOW_CAP_VER);
+    pci_set_long(pdev->config + pos, 0x53484457); /* SHDW */
+    memcpy(pdev->config + pos + 4, membar_phys, 16);
 
     return true;
 }
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 07257d0fa0..d14e96b2f8 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -117,6 +117,7 @@ static void vfio_intx_mmap_enable(void *opaque)
 static void vfio_intx_interrupt(void *opaque)
 {
     VFIOPCIDevice *vdev = opaque;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     if (!event_notifier_test_and_clear(&vdev->intx.interrupt)) {
         return;
@@ -125,7 +126,7 @@ static void vfio_intx_interrupt(void *opaque)
     trace_vfio_intx_interrupt(vdev->vbasedev.name, 'A' + vdev->intx.pin);
 
     vdev->intx.pending = true;
-    pci_irq_assert(&vdev->pdev);
+    pci_irq_assert(pdev);
     vfio_mmap_set_enabled(vdev, false);
     if (vdev->intx.mmap_timeout) {
         timer_mod(vdev->intx.mmap_timer,
@@ -136,6 +137,7 @@ static void vfio_intx_interrupt(void *opaque)
 void vfio_pci_intx_eoi(VFIODevice *vbasedev)
 {
     VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     if (!vdev->intx.pending) {
         return;
@@ -144,13 +146,14 @@ void vfio_pci_intx_eoi(VFIODevice *vbasedev)
     trace_vfio_pci_intx_eoi(vbasedev->name);
 
     vdev->intx.pending = false;
-    pci_irq_deassert(&vdev->pdev);
+    pci_irq_deassert(pdev);
     vfio_device_irq_unmask(vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
 }
 
 static bool vfio_intx_enable_kvm(VFIOPCIDevice *vdev, Error **errp)
 {
 #ifdef CONFIG_KVM
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int irq_fd = event_notifier_get_fd(&vdev->intx.interrupt);
 
     if (vdev->no_kvm_intx || !kvm_irqfds_enabled() ||
@@ -163,7 +166,7 @@ static bool vfio_intx_enable_kvm(VFIOPCIDevice *vdev, Error **errp)
     qemu_set_fd_handler(irq_fd, NULL, NULL, vdev);
     vfio_device_irq_mask(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
     vdev->intx.pending = false;
-    pci_irq_deassert(&vdev->pdev);
+    pci_irq_deassert(pdev);
 
     /* Get an eventfd for resample/unmask */
     if (!vfio_notifier_init(vdev, &vdev->intx.unmask, "intx-unmask", 0, errp)) {
@@ -241,6 +244,8 @@ static bool vfio_cpr_intx_enable_kvm(VFIOPCIDevice *vdev, Error **errp)
 static void vfio_intx_disable_kvm(VFIOPCIDevice *vdev)
 {
 #ifdef CONFIG_KVM
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
     if (!vdev->intx.kvm_accel) {
         return;
     }
@@ -251,7 +256,7 @@ static void vfio_intx_disable_kvm(VFIOPCIDevice *vdev)
      */
     vfio_device_irq_mask(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
     vdev->intx.pending = false;
-    pci_irq_deassert(&vdev->pdev);
+    pci_irq_deassert(pdev);
 
     /* Tell KVM to stop listening for an INTx irqfd */
     if (kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, &vdev->intx.interrupt,
@@ -307,7 +312,7 @@ static void vfio_intx_routing_notifier(PCIDevice *pdev)
         return;
     }
 
-    route = pci_device_route_intx_to_irq(&vdev->pdev, vdev->intx.pin);
+    route = pci_device_route_intx_to_irq(pdev, vdev->intx.pin);
 
     if (pci_intx_route_changed(&vdev->intx.route, &route)) {
         vfio_intx_update(vdev, &route);
@@ -324,7 +329,8 @@ static void vfio_irqchip_change(Notifier *notify, void *data)
 
 static bool vfio_intx_enable(VFIOPCIDevice *vdev, Error **errp)
 {
-    uint8_t pin = vfio_pci_read_config(&vdev->pdev, PCI_INTERRUPT_PIN, 1);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+    uint8_t pin = vfio_pci_read_config(pdev, PCI_INTERRUPT_PIN, 1);
     Error *err = NULL;
     int32_t fd;
 
@@ -342,7 +348,7 @@ static bool vfio_intx_enable(VFIOPCIDevice *vdev, Error **errp)
     }
 
     vdev->intx.pin = pin - 1; /* Pin A (1) -> irq[0] */
-    pci_config_set_interrupt_pin(vdev->pdev.config, pin);
+    pci_config_set_interrupt_pin(pdev->config, pin);
 
 #ifdef CONFIG_KVM
     /*
@@ -350,7 +356,7 @@ static bool vfio_intx_enable(VFIOPCIDevice *vdev, Error **errp)
      * where we won't actually use the result anyway.
      */
     if (kvm_irqfds_enabled() && kvm_resamplefds_enabled()) {
-        vdev->intx.route = pci_device_route_intx_to_irq(&vdev->pdev,
+        vdev->intx.route = pci_device_route_intx_to_irq(pdev,
                                                         vdev->intx.pin);
     }
 #endif
@@ -390,13 +396,14 @@ skip_signaling:
 
 static void vfio_intx_disable(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int fd;
 
     timer_del(vdev->intx.mmap_timer);
     vfio_intx_disable_kvm(vdev);
     vfio_device_irq_disable(&vdev->vbasedev, VFIO_PCI_INTX_IRQ_INDEX);
     vdev->intx.pending = false;
-    pci_irq_deassert(&vdev->pdev);
+    pci_irq_deassert(pdev);
     vfio_mmap_set_enabled(vdev, true);
 
     fd = event_notifier_get_fd(&vdev->intx.interrupt);
@@ -428,6 +435,7 @@ static void vfio_msi_interrupt(void *opaque)
 {
     VFIOMSIVector *vector = opaque;
     VFIOPCIDevice *vdev = vector->vdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     MSIMessage (*get_msg)(PCIDevice *dev, unsigned vector);
     void (*notify)(PCIDevice *dev, unsigned vector);
     MSIMessage msg;
@@ -442,9 +450,9 @@ static void vfio_msi_interrupt(void *opaque)
         notify = msix_notify;
 
         /* A masked vector firing needs to use the PBA, enable it */
-        if (msix_is_masked(&vdev->pdev, nr)) {
+        if (msix_is_masked(pdev, nr)) {
             set_bit(nr, vdev->msix->pending);
-            memory_region_set_enabled(&vdev->pdev.msix_pba_mmio, true);
+            memory_region_set_enabled(&pdev->msix_pba_mmio, true);
             trace_vfio_msix_pba_enable(vdev->vbasedev.name);
         }
     } else if (vdev->interrupt == VFIO_INT_MSI) {
@@ -454,9 +462,9 @@ static void vfio_msi_interrupt(void *opaque)
         abort();
     }
 
-    msg = get_msg(&vdev->pdev, nr);
+    msg = get_msg(pdev, nr);
     trace_vfio_msi_interrupt(vdev->vbasedev.name, nr, msg.address, msg.data);
-    notify(&vdev->pdev, nr);
+    notify(pdev, nr);
 }
 
 void vfio_pci_msi_set_handler(VFIOPCIDevice *vdev, int nr, bool enable)
@@ -495,6 +503,7 @@ static int vfio_enable_msix_no_vec(VFIOPCIDevice *vdev)
 
 static int vfio_enable_vectors(VFIOPCIDevice *vdev, bool msix)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     struct vfio_irq_set *irq_set;
     int ret = 0, i, argsz;
     int32_t *fds;
@@ -537,7 +546,7 @@ static int vfio_enable_vectors(VFIOPCIDevice *vdev, bool msix)
          */
         if (vdev->msi_vectors[i].use) {
             if (vdev->msi_vectors[i].virq < 0 ||
-                (msix && msix_is_masked(&vdev->pdev, i))) {
+                (msix && msix_is_masked(pdev, i))) {
                 fd = event_notifier_get_fd(&vdev->msi_vectors[i].interrupt);
             } else {
                 fd = event_notifier_get_fd(&vdev->msi_vectors[i].kvm_interrupt);
@@ -557,12 +566,14 @@ static int vfio_enable_vectors(VFIOPCIDevice *vdev, bool msix)
 void vfio_pci_add_kvm_msi_virq(VFIOPCIDevice *vdev, VFIOMSIVector *vector,
                                int vector_n, bool msix)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
     if ((msix && vdev->no_kvm_msix) || (!msix && vdev->no_kvm_msi)) {
         return;
     }
 
     vector->virq = kvm_irqchip_add_msi_route(&vfio_route_change,
-                                             vector_n, &vdev->pdev);
+                                             vector_n, pdev);
 }
 
 static void vfio_connect_kvm_msi_virq(VFIOMSIVector *vector, int nr)
@@ -631,7 +642,7 @@ static void set_irq_signalling(VFIODevice *vbasedev, VFIOMSIVector *vector,
 void vfio_pci_vector_init(VFIOPCIDevice *vdev, int nr)
 {
     VFIOMSIVector *vector = &vdev->msi_vectors[nr];
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     Error *local_err = NULL;
 
     vector->vdev = vdev;
@@ -720,7 +731,7 @@ static int vfio_msix_vector_do_use(PCIDevice *pdev, unsigned int nr,
     clear_bit(nr, vdev->msix->pending);
     if (find_first_bit(vdev->msix->pending,
                        vdev->nr_vectors) == vdev->nr_vectors) {
-        memory_region_set_enabled(&vdev->pdev.msix_pba_mmio, false);
+        memory_region_set_enabled(&pdev->msix_pba_mmio, false);
         trace_vfio_msix_pba_disable(vdev->vbasedev.name);
     }
 
@@ -771,7 +782,9 @@ static void vfio_msix_vector_release(PCIDevice *pdev, unsigned int nr)
 
 void vfio_pci_msix_set_notifiers(VFIOPCIDevice *vdev)
 {
-    msix_set_vector_notifiers(&vdev->pdev, vfio_msix_vector_use,
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
+    msix_set_vector_notifiers(pdev, vfio_msix_vector_use,
                               vfio_msix_vector_release, NULL);
 }
 
@@ -798,6 +811,7 @@ void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev)
 
 static void vfio_msix_enable(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int ret;
 
     vfio_disable_interrupts(vdev);
@@ -814,7 +828,7 @@ static void vfio_msix_enable(VFIOPCIDevice *vdev)
      */
     vfio_pci_prepare_kvm_msi_virq_batch(vdev);
 
-    if (msix_set_vector_notifiers(&vdev->pdev, vfio_msix_vector_use,
+    if (msix_set_vector_notifiers(pdev, vfio_msix_vector_use,
                                   vfio_msix_vector_release, NULL)) {
         error_report("vfio: msix_set_vector_notifiers failed");
     }
@@ -852,11 +866,12 @@ static void vfio_msix_enable(VFIOPCIDevice *vdev)
 
 static void vfio_msi_enable(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int ret, i;
 
     vfio_disable_interrupts(vdev);
 
-    vdev->nr_vectors = msi_nr_vectors_allocated(&vdev->pdev);
+    vdev->nr_vectors = msi_nr_vectors_allocated(pdev);
 retry:
     /*
      * Setting vector notifiers needs to enable route for each vector.
@@ -949,10 +964,11 @@ static void vfio_msi_disable_common(VFIOPCIDevice *vdev)
 
 static void vfio_msix_disable(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     Error *err = NULL;
     int i;
 
-    msix_unset_vector_notifiers(&vdev->pdev);
+    msix_unset_vector_notifiers(pdev);
 
     /*
      * MSI-X will only release vectors if MSI-X is still enabled on the
@@ -960,8 +976,8 @@ static void vfio_msix_disable(VFIOPCIDevice *vdev)
      */
     for (i = 0; i < vdev->nr_vectors; i++) {
         if (vdev->msi_vectors[i].use) {
-            vfio_msix_vector_release(&vdev->pdev, i);
-            msix_vector_unuse(&vdev->pdev, i);
+            vfio_msix_vector_release(pdev, i);
+            msix_vector_unuse(pdev, i);
         }
     }
 
@@ -998,6 +1014,7 @@ static void vfio_msi_disable(VFIOPCIDevice *vdev)
 
 static void vfio_update_msi(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int i;
 
     for (i = 0; i < vdev->nr_vectors; i++) {
@@ -1008,8 +1025,8 @@ static void vfio_update_msi(VFIOPCIDevice *vdev)
             continue;
         }
 
-        msg = msi_get_message(&vdev->pdev, i);
-        vfio_update_kvm_msi_virq(vector, msg, &vdev->pdev);
+        msg = msi_get_message(pdev, i);
+        vfio_update_kvm_msi_virq(vector, msg, pdev);
     }
 }
 
@@ -1171,13 +1188,14 @@ static const MemoryRegionOps vfio_rom_ops = {
 
 static void vfio_pci_size_rom(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIODevice *vbasedev = &vdev->vbasedev;
     uint32_t orig, size = cpu_to_le32((uint32_t)PCI_ROM_ADDRESS_MASK);
     char *name;
 
-    if (vdev->pdev.romfile || !vdev->pdev.rom_bar) {
+    if (pdev->romfile || !pdev->rom_bar) {
         /* Since pci handles romfile, just print a message and return */
-        if (vfio_opt_rom_in_denylist(vdev) && vdev->pdev.romfile) {
+        if (vfio_opt_rom_in_denylist(vdev) && pdev->romfile) {
             warn_report("Device at %s is known to cause system instability"
                         " issues during option rom execution",
                         vdev->vbasedev.name);
@@ -1206,7 +1224,7 @@ static void vfio_pci_size_rom(VFIOPCIDevice *vdev)
     }
 
     if (vfio_opt_rom_in_denylist(vdev)) {
-        if (vdev->pdev.rom_bar > 0) {
+        if (pdev->rom_bar > 0) {
             warn_report("Device at %s is known to cause system instability"
                         " issues during option rom execution",
                         vdev->vbasedev.name);
@@ -1225,12 +1243,12 @@ static void vfio_pci_size_rom(VFIOPCIDevice *vdev)
 
     name = g_strdup_printf("vfio[%s].rom", vdev->vbasedev.name);
 
-    memory_region_init_io(&vdev->pdev.rom, OBJECT(vdev),
+    memory_region_init_io(&pdev->rom, OBJECT(vdev),
                           &vfio_rom_ops, vdev, name, size);
     g_free(name);
 
-    pci_register_bar(&vdev->pdev, PCI_ROM_SLOT,
-                     PCI_BASE_ADDRESS_SPACE_MEMORY, &vdev->pdev.rom);
+    pci_register_bar(pdev, PCI_ROM_SLOT,
+                     PCI_BASE_ADDRESS_SPACE_MEMORY, &pdev->rom);
 
     vdev->rom_read_failed = false;
 }
@@ -1503,6 +1521,7 @@ static void vfio_disable_interrupts(VFIOPCIDevice *vdev)
 
 static bool vfio_msi_setup(VFIOPCIDevice *vdev, int pos, Error **errp)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint16_t ctrl;
     bool msi_64bit, msi_maskbit;
     int ret, entries;
@@ -1523,7 +1542,7 @@ static bool vfio_msi_setup(VFIOPCIDevice *vdev, int pos, Error **errp)
 
     trace_vfio_msi_setup(vdev->vbasedev.name, pos);
 
-    ret = msi_init(&vdev->pdev, pos, entries, msi_64bit, msi_maskbit, &err);
+    ret = msi_init(pdev, pos, entries, msi_64bit, msi_maskbit, &err);
     if (ret < 0) {
         if (ret == -ENOTSUP) {
             return true;
@@ -1716,6 +1735,7 @@ static bool vfio_pci_relocate_msix(VFIOPCIDevice *vdev, Error **errp)
  */
 static bool vfio_msix_early_setup(VFIOPCIDevice *vdev, Error **errp)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint8_t pos;
     uint16_t ctrl;
     uint32_t table, pba;
@@ -1723,7 +1743,7 @@ static bool vfio_msix_early_setup(VFIOPCIDevice *vdev, Error **errp)
     VFIOMSIXInfo *msix;
     int ret;
 
-    pos = pci_find_capability(&vdev->pdev, PCI_CAP_ID_MSIX);
+    pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX);
     if (!pos) {
         return true;
     }
@@ -1815,12 +1835,13 @@ static bool vfio_msix_early_setup(VFIOPCIDevice *vdev, Error **errp)
 
 static bool vfio_msix_setup(VFIOPCIDevice *vdev, int pos, Error **errp)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int ret;
     Error *err = NULL;
 
     vdev->msix->pending = g_new0(unsigned long,
                                  BITS_TO_LONGS(vdev->msix->entries));
-    ret = msix_init(&vdev->pdev, vdev->msix->entries,
+    ret = msix_init(pdev, vdev->msix->entries,
                     vdev->bars[vdev->msix->table_bar].mr,
                     vdev->msix->table_bar, vdev->msix->table_offset,
                     vdev->bars[vdev->msix->pba_bar].mr,
@@ -1852,7 +1873,7 @@ static bool vfio_msix_setup(VFIOPCIDevice *vdev, int pos, Error **errp)
      * vector-use notifier is called, which occurs on unmask, we test whether
      * PBA emulation is needed and again disable if not.
      */
-    memory_region_set_enabled(&vdev->pdev.msix_pba_mmio, false);
+    memory_region_set_enabled(&pdev->msix_pba_mmio, false);
 
     /*
      * The emulated machine may provide a paravirt interface for MSIX setup
@@ -1864,7 +1885,7 @@ static bool vfio_msix_setup(VFIOPCIDevice *vdev, int pos, Error **errp)
      */
     if (object_property_get_bool(OBJECT(qdev_get_machine()),
                                  "vfio-no-msix-emulation", NULL)) {
-        memory_region_set_enabled(&vdev->pdev.msix_table_mmio, false);
+        memory_region_set_enabled(&pdev->msix_table_mmio, false);
     }
 
     return true;
@@ -1872,10 +1893,12 @@ static bool vfio_msix_setup(VFIOPCIDevice *vdev, int pos, Error **errp)
 
 void vfio_pci_teardown_msi(VFIOPCIDevice *vdev)
 {
-    msi_uninit(&vdev->pdev);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
+    msi_uninit(pdev);
 
     if (vdev->msix) {
-        msix_uninit(&vdev->pdev,
+        msix_uninit(pdev,
                     vdev->bars[vdev->msix->table_bar].mr,
                     vdev->bars[vdev->msix->pba_bar].mr);
         g_free(vdev->msix->pending);
@@ -1936,6 +1959,7 @@ static void vfio_bars_prepare(VFIOPCIDevice *vdev)
 
 static void vfio_bar_register(VFIOPCIDevice *vdev, int nr)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIOBAR *bar = &vdev->bars[nr];
     char *name;
 
@@ -1957,7 +1981,7 @@ static void vfio_bar_register(VFIOPCIDevice *vdev, int nr)
         }
     }
 
-    pci_register_bar(&vdev->pdev, nr, bar->type, bar->mr);
+    pci_register_bar(pdev, nr, bar->type, bar->mr);
 }
 
 static void vfio_bars_register(VFIOPCIDevice *vdev)
@@ -1971,6 +1995,7 @@ static void vfio_bars_register(VFIOPCIDevice *vdev)
 
 void vfio_pci_bars_exit(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int i;
 
     for (i = 0; i < PCI_ROM_SLOT; i++) {
@@ -1984,7 +2009,7 @@ void vfio_pci_bars_exit(VFIOPCIDevice *vdev)
     }
 
     if (vdev->vga) {
-        pci_unregister_vga(&vdev->pdev);
+        pci_unregister_vga(pdev);
         vfio_vga_quirk_exit(vdev);
     }
 }
@@ -2056,8 +2081,10 @@ static void vfio_set_word_bits(uint8_t *buf, uint16_t val, uint16_t mask)
 static void vfio_add_emulated_word(VFIOPCIDevice *vdev, int pos,
                                    uint16_t val, uint16_t mask)
 {
-    vfio_set_word_bits(vdev->pdev.config + pos, val, mask);
-    vfio_set_word_bits(vdev->pdev.wmask + pos, ~mask, mask);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
+    vfio_set_word_bits(pdev->config + pos, val, mask);
+    vfio_set_word_bits(pdev->wmask + pos, ~mask, mask);
     vfio_set_word_bits(vdev->emulated_config_bits + pos, mask, mask);
 }
 
@@ -2069,8 +2096,10 @@ static void vfio_set_long_bits(uint8_t *buf, uint32_t val, uint32_t mask)
 static void vfio_add_emulated_long(VFIOPCIDevice *vdev, int pos,
                                    uint32_t val, uint32_t mask)
 {
-    vfio_set_long_bits(vdev->pdev.config + pos, val, mask);
-    vfio_set_long_bits(vdev->pdev.wmask + pos, ~mask, mask);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
+    vfio_set_long_bits(pdev->config + pos, val, mask);
+    vfio_set_long_bits(pdev->wmask + pos, ~mask, mask);
     vfio_set_long_bits(vdev->emulated_config_bits + pos, mask, mask);
 }
 
@@ -2078,7 +2107,8 @@ static void vfio_pci_enable_rp_atomics(VFIOPCIDevice *vdev)
 {
     struct vfio_device_info_cap_pci_atomic_comp *cap;
     g_autofree struct vfio_device_info *info = NULL;
-    PCIBus *bus = pci_get_bus(&vdev->pdev);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+    PCIBus *bus = pci_get_bus(pdev);
     PCIDevice *parent = bus->parent_dev;
     struct vfio_info_cap_header *hdr;
     uint32_t mask = 0;
@@ -2094,8 +2124,8 @@ static void vfio_pci_enable_rp_atomics(VFIOPCIDevice *vdev)
     if (pci_bus_is_root(bus) || !parent || !parent->exp.exp_cap ||
         pcie_cap_get_type(parent) != PCI_EXP_TYPE_ROOT_PORT ||
         pcie_cap_get_version(parent) != PCI_EXP_FLAGS_VER2 ||
-        vdev->pdev.devfn ||
-        vdev->pdev.cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
+        pdev->devfn ||
+        pdev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
         return;
     }
 
@@ -2139,8 +2169,10 @@ static void vfio_pci_enable_rp_atomics(VFIOPCIDevice *vdev)
 
 static void vfio_pci_disable_rp_atomics(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+
     if (vdev->clear_parent_atomics_on_exit) {
-        PCIDevice *parent = pci_get_bus(&vdev->pdev)->parent_dev;
+        PCIDevice *parent = pci_get_bus(pdev)->parent_dev;
         uint8_t *pos = parent->config + parent->exp.exp_cap + PCI_EXP_DEVCAP2;
 
         pci_long_test_and_clear_mask(pos, PCI_EXP_DEVCAP2_ATOMIC_COMP32 |
@@ -2152,10 +2184,11 @@ static void vfio_pci_disable_rp_atomics(VFIOPCIDevice *vdev)
 static bool vfio_setup_pcie_cap(VFIOPCIDevice *vdev, int pos, uint8_t size,
                                 Error **errp)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint16_t flags;
     uint8_t type;
 
-    flags = pci_get_word(vdev->pdev.config + pos + PCI_CAP_FLAGS);
+    flags = pci_get_word(pdev->config + pos + PCI_CAP_FLAGS);
     type = (flags & PCI_EXP_FLAGS_TYPE) >> 4;
 
     if (type != PCI_EXP_TYPE_ENDPOINT &&
@@ -2167,8 +2200,8 @@ static bool vfio_setup_pcie_cap(VFIOPCIDevice *vdev, int pos, uint8_t size,
         return false;
     }
 
-    if (!pci_bus_is_express(pci_get_bus(&vdev->pdev))) {
-        PCIBus *bus = pci_get_bus(&vdev->pdev);
+    if (!pci_bus_is_express(pci_get_bus(pdev))) {
+        PCIBus *bus = pci_get_bus(pdev);
         PCIDevice *bridge;
 
         /*
@@ -2200,7 +2233,7 @@ static bool vfio_setup_pcie_cap(VFIOPCIDevice *vdev, int pos, uint8_t size,
             return true;
         }
 
-    } else if (pci_bus_is_root(pci_get_bus(&vdev->pdev))) {
+    } else if (pci_bus_is_root(pci_get_bus(pdev))) {
         /*
          * On a Root Complex bus Endpoints become Root Complex Integrated
          * Endpoints, which changes the type and clears the LNK & LNK2 fields.
@@ -2268,20 +2301,20 @@ static bool vfio_setup_pcie_cap(VFIOPCIDevice *vdev, int pos, uint8_t size,
                                1, PCI_EXP_FLAGS_VERS);
     }
 
-    pos = pci_add_capability(&vdev->pdev, PCI_CAP_ID_EXP, pos, size,
-                             errp);
+    pos = pci_add_capability(pdev, PCI_CAP_ID_EXP, pos, size, errp);
     if (pos < 0) {
         return false;
     }
 
-    vdev->pdev.exp.exp_cap = pos;
+    pdev->exp.exp_cap = pos;
 
     return true;
 }
 
 static void vfio_check_pcie_flr(VFIOPCIDevice *vdev, uint8_t pos)
 {
-    uint32_t cap = pci_get_long(vdev->pdev.config + pos + PCI_EXP_DEVCAP);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+    uint32_t cap = pci_get_long(pdev->config + pos + PCI_EXP_DEVCAP);
 
     if (cap & PCI_EXP_DEVCAP_FLR) {
         trace_vfio_check_pcie_flr(vdev->vbasedev.name);
@@ -2291,7 +2324,8 @@ static void vfio_check_pcie_flr(VFIOPCIDevice *vdev, uint8_t pos)
 
 static void vfio_check_pm_reset(VFIOPCIDevice *vdev, uint8_t pos)
 {
-    uint16_t csr = pci_get_word(vdev->pdev.config + pos + PCI_PM_CTRL);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+    uint16_t csr = pci_get_word(pdev->config + pos + PCI_PM_CTRL);
 
     if (!(csr & PCI_PM_CTRL_NO_SOFT_RESET)) {
         trace_vfio_check_pm_reset(vdev->vbasedev.name);
@@ -2301,7 +2335,8 @@ static void vfio_check_pm_reset(VFIOPCIDevice *vdev, uint8_t pos)
 
 static void vfio_check_af_flr(VFIOPCIDevice *vdev, uint8_t pos)
 {
-    uint8_t cap = pci_get_byte(vdev->pdev.config + pos + PCI_AF_CAP);
+    PCIDevice *pdev = PCI_DEVICE(vdev);
+    uint8_t cap = pci_get_byte(pdev->config + pos + PCI_AF_CAP);
 
     if ((cap & PCI_AF_CAP_TP) && (cap & PCI_AF_CAP_FLR)) {
         trace_vfio_check_af_flr(vdev->vbasedev.name);
@@ -2312,7 +2347,7 @@ static void vfio_check_af_flr(VFIOPCIDevice *vdev, uint8_t pos)
 static bool vfio_add_vendor_specific_cap(VFIOPCIDevice *vdev, int pos,
                                          uint8_t size, Error **errp)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     pos = pci_add_capability(pdev, PCI_CAP_ID_VNDR, pos, size, errp);
     if (pos < 0) {
@@ -2334,7 +2369,7 @@ static bool vfio_add_vendor_specific_cap(VFIOPCIDevice *vdev, int pos,
 static bool vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos, Error **errp)
 {
     ERRP_GUARD();
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint8_t cap_id, next, size;
     bool ret;
 
@@ -2420,17 +2455,18 @@ static bool vfio_add_std_cap(VFIOPCIDevice *vdev, uint8_t pos, Error **errp)
 
 static int vfio_setup_rebar_ecap(VFIOPCIDevice *vdev, uint16_t pos)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint32_t ctrl;
     int i, nbar;
 
-    ctrl = pci_get_long(vdev->pdev.config + pos + PCI_REBAR_CTRL);
+    ctrl = pci_get_long(pdev->config + pos + PCI_REBAR_CTRL);
     nbar = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> PCI_REBAR_CTRL_NBAR_SHIFT;
 
     for (i = 0; i < nbar; i++) {
         uint32_t cap;
         int size;
 
-        ctrl = pci_get_long(vdev->pdev.config + pos + PCI_REBAR_CTRL + (i * 8));
+        ctrl = pci_get_long(pdev->config + pos + PCI_REBAR_CTRL + (i * 8));
         size = (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT;
 
         /* The cap register reports sizes 1MB to 128TB, with 4 reserved bits */
@@ -2468,7 +2504,7 @@ static int vfio_setup_rebar_ecap(VFIOPCIDevice *vdev, uint16_t pos)
 
 static void vfio_add_ext_cap(VFIOPCIDevice *vdev)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint32_t header;
     uint16_t cap_id, next, size;
     uint8_t cap_ver;
@@ -2562,7 +2598,7 @@ static void vfio_add_ext_cap(VFIOPCIDevice *vdev)
 
 bool vfio_pci_add_capabilities(VFIOPCIDevice *vdev, Error **errp)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     if (!(pdev->config[PCI_STATUS] & PCI_STATUS_CAP_LIST) ||
         !pdev->config[PCI_CAPABILITY_LIST]) {
@@ -2579,7 +2615,7 @@ bool vfio_pci_add_capabilities(VFIOPCIDevice *vdev, Error **errp)
 
 void vfio_pci_pre_reset(VFIOPCIDevice *vdev)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     uint16_t cmd;
 
     vfio_disable_interrupts(vdev);
@@ -2775,8 +2811,8 @@ static const VMStateDescription vmstate_vfio_pci_config = {
     .version_id = 1,
     .minimum_version_id = 1,
     .fields = (const VMStateField[]) {
-        VMSTATE_PCI_DEVICE(pdev, VFIOPCIDevice),
-        VMSTATE_MSIX_TEST(pdev, VFIOPCIDevice, vfio_msix_present),
+        VMSTATE_PCI_DEVICE(parent_obj, VFIOPCIDevice),
+        VMSTATE_MSIX_TEST(parent_obj, VFIOPCIDevice, vfio_msix_present),
         VMSTATE_END_OF_LIST()
     },
     .subsections = (const VMStateDescription * const []) {
@@ -2796,7 +2832,7 @@ static int vfio_pci_save_config(VFIODevice *vbasedev, QEMUFile *f, Error **errp)
 static int vfio_pci_load_config(VFIODevice *vbasedev, QEMUFile *f)
 {
     VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     pcibus_t old_addr[PCI_NUM_REGIONS - 1];
     int bar, ret;
 
@@ -2833,9 +2869,18 @@ static int vfio_pci_load_config(VFIODevice *vbasedev, QEMUFile *f)
     return ret;
 }
 
+/* Transform from VFIODevice to VFIOPCIDevice. Return NULL if fails. */
+VFIOPCIDevice *vfio_pci_from_vfio_device(VFIODevice *vbasedev)
+{
+    if (vbasedev && vbasedev->type == VFIO_DEVICE_TYPE_PCI) {
+        return container_of(vbasedev, VFIOPCIDevice, vbasedev);
+    }
+    return NULL;
+}
+
 void vfio_sub_page_bar_update_mappings(VFIOPCIDevice *vdev)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     int page_size = qemu_real_host_page_size();
     int bar;
 
@@ -2919,6 +2964,7 @@ bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp)
 
 bool vfio_pci_populate_device(VFIOPCIDevice *vdev, Error **errp)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIODevice *vbasedev = &vdev->vbasedev;
     struct vfio_region_info *reg_info = NULL;
     struct vfio_irq_info irq_info;
@@ -2970,7 +3016,7 @@ bool vfio_pci_populate_device(VFIOPCIDevice *vdev, Error **errp)
 
     vdev->config_size = reg_info->size;
     if (vdev->config_size == PCI_CONFIG_SPACE_SIZE) {
-        vdev->pdev.cap_present &= ~QEMU_PCI_CAP_EXPRESS;
+        pdev->cap_present &= ~QEMU_PCI_CAP_EXPRESS;
     }
     vdev->config_offset = reg_info->offset;
 
@@ -3174,25 +3220,26 @@ static void vfio_unregister_req_notifier(VFIOPCIDevice *vdev)
 
 void vfio_pci_config_register_vga(VFIOPCIDevice *vdev)
 {
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     assert(vdev->vga != NULL);
 
-    pci_register_vga(&vdev->pdev, &vdev->vga->region[QEMU_PCI_VGA_MEM].mem,
+    pci_register_vga(pdev, &vdev->vga->region[QEMU_PCI_VGA_MEM].mem,
                      &vdev->vga->region[QEMU_PCI_VGA_IO_LO].mem,
                      &vdev->vga->region[QEMU_PCI_VGA_IO_HI].mem);
 }
 
 bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
     VFIODevice *vbasedev = &vdev->vbasedev;
     uint32_t config_space_size;
     int ret;
 
-    config_space_size = MIN(pci_config_size(&vdev->pdev), vdev->config_size);
+    config_space_size = MIN(pci_config_size(pdev), vdev->config_size);
 
     /* Get a copy of config space */
     ret = vfio_pci_config_space_read(vdev, 0, config_space_size,
-                                     vdev->pdev.config);
+                                     pdev->config);
     if (ret < (int)config_space_size) {
         ret = ret < 0 ? -ret : EFAULT;
         error_setg_errno(errp, ret, "failed to read device config space");
@@ -3277,10 +3324,10 @@ bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp)
                                               PCI_HEADER_TYPE_MULTI_FUNCTION;
 
     /* Restore or clear multifunction, this is always controlled by QEMU */
-    if (vdev->pdev.cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
-        vdev->pdev.config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION;
+    if (pdev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION) {
+        pdev->config[PCI_HEADER_TYPE] |= PCI_HEADER_TYPE_MULTI_FUNCTION;
     } else {
-        vdev->pdev.config[PCI_HEADER_TYPE] &= ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+        pdev->config[PCI_HEADER_TYPE] &= ~PCI_HEADER_TYPE_MULTI_FUNCTION;
     }
 
     /*
@@ -3288,8 +3335,8 @@ bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp)
      * BAR, such as might be the case with the option ROM, we can get
      * confusing, unwritable, residual addresses from the host here.
      */
-    memset(&vdev->pdev.config[PCI_BASE_ADDRESS_0], 0, 24);
-    memset(&vdev->pdev.config[PCI_ROM_ADDRESS], 0, 4);
+    memset(&pdev->config[PCI_BASE_ADDRESS_0], 0, 24);
+    memset(&pdev->config[PCI_ROM_ADDRESS], 0, 4);
 
     vfio_pci_size_rom(vdev);
 
@@ -3310,7 +3357,7 @@ bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp)
 
 bool vfio_pci_interrupt_setup(VFIOPCIDevice *vdev, Error **errp)
 {
-    PCIDevice *pdev = &vdev->pdev;
+    PCIDevice *pdev = PCI_DEVICE(vdev);
 
     /* QEMU emulates all of MSI & MSIX */
     if (pdev->cap_present & QEMU_PCI_CAP_MSIX) {
@@ -3323,10 +3370,10 @@ bool vfio_pci_interrupt_setup(VFIOPCIDevice *vdev, Error **errp)
                vdev->msi_cap_size);
     }
 
-    if (vfio_pci_read_config(&vdev->pdev, PCI_INTERRUPT_PIN, 1)) {
+    if (vfio_pci_read_config(pdev, PCI_INTERRUPT_PIN, 1)) {
         vdev->intx.mmap_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
                                              vfio_intx_mmap_enable, vdev);
-        pci_device_set_intx_routing_notifier(&vdev->pdev,
+        pci_device_set_intx_routing_notifier(pdev,
                                              vfio_intx_routing_notifier);
         vdev->irqchip_change_notifier.notify = vfio_irqchip_change;
         kvm_irqchip_add_change_notifier(&vdev->irqchip_change_notifier);
@@ -3338,7 +3385,7 @@ bool vfio_pci_interrupt_setup(VFIOPCIDevice *vdev, Error **errp)
          */
         if (!cpr_is_incoming() && !vfio_intx_enable(vdev, errp)) {
             timer_free(vdev->intx.mmap_timer);
-            pci_device_set_intx_routing_notifier(&vdev->pdev, NULL);
+            pci_device_set_intx_routing_notifier(pdev, NULL);
             kvm_irqchip_remove_change_notifier(&vdev->irqchip_change_notifier);
             return false;
         }
@@ -3489,7 +3536,7 @@ out_deregister:
     if (vdev->interrupt == VFIO_INT_INTx) {
         vfio_intx_disable(vdev);
     }
-    pci_device_set_intx_routing_notifier(&vdev->pdev, NULL);
+    pci_device_set_intx_routing_notifier(pdev, NULL);
     if (vdev->irqchip_change_notifier.notify) {
         kvm_irqchip_remove_change_notifier(&vdev->irqchip_change_notifier);
     }
@@ -3521,7 +3568,7 @@ static void vfio_exitfn(PCIDevice *pdev)
 
     vfio_unregister_req_notifier(vdev);
     vfio_unregister_err_notifier(vdev);
-    pci_device_set_intx_routing_notifier(&vdev->pdev, NULL);
+    pci_device_set_intx_routing_notifier(pdev, NULL);
     if (vdev->irqchip_change_notifier.notify) {
         kvm_irqchip_remove_change_notifier(&vdev->irqchip_change_notifier);
     }
diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h
index 810a842f4a..e0aef82a89 100644
--- a/hw/vfio/pci.h
+++ b/hw/vfio/pci.h
@@ -123,7 +123,8 @@ typedef struct VFIOMSIXInfo {
 OBJECT_DECLARE_SIMPLE_TYPE(VFIOPCIDevice, VFIO_PCI_BASE)
 
 struct VFIOPCIDevice {
-    PCIDevice pdev;
+    PCIDevice parent_obj;
+
     VFIODevice vbasedev;
     VFIOINTx intx;
     unsigned int config_size;
@@ -203,6 +204,11 @@ static inline bool vfio_is_vga(VFIOPCIDevice *vdev)
     return (vdev->class_code >> 8) == PCI_CLASS_DISPLAY_VGA;
 }
 
+static inline bool vfio_is_base_display(VFIOPCIDevice *vdev)
+{
+    return (vdev->class_code >> 16) == PCI_BASE_CLASS_DISPLAY;
+}
+
 /* MSI/MSI-X/INTx */
 void vfio_pci_vector_init(VFIOPCIDevice *vdev, int nr);
 void vfio_pci_add_kvm_msi_virq(VFIOPCIDevice *vdev, VFIOMSIVector *vector,
@@ -221,6 +227,18 @@ void vfio_pci_write_config(PCIDevice *pdev,
 uint64_t vfio_vga_read(void *opaque, hwaddr addr, unsigned size);
 void vfio_vga_write(void *opaque, hwaddr addr, uint64_t data, unsigned size);
 
+/**
+ * vfio_pci_from_vfio_device: Transform from VFIODevice to
+ * VFIOPCIDevice
+ *
+ * This function checks if the given @vbasedev is a VFIO PCI device.
+ * If it is, it returns the containing VFIOPCIDevice.
+ *
+ * @vbasedev: The VFIODevice to transform
+ *
+ * Return: The VFIOPCIDevice on success, NULL on failure.
+ */
+VFIOPCIDevice *vfio_pci_from_vfio_device(VFIODevice *vbasedev);
 void vfio_sub_page_bar_update_mappings(VFIOPCIDevice *vdev);
 bool vfio_opt_rom_in_denylist(VFIOPCIDevice *vdev);
 bool vfio_config_quirk_setup(VFIOPCIDevice *vdev, Error **errp);
diff --git a/hw/vfio/platform.c b/hw/vfio/platform.c
deleted file mode 100644
index 5c1795a26f..0000000000
--- a/hw/vfio/platform.c
+++ /dev/null
@@ -1,716 +0,0 @@
-/*
- * vfio based device assignment support - platform devices
- *
- * Copyright Linaro Limited, 2014
- *
- * Authors:
- *  Kim Phillips <kim.phillips@linaro.org>
- *  Eric Auger <eric.auger@linaro.org>
- *
- * This work is licensed under the terms of the GNU GPL, version 2.  See
- * the COPYING file in the top-level directory.
- *
- * Based on vfio based PCI device assignment support:
- *  Copyright Red Hat, Inc. 2012
- */
-
-#include "qemu/osdep.h"
-#include CONFIG_DEVICES /* CONFIG_IOMMUFD */
-#include "qapi/error.h"
-#include <sys/ioctl.h>
-#include <linux/vfio.h>
-
-#include "hw/vfio/vfio-platform.h"
-#include "system/iommufd.h"
-#include "migration/vmstate.h"
-#include "qemu/error-report.h"
-#include "qemu/lockable.h"
-#include "qemu/main-loop.h"
-#include "qemu/module.h"
-#include "qemu/range.h"
-#include "system/memory.h"
-#include "system/address-spaces.h"
-#include "qemu/queue.h"
-#include "hw/sysbus.h"
-#include "trace.h"
-#include "hw/irq.h"
-#include "hw/platform-bus.h"
-#include "hw/qdev-properties.h"
-#include "system/kvm.h"
-#include "hw/vfio/vfio-region.h"
-
-/*
- * Functions used whatever the injection method
- */
-
-static inline bool vfio_irq_is_automasked(VFIOINTp *intp)
-{
-    return intp->flags & VFIO_IRQ_INFO_AUTOMASKED;
-}
-
-/**
- * vfio_init_intp - allocate, initialize the IRQ struct pointer
- * and add it into the list of IRQs
- * @vbasedev: the VFIO device handle
- * @info: irq info struct retrieved from VFIO driver
- * @errp: error object
- */
-static VFIOINTp *vfio_init_intp(VFIODevice *vbasedev,
-                                struct vfio_irq_info info, Error **errp)
-{
-    int ret;
-    VFIOPlatformDevice *vdev =
-        container_of(vbasedev, VFIOPlatformDevice, vbasedev);
-    SysBusDevice *sbdev = SYS_BUS_DEVICE(vdev);
-    VFIOINTp *intp;
-
-    intp = g_malloc0(sizeof(*intp));
-    intp->vdev = vdev;
-    intp->pin = info.index;
-    intp->flags = info.flags;
-    intp->state = VFIO_IRQ_INACTIVE;
-    intp->kvm_accel = false;
-
-    sysbus_init_irq(sbdev, &intp->qemuirq);
-
-    /* Get an eventfd for trigger */
-    intp->interrupt = g_new0(EventNotifier, 1);
-    ret = event_notifier_init(intp->interrupt, 0);
-    if (ret) {
-        g_free(intp->interrupt);
-        g_free(intp);
-        error_setg_errno(errp, -ret,
-                         "failed to initialize trigger eventfd notifier");
-        return NULL;
-    }
-    if (vfio_irq_is_automasked(intp)) {
-        /* Get an eventfd for resample/unmask */
-        intp->unmask = g_new0(EventNotifier, 1);
-        ret = event_notifier_init(intp->unmask, 0);
-        if (ret) {
-            g_free(intp->interrupt);
-            g_free(intp->unmask);
-            g_free(intp);
-            error_setg_errno(errp, -ret,
-                             "failed to initialize resample eventfd notifier");
-            return NULL;
-        }
-    }
-
-    QLIST_INSERT_HEAD(&vdev->intp_list, intp, next);
-    return intp;
-}
-
-/**
- * vfio_set_trigger_eventfd - set VFIO eventfd handling
- *
- * @intp: IRQ struct handle
- * @handler: handler to be called on eventfd signaling
- *
- * Setup VFIO signaling and attach an optional user-side handler
- * to the eventfd
- */
-static int vfio_set_trigger_eventfd(VFIOINTp *intp,
-                                    eventfd_user_side_handler_t handler)
-{
-    VFIODevice *vbasedev = &intp->vdev->vbasedev;
-    int32_t fd = event_notifier_get_fd(intp->interrupt);
-    Error *err = NULL;
-
-    qemu_set_fd_handler(fd, (IOHandler *)handler, NULL, intp);
-
-    if (!vfio_device_irq_set_signaling(vbasedev, intp->pin, 0,
-                                       VFIO_IRQ_SET_ACTION_TRIGGER, fd, &err)) {
-        error_reportf_err(err, VFIO_MSG_PREFIX, vbasedev->name);
-        qemu_set_fd_handler(fd, NULL, NULL, NULL);
-        return -EINVAL;
-    }
-
-    return 0;
-}
-
-/*
- * Functions only used when eventfds are handled on user-side
- * ie. without irqfd
- */
-
-/**
- * vfio_mmap_set_enabled - enable/disable the fast path mode
- * @vdev: the VFIO platform device
- * @enabled: the target mmap state
- *
- * enabled = true ~ fast path = MMIO region is mmaped (no KVM TRAP);
- * enabled = false ~ slow path = MMIO region is trapped and region callbacks
- * are called; slow path enables to trap the device IRQ status register reset
-*/
-
-static void vfio_mmap_set_enabled(VFIOPlatformDevice *vdev, bool enabled)
-{
-    int i;
-
-    for (i = 0; i < vdev->vbasedev.num_regions; i++) {
-        vfio_region_mmaps_set_enabled(vdev->regions[i], enabled);
-    }
-}
-
-/**
- * vfio_intp_mmap_enable - timer function, restores the fast path
- * if there is no more active IRQ
- * @opaque: actually points to the VFIO platform device
- *
- * Called on mmap timer timeout, this function checks whether the
- * IRQ is still active and if not, restores the fast path.
- * by construction a single eventfd is handled at a time.
- * if the IRQ is still active, the timer is re-programmed.
- */
-static void vfio_intp_mmap_enable(void *opaque)
-{
-    VFIOINTp *tmp;
-    VFIOPlatformDevice *vdev = (VFIOPlatformDevice *)opaque;
-
-    QEMU_LOCK_GUARD(&vdev->intp_mutex);
-    QLIST_FOREACH(tmp, &vdev->intp_list, next) {
-        if (tmp->state == VFIO_IRQ_ACTIVE) {
-            trace_vfio_platform_intp_mmap_enable(tmp->pin);
-            /* re-program the timer to check active status later */
-            timer_mod(vdev->mmap_timer,
-                      qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
-                          vdev->mmap_timeout);
-            return;
-        }
-    }
-    vfio_mmap_set_enabled(vdev, true);
-}
-
-/**
- * vfio_intp_inject_pending_lockheld - Injects a pending IRQ
- * @opaque: opaque pointer, in practice the VFIOINTp handle
- *
- * The function is called on a previous IRQ completion, from
- * vfio_platform_eoi, while the intp_mutex is locked.
- * Also in such situation, the slow path already is set and
- * the mmap timer was already programmed.
- */
-static void vfio_intp_inject_pending_lockheld(VFIOINTp *intp)
-{
-    trace_vfio_platform_intp_inject_pending_lockheld(intp->pin,
-                              event_notifier_get_fd(intp->interrupt));
-
-    intp->state = VFIO_IRQ_ACTIVE;
-
-    /* trigger the virtual IRQ */
-    qemu_set_irq(intp->qemuirq, 1);
-}
-
-/**
- * vfio_intp_interrupt - The user-side eventfd handler
- * @opaque: opaque pointer which in practice is the VFIOINTp handle
- *
- * the function is entered in event handler context:
- * the vIRQ is injected into the guest if there is no other active
- * or pending IRQ.
- */
-static void vfio_intp_interrupt(VFIOINTp *intp)
-{
-    int ret;
-    VFIOINTp *tmp;
-    VFIOPlatformDevice *vdev = intp->vdev;
-    bool delay_handling = false;
-
-    QEMU_LOCK_GUARD(&vdev->intp_mutex);
-    if (intp->state == VFIO_IRQ_INACTIVE) {
-        QLIST_FOREACH(tmp, &vdev->intp_list, next) {
-            if (tmp->state == VFIO_IRQ_ACTIVE ||
-                tmp->state == VFIO_IRQ_PENDING) {
-                delay_handling = true;
-                break;
-            }
-        }
-    }
-    if (delay_handling) {
-        /*
-         * the new IRQ gets a pending status and is pushed in
-         * the pending queue
-         */
-        intp->state = VFIO_IRQ_PENDING;
-        trace_vfio_intp_interrupt_set_pending(intp->pin);
-        QSIMPLEQ_INSERT_TAIL(&vdev->pending_intp_queue,
-                             intp, pqnext);
-        event_notifier_test_and_clear(intp->interrupt);
-        return;
-    }
-
-    trace_vfio_platform_intp_interrupt(intp->pin,
-                              event_notifier_get_fd(intp->interrupt));
-
-    ret = event_notifier_test_and_clear(intp->interrupt);
-    if (!ret) {
-        error_report("Error when clearing fd=%d (ret = %d)",
-                     event_notifier_get_fd(intp->interrupt), ret);
-    }
-
-    intp->state = VFIO_IRQ_ACTIVE;
-
-    /* sets slow path */
-    vfio_mmap_set_enabled(vdev, false);
-
-    /* trigger the virtual IRQ */
-    qemu_set_irq(intp->qemuirq, 1);
-
-    /*
-     * Schedule the mmap timer which will restore fastpath when no IRQ
-     * is active anymore
-     */
-    if (vdev->mmap_timeout) {
-        timer_mod(vdev->mmap_timer,
-                  qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
-                      vdev->mmap_timeout);
-    }
-}
-
-/**
- * vfio_platform_eoi - IRQ completion routine
- * @vbasedev: the VFIO device handle
- *
- * De-asserts the active virtual IRQ and unmasks the physical IRQ
- * (effective for level sensitive IRQ auto-masked by the  VFIO driver).
- * Then it handles next pending IRQ if any.
- * eoi function is called on the first access to any MMIO region
- * after an IRQ was triggered, trapped since slow path was set.
- * It is assumed this access corresponds to the IRQ status
- * register reset. With such a mechanism, a single IRQ can be
- * handled at a time since there is no way to know which IRQ
- * was completed by the guest (we would need additional details
- * about the IRQ status register mask).
- */
-static void vfio_platform_eoi(VFIODevice *vbasedev)
-{
-    VFIOINTp *intp;
-    VFIOPlatformDevice *vdev =
-        container_of(vbasedev, VFIOPlatformDevice, vbasedev);
-
-    QEMU_LOCK_GUARD(&vdev->intp_mutex);
-    QLIST_FOREACH(intp, &vdev->intp_list, next) {
-        if (intp->state == VFIO_IRQ_ACTIVE) {
-            trace_vfio_platform_eoi(intp->pin,
-                                event_notifier_get_fd(intp->interrupt));
-            intp->state = VFIO_IRQ_INACTIVE;
-
-            /* deassert the virtual IRQ */
-            qemu_set_irq(intp->qemuirq, 0);
-
-            if (vfio_irq_is_automasked(intp)) {
-                /* unmasks the physical level-sensitive IRQ */
-                vfio_device_irq_unmask(vbasedev, intp->pin);
-            }
-
-            /* a single IRQ can be active at a time */
-            break;
-        }
-    }
-    /* in case there are pending IRQs, handle the first one */
-    if (!QSIMPLEQ_EMPTY(&vdev->pending_intp_queue)) {
-        intp = QSIMPLEQ_FIRST(&vdev->pending_intp_queue);
-        vfio_intp_inject_pending_lockheld(intp);
-        QSIMPLEQ_REMOVE_HEAD(&vdev->pending_intp_queue, pqnext);
-    }
-}
-
-/**
- * vfio_start_eventfd_injection - starts the virtual IRQ injection using
- * user-side handled eventfds
- * @sbdev: the sysbus device handle
- * @irq: the qemu irq handle
- */
-
-static void vfio_start_eventfd_injection(SysBusDevice *sbdev, qemu_irq irq)
-{
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
-    VFIOINTp *intp;
-
-    QLIST_FOREACH(intp, &vdev->intp_list, next) {
-        if (intp->qemuirq == irq) {
-            break;
-        }
-    }
-    assert(intp);
-
-    if (vfio_set_trigger_eventfd(intp, vfio_intp_interrupt)) {
-        abort();
-    }
-}
-
-/*
- * Functions used for irqfd
- */
-
-/**
- * vfio_set_resample_eventfd - sets the resamplefd for an IRQ
- * @intp: the IRQ struct handle
- * programs the VFIO driver to unmask this IRQ when the
- * intp->unmask eventfd is triggered
- */
-static int vfio_set_resample_eventfd(VFIOINTp *intp)
-{
-    int32_t fd = event_notifier_get_fd(intp->unmask);
-    VFIODevice *vbasedev = &intp->vdev->vbasedev;
-    Error *err = NULL;
-
-    qemu_set_fd_handler(fd, NULL, NULL, NULL);
-    if (!vfio_device_irq_set_signaling(vbasedev, intp->pin, 0,
-                                       VFIO_IRQ_SET_ACTION_UNMASK, fd, &err)) {
-        error_reportf_err(err, VFIO_MSG_PREFIX, vbasedev->name);
-        return -EINVAL;
-    }
-    return 0;
-}
-
-/**
- * vfio_start_irqfd_injection - starts the virtual IRQ injection using
- * irqfd
- *
- * @sbdev: the sysbus device handle
- * @irq: the qemu irq handle
- *
- * In case the irqfd setup fails, we fallback to userspace handled eventfd
- */
-static void vfio_start_irqfd_injection(SysBusDevice *sbdev, qemu_irq irq)
-{
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev);
-    VFIOINTp *intp;
-
-    if (!kvm_irqfds_enabled() || !kvm_resamplefds_enabled() ||
-        !vdev->irqfd_allowed) {
-        goto fail_irqfd;
-    }
-
-    QLIST_FOREACH(intp, &vdev->intp_list, next) {
-        if (intp->qemuirq == irq) {
-            break;
-        }
-    }
-    assert(intp);
-
-    if (kvm_irqchip_add_irqfd_notifier(kvm_state, intp->interrupt,
-                                   intp->unmask, irq) < 0) {
-        goto fail_irqfd;
-    }
-
-    if (vfio_set_trigger_eventfd(intp, NULL) < 0) {
-        goto fail_vfio;
-    }
-    if (vfio_irq_is_automasked(intp)) {
-        if (vfio_set_resample_eventfd(intp) < 0) {
-            goto fail_vfio;
-        }
-        trace_vfio_platform_start_level_irqfd_injection(intp->pin,
-                                    event_notifier_get_fd(intp->interrupt),
-                                    event_notifier_get_fd(intp->unmask));
-    } else {
-        trace_vfio_platform_start_edge_irqfd_injection(intp->pin,
-                                    event_notifier_get_fd(intp->interrupt));
-    }
-
-    intp->kvm_accel = true;
-
-    return;
-fail_vfio:
-    kvm_irqchip_remove_irqfd_notifier(kvm_state, intp->interrupt, irq);
-    abort();
-fail_irqfd:
-    vfio_start_eventfd_injection(sbdev, irq);
-}
-
-/* VFIO skeleton */
-
-static void vfio_platform_compute_needs_reset(VFIODevice *vbasedev)
-{
-    vbasedev->needs_reset = true;
-}
-
-/* not implemented yet */
-static int vfio_platform_hot_reset_multi(VFIODevice *vbasedev)
-{
-    return -1;
-}
-
-/**
- * vfio_populate_device - Allocate and populate MMIO region
- * and IRQ structs according to driver returned information
- * @vbasedev: the VFIO device handle
- * @errp: error object
- *
- */
-static bool vfio_populate_device(VFIODevice *vbasedev, Error **errp)
-{
-    VFIOINTp *intp, *tmp;
-    int i, ret = -1;
-    VFIOPlatformDevice *vdev =
-        container_of(vbasedev, VFIOPlatformDevice, vbasedev);
-
-    if (!(vbasedev->flags & VFIO_DEVICE_FLAGS_PLATFORM)) {
-        error_setg(errp, "this isn't a platform device");
-        return false;
-    }
-
-    vdev->regions = g_new0(VFIORegion *, vbasedev->num_regions);
-
-    for (i = 0; i < vbasedev->num_regions; i++) {
-        char *name = g_strdup_printf("VFIO %s region %d\n", vbasedev->name, i);
-
-        vdev->regions[i] = g_new0(VFIORegion, 1);
-        ret = vfio_region_setup(OBJECT(vdev), vbasedev,
-                                vdev->regions[i], i, name);
-        g_free(name);
-        if (ret) {
-            error_setg_errno(errp, -ret, "failed to get region %d info", i);
-            goto reg_error;
-        }
-    }
-
-    vdev->mmap_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
-                                    vfio_intp_mmap_enable, vdev);
-
-    QSIMPLEQ_INIT(&vdev->pending_intp_queue);
-
-    for (i = 0; i < vbasedev->num_irqs; i++) {
-        struct vfio_irq_info irq;
-
-        ret = vfio_device_get_irq_info(vbasedev, i, &irq);
-
-        if (ret) {
-            error_setg_errno(errp, -ret, "failed to get device irq info");
-            goto irq_err;
-        } else {
-            trace_vfio_platform_populate_interrupts(irq.index,
-                                                    irq.count,
-                                                    irq.flags);
-            intp = vfio_init_intp(vbasedev, irq, errp);
-            if (!intp) {
-                goto irq_err;
-            }
-        }
-    }
-    return true;
-irq_err:
-    timer_del(vdev->mmap_timer);
-    QLIST_FOREACH_SAFE(intp, &vdev->intp_list, next, tmp) {
-        QLIST_REMOVE(intp, next);
-        g_free(intp);
-    }
-reg_error:
-    for (i = 0; i < vbasedev->num_regions; i++) {
-        if (vdev->regions[i]) {
-            vfio_region_finalize(vdev->regions[i]);
-        }
-        g_free(vdev->regions[i]);
-    }
-    g_free(vdev->regions);
-    return false;
-}
-
-/* specialized functions for VFIO Platform devices */
-static VFIODeviceOps vfio_platform_ops = {
-    .vfio_compute_needs_reset = vfio_platform_compute_needs_reset,
-    .vfio_hot_reset_multi = vfio_platform_hot_reset_multi,
-    .vfio_eoi = vfio_platform_eoi,
-};
-
-/**
- * vfio_base_device_init - perform preliminary VFIO setup
- * @vbasedev: the VFIO device handle
- * @errp: error object
- *
- * Implement the VFIO command sequence that allows to discover
- * assigned device resources: group extraction, device
- * fd retrieval, resource query.
- * Precondition: the device name must be initialized
- */
-static bool vfio_base_device_init(VFIODevice *vbasedev, Error **errp)
-{
-    /* @fd takes precedence over @sysfsdev which takes precedence over @host */
-    if (vbasedev->fd < 0 && vbasedev->sysfsdev) {
-        vfio_device_free_name(vbasedev);
-        vbasedev->name = g_path_get_basename(vbasedev->sysfsdev);
-    } else if (vbasedev->fd < 0) {
-        if (!vbasedev->name || strchr(vbasedev->name, '/')) {
-            error_setg(errp, "wrong host device name");
-            return false;
-        }
-
-        vbasedev->sysfsdev = g_strdup_printf("/sys/bus/platform/devices/%s",
-                                             vbasedev->name);
-    }
-
-    if (!vfio_device_get_name(vbasedev, errp)) {
-        return false;
-    }
-
-    if (!vfio_device_attach(vbasedev->name, vbasedev,
-                            &address_space_memory, errp)) {
-        return false;
-    }
-
-    if (vfio_populate_device(vbasedev, errp)) {
-        return true;
-    }
-
-    vfio_device_detach(vbasedev);
-    return false;
-}
-
-/**
- * vfio_platform_realize  - the device realize function
- * @dev: device state pointer
- * @errp: error
- *
- * initialize the device, its memory regions and IRQ structures
- * IRQ are started separately
- */
-static void vfio_platform_realize(DeviceState *dev, Error **errp)
-{
-    ERRP_GUARD();
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(dev);
-    SysBusDevice *sbdev = SYS_BUS_DEVICE(dev);
-    VFIODevice *vbasedev = &vdev->vbasedev;
-    int i;
-
-    warn_report("-device vfio-platform is deprecated");
-    qemu_mutex_init(&vdev->intp_mutex);
-
-    trace_vfio_platform_realize(vbasedev->sysfsdev ?
-                                vbasedev->sysfsdev : vbasedev->name,
-                                vdev->compat);
-
-    if (!vfio_base_device_init(vbasedev, errp)) {
-        goto init_err;
-    }
-
-    if (!vdev->compat) {
-        GError *gerr = NULL;
-        gchar *contents;
-        gsize length;
-        char *path;
-
-        path = g_strdup_printf("%s/of_node/compatible", vbasedev->sysfsdev);
-        if (!g_file_get_contents(path, &contents, &length, &gerr)) {
-            error_setg(errp, "%s", gerr->message);
-            g_error_free(gerr);
-            g_free(path);
-            return;
-        }
-        g_free(path);
-        vdev->compat = contents;
-        for (vdev->num_compat = 0; length; vdev->num_compat++) {
-            size_t skip = strlen(contents) + 1;
-            contents += skip;
-            length -= skip;
-        }
-    }
-
-    for (i = 0; i < vbasedev->num_regions; i++) {
-        if (vfio_region_mmap(vdev->regions[i])) {
-            warn_report("%s mmap unsupported, performance may be slow",
-                        memory_region_name(vdev->regions[i]->mem));
-        }
-        sysbus_init_mmio(sbdev, vdev->regions[i]->mem);
-    }
-    return;
-
-init_err:
-    if (vdev->vbasedev.name) {
-        error_prepend(errp, VFIO_MSG_PREFIX, vdev->vbasedev.name);
-    } else {
-        error_prepend(errp, "vfio error: ");
-    }
-}
-
-static const VMStateDescription vfio_platform_vmstate = {
-    .name = "vfio-platform",
-    .unmigratable = 1,
-};
-
-static const Property vfio_platform_dev_properties[] = {
-    DEFINE_PROP_STRING("host", VFIOPlatformDevice, vbasedev.name),
-    DEFINE_PROP_STRING("sysfsdev", VFIOPlatformDevice, vbasedev.sysfsdev),
-    DEFINE_PROP_BOOL("x-no-mmap", VFIOPlatformDevice, vbasedev.no_mmap, false),
-    DEFINE_PROP_UINT32("mmap-timeout-ms", VFIOPlatformDevice,
-                       mmap_timeout, 1100),
-    DEFINE_PROP_BOOL("x-irqfd", VFIOPlatformDevice, irqfd_allowed, true),
-#ifdef CONFIG_IOMMUFD
-    DEFINE_PROP_LINK("iommufd", VFIOPlatformDevice, vbasedev.iommufd,
-                     TYPE_IOMMUFD_BACKEND, IOMMUFDBackend *),
-#endif
-};
-
-static void vfio_platform_instance_init(Object *obj)
-{
-    VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(obj);
-    VFIODevice *vbasedev = &vdev->vbasedev;
-
-    vfio_device_init(vbasedev, VFIO_DEVICE_TYPE_PLATFORM, &vfio_platform_ops,
-                     DEVICE(vdev), false);
-}
-
-#ifdef CONFIG_IOMMUFD
-static void vfio_platform_set_fd(Object *obj, const char *str, Error **errp)
-{
-    vfio_device_set_fd(&VFIO_PLATFORM_DEVICE(obj)->vbasedev, str, errp);
-}
-#endif
-
-static void vfio_platform_class_init(ObjectClass *klass, const void *data)
-{
-    DeviceClass *dc = DEVICE_CLASS(klass);
-    SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
-
-    dc->realize = vfio_platform_realize;
-    device_class_set_props(dc, vfio_platform_dev_properties);
-#ifdef CONFIG_IOMMUFD
-    object_class_property_add_str(klass, "fd", NULL, vfio_platform_set_fd);
-#endif
-    dc->vmsd = &vfio_platform_vmstate;
-    dc->desc = "VFIO-based platform device assignment";
-    sbc->connect_irq_notifier = vfio_start_irqfd_injection;
-    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
-
-    object_class_property_set_description(klass, /* 2.4 */
-                                          "host",
-                                          "Host device name of assigned device");
-    object_class_property_set_description(klass, /* 2.4 and 2.5 */
-                                          "x-no-mmap",
-                                          "Disable MMAP for device. Allows to trace MMIO "
-                                          "accesses (DEBUG)");
-    object_class_property_set_description(klass, /* 2.4 */
-                                          "mmap-timeout-ms",
-                                          "When EOI is not provided by KVM/QEMU, wait time "
-                                          "(milliseconds) to re-enable device direct access "
-                                          "after level interrupt (DEBUG)");
-    object_class_property_set_description(klass, /* 2.4 */
-                                          "x-irqfd",
-                                          "Allow disabling irqfd support (DEBUG)");
-    object_class_property_set_description(klass, /* 2.6 */
-                                          "sysfsdev",
-                                          "Host sysfs path of assigned device");
-#ifdef CONFIG_IOMMUFD
-    object_class_property_set_description(klass, /* 9.0 */
-                                          "iommufd",
-                                          "Set host IOMMUFD backend device");
-#endif
-}
-
-static const TypeInfo vfio_platform_dev_info = {
-    .name = TYPE_VFIO_PLATFORM,
-    .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE,
-    .instance_size = sizeof(VFIOPlatformDevice),
-    .instance_init = vfio_platform_instance_init,
-    .class_init = vfio_platform_class_init,
-    .class_size = sizeof(VFIOPlatformDeviceClass),
-};
-
-static void register_vfio_platform_dev_type(void)
-{
-    type_register_static(&vfio_platform_dev_info);
-}
-
-type_init(register_vfio_platform_dev_type)
diff --git a/hw/vfio/spapr.c b/hw/vfio/spapr.c
index 564b70ef97..c41e4588d6 100644
--- a/hw/vfio/spapr.c
+++ b/hw/vfio/spapr.c
@@ -62,7 +62,7 @@ static void vfio_prereg_listener_region_add(MemoryListener *listener,
     VFIOSpaprContainer *scontainer = container_of(listener, VFIOSpaprContainer,
                                                   prereg_listener);
     VFIOContainer *container = &scontainer->container;
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     const hwaddr gpa = section->offset_within_address_space;
     hwaddr end;
     int ret;
@@ -244,7 +244,7 @@ static bool vfio_spapr_create_window(VFIOContainer *container,
                                     hwaddr *pgsize, Error **errp)
 {
     int ret = 0;
-    VFIOContainerBase *bcontainer = &container->bcontainer;
+    VFIOContainerBase *bcontainer = VFIO_IOMMU(container);
     VFIOSpaprContainer *scontainer = container_of(container, VFIOSpaprContainer,
                                                   container);
     IOMMUMemoryRegion *iommu_mr = IOMMU_MEMORY_REGION(section->mr);
@@ -352,8 +352,7 @@ vfio_spapr_container_add_section_window(VFIOContainerBase *bcontainer,
                                         MemoryRegionSection *section,
                                         Error **errp)
 {
-    VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                            bcontainer);
+    VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     VFIOSpaprContainer *scontainer = container_of(container, VFIOSpaprContainer,
                                                   container);
     VFIOHostDMAWindow *hostwin;
@@ -443,8 +442,7 @@ static void
 vfio_spapr_container_del_section_window(VFIOContainerBase *bcontainer,
                                         MemoryRegionSection *section)
 {
-    VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                            bcontainer);
+    VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     VFIOSpaprContainer *scontainer = container_of(container, VFIOSpaprContainer,
                                                   container);
 
@@ -465,8 +463,7 @@ vfio_spapr_container_del_section_window(VFIOContainerBase *bcontainer,
 
 static void vfio_spapr_container_release(VFIOContainerBase *bcontainer)
 {
-    VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                            bcontainer);
+    VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     VFIOSpaprContainer *scontainer = container_of(container, VFIOSpaprContainer,
                                                   container);
     VFIOHostDMAWindow *hostwin, *next;
@@ -484,8 +481,7 @@ static void vfio_spapr_container_release(VFIOContainerBase *bcontainer)
 static bool vfio_spapr_container_setup(VFIOContainerBase *bcontainer,
                                        Error **errp)
 {
-    VFIOContainer *container = container_of(bcontainer, VFIOContainer,
-                                            bcontainer);
+    VFIOContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
     VFIOSpaprContainer *scontainer = container_of(container, VFIOSpaprContainer,
                                                   container);
     struct vfio_iommu_spapr_tce_info info;
diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events
index fc6ed230d0..e3d571f8c8 100644
--- a/hw/vfio/trace-events
+++ b/hw/vfio/trace-events
@@ -127,17 +127,6 @@ vfio_region_unmap(const char *name, unsigned long offset, unsigned long end) "Re
 vfio_region_sparse_mmap_header(const char *name, int index, int nr_areas) "Device %s region %d: %d sparse mmap entries"
 vfio_region_sparse_mmap_entry(int i, unsigned long start, unsigned long end) "sparse entry %d [0x%lx - 0x%lx]"
 
-# platform.c
-vfio_platform_realize(char *name, char *compat) "vfio device %s, compat = %s"
-vfio_platform_eoi(int pin, int fd) "EOI IRQ pin %d (fd=%d)"
-vfio_platform_intp_mmap_enable(int pin) "IRQ #%d still active, stay in slow path"
-vfio_platform_intp_interrupt(int pin, int fd) "Inject IRQ #%d (fd = %d)"
-vfio_platform_intp_inject_pending_lockheld(int pin, int fd) "Inject pending IRQ #%d (fd = %d)"
-vfio_platform_populate_interrupts(int pin, int count, int flags) "- IRQ index %d: count %d, flags=0x%x"
-vfio_intp_interrupt_set_pending(int index) "irq %d is set PENDING"
-vfio_platform_start_level_irqfd_injection(int index, int fd, int resamplefd) "IRQ index=%d, fd = %d, resamplefd = %d"
-vfio_platform_start_edge_irqfd_injection(int index, int fd) "IRQ index=%d, fd = %d"
-
 # spapr.c
 vfio_prereg_listener_region_add_skip(uint64_t start, uint64_t end) "0x%"PRIx64" - 0x%"PRIx64
 vfio_prereg_listener_region_del_skip(uint64_t start, uint64_t end) "0x%"PRIx64" - 0x%"PRIx64
diff --git a/include/hw/vfio/vfio-region.h b/hw/vfio/vfio-region.h
index ede6e0c8f9..ede6e0c8f9 100644
--- a/include/hw/vfio/vfio-region.h
+++ b/hw/vfio/vfio-region.h
diff --git a/include/hw/display/bcm2835_fb.h b/include/hw/display/bcm2835_fb.h
index 49541bf08f..acc9230b6a 100644
--- a/include/hw/display/bcm2835_fb.h
+++ b/include/hw/display/bcm2835_fb.h
@@ -13,7 +13,6 @@
 #define BCM2835_FB_H
 
 #include "hw/sysbus.h"
-#include "ui/console.h"
 #include "qom/object.h"
 
 #define UPPER_RAM_BASE 0x40000000
diff --git a/include/hw/vfio/vfio-amd-xgbe.h b/include/hw/vfio/vfio-amd-xgbe.h
deleted file mode 100644
index a894546c02..0000000000
--- a/include/hw/vfio/vfio-amd-xgbe.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * VFIO AMD XGBE device
- *
- * Copyright Linaro Limited, 2015
- *
- * Authors:
- *  Eric Auger <eric.auger@linaro.org>
- *
- * This work is licensed under the terms of the GNU GPL, version 2.  See
- * the COPYING file in the top-level directory.
- *
- */
-
-#ifndef HW_VFIO_VFIO_AMD_XGBE_H
-#define HW_VFIO_VFIO_AMD_XGBE_H
-
-#include "hw/vfio/vfio-platform.h"
-#include "qom/object.h"
-
-#define TYPE_VFIO_AMD_XGBE "vfio-amd-xgbe"
-
-/**
- * This device exposes:
- * - 5 MMIO regions: MAC, PCS, SerDes Rx/Tx regs,
-     SerDes Integration Registers 1/2 & 2/2
- * - 2 level sensitive IRQs and optional DMA channel IRQs
- */
-struct VFIOAmdXgbeDevice {
-    VFIOPlatformDevice vdev;
-};
-
-typedef struct VFIOAmdXgbeDevice VFIOAmdXgbeDevice;
-
-struct VFIOAmdXgbeDeviceClass {
-    /*< private >*/
-    VFIOPlatformDeviceClass parent_class;
-    /*< public >*/
-    DeviceRealize parent_realize;
-};
-
-typedef struct VFIOAmdXgbeDeviceClass VFIOAmdXgbeDeviceClass;
-
-DECLARE_OBJ_CHECKERS(VFIOAmdXgbeDevice, VFIOAmdXgbeDeviceClass,
-                     VFIO_AMD_XGBE_DEVICE, TYPE_VFIO_AMD_XGBE)
-
-#endif
diff --git a/include/hw/vfio/vfio-calxeda-xgmac.h b/include/hw/vfio/vfio-calxeda-xgmac.h
deleted file mode 100644
index 8482f151dd..0000000000
--- a/include/hw/vfio/vfio-calxeda-xgmac.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * VFIO calxeda xgmac device
- *
- * Copyright Linaro Limited, 2014
- *
- * Authors:
- *  Eric Auger <eric.auger@linaro.org>
- *
- * This work is licensed under the terms of the GNU GPL, version 2.  See
- * the COPYING file in the top-level directory.
- *
- */
-
-#ifndef HW_VFIO_VFIO_CALXEDA_XGMAC_H
-#define HW_VFIO_VFIO_CALXEDA_XGMAC_H
-
-#include "hw/vfio/vfio-platform.h"
-#include "qom/object.h"
-
-#define TYPE_VFIO_CALXEDA_XGMAC "vfio-calxeda-xgmac"
-
-/**
- * This device exposes:
- * - a single MMIO region corresponding to its register space
- * - 3 IRQS (main and 2 power related IRQs)
- */
-struct VFIOCalxedaXgmacDevice {
-    VFIOPlatformDevice vdev;
-};
-typedef struct VFIOCalxedaXgmacDevice VFIOCalxedaXgmacDevice;
-
-struct VFIOCalxedaXgmacDeviceClass {
-    /*< private >*/
-    VFIOPlatformDeviceClass parent_class;
-    /*< public >*/
-    DeviceRealize parent_realize;
-};
-typedef struct VFIOCalxedaXgmacDeviceClass VFIOCalxedaXgmacDeviceClass;
-
-DECLARE_OBJ_CHECKERS(VFIOCalxedaXgmacDevice, VFIOCalxedaXgmacDeviceClass,
-                     VFIO_CALXEDA_XGMAC_DEVICE, TYPE_VFIO_CALXEDA_XGMAC)
-
-#endif
diff --git a/include/hw/vfio/vfio-container-base.h b/include/hw/vfio/vfio-container-base.h
index bded6e993f..acbd48a18a 100644
--- a/include/hw/vfio/vfio-container-base.h
+++ b/include/hw/vfio/vfio-container-base.h
@@ -33,8 +33,9 @@ typedef struct VFIOAddressSpace {
 /*
  * This is the base object for vfio container backends
  */
-typedef struct VFIOContainerBase {
-    Object parent;
+struct VFIOContainerBase {
+    Object parent_obj;
+
     VFIOAddressSpace *space;
     MemoryListener listener;
     Error *error;
@@ -51,7 +52,10 @@ typedef struct VFIOContainerBase {
     QLIST_HEAD(, VFIODevice) device_list;
     GList *iova_ranges;
     NotifierWithReturn cpr_reboot_notifier;
-} VFIOContainerBase;
+};
+
+#define TYPE_VFIO_IOMMU "vfio-iommu"
+OBJECT_DECLARE_TYPE(VFIOContainerBase, VFIOIOMMUClass, VFIO_IOMMU)
 
 typedef struct VFIOGuestIOMMU {
     VFIOContainerBase *bcontainer;
@@ -105,14 +109,11 @@ vfio_container_get_page_size_mask(const VFIOContainerBase *bcontainer)
     return bcontainer->pgsizes;
 }
 
-#define TYPE_VFIO_IOMMU "vfio-iommu"
 #define TYPE_VFIO_IOMMU_LEGACY TYPE_VFIO_IOMMU "-legacy"
 #define TYPE_VFIO_IOMMU_SPAPR TYPE_VFIO_IOMMU "-spapr"
 #define TYPE_VFIO_IOMMU_IOMMUFD TYPE_VFIO_IOMMU "-iommufd"
 #define TYPE_VFIO_IOMMU_USER TYPE_VFIO_IOMMU "-user"
 
-OBJECT_DECLARE_TYPE(VFIOContainerBase, VFIOIOMMUClass, VFIO_IOMMU)
-
 struct VFIOIOMMUClass {
     ObjectClass parent_class;
 
diff --git a/include/hw/vfio/vfio-container.h b/include/hw/vfio/vfio-container.h
index 21e5807e48..240f566993 100644
--- a/include/hw/vfio/vfio-container.h
+++ b/include/hw/vfio/vfio-container.h
@@ -25,13 +25,14 @@ typedef struct VFIOGroup {
     bool ram_block_discard_allowed;
 } VFIOGroup;
 
-typedef struct VFIOContainer {
-    VFIOContainerBase bcontainer;
+struct VFIOContainer {
+    VFIOContainerBase parent_obj;
+
     int fd; /* /dev/vfio/vfio, empowered by the attached groups */
     unsigned iommu_type;
     QLIST_HEAD(, VFIOGroup) group_list;
     VFIOContainerCPR cpr;
-} VFIOContainer;
+};
 
 OBJECT_DECLARE_SIMPLE_TYPE(VFIOContainer, VFIO_IOMMU_LEGACY);
 
diff --git a/include/hw/vfio/vfio-device.h b/include/hw/vfio/vfio-device.h
index 6e4d5ccdac..e7e6243e2d 100644
--- a/include/hw/vfio/vfio-device.h
+++ b/include/hw/vfio/vfio-device.h
@@ -36,7 +36,7 @@
 
 enum {
     VFIO_DEVICE_TYPE_PCI = 0,
-    VFIO_DEVICE_TYPE_PLATFORM = 1,
+    VFIO_DEVICE_TYPE_UNUSED = 1,
     VFIO_DEVICE_TYPE_CCW = 2,
     VFIO_DEVICE_TYPE_AP = 3,
 };
diff --git a/include/hw/vfio/vfio-platform.h b/include/hw/vfio/vfio-platform.h
deleted file mode 100644
index 256d8500b7..0000000000
--- a/include/hw/vfio/vfio-platform.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * vfio based device assignment support - platform devices
- *
- * Copyright Linaro Limited, 2014
- *
- * Authors:
- *  Kim Phillips <kim.phillips@linaro.org>
- *
- * This work is licensed under the terms of the GNU GPL, version 2.  See
- * the COPYING file in the top-level directory.
- *
- * Based on vfio based PCI device assignment support:
- *  Copyright Red Hat, Inc. 2012
- */
-
-#ifndef HW_VFIO_VFIO_PLATFORM_H
-#define HW_VFIO_VFIO_PLATFORM_H
-
-#include "hw/sysbus.h"
-#include "hw/vfio/vfio-device.h"
-#include "qemu/event_notifier.h"
-#include "qemu/queue.h"
-#include "qom/object.h"
-
-#define TYPE_VFIO_PLATFORM "vfio-platform"
-
-enum {
-    VFIO_IRQ_INACTIVE = 0,
-    VFIO_IRQ_PENDING = 1,
-    VFIO_IRQ_ACTIVE = 2,
-    /* VFIO_IRQ_ACTIVE_AND_PENDING cannot happen with VFIO */
-};
-
-typedef struct VFIOINTp {
-    QLIST_ENTRY(VFIOINTp) next; /* entry for IRQ list */
-    QSIMPLEQ_ENTRY(VFIOINTp) pqnext; /* entry for pending IRQ queue */
-    EventNotifier *interrupt; /* eventfd triggered on interrupt */
-    EventNotifier *unmask; /* eventfd for unmask on QEMU bypass */
-    qemu_irq qemuirq;
-    struct VFIOPlatformDevice *vdev; /* back pointer to device */
-    int state; /* inactive, pending, active */
-    uint8_t pin; /* index */
-    uint32_t flags; /* IRQ info flags */
-    bool kvm_accel; /* set when QEMU bypass through KVM enabled */
-} VFIOINTp;
-
-/* function type for user side eventfd handler */
-typedef void (*eventfd_user_side_handler_t)(VFIOINTp *intp);
-
-typedef struct VFIORegion VFIORegion;
-
-struct VFIOPlatformDevice {
-    SysBusDevice sbdev;
-    VFIODevice vbasedev; /* not a QOM object */
-    VFIORegion **regions;
-    QLIST_HEAD(, VFIOINTp) intp_list; /* list of IRQs */
-    /* queue of pending IRQs */
-    QSIMPLEQ_HEAD(, VFIOINTp) pending_intp_queue;
-    char *compat; /* DT compatible values, separated by NUL */
-    unsigned int num_compat; /* number of compatible values */
-    uint32_t mmap_timeout; /* delay to re-enable mmaps after interrupt */
-    QEMUTimer *mmap_timer; /* allows fast-path resume after IRQ hit */
-    QemuMutex intp_mutex; /* protect the intp_list IRQ state */
-    bool irqfd_allowed; /* debug option to force irqfd on/off */
-};
-typedef struct VFIOPlatformDevice VFIOPlatformDevice;
-
-struct VFIOPlatformDeviceClass {
-    /*< private >*/
-    SysBusDeviceClass parent_class;
-    /*< public >*/
-};
-typedef struct VFIOPlatformDeviceClass VFIOPlatformDeviceClass;
-
-DECLARE_OBJ_CHECKERS(VFIOPlatformDevice, VFIOPlatformDeviceClass,
-                     VFIO_PLATFORM_DEVICE, TYPE_VFIO_PLATFORM)
-
-#endif /* HW_VFIO_VFIO_PLATFORM_H */
diff --git a/meson.build b/meson.build
index fa6186db33..3d73873356 100644
--- a/meson.build
+++ b/meson.build
@@ -709,11 +709,7 @@ hardening_flags = [
 #
 # NB: Clang 17 is broken and SEGVs
 # https://github.com/llvm/llvm-project/issues/75168
-#
-# NB2: This clashes with the "retguard" extension of OpenBSD's Clang
-# https://gitlab.com/qemu-project/qemu/-/issues/2278
-if host_os != 'openbsd' and \
-   cc.compiles('extern struct { void (*cb)(void); } s; void f(void) { s.cb(); }',
+if cc.compiles('extern struct { void (*cb)(void); } s; void f(void) { s.cb(); }',
                name: '-fzero-call-used-regs=used-gpr',
                args: ['-O2', '-fzero-call-used-regs=used-gpr'])
     hardening_flags += '-fzero-call-used-regs=used-gpr'
diff --git a/python/qemu/machine/qtest.py b/python/qemu/machine/qtest.py
index 4f5ede85b2..781f674ffa 100644
--- a/python/qemu/machine/qtest.py
+++ b/python/qemu/machine/qtest.py
@@ -177,6 +177,8 @@ class QEMUQtestMachine(QEMUMachine):
             self._qtest_sock_pair[0].close()
             self._qtest_sock_pair[1].close()
             self._qtest_sock_pair = None
+        if self._qtest is not None:
+            self._qtest.close()
         super()._post_shutdown()
 
     def qtest(self, cmd: str) -> str:
diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py
index 69190d057a..058139dc3c 100644
--- a/python/qemu/qmp/__init__.py
+++ b/python/qemu/qmp/__init__.py
@@ -39,7 +39,8 @@ from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient
 logging.getLogger('qemu.qmp').addHandler(logging.NullHandler())
 
 
-# The order of these fields impact the Sphinx documentation order.
+# IMPORTANT: When modifying this list, update the Sphinx overview docs.
+# Anything visible in the qemu.qmp namespace should be on the overview page.
 __all__ = (
     # Classes, most to least important
     'QMPClient',
diff --git a/python/qemu/qmp/error.py b/python/qemu/qmp/error.py
index 24ba4d5054..c87b078f62 100644
--- a/python/qemu/qmp/error.py
+++ b/python/qemu/qmp/error.py
@@ -44,7 +44,10 @@ class ProtocolError(QMPError):
 
     :param error_message: Human-readable string describing the error.
     """
-    def __init__(self, error_message: str):
-        super().__init__(error_message)
+    def __init__(self, error_message: str, *args: object):
+        super().__init__(error_message, *args)
         #: Human-readable error message, without any prefix.
         self.error_message: str = error_message
+
+    def __str__(self) -> str:
+        return self.error_message
diff --git a/python/qemu/qmp/events.py b/python/qemu/qmp/events.py
index 6199776cc6..cfb5f0ac62 100644
--- a/python/qemu/qmp/events.py
+++ b/python/qemu/qmp/events.py
@@ -12,7 +12,14 @@ EventListener Tutorial
 ----------------------
 
 In all of the following examples, we assume that we have a `QMPClient`
-instantiated named ``qmp`` that is already connected.
+instantiated named ``qmp`` that is already connected. For example:
+
+.. code:: python
+
+   from qemu.qmp import QMPClient
+
+   qmp = QMPClient('example-vm')
+   await qmp.connect('127.0.0.1', 1234)
 
 
 `listener()` context blocks with one name
@@ -87,7 +94,9 @@ This is analogous to the following code:
            event = listener.get()
            print(f"Event arrived: {event['event']}")
 
-This event stream will never end, so these blocks will never terminate.
+This event stream will never end, so these blocks will never
+terminate. Even if the QMP connection errors out prematurely, this
+listener will go silent without raising an error.
 
 
 Using asyncio.Task to concurrently retrieve events
@@ -227,16 +236,20 @@ Clearing listeners
 .. code:: python
 
    await qmp.execute('stop')
-   qmp.events.clear()
+   discarded = qmp.events.clear()
    await qmp.execute('cont')
    event = await qmp.events.get()
    assert event['event'] == 'RESUME'
+   assert discarded[0]['event'] == 'STOP'
 
 `EventListener` objects are FIFO queues. If events are not consumed,
 they will remain in the queue until they are witnessed or discarded via
 `clear()`. FIFO queues will be drained automatically upon leaving a
 context block, or when calling `remove_listener()`.
 
+Any events removed from the queue in this fashion will be returned by
+the clear call.
+
 
 Accessing listener history
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -350,6 +363,12 @@ While `listener()` is only capable of creating a single listener,
                break
 
 
+Note that in the above example, we explicitly wait on jobA to conclude
+first, and then wait for jobB to do the same. All we have guaranteed is
+that the code that waits for jobA will not accidentally consume the
+event intended for the jobB waiter.
+
+
 Extending the `EventListener` class
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -407,13 +426,13 @@ Experimental Interfaces & Design Issues
 These interfaces are not ones I am sure I will keep or otherwise modify
 heavily.
 
-qmp.listener()’s type signature
+qmp.listen()’s type signature
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-`listener()` does not return anything, because it was assumed the caller
+`listen()` does not return anything, because it was assumed the caller
 already had a handle to the listener. However, for
-``qmp.listener(EventListener())`` forms, the caller will not have saved
-a handle to the listener.
+``qmp.listen(EventListener())`` forms, the caller will not have saved a
+handle to the listener.
 
 Because this function can accept *many* listeners, I found it hard to
 accurately type in a way where it could be used in both “one” or “many”
@@ -497,6 +516,21 @@ class EventListener:
         #: Optional, secondary event filter.
         self.event_filter: Optional[EventFilter] = event_filter
 
+    def __repr__(self) -> str:
+        args: List[str] = []
+        if self.names:
+            args.append(f"names={self.names!r}")
+        if self.event_filter:
+            args.append(f"event_filter={self.event_filter!r}")
+
+        if self._queue.qsize():
+            state = f"<pending={self._queue.qsize()}>"
+        else:
+            state = ''
+
+        argstr = ", ".join(args)
+        return f"{type(self).__name__}{state}({argstr})"
+
     @property
     def history(self) -> Tuple[Message, ...]:
         """
@@ -618,7 +652,7 @@ class Events:
     def __init__(self) -> None:
         self._listeners: List[EventListener] = []
 
-        #: Default, all-events `EventListener`.
+        #: Default, all-events `EventListener`. See `qmp.events` for more info.
         self.events: EventListener = EventListener()
         self.register_listener(self.events)
 
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index 22a2b5616e..060ed0eb9d 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -38,6 +38,7 @@ from typing import (
 from .error import QMPError
 from .protocol import Runstate, SocketAddrT
 from .qmp_client import QMPClient
+from .util import get_or_create_event_loop
 
 
 #: QMPMessage is an entire QMP message of any kind.
@@ -86,10 +87,13 @@ class QEMUMonitorProtocol:
                 "server argument should be False when passing a socket")
 
         self._qmp = QMPClient(nickname)
-        self._aloop = asyncio.get_event_loop()
         self._address = address
         self._timeout: Optional[float] = None
 
+        # This is a sync shim intended for use in fully synchronous
+        # programs. Create and set an event loop if necessary.
+        self._aloop = get_or_create_event_loop()
+
         if server:
             assert not isinstance(self._address, socket.socket)
             self._sync(self._qmp.start_server(self._address))
@@ -231,6 +235,9 @@ class QEMUMonitorProtocol:
 
         :return: The first available QMP event, or None.
         """
+        # Kick the event loop to allow events to accumulate
+        self._sync(asyncio.sleep(0))
+
         if not wait:
             # wait is False/0: "do not wait, do not except."
             if self._qmp.events.empty():
@@ -286,8 +293,8 @@ class QEMUMonitorProtocol:
         """
         Set the timeout for QMP RPC execution.
 
-        This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
-        The `accept`, `pull_event` and `get_event` methods have their
+        This timeout affects the `cmd`, `cmd_obj`, and `cmd_raw` methods.
+        The `accept`, `pull_event` and `get_events` methods have their
         own configurable timeouts.
 
         :param timeout:
@@ -303,17 +310,30 @@ class QEMUMonitorProtocol:
         self._qmp.send_fd_scm(fd)
 
     def __del__(self) -> None:
-        if self._qmp.runstate == Runstate.IDLE:
-            return
+        if self._qmp.runstate != Runstate.IDLE:
+            self._qmp.logger.warning(
+                "QEMUMonitorProtocol object garbage collected without a prior "
+                "call to close()"
+            )
 
         if not self._aloop.is_running():
-            self.close()
-        else:
-            # Garbage collection ran while the event loop was running.
-            # Nothing we can do about it now, but if we don't raise our
-            # own error, the user will be treated to a lot of traceback
-            # they might not understand.
+            if self._qmp.runstate != Runstate.IDLE:
+                # If the user neglected to close the QMP session and we
+                # are not currently running in an asyncio context, we
+                # have the opportunity to close the QMP session. If we
+                # do not do this, the error messages presented over
+                # dangling async resources may not make any sense to the
+                # user.
+                self.close()
+
+        if self._qmp.runstate != Runstate.IDLE:
+            # If QMP is still not quiesced, it means that the garbage
+            # collector ran from a context within the event loop and we
+            # are simply too late to take any corrective action. Raise
+            # our own error to give meaningful feedback to the user in
+            # order to prevent pages of asyncio stacktrace jargon.
             raise QMPError(
-                "QEMUMonitorProtocol.close()"
-                " was not called before object was garbage collected"
+                "QEMUMonitorProtocol.close() was not called before object was "
+                "garbage collected, and could not be closed due to GC running "
+                "in the event loop"
             )
diff --git a/python/qemu/qmp/message.py b/python/qemu/qmp/message.py
index f76ccc9074..dabb8ec360 100644
--- a/python/qemu/qmp/message.py
+++ b/python/qemu/qmp/message.py
@@ -28,7 +28,8 @@ class Message(MutableMapping[str, object]):
     be instantiated from either another mapping (like a `dict`), or from
     raw `bytes` that still need to be deserialized.
 
-    Once instantiated, it may be treated like any other MutableMapping::
+    Once instantiated, it may be treated like any other
+    :py:obj:`~collections.abc.MutableMapping`::
 
         >>> msg = Message(b'{"hello": "world"}')
         >>> assert msg['hello'] == 'world'
@@ -50,12 +51,19 @@ class Message(MutableMapping[str, object]):
        >>> dict(msg)
        {'hello': 'world'}
 
+    Or pretty-printed::
+
+       >>> print(str(msg))
+       {
+         "hello": "world"
+       }
 
     :param value: Initial value, if any.
     :param eager:
         When `True`, attempt to serialize or deserialize the initial value
         immediately, so that conversion exceptions are raised during
         the call to ``__init__()``.
+
     """
     # pylint: disable=too-many-ancestors
 
@@ -178,15 +186,15 @@ class DeserializationError(ProtocolError):
     :param raw: The raw `bytes` that prompted the failure.
     """
     def __init__(self, error_message: str, raw: bytes):
-        super().__init__(error_message)
+        super().__init__(error_message, raw)
         #: The raw `bytes` that were not understood as JSON.
         self.raw: bytes = raw
 
     def __str__(self) -> str:
-        return "\n".join([
+        return "\n".join((
             super().__str__(),
             f"  raw bytes were: {str(self.raw)}",
-        ])
+        ))
 
 
 class UnexpectedTypeError(ProtocolError):
@@ -197,13 +205,13 @@ class UnexpectedTypeError(ProtocolError):
     :param value: The deserialized JSON value that wasn't an object.
     """
     def __init__(self, error_message: str, value: object):
-        super().__init__(error_message)
+        super().__init__(error_message, value)
         #: The JSON value that was expected to be an object.
         self.value: object = value
 
     def __str__(self) -> str:
         strval = json.dumps(self.value, indent=2)
-        return "\n".join([
+        return "\n".join((
             super().__str__(),
             f"  json value was: {strval}",
-        ])
+        ))
diff --git a/python/qemu/qmp/models.py b/python/qemu/qmp/models.py
index da52848d5a..7e0d0baf03 100644
--- a/python/qemu/qmp/models.py
+++ b/python/qemu/qmp/models.py
@@ -54,7 +54,7 @@ class Model:
 
 class Greeting(Model):
     """
-    Defined in qmp-spec.rst, section "Server Greeting".
+    Defined in `interop/qmp-spec`, "Server Greeting" section.
 
     :param raw: The raw Greeting object.
     :raise KeyError: If any required fields are absent.
@@ -82,7 +82,7 @@ class Greeting(Model):
 
 class QMPGreeting(Model):
     """
-    Defined in qmp-spec.rst, section "Server Greeting".
+    Defined in `interop/qmp-spec`, "Server Greeting" section.
 
     :param raw: The raw QMPGreeting object.
     :raise KeyError: If any required fields are absent.
@@ -104,7 +104,7 @@ class QMPGreeting(Model):
 
 class ErrorResponse(Model):
     """
-    Defined in qmp-spec.rst, section "Error".
+    Defined in `interop/qmp-spec`, "Error" section.
 
     :param raw: The raw ErrorResponse object.
     :raise KeyError: If any required fields are absent.
@@ -126,7 +126,7 @@ class ErrorResponse(Model):
 
 class ErrorInfo(Model):
     """
-    Defined in qmp-spec.rst, section "Error".
+    Defined in `interop/qmp-spec`, "Error" section.
 
     :param raw: The raw ErrorInfo object.
     :raise KeyError: If any required fields are absent.
diff --git a/python/qemu/qmp/protocol.py b/python/qemu/qmp/protocol.py
index a4ffdfad51..219d092a79 100644
--- a/python/qemu/qmp/protocol.py
+++ b/python/qemu/qmp/protocol.py
@@ -15,13 +15,16 @@ class.
 
 import asyncio
 from asyncio import StreamReader, StreamWriter
+from contextlib import asynccontextmanager
 from enum import Enum
 from functools import wraps
+from inspect import iscoroutinefunction
 import logging
 import socket
 from ssl import SSLContext
 from typing import (
     Any,
+    AsyncGenerator,
     Awaitable,
     Callable,
     Generic,
@@ -36,13 +39,10 @@ from typing import (
 from .error import QMPError
 from .util import (
     bottom_half,
-    create_task,
     exception_summary,
     flush,
-    is_closing,
     pretty_traceback,
     upper_half,
-    wait_closed,
 )
 
 
@@ -54,6 +54,9 @@ InternetAddrT = Tuple[str, int]
 UnixAddrT = str
 SocketAddrT = Union[UnixAddrT, InternetAddrT]
 
+# Maximum allowable size of read buffer, default
+_DEFAULT_READBUFLEN = 64 * 1024
+
 
 class Runstate(Enum):
     """Protocol session runstate."""
@@ -76,11 +79,17 @@ class ConnectError(QMPError):
     This Exception always wraps a "root cause" exception that can be
     interrogated for additional information.
 
+    For example, when connecting to a non-existent socket::
+
+        await qmp.connect('not_found.sock')
+        # ConnectError: Failed to establish connection:
+        #               [Errno 2] No such file or directory
+
     :param error_message: Human-readable string describing the error.
     :param exc: The root-cause exception.
     """
     def __init__(self, error_message: str, exc: Exception):
-        super().__init__(error_message)
+        super().__init__(error_message, exc)
         #: Human-readable error string
         self.error_message: str = error_message
         #: Wrapped root cause exception
@@ -99,8 +108,8 @@ class StateError(QMPError):
     An API command (connect, execute, etc) was issued at an inappropriate time.
 
     This error is raised when a command like
-    :py:meth:`~AsyncProtocol.connect()` is issued at an inappropriate
-    time.
+    :py:meth:`~AsyncProtocol.connect()` is called when the client is
+    already connected.
 
     :param error_message: Human-readable string describing the state violation.
     :param state: The actual `Runstate` seen at the time of the violation.
@@ -108,11 +117,14 @@ class StateError(QMPError):
     """
     def __init__(self, error_message: str,
                  state: Runstate, required: Runstate):
-        super().__init__(error_message)
+        super().__init__(error_message, state, required)
         self.error_message = error_message
         self.state = state
         self.required = required
 
+    def __str__(self) -> str:
+        return self.error_message
+
 
 F = TypeVar('F', bound=Callable[..., Any])  # pylint: disable=invalid-name
 
@@ -125,6 +137,25 @@ def require(required_state: Runstate) -> Callable[[F], F]:
     :param required_state: The `Runstate` required to invoke this method.
     :raise StateError: When the required `Runstate` is not met.
     """
+    def _check(proto: 'AsyncProtocol[Any]') -> None:
+        name = type(proto).__name__
+        if proto.runstate == required_state:
+            return
+
+        if proto.runstate == Runstate.CONNECTING:
+            emsg = f"{name} is currently connecting."
+        elif proto.runstate == Runstate.DISCONNECTING:
+            emsg = (f"{name} is disconnecting."
+                    " Call disconnect() to return to IDLE state.")
+        elif proto.runstate == Runstate.RUNNING:
+            emsg = f"{name} is already connected and running."
+        elif proto.runstate == Runstate.IDLE:
+            emsg = f"{name} is disconnected and idle."
+        else:
+            assert False
+
+        raise StateError(emsg, proto.runstate, required_state)
+
     def _decorator(func: F) -> F:
         # _decorator is the decorator that is built by calling the
         # require() decorator factory; e.g.:
@@ -135,29 +166,20 @@ def require(required_state: Runstate) -> Callable[[F], F]:
         @wraps(func)
         def _wrapper(proto: 'AsyncProtocol[Any]',
                      *args: Any, **kwargs: Any) -> Any:
-            # _wrapper is the function that gets executed prior to the
-            # decorated method.
-
-            name = type(proto).__name__
-
-            if proto.runstate != required_state:
-                if proto.runstate == Runstate.CONNECTING:
-                    emsg = f"{name} is currently connecting."
-                elif proto.runstate == Runstate.DISCONNECTING:
-                    emsg = (f"{name} is disconnecting."
-                            " Call disconnect() to return to IDLE state.")
-                elif proto.runstate == Runstate.RUNNING:
-                    emsg = f"{name} is already connected and running."
-                elif proto.runstate == Runstate.IDLE:
-                    emsg = f"{name} is disconnected and idle."
-                else:
-                    assert False
-                raise StateError(emsg, proto.runstate, required_state)
-            # No StateError, so call the wrapped method.
+            _check(proto)
             return func(proto, *args, **kwargs)
 
-        # Return the decorated method;
-        # Transforming Func to Decorated[Func].
+        @wraps(func)
+        async def _async_wrapper(proto: 'AsyncProtocol[Any]',
+                                 *args: Any, **kwargs: Any) -> Any:
+            _check(proto)
+            return await func(proto, *args, **kwargs)
+
+        # Return the decorated method; F => Decorated[F]
+        # Use an async version when applicable, which
+        # preserves async signature generation in sphinx.
+        if iscoroutinefunction(func):
+            return cast(F, _async_wrapper)
         return cast(F, _wrapper)
 
     # Return the decorator instance from the decorator factory. Phew!
@@ -200,24 +222,26 @@ class AsyncProtocol(Generic[T]):
         will log to 'qemu.qmp.protocol', but each individual connection
         can be given its own logger by giving it a name; messages will
         then log to 'qemu.qmp.protocol.${name}'.
+    :param readbuflen:
+        The maximum read buffer length of the underlying StreamReader
+        instance.
     """
     # pylint: disable=too-many-instance-attributes
 
     #: Logger object for debugging messages from this connection.
     logger = logging.getLogger(__name__)
 
-    # Maximum allowable size of read buffer
-    _limit = 64 * 1024
-
     # -------------------------
     # Section: Public interface
     # -------------------------
 
-    def __init__(self, name: Optional[str] = None) -> None:
-        #: The nickname for this connection, if any.
-        self.name: Optional[str] = name
-        if self.name is not None:
-            self.logger = self.logger.getChild(self.name)
+    def __init__(
+        self, name: Optional[str] = None,
+        readbuflen: int = _DEFAULT_READBUFLEN
+    ) -> None:
+        self._name: Optional[str]
+        self.name = name
+        self.readbuflen = readbuflen
 
         # stream I/O
         self._reader: Optional[StreamReader] = None
@@ -254,6 +278,24 @@ class AsyncProtocol(Generic[T]):
         tokens.append(f"runstate={self.runstate.name}")
         return f"<{cls_name} {' '.join(tokens)}>"
 
+    @property
+    def name(self) -> Optional[str]:
+        """
+        The nickname for this connection, if any.
+
+        This name is used for differentiating instances in debug output.
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name: Optional[str]) -> None:
+        logger = logging.getLogger(__name__)
+        if name:
+            self.logger = logger.getChild(name)
+        else:
+            self.logger = logger
+        self._name = name
+
     @property  # @upper_half
     def runstate(self) -> Runstate:
         """The current `Runstate` of the connection."""
@@ -262,7 +304,7 @@ class AsyncProtocol(Generic[T]):
     @upper_half
     async def runstate_changed(self) -> Runstate:
         """
-        Wait for the `runstate` to change, then return that runstate.
+        Wait for the `runstate` to change, then return that `Runstate`.
         """
         await self._runstate_event.wait()
         return self.runstate
@@ -276,9 +318,9 @@ class AsyncProtocol(Generic[T]):
         """
         Accept a connection and begin processing message queues.
 
-        If this call fails, `runstate` is guaranteed to be set back to `IDLE`.
-        This method is precisely equivalent to calling `start_server()`
-        followed by `accept()`.
+        If this call fails, `runstate` is guaranteed to be set back to
+        `IDLE`.  This method is precisely equivalent to calling
+        `start_server()` followed by :py:meth:`~AsyncProtocol.accept()`.
 
         :param address:
             Address to listen on; UNIX socket path or TCP address/port.
@@ -291,7 +333,8 @@ class AsyncProtocol(Generic[T]):
             This exception will wrap a more concrete one. In most cases,
             the wrapped exception will be `OSError` or `EOFError`. If a
             protocol-level failure occurs while establishing a new
-            session, the wrapped error may also be an `QMPError`.
+            session, the wrapped error may also be a `QMPError`.
+
         """
         await self.start_server(address, ssl)
         await self.accept()
@@ -307,8 +350,8 @@ class AsyncProtocol(Generic[T]):
         This method starts listening for an incoming connection, but
         does not block waiting for a peer. This call will return
         immediately after binding and listening on a socket. A later
-        call to `accept()` must be made in order to finalize the
-        incoming connection.
+        call to :py:meth:`~AsyncProtocol.accept()` must be made in order
+        to finalize the incoming connection.
 
         :param address:
             Address to listen on; UNIX socket path or TCP address/port.
@@ -321,9 +364,8 @@ class AsyncProtocol(Generic[T]):
             This exception will wrap a more concrete one. In most cases,
             the wrapped exception will be `OSError`.
         """
-        await self._session_guard(
-            self._do_start_server(address, ssl),
-            'Failed to establish connection')
+        async with self._session_guard('Failed to establish connection'):
+            await self._do_start_server(address, ssl)
         assert self.runstate == Runstate.CONNECTING
 
     @upper_half
@@ -332,10 +374,12 @@ class AsyncProtocol(Generic[T]):
         """
         Accept an incoming connection and begin processing message queues.
 
-        If this call fails, `runstate` is guaranteed to be set back to `IDLE`.
+        Used after a previous call to `start_server()` to accept an
+        incoming connection. If this call fails, `runstate` is
+        guaranteed to be set back to `IDLE`.
 
         :raise StateError: When the `Runstate` is not `CONNECTING`.
-        :raise QMPError: When `start_server()` was not called yet.
+        :raise QMPError: When `start_server()` was not called first.
         :raise ConnectError:
             When a connection or session cannot be established.
 
@@ -346,12 +390,10 @@ class AsyncProtocol(Generic[T]):
         """
         if self._accepted is None:
             raise QMPError("Cannot call accept() before start_server().")
-        await self._session_guard(
-            self._do_accept(),
-            'Failed to establish connection')
-        await self._session_guard(
-            self._establish_session(),
-            'Failed to establish session')
+        async with self._session_guard('Failed to establish connection'):
+            await self._do_accept()
+        async with self._session_guard('Failed to establish session'):
+            await self._establish_session()
         assert self.runstate == Runstate.RUNNING
 
     @upper_half
@@ -376,12 +418,10 @@ class AsyncProtocol(Generic[T]):
             protocol-level failure occurs while establishing a new
             session, the wrapped error may also be an `QMPError`.
         """
-        await self._session_guard(
-            self._do_connect(address, ssl),
-            'Failed to establish connection')
-        await self._session_guard(
-            self._establish_session(),
-            'Failed to establish session')
+        async with self._session_guard('Failed to establish connection'):
+            await self._do_connect(address, ssl)
+        async with self._session_guard('Failed to establish session'):
+            await self._establish_session()
         assert self.runstate == Runstate.RUNNING
 
     @upper_half
@@ -392,7 +432,11 @@ class AsyncProtocol(Generic[T]):
         If there was an exception that caused the reader/writers to
         terminate prematurely, it will be raised here.
 
-        :raise Exception: When the reader or writer terminate unexpectedly.
+        :raise Exception:
+            When the reader or writer terminate unexpectedly. You can
+            expect to see `EOFError` if the server hangs up, or
+            `OSError` for connection-related issues. If there was a QMP
+            protocol-level problem, `ProtocolError` will be seen.
         """
         self.logger.debug("disconnect() called.")
         self._schedule_disconnect()
@@ -402,7 +446,8 @@ class AsyncProtocol(Generic[T]):
     # Section: Session machinery
     # --------------------------
 
-    async def _session_guard(self, coro: Awaitable[None], emsg: str) -> None:
+    @asynccontextmanager
+    async def _session_guard(self, emsg: str) -> AsyncGenerator[None, None]:
         """
         Async guard function used to roll back to `IDLE` on any error.
 
@@ -419,10 +464,9 @@ class AsyncProtocol(Generic[T]):
         :raise ConnectError:
             When any other error is encountered in the guarded block.
         """
-        # Note: After Python 3.6 support is removed, this should be an
-        # @asynccontextmanager instead of accepting a callback.
         try:
-            await coro
+            # Caller's code runs here.
+            yield
         except BaseException as err:
             self.logger.error("%s: %s", emsg, exception_summary(err))
             self.logger.debug("%s:\n%s\n", emsg, pretty_traceback())
@@ -561,7 +605,7 @@ class AsyncProtocol(Generic[T]):
                 port=address[1],
                 ssl=ssl,
                 backlog=1,
-                limit=self._limit,
+                limit=self.readbuflen,
             )
         else:
             coro = asyncio.start_unix_server(
@@ -569,7 +613,7 @@ class AsyncProtocol(Generic[T]):
                 path=address,
                 ssl=ssl,
                 backlog=1,
-                limit=self._limit,
+                limit=self.readbuflen,
             )
 
         # Allow runstate watchers to witness 'CONNECTING' state; some
@@ -624,7 +668,7 @@ class AsyncProtocol(Generic[T]):
                               "fd=%d, family=%r, type=%r",
                               address.fileno(), address.family, address.type)
             connect = asyncio.open_connection(
-                limit=self._limit,
+                limit=self.readbuflen,
                 ssl=ssl,
                 sock=address,
             )
@@ -634,14 +678,14 @@ class AsyncProtocol(Generic[T]):
                 address[0],
                 address[1],
                 ssl=ssl,
-                limit=self._limit,
+                limit=self.readbuflen,
             )
         else:
             self.logger.debug("Connecting to file://%s ...", address)
             connect = asyncio.open_unix_connection(
                 path=address,
                 ssl=ssl,
-                limit=self._limit,
+                limit=self.readbuflen,
             )
 
         self._reader, self._writer = await connect
@@ -663,8 +707,8 @@ class AsyncProtocol(Generic[T]):
         reader_coro = self._bh_loop_forever(self._bh_recv_message, 'Reader')
         writer_coro = self._bh_loop_forever(self._bh_send_message, 'Writer')
 
-        self._reader_task = create_task(reader_coro)
-        self._writer_task = create_task(writer_coro)
+        self._reader_task = asyncio.create_task(reader_coro)
+        self._writer_task = asyncio.create_task(writer_coro)
 
         self._bh_tasks = asyncio.gather(
             self._reader_task,
@@ -689,7 +733,7 @@ class AsyncProtocol(Generic[T]):
         if not self._dc_task:
             self._set_state(Runstate.DISCONNECTING)
             self.logger.debug("Scheduling disconnect.")
-            self._dc_task = create_task(self._bh_disconnect())
+            self._dc_task = asyncio.create_task(self._bh_disconnect())
 
     @upper_half
     async def _wait_disconnect(self) -> None:
@@ -825,13 +869,13 @@ class AsyncProtocol(Generic[T]):
         if not self._writer:
             return
 
-        if not is_closing(self._writer):
+        if not self._writer.is_closing():
             self.logger.debug("Closing StreamWriter.")
             self._writer.close()
 
         self.logger.debug("Waiting for StreamWriter to close ...")
         try:
-            await wait_closed(self._writer)
+            await self._writer.wait_closed()
         except Exception:  # pylint: disable=broad-except
             # It's hard to tell if the Stream is already closed or
             # not. Even if one of the tasks has failed, it may have
diff --git a/python/qemu/qmp/qmp_client.py b/python/qemu/qmp/qmp_client.py
index 2a817f9db3..8beccfe29d 100644
--- a/python/qemu/qmp/qmp_client.py
+++ b/python/qemu/qmp/qmp_client.py
@@ -41,7 +41,7 @@ class _WrappedProtocolError(ProtocolError):
     :param exc: The root-cause exception.
     """
     def __init__(self, error_message: str, exc: Exception):
-        super().__init__(error_message)
+        super().__init__(error_message, exc)
         self.exc = exc
 
     def __str__(self) -> str:
@@ -70,21 +70,38 @@ class ExecuteError(QMPError):
     """
     Exception raised by `QMPClient.execute()` on RPC failure.
 
+    This exception is raised when the server received, interpreted, and
+    replied to a command successfully; but the command itself returned a
+    failure status.
+
+    For example::
+
+        await qmp.execute('block-dirty-bitmap-add',
+                          {'node': 'foo', 'name': 'my_bitmap'})
+        # qemu.qmp.qmp_client.ExecuteError:
+        #     Cannot find device='foo' nor node-name='foo'
+
     :param error_response: The RPC error response object.
     :param sent: The sent RPC message that caused the failure.
     :param received: The raw RPC error reply received.
     """
     def __init__(self, error_response: ErrorResponse,
                  sent: Message, received: Message):
-        super().__init__(error_response.error.desc)
+        super().__init__(error_response, sent, received)
         #: The sent `Message` that caused the failure
         self.sent: Message = sent
         #: The received `Message` that indicated failure
         self.received: Message = received
         #: The parsed error response
         self.error: ErrorResponse = error_response
-        #: The QMP error class
-        self.error_class: str = error_response.error.class_
+
+    @property
+    def error_class(self) -> str:
+        """The QMP error class"""
+        return self.error.error.class_
+
+    def __str__(self) -> str:
+        return self.error.error.desc
 
 
 class ExecInterruptedError(QMPError):
@@ -93,9 +110,22 @@ class ExecInterruptedError(QMPError):
 
     This error is raised when an `execute()` statement could not be
     completed.  This can occur because the connection itself was
-    terminated before a reply was received.
+    terminated before a reply was received. The true cause of the
+    interruption will be available via `disconnect()`.
 
-    The true cause of the interruption will be available via `disconnect()`.
+    The QMP protocol does not make it possible to know if a command
+    succeeded or failed after such an event; the client will need to
+    query the server to determine the state of the server on a
+    case-by-case basis.
+
+    For example, ECONNRESET might look like this::
+
+        try:
+            await qmp.execute('query-block')
+            # ExecInterruptedError: Disconnected
+        except ExecInterruptedError:
+            await qmp.disconnect()
+            # ConnectionResetError: [Errno 104] Connection reset by peer
     """
 
 
@@ -110,8 +140,8 @@ class _MsgProtocolError(ProtocolError):
     :param error_message: Human-readable string describing the error.
     :param msg: The QMP `Message` that caused the error.
     """
-    def __init__(self, error_message: str, msg: Message):
-        super().__init__(error_message)
+    def __init__(self, error_message: str, msg: Message, *args: object):
+        super().__init__(error_message, msg, *args)
         #: The received `Message` that caused the error.
         self.msg: Message = msg
 
@@ -150,30 +180,44 @@ class BadReplyError(_MsgProtocolError):
     :param sent: The message that was sent that prompted the error.
     """
     def __init__(self, error_message: str, msg: Message, sent: Message):
-        super().__init__(error_message, msg)
+        super().__init__(error_message, msg, sent)
         #: The sent `Message` that caused the failure
         self.sent = sent
 
 
 class QMPClient(AsyncProtocol[Message], Events):
-    """
-    Implements a QMP client connection.
+    """Implements a QMP client connection.
+
+    `QMPClient` can be used to either connect or listen to a QMP server,
+    but always acts as the QMP client.
 
-    QMP can be used to establish a connection as either the transport
-    client or server, though this class always acts as the QMP client.
+    :param name:
+        Optional nickname for the connection, used to differentiate
+        instances when logging.
 
-    :param name: Optional nickname for the connection, used for logging.
+    :param readbuflen:
+        The maximum buffer length for reads and writes to and from the QMP
+        server, in bytes. Default is 10MB. If `QMPClient` is used to
+        connect to a guest agent to transfer files via ``guest-file-read``/
+        ``guest-file-write``, increasing this value may be required.
 
     Basic script-style usage looks like this::
 
-      qmp = QMPClient('my_virtual_machine_name')
-      await qmp.connect(('127.0.0.1', 1234))
-      ...
-      res = await qmp.execute('block-query')
-      ...
-      await qmp.disconnect()
+      import asyncio
+      from qemu.qmp import QMPClient
+
+      async def main():
+          qmp = QMPClient('my_virtual_machine_name')
+          await qmp.connect(('127.0.0.1', 1234))
+          ...
+          res = await qmp.execute('query-block')
+          ...
+          await qmp.disconnect()
 
-    Basic async client-style usage looks like this::
+      asyncio.run(main())
+
+    A more advanced example that starts to take advantage of asyncio
+    might look like this::
 
       class Client:
           def __init__(self, name: str):
@@ -193,25 +237,32 @@ class QMPClient(AsyncProtocol[Message], Events):
               await self.disconnect()
 
     See `qmp.events` for more detail on event handling patterns.
+
     """
     #: Logger object used for debugging messages.
     logger = logging.getLogger(__name__)
 
-    # Read buffer limit; 10MB like libvirt default
-    _limit = 10 * 1024 * 1024
+    # Read buffer default limit; 10MB like libvirt default
+    _readbuflen = 10 * 1024 * 1024
 
     # Type alias for pending execute() result items
     _PendingT = Union[Message, ExecInterruptedError]
 
-    def __init__(self, name: Optional[str] = None) -> None:
-        super().__init__(name)
+    def __init__(
+        self,
+        name: Optional[str] = None,
+        readbuflen: int = _readbuflen
+    ) -> None:
+        super().__init__(name, readbuflen)
         Events.__init__(self)
 
         #: Whether or not to await a greeting after establishing a connection.
+        #: Defaults to True; QGA servers expect this to be False.
         self.await_greeting: bool = True
 
-        #: Whether or not to perform capabilities negotiation upon connection.
-        #: Implies `await_greeting`.
+        #: Whether or not to perform capabilities negotiation upon
+        #: connection. Implies `await_greeting`. Defaults to True; QGA
+        #: servers expect this to be False.
         self.negotiate: bool = True
 
         # Cached Greeting, if one was awaited.
@@ -228,7 +279,13 @@ class QMPClient(AsyncProtocol[Message], Events):
 
     @property
     def greeting(self) -> Optional[Greeting]:
-        """The `Greeting` from the QMP server, if any."""
+        """
+        The `Greeting` from the QMP server, if any.
+
+        Defaults to ``None``, and will be set after a greeting is
+        received during the connection process. It is reset at the start
+        of each connection attempt.
+        """
         return self._greeting
 
     @upper_half
@@ -369,7 +426,7 @@ class QMPClient(AsyncProtocol[Message], Events):
             # This is very likely a server parsing error.
             # It doesn't inherently belong to any pending execution.
             # Instead of performing clever recovery, just terminate.
-            # See "NOTE" in qmp-spec.rst, section "Error".
+            # See "NOTE" in interop/qmp-spec, "Error" section.
             raise ServerParseError(
                 ("Server sent an error response without an ID, "
                  "but there are no ID-less executions pending. "
@@ -377,7 +434,7 @@ class QMPClient(AsyncProtocol[Message], Events):
                 msg
             )
 
-        # qmp-spec.rst, section "Commands Responses":
+        # qmp-spec.rst, "Commands Responses" section:
         # 'Clients should drop all the responses
         # that have an unknown "id" field.'
         self.logger.log(
@@ -550,7 +607,7 @@ class QMPClient(AsyncProtocol[Message], Events):
     @require(Runstate.RUNNING)
     async def execute_msg(self, msg: Message) -> object:
         """
-        Execute a QMP command and return its value.
+        Execute a QMP command on the server and return its value.
 
         :param msg: The QMP `Message` to execute.
 
@@ -562,7 +619,9 @@ class QMPClient(AsyncProtocol[Message], Events):
             If the QMP `Message` does not have either the 'execute' or
             'exec-oob' fields set.
         :raise ExecuteError: When the server returns an error response.
-        :raise ExecInterruptedError: if the connection was terminated early.
+        :raise ExecInterruptedError:
+            If the connection was disrupted before
+            receiving a reply from the server.
         """
         if not ('execute' in msg or 'exec-oob' in msg):
             raise ValueError("Requires 'execute' or 'exec-oob' message")
@@ -601,9 +660,11 @@ class QMPClient(AsyncProtocol[Message], Events):
 
         :param cmd: QMP command name.
         :param arguments: Arguments (if any). Must be JSON-serializable.
-        :param oob: If `True`, execute "out of band".
+        :param oob:
+            If `True`, execute "out of band". See `interop/qmp-spec`
+            section "Out-of-band execution".
 
-        :return: An executable QMP `Message`.
+        :return: A QMP `Message` that can be executed with `execute_msg()`.
         """
         msg = Message({'exec-oob' if oob else 'execute': cmd})
         if arguments is not None:
@@ -615,18 +676,22 @@ class QMPClient(AsyncProtocol[Message], Events):
                       arguments: Optional[Mapping[str, object]] = None,
                       oob: bool = False) -> object:
         """
-        Execute a QMP command and return its value.
+        Execute a QMP command on the server and return its value.
 
         :param cmd: QMP command name.
         :param arguments: Arguments (if any). Must be JSON-serializable.
-        :param oob: If `True`, execute "out of band".
+        :param oob:
+            If `True`, execute "out of band". See `interop/qmp-spec`
+            section "Out-of-band execution".
 
         :return:
             The command execution return value from the server. The type of
             object returned depends on the command that was issued,
             though most in QEMU return a `dict`.
         :raise ExecuteError: When the server returns an error response.
-        :raise ExecInterruptedError: if the connection was terminated early.
+        :raise ExecInterruptedError:
+            If the connection was disrupted before
+            receiving a reply from the server.
         """
         msg = self.make_execute_msg(cmd, arguments, oob=oob)
         return await self.execute_msg(msg)
@@ -634,8 +699,20 @@ class QMPClient(AsyncProtocol[Message], Events):
     @upper_half
     @require(Runstate.RUNNING)
     def send_fd_scm(self, fd: int) -> None:
-        """
-        Send a file descriptor to the remote via SCM_RIGHTS.
+        """Send a file descriptor to the remote via SCM_RIGHTS.
+
+        This method does not close the file descriptor.
+
+        :param fd: The file descriptor to send to QEMU.
+
+        This is an advanced feature of QEMU where file descriptors can
+        be passed from client to server. This is usually used as a
+        security measure to isolate the QEMU process from being able to
+        open its own files. See the QMP commands ``getfd`` and
+        ``add-fd`` for more information.
+
+        See `socket.socket.sendmsg` for more information on the Python
+        implementation for sending file descriptors over a UNIX socket.
         """
         assert self._writer is not None
         sock = self._writer.transport.get_extra_info('socket')
diff --git a/python/qemu/qmp/qmp_shell.py b/python/qemu/qmp/qmp_shell.py
index 98e684e9e8..f818800568 100644
--- a/python/qemu/qmp/qmp_shell.py
+++ b/python/qemu/qmp/qmp_shell.py
@@ -10,9 +10,15 @@
 #
 
 """
-Low-level QEMU shell on top of QMP.
+qmp-shell - An interactive QEMU shell powered by QMP
 
-usage: qmp-shell [-h] [-H] [-N] [-v] [-p] qmp_server
+qmp-shell offers a simple shell with a convenient shorthand syntax as an
+alternative to typing JSON by hand. This syntax is not standardized and
+is not meant to be used as a scriptable interface. This shorthand *may*
+change incompatibly in the future, and it is strongly encouraged to use
+the QMP library to provide API-stable scripting when needed.
+
+usage: qmp-shell [-h] [-H] [-v] [-p] [-l LOGFILE] [-N] qmp_server
 
 positional arguments:
   qmp_server            < UNIX socket path | TCP address:port >
@@ -20,41 +26,52 @@ positional arguments:
 optional arguments:
   -h, --help            show this help message and exit
   -H, --hmp             Use HMP interface
-  -N, --skip-negotiation
-                        Skip negotiate (for qemu-ga)
   -v, --verbose         Verbose (echo commands sent and received)
   -p, --pretty          Pretty-print JSON
+  -l LOGFILE, --logfile LOGFILE
+                        Save log of all QMP messages to PATH
+  -N, --skip-negotiation
+                        Skip negotiate (for qemu-ga)
+
+Usage
+-----
 
+First, start QEMU with::
 
-Start QEMU with:
+    > qemu [...] -qmp unix:./qmp-sock,server=on[,wait=off]
 
-# qemu [...] -qmp unix:./qmp-sock,server
+Then run the shell, passing the address of the socket::
 
-Run the shell:
+    > qmp-shell ./qmp-sock
 
-$ qmp-shell ./qmp-sock
+Syntax
+------
 
-Commands have the following format:
+Commands have the following format::
 
-   < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
+    < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ]
 
-For example:
+For example, to add a network device::
 
-(QEMU) device_add driver=e1000 id=net1
-{'return': {}}
-(QEMU)
+    (QEMU) device_add driver=e1000 id=net1
+    {'return': {}}
+    (QEMU)
 
-key=value pairs also support Python or JSON object literal subset notations,
-without spaces. Dictionaries/objects {} are supported as are arrays [].
+key=value pairs support either Python or JSON object literal notations,
+**without spaces**. Dictionaries/objects ``{}`` are supported, as are
+arrays ``[]``::
 
-   example-command arg-name1={'key':'value','obj'={'prop':"value"}}
+    example-command arg-name1={'key':'value','obj'={'prop':"value"}}
 
-Both JSON and Python formatting should work, including both styles of
-string literal quotes. Both paradigms of literal values should work,
-including null/true/false for JSON and None/True/False for Python.
+Either JSON or Python formatting for compound values works, including
+both styles of string literal quotes (either single or double
+quotes). Both paradigms of literal values are accepted, including
+``null/true/false`` for JSON and ``None/True/False`` for Python.
 
+Transactions
+------------
 
-Transactions have the following multi-line format:
+Transactions have the following multi-line format::
 
    transaction(
    action-name1 [ arg-name1=arg1 ] ... [arg-nameN=argN ]
@@ -62,11 +79,11 @@ Transactions have the following multi-line format:
    action-nameN [ arg-name1=arg1 ] ... [arg-nameN=argN ]
    )
 
-One line transactions are also supported:
+One line transactions are also supported::
 
    transaction( action-name1 ... )
 
-For example:
+For example::
 
     (QEMU) transaction(
     TRANS> block-dirty-bitmap-add node=drive0 name=bitmap1
@@ -75,9 +92,35 @@ For example:
     {"return": {}}
     (QEMU)
 
-Use the -v and -p options to activate the verbose and pretty-print options,
-which will echo back the properly formatted JSON-compliant QMP that is being
-sent to QEMU, which is useful for debugging and documentation generation.
+Commands
+--------
+
+Autocomplete of command names using <tab> is supported. Pressing <tab>
+at a blank CLI prompt will show you a list of all available commands
+that the connected QEMU instance supports.
+
+For documentation on QMP commands and their arguments, please see
+`qmp ref`.
+
+Events
+------
+
+qmp-shell will display events received from the server, but this version
+does not do so asynchronously. To check for new events from the server,
+press <enter> on a blank line::
+
+    (QEMU) ⏎
+    {'timestamp': {'seconds': 1660071944, 'microseconds': 184667},
+     'event': 'STOP'}
+
+Display options
+---------------
+
+Use the -v and -p options to activate the verbose and pretty-print
+options, which will echo back the properly formatted JSON-compliant QMP
+that is being sent to QEMU. This is useful for debugging to see the
+wire-level QMP data being exchanged, and generating output for use in
+writing documentation for QEMU.
 """
 
 import argparse
@@ -514,21 +557,29 @@ def die(msg: str) -> NoReturn:
     sys.exit(1)
 
 
-def main() -> None:
-    """
-    qmp-shell entry point: parse command line arguments and start the REPL.
-    """
+def common_parser() -> argparse.ArgumentParser:
+    """Build common parsing options used by qmp-shell and qmp-shell-wrap."""
     parser = argparse.ArgumentParser()
     parser.add_argument('-H', '--hmp', action='store_true',
                         help='Use HMP interface')
-    parser.add_argument('-N', '--skip-negotiation', action='store_true',
-                        help='Skip negotiate (for qemu-ga)')
     parser.add_argument('-v', '--verbose', action='store_true',
                         help='Verbose (echo commands sent and received)')
     parser.add_argument('-p', '--pretty', action='store_true',
                         help='Pretty-print JSON')
     parser.add_argument('-l', '--logfile',
                         help='Save log of all QMP messages to PATH')
+    # NOTE: When changing arguments, update both this module docstring
+    # and the manpage synopsis in docs/man/qmp_shell.rst.
+    return parser
+
+
+def main() -> None:
+    """
+    qmp-shell entry point: parse command line arguments and start the REPL.
+    """
+    parser = common_parser()
+    parser.add_argument('-N', '--skip-negotiation', action='store_true',
+                        help='Skip negotiate (for qemu-ga)')
 
     default_server = os.environ.get('QMP_SOCKET')
     parser.add_argument('qmp_server', action='store',
@@ -561,19 +612,37 @@ def main() -> None:
 
 def main_wrap() -> None:
     """
-    qmp-shell-wrap entry point: parse command line arguments and
-    start the REPL.
-    """
-    parser = argparse.ArgumentParser()
-    parser.add_argument('-H', '--hmp', action='store_true',
-                        help='Use HMP interface')
-    parser.add_argument('-v', '--verbose', action='store_true',
-                        help='Verbose (echo commands sent and received)')
-    parser.add_argument('-p', '--pretty', action='store_true',
-                        help='Pretty-print JSON')
-    parser.add_argument('-l', '--logfile',
-                        help='Save log of all QMP messages to PATH')
+    qmp-shell-wrap - QEMU + qmp-shell launcher utility
+
+    Launch QEMU and connect to it with `qmp-shell` in a single command.
+    CLI arguments will be forwarded to qemu, with additional arguments
+    added to allow `qmp-shell` to then connect to the recently launched
+    QEMU instance.
+
+    usage: qmp-shell-wrap [-h] [-H] [-v] [-p] [-l LOGFILE] ...
 
+    positional arguments:
+      command               QEMU command line to invoke
+
+    optional arguments:
+      -h, --help            show this help message and exit
+      -H, --hmp             Use HMP interface
+      -v, --verbose         Verbose (echo commands sent and received)
+      -p, --pretty          Pretty-print JSON
+      -l LOGFILE, --logfile LOGFILE
+                            Save log of all QMP messages to PATH
+
+    Usage
+    -----
+
+    Prepend "qmp-shell-wrap" to your usual QEMU command line::
+
+        > qmp-shell-wrap qemu-system-x86_64 -M q35 -m 4096 -display none
+        Welcome to the QMP low-level shell!
+        Connected
+        (QEMU)
+    """
+    parser = common_parser()
     parser.add_argument('command', nargs=argparse.REMAINDER,
                         help='QEMU command line to invoke')
 
@@ -610,6 +679,8 @@ def main_wrap() -> None:
 
                 for _ in qemu.repl():
                     pass
+    except FileNotFoundError:
+        sys.stderr.write(f"ERROR: QEMU executable '{cmd[0]}' not found.\n")
     finally:
         os.unlink(sockpath)
 
diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
index 2d9ebbd20b..d946c20513 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -21,6 +21,7 @@ import json
 import logging
 from logging import Handler, LogRecord
 import signal
+import sys
 from typing import (
     List,
     Optional,
@@ -30,17 +31,27 @@ from typing import (
     cast,
 )
 
-from pygments import lexers
-from pygments import token as Token
-import urwid
-import urwid_readline
+
+try:
+    from pygments import lexers
+    from pygments import token as Token
+    import urwid
+    import urwid_readline
+except ModuleNotFoundError as exc:
+    print(
+        f"Module '{exc.name}' not found.",
+        "You need the optional 'tui' group: pip install qemu.qmp[tui]",
+        sep='\n',
+        file=sys.stderr,
+    )
+    sys.exit(1)
 
 from .error import ProtocolError
 from .legacy import QEMUMonitorProtocol, QMPBadPortError
 from .message import DeserializationError, Message, UnexpectedTypeError
 from .protocol import ConnectError, Runstate
 from .qmp_client import ExecInterruptedError, QMPClient
-from .util import create_task, pretty_traceback
+from .util import get_or_create_event_loop, pretty_traceback
 
 
 # The name of the signal that is used to update the history list
@@ -225,7 +236,7 @@ class App(QMPClient):
         """
         try:
             msg = Message(bytes(raw_msg, encoding='utf-8'))
-            create_task(self._send_to_server(msg))
+            asyncio.create_task(self._send_to_server(msg))
         except (DeserializationError, UnexpectedTypeError) as err:
             raw_msg = format_json(raw_msg)
             logging.info('Invalid message: %s', err.error_message)
@@ -246,7 +257,7 @@ class App(QMPClient):
         Initiates killing of app. A bridge between asynchronous and synchronous
         code.
         """
-        create_task(self._kill_app())
+        asyncio.create_task(self._kill_app())
 
     async def _kill_app(self) -> None:
         """
@@ -376,8 +387,7 @@ class App(QMPClient):
         """
         screen = urwid.raw_display.Screen()
         screen.set_terminal_properties(256)
-
-        self.aloop = asyncio.get_event_loop()
+        self.aloop = get_or_create_event_loop()
         self.aloop.set_debug(debug)
 
         # Gracefully handle SIGTERM and SIGINT signals
@@ -393,7 +403,7 @@ class App(QMPClient):
                                    handle_mouse=True,
                                    event_loop=event_loop)
 
-        create_task(self.manage_connection(), self.aloop)
+        self.aloop.create_task(self.manage_connection())
         try:
             main_loop.run()
         except Exception as err:
diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
index ca6225e9cd..a8229e5524 100644
--- a/python/qemu/qmp/util.py
+++ b/python/qemu/qmp/util.py
@@ -1,25 +1,16 @@
 """
 Miscellaneous Utilities
 
-This module provides asyncio utilities and compatibility wrappers for
-Python 3.6 to provide some features that otherwise become available in
-Python 3.7+.
-
-Various logging and debugging utilities are also provided, such as
-`exception_summary()` and `pretty_traceback()`, used primarily for
-adding information into the logging stream.
+This module provides asyncio and various logging and debugging
+utilities, such as `exception_summary()` and `pretty_traceback()`, used
+primarily for adding information into the logging stream.
 """
 
 import asyncio
 import sys
 import traceback
-from typing import (
-    Any,
-    Coroutine,
-    Optional,
-    TypeVar,
-    cast,
-)
+from typing import TypeVar, cast
+import warnings
 
 
 T = TypeVar('T')
@@ -30,9 +21,35 @@ T = TypeVar('T')
 # --------------------------
 
 
+def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
+    """
+    Return this thread's current event loop, or create a new one.
+
+    This function behaves similarly to asyncio.get_event_loop() in
+    Python<=3.13, where if there is no event loop currently associated
+    with the current context, it will create and register one. It should
+    generally not be used in any asyncio-native applications.
+    """
+    try:
+        with warnings.catch_warnings():
+            # Python <= 3.13 will trigger deprecation warnings if no
+            # event loop is set, but will create and set a new loop.
+            warnings.simplefilter("ignore")
+            loop = asyncio.get_event_loop()
+    except RuntimeError:
+        # Python 3.14+: No event loop set for this thread,
+        # create and set one.
+        loop = asyncio.new_event_loop()
+        # Set this loop as the current thread's loop, to be returned
+        # by calls to get_event_loop() in the future.
+        asyncio.set_event_loop(loop)
+
+    return loop
+
+
 async def flush(writer: asyncio.StreamWriter) -> None:
     """
-    Utility function to ensure a StreamWriter is *fully* drained.
+    Utility function to ensure an `asyncio.StreamWriter` is *fully* drained.
 
     `asyncio.StreamWriter.drain` only promises we will return to below
     the "high-water mark". This function ensures we flush the entire
@@ -72,102 +89,13 @@ def bottom_half(func: T) -> T:
 
     These methods do not, in general, have the ability to directly
     report information to a caller’s context and will usually be
-    collected as a Task result instead.
+    collected as an `asyncio.Task` result instead.
 
     They must not call upper-half functions directly.
     """
     return func
 
 
-# -------------------------------
-# Section: Compatibility Wrappers
-# -------------------------------
-
-
-def create_task(coro: Coroutine[Any, Any, T],
-                loop: Optional[asyncio.AbstractEventLoop] = None
-                ) -> 'asyncio.Future[T]':
-    """
-    Python 3.6-compatible `asyncio.create_task` wrapper.
-
-    :param coro: The coroutine to execute in a task.
-    :param loop: Optionally, the loop to create the task in.
-
-    :return: An `asyncio.Future` object.
-    """
-    if sys.version_info >= (3, 7):
-        if loop is not None:
-            return loop.create_task(coro)
-        return asyncio.create_task(coro)  # pylint: disable=no-member
-
-    # Python 3.6:
-    return asyncio.ensure_future(coro, loop=loop)
-
-
-def is_closing(writer: asyncio.StreamWriter) -> bool:
-    """
-    Python 3.6-compatible `asyncio.StreamWriter.is_closing` wrapper.
-
-    :param writer: The `asyncio.StreamWriter` object.
-    :return: `True` if the writer is closing, or closed.
-    """
-    if sys.version_info >= (3, 7):
-        return writer.is_closing()
-
-    # Python 3.6:
-    transport = writer.transport
-    assert isinstance(transport, asyncio.WriteTransport)
-    return transport.is_closing()
-
-
-async def wait_closed(writer: asyncio.StreamWriter) -> None:
-    """
-    Python 3.6-compatible `asyncio.StreamWriter.wait_closed` wrapper.
-
-    :param writer: The `asyncio.StreamWriter` to wait on.
-    """
-    if sys.version_info >= (3, 7):
-        await writer.wait_closed()
-        return
-
-    # Python 3.6
-    transport = writer.transport
-    assert isinstance(transport, asyncio.WriteTransport)
-
-    while not transport.is_closing():
-        await asyncio.sleep(0)
-
-    # This is an ugly workaround, but it's the best I can come up with.
-    sock = transport.get_extra_info('socket')
-
-    if sock is None:
-        # Our transport doesn't have a socket? ...
-        # Nothing we can reasonably do.
-        return
-
-    while sock.fileno() != -1:
-        await asyncio.sleep(0)
-
-
-def asyncio_run(coro: Coroutine[Any, Any, T], *, debug: bool = False) -> T:
-    """
-    Python 3.6-compatible `asyncio.run` wrapper.
-
-    :param coro: A coroutine to execute now.
-    :return: The return value from the coroutine.
-    """
-    if sys.version_info >= (3, 7):
-        return asyncio.run(coro, debug=debug)
-
-    # Python 3.6
-    loop = asyncio.get_event_loop()
-    loop.set_debug(debug)
-    ret = loop.run_until_complete(coro)
-    loop.close()
-
-    return ret
-
-
 # ----------------------------
 # Section: Logging & Debugging
 # ----------------------------
@@ -177,8 +105,11 @@ def exception_summary(exc: BaseException) -> str:
     """
     Return a summary string of an arbitrary exception.
 
-    It will be of the form "ExceptionType: Error Message", if the error
+    It will be of the form "ExceptionType: Error Message" if the error
     string is non-empty, and just "ExceptionType" otherwise.
+
+    This code is based on CPython's implementation of
+    `traceback.TracebackException.format_exception_only`.
     """
     name = type(exc).__qualname__
     smod = type(exc).__module__
diff --git a/python/tests/protocol.py b/python/tests/protocol.py
index 56c4d441f9..e565802516 100644
--- a/python/tests/protocol.py
+++ b/python/tests/protocol.py
@@ -8,7 +8,6 @@ import avocado
 
 from qemu.qmp import ConnectError, Runstate
 from qemu.qmp.protocol import AsyncProtocol, StateError
-from qemu.qmp.util import asyncio_run, create_task
 
 
 class NullProtocol(AsyncProtocol[None]):
@@ -124,7 +123,7 @@ def run_as_task(coro, allow_cancellation=False):
             if allow_cancellation:
                 return
             raise
-    return create_task(_runner())
+    return asyncio.create_task(_runner())
 
 
 @contextmanager
@@ -228,7 +227,7 @@ class TestBase(avocado.Test):
         Decorator; adds SetUp and TearDown to async tests.
         """
         async def _wrapper(self, *args, **kwargs):
-            loop = asyncio.get_event_loop()
+            loop = asyncio.get_running_loop()
             loop.set_debug(True)
 
             await self._asyncSetUp()
@@ -271,7 +270,7 @@ class TestBase(avocado.Test):
                     msg=f"Expected state '{state.name}'",
                 )
 
-        self.runstate_watcher = create_task(_watcher())
+        self.runstate_watcher = asyncio.create_task(_watcher())
         # Kick the loop and force the task to block on the event.
         await asyncio.sleep(0)
 
@@ -589,7 +588,8 @@ class SimpleSession(TestBase):
     async def testSmoke(self):
         with TemporaryDirectory(suffix='.qmp') as tmpdir:
             sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock")
-            server_task = create_task(self.server.start_server_and_accept(sock))
+            server_task = asyncio.create_task(
+                self.server.start_server_and_accept(sock))
 
             # give the server a chance to start listening [...]
             await asyncio.sleep(0)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 833f20f555..91616c974f 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -1368,6 +1368,9 @@ sub checkspdx {
     $expr =~ s/^\s*//g;
     $expr =~ s/\s*$//g;
 
+    # Cull C comment end
+    $expr =~ s/\*\/.*//;
+
     my @bits = split / +/, $expr;
 
     my $prefer = "GPL-2.0-or-later";
diff --git a/scripts/tracetool/__init__.py b/scripts/tracetool/__init__.py
index 2ae2e562d6..1d5238a084 100644
--- a/scripts/tracetool/__init__.py
+++ b/scripts/tracetool/__init__.py
@@ -170,10 +170,16 @@ class Arguments:
 
     def __str__(self):
         """String suitable for declaring function arguments."""
+        def onearg(t, n):
+            if t[-1] == '*':
+                return "".join([t, n])
+            else:
+                return " ".join([t, n])
+
         if len(self._args) == 0:
             return "void"
         else:
-            return ", ".join([ " ".join([t, n]) for t,n in self._args ])
+            return ", ".join([ onearg(t, n) for t,n in self._args ])
 
     def __repr__(self):
         """Evaluable string representation for this object."""
@@ -332,7 +338,6 @@ class Event(object):
         return self._FMT.findall(self.fmt)
 
     QEMU_TRACE               = "trace_%(name)s"
-    QEMU_TRACE_NOCHECK       = "_nocheck__" + QEMU_TRACE
     QEMU_TRACE_TCG           = QEMU_TRACE + "_tcg"
     QEMU_DSTATE              = "_TRACE_%(NAME)s_DSTATE"
     QEMU_BACKEND_DSTATE      = "TRACE_%(NAME)s_BACKEND_DSTATE"
diff --git a/scripts/tracetool/format/c.py b/scripts/tracetool/format/c.py
index 69edf0d588..e473fb6c6e 100644
--- a/scripts/tracetool/format/c.py
+++ b/scripts/tracetool/format/c.py
@@ -22,6 +22,7 @@ def generate(events, backend, group):
     header = "trace-" + group + ".h"
 
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '',
         '#include "qemu/osdep.h"',
         '#include "qemu/module.h"',
@@ -36,7 +37,7 @@ def generate(events, backend, group):
             '    .id = 0,',
             '    .name = \"%(name)s\",',
             '    .sstate = %(sstate)s,',
-            '    .dstate = &%(dstate)s ',
+            '    .dstate = &%(dstate)s',
             '};',
             event = e.api(e.QEMU_EVENT),
             name = e.name,
diff --git a/scripts/tracetool/format/d.py b/scripts/tracetool/format/d.py
index ebfb714200..a5e096e214 100644
--- a/scripts/tracetool/format/d.py
+++ b/scripts/tracetool/format/d.py
@@ -39,7 +39,8 @@ def generate(events, backend, group):
     if not events and platform != "darwin":
         return
 
-    out('/* This file is autogenerated by tracetool, do not edit. */'
+    out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '',
         'provider qemu {')
 
diff --git a/scripts/tracetool/format/h.py b/scripts/tracetool/format/h.py
index ea126b07ea..b42a8268a8 100644
--- a/scripts/tracetool/format/h.py
+++ b/scripts/tracetool/format/h.py
@@ -19,6 +19,7 @@ def generate(events, backend, group):
     header = "trace/control.h"
 
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '',
         '#ifndef TRACE_%s_GENERATED_TRACERS_H' % group.upper(),
         '#define TRACE_%s_GENERATED_TRACERS_H' % group.upper(),
@@ -63,7 +64,7 @@ def generate(events, backend, group):
         out('',
             'static inline void %(api)s(%(args)s)',
             '{',
-            api=e.api(e.QEMU_TRACE_NOCHECK),
+            api=e.api(),
             args=e.args)
 
         if "disable" not in e.properties:
@@ -71,20 +72,6 @@ def generate(events, backend, group):
 
         out('}')
 
-        cond = "true"
-
-        out('',
-            'static inline void %(api)s(%(args)s)',
-            '{',
-            '    if (%(cond)s) {',
-            '        %(api_nocheck)s(%(names)s);',
-            '    }',
-            '}',
-            api=e.api(),
-            api_nocheck=e.api(e.QEMU_TRACE_NOCHECK),
-            args=e.args,
-            names=", ".join(e.args.names()),
-            cond=cond)
 
     backend.generate_end(events, group)
 
diff --git a/scripts/tracetool/format/log_stap.py b/scripts/tracetool/format/log_stap.py
index b49afababd..710d62bffe 100644
--- a/scripts/tracetool/format/log_stap.py
+++ b/scripts/tracetool/format/log_stap.py
@@ -88,6 +88,7 @@ def c_fmt_to_stap(fmt):
 
 def generate(events, backend, group):
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '')
 
     for event_id, e in enumerate(events):
diff --git a/scripts/tracetool/format/simpletrace_stap.py b/scripts/tracetool/format/simpletrace_stap.py
index 4f4633b4e6..72971133bf 100644
--- a/scripts/tracetool/format/simpletrace_stap.py
+++ b/scripts/tracetool/format/simpletrace_stap.py
@@ -22,6 +22,7 @@ def global_var_name(name):
 
 def generate(events, backend, group):
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '')
 
     for event_id, e in enumerate(events):
diff --git a/scripts/tracetool/format/stap.py b/scripts/tracetool/format/stap.py
index a218b0445c..4d77fbc11a 100644
--- a/scripts/tracetool/format/stap.py
+++ b/scripts/tracetool/format/stap.py
@@ -38,6 +38,7 @@ def generate(events, backend, group):
               if "disable" not in e.properties]
 
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '')
 
     for e in events:
diff --git a/scripts/tracetool/format/ust_events_c.py b/scripts/tracetool/format/ust_events_c.py
index deced9533d..569754a304 100644
--- a/scripts/tracetool/format/ust_events_c.py
+++ b/scripts/tracetool/format/ust_events_c.py
@@ -20,6 +20,7 @@ def generate(events, backend, group):
               if "disabled" not in e.properties]
 
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '',
         '#include "qemu/osdep.h"',
         '',
diff --git a/scripts/tracetool/format/ust_events_h.py b/scripts/tracetool/format/ust_events_h.py
index b99fe6896b..2a31fefeca 100644
--- a/scripts/tracetool/format/ust_events_h.py
+++ b/scripts/tracetool/format/ust_events_h.py
@@ -25,6 +25,7 @@ def generate(events, backend, group):
         include = "trace-ust.h"
 
     out('/* This file is autogenerated by tracetool, do not edit. */',
+        '/* SPDX-License-Identifier: GPL-2.0-or-later */',
         '',
         '#undef TRACEPOINT_PROVIDER',
         '#define TRACEPOINT_PROVIDER qemu',
diff --git a/system/memory.c b/system/memory.c
index 44701c465c..cf8cad6961 100644
--- a/system/memory.c
+++ b/system/memory.c
@@ -1796,16 +1796,37 @@ static void memory_region_finalize(Object *obj)
 {
     MemoryRegion *mr = MEMORY_REGION(obj);
 
-    assert(!mr->container);
-
-    /* We know the region is not visible in any address space (it
-     * does not have a container and cannot be a root either because
-     * it has no references, so we can blindly clear mr->enabled.
-     * memory_region_set_enabled instead could trigger a transaction
-     * and cause an infinite loop.
+    /*
+     * Each memory region (that can be freed) must have an owner, and it
+     * always has the same lifecycle of its owner.  It means when reaching
+     * here, the memory region's owner's refcount is zero.
+     *
+     * Here it is possible that the MR has:
+     *
+     * (1) mr->container set, which means this MR is a subregion of a
+     *     container MR. In this case they must share the same owner as the
+     *     container (otherwise the container should have kept a refcount
+     *     of this MR's owner).
+     *
+     * (2) mr->subregions non-empty, which means this MR is a container of
+     *     one or more other MRs (which might have the the owner as this
+     *     MR, or a different owner).
+     *
+     * We know the MR, or any MR that is attached to this one as either
+     * container or children, is not visible in any address space, because
+     * otherwise the address space should have taken at least one refcount
+     * of this MR's owner.  So we can blindly clear mr->enabled.
+     *
+     * memory_region_set_enabled instead could trigger a transaction and
+     * cause an infinite loop.
      */
     mr->enabled = false;
     memory_region_transaction_begin();
+    if (mr->container) {
+        /* Must share the owner; see above comments */
+        assert(mr->container->owner == mr->owner);
+        memory_region_del_subregion(mr->container, mr);
+    }
     while (!QTAILQ_EMPTY(&mr->subregions)) {
         MemoryRegion *subregion = QTAILQ_FIRST(&mr->subregions);
         memory_region_del_subregion(mr, subregion);
@@ -2640,7 +2661,10 @@ static void memory_region_update_container_subregions(MemoryRegion *subregion)
 
     memory_region_transaction_begin();
 
-    memory_region_ref(subregion);
+    if (mr->owner != subregion->owner) {
+        memory_region_ref(subregion);
+    }
+
     QTAILQ_FOREACH(other, &mr->subregions, subregions_link) {
         if (subregion->priority >= other->priority) {
             QTAILQ_INSERT_BEFORE(other, subregion, subregions_link);
@@ -2698,7 +2722,11 @@ void memory_region_del_subregion(MemoryRegion *mr,
         assert(alias->mapped_via_alias >= 0);
     }
     QTAILQ_REMOVE(&mr->subregions, subregion, subregions_link);
-    memory_region_unref(subregion);
+
+    if (mr->owner != subregion->owner) {
+        memory_region_unref(subregion);
+    }
+
     memory_region_update_pending |= mr->enabled && subregion->enabled;
     memory_region_transaction_commit();
 }
diff --git a/system/physmem.c b/system/physmem.c
index f498572fc8..ddd58e9eb8 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -3027,7 +3027,7 @@ static MemTxResult flatview_write(FlatView *fv, hwaddr addr, MemTxAttrs attrs,
 
     l = len;
     mr = flatview_translate(fv, addr, &mr_addr, &l, true, attrs);
-    if (!flatview_access_allowed(mr, attrs, addr, len)) {
+    if (!flatview_access_allowed(mr, attrs, mr_addr, l)) {
         return MEMTX_ACCESS_ERROR;
     }
     return flatview_write_continue(fv, addr, attrs, buf, len,
@@ -3118,7 +3118,7 @@ static MemTxResult flatview_read(FlatView *fv, hwaddr addr,
 
     l = len;
     mr = flatview_translate(fv, addr, &mr_addr, &l, false, attrs);
-    if (!flatview_access_allowed(mr, attrs, addr, len)) {
+    if (!flatview_access_allowed(mr, attrs, mr_addr, l)) {
         return MEMTX_ACCESS_ERROR;
     }
     return flatview_read_continue(fv, addr, attrs, buf, len,
@@ -3231,8 +3231,10 @@ static inline MemTxResult address_space_write_rom_internal(AddressSpace *as,
             }
         }
         len -= l;
-        buf += l;
         addr += l;
+        if (buf) {
+            buf += l;
+        }
     }
     return MEMTX_OK;
 }
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 23fb722d42..3538c0c740 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -13,6 +13,7 @@ check-help:
 	@echo " $(MAKE) check-functional-TARGET  Run functional tests for a given target"
 	@echo " $(MAKE) check-unit               Run qobject tests"
 	@echo " $(MAKE) check-qapi-schema        Run QAPI schema tests"
+	@echo " $(MAKE) check-tracetool          Run tracetool generator tests"
 	@echo " $(MAKE) check-block              Run block tests"
 ifneq ($(filter $(all-check-targets), check-softfloat),)
 	@echo " $(MAKE) check-tcg                Run TCG tests"
diff --git a/tests/functional/aarch64/test_aspeed_ast2700.py b/tests/functional/aarch64/test_aspeed_ast2700.py
index d02dc7991c..8a08bc4688 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700.py
@@ -54,6 +54,10 @@ class AST2x00MachineSDK(QemuSystemTest):
             'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.06/ast2700-default-obmc.tar.gz',
             'f1d53e0be8a404ecce3e105f72bc50fa4e090ad13160ffa91b10a6e0233a9dc6')
 
+    ASSET_SDK_V907_AST2700A1_VBOOROM = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.07/ast2700-default-obmc.tar.gz',
+            '6e9e0c4b13e0f26040eca3f4a7f17cf09fc0f5c37c820500ff79370cc3c44add')
+
     def do_ast2700_i2c_test(self):
         exec_command_and_wait_for_pattern(self,
             'echo lm75 0x4d > /sys/class/i2c-dev/i2c-1/device/new_device ',
@@ -127,10 +131,10 @@ class AST2x00MachineSDK(QemuSystemTest):
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700_i2c_test()
 
-    def test_aarch64_ast2700a1_evb_sdk_vbootrom_v09_06(self):
+    def test_aarch64_ast2700a1_evb_sdk_vbootrom_v09_07(self):
         self.set_machine('ast2700a1-evb')
 
-        self.archive_extract(self.ASSET_SDK_V906_AST2700A1)
+        self.archive_extract(self.ASSET_SDK_V907_AST2700A1_VBOOROM)
         self.start_ast2700_test_vbootrom('ast2700-default')
         self.verify_vbootrom_firmware_flow()
         self.verify_openbmc_boot_and_login('ast2700-default')
diff --git a/tests/functional/aarch64/test_hotplug_pci.py b/tests/functional/aarch64/test_hotplug_pci.py
index 0c67991b89..bf67720431 100755
--- a/tests/functional/aarch64/test_hotplug_pci.py
+++ b/tests/functional/aarch64/test_hotplug_pci.py
@@ -15,14 +15,14 @@ from qemu_test import BUILD_DIR
 class HotplugPCI(LinuxKernelTest):
 
     ASSET_KERNEL = Asset(
-        ('https://ftp.debian.org/debian/dists/bookworm/main/installer-arm64/'
-         '20230607+deb12u11/images/netboot/debian-installer/arm64/linux'),
-         'd92a60392ce1e379ca198a1a820899f8f0d39a62d047c41ab79492f81541a9d9')
+        ('https://ftp.debian.org/debian/dists/trixie/main/installer-arm64/'
+         '20250803/images/netboot/debian-installer/arm64/linux'),
+         '93a6e4f9627d759375d28f863437a86a0659e125792a435f8e526dda006b7d5e')
 
     ASSET_INITRD = Asset(
-        ('https://ftp.debian.org/debian/dists/bookworm/main/installer-arm64/'
-         '20230607+deb12u11/images/netboot/debian-installer/arm64/initrd.gz'),
-         '9f817f76951f3237bca8216bee35267bfb826815687f4b2fcdd5e6c2a917790c')
+        ('https://ftp.debian.org/debian/dists/trixie/main/installer-arm64/'
+         '20250803/images/netboot/debian-installer/arm64/initrd.gz'),
+         'f6c78af7078ca67638ef3a50c926cd3c1485673243f8b37952e6bd854d6ba007')
 
     def test_hotplug_pci(self):
 
diff --git a/tests/functional/arm/test_aspeed_ast1030.py b/tests/functional/arm/test_aspeed_ast1030.py
index 77037f0179..42126b514f 100755
--- a/tests/functional/arm/test_aspeed_ast1030.py
+++ b/tests/functional/arm/test_aspeed_ast1030.py
@@ -12,17 +12,17 @@ from qemu_test import exec_command_and_wait_for_pattern
 
 class AST1030Machine(LinuxKernelTest):
 
-    ASSET_ZEPHYR_3_00 = Asset(
+    ASSET_ZEPHYR_3_02 = Asset(
         ('https://github.com/AspeedTech-BMC'
-         '/zephyr/releases/download/v00.03.00/ast1030-evb-demo.zip'),
-        '37fe3ecd4a1b9d620971a15b96492a81093435396eeac69b6f3e384262ff555f')
+         '/zephyr/releases/download/v00.03.02/ast1030-evb-demo.zip'),
+        '1ec83caab3ddd5d09481772801be7210e222cb015ce22ec6fffb8a76956dcd4f')
 
-    def test_ast1030_zephyros_3_00(self):
+    def test_ast1030_zephyros_3_02(self):
         self.set_machine('ast1030-evb')
 
-        kernel_name = "ast1030-evb-demo/zephyr.elf"
+        kernel_name = "ast1030-evb-demo-3/zephyr.elf"
         kernel_file = self.archive_extract(
-            self.ASSET_ZEPHYR_3_00, member=kernel_name)
+            self.ASSET_ZEPHYR_3_02, member=kernel_name)
 
         self.vm.set_console()
         self.vm.add_args('-kernel', kernel_file, '-nographic')
diff --git a/tests/functional/arm/test_aspeed_ast2500.py b/tests/functional/arm/test_aspeed_ast2500.py
index 6923fe8701..4fdd81e2f9 100755
--- a/tests/functional/arm/test_aspeed_ast2500.py
+++ b/tests/functional/arm/test_aspeed_ast2500.py
@@ -37,14 +37,14 @@ class AST2500Machine(AspeedTest):
 
         self.do_test_arm_aspeed_buildroot_poweroff()
 
-    ASSET_SDK_V906_AST2500 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.06/ast2500-default-obmc.tar.gz',
-        '542db84645b4efd8aed50385d7f4dd1caff379a987032311cfa7b563a3addb2a')
+    ASSET_SDK_V907_AST2500 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.07/ast2500-default-obmc.tar.gz',
+        'd52bcc279a37c8d7679b3e4ef22cc77c36f0f6624c687b37334f798828afb077')
 
     def test_arm_ast2500_evb_sdk(self):
         self.set_machine('ast2500-evb')
 
-        self.archive_extract(self.ASSET_SDK_V906_AST2500)
+        self.archive_extract(self.ASSET_SDK_V907_AST2500)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2500-default", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2600.py b/tests/functional/arm/test_aspeed_ast2600.py
index fdae4c939d..129695ca4e 100755
--- a/tests/functional/arm/test_aspeed_ast2600.py
+++ b/tests/functional/arm/test_aspeed_ast2600.py
@@ -97,14 +97,14 @@ class AST2600Machine(AspeedTest):
 
         self.do_test_arm_aspeed_buildroot_poweroff()
 
-    ASSET_SDK_V906_AST2600 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.06/ast2600-default-obmc.tar.gz',
-        '768d76e247896ad78c154b9cff4f766da2ce65f217d620b286a4a03a8a4f68f5')
+    ASSET_SDK_V907_AST2600 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.07/ast2600-default-obmc.tar.gz',
+        'cb6c08595bcbba1672ce716b068ba4e48eda1ed9abe78a07b30392ba2278feba')
 
     def test_arm_ast2600_evb_sdk(self):
         self.set_machine('ast2600-evb')
 
-        self.archive_extract(self.ASSET_SDK_V906_AST2600)
+        self.archive_extract(self.ASSET_SDK_V907_AST2600)
 
         self.vm.add_args('-device',
             'tmp105,bus=aspeed.i2c.bus.5,address=0x4d,id=tmp-test')
diff --git a/tests/functional/m68k/test_nextcube.py b/tests/functional/m68k/test_nextcube.py
index 13c72bd136..c1610e5845 100755
--- a/tests/functional/m68k/test_nextcube.py
+++ b/tests/functional/m68k/test_nextcube.py
@@ -44,7 +44,8 @@ class NextCubeMachine(QemuSystemTest):
         self.check_bootrom_framebuffer(screenshot_path)
 
         from PIL import Image
-        width, height = Image.open(screenshot_path).size
+        with Image.open(screenshot_path) as image:
+                width, height = image.size
         self.assertEqual(width, 1120)
         self.assertEqual(height, 832)
 
diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py
index 704b84d0ea..2dd32bf28d 100644
--- a/tests/functional/qemu_test/asset.py
+++ b/tests/functional/qemu_test/asset.py
@@ -15,7 +15,7 @@ import urllib.request
 from time import sleep
 from pathlib import Path
 from shutil import copyfileobj
-from urllib.error import HTTPError
+from urllib.error import HTTPError, URLError
 
 class AssetError(Exception):
     def __init__(self, asset, msg, transient=False):
@@ -72,6 +72,10 @@ class Asset:
         return self.hash == hl.hexdigest()
 
     def valid(self):
+        if os.getenv("QEMU_TEST_REFRESH_CACHE", None) is not None:
+            self.log.info("Force refresh of asset %s", self.url)
+            return False
+
         return self.cache_file.exists() and self._check(self.cache_file)
 
     def fetchable(self):
@@ -167,9 +171,17 @@ class Asset:
                     raise AssetError(self, "Unable to download: "
                                      "HTTP error %d" % e.code)
                 continue
+            except URLError as e:
+                # This is typically a network/service level error
+                # eg urlopen error [Errno 110] Connection timed out>
+                tmp_cache_file.unlink()
+                self.log.error("Unable to download %s: URL error %s",
+                               self.url, e.reason)
+                raise AssetError(self, "Unable to download: URL error %s" %
+                                 e.reason, transient=True)
             except Exception as e:
                 tmp_cache_file.unlink()
-                raise AssetError(self, "Unable to download: " % e)
+                raise AssetError(self, "Unable to download: %s" % e)
 
         if not os.path.exists(tmp_cache_file):
             raise AssetError(self, "Download retries exceeded", transient=True)
diff --git a/tests/functional/qemu_test/cmd.py b/tests/functional/qemu_test/cmd.py
index dc5f422b77..f544566245 100644
--- a/tests/functional/qemu_test/cmd.py
+++ b/tests/functional/qemu_test/cmd.py
@@ -45,13 +45,16 @@ def is_readable_executable_file(path):
 # If end of line is seen, with neither @success or @failure
 # return False
 #
+# In both cases, also return the contents of the line (in bytes)
+# up to that point.
+#
 # If @failure is seen, then mark @test as failed
 def _console_read_line_until_match(test, vm, success, failure):
     msg = bytes([])
     done = False
     while True:
         c = vm.console_socket.recv(1)
-        if c is None:
+        if not c:
             done = True
             test.fail(
                 f"EOF in console, expected '{success}'")
@@ -76,10 +79,23 @@ def _console_read_line_until_match(test, vm, success, failure):
     except:
         console_logger.debug(msg)
 
-    return done
+    return done, msg
 
 def _console_interaction(test, success_message, failure_message,
                          send_string, keep_sending=False, vm=None):
+    """
+    Interact with the console until either message is seen.
+
+    :param success_message: if this message appears, finish interaction
+    :param failure_message: if this message appears, test fails
+    :param send_string: a string to send to the console before trying
+                        to read a new line
+    :param keep_sending: keep sending the send string each time
+    :param vm: the VM to interact with
+
+    :return: The collected output (in bytes form).
+    """
+
     assert not keep_sending or send_string
     assert success_message or send_string
 
@@ -101,6 +117,8 @@ def _console_interaction(test, success_message, failure_message,
     if failure_message is not None:
         failure_message_b = failure_message.encode()
 
+    out = bytes([])
+
     while True:
         if send_string:
             vm.console_socket.sendall(send_string.encode())
@@ -113,14 +131,21 @@ def _console_interaction(test, success_message, failure_message,
                 break
             continue
 
-        if _console_read_line_until_match(test, vm,
-                                          success_message_b,
-                                          failure_message_b):
+        done, line = _console_read_line_until_match(test, vm,
+                                                    success_message_b,
+                                                    failure_message_b)
+
+        out += line
+
+        if done:
             break
 
+    return out
+
 def interrupt_interactive_console_until_pattern(test, success_message,
                                                 failure_message=None,
-                                                interrupt_string='\r'):
+                                                interrupt_string='\r',
+                                                vm=None):
     """
     Keep sending a string to interrupt a console prompt, while logging the
     console output. Typical use case is to break a boot loader prompt, such:
@@ -140,10 +165,13 @@ def interrupt_interactive_console_until_pattern(test, success_message,
     :param failure_message: if this message appears, test fails
     :param interrupt_string: a string to send to the console before trying
                              to read a new line
+    :param vm: VM to use
+
+    :return: The collected output (in bytes form).
     """
     assert success_message
-    _console_interaction(test, success_message, failure_message,
-                         interrupt_string, True)
+    return _console_interaction(test, success_message, failure_message,
+                                interrupt_string, True, vm=vm)
 
 def wait_for_console_pattern(test, success_message, failure_message=None,
                              vm=None):
@@ -155,11 +183,15 @@ def wait_for_console_pattern(test, success_message, failure_message=None,
     :type test: :class:`qemu_test.QemuSystemTest`
     :param success_message: if this message appears, test succeeds
     :param failure_message: if this message appears, test fails
+    :param vm: VM to use
+
+    :return: The collected output (in bytes form).
     """
     assert success_message
-    _console_interaction(test, success_message, failure_message, None, vm=vm)
+    return _console_interaction(test, success_message, failure_message,
+                                None, vm=vm)
 
-def exec_command(test, command):
+def exec_command(test, command, vm=None):
     """
     Send a command to a console (appending CRLF characters), while logging
     the content.
@@ -167,12 +199,16 @@ def exec_command(test, command):
     :param test: a test containing a VM.
     :type test: :class:`qemu_test.QemuSystemTest`
     :param command: the command to send
+    :param vm: VM to use
     :type command: str
+
+    :return: The collected output (in bytes form).
     """
-    _console_interaction(test, None, None, command + '\r')
+    return _console_interaction(test, None, None, command + '\r', vm=vm)
 
 def exec_command_and_wait_for_pattern(test, command,
-                                      success_message, failure_message=None):
+                                      success_message, failure_message=None,
+                                      vm=None):
     """
     Send a command to a console (appending CRLF characters), then wait
     for success_message to appear on the console, while logging the.
@@ -184,9 +220,14 @@ def exec_command_and_wait_for_pattern(test, command,
     :param command: the command to send
     :param success_message: if this message appears, test succeeds
     :param failure_message: if this message appears, test fails
+    :param vm: VM to use
+
+    :return: The collected output (in bytes form).
     """
     assert success_message
-    _console_interaction(test, success_message, failure_message, command + '\r')
+
+    return _console_interaction(test, success_message, failure_message,
+                                command + '\r', vm=vm)
 
 def get_qemu_img(test):
     test.log.debug('Looking for and selecting a qemu-img binary')
diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py
index fbeb171058..2c0abde395 100644
--- a/tests/functional/qemu_test/testcase.py
+++ b/tests/functional/qemu_test/testcase.py
@@ -205,6 +205,10 @@ class QemuBaseTest(unittest.TestCase):
         self.outputdir = self.build_file('tests', 'functional',
                                          self.arch, self.id())
         self.workdir = os.path.join(self.outputdir, 'scratch')
+        if os.path.exists(self.workdir):
+            # Purge as safety net in case of unclean termination of
+            # previous test, or use of QEMU_TEST_KEEP_SCRATCH
+            shutil.rmtree(self.workdir)
         os.makedirs(self.workdir, exist_ok=True)
 
         self.log_filename = self.log_file('base.log')
@@ -251,13 +255,14 @@ class QemuBaseTest(unittest.TestCase):
                                    test_output_log = pycotap.LogMode.LogToError)
         res = unittest.main(module = None, testRunner = tr, exit = False,
                             argv=[sys.argv[0], path] + sys.argv[1:])
+        failed = {}
         for (test, message) in res.result.errors + res.result.failures:
-
-            if hasattr(test, "log_filename"):
+            if hasattr(test, "log_filename") and not test.id() in failed:
                 print('More information on ' + test.id() + ' could be found here:'
                       '\n %s' % test.log_filename, file=sys.stderr)
                 if hasattr(test, 'console_log_name'):
                     print(' %s' % test.console_log_name, file=sys.stderr)
+                failed[test.id()] = True
         sys.exit(not res.result.wasSuccessful())
 
 
@@ -403,7 +408,10 @@ class QemuSystemTest(QemuBaseTest):
 
     def tearDown(self):
         for vm in self._vms.values():
-            vm.shutdown()
+            try:
+                vm.shutdown()
+            except Exception as ex:
+                self.log.error("Failed to teardown VM: %s" % ex)
         logging.getLogger('console').removeHandler(self._console_log_fh)
         self._console_log_fh.close()
         super().tearDown()
diff --git a/tests/functional/x86_64/test_memlock.py b/tests/functional/x86_64/test_memlock.py
index 2b515ff979..81bce80b0c 100755
--- a/tests/functional/x86_64/test_memlock.py
+++ b/tests/functional/x86_64/test_memlock.py
@@ -37,7 +37,8 @@ class MemlockTest(QemuSystemTest):
 
         status = self.get_process_status_values(self.vm.get_pid())
 
-        self.assertTrue(status['VmLck'] == 0)
+        # libgcrypt may mlock a few pages
+        self.assertTrue(status['VmLck'] < 32)
 
     def test_memlock_on(self):
         self.common_vm_setup_with_memlock('on')
diff --git a/tests/meson.build b/tests/meson.build
index c59619220f..cbe7916241 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -88,3 +88,4 @@ subdir('qapi-schema')
 subdir('qtest')
 subdir('migration-stress')
 subdir('functional')
+subdir('tracetool')
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 4be930228c..cf7fb8a6df 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -165,7 +165,7 @@ def test_and_diff(test_name, dir_name, update):
     if actual_out == expected_out and actual_err == expected_err:
         return 0
 
-    print("%s %s" % (test_name, 'UPDATE' if update else 'FAIL'),
+    print("%s: %s" % (test_name, 'UPDATE' if update else 'FAIL'),
           file=sys.stderr)
     out_diff = difflib.unified_diff(expected_out, actual_out, outfp.name)
     err_diff = difflib.unified_diff(expected_err, actual_err, errfp.name)
@@ -173,6 +173,9 @@ def test_and_diff(test_name, dir_name, update):
     sys.stdout.writelines(err_diff)
 
     if not update:
+        print(("\n%s: set QEMU_TEST_REGENERATE=1 to recreate reference output" +
+               "if the QAPI schema generator was intentionally changed") % test_name,
+              file=sys.stderr)
         return 1
 
     try:
@@ -197,7 +200,7 @@ def main(argv):
     parser.add_argument('-d', '--dir', action='store', default='',
                         help="directory containing tests")
     parser.add_argument('-u', '--update', action='store_true',
-                        default='QAPI_TEST_UPDATE' in os.environ,
+                        default='QEMU_TEST_REGENERATE' in os.environ,
                         help="update expected test results")
     parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
     args = parser.parse_args()
diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147
index 6d6f077a14..3e14bd389a 100755
--- a/tests/qemu-iotests/147
+++ b/tests/qemu-iotests/147
@@ -277,6 +277,7 @@ class BuiltinNBD(NBDBlockdevAddBase):
                      } }
         self.client_test(filename, flatten_sock_addr(address), 'nbd-export')
 
+        sockfd.close()
         self._server_down()
 
 
diff --git a/tests/qemu-iotests/151 b/tests/qemu-iotests/151
index f2ff9c5dac..06ee3585db 100755
--- a/tests/qemu-iotests/151
+++ b/tests/qemu-iotests/151
@@ -263,6 +263,11 @@ class TestThrottledWithNbdExportBase(iotests.QMPTestCase):
                         break
                     except subprocess.TimeoutExpired:
                         self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
+                try:
+                    p.kill()
+                    p.stdout.close()
+                except:
+                    pass
         except IndexError:
             pass
 
diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check
index 545f9ec7bd..d9b7c1d598 100755
--- a/tests/qemu-iotests/check
+++ b/tests/qemu-iotests/check
@@ -21,6 +21,7 @@ import sys
 import argparse
 import shutil
 from pathlib import Path
+import warnings
 
 from findtests import TestFinder
 from testenv import TestEnv
@@ -137,6 +138,9 @@ def make_argparser() -> argparse.ArgumentParser:
 
 
 if __name__ == '__main__':
+    warnings.simplefilter("default")
+    os.environ["PYTHONWARNINGS"] = "default"
+
     args = make_argparser().parse_args()
 
     env = TestEnv(source_dir=args.source_dir,
diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py
index 6326e46b7b..29caaa8a34 100644
--- a/tests/qemu-iotests/testenv.py
+++ b/tests/qemu-iotests/testenv.py
@@ -22,15 +22,12 @@ import tempfile
 from pathlib import Path
 import shutil
 import collections
+import contextlib
 import random
 import subprocess
 import glob
 from typing import List, Dict, Any, Optional
 
-if sys.version_info >= (3, 9):
-    from contextlib import AbstractContextManager as ContextManager
-else:
-    from typing import ContextManager
 
 DEF_GDB_OPTIONS = 'localhost:12345'
 
@@ -58,7 +55,7 @@ def get_default_machine(qemu_prog: str) -> str:
     return default_machine
 
 
-class TestEnv(ContextManager['TestEnv']):
+class TestEnv(contextlib.AbstractContextManager['TestEnv']):
     """
     Manage system environment for running tests
 
diff --git a/tests/qemu-iotests/testrunner.py b/tests/qemu-iotests/testrunner.py
index 2e236c8fa3..14cc8492f9 100644
--- a/tests/qemu-iotests/testrunner.py
+++ b/tests/qemu-iotests/testrunner.py
@@ -30,11 +30,6 @@ from multiprocessing import Pool
 from typing import List, Optional, Any, Sequence, Dict
 from testenv import TestEnv
 
-if sys.version_info >= (3, 9):
-    from contextlib import AbstractContextManager as ContextManager
-else:
-    from typing import ContextManager
-
 
 def silent_unlink(path: Path) -> None:
     try:
@@ -57,7 +52,7 @@ def file_diff(file1: str, file2: str) -> List[str]:
         return res
 
 
-class LastElapsedTime(ContextManager['LastElapsedTime']):
+class LastElapsedTime(contextlib.AbstractContextManager['LastElapsedTime']):
     """ Cache for elapsed time for tests, to show it during new test run
 
     It is safe to use get() at any time.  To use update(), you must either
@@ -112,7 +107,7 @@ class TestResult:
         self.interrupted = interrupted
 
 
-class TestRunner(ContextManager['TestRunner']):
+class TestRunner(contextlib.AbstractContextManager['TestRunner']):
     shared_self = None
 
     @staticmethod
diff --git a/tests/tracetool/dtrace.c b/tests/tracetool/dtrace.c
new file mode 100644
index 0000000000..9f862fa14d
--- /dev/null
+++ b/tests/tracetool/dtrace.c
@@ -0,0 +1,32 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "trace-testsuite.h"
+
+uint16_t _TRACE_TEST_BLAH_DSTATE;
+uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+TraceEvent _TRACE_TEST_BLAH_EVENT = {
+    .id = 0,
+    .name = "test_blah",
+    .sstate = TRACE_TEST_BLAH_ENABLED,
+    .dstate = &_TRACE_TEST_BLAH_DSTATE
+};
+TraceEvent _TRACE_TEST_WIBBLE_EVENT = {
+    .id = 0,
+    .name = "test_wibble",
+    .sstate = TRACE_TEST_WIBBLE_ENABLED,
+    .dstate = &_TRACE_TEST_WIBBLE_DSTATE
+};
+TraceEvent *testsuite_trace_events[] = {
+    &_TRACE_TEST_BLAH_EVENT,
+    &_TRACE_TEST_WIBBLE_EVENT,
+  NULL,
+};
+
+static void trace_testsuite_register_events(void)
+{
+    trace_event_register_group(testsuite_trace_events);
+}
+trace_init(trace_testsuite_register_events)
diff --git a/tests/tracetool/dtrace.d b/tests/tracetool/dtrace.d
new file mode 100644
index 0000000000..5cc06f9f4f
--- /dev/null
+++ b/tests/tracetool/dtrace.d
@@ -0,0 +1,10 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+provider qemu {
+
+probe test_blah(void * context,const char * filename);
+
+probe test_wibble(void * context,int value);
+
+};
diff --git a/tests/tracetool/dtrace.h b/tests/tracetool/dtrace.h
new file mode 100644
index 0000000000..c8931a8d7b
--- /dev/null
+++ b/tests/tracetool/dtrace.h
@@ -0,0 +1,45 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef TRACE_TESTSUITE_GENERATED_TRACERS_H
+#define TRACE_TESTSUITE_GENERATED_TRACERS_H
+
+#include "trace/control.h"
+
+extern TraceEvent _TRACE_TEST_BLAH_EVENT;
+extern TraceEvent _TRACE_TEST_WIBBLE_EVENT;
+extern uint16_t _TRACE_TEST_BLAH_DSTATE;
+extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+#define TRACE_TEST_BLAH_ENABLED 1
+#define TRACE_TEST_WIBBLE_ENABLED 1
+#ifndef SDT_USE_VARIADIC
+#define SDT_USE_VARIADIC 1
+#endif
+#include "trace-dtrace-testsuite.h"
+
+#undef SDT_USE_VARIADIC
+#ifndef QEMU_TEST_BLAH_ENABLED
+#define QEMU_TEST_BLAH_ENABLED() true
+#endif
+#ifndef QEMU_TEST_WIBBLE_ENABLED
+#define QEMU_TEST_WIBBLE_ENABLED() true
+#endif
+
+#define TRACE_TEST_BLAH_BACKEND_DSTATE() ( \
+    QEMU_TEST_BLAH_ENABLED() || \
+    false)
+
+static inline void trace_test_blah(void *context, const char *filename)
+{
+    QEMU_TEST_BLAH(context, filename);
+}
+
+#define TRACE_TEST_WIBBLE_BACKEND_DSTATE() ( \
+    QEMU_TEST_WIBBLE_ENABLED() || \
+    false)
+
+static inline void trace_test_wibble(void *context, int value)
+{
+    QEMU_TEST_WIBBLE(context, value);
+}
+#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/dtrace.log-stap b/tests/tracetool/dtrace.log-stap
new file mode 100644
index 0000000000..092986e0b6
--- /dev/null
+++ b/tests/tracetool/dtrace.log-stap
@@ -0,0 +1,15 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+probe qemu.log.test_blah = qemu.test_blah ?
+{
+    try {
+        argfilename_str = filename ? user_string_n(filename, 512) : "<null>"
+    } catch {}
+    printf("%d@%d test_blah Blah context=%p filename=%s\n", pid(), gettimeofday_ns(), context, argfilename_str)
+}
+probe qemu.log.test_wibble = qemu.test_wibble ?
+{
+    printf("%d@%d test_wibble Wibble context=%p value=%d\n", pid(), gettimeofday_ns(), context, value)
+}
+
diff --git a/tests/tracetool/dtrace.simpletrace-stap b/tests/tracetool/dtrace.simpletrace-stap
new file mode 100644
index 0000000000..d064e3e286
--- /dev/null
+++ b/tests/tracetool/dtrace.simpletrace-stap
@@ -0,0 +1,16 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+probe qemu.simpletrace.test_blah = qemu.test_blah ?
+{
+    try {
+        argfilename_str = filename ? user_string_n(filename, 512) : "<null>"
+    } catch {}
+    argfilename_len = strlen(argfilename_str)
+    printf("%8b%8b%8b%4b%4b%8b%4b%.*s", 1, 0, gettimeofday_ns(), 24 + 8 + 4 + argfilename_len, pid(), context, argfilename_len, argfilename_len, argfilename_str)
+}
+probe qemu.simpletrace.test_wibble = qemu.test_wibble ?
+{
+    printf("%8b%8b%8b%4b%4b%8b%8b", 1, 1, gettimeofday_ns(), 24 + 8 + 8, pid(), context, value)
+}
+
diff --git a/tests/tracetool/dtrace.stap b/tests/tracetool/dtrace.stap
new file mode 100644
index 0000000000..9c5d8a527c
--- /dev/null
+++ b/tests/tracetool/dtrace.stap
@@ -0,0 +1,14 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+probe qemu.test_blah = process("qemu").mark("test_blah")
+{
+  context = $arg1;
+  filename = $arg2;
+}
+probe qemu.test_wibble = process("qemu").mark("test_wibble")
+{
+  context = $arg1;
+  value = $arg2;
+}
+
diff --git a/tests/tracetool/ftrace.c b/tests/tracetool/ftrace.c
new file mode 100644
index 0000000000..9f862fa14d
--- /dev/null
+++ b/tests/tracetool/ftrace.c
@@ -0,0 +1,32 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "trace-testsuite.h"
+
+uint16_t _TRACE_TEST_BLAH_DSTATE;
+uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+TraceEvent _TRACE_TEST_BLAH_EVENT = {
+    .id = 0,
+    .name = "test_blah",
+    .sstate = TRACE_TEST_BLAH_ENABLED,
+    .dstate = &_TRACE_TEST_BLAH_DSTATE
+};
+TraceEvent _TRACE_TEST_WIBBLE_EVENT = {
+    .id = 0,
+    .name = "test_wibble",
+    .sstate = TRACE_TEST_WIBBLE_ENABLED,
+    .dstate = &_TRACE_TEST_WIBBLE_DSTATE
+};
+TraceEvent *testsuite_trace_events[] = {
+    &_TRACE_TEST_BLAH_EVENT,
+    &_TRACE_TEST_WIBBLE_EVENT,
+  NULL,
+};
+
+static void trace_testsuite_register_events(void)
+{
+    trace_event_register_group(testsuite_trace_events);
+}
+trace_init(trace_testsuite_register_events)
diff --git a/tests/tracetool/ftrace.h b/tests/tracetool/ftrace.h
new file mode 100644
index 0000000000..fe22ea0f09
--- /dev/null
+++ b/tests/tracetool/ftrace.h
@@ -0,0 +1,59 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef TRACE_TESTSUITE_GENERATED_TRACERS_H
+#define TRACE_TESTSUITE_GENERATED_TRACERS_H
+
+#include "trace/control.h"
+
+extern TraceEvent _TRACE_TEST_BLAH_EVENT;
+extern TraceEvent _TRACE_TEST_WIBBLE_EVENT;
+extern uint16_t _TRACE_TEST_BLAH_DSTATE;
+extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+#define TRACE_TEST_BLAH_ENABLED 1
+#define TRACE_TEST_WIBBLE_ENABLED 1
+#include "trace/ftrace.h"
+
+
+#define TRACE_TEST_BLAH_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_BLAH) || \
+    false)
+
+static inline void trace_test_blah(void *context, const char *filename)
+{
+    {
+        char ftrace_buf[MAX_TRACE_STRLEN];
+        int unused __attribute__ ((unused));
+        int trlen;
+        if (trace_event_get_state(TRACE_TEST_BLAH)) {
+#line 4 "trace-events"
+            trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,
+                             "test_blah " "Blah context=%p filename=%s" "\n" , context, filename);
+#line 33 "ftrace.h"
+            trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);
+            unused = write(trace_marker_fd, ftrace_buf, trlen);
+        }
+    }
+}
+
+#define TRACE_TEST_WIBBLE_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_WIBBLE) || \
+    false)
+
+static inline void trace_test_wibble(void *context, int value)
+{
+    {
+        char ftrace_buf[MAX_TRACE_STRLEN];
+        int unused __attribute__ ((unused));
+        int trlen;
+        if (trace_event_get_state(TRACE_TEST_WIBBLE)) {
+#line 5 "trace-events"
+            trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,
+                             "test_wibble " "Wibble context=%p value=%d" "\n" , context, value);
+#line 54 "ftrace.h"
+            trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);
+            unused = write(trace_marker_fd, ftrace_buf, trlen);
+        }
+    }
+}
+#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/log.c b/tests/tracetool/log.c
new file mode 100644
index 0000000000..9f862fa14d
--- /dev/null
+++ b/tests/tracetool/log.c
@@ -0,0 +1,32 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "trace-testsuite.h"
+
+uint16_t _TRACE_TEST_BLAH_DSTATE;
+uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+TraceEvent _TRACE_TEST_BLAH_EVENT = {
+    .id = 0,
+    .name = "test_blah",
+    .sstate = TRACE_TEST_BLAH_ENABLED,
+    .dstate = &_TRACE_TEST_BLAH_DSTATE
+};
+TraceEvent _TRACE_TEST_WIBBLE_EVENT = {
+    .id = 0,
+    .name = "test_wibble",
+    .sstate = TRACE_TEST_WIBBLE_ENABLED,
+    .dstate = &_TRACE_TEST_WIBBLE_DSTATE
+};
+TraceEvent *testsuite_trace_events[] = {
+    &_TRACE_TEST_BLAH_EVENT,
+    &_TRACE_TEST_WIBBLE_EVENT,
+  NULL,
+};
+
+static void trace_testsuite_register_events(void)
+{
+    trace_event_register_group(testsuite_trace_events);
+}
+trace_init(trace_testsuite_register_events)
diff --git a/tests/tracetool/log.h b/tests/tracetool/log.h
new file mode 100644
index 0000000000..edcc7f9d47
--- /dev/null
+++ b/tests/tracetool/log.h
@@ -0,0 +1,43 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef TRACE_TESTSUITE_GENERATED_TRACERS_H
+#define TRACE_TESTSUITE_GENERATED_TRACERS_H
+
+#include "trace/control.h"
+
+extern TraceEvent _TRACE_TEST_BLAH_EVENT;
+extern TraceEvent _TRACE_TEST_WIBBLE_EVENT;
+extern uint16_t _TRACE_TEST_BLAH_DSTATE;
+extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+#define TRACE_TEST_BLAH_ENABLED 1
+#define TRACE_TEST_WIBBLE_ENABLED 1
+#include "qemu/log-for-trace.h"
+
+
+#define TRACE_TEST_BLAH_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_BLAH) || \
+    false)
+
+static inline void trace_test_blah(void *context, const char *filename)
+{
+    if (trace_event_get_state(TRACE_TEST_BLAH) && qemu_loglevel_mask(LOG_TRACE)) {
+#line 4 "trace-events"
+        qemu_log("test_blah " "Blah context=%p filename=%s" "\n", context, filename);
+#line 28 "log.h"
+    }
+}
+
+#define TRACE_TEST_WIBBLE_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_WIBBLE) || \
+    false)
+
+static inline void trace_test_wibble(void *context, int value)
+{
+    if (trace_event_get_state(TRACE_TEST_WIBBLE) && qemu_loglevel_mask(LOG_TRACE)) {
+#line 5 "trace-events"
+        qemu_log("test_wibble " "Wibble context=%p value=%d" "\n", context, value);
+#line 41 "log.h"
+    }
+}
+#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/meson.build b/tests/tracetool/meson.build
new file mode 100644
index 0000000000..09bbaaa86b
--- /dev/null
+++ b/tests/tracetool/meson.build
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+test_env = environment()
+test_env.set('PYTHONPATH', meson.project_source_root() / 'scripts')
+test_env.set('PYTHONIOENCODING', 'utf-8')
+
+backends = [
+    'dtrace',
+    'ftrace',
+    'log',
+    'simple',
+    'syslog',
+    'ust'
+]
+
+# The tracetool-test.py program has portability problems on Windows.
+if host_machine.system() != 'windows'
+    foreach backend: backends
+        test(backend,
+             python,
+             args: [meson.current_source_dir() / 'tracetool-test.py',
+                    meson.project_source_root() / 'scripts' / 'tracetool.py',
+                    backend,
+                    meson.current_source_dir(),
+                    meson.current_build_dir()],
+             suite: ['tracetool'])
+    endforeach
+endif
diff --git a/tests/tracetool/simple.c b/tests/tracetool/simple.c
new file mode 100644
index 0000000000..0484177481
--- /dev/null
+++ b/tests/tracetool/simple.c
@@ -0,0 +1,61 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "trace-testsuite.h"
+
+uint16_t _TRACE_TEST_BLAH_DSTATE;
+uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+TraceEvent _TRACE_TEST_BLAH_EVENT = {
+    .id = 0,
+    .name = "test_blah",
+    .sstate = TRACE_TEST_BLAH_ENABLED,
+    .dstate = &_TRACE_TEST_BLAH_DSTATE
+};
+TraceEvent _TRACE_TEST_WIBBLE_EVENT = {
+    .id = 0,
+    .name = "test_wibble",
+    .sstate = TRACE_TEST_WIBBLE_ENABLED,
+    .dstate = &_TRACE_TEST_WIBBLE_DSTATE
+};
+TraceEvent *testsuite_trace_events[] = {
+    &_TRACE_TEST_BLAH_EVENT,
+    &_TRACE_TEST_WIBBLE_EVENT,
+  NULL,
+};
+
+static void trace_testsuite_register_events(void)
+{
+    trace_event_register_group(testsuite_trace_events);
+}
+trace_init(trace_testsuite_register_events)
+#include "qemu/osdep.h"
+#include "trace/control.h"
+#include "trace/simple.h"
+
+void _simple_trace_test_blah(void *context, const char *filename)
+{
+    TraceBufferRecord rec;
+    size_t argfilename_len = filename ? MIN(strlen(filename), MAX_TRACE_STRLEN) : 0;
+
+    if (trace_record_start(&rec, _TRACE_TEST_BLAH_EVENT.id, 8 + 4 + argfilename_len)) {
+        return; /* Trace Buffer Full, Event Dropped ! */
+    }
+    trace_record_write_u64(&rec, (uintptr_t)(uint64_t *)context);
+    trace_record_write_str(&rec, filename, argfilename_len);
+    trace_record_finish(&rec);
+}
+
+void _simple_trace_test_wibble(void *context, int value)
+{
+    TraceBufferRecord rec;
+
+    if (trace_record_start(&rec, _TRACE_TEST_WIBBLE_EVENT.id, 8 + 8)) {
+        return; /* Trace Buffer Full, Event Dropped ! */
+    }
+    trace_record_write_u64(&rec, (uintptr_t)(uint64_t *)context);
+    trace_record_write_u64(&rec, (uint64_t)value);
+    trace_record_finish(&rec);
+}
+
diff --git a/tests/tracetool/simple.h b/tests/tracetool/simple.h
new file mode 100644
index 0000000000..ec6fcb22c3
--- /dev/null
+++ b/tests/tracetool/simple.h
@@ -0,0 +1,40 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef TRACE_TESTSUITE_GENERATED_TRACERS_H
+#define TRACE_TESTSUITE_GENERATED_TRACERS_H
+
+#include "trace/control.h"
+
+extern TraceEvent _TRACE_TEST_BLAH_EVENT;
+extern TraceEvent _TRACE_TEST_WIBBLE_EVENT;
+extern uint16_t _TRACE_TEST_BLAH_DSTATE;
+extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+#define TRACE_TEST_BLAH_ENABLED 1
+#define TRACE_TEST_WIBBLE_ENABLED 1
+void _simple_trace_test_blah(void *context, const char *filename);
+void _simple_trace_test_wibble(void *context, int value);
+
+
+#define TRACE_TEST_BLAH_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_BLAH) || \
+    false)
+
+static inline void trace_test_blah(void *context, const char *filename)
+{
+    if (trace_event_get_state(TRACE_TEST_BLAH)) {
+        _simple_trace_test_blah(context, filename);
+    }
+}
+
+#define TRACE_TEST_WIBBLE_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_WIBBLE) || \
+    false)
+
+static inline void trace_test_wibble(void *context, int value)
+{
+    if (trace_event_get_state(TRACE_TEST_WIBBLE)) {
+        _simple_trace_test_wibble(context, value);
+    }
+}
+#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/syslog.c b/tests/tracetool/syslog.c
new file mode 100644
index 0000000000..9f862fa14d
--- /dev/null
+++ b/tests/tracetool/syslog.c
@@ -0,0 +1,32 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "trace-testsuite.h"
+
+uint16_t _TRACE_TEST_BLAH_DSTATE;
+uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+TraceEvent _TRACE_TEST_BLAH_EVENT = {
+    .id = 0,
+    .name = "test_blah",
+    .sstate = TRACE_TEST_BLAH_ENABLED,
+    .dstate = &_TRACE_TEST_BLAH_DSTATE
+};
+TraceEvent _TRACE_TEST_WIBBLE_EVENT = {
+    .id = 0,
+    .name = "test_wibble",
+    .sstate = TRACE_TEST_WIBBLE_ENABLED,
+    .dstate = &_TRACE_TEST_WIBBLE_DSTATE
+};
+TraceEvent *testsuite_trace_events[] = {
+    &_TRACE_TEST_BLAH_EVENT,
+    &_TRACE_TEST_WIBBLE_EVENT,
+  NULL,
+};
+
+static void trace_testsuite_register_events(void)
+{
+    trace_event_register_group(testsuite_trace_events);
+}
+trace_init(trace_testsuite_register_events)
diff --git a/tests/tracetool/syslog.h b/tests/tracetool/syslog.h
new file mode 100644
index 0000000000..ed4305554c
--- /dev/null
+++ b/tests/tracetool/syslog.h
@@ -0,0 +1,43 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef TRACE_TESTSUITE_GENERATED_TRACERS_H
+#define TRACE_TESTSUITE_GENERATED_TRACERS_H
+
+#include "trace/control.h"
+
+extern TraceEvent _TRACE_TEST_BLAH_EVENT;
+extern TraceEvent _TRACE_TEST_WIBBLE_EVENT;
+extern uint16_t _TRACE_TEST_BLAH_DSTATE;
+extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+#define TRACE_TEST_BLAH_ENABLED 1
+#define TRACE_TEST_WIBBLE_ENABLED 1
+#include <syslog.h>
+
+
+#define TRACE_TEST_BLAH_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_BLAH) || \
+    false)
+
+static inline void trace_test_blah(void *context, const char *filename)
+{
+    if (trace_event_get_state(TRACE_TEST_BLAH)) {
+#line 4 "trace-events"
+        syslog(LOG_INFO, "test_blah " "Blah context=%p filename=%s" , context, filename);
+#line 28 "syslog.h"
+    }
+}
+
+#define TRACE_TEST_WIBBLE_BACKEND_DSTATE() ( \
+    trace_event_get_state_dynamic_by_id(TRACE_TEST_WIBBLE) || \
+    false)
+
+static inline void trace_test_wibble(void *context, int value)
+{
+    if (trace_event_get_state(TRACE_TEST_WIBBLE)) {
+#line 5 "trace-events"
+        syslog(LOG_INFO, "test_wibble " "Wibble context=%p value=%d" , context, value);
+#line 41 "syslog.h"
+    }
+}
+#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/trace-events b/tests/tracetool/trace-events
new file mode 100644
index 0000000000..72cf4d6f70
--- /dev/null
+++ b/tests/tracetool/trace-events
@@ -0,0 +1,5 @@
+# See docs/devel/tracing.rst for syntax documentation.
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+test_blah(void *context, const char *filename) "Blah context=%p filename=%s"
+test_wibble(void *context, int value) "Wibble context=%p value=%d"
diff --git a/tests/tracetool/tracetool-test.py b/tests/tracetool/tracetool-test.py
new file mode 100755
index 0000000000..65430fdedc
--- /dev/null
+++ b/tests/tracetool/tracetool-test.py
@@ -0,0 +1,107 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+from pathlib import Path
+from shutil import copyfile
+from subprocess import check_call
+import sys
+import tempfile
+
+
+def get_formats(backend):
+    formats = [
+        "c",
+        "h",
+    ]
+    if backend == "dtrace":
+        formats += [
+            "d",
+            "log-stap",
+            "simpletrace-stap",
+            "stap",
+        ]
+    if backend == "ust":
+        formats += [
+            "ust-events-c",
+            "ust-events-h",
+        ]
+    return formats
+
+
+def test_tracetool_one(tracetool, backend, fmt, src_dir, build_dir):
+    rel_filename = backend + "." + fmt
+    actual_file = Path(build_dir, rel_filename)
+    expect_file = Path(src_dir, rel_filename)
+
+    args = [tracetool, f"--format={fmt}", f"--backends={backend}", "--group=testsuite"]
+
+    if fmt.find("stap") != -1:
+        args += ["--binary=qemu", "--probe-prefix=qemu"]
+
+    # Use relative files for both, as these filenames end
+    # up in '#line' statements in the output
+    args += ["trace-events", rel_filename]
+
+    try:
+        check_call(args, cwd=build_dir)
+        actual = actual_file.read_text()
+    finally:
+        actual_file.unlink()
+
+    if os.getenv("QEMU_TEST_REGENERATE", False):
+        print(f"# regenerate {expect_file}")
+        expect_file.write_text(actual)
+
+    expect = expect_file.read_text()
+
+    assert expect == actual
+
+
+def test_tracetool(tracetool, backend, source_dir, build_dir):
+    fail = False
+    scenarios = len(get_formats(backend))
+
+    print(f"1..{scenarios}")
+
+    src_events = Path(source_dir, "trace-events")
+    build_events = Path(build_dir, "trace-events")
+
+    try:
+        # We need a stable relative filename under build dir
+        # for the '#line' statements, so copy over the input
+        copyfile(src_events, build_events)
+
+        num = 1
+        for fmt in get_formats(backend):
+            status = "not ok"
+            hint = ""
+            try:
+                test_tracetool_one(tracetool, backend, fmt, source_dir, build_dir)
+                status = "ok"
+            except Exception as e:
+                print(f"# {e}")
+                fail = True
+                hint = (
+                    " (set QEMU_TEST_REGENERATE=1 to recreate reference "
+                    + "output if tracetool generator was intentionally changed)"
+                )
+            finally:
+                print(f"{status} {num} - {backend}.{fmt}{hint}")
+    finally:
+        build_events.unlink()
+
+    return fail
+
+
+if __name__ == "__main__":
+    if len(sys.argv) != 5:
+        argv0 = sys.argv[0]
+        print("syntax: {argv0} TRACE-TOOL BACKEND SRC-DIR BUILD-DIR", file=sys.stderr)
+        sys.exit(1)
+
+    with tempfile.TemporaryDirectory(prefix=sys.argv[4]) as tmpdir:
+        fail = test_tracetool(sys.argv[1], sys.argv[2], sys.argv[3], tmpdir)
+        if fail:
+            sys.exit(1)
+    sys.exit(0)
diff --git a/tests/tracetool/ust.c b/tests/tracetool/ust.c
new file mode 100644
index 0000000000..9f862fa14d
--- /dev/null
+++ b/tests/tracetool/ust.c
@@ -0,0 +1,32 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "trace-testsuite.h"
+
+uint16_t _TRACE_TEST_BLAH_DSTATE;
+uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+TraceEvent _TRACE_TEST_BLAH_EVENT = {
+    .id = 0,
+    .name = "test_blah",
+    .sstate = TRACE_TEST_BLAH_ENABLED,
+    .dstate = &_TRACE_TEST_BLAH_DSTATE
+};
+TraceEvent _TRACE_TEST_WIBBLE_EVENT = {
+    .id = 0,
+    .name = "test_wibble",
+    .sstate = TRACE_TEST_WIBBLE_ENABLED,
+    .dstate = &_TRACE_TEST_WIBBLE_DSTATE
+};
+TraceEvent *testsuite_trace_events[] = {
+    &_TRACE_TEST_BLAH_EVENT,
+    &_TRACE_TEST_WIBBLE_EVENT,
+  NULL,
+};
+
+static void trace_testsuite_register_events(void)
+{
+    trace_event_register_group(testsuite_trace_events);
+}
+trace_init(trace_testsuite_register_events)
diff --git a/tests/tracetool/ust.h b/tests/tracetool/ust.h
new file mode 100644
index 0000000000..b7acd0c39b
--- /dev/null
+++ b/tests/tracetool/ust.h
@@ -0,0 +1,41 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef TRACE_TESTSUITE_GENERATED_TRACERS_H
+#define TRACE_TESTSUITE_GENERATED_TRACERS_H
+
+#include "trace/control.h"
+
+extern TraceEvent _TRACE_TEST_BLAH_EVENT;
+extern TraceEvent _TRACE_TEST_WIBBLE_EVENT;
+extern uint16_t _TRACE_TEST_BLAH_DSTATE;
+extern uint16_t _TRACE_TEST_WIBBLE_DSTATE;
+#define TRACE_TEST_BLAH_ENABLED 1
+#define TRACE_TEST_WIBBLE_ENABLED 1
+#include <lttng/tracepoint.h>
+#include "trace-ust-testsuite.h"
+
+/* tracepoint_enabled() was introduced in LTTng UST 2.7 */
+#ifndef tracepoint_enabled
+#define tracepoint_enabled(a, b) true
+#endif
+
+
+#define TRACE_TEST_BLAH_BACKEND_DSTATE() ( \
+    tracepoint_enabled(qemu, test_blah) || \
+    false)
+
+static inline void trace_test_blah(void *context, const char *filename)
+{
+    tracepoint(qemu, test_blah, context, filename);
+}
+
+#define TRACE_TEST_WIBBLE_BACKEND_DSTATE() ( \
+    tracepoint_enabled(qemu, test_wibble) || \
+    false)
+
+static inline void trace_test_wibble(void *context, int value)
+{
+    tracepoint(qemu, test_wibble, context, value);
+}
+#endif /* TRACE_TESTSUITE_GENERATED_TRACERS_H */
diff --git a/tests/tracetool/ust.ust-events-c b/tests/tracetool/ust.ust-events-c
new file mode 100644
index 0000000000..db23224056
--- /dev/null
+++ b/tests/tracetool/ust.ust-events-c
@@ -0,0 +1,14 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+
+#define TRACEPOINT_DEFINE
+#define TRACEPOINT_CREATE_PROBES
+
+/* If gcc version 4.7 or older is used, LTTng ust gives a warning when compiling with
+   -Wredundant-decls.
+ */
+#pragma GCC diagnostic ignored "-Wredundant-decls"
+
+#include "trace-ust-all.h"
diff --git a/tests/tracetool/ust.ust-events-h b/tests/tracetool/ust.ust-events-h
new file mode 100644
index 0000000000..4621a995fc
--- /dev/null
+++ b/tests/tracetool/ust.ust-events-h
@@ -0,0 +1,56 @@
+/* This file is autogenerated by tracetool, do not edit. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#undef TRACEPOINT_PROVIDER
+#define TRACEPOINT_PROVIDER qemu
+
+#undef TRACEPOINT_INCLUDE
+#define TRACEPOINT_INCLUDE "./trace-ust.h"
+
+#if !defined (TRACE_TESTSUITE_GENERATED_UST_H) || \
+     defined(TRACEPOINT_HEADER_MULTI_READ)
+#define TRACE_TESTSUITE_GENERATED_UST_H
+
+#include <lttng/tracepoint.h>
+
+/*
+ * LTTng ust 2.0 does not allow you to use TP_ARGS(void) for tracepoints
+ * requiring no arguments. We define these macros introduced in more recent * versions of LTTng ust as a workaround
+ */
+#ifndef _TP_EXPROTO1
+#define _TP_EXPROTO1(a)               void
+#endif
+#ifndef _TP_EXDATA_PROTO1
+#define _TP_EXDATA_PROTO1(a)          void *__tp_data
+#endif
+#ifndef _TP_EXDATA_VAR1
+#define _TP_EXDATA_VAR1(a)            __tp_data
+#endif
+#ifndef _TP_EXVAR1
+#define _TP_EXVAR1(a)
+#endif
+
+TRACEPOINT_EVENT(
+   qemu,
+   test_blah,
+   TP_ARGS(void *, context, const char *, filename),
+   TP_FIELDS(
+       ctf_integer_hex(void *, context, context)
+       ctf_string(filename, filename)
+   )
+)
+
+TRACEPOINT_EVENT(
+   qemu,
+   test_wibble,
+   TP_ARGS(void *, context, int, value),
+   TP_FIELDS(
+       ctf_integer_hex(void *, context, context)
+       ctf_integer(int, value, value)
+   )
+)
+
+#endif /* TRACE_TESTSUITE_GENERATED_UST_H */
+
+/* This part must be outside ifdef protection */
+#include <lttng/tracepoint-event.h>
diff --git a/ui/vnc.c b/ui/vnc.c
index 68ca4a68e7..9054fc8125 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -4309,8 +4309,9 @@ void vnc_display_add_client(const char *id, int csock, bool skipauth)
     }
 }
 
-static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts)
+static char *vnc_auto_assign_id(QemuOpts *opts)
 {
+    QemuOptsList *olist = qemu_find_opts("vnc");
     int i = 2;
     char *id;
 
@@ -4320,23 +4321,18 @@ static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts)
         id = g_strdup_printf("vnc%d", i++);
     }
     qemu_opts_set_id(opts, id);
+
+    return id;
 }
 
 void vnc_parse(const char *str)
 {
     QemuOptsList *olist = qemu_find_opts("vnc");
     QemuOpts *opts = qemu_opts_parse_noisily(olist, str, !is_help_option(str));
-    const char *id;
 
     if (!opts) {
         exit(1);
     }
-
-    id = qemu_opts_id(opts);
-    if (!id) {
-        /* auto-assign id if not present */
-        vnc_auto_assign_id(olist, opts);
-    }
 }
 
 int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
@@ -4344,7 +4340,11 @@ int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
     Error *local_err = NULL;
     char *id = (char *)qemu_opts_id(opts);
 
-    assert(id);
+    if (!id) {
+        /* auto-assign id if not present */
+        id = vnc_auto_assign_id(opts);
+    }
+
     vnc_display_init(id, &local_err);
     if (local_err) {
         error_propagate(errp, local_err);