Skip to contentSkip to conversation

pci, vhost, virtio, iommu, acpi: features, fixes

GIT PULL Applied Michael S. Tsirkin 6 participants 92 messages Oct 28 – Nov 3

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

The following changes since commit 29b1dc631d6ac6bfc39464e0be8bf4cad556a591:

Merge tag 'block-pull-request' of https://gitlab.com/stefanha/qemu into staging (2026-10-27 20:02:57 -0500)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

for you to fetch changes up to c155e7e9e025b60ce17e26a7c382d86f3f9c9fee:

virtio-net: add mtu-from-tap property for automatic MTU detection (2026-10-28 13:11:16 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu, acpi: features, fixes

multiboot can again load 64 bit images vdpa-dev now implements guest_notifier_mask/pending pci bridge can now configure the negotiated bandwidth tap can now auto-detect host mtu and expose to guest amd iommu can now attach on non system bus vhost-user spec now has its own mailing list

Fixes, cleanups all over the place.

----------------------------------------------------------------
Alexander Mikhalitsyn (1):
virtio-snd: don't use virtio_snd_pcm_get_stream() in virtio_snd_post_load()

Alexandr Moshkov (2):
vhost-user: add missing stub for vhost_user_has_protocol_feature()
vhost: move skip-drain feature check to vhost-user

Andrey Drobyshev (22):
migration/cpr: forbid saving duplicate CprFds into hash table tests/unit: add test for duplicate keys in CPR FD table
migration: forbid snapshots in CPR migration modes
migration: forbid snapshots under Xen
tests/qtest/migration: add test for snapshot rejection in CPR modes
vhost-vsock: block CPR migration modes
vhost: add vhost_reset_owner op
vhost-vsock: don't reset connections during CPR
vhost-vsock: fix FD leak in realize()
vhost-vsock: preserve vhost FD during CPR
vhost: factor out vhost_dev_init_backend()
vhost: make vhost_dev_cleanup() safe on a partially initialized device
vhost: add vhost_dev_set_owner() / vhost_dev_reset_owner() helpers
vhost: add vhost_dev_is_initialized() helper
vhost: release ownership in vhost_dev_cleanup()
migration: add MIG_EVENT_INCOMING_FAILED
vhost: release ownership when the incoming migration fails
vhost: release ownership on exit
vhost-vsock: hand off device ownership across CPR tests/qtest/migration: add vhost-vsock tests for cpr-transfer and cpr-exec tests/functional: add vhost-vsock CPR test with a live guest tests/functional: cover a cpr-transfer target going away with vhost-vsock

Connor Kite (1):
hw/virtio/vhost-shadow-virtqueue: fix range boundary in SVQ translation

Daniel P. Berrangé (2):
hw/arm: remove pci-bridge/pci_expander_bridge.h from virt.c hw/pci-bridge: split CXL PXB into separate file

Daniel Paziyski (1):
pcie_sriov: register user created virtual function before realizing it

David Vrabel (4):
hw/pci-bridge: Add generic PCIe switch upstream and downstream ports hw/pci-bridge: Make speed and width for PCIe ports non-experimental hw/pci-bridge: Default the generic PCIe root port as Gen6 capable hw/pci: Ensure PCIe link speed/width is consistent between both ends

Gavin Shan (1):
hw/virtio/virtio-mem: Fix error message on invalid NUMA node

Junjie Cao (7):
hw/cxl: destroy primary CCI before re-initialization on reset hw/cxl: convert cxl-type3 to three-phase reset
hw/cxl: add cxl_destroy_mailbox_t3() as the mailbox teardown counterpart hw/cxl: discard in-flight sanitize state on mailbox teardown hw/cxl: clear event logs, scan media and interrupt policy on reset hw/cxl: clear poison lists and feature transfer state on reset
MAINTAINERS: Update Junjie Cao's email address

Li RongQing (4):
vhost: fix off-by-one in vhost_svq_next_desc with IN_ORDER
pcie_sriov: fix wrong write width and off-by-one in pf_reset
virtio-qmp: Fix out-of-bounds vqs index in vhost queue status
pcie_aer: fix pci_set_long overflow into PCI_EXP_LNKCAP

Li Zhaoxin (1):
vdpa-dev: implement guest_notifier_mask/pending

Michael S. Tsirkin (2):
libvhost-user: protect against OOB memory table access
vhost-user: fix SET_MEM_TABLE number of regions

Nariman Sayed (3):
net/tap: add tap_fd_get_mtu() to query host tap MTU
net: add generic get_mtu NetClientInfo callback
virtio-net: add mtu-from-tap property for automatic MTU detection

Sairaj Kodilkar (8):
amd_iommu: Assign ioapic_as only when interrupt remapping is supported.
x86-iommu: Introduce x86_iommu_device_get() for x86-iommu retrieval
amd_iommu: Add support for get_x86_iommu callback
intel_iommu: Add support for get_x86_iommu callback i386/kvm: Use x86_iommu_device_get() for per-device IOMMU lookup
amd_iommu: Introduce root bus
acpi-build: Create PCI topology using amd iommu root bus
amd_iommu: Allow IOMMU to attachment on non-system (pcie.0) bus

Samuel Thibault (1):

      Revert 9696846600ca ("multiboot: Prevent loading of x86_64 images")

Stefano Garzarella (1):
vhost-user: add dedicated mailing list

Vladimir Sementsov-Ogievskiy (8):
tests/functional: add test_tap_migration
net/tap: tap_post_load(): poll only on success hw/net: finish backend initialization earler for local migration net/tap: move vmstate initialization to new_tap() net/tap: net_init_tap_one(): fix failure path qapi/virtio: Restore backend features in x-query-virtio-status qapi/monitor: Handle missing vhost-user protocol features util/event_notifier: more strict API

Yodel Eldar (2):
contrib/vhost-user-bridge: Use stderr for debug output tests/functional/x86_64/test_vhost_user_bridge: Eliminate stdbuf usage

Yuho Choi (1):
vhost-user: remove shared object entries during cleanup

Zhenzhong Duan (6):
intel_iommu_accel: Guard VTDAccelPASIDCacheEntry definition backends/iommufd: Introduce iommufd_backend_alloc_faultq backends/iommufd: Extend iommufd_backend_alloc_hwpt() with fault_id
intel_iommu_accel: Add PRQ injection for passthrough device
intel_iommu_accel: Accept PRQ response for passthrough device
intel_iommu_accel: Teardown FAULTQ resources in bottom half

 MAINTAINERS                                        |  14 +-
 docs/interop/vhost-user.rst                        |   8 +-
 docs/pcie.txt                                      |   6 +-
 docs/specs/pci-ids.rst                             |   4 +
 qapi/virtio.json                                   |   5 +-
 hw/i386/amd_iommu.h                                |   3 +
 hw/i386/intel_iommu_accel.h                        |  16 +-
 hw/i386/intel_iommu_internal.h                     |   3 +
 hw/pci-bridge/pci_expander_bridge_common.h         |  28 ++
 include/hw/cxl/cxl_device.h                        |   3 +
 include/hw/i386/intel_iommu.h                      |   6 +
 include/hw/i386/x86-iommu.h                        |   6 +
 ...expander_bridge.h => pci_expander_bridge_cxl.h} |   0
 include/hw/pci/pci.h                               |  14 +
 include/hw/vfio/vfio-cpr.h                         |   7 +-
 include/hw/virtio/vhost-backend.h                  |   2 +
 include/hw/virtio/vhost.h                          |  80 ++++
 include/hw/virtio/virtio-dmabuf.h                  |   8 +
 include/hw/virtio/virtio-net.h                     |   1 +
 include/migration/cpr.h                            |   2 +-
 include/migration/misc.h                           |   5 +
 include/net/net.h                                  |   3 +
 include/qemu/event_notifier.h                      |   1 +
 include/system/iommufd.h                           |   7 +-
 net/tap_int.h                                      |   1 +
 backends/hostmem-memfd.c                           |   5 +-
 backends/hostmem-shm.c                             |   5 +-
 backends/iommufd.c                                 |  32 +-
 contrib/vhost-user-bridge/vhost-user-bridge.c      |   2 +-
 hw/arm/smmuv3-accel.c                              |   6 +-
 hw/arm/virt.c                                      |   1 -
 hw/audio/virtio-snd.c                              |   3 +-
 hw/core/machine.c                                  |   1 +
 hw/cxl/cxl-host.c                                  |   2 +-
 hw/cxl/cxl-mailbox-utils.c                         |  22 +-
 hw/display/virtio-dmabuf.c                         |  18 +
 hw/i386/acpi-build.c                               | 152 ++++----
 hw/i386/amd_iommu.c                                |  75 +++-
 hw/i386/intel_iommu.c                              |  26 +-
 hw/i386/intel_iommu_accel.c                        | 309 ++++++++++++++-
 hw/i386/multiboot.c                                |   5 -
 hw/i386/x86-iommu-stub.c                           |   5 +
 hw/i386/x86-iommu.c                                |  16 +
 hw/intc/ioapic.c                                   |   3 +-
 hw/mem/cxl_type3.c                                 |  60 ++-
 hw/net/virtio-net.c                                |  50 +--
 hw/pci-bridge/gen_pcie_downstream_port.c           | 202 ++++++++++
 hw/pci-bridge/gen_pcie_root_port.c                 |   6 +-
 hw/pci-bridge/gen_pcie_upstream_port.c             | 177 +++++++++
 hw/pci-bridge/pci_expander_bridge.c                | 194 +---------
 hw/pci-bridge/pci_expander_bridge_cxl.c            | 201 ++++++++++
 hw/pci-bridge/pci_expander_bridge_stubs.c          |  14 -
 hw/pci/pci.c                                       |  42 +-
 hw/pci/pcie.c                                      |  26 +-
 hw/pci/pcie_aer.c                                  |   2 +-
 hw/pci/pcie_sriov.c                                |  16 +-
 hw/remote/iohub.c                                  |  11 +-
 hw/vfio/container-legacy.c                         |   7 +-
 hw/vfio/cpr-legacy.c                               |  31 +-
 hw/vfio/cpr.c                                      |   6 +-
 hw/vfio/iommufd.c                                  |   2 +-
 hw/vfio/pci.c                                      |   5 +-
 hw/virtio/vdpa-dev.c                               |  30 +-
 hw/virtio/vhost-kernel.c                           |   6 +
 hw/virtio/vhost-shadow-virtqueue.c                 |  44 ++-
 hw/virtio/vhost-user-stub.c                        |   5 +
 hw/virtio/vhost-user.c                             |   5 +-
 hw/virtio/vhost-vsock.c                            | 193 +++++++++-
 hw/virtio/vhost.c                                  | 141 ++++++-
 hw/virtio/virtio-hmp-cmds.c                        |   6 +-
 hw/virtio/virtio-mem.c                             |   2 +-
 hw/virtio/virtio-qmp.c                             |  21 +-
 migration/cpr.c                                    |  64 ++--
 migration/migration.c                              |  10 +
 migration/options.c                                |  13 +
 net/net.c                                          |   9 +
 net/tap-bsd.c                                      |   5 +
 net/tap-linux.c                                    |  30 ++
 net/tap-solaris.c                                  |   5 +
 net/tap-stub.c                                     |   5 +
 net/tap.c                                          |  19 +-
 subprojects/libvhost-user/libvhost-user.c          |   7 +
 system/physmem.c                                   |   5 +-
 target/i386/kvm/kvm.c                              |  15 +-
 tests/qtest/migration/cpr-tests.c                  | 133 ++++++-
 tests/unit/test-cpr.c                              |  98 +++++
 tests/unit/test-virtio-dmabuf.c                    |  43 +++
 util/event_notifier-posix.c                        |  10 +
 backends/trace-events                              |   3 +-
 hw/i386/trace-events                               |   2 +
 hw/pci-bridge/meson.build                          |   5 +-
 tests/functional/x86_64/meson.build                |   4 +
 tests/functional/x86_64/test_pcie_switch.py        |  98 +++++
 tests/functional/x86_64/test_tap_migration.py      | 425 +++++++++++++++++++++
 tests/functional/x86_64/test_vhost_user_bridge.py  |   6 -
 tests/functional/x86_64/test_vhost_vsock_cpr.py    | 347 +++++++++++++++++
 tests/unit/meson.build                             |   1 +
 97 files changed, 3256 insertions(+), 532 deletions(-)
 create mode 100644 hw/pci-bridge/pci_expander_bridge_common.h
 rename include/hw/pci-bridge/{pci_expander_bridge.h => pci_expander_bridge_cxl.h} (100%)
 create mode 100644 hw/pci-bridge/gen_pcie_downstream_port.c
 create mode 100644 hw/pci-bridge/gen_pcie_upstream_port.c
 create mode 100644 hw/pci-bridge/pci_expander_bridge_cxl.c
 delete mode 100644 hw/pci-bridge/pci_expander_bridge_stubs.c
 create mode 100644 tests/unit/test-cpr.c
 create mode 100755 tests/functional/x86_64/test_pcie_switch.py
 create mode 100755 tests/functional/x86_64/test_tap_migration.py
 create mode 100755 tests/functional/x86_64/test_vhost_vsock_cpr.py
#1 lore raw

[PULL 02/78] vhost-user: fix SET_MEM_TABLE number of regions

vhost-user.rst says the legacy SET_MEM_TABLE contains 8 regions, but this does not make sense - we have a num_regions field for this. And it is not true in practice: if QEMU has less, it sends less.

The incorrect wording was introduced, without explanation, as part of an editorial cleanup. Probably an oversight.

It's really "up to 8".

Fixes b87ea798eb83 ("docs: fix vhost-user protocol doc")
Cc luzhixing12345 <[email protected]>

Message-ID: <712e301bf7f815412fb264de8159cb3475bd237a.1789469028.git.mst@redhat.com>

---
 docs/interop/vhost-user.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
docs/interop/vhost-user.rst +1 -1
index c1e02ff7e9..488214a306 100644
@@ -248,7 +248,7 @@ Multiple Memory regions description
 
 :padding: 32-bit
 
-:regions: regions field contains 8 regions of type :ref:`Memory region description <memory_region_description>`.
+:regions: regions field contains up to 8 regions of type :ref:`Memory region description <memory_region_description>`.
 
 Log description
 ^^^^^^^^^^^^^^^
Signature · 1 line
MST
#2 lore raw

[PULL 01/78] libvhost-user: protect against OOB memory table access

In reply to Michael S. Tsirkin

SET_MEM_TABLE handler uses nregions from the message as a loop bound without checking that it is below VHOST_MEMORY_BASELINE_NREGIONS, so a malformed message causes an out-of-bounds access to the regions[] and fds[] arrays.

Frontend is trusted so not a security problem, but an OOB access is not a nice way to handle errors. Check, and panic.

Fixes 7b2e5c65f42f ("contrib: add libvhost-user")

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4403

Message-ID: <8adc88f2f535ab0566a5df1c66db728112dd6f74.1789469161.git.mst@redhat.com>

---
 subprojects/libvhost-user/libvhost-user.c | 7 +++++++
 1 file changed, 7 insertions(+)
subprojects/libvhost-user/libvhost-user.c +7 -0
index 2fed792e85..52817cc733 100644
@@ -1105,6 +1105,13 @@ vu_set_mem_table_exec(VuDev *dev, VhostUserMsg *vmsg)
     vu_remove_all_mem_regs(dev);
 
     DPRINT("Nregions: %u\n", memory->nregions);
+
+    if (memory->nregions > VHOST_MEMORY_BASELINE_NREGIONS) {
+        vu_panic(dev, "Invalid set_mem_table nregions: %u > %u",
+                 memory->nregions, VHOST_MEMORY_BASELINE_NREGIONS);
+        return false;
+    }
+
     for (i = 0; i < memory->nregions; i++) {
         _vu_add_mem_reg(dev, &memory->regions[i], vmsg->fds[i]);
         close(vmsg->fds[i]);
Signature · 1 line
MST
#3 lore raw

[PULL 10/78] migration/cpr: forbid saving duplicate CprFds into hash table

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

In its current form, the mechanism for saving CPR FDs into hash table has flaws.

The CPR fd hash table uses the same CprFd for both key and value, and is created with a key destroy function. Inserting a second element with the same (name, id) key leads to a use after free. According to GLib docs [1], g_hash_table_insert() keeps the old key, replaces the value with the new element, and destroys the new key. Thus when inserting 2 CprFd elements fd1 and fd2 with equal keys we get:

g_hash_table_insert(table, fd1, fd1); /* table has fd1 -> fd1 */ g_hash_table_insert(table, fd2, fd2); /* table has fd1 -> fd2, fd2 freed */

Any subsequent cpr_find_fd() for that key then returns freed memory.

This is reachable whenever two CPR-aware objects register fds under the same name, and it happens long before an actual CPR, since cpr_save_fd() is typically called from .realize(), i.e. at boot time.

Apart from the UAF itself, when the user decides to perform CPR migration, on CPR target we'd get corrupted FD from the hash table and fail somewhere in .realize() for affected devices.

As a solution, let's altogether prohibit adding CprFds with duplicate keys. Since FDs are being registered for a subsequent CPR right at the startup in .realize(), that'll allow us to error out and exit QEMU (or fail a device hotplug op) gracefully instead of delaying potential problems for later. To do so, let's make cpr_save_fd() return 'bool' instead of 'void' and provide an 'errp' argument to it. That way caller becomes responsible for checking the return value and propagating the error.

Also update all the callers to use the adjusted interface to keep things compilable and bisectable.

[1] https://docs.gtk.org/glib/type_func.HashTable.insert.html

Message-ID: <[email protected]>

---
 include/hw/vfio/vfio-cpr.h |  7 +++--
 include/migration/cpr.h    |  2 +-
 backends/hostmem-memfd.c   |  5 ++-
 backends/hostmem-shm.c     |  5 ++-
 backends/iommufd.c         |  4 ++-
 hw/vfio/container-legacy.c |  7 +----
 hw/vfio/cpr-legacy.c       | 31 +++++++++++++-----
 hw/vfio/cpr.c              |  6 ++--
 hw/vfio/pci.c              |  5 ++-
 migration/cpr.c            | 64 ++++++++++++++++++++++++--------------
 system/physmem.c           |  5 +--
 11 files changed, 92 insertions(+), 49 deletions(-)
include/hw/vfio/vfio-cpr.h +4 -3
index ecabe0c747..9157a4ac64 100644
@@ -60,7 +60,8 @@ void vfio_iommufd_cpr_register_device(struct VFIODevice *vbasedev);
 void vfio_iommufd_cpr_unregister_device(struct VFIODevice *vbasedev);
 void vfio_cpr_load_device(struct VFIODevice *vbasedev);
 
-int vfio_cpr_group_get_device_fd(int d, const char *name);
+int vfio_cpr_group_get_device_fd(struct VFIOGroup *group, const char *name,
+                                 Error **errp);
 
 bool vfio_cpr_container_match(struct VFIOLegacyContainer *container,
                               struct VFIOGroup *group, int fd);
@@ -71,8 +72,8 @@ void vfio_cpr_giommu_remap(struct VFIOContainer *bcontainer,
 bool vfio_cpr_ram_discard_replay_populated(
     struct VFIOContainer *bcontainer, const MemoryRegionSection *section);
 
-void vfio_cpr_save_vector_fd(struct VFIOPCIDevice *vdev, const char *name,
-                             int nr, int fd);
+bool vfio_cpr_save_vector_fd(struct VFIOPCIDevice *vdev, const char *name,
+                             int nr, int fd, Error **errp);
 int vfio_cpr_load_vector_fd(struct VFIOPCIDevice *vdev, const char *name,
                             int nr);
 void vfio_cpr_delete_vector_fd(struct VFIOPCIDevice *vdev, const char *name,
include/migration/cpr.h +1 -1
index 56fb67e6b4..3ad084d5ba 100644
@@ -28,7 +28,7 @@ typedef struct CprState {
 
 extern CprState cpr_state;
 
-void cpr_save_fd(const char *name, int id, int fd);
+bool cpr_save_fd(const char *name, int id, int fd, Error **errp);
 void cpr_delete_fd(const char *name, int id);
 int cpr_find_fd(const char *name, int id);
 void cpr_resave_fd(const char *name, int id, int fd);
backends/hostmem-memfd.c +4 -1
index 666593ff6c..67f1924a36 100644
@@ -84,7 +84,10 @@ memfd_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
     if (fd < 0) {
         return false;
     }
-    cpr_save_fd(name, 0, fd);
+    if (!cpr_save_fd(name, 0, fd, errp)) {
+        close(fd);
+        return false;
+    }
 
 have_fd:
     backend->aligned = true;
backends/hostmem-shm.c +4 -1
index 4766db6aad..adf60957c3 100644
@@ -48,7 +48,10 @@ shm_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
     if (fd < 0) {
         return false;
     }
-    cpr_save_fd(backend_name, 0, fd);
+    if (!cpr_save_fd(backend_name, 0, fd, errp)) {
+        close(fd);
+        return false;
+    }
 
 have_fd:
     /* Let's do the same as memory-backend-ram,share=on would do. */
backends/iommufd.c +3 -1
index 15f2a51350..45fd2e44c7 100644
@@ -85,7 +85,9 @@ static void iommufd_backend_complete(UserCreatable *uc, Error **errp)
         if (cpr_is_incoming()) {
             be->fd = cpr_find_fd(name, 0);
         } else {
-            cpr_save_fd(name, 0, be->fd);
+            if (!cpr_save_fd(name, 0, be->fd, errp)) {
+                return;
+            }
         }
     } else if (!g_file_test("/dev/iommu", G_FILE_TEST_EXISTS)) {
         error_setg(errp, "/dev/iommu does not exist"
hw/vfio/container-legacy.c +1 -6
index 7ac7b37214..50ca5a3a7e 100644
@@ -847,13 +847,8 @@ static bool vfio_device_get(VFIOGroup *group, const char *name,
     g_autofree struct vfio_device_info *info = NULL;
     int fd;
 
-    fd = vfio_cpr_group_get_device_fd(group->fd, name);
+    fd = vfio_cpr_group_get_device_fd(group, name, errp);
     if (fd < 0) {
-        error_setg_errno(errp, errno, "error getting device from group %d",
-                         group->groupid);
-        error_append_hint(errp,
-                      "Verify all devices in group %d are bound to vfio-<bus> "
-                      "or pci-stub and not already in use\n", group->groupid);
         return false;
     }
 
hw/vfio/cpr-legacy.c +24 -7
index 2d40d8baea..807ffacdbc 100644
@@ -254,17 +254,33 @@ bool vfio_cpr_ram_discard_replay_populated(VFIOContainer *bcontainer,
                                                 &vrdl->listener) == 0;
 }
 
-int vfio_cpr_group_get_device_fd(int d, const char *name)
+int vfio_cpr_group_get_device_fd(VFIOGroup *group, const char *name,
+                                 Error **errp)
 {
+    ERRP_GUARD();
     const int id = 0;
     int fd = cpr_find_fd(name, id);
 
-    if (fd < 0) {
-        fd = ioctl(d, VFIO_GROUP_GET_DEVICE_FD, name);
-        if (fd >= 0) {
-            cpr_save_fd(name, id, fd);
-        }
+    if (fd >= 0) {
+        return fd;
     }
+
+    fd = ioctl(group->fd, VFIO_GROUP_GET_DEVICE_FD, name);
+    if (fd < 0) {
+        error_setg_errno(errp, errno, "error getting device from group %d",
+                         group->groupid);
+        error_append_hint(errp,
+                          "Verify all devices in group %d are bound to "
+                          "vfio-<bus> or pci-stub and not already in use\n",
+                          group->groupid);
+        return -1;
+    }
+
+    if (!cpr_save_fd(name, id, fd, errp)) {
+        close(fd);
+        return -1;
+    }
+
     return fd;
 }
 
@@ -291,6 +307,7 @@ bool vfio_cpr_container_match(VFIOLegacyContainer *container, VFIOGroup *group,
      */
     cpr_delete_fd("vfio_container_for_group", group->groupid);
     close(fd);
-    cpr_save_fd("vfio_container_for_group", group->groupid, container->fd);
+    cpr_save_fd("vfio_container_for_group", group->groupid, container->fd,
+                &error_abort);
     return true;
 }
hw/vfio/cpr.c +3 -3
index ffa4f8e099..6e1659dcdd 100644
@@ -32,11 +32,11 @@ int vfio_cpr_reboot_notifier(NotifierWithReturn *notifier,
 #define STRDUP_VECTOR_FD_NAME(vdev, name)   \
     g_strdup_printf("%s_%s", (vdev)->vbasedev.name, (name))
 
-void vfio_cpr_save_vector_fd(VFIOPCIDevice *vdev, const char *name, int nr,
-                             int fd)
+bool vfio_cpr_save_vector_fd(VFIOPCIDevice *vdev, const char *name, int nr,
+                             int fd, Error **errp)
 {
     g_autofree char *fdname = STRDUP_VECTOR_FD_NAME(vdev, name);
-    cpr_save_fd(fdname, nr, fd);
+    return cpr_save_fd(fdname, nr, fd, errp);
 }
 
 int vfio_cpr_load_vector_fd(VFIOPCIDevice *vdev, const char *name, int nr)
hw/vfio/pci.c +4 -1
index 428ab2f069..ad13f2306f 100644
@@ -76,7 +76,10 @@ static bool vfio_notifier_init(VFIOPCIDevice *vdev, EventNotifier *e,
     }
 
     fd = event_notifier_get_fd(e);
-    vfio_cpr_save_vector_fd(vdev, name, nr, fd);
+    if (!vfio_cpr_save_vector_fd(vdev, name, nr, fd, errp)) {
+        event_notifier_cleanup(e);
+        return false;
+    }
     return true;
 }
 
migration/cpr.c +41 -23

109 lines not on this page · raw message

system/physmem.c +3 -2
index db3dda563d..a7300bb9d9 100644
@@ -2456,8 +2456,9 @@ static int qemu_ram_get_shared_fd(const char *name, bool *reused, Error **errp)
         fd = qemu_shm_alloc(0, errp);
     }
 
-    if (fd >= 0) {
-        cpr_save_fd(name, 0, fd);
+    if (fd >= 0 && !cpr_save_fd(name, 0, fd, errp)) {
+        close(fd);
+        fd = -1;
     }
     *reused = false;
     return fd;
Signature · 1 line
MST
#4 lore raw

[PULL 03/78] hw/virtio/vhost-shadow-virtqueue: fix range boundary in SVQ translation

In reply to Michael S. Tsirkin

From: Connor Kite <[email protected]>

iova-tree expects inclusive range sizing when maps are allocated or searched. Currently, svqs use exclusive sizing when searching their vhost-iova-tree for a match to the region to be translated. This could lead to errors if the region to be translated is at the edge of an iova region.

Fix this by reducing needle.size by 1 in vhost_svq_translate_addr to bring it in line with DMAMap and iova-tree convention.

The only current user of svq->iova_tree is hw/virtio/vhost-vdpa.c, which also treats size as inclusive when using the tree API. In progress work on vhost-user isolation mode in hw/virtio/vhost-user.c also uses inclusive sizing when interacting with the tree.

This patch is an updated version of one from the patch series "vhost-user:
isolated memory". The patch has been isolated from the series as it is not tightly coupled with the rest of the series.

Message-ID: <[email protected]>

---
 hw/virtio/vhost-shadow-virtqueue.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
hw/virtio/vhost-shadow-virtqueue.c +8 -2
index bcb7f2ffc7..c8831d52be 100644
@@ -99,19 +99,25 @@ static bool vhost_svq_translate_addr(const VhostShadowVirtqueue *svq,
         const DMAMap *map;
         DMAMap needle;
 
+        if (unlikely(iovec[i].iov_len == 0)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "Zero-sized buffer made available by guest");
+            return false;
+        }
+
         /* Check if the descriptor is backed by guest memory  */
         if (gpas) {
             /* Search the GPA->IOVA tree */
             needle = (DMAMap) {
                 .translated_addr = gpas[i],
-                .size = iovec[i].iov_len,
+                .size = iovec[i].iov_len - 1,  /* Inclusive */
             };
             map = vhost_iova_tree_find_gpa(svq->iova_tree, &needle);
         } else {
             /* Search the IOVA->HVA tree */
             needle = (DMAMap) {
                 .translated_addr = (hwaddr)(uintptr_t)iovec[i].iov_base,
-                .size = iovec[i].iov_len,
+                .size = iovec[i].iov_len - 1, /* Inclusive */
             };
             map = vhost_iova_tree_find_iova(svq->iova_tree, &needle);
         }
Signature · 1 line
MST
#5 lore raw

[PULL 09/78] Revert 9696846600ca ("multiboot: Prevent loading of x86_64 images")

In reply to Michael S. Tsirkin

From: Samuel Thibault <[email protected]>

Loading x86_64 images in multiboot was disabled on the ground that since multiboot actually boots 64bit kernels in 32bit mode, the binary would have to be 32bit.

https://lists.gnu.org/archive/html/qemu-devel/2010-08/msg00913.html

But making the binary 32bit makes using debugging tools such as gdb unnecessarily complex since they don't understand what architecture the kernel actually is. Letting qemu load x86_64 images and boot them in 32bit mode is completely fine, works, and is what the multiboot standard actually expects.

This notably fixes loading gnumach in x86_64 mode.

Message-ID: <[email protected]>

---
 hw/i386/multiboot.c | 5 -----
 1 file changed, 5 deletions(-)
hw/i386/multiboot.c +0 -5
index d544b62afb..004d5185fb 100644
@@ -196,11 +196,6 @@ int load_multiboot(X86MachineState *x86ms,
         int kernel_size;
         fclose(f);
 
-        if (((struct elf64_hdr*)header)->e_machine == EM_X86_64) {
-            error_report("Cannot load x86-64 image, give a 32bit one.");
-            exit(1);
-        }
-
         kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, &elf_entry,
                                &elf_low, &elf_high, NULL,
                                ELFDATA2LSB, I386_ELF_MACHINE, 0, 0);
Signature · 1 line
MST
#6 lore raw

[PULL 21/78] vhost: make vhost_dev_cleanup() safe on a partially initialized device

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

A device which only went through vhost_dev_init_backend() has its VQs not yet initialized, so vq->dev is NULL and vhost_virtqueue_cleanup() would crash dereferencing it. Check vq->dev before use, so that vhost_dev_cleanup() can be called to release a device whose full vhost_dev_init() never ran or failed along the way.

As of now, there's no way for the cleanup path to be reached with vq->dev uninitialized. However after a subsequent patch adding CPR support for vhost-vsock this'll become a possibility, so let's prevent such a crash beforehand.

Message-ID: <[email protected]>

---
 hw/virtio/vhost.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/virtio/vhost.c +1 -1
index 99561ac586..dfdfd49da3 100644
@@ -1661,7 +1661,7 @@ fail_call:
 static void vhost_virtqueue_cleanup(struct vhost_virtqueue *vq)
 {
     event_notifier_cleanup(&vq->masked_notifier);
-    if (vq->dev->vhost_ops->vhost_set_vring_err) {
+    if (vq->dev && vq->dev->vhost_ops->vhost_set_vring_err) {
         event_notifier_set_handler(&vq->error_notifier, NULL);
         event_notifier_cleanup(&vq->error_notifier);
     }
Signature · 1 line
MST
#7 lore raw

[PULL 04/78] tests/functional: add test_tap_migration

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

Add test for a new local-migration migration of virtio-net/tap, with fd passing through UNIX socket.

Message-ID: <[email protected]>

---
 tests/functional/x86_64/meson.build           |   1 +
 tests/functional/x86_64/test_tap_migration.py | 425 ++++++++++++++++++
 2 files changed, 426 insertions(+)
 create mode 100755 tests/functional/x86_64/test_tap_migration.py
tests/functional/x86_64/meson.build +1 -0
index 0353b2af8e..6254149239 100644
@@ -51,4 +51,5 @@ tests_x86_64_system_thorough = [
   'virtio_balloon',
   'virtio_gpu',
   'rebuild_vmfd',
+  'tap_migration',
 ]
tests/functional/x86_64/test_tap_migration.py new file +425 -0

425 lines not on this page · raw message

Signature · 1 line
MST
#8 lore raw

[PULL 11/78] tests/unit: add test for duplicate keys in CPR FD table

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

cpr_save_fd() now refuses to register an FD under a (name, id) key which is already taken. Add a test which checks that 2nd registration fails with an error while the 1st one is left intact, that different names or ids don't clash, and that the key can be reused once deleted.

Message-ID: <[email protected]>

---
 tests/unit/test-cpr.c  | 98 ++++++++++++++++++++++++++++++++++++++++++
 tests/unit/meson.build |  1 +
 2 files changed, 99 insertions(+)
 create mode 100644 tests/unit/test-cpr.c
tests/unit/test-cpr.c new file +98 -0
new file mode 100644
index 0000000000..dfc6b1f2ec
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Unit tests for the CPR fd table
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/cpr.h"
+#include "migration/vmstate.h"
+#include "monitor/monitor.h"
+#include "hw/vfio/vfio-cpr.h"
+
+/* Stubs for the parts of migration/cpr.c that these tests don't exercise */
+
+const VMStateDescription vmstate_cpr_vfio_devices = {
+    .name = "cpr vfio devices",
+    .fields = (const VMStateField[]) {
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+QEMUFile *cpr_transfer_output(MigrationChannel *channel, Error **errp)
+{
+    g_assert_not_reached();
+}
+
+QEMUFile *cpr_transfer_input(MigrationChannel *channel, Error **errp)
+{
+    g_assert_not_reached();
+}
+
+QEMUFile *cpr_exec_output(Error **errp)
+{
+    g_assert_not_reached();
+}
+
+QEMUFile *cpr_exec_input(Error **errp)
+{
+    g_assert_not_reached();
+}
+
+bool cpr_exec_persist_state(QEMUFile *f, Error **errp)
+{
+    g_assert_not_reached();
+}
+
+bool cpr_exec_has_state(void)
+{
+    return false;
+}
+
+void cpr_exec_unpreserve_fds(void)
+{
+}
+
+int monitor_fd_param(Monitor *mon, const char *fdname, Error **errp)
+{
+    g_assert_not_reached();
+}
+
+static void test_cpr_fd_duplicate(void)
+{
+    Error *err = NULL;
+
+    g_assert_true(cpr_save_fd("dup", 0, 10, &error_abort));
+
+    /* A second registration of the same (name, id) key must be refused */
+    g_assert_false(cpr_save_fd("dup", 0, 11, &err));
+    g_assert_nonnull(err);
+    g_assert_nonnull(strstr(error_get_pretty(err), "already registered"));
+    error_free(err);
+
+    /* and the first registration must be left intact */
+    g_assert_cmpint(cpr_find_fd("dup", 0), ==, 10);
+
+    /* A different id or name is a different key */
+    g_assert_true(cpr_save_fd("dup", 1, 12, &error_abort));
+    g_assert_true(cpr_save_fd("other", 0, 13, &error_abort));
+    g_assert_cmpint(cpr_find_fd("dup", 1), ==, 12);
+    g_assert_cmpint(cpr_find_fd("other", 0), ==, 13);
+
+    /* Once deleted, the key can be registered again */
+    cpr_delete_fd("dup", 0);
+    g_assert_cmpint(cpr_find_fd("dup", 0), ==, -1);
+    g_assert_true(cpr_save_fd("dup", 0, 14, &error_abort));
+    g_assert_cmpint(cpr_find_fd("dup", 0), ==, 14);
+
+    cpr_delete_fd("dup", 0);
+    cpr_delete_fd("dup", 1);
+    cpr_delete_fd("other", 0);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    g_test_add_func("/cpr/fd/duplicate", test_cpr_fd_duplicate);
+    return g_test_run();
+}
tests/unit/meson.build +1 -0
index d17435c187..5d2b413fca 100644
@@ -140,6 +140,7 @@ if have_system
     'test-bufferiszero': [],
     'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-smp.c'],
     'test-vmstate': [migration, io],
+    'test-cpr': [migration, io, meson.project_source_root() / 'migration/cpr.c'],
     'test-yank': ['socket-helpers.c', qom, io, chardev],
     'test-ram-discard-manager': [
       'test-ram-discard-manager.c',
Signature · 1 line
MST
#9 lore raw

[PULL 19/78] vhost-vsock: preserve vhost FD during CPR

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

During CPR (checkpoint-restore) migration the guest keeps running on the same host, so instead of reopening /dev/vhost-vsock on the destination, we should reuse the FD from the source. The FD is saved in the CPR namespace (hash table) with cpr_save_fd() and then reclaimed on the target via cpr_find_fd().

Use the (suffixed) device path as the key in CPR hash table. Device path is obtained with qdev_get_dev_path() and is essentially the device's address on the underlying bus. As any vhost-vsock device is attached to a virtio bus, whose .get_dev_path() forwards to the proxy device, and every proxy bus (PCI, MMIO, CCW) implements it, we should always be getting a unique non-empty path with qdev_get_dev_path().

vhost_dev_init() (and thus VHOST_SET_OWNER) still runs in realize() here. Deferring the ownership handoff to pre_save/post_load is done in a following patch. Until then CPR remains blocked, so the restore path added here is not reachable yet.

Message-ID: <[email protected]>

---
 hw/virtio/vhost-vsock.c | 37 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)
hw/virtio/vhost-vsock.c +36 -1
index 3ec20d8760..6e89007b2c 100644
@@ -69,6 +69,18 @@ static int vhost_vsock_set_running(VirtIODevice *vdev, int start)
     return 0;
 }
 
+/*
+ * Any vhost-vsock device is attached to a virtio bus, whose .get_dev_path()
+ * forwards to the proxy device, and every proxy bus (PCI, MMIO, CCW)
+ * implements it.  Thus qdev_get_dev_path() always returns a non-empty string,
+ * which guarantees us a unique CPR name.
+ */
+static char *vhost_vsock_cpr_name(DeviceState *dev)
+{
+    g_autofree char *path = qdev_get_dev_path(dev);
+    g_assert(path && path[0]);
+    return g_strdup_printf("%s/vhost-vsock", path);
+}
 
 static int vhost_vsock_set_status(VirtIODevice *vdev, uint8_t status)
 {
@@ -143,6 +155,8 @@ static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
     VHostVSockCommon *vvc = VHOST_VSOCK_COMMON(dev);
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
     VHostVSock *vsock = VHOST_VSOCK(dev);
+    g_autofree char *cpr_name = vhost_vsock_cpr_name(dev);
+    bool cpr_incoming = cpr_is_incoming();
     int vhostfd;
     int ret;
 
@@ -170,7 +184,14 @@ static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    if (vsock->conf.vhostfd) {
+    if (cpr_incoming) {
+        /* Reuse the fd handed over from the source QEMU */
+        vhostfd = cpr_find_fd(cpr_name, 0);
+        if (vhostfd < 0) {
+            error_setg(errp, "vhost-vsock: could not find restored vhost FD");
+            goto err_blocker;
+        }
+    } else if (vsock->conf.vhostfd) {
         vhostfd = monitor_fd_param(monitor_cur(), vsock->conf.vhostfd, errp);
         if (vhostfd == -1) {
             error_prepend(errp, "vhost-vsock: unable to parse vhostfd: ");
@@ -186,6 +207,9 @@ static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
 
     if (!qemu_set_blocking(vhostfd, false, errp)) {
         close(vhostfd);
+        if (cpr_incoming) {
+            cpr_delete_fd(cpr_name, 0);
+        }
         goto err_blocker;
     }
 
@@ -207,11 +231,20 @@ static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
         goto err_vhost_dev;
     }
 
+    /* Register the fd for a future CPR after a fully successful realize */
+    if (!cpr_incoming && !cpr_save_fd(cpr_name, 0, vhostfd, errp)) {
+        goto err_vhost_dev;
+    }
+
     return;
 
 err_vhost_dev:
     /* vhost_dev_cleanup() closes the vhostfd passed to vhost_dev_init() */
     vhost_dev_cleanup(&vvc->vhost_dev);
+    if (cpr_incoming) {
+        /* The fd came from cpr_find_fd() and is closed now, drop the entry */
+        cpr_delete_fd(cpr_name, 0);
+    }
 err_virtio:
     vhost_vsock_common_unrealize(vdev);
 err_blocker:
@@ -223,10 +256,12 @@ static void vhost_vsock_device_unrealize(DeviceState *dev)
     VHostVSockCommon *vvc = VHOST_VSOCK_COMMON(dev);
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
     VHostVSock *vsock = VHOST_VSOCK(dev);
+    g_autofree char *cpr_name = vhost_vsock_cpr_name(dev);
 
     /* This will stop vhost backend if appropriate. */
     vhost_vsock_set_status(vdev, 0);
 
+    cpr_delete_fd(cpr_name, 0);
     migrate_del_blocker(&vsock->migration_blocker);
     vhost_dev_cleanup(&vvc->vhost_dev);
     vhost_vsock_common_unrealize(vdev);
Signature · 1 line
MST
#10 lore raw

[PULL 13/78] migration: forbid snapshots under Xen

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

The snapshot RAM-saving path does not support Xen's memory mappings. Refuse 'savevm' and 'loadvm' when running as a Xen device model, the same way they are refused for CPR migration modes.

Message-ID: <[email protected]>

---
 migration/options.c | 6 ++++++
 1 file changed, 6 insertions(+)
migration/options.c +6 -0
index 92eda90114..0b7f393492 100644
@@ -37,6 +37,7 @@
 #include "ram.h"
 #include "options.h"
 #include "system/kvm.h"
+#include "system/xen.h"
 
 /* Maximum migrate downtime set to 2000 seconds */
 #define MAX_MIGRATE_DOWNTIME_SECONDS 2000
@@ -563,6 +564,11 @@ bool migrate_can_snapshot(Error **errp)
     MigMode mode = migrate_mode();
     int i;
 
+    if (xen_enabled()) {
+        error_setg(errp, "Snapshots are not compatible with Xen");
+        return false;
+    }
+
     if (mode != MIG_MODE_NORMAL) {
         error_setg(errp, "Snapshots are not compatible with migration mode %s",
                    MigMode_str(mode));
Signature · 1 line
MST
#11 lore raw

[PULL 35/78] hw/cxl: convert cxl-type3 to three-phase reset

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

Replace the deprecated device_class_set_legacy_reset() registration with the three-phase resettable interface, following the pattern already established by the CXL root port (cxl_rp_reset_hold).

Only the hold phase is needed; enter and exit are left NULL. The parent hold phase is chained for correctness: TYPE_PCI_DEVICE installs no hold phase today, so parent_phases.hold is currently NULL and the chained call is a no-op, but capturing and invoking it is the correct forward-compatible pattern and mirrors cxl_rp_reset_hold().

No functional change — the reset body is identical; only the registration path and function signature change.

Message-ID: <[email protected]>

---
 include/hw/cxl/cxl_device.h |  2 ++
 hw/mem/cxl_type3.c          | 16 ++++++++++++----
 2 files changed, 14 insertions(+), 4 deletions(-)
include/hw/cxl/cxl_device.h +2 -0
index ba551fa5f9..b7e20e3fe4 100644
@@ -805,6 +805,8 @@ struct CXLType3Class {
     /* Private */
     PCIDeviceClass parent_class;
 
+    ResettablePhases parent_phases;
+
     /* public */
     uint64_t (*get_lsa_size)(CXLType3Dev *ct3d);
 
hw/mem/cxl_type3.c +12 -4
index 363ac632f9..734397da62 100644
@@ -1329,13 +1329,18 @@ MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
     return address_space_write(as, dpa_offset, attrs, &data, size);
 }
 
-static void ct3d_reset(DeviceState *dev)
+static void ct3d_reset_hold(Object *obj, ResetType type)
 {
-    CXLType3Dev *ct3d = CXL_TYPE3(dev);
+    CXLType3Dev *ct3d = CXL_TYPE3(obj);
+    CXLType3Class *cvc = CXL_TYPE3_GET_CLASS(obj);
     uint32_t *reg_state = ct3d->cxl_cstate.crb.cache_mem_registers;
     uint32_t *write_msk = ct3d->cxl_cstate.crb.cache_mem_regs_write_mask;
 
-    pcie_cap_fill_link_ep_usp(PCI_DEVICE(dev), ct3d->width, ct3d->speed,
+    if (cvc->parent_phases.hold) {
+        cvc->parent_phases.hold(obj, type);
+    }
+
+    pcie_cap_fill_link_ep_usp(PCI_DEVICE(obj), ct3d->width, ct3d->speed,
                               ct3d->flitmode);
     cxl_component_register_init_common(reg_state, write_msk,
                                        CXL2_TYPE3_DEVICE, ct3d->hdmdb);
@@ -2467,6 +2472,7 @@ static void ct3_class_init(ObjectClass *oc, const void *data)
     DeviceClass *dc = DEVICE_CLASS(oc);
     PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
     CXLType3Class *cvc = CXL_TYPE3_CLASS(oc);
+    ResettableClass *rc = RESETTABLE_CLASS(oc);
 
     pc->realize = ct3_realize;
     pc->exit = ct3_exit;
@@ -2480,9 +2486,11 @@ static void ct3_class_init(ObjectClass *oc, const void *data)
 
     set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
     dc->desc = "CXL Memory Device (Type 3)";
-    device_class_set_legacy_reset(dc, ct3d_reset);
     device_class_set_props(dc, ct3_props);
 
+    resettable_class_set_parent_phases(rc, NULL, ct3d_reset_hold, NULL,
+                                       &cvc->parent_phases);
+
     cvc->get_lsa_size = get_lsa_size;
     cvc->get_lsa = get_lsa;
     cvc->set_lsa = set_lsa;
Signature · 1 line
MST
#12 lore raw

[PULL 36/78] hw/cxl: add cxl_destroy_mailbox_t3() as the mailbox teardown counterpart

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

The primary CCI teardown is open-coded, guarded by .initialized, at the two places that need it: the reset path (before cxl_device_register_init_t3() brings the mailbox back up) and ct3_exit().

Factor it into cxl_destroy_mailbox_t3(), the teardown counterpart of cxl_initialize_mailbox_t3(), keeping the .initialized guard inside the
helper: the primary CCI is only brought up from the reset path, so the first reset after realize and an unrealize before any reset must both skip the teardown.

No functional change.

Message-ID: <[email protected]>

---
 include/hw/cxl/cxl_device.h | 1 +
 hw/cxl/cxl-mailbox-utils.c  | 7 +++++++
 hw/mem/cxl_type3.c          | 8 ++------
 3 files changed, 10 insertions(+), 6 deletions(-)
include/hw/cxl/cxl_device.h +1 -0
index b7e20e3fe4..608d05165a 100644
@@ -325,6 +325,7 @@ CXL_DEVICE_CAPABILITY_HEADER_REGISTER(MEMORY_DEVICE,
                                           CXL_DEVICE_CAP_REG_SIZE * 2)
 
 void cxl_initialize_mailbox_t3(CXLCCI *cci, DeviceState *d, size_t payload_max);
+void cxl_destroy_mailbox_t3(CXLType3Dev *ct3d);
 void cxl_initialize_mailbox_swcci(CXLCCI *cci, DeviceState *intf,
                                   DeviceState *d, size_t payload_max);
 void cxl_init_cci(CXLCCI *cci, size_t payload_max);
hw/cxl/cxl-mailbox-utils.c +7 -0
index 603677a97b..bc00fec9f8 100644
@@ -4843,6 +4843,13 @@ void cxl_initialize_mailbox_t3(CXLCCI *cci, DeviceState *d, size_t payload_max)
     cxl_init_cci(cci, payload_max);
 }
 
+void cxl_destroy_mailbox_t3(CXLType3Dev *ct3d)
+{
+    if (ct3d->cci.initialized) {
+        cxl_destroy_cci(&ct3d->cci);
+    }
+}
+
 static const struct cxl_cmd cxl_cmd_set_t3_ld[256][256] = {
     [INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0 },
     [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0,
hw/mem/cxl_type3.c +2 -6
index 734397da62..04558a114f 100644
@@ -1076,9 +1076,7 @@ static void ct3_exit(PCIDevice *pci_dev)
     cxl_doe_cdat_release(cxl_cstate);
     msix_uninit_exclusive_bar(pci_dev);
     g_free(regs->special_ops);
-    if (ct3d->cci.initialized) {
-        cxl_destroy_cci(&ct3d->cci);
-    }
+    cxl_destroy_mailbox_t3(ct3d);
     if (ct3d->vdm_fm_owned_ld_mctp_cci.initialized) {
         cxl_destroy_cci(&ct3d->vdm_fm_owned_ld_mctp_cci);
     }
@@ -1344,9 +1342,7 @@ static void ct3d_reset_hold(Object *obj, ResetType type)
                               ct3d->flitmode);
     cxl_component_register_init_common(reg_state, write_msk,
                                        CXL2_TYPE3_DEVICE, ct3d->hdmdb);
-    if (ct3d->cci.initialized) {
-        cxl_destroy_cci(&ct3d->cci);
-    }
+    cxl_destroy_mailbox_t3(ct3d);
     cxl_device_register_init_t3(ct3d, CXL_T3_MSIX_MBOX);
 
     /*
Signature · 1 line
MST
#13 lore raw

[PULL 34/78] hw/cxl: destroy primary CCI before re-initialization on reset

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

ct3d_reset() re-initializes the primary CCI through the call chain cxl_device_register_init_t3() -> cxl_initialize_mailbox_t3() -> cxl_init_cci(), but never calls cxl_destroy_cci() first. Each reset cycle therefore leaks the old timer and leaves the old mutex undestroyed while silently overwriting the CCI state.

Per CXL r4.0, the "Mailbox Interfaces Ready" bit in the Memory Device Status register (Table 8-212) is set after a Conventional Reset or CXL Reset once the device has re-initialized its mailbox interfaces. The CCI is the software abstraction of these interfaces and must be properly torn down before re-initialization.

The secondary CCIs (vdm_fm_owned_ld_mctp_cci, ld0_cci) already follow the correct destroy-before-reinit pattern in the same function; apply the same discipline to the primary CCI.

Also destroy the secondary CCIs in ct3_exit() where they were previously leaked at device removal time. Guard the primary CCI teardown there with the same .initialized check used for the secondary
CCIs: the primary CCI is only brought up from the reset path (cxl_device_register_init_t3()), so a device that is unrealized before its first reset would otherwise tear down a never-initialized CCI.

Fixes cac36a8faffc ("hw/cxl/mbox: Pull the CCI definition out of the CXLDeviceState")

Message-ID: <[email protected]>

---
 hw/mem/cxl_type3.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)
hw/mem/cxl_type3.c +12 -1
index a04dc74ecc..363ac632f9 100644
@@ -1076,7 +1076,15 @@ static void ct3_exit(PCIDevice *pci_dev)
     cxl_doe_cdat_release(cxl_cstate);
     msix_uninit_exclusive_bar(pci_dev);
     g_free(regs->special_ops);
-    cxl_destroy_cci(&ct3d->cci);
+    if (ct3d->cci.initialized) {
+        cxl_destroy_cci(&ct3d->cci);
+    }
+    if (ct3d->vdm_fm_owned_ld_mctp_cci.initialized) {
+        cxl_destroy_cci(&ct3d->vdm_fm_owned_ld_mctp_cci);
+    }
+    if (ct3d->ld0_cci.initialized) {
+        cxl_destroy_cci(&ct3d->ld0_cci);
+    }
     if (ct3d->dc.host_dc) {
         cxl_destroy_dc_regions(ct3d);
         address_space_destroy(&ct3d->dc.host_dc_as);
@@ -1331,6 +1339,9 @@ static void ct3d_reset(DeviceState *dev)
                               ct3d->flitmode);
     cxl_component_register_init_common(reg_state, write_msk,
                                        CXL2_TYPE3_DEVICE, ct3d->hdmdb);
+    if (ct3d->cci.initialized) {
+        cxl_destroy_cci(&ct3d->cci);
+    }
     cxl_device_register_init_t3(ct3d, CXL_T3_MSIX_MBOX);
 
     /*
Signature · 1 line
MST
#14 lore raw

[PULL 37/78] hw/cxl: discard in-flight sanitize state on mailbox teardown

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

Per CXL r4.0 Section 8.2.9.4, "Background commands do not continue to execute across Conventional Resets." If a Media Operations sanitize is in progress when the device is reset, the background timer is cancelled and freed via cxl_destroy_cci(), but the per-operation state (media_op_sanitize) is heap-allocated separately and would be leaked.

Free it from cxl_destroy_mailbox_t3(): the timer that advances the operation lives in the CCI torn down there, so the operation can never complete once the mailbox is gone; preserving the heap state has no benefit and would leak it the next time media_op_sanitize is assigned. Routing the discard through the mailbox teardown also covers device unrealize, where ct3_exit() previously leaked an in-flight sanitize.

The completion path in __do_sanitize() frees the same state; factor the free into a cxl_discard_media_op_sanitize() helper used by both places, so the whole media_op_sanitize lifecycle now stays within cxl-mailbox-utils.c.

Note that Section 8.2.10.9.5.1 additionally requires a device whose Sanitize was interrupted by reset to remain in the Media Disabled state until a successful Sanitize completes. That latch is not modelled here (reset re-enables media via memdev_reg_init_common()) and is left for future work; this patch only addresses the resource leak.

Message-ID: <[email protected]>

---
 hw/cxl/cxl-mailbox-utils.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)
hw/cxl/cxl-mailbox-utils.c +13 -2
index bc00fec9f8..cbd6ebf9fe 100644
@@ -2636,6 +2636,12 @@ static int sanitize_range(CXLType3Dev *ct3d, uint64_t dpa_addr, size_t length,
     return address_space_set(as, dpa_addr, fill_value, length, mem_attrs);
 }
 
+static void cxl_discard_media_op_sanitize(CXLType3Dev *ct3d)
+{
+    g_free(ct3d->media_op_sanitize);
+    ct3d->media_op_sanitize = NULL;
+}
+
 /* Perform the actual device zeroing */
 static void __do_sanitize(CXLType3Dev *ct3d)
 {
@@ -2653,8 +2659,7 @@ static void __do_sanitize(CXLType3Dev *ct3d)
         }
     }
 exit:
-    g_free(ct3d->media_op_sanitize);
-    ct3d->media_op_sanitize = NULL;
+    cxl_discard_media_op_sanitize(ct3d);
     return;
 }
 
@@ -4848,6 +4853,12 @@ void cxl_destroy_mailbox_t3(CXLType3Dev *ct3d)
     if (ct3d->cci.initialized) {
         cxl_destroy_cci(&ct3d->cci);
     }
+    /*
+     * An in-flight Media Operations sanitize is only advanced by this CCI's
+     * background timer; with the CCI gone the operation can never complete,
+     * so its state would otherwise be leaked (CXL r4.0 Section 8.2.9.4).
+     */
+    cxl_discard_media_op_sanitize(ct3d);
 }
 
 static const struct cxl_cmd cxl_cmd_set_t3_ld[256][256] = {
Signature · 1 line
MST
#15 lore raw

[PULL 38/78] hw/cxl: clear event logs, scan media and interrupt policy on reset

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

Event records, scan media results and event interrupt settings are device-internal dynamic state that should not survive a device reset.

Per CXL r4.0 Section 8.2.10.9.4.6 (Get Scan Media Results), "If the Scan Media command has not been called since the last Conventional Reset, the device shall return the Unsupported return code." This explicitly invalidates scan media results across reset, so clear the scan_media_hasrun flag.

Per CXL r4.0 Section 8.2.10.2.5 (Set Event Interrupt Policy), "All event log interrupt settings shall be reset to 00b (No Interrupts) by the device on Conventional Reset." Clear irq_enabled for every event log accordingly.

For the event records there is no direct spec mandate to drop the stored records on reset; the Event Status register (Table 8-203) is non-sticky and resets to zero per Section 9.7, and no reset flavor requires the already-reported records to persist. Draining the queues is therefore a reasonable modelling choice that keeps the records consistent with the freshly-reset status register, rather than a spec requirement. Call the existing cxl_discard_all_event_records() helper to drain all event queues. The event log infrastructure itself (mutexes, IRQ vectors) remains intact as it is initialized once during device realize.

This is also where reset-type gating begins: a wakeup from suspend-to-RAM (RESET_TYPE_WAKEUP) is not a Conventional or CXL Reset and must retain device-internal state, so the hold phase returns early for that type before discarding any records. This mirrors the RESET_TYPE_WAKEUP shortcut in virtio-mem and virtio-balloon. Only the unconditional mailbox interface re-initialization and the in-flight sanitize free (whose backing timer was already destroyed) run on a wakeup.

Message-ID: <[email protected]>

---
 hw/mem/cxl_type3.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
hw/mem/cxl_type3.c +17 -0
index 04558a114f..26c3773dc0 100644
@@ -1360,6 +1360,23 @@ static void ct3d_reset_hold(Object *obj, ResetType type)
     }
     cxl_initialize_t3_ld_cci(&ct3d->ld0_cci, DEVICE(ct3d), DEVICE(ct3d),
                              512); /* Max payload made up */
+
+    /*
+     * A wakeup from suspend-to-RAM is not a Conventional or CXL Reset.  The
+     * device-internal dynamic state cleared below (event logs, scan media
+     * results, and the poison/feature-transfer state cleared in subsequent
+     * patches) must be preserved across resume, so stop here for a wakeup.
+     * virtio-mem and virtio-balloon take the same RESET_TYPE_WAKEUP shortcut.
+     */
+    if (type == RESET_TYPE_WAKEUP) {
+        return;
+    }
+
+    cxl_discard_all_event_records(&ct3d->cxl_dstate);
+    for (int i = 0; i < CXL_EVENT_TYPE_MAX; i++) {
+        ct3d->cxl_dstate.event_logs[i].irq_enabled = false;
+    }
+    ct3d->scan_media_hasrun = false;
 }
 
 static const Property ct3_props[] = {
Signature · 1 line
MST
#16 lore raw

[PULL 39/78] hw/cxl: clear poison lists and feature transfer state on reset

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

The poison lists (active, backup, and scan-media results), their associated overflow tracking, and the Set Feature partial-transfer state are all device-internal dynamic state that accumulates during the device's lifetime.

Per CXL r4.0 Table 8-309 (Identify Memory Device), the "Injects Persistent Poison" capability bit (offset 41h, Bit[0]) controls poison retention across reset. When cleared — the QEMU default — "a Conventional Reset or CXL Reset shall automatically clear the injected poison." Clear all poison lists accordingly, reusing the existing cxl_clear_poison_list_overflowed() helper for the overflow tracking.

For the Set Feature transfer state, CXL r4.0 Section 8.2.10.6.3 (Set Feature) requires: "If the Feature data transfer is interrupted by a Conventional Reset or a CXL Reset, the Feature data transfer shall be aborted by the device [...] the device shall require the Feature data transfer to be started from the beginning." Zero set_feat_info on reset so any partially transferred Set Feature is abandoned and must restart from the beginning.

Both clears run after the RESET_TYPE_WAKEUP early-return added in the previous patch, so this state is preserved across a suspend-to-RAM wakeup.

Message-ID: <[email protected]>

---
 hw/mem/cxl_type3.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
hw/mem/cxl_type3.c +18 -0
index 26c3773dc0..1ff01121f8 100644
@@ -1327,6 +1327,16 @@ MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
     return address_space_write(as, dpa_offset, attrs, &data, size);
 }
 
+static void ct3d_clear_poison_list(CXLPoisonList *list)
+{
+    CXLPoison *ent, *next;
+
+    QLIST_FOREACH_SAFE(ent, list, node, next) {
+        QLIST_REMOVE(ent, node);
+        g_free(ent);
+    }
+}
+
 static void ct3d_reset_hold(Object *obj, ResetType type)
 {
     CXLType3Dev *ct3d = CXL_TYPE3(obj);
@@ -1377,6 +1387,14 @@ static void ct3d_reset_hold(Object *obj, ResetType type)
         ct3d->cxl_dstate.event_logs[i].irq_enabled = false;
     }
     ct3d->scan_media_hasrun = false;
+
+    ct3d_clear_poison_list(&ct3d->poison_list);
+    ct3d_clear_poison_list(&ct3d->poison_list_bkp);
+    ct3d_clear_poison_list(&ct3d->scan_media_results);
+    ct3d->poison_list_cnt = 0;
+    cxl_clear_poison_list_overflowed(ct3d);
+
+    memset(&ct3d->set_feat_info, 0, sizeof(ct3d->set_feat_info));
 }
 
 static const Property ct3_props[] = {
Signature · 1 line
MST
#17 lore raw

[PULL 24/78] vhost: release ownership in vhost_dev_cleanup()

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

vhost_dev_cleanup() closes the backend FD, but that doesn't release the device ownership taken with VHOST_SET_OWNER if the same file is open elsewhere. That's the case on a CPR target: the source keeps the FD, so if the target fails after SET_OWNER, either inside vhost_dev_init() or later in its caller, the ownership stays with the target. Once the source is resumed it can't take the device back, SET_OWNER fails with EBUSY.

Track the ownership with a new 'owner' flag in struct vhost_dev, set and cleared by vhost_dev_set_owner() and vhost_dev_reset_owner(), and issue VHOST_RESET_OWNER in vhost_dev_cleanup() as the common teardown for all those paths. Only do it if the ownership was handed over to us by another process, which is marked by the device with a new bool flag 'owner_handed_over'. Upon ownership release failure, we have nothing to unwind at this point (CPR target teardown), so we just report the failure.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost.h | 11 +++++++++++
 hw/virtio/vhost.c         | 40 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 49 insertions(+), 2 deletions(-)
include/hw/virtio/vhost.h +11 -0
index 73ea130d7e..efe1eed260 100644
@@ -111,6 +111,17 @@ struct vhost_dev {
     bool started;
     /* @initialized: has the full vhost_dev_init() completed? */
     bool initialized;
+    /*
+     * @owner: has this vhost_dev acquired the device ownership?
+     * Updated only by vhost_dev_set_owner()/vhost_dev_reset_owner()
+     */
+    bool owner;
+    /*
+     * @owner_handed_over: CPR target side.  The ownership was handed over
+     * to us by another process (the CPR source) and must be given back
+     * when this vhost_dev goes away.  Set by the device.
+     */
+    bool owner_handed_over;
     bool log_enabled;
     uint64_t log_size;
     Error *migration_blocker;
hw/virtio/vhost.c +38 -2
index c01ccc800c..085e2ef6f1 100644
@@ -53,6 +53,25 @@ static QLIST_HEAD(, vhost_dev) vhost_log_devs[VHOST_BACKEND_TYPE_MAX];
 static QLIST_HEAD(, vhost_dev) vhost_devices =
     QLIST_HEAD_INITIALIZER(vhost_devices);
 
+/*
+ * Release the device ownership if it was handed over to us by another
+ * process, e.g. the CPR source.
+ */
+static void vhost_dev_try_release_owner(struct vhost_dev *hdev,
+                                        const char *when)
+{
+    int r;
+
+    if (!hdev->owner || !hdev->owner_handed_over) {
+        return;
+    }
+    r = vhost_dev_reset_owner(hdev);
+    if (r < 0) {
+        error_report("vhost: cannot release device ownership on %s: %s",
+                     when, strerror(-r));
+    }
+}
+
 unsigned int vhost_get_max_memslots(void)
 {
     unsigned int max = UINT_MAX;
@@ -1705,20 +1724,32 @@ int vhost_dev_init_backend(struct vhost_dev *hdev, void *opaque,
 
 int vhost_dev_set_owner(struct vhost_dev *hdev)
 {
+    int r;
+
     assert(hdev->vhost_ops);
     if (!hdev->vhost_ops->vhost_set_owner) {
         return -ENOSYS;
     }
-    return hdev->vhost_ops->vhost_set_owner(hdev);
+    r = hdev->vhost_ops->vhost_set_owner(hdev);
+    if (!r) {
+        hdev->owner = true;
+    }
+    return r;
 }
 
 int vhost_dev_reset_owner(struct vhost_dev *hdev)
 {
+    int r;
+
     assert(hdev->vhost_ops);
     if (!hdev->vhost_ops->vhost_reset_owner) {
         return -ENOSYS;
     }
-    return hdev->vhost_ops->vhost_reset_owner(hdev);
+    r = hdev->vhost_ops->vhost_reset_owner(hdev);
+    if (!r) {
+        hdev->owner = false;
+    }
+    return r;
 }
 
 int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
@@ -1864,6 +1895,11 @@ void vhost_dev_cleanup(struct vhost_dev *hdev)
     g_free(hdev->mem);
     g_free(hdev->mem_sections);
     if (hdev->vhost_ops) {
+        /*
+         * Release the device ownership if we hold it.  Needed e.g. for
+         * failing CPR target, so the source can re-acquire it.
+         */
+        vhost_dev_try_release_owner(hdev, "cleanup");
         hdev->vhost_ops->vhost_cleanup(hdev);
     }
     assert(!hdev->log);
Signature · 1 line
MST
#18 lore raw

[PULL 46/78] vhost-user: remove shared object entries during cleanup

In reply to Michael S. Tsirkin

From: Yuho Choi <[email protected]>

SHARED_OBJECT_ADD publishes a borrowed vhost_dev pointer in the global UUID table. If an exporter goes away without sending SHARED_OBJECT_REMOVE, vhost_user_backend_cleanup() frees dev->opaque but leaves these entries behind. A later lookup from another backend can dereference the NULL opaque pointer, or access a freed vhost_dev if its containing allocation has also been released.

Remove all entries exported by the device after closing its backend request channel and before freeing its state. Match both the resource type and owner pointer so other exporters and dma-buf entries are kept.

Add unit coverage for multiple UUIDs per owner, unrelated resources, repeated cleanup, UUID reuse, and an uninitialized or empty table.

Fixes 160947666276 ("vhost-user: add shared_object msg")

Message-ID: <[email protected]>

---
 include/hw/virtio/virtio-dmabuf.h |  8 ++++++
 hw/display/virtio-dmabuf.c        | 18 +++++++++++++
 hw/virtio/vhost-user.c            |  1 +
 tests/unit/test-virtio-dmabuf.c   | 43 +++++++++++++++++++++++++++++++
 4 files changed, 70 insertions(+)
include/hw/virtio/virtio-dmabuf.h +8 -0
index 627c3b6db7..a1ad362c37 100644
@@ -65,6 +65,14 @@ bool virtio_add_vhost_device(QemuUUID *uuid, struct vhost_dev *dev);
  */
 bool virtio_remove_resource(const QemuUUID *uuid);
 
+/**
+ * virtio_remove_vhost_device() - Remove a vhost device's exported resources
+ * @dev: the exporter whose entries are to be removed
+ *
+ * The caller must remove the entries before cleaning up the device.
+ */
+void virtio_remove_vhost_device(struct vhost_dev *dev);
+
 /**
  * virtio_lookup_dmabuf() - Looks for a dma-buf resource in the lookup table
  * @uuid: resource's UUID
hw/display/virtio-dmabuf.c +18 -0
index 5e0395be77..636372543a 100644
@@ -96,6 +96,24 @@ bool virtio_remove_resource(const QemuUUID *uuid)
     return result;
 }
 
+static gboolean virtio_vhost_device_match(gpointer key, gpointer value,
+                                         gpointer dev)
+{
+    VirtioSharedObject *vso = value;
+
+    return vso->type == TYPE_VHOST_DEV && vso->value == dev;
+}
+
+void virtio_remove_vhost_device(struct vhost_dev *dev)
+{
+    g_mutex_lock(&lock);
+    if (resource_uuids != NULL) {
+        g_hash_table_foreach_remove(resource_uuids, virtio_vhost_device_match,
+                                    dev);
+    }
+    g_mutex_unlock(&lock);
+}
+
 static VirtioSharedObject *get_shared_object(const QemuUUID *uuid)
 {
     gpointer lookup_res = NULL;
hw/virtio/vhost-user.c +1 -0
index 65889b0776..3402accc6f 100644
@@ -2711,6 +2711,7 @@ static int vhost_user_backend_cleanup(struct vhost_dev *dev)
     if (u->backend_sioc) {
         close_backend_channel(u);
     }
+    virtio_remove_vhost_device(dev);
     g_free(u->region_rb);
     u->region_rb = NULL;
     g_free(u->region_rb_offset);
tests/unit/test-virtio-dmabuf.c +43 -0

55 lines not on this page · raw message

Signature · 1 line
MST
#19 lore raw

[PULL 06/78] hw/net: finish backend initialization earler for local migration

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

Thanks to pririty, backend is migrated before the virtio-net itself. So we can finish the releate initialization already in .pre_load, not postponing it to .post_load.

Fixes bb71b1bffe58 ("virtio-net: support local migration of backend")

Message-ID: <[email protected]>

---
 hw/net/virtio-net.c | 40 +++++++++++++++++-----------------------
 1 file changed, 17 insertions(+), 23 deletions(-)
hw/net/virtio-net.c +17 -23
index 19f453fb7d..4794496b96 100644
@@ -3250,13 +3250,28 @@ static void virtio_net_get_features(VirtIODevice *vdev, uint64_t *features,
     }
 }
 
+static bool virtio_net_pre_load_device(void *opaque, Error **errp)
+{
+    ERRP_GUARD();
+    VirtIONet *n = opaque;
+    VirtIODevice *vdev = VIRTIO_DEVICE(n);
+
+    if (n->peers_wait_incoming) {
+        /* MIG_PRI_BACKEND ensures the peers have already been loaded. */
+        n->peers_wait_incoming = false;
+        peer_test_vnet_hdr(n);
+        virtio_net_get_features(vdev, vdev->host_features_ex, errp);
+    }
+
+    return !*errp;
+}
+
 static int virtio_net_post_load_device(void *opaque, int version_id)
 {
     VirtIONet *n = opaque;
     VirtIODevice *vdev = VIRTIO_DEVICE(n);
     int i, link_down;
     bool has_tunnel_hdr = virtio_has_tunnel_hdr(vdev->guest_features_ex);
-    Error *local_err = NULL;
 
     trace_virtio_net_post_load_device();
     virtio_net_set_mrg_rx_bufs(n, n->mergeable_rx_bufs,
@@ -3314,20 +3329,6 @@ static int virtio_net_post_load_device(void *opaque, int version_id)
     }
 
     virtio_net_commit_rss_config(n);
-
-    /*
-     * If live-migration is enabled for some backend, than backend
-     * has already been migrated at higher priority (MIG_PRI_BACKEND)
-     * and virtio_net_vnet_post_load() has already called
-     * peer_test_vnet_hdr().  Recompute host_features so that virtio-net
-     * reflects the capabilities of the restored backend.
-     */
-    virtio_net_get_features(vdev, &vdev->host_features, &local_err);
-    if (local_err) {
-        error_report_err(local_err);
-        return -EINVAL;
-    }
-
     return 0;
 }
 
@@ -3478,14 +3479,6 @@ static int virtio_net_vnet_post_load(void *opaque, int version_id)
 {
     struct VirtIONetMigTmp *tmp = opaque;
 
-    /*
-     * If live-migration is enabled for some backend, than backend
-     * has already been migrated at higher priority (MIG_PRI_BACKEND),
-     * so n->has_vnet_hdr can be refreshed from the live backend right
-     * here.
-     */
-    peer_test_vnet_hdr(tmp->parent);
-
     if (tmp->has_vnet_hdr && !peer_has_vnet_hdr(tmp->parent)) {
         error_report("virtio-net: saved image requires vnet_hdr=on");
         return -EINVAL;
@@ -3674,6 +3667,7 @@ static const VMStateDescription vmstate_virtio_net_device = {
     .name = "virtio-net-device",
     .version_id = VIRTIO_NET_VM_VERSION,
     .minimum_version_id = VIRTIO_NET_VM_VERSION,
+    .pre_load_errp = virtio_net_pre_load_device,
     .post_load = virtio_net_post_load_device,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT8_ARRAY(mac, VirtIONet, ETH_ALEN),
Signature · 1 line
MST
#20 lore raw

[PULL 08/78] net/tap: net_init_tap_one(): fix failure path

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We should "goto failed" here, to delete net client, same as for previous failure path in the function.

Fixes 69036a855492 ("net/tap: move vhost initialization to tap_setup_vhost()")

Message-ID: <[email protected]>

---
 net/tap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
net/tap.c +1 -1
index 9f829f49c1..acccabca56 100644
@@ -996,7 +996,7 @@ static bool net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
     s->vhostfd = vhostfd;
     s->vhost_busyloop_timeout = tap->has_poll_us ? tap->poll_us : 0;
     if (!tap_setup_vhost(s, errp)) {
-        return false;
+        goto failed;
     }
 
     return true;
Signature · 1 line
MST
#21 lore raw

[PULL 56/78] hw/pci-bridge: split CXL PXB into separate file

In reply to Michael S. Tsirkin

From: Daniel P. Berrangé <[email protected]>

If CONFIG_CXL=n is added to the Kconfig, the build fails to link the PXB device:

/usr/bin/ld.bfd: libsystem.a.p/hw_pci-bridge_pci_expander_bridge.c.o: in function `pxb_cxl_realize':
/home/berrange/src/virt/qemu/build/../hw/pci-bridge/pci_expander_bridge.c:202:(.text+0x330): undefined reference to cxl_component_register_block_init' /usr/bin/ld.bfd: libsystem.a.p/hw_pci-bridge_pci_expander_bridge.c.o: in function pxb_cxl_dev_reset':
/home/berrange/src/virt/qemu/build/../hw/pci-bridge/pci_expander_bridge.c:309:(.text+0x768): undefined reference to `cxl_component_register_init_common'

We cannot use CONFIG_CXL in pci_expander_bridge.c because the identifier is poisoned. Instead the CXL pieces must be split off into a separate source file which can be conditionally built.

With this change, the stubs file is no longer required, since the code referencing the stub was already protected by CONFIG_CXL.

Fixes 6e4e3ae936e6 ("hw/cxl/component: Implement host bridge MMIO (8.2.5, table 142)")

Message-ID: <[email protected]>

---
 hw/pci-bridge/pci_expander_bridge_common.h    |  28 +++
 ...der_bridge.h => pci_expander_bridge_cxl.h} |   0
 hw/cxl/cxl-host.c                             |   2 +-
 hw/pci-bridge/pci_expander_bridge.c           | 194 +----------------
 hw/pci-bridge/pci_expander_bridge_cxl.c       | 201 ++++++++++++++++++
 hw/pci-bridge/pci_expander_bridge_stubs.c     |  14 --
 hw/pci-bridge/meson.build                     |   2 +-
 7 files changed, 240 insertions(+), 201 deletions(-)
 create mode 100644 hw/pci-bridge/pci_expander_bridge_common.h
 rename include/hw/pci-bridge/{pci_expander_bridge.h => pci_expander_bridge_cxl.h} (100%)
 create mode 100644 hw/pci-bridge/pci_expander_bridge_cxl.c
 delete mode 100644 hw/pci-bridge/pci_expander_bridge_stubs.c
hw/pci-bridge/pci_expander_bridge_common.h new file +28 -0
new file mode 100644
index 0000000000..d894ed0add
@@ -0,0 +1,28 @@
+/*
+ * PCI Expander Bridge Device Emulation Common Code
+ *
+ * Copyright (C) 2015 Red Hat Inc
+ *
+ * Authors:
+ *   Marcel Apfelbaum <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+enum BusType { PCI, PCIE, CXL };
+
+typedef struct PXBBus PXBBus;
+
+struct PXBBus {
+    /*< private >*/
+    PCIBus parent_obj;
+    /*< public >*/
+
+    char bus_path[8];
+};
+
+int pxb_bus_num(PCIBus *bus);
+void pxb_bus_class_init(ObjectClass *class, const void *data);
+bool pxb_dev_realize_common(PCIDevice *dev, enum BusType type,
+                            Error **errp);
+void pxb_dev_exitfn(PCIDevice *pci_dev);
include/hw/pci-bridge/pci_expander_bridge_cxl.h renamed from include/hw/pci-bridge/pci_expander_bridge.h +0 -0
similarity index 100%
rename from include/hw/pci-bridge/pci_expander_bridge.h
rename to include/hw/pci-bridge/pci_expander_bridge_cxl.h
hw/cxl/cxl-host.c +1 -1
index e279ce5fe3..cda4dfed50 100644
@@ -20,7 +20,7 @@
 #include "hw/pci/pci_bridge.h"
 #include "hw/pci/pci_host.h"
 #include "hw/pci/pcie_port.h"
-#include "hw/pci-bridge/pci_expander_bridge.h"
+#include "hw/pci-bridge/pci_expander_bridge_cxl.h"
 
 static void cxl_fixed_memory_window_config(CXLFixedMemoryWindowOptions *object,
                                            int index, Error **errp)
hw/pci-bridge/pci_expander_bridge.c +9 -185

273 lines not on this page · raw message

hw/pci-bridge/pci_expander_bridge_cxl.c new file +201 -0

201 lines not on this page · raw message

hw/pci-bridge/pci_expander_bridge_stubs.c deleted +0 -14
deleted file mode 100644
index b35180311f..0000000000
@@ -1,14 +0,0 @@
-/*
- * SPDX-License-Identifier: GPL-2.0-or-later
- *
- * Stubs for calls made from machines to handle the case where CONFIG_PXB
- * is not enabled.
- */
-#include "qemu/osdep.h"
-#include "qapi/error.h"
-#include "hw/pci/pci.h"
-#include "hw/pci/pci_bus.h"
-#include "hw/pci-bridge/pci_expander_bridge.h"
-#include "hw/cxl/cxl.h"
-
-void pxb_cxl_hook_up_registers(CXLState *state, PCIBus *bus, Error **errp) {};
hw/pci-bridge/meson.build +1 -1
index aa9959df04..563d3f5147 100644
@@ -6,7 +6,7 @@ pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('pcie_root_port.c', 'gen_pci
                                                     'gen_pcie_upstream_port.c', 'gen_pcie_downstream_port.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PCI_BRIDGE', if_true: files('pcie_pci_bridge.c'))
 pci_ss.add(when: 'CONFIG_PXB', if_true: files('pci_expander_bridge.c'))
-stub_ss.add(files('pci_expander_bridge_stubs.c'))
+pci_ss.add(when: 'CONFIG_CXL', if_true: files('pci_expander_bridge_cxl.c'))
 pci_ss.add(when: 'CONFIG_XIO3130', if_true: files('xio3130_upstream.c', 'xio3130_downstream.c'))
 pci_ss.add(when: 'CONFIG_CXL', if_true: files('cxl_root_port.c', 'cxl_upstream.c', 'cxl_downstream.c'))
 
Signature · 1 line
MST
#22 lore raw

[PULL 29/78] tests/qtest/migration: add vhost-vsock tests for cpr-transfer and cpr-exec

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

Run the existing cpr-transfer and cpr-exec tests with a vhost-vsock-pci device attached. There's no guest involved which is driving the device, so here we only test the QEMU side of the handoff, i.e. the vhost FD is preserved, the device ownership is released on the source and re-acquired on the target.

The tests need /dev/vhost-vsock on the host and a kernel supporting VHOST_RESET_OWNER for vhost-vsock, they are skipped otherwise.

Message-ID: <[email protected]>

---
 tests/qtest/migration/cpr-tests.c | 93 +++++++++++++++++++++++++++++--
 1 file changed, 87 insertions(+), 6 deletions(-)
tests/qtest/migration/cpr-tests.c +87 -6
index 05c2ac1336..3c21b79001 100644
@@ -19,6 +19,13 @@
 #include "qobject/qjson.h"
 #include "qobject/qlist.h"
 
+#ifdef CONFIG_LINUX
+#include <sys/ioctl.h>
+#include <linux/vhost.h>
+#endif
+
+/* We expect this CID to be unassigned on the host */
+#define VHOST_VSOCK_TEST_DEV "vhost-vsock-pci,guest-cid=4000000000"
 
 static char *tmpfs;
 
@@ -101,14 +108,17 @@ static int test_transfer(MigrateCommon *args, const char *cpr_channel,
  * migration, and cannot connect synchronously to the monitor, so defer
  * the target connection.
  */
-static void test_mode_transfer_common(MigrateCommon *args, bool incoming_defer)
+static void test_mode_transfer_common(MigrateCommon *args, bool incoming_defer,
+                                      const char *dev_opts)
 {
     g_autofree char *cpr_path = g_strdup_printf("%s/cpr.sock", tmpfs);
     g_autofree char *mig_path = g_strdup_printf("%s/migsocket", tmpfs);
     g_autofree char *uri = g_strdup_printf("unix:%s", mig_path);
     g_autofree char *opts_target = NULL;
 
-    const char *opts = "-machine aux-ram-share=on -nodefaults";
+    g_autofree char *opts = g_strdup_printf("-machine aux-ram-share=on "
+                                            "-nodefaults %s",
+                                            dev_opts ? dev_opts : "");
     g_autofree const char *cpr_channel = g_strdup_printf(
         "cpr,addr.transport=socket,addr.type=unix,addr.path=%s",
         cpr_path);
@@ -154,12 +164,12 @@ static void test_mode_transfer_common(MigrateCommon *args, bool incoming_defer)
 
 static void test_mode_transfer(char *name, MigrateCommon *args)
 {
-    test_mode_transfer_common(args, false);
+    test_mode_transfer_common(args, false, NULL);
 }
 
 static void test_mode_transfer_defer(char *name, MigrateCommon *args)
 {
-    test_mode_transfer_common(args, true);
+    test_mode_transfer_common(args, true, NULL);
 }
 
 static void set_cpr_exec_args(QTestState *who, MigrateCommon *args)
@@ -276,20 +286,85 @@ static void *test_mode_exec_start(QTestState *from, QTestState *to)
     return NULL;
 }
 
-static void test_mode_exec(char *name, MigrateCommon *args)
+static void test_mode_exec_common(MigrateCommon *args, const char *dev_opts)
 {
     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
                                            FILE_TEST_FILENAME);
+    g_autofree char *opts = g_strdup_printf("-machine aux-ram-share=on "
+                                            "-nodefaults %s",
+                                            dev_opts ? dev_opts : "");
     args->uri = uri;
     args->start_hook = test_mode_exec_start;
 
     args->start.only_source = true;
-    args->start.opts_source = "-machine aux-ram-share=on -nodefaults";
+    args->start.opts_source = opts;
     args->start.mem_type = MEM_TYPE_MEMFD;
 
     test_cpr_exec(args);
 }
 
+static void test_mode_exec(char *name, MigrateCommon *args)
+{
+    test_mode_exec_common(args, NULL);
+}
+
+/*
+ * A vhost-vsock device can be handed over across CPR only if the host
+ * exposes /dev/vhost-vsock and its kernel supports VHOST_RESET_OWNER
+ * for it.  Skip the test otherwise.
+ */
+static bool vhost_vsock_cpr_check(void)
+{
+#ifdef CONFIG_LINUX
+    bool set_owner, reset_owner;
+    int fd;
+
+    if (!qtest_has_device("vhost-vsock-pci")) {
+        g_test_skip("vhost-vsock-pci device is not available");
+        return false;
+    }
+
+    fd = open("/dev/vhost-vsock", O_RDWR);
+    if (fd < 0) {
+        g_test_skip("/dev/vhost-vsock is not available");
+        return false;
+    }
+
+    set_owner = ioctl(fd, VHOST_SET_OWNER) == 0;
+    reset_owner = ioctl(fd, VHOST_RESET_OWNER) == 0;
+    close(fd);
+
+    if (!set_owner) {
+        g_test_skip("VHOST_SET_OWNER is not supported for vhost-vsock");
+        return false;
+    }
+
+    if (!reset_owner) {
+        g_test_skip("VHOST_RESET_OWNER is not supported for vhost-vsock");
+        return false;
+    }
+
+    return true;
+#else
+    g_test_skip("vhost-vsock is Linux only");
+    return false;
+#endif
+}
+
+static void test_mode_transfer_vhost_vsock(char *name, MigrateCommon *args)
+{
+    if (vhost_vsock_cpr_check()) {
+        test_mode_transfer_common(args, false, "-device " VHOST_VSOCK_TEST_DEV);
+    }
+}
+
+static void test_mode_exec_vhost_vsock(char *name, MigrateCommon *args)
+{
+    if (vhost_vsock_cpr_check()) {
+        test_mode_exec_common(args, "-device " VHOST_VSOCK_TEST_DEV);
+    }
+}
+
 /* Snapshots must be refused while a CPR migration mode is set */
 static void test_mode_no_snapshot(char *name, MigrateCommon *args)
 {
@@ -354,4 +429,10 @@ void migration_test_add_cpr(MigrationTestEnv *env)
                            test_mode_transfer_defer);
         migration_test_add("/migration/mode/exec", test_mode_exec);
     }
+
+    /* The vhost-vsock handoff doesn't need KVM */
+    migration_test_add("/migration/mode/transfer/vhost-vsock",
+                       test_mode_transfer_vhost_vsock);
+    migration_test_add("/migration/mode/exec/vhost-vsock",
+                       test_mode_exec_vhost_vsock);
 }
Signature · 1 line
MST
#23 lore raw

[PULL 41/78] hw/pci-bridge: Add generic PCIe switch upstream and downstream ports

In reply to Michael S. Tsirkin

From: David Vrabel <[email protected]>

The TI XIO3130 PCIe switch is a vintage Gen1 device, so we add generic PCIe upstream and downstream port devices that are Gen6.

In guests with pairs of passthrough PCIe devices (e.g, a GPU+NIC pair) often need to place the devices behind a common (emulated) PCIe switch so in-guest software (such as NCCL) can correctly determine that P2P transactions between the devices is possible.

NCCL also needs to be able to determine the available PCIe bandwidth. If such a guest sees the TI XIO3130 PCIe switch, then it incorrectly calculates only 2.5 GT/s and x1 is available, but the real hardware might be 32 GT/s and x16.

Instead of enhancing the existing switch and claiming it is more capable than the real hardware we add additional generic PCIe upstream and downstream port devices, which matches the existing generic PCIe root port device.

The speed and width of the ports are configurable.

Message-ID: <[email protected]>

---
 docs/pcie.txt                               |   6 +-
 docs/specs/pci-ids.rst                      |   4 +
 include/hw/pci/pci.h                        |   2 +
 hw/pci-bridge/gen_pcie_downstream_port.c    | 202 ++++++++++++++++++++
 hw/pci-bridge/gen_pcie_upstream_port.c      | 177 +++++++++++++++++
 hw/pci-bridge/meson.build                   |   3 +-
 tests/functional/x86_64/meson.build         |   1 +
 tests/functional/x86_64/test_pcie_switch.py |  98 ++++++++++
 8 files changed, 489 insertions(+), 4 deletions(-)
 create mode 100644 hw/pci-bridge/gen_pcie_downstream_port.c
 create mode 100644 hw/pci-bridge/gen_pcie_upstream_port.c
 create mode 100755 tests/functional/x86_64/test_pcie_switch.py
docs/pcie.txt +3 -3
index df49178311..f12a9daaad 100644
@@ -85,7 +85,7 @@ number of PCI Express Root Ports per PCI Express Root Bus is 256.
 
 Prefer grouping PCI Express Root Ports into multi-function devices
 to keep a simple flat hierarchy that is enough for most scenarios.
-Only use PCI Express Switches (x3130-upstream, xio3130-downstream)
+Only use PCI Express Switches (pcie-upstream-port, pcie-downstream-port)
 if there is no more room for PCI Express Root Ports.
 Please see section 4. for further justifications.
 
@@ -120,8 +120,8 @@ Plug only PCI Express devices into PCI Express Ports.
       -device pcie-root-port,id=root_port3,chassis=x2,addr=z.2[,slot=y2][,bus=pcie.0] \
 2.2.3 Plugging a PCI Express device into a Switch:
       -device pcie-root-port,id=root_port1,chassis=x,slot=y[,bus=pcie.0][,addr=z]  \
-      -device x3130-upstream,id=upstream_port1,bus=root_port1[,addr=x]          \
-      -device xio3130-downstream,id=downstream_port1,bus=upstream_port1,chassis=x1,slot=y1[,addr=z1]] \
+      -device pcie-upstream-port,id=upstream_port1,bus=root_port1[,addr=x]          \
+      -device pcie-downstream-port,id=downstream_port1,bus=upstream_port1,chassis=x1,slot=y1[,addr=z1]] \
       -device <dev>,bus=downstream_port1
 
 Notes:
docs/specs/pci-ids.rst +4 -0
index 261b0f359f..60cb0f7db6 100644
@@ -100,6 +100,10 @@ PCI devices (other than virtio):
   PCI UFS device (``-device ufs``)
 1b36:0014
   PCI RISC-V IOMMU device
+1b36:0015
+  PCIe Upstream Switch Port (``-device pcie-upstream-port``)
+1b36:0016
+  PCIe Downstream Switch Port (``-device pcie-downstream-port``)
 
 All these devices are documented in :doc:`index`.
 
include/hw/pci/pci.h +2 -0
index f2448e941a..6520886855 100644
@@ -121,6 +121,8 @@ extern bool pci_available;
 #define PCI_DEVICE_ID_REDHAT_ACPI_ERST   0x0012
 #define PCI_DEVICE_ID_REDHAT_UFS         0x0013
 #define PCI_DEVICE_ID_REDHAT_RISCV_IOMMU 0x0014
+#define PCI_DEVICE_ID_REDHAT_PCIE_USP    0x0015
+#define PCI_DEVICE_ID_REDHAT_PCIE_DSP    0x0016
 #define PCI_DEVICE_ID_REDHAT_QXL         0x0100
 
 #define FMT_PCIBUS                      PRIx64
hw/pci-bridge/gen_pcie_downstream_port.c new file +202 -0

202 lines not on this page · raw message

hw/pci-bridge/gen_pcie_upstream_port.c new file +177 -0

177 lines not on this page · raw message

hw/pci-bridge/meson.build +2 -1
index 073aeacf7f..aa9959df04 100644
@@ -2,7 +2,8 @@ pci_ss = ss.source_set()
 pci_ss.add(when: 'CONFIG_PCI_BRIDGE', if_true: files('pci_bridge_dev.c'))
 pci_ss.add(when: 'CONFIG_I82801B11', if_true: files('i82801b11.c'))
 pci_ss.add(when: 'CONFIG_IOH3420', if_true: files('ioh3420.c'))
-pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('pcie_root_port.c', 'gen_pcie_root_port.c'))
+pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('pcie_root_port.c', 'gen_pcie_root_port.c',
+                                                    'gen_pcie_upstream_port.c', 'gen_pcie_downstream_port.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PCI_BRIDGE', if_true: files('pcie_pci_bridge.c'))
 pci_ss.add(when: 'CONFIG_PXB', if_true: files('pci_expander_bridge.c'))
 stub_ss.add(files('pci_expander_bridge_stubs.c'))
tests/functional/x86_64/meson.build +1 -0
index bcdb1ee558..893214e8bb 100644
@@ -44,6 +44,7 @@ tests_x86_64_system_thorough = [
   'multiprocess',
   'netdev_ethtool',
   'nvme_migration',
+  'pcie_switch',
   'replay',
   'reverse_debug',
   'tuxrun',
tests/functional/x86_64/test_pcie_switch.py new file +98 -0

98 lines not on this page · raw message

Signature · 1 line
MST
#24 lore raw

[PULL 58/78] vhost: move skip-drain feature check to vhost-user

In reply to Michael S. Tsirkin

From: Alexandr Moshkov <[email protected]>

Currently, do_vhost_virtqueue_stop() calls vhost_user_has_protocol_feature(), causing a link failure with --disable-vhost-user when other vhost backends are enabled.

Move the assertion to vhost_user_get_vring_base_skip_drain(), keeping the protocol feature check in the vhost-user backend and removing the dependency from the common vhost code.

Fixes 5eb2347867ee ("vhost-user: add skip_drain param to do_vhost_virtqueue_stop")

Message-ID: <[email protected]>

---
 hw/virtio/vhost-user.c | 4 +---
 hw/virtio/vhost.c      | 5 -----
 2 files changed, 1 insertion(+), 8 deletions(-)
hw/virtio/vhost-user.c +1 -3
index 3402accc6f..402d9603c0 100644
@@ -1463,9 +1463,7 @@ static int vhost_user_get_vring_base_skip_drain(struct vhost_dev *dev,
     bool skip_drain_supported = vhost_user_has_protocol_feature(dev,
                                VHOST_USER_PROTOCOL_F_GET_VRING_BASE_SKIP_DRAIN);
 
-    if (!skip_drain_supported) {
-        return 0;
-    }
+    assert(skip_drain_supported);
 
     return get_vring_base(dev, ring, true);
 }
hw/virtio/vhost.c +0 -5
index cbf6e82afb..b25919c5c7 100644
@@ -23,7 +23,6 @@
 #include "qemu/log.h"
 #include "standard-headers/linux/vhost_types.h"
 #include "hw/virtio/virtio-bus.h"
-#include "hw/virtio/vhost-user.h"
 #include "hw/mem/memory-device.h"
 #include "migration/blocker.h"
 #include "migration/misc.h"
@@ -1554,10 +1553,6 @@ static int do_vhost_virtqueue_stop(struct vhost_dev *dev,
                                    unsigned idx, bool force,
                                    bool skip_drain)
 {
-    if (skip_drain) {
-        assert(vhost_user_has_protocol_feature(dev,
-               VHOST_USER_PROTOCOL_F_GET_VRING_BASE_SKIP_DRAIN));
-    }
     int vhost_vq_index = dev->vhost_ops->vhost_get_vq_index(dev, idx);
     struct vhost_vring_state state = {
         .index = vhost_vq_index,
Signature · 1 line
MST
#25 lore raw

[PULL 60/78] vhost-user: add dedicated mailing list

In reply to Michael S. Tsirkin

From: Stefano Garzarella <[email protected]>

During the "Virtio and Vhost-User Ecosystem for Production and Automotive Systems" BoF at LPC 2026 [1] we discussed and agreed on creating a new vhost-user mailing list, mainly for changes to the specification and discussions around the protocol.

The main reason is that the vhost-user specification is also used by other VMMs and implemented by several back-ends outside the QEMU repository, whose developers often do not follow qemu-devel.

Add a paragraph in the specification about the new mailing list and a dedicated MAINTAINERS entry for the vhost-user protocol, covering the specification, the QEMU front-end and libvhost-user (back-end library).

[1] https://lpc.events/event/20/contributions/2365/

Message-ID: <[email protected]>

---
 MAINTAINERS                 | 12 ++++++++++--
 docs/interop/vhost-user.rst |  6 ++++++
 2 files changed, 16 insertions(+), 2 deletions(-)
MAINTAINERS +10 -2
index d7ec12ac39..04842adba6 100644
@@ -2544,19 +2544,27 @@ M: Michael S. Tsirkin <[email protected]>
 R: Stefano Garzarella <[email protected]>
 S: Supported
 F: hw/*/*vhost*
-F: docs/interop/vhost-user*
 F: docs/system/devices/virtio/vhost-user*
 F: contrib/vhost-user-*/
 F: backends/*vhost*
 F: include/system/vhost-user-backend.h
 F: include/hw/virtio/vhost*
 F: include/*/vhost*
-F: subprojects/libvhost-user/
 F: block/export/vhost-user*
 F: util/vhost-user-server.c
 F: net/vhost*
 F: tests/functional/x86_64/test_vhost_user_bridge.py
 
+vhost-user protocol
+M: Michael S. Tsirkin <[email protected]>
+R: Stefano Garzarella <[email protected]>
+L: [email protected]
+S: Supported
+F: docs/interop/vhost-user*
+F: hw/virtio/vhost-user.c
+F: include/hw/virtio/vhost-user.h
+F: subprojects/libvhost-user/
+
 vhost-shadow-virtqueue
 R: Eugenio Pérez <[email protected]>
 F: hw/virtio/vhost-shadow-virtqueue.*
docs/interop/vhost-user.rst +6 -0
index 488214a306..c31a75def3 100644
@@ -38,6 +38,12 @@ conventions <backend_conventions>`.
 The *front-end* and *back-end* can be either a client (i.e. connecting) or
 server (listening) in the socket communication.
 
+The protocol is developed and discussed on the `vhost-user mailing list
+<mailto:[email protected]>`_, archived at
+https://lore.kernel.org/vhost-user/. Until October 2026, when this list
+was created, discussions took place on the qemu-devel mailing list,
+archived at https://lore.kernel.org/qemu-devel/.
+
 Support for platforms other than Linux
 --------------------------------------
 
Signature · 1 line
MST
#26 lore raw

[PULL 33/78] qapi/monitor: Handle missing vhost-user protocol features

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

Commit f9a8f5812607 moved protocol_features to the vhost-user backend. As a result, VhostStatus.protocol_features is only populated for vhost-user devices.

For an active kernel vhost device, HMP info virtio-status and QMP x-query-virtio-status crashes now because of unset protocol-features.

Make protocol-features optional in VhostStatus and skip dumping a missing object in HMP.

This is a compatibility break. Okay, since VhostStatus is only used by x-query-virtio-status, which is unstable.

Message-ID: <[email protected]>

---
 qapi/virtio.json            | 5 +++--
 hw/virtio/virtio-hmp-cmds.c | 6 ++++--
 2 files changed, 7 insertions(+), 4 deletions(-)
qapi/virtio.json +3 -2
index 0a4753cc34..f6876a5de1 100644
@@ -82,7 +82,8 @@
 #
 # @acked-features: vhost_dev acked_features
 #
-# @protocol-features: vhost_dev protocol_features
+# @protocol-features: vhost-user protocol features.  Present only for
+#     vhost-user devices.
 #
 # @max-queues: vhost_dev max_queues
 #
@@ -101,7 +102,7 @@
             'vq-index': 'int',
             'features': 'VirtioDeviceFeatures',
             'acked-features': 'VirtioDeviceFeatures',
-            'protocol-features': 'VhostDeviceProtocols',
+            '*protocol-features': 'VhostDeviceProtocols',
             'max-queues': 'uint64',
             'backend-cap': 'uint64',
             'log-enabled': 'bool',
hw/virtio/virtio-hmp-cmds.c +4 -2
index e5da6f0069..5b08a06ec4 100644
@@ -176,8 +176,10 @@ void hmp_virtio_status(MonitorHMP *hmp, const QDict *qdict)
         hmp_virtio_dump_features(hmp, s->vhost_dev->features);
         monitor_hmp_printf(hmp, "    Acked features:\n");
         hmp_virtio_dump_features(hmp, s->vhost_dev->acked_features);
-        monitor_hmp_printf(hmp, "    Protocol features:\n");
-        hmp_virtio_dump_protocols(hmp, s->vhost_dev->protocol_features);
+        if (s->vhost_dev->protocol_features) {
+            monitor_hmp_printf(hmp, "    Protocol features:\n");
+            hmp_virtio_dump_protocols(hmp, s->vhost_dev->protocol_features);
+        }
     }
 
     qapi_free_VirtioStatus(s);
Signature · 1 line
MST
#27 lore raw

[PULL 65/78] i386/kvm: Use x86_iommu_device_get() for per-device IOMMU lookup

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

Currently, kvm_arch_fixup_msi_route() unconditionally uses the default system IOMMU for interrupt remapping when IOMMU is present. This approach fails when an IOMMU is attached to a non-system bus and serves only a subset of devices, because the kernel does not program IRT (Interrupt Remapping Table) entries for devices outside the IOMMU's scope.

This patch changes the behavior to perform per-device IOMMU lookup:
- For PCI devices: Use pci_device_get_x86_iommu() to find the specific IOMMU serving that device, if any
- For non-PCI devices (IOAPIC): Fall back to the default IOMMU only if interrupt remapping is enabled (ioapic_as != address_space_memory)

This ensures MSI route translation occurs only for devices actually managed by an IOMMU, enabling proper interrupt remapping in configurations where IOMMUs are attached to arbitrary buses.

NOTE: This patch assumes that only non-PCI device calling is IOAPIC and checks ioapic_as to determine interrupt remapping.

Message-ID: <[email protected]>

---
 hw/intc/ioapic.c      |  3 ++-
 target/i386/kvm/kvm.c | 15 ++++++++++++---
 2 files changed, 14 insertions(+), 4 deletions(-)
hw/intc/ioapic.c +2 -1
index 1932b14642..e171cf8432 100644
@@ -438,8 +438,9 @@ static void ioapic_machine_done_notify(Notifier *notifier, void *data)
 #ifdef ACCEL_GSI_IRQFD_POSSIBLE
     IOAPICCommonState *s = container_of(notifier, IOAPICCommonState,
                                         machine_done);
+    X86MachineState *x86ms = X86_MACHINE(qdev_get_machine());
 
-    if (accel_irqchip_is_split()) {
+    if (accel_irqchip_is_split() && x86ms->ioapic_as != &address_space_memory) {
         X86IOMMUState *iommu = x86_iommu_get_default();
         if (iommu) {
             /* Register this IOAPIC with IOMMU IEC notifier, so that
target/i386/kvm/kvm.c +12 -3
index da922f72d7..072da8b9f1 100644
@@ -6746,7 +6746,15 @@ uint64_t kvm_swizzle_msi_ext_dest_id(uint64_t address)
 int kvm_arch_fixup_msi_route(struct kvm_irq_routing_entry *route,
                              uint64_t address, uint32_t data, PCIDevice *dev)
 {
-    X86IOMMUState *iommu = x86_iommu_get_default();
+    X86IOMMUState *iommu = NULL;
+    MachineState *ms = MACHINE(qdev_get_machine());
+    X86MachineState *x86ms = X86_MACHINE(ms);
+
+    if (dev) {
+        iommu = x86_iommu_device_get(dev);
+    } else if (x86ms->ioapic_as != &address_space_memory) {
+        iommu = x86_iommu_get_default();
+    }
 
     if (iommu) {
         X86IOMMUClass *class = X86_IOMMU_DEVICE_GET_CLASS(iommu);
@@ -6848,6 +6856,7 @@ int kvm_arch_add_msi_route_post(struct kvm_irq_routing_entry *route,
 {
     static bool notify_list_inited = false;
     MSIRouteEntry *entry;
+    X86IOMMUState *iommu = NULL;
 
     if (!dev) {
         /* These are (possibly) IOAPIC routes only used for split
@@ -6867,13 +6876,13 @@ int kvm_arch_add_msi_route_post(struct kvm_irq_routing_entry *route,
     if (!notify_list_inited) {
         /* For the first time we do add route, add ourselves into
          * IOMMU's IEC notify list if needed. */
-        X86IOMMUState *iommu = x86_iommu_get_default();
+        iommu = x86_iommu_device_get(dev);
         if (iommu) {
             x86_iommu_iec_register_notifier(iommu,
                                             kvm_update_msi_routes_all,
                                             NULL);
+            notify_list_inited = true;
         }
-        notify_list_inited = true;
     }
     return 0;
 }
Signature · 1 line
MST
#28 lore raw

[PULL 25/78] migration: add MIG_EVENT_INCOMING_FAILED

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

A device may take over a resource from the source in post_load(), e.g. vhost-vsock acquires the device ownership on a CPR target. If the incoming migration fails afterwards, there's currently no way for the device to give that resource back before the failure is published, i.e. before the source is told about it over the return path and resumes, or the management sees the MIGRATION event on the target and resumes the source, or the target exits.

Add a migration event for that, sent to the migration notifiers from the failure path of process_incoming_migration_co() before the failure is published.

Message-ID: <[email protected]>

---
 include/migration/misc.h |  5 +++++
 migration/migration.c    | 10 ++++++++++
 2 files changed, 15 insertions(+)
include/migration/misc.h +5 -0
index 019717f4cd..86cc7eefd6 100644
@@ -65,12 +65,17 @@ bool migration_thread_is_self(void);
  *    - MIG_EVENT_SETUP [-> MIG_EVENT_POSTCOPY_START] -> MIG_EVENT_DONE
  *    - MIG_EVENT_SETUP [-> MIG_EVENT_POSTCOPY_START] -> MIG_EVENT_FAILED
  *    - MIG_EVENT_FAILED
+ *
+ * MIG_EVENT_INCOMING_FAILED is currently the only event sent on the
+ * incoming side, when the incoming migration fails and before the failure
+ * is published.
  */
 typedef enum MigrationEventType {
     MIG_EVENT_SETUP,
     MIG_EVENT_POSTCOPY_START,
     MIG_EVENT_DONE,
     MIG_EVENT_FAILED,
+    MIG_EVENT_INCOMING_FAILED,
     MIG_EVENT_MAX
 } MigrationEventType;
 
migration/migration.c +10 -0
index fab7949fe1..82a8a25228 100644
@@ -815,6 +815,16 @@ process_incoming_migration_co(void *opaque)
     goto out;
 
 fail:
+    /*
+     * Allow the devices which have registered their callbacks for
+     * migration target failure to undo the changes they may have
+     * done in post_load().
+     *
+     * Do that before the failure is published in any way: the MIGRATION
+     * event emitted by migrate_set_state() below, the return path (if
+     * present) in migration_incoming_state_destroy(), or the exit().
+     */
+    migration_call_notifiers(MIG_EVENT_INCOMING_FAILED, NULL);
     migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
                       MIGRATION_STATUS_FAILED);
     migrate_error_propagate(s, local_err);
Signature · 1 line
MST
#29 lore raw

[PULL 18/78] vhost-vsock: fix FD leak in realize()

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

In vhost_vsock_device_realize(), after unsuccessful call to qemu_set_blocking(vhostfd, false), vhostfd gets leaked. Let's close it explicitly in this case. CPR-saved FDs don't get automatically closed, thus it is safe both for vhostfd obtained from cpr_find_fd() and from open(/dev/vhost-vsock).

Fixes 384c2561bddf ("vhost-vsock: set vhostfd to non-blocking mode")
Fixes 701544cfaf46 ("hw: replace qemu_set_nonblock()")

Message-ID: <[email protected]>

---
 hw/virtio/vhost-vsock.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)
hw/virtio/vhost-vsock.c +4 -7
index c582ac9fcc..3ec20d8760 100644
@@ -176,20 +176,17 @@ static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
             error_prepend(errp, "vhost-vsock: unable to parse vhostfd: ");
             goto err_blocker;
         }
-
-        if (!qemu_set_blocking(vhostfd, false, errp)) {
-            goto err_blocker;
-        }
     } else {
         vhostfd = open("/dev/vhost-vsock", O_RDWR);
         if (vhostfd < 0) {
             error_setg_file_open(errp, errno, "/dev/vhost-vsock");
             goto err_blocker;
         }
+    }
 
-        if (!qemu_set_blocking(vhostfd, false, errp)) {
-            goto err_blocker;
-        }
+    if (!qemu_set_blocking(vhostfd, false, errp)) {
+        close(vhostfd);
+        goto err_blocker;
     }
 
     vhost_vsock_common_realize(vdev);
Signature · 1 line
MST
#30 lore raw

[PULL 44/78] hw/pci: Ensure PCIe link speed/width is consistent between both ends

In reply to Michael S. Tsirkin

From: David Vrabel <[email protected]>

If an emulated PCIe endpoint has higher link speed or width capabilities than the upstream port it is connected to, ensure that the link status on both ends is consistent by using the link status computed for the upstream port.

If the PCIe endpoint is a passthrough device, the link status continues to match the real status and consistent values requires the emulated upstream port to be at least as capable as the endpoint.

Message-ID: <[email protected]>

---
 hw/pci/pcie.c | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)
hw/pci/pcie.c +22 -4
index 42bcb9206d..bd00305ecc 100644
@@ -169,18 +169,36 @@ void pcie_cap_fill_link_ep_usp(PCIDevice *dev, PCIExpLinkWidth width,
                                PCIExpLinkSpeed speed, bool flitmode)
 {
     uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
+    PCIDevice *upstream;
 
     /*
-     * For an end point or USP need to set the current status as well
-     * as the capabilities.
+     * For an endpoint or USP, set Link Status as well as capabilities.
+     * Provisionally use this device's width/speed, then adopt the upstream
+     * port's values so both ends of an emulated link agree.  The upstream
+     * LNKSTA is already clamped to the negotiated link capabilities via
+     * pcie_sync_bridge_lnk().
      */
-    pci_long_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
+    pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
                                  PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW);
-    pci_long_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA,
+    pci_word_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA,
                                QEMU_PCI_EXP_LNKSTA_NLW(width) |
                                QEMU_PCI_EXP_LNKSTA_CLS(speed));
 
     pcie_cap_fill_lnk(exp_cap, width, speed, flitmode);
+
+    upstream = pci_bridge_get_device(pci_get_bus(dev));
+    if (upstream && pci_is_express_downstream_port(upstream)) {
+        uint8_t *up_exp = upstream->config + upstream->exp.exp_cap;
+        uint16_t lnksta;
+
+        pcie_sync_bridge_lnk(upstream);
+        lnksta = pci_get_word(up_exp + PCI_EXP_LNKSTA);
+        pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
+                                     PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW);
+        pci_word_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA,
+                                   lnksta & (PCI_EXP_LNKSTA_CLS |
+                                             PCI_EXP_LNKSTA_NLW));
+    }
 }
 
 static void pcie_cap_fill_slot_lnk(PCIDevice *dev)
Signature · 1 line
MST
#31 lore raw

[PULL 57/78] vhost-user: add missing stub for vhost_user_has_protocol_feature()

In reply to Michael S. Tsirkin

From: Alexandr Moshkov <[email protected]>

Currently, building with --disable-vhost-user fails to link when other vhost backends are enabled: vhost.c calls vhost_user_has_protocol_feature(), but its implementation is not built.

Add a stub returning false to fix the link error.

Fixes 5eb2347867ee ("vhost-user: add skip_drain param to do_vhost_virtqueue_stop")

Message-ID: <[email protected]>

---
 hw/virtio/vhost-user-stub.c | 5 +++++
 1 file changed, 5 insertions(+)
hw/virtio/vhost-user-stub.c +5 -0
index 45559788dd..9d2e521788 100644
@@ -4,3 +4,8 @@
 void vhost_user_qmp_status(struct vhost_dev *dev, VirtioStatus *status)
 {
 }
+
+bool vhost_user_has_protocol_feature(struct vhost_dev *dev, uint64_t feature)
+{
+    return false;
+}
Signature · 1 line
MST
#32 lore raw

[PULL 67/78] acpi-build: Create PCI topology using amd iommu root bus

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

Current AMD IOMMU ACPI build adds all the devices and buses under the IVHD. In order to support the IOMMU attached on a non system bus and serving only child devices of that bus, start exploring devices from the child bus. Also, use child list provided by the PCIBus, instead of iterating over all the objects in the qemu to find pci bridges.

Message-ID: <[email protected]>

---
 hw/i386/acpi-build.c | 152 ++++++++++++++++++++++---------------------
 1 file changed, 79 insertions(+), 73 deletions(-)
hw/i386/acpi-build.c +79 -73
index 6d7308b4e4..9855154349 100644
@@ -1763,83 +1763,89 @@ insert_ivhd(PCIBus *bus, PCIDevice *dev, void *opaque)
     /* "Select" IVHD entry, type 0x2 */
     entry = PCI_BUILD_BDF(pci_bus_num(bus), dev->devfn) << 8 | 0x2;
     build_append_int_noprefix(table_data, entry, 4);
-
-    if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_BRIDGE)) {
-        PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(dev));
-        uint8_t sec = pci_bus_num(sec_bus);
-        uint8_t sub = dev->config[PCI_SUBORDINATE_BUS];
-
-        if (pci_bus_is_express(sec_bus)) {
-            /*
-             * Walk the bus if there are subordinates, otherwise use a range
-             * to cover an entire leaf bus.  We could potentially also use a
-             * range for traversed buses, but we'd need to take care not to
-             * create both Select and Range entries covering the same device.
-             * This is easier and potentially more compact.
-             *
-             * An example bare metal system seems to use Select entries for
-             * root ports without a slot (ie. built-ins) and Range entries
-             * when there is a slot.  The same system also only hard-codes
-             * the alias range for an onboard PCIe-to-PCI bridge, apparently
-             * making no effort to support nested bridges.  We attempt to
-             * be more thorough here.
-             */
-            if (sec == sub) { /* leaf bus */
-                /* "Start of Range" IVHD entry, type 0x3 */
-                entry = PCI_BUILD_BDF(sec, PCI_DEVFN(0, 0)) << 8 | 0x3;
-                build_append_int_noprefix(table_data, entry, 4);
-                /* "End of Range" IVHD entry, type 0x4 */
-                entry = PCI_BUILD_BDF(sub, PCI_DEVFN(31, 7)) << 8 | 0x4;
-                build_append_int_noprefix(table_data, entry, 4);
-            } else {
-                pci_for_each_device(sec_bus, sec, insert_ivhd, table_data);
-            }
-        } else {
-            /*
-             * If the secondary bus is conventional, then we need to create an
-             * Alias range for everything downstream.  The range covers the
-             * first devfn on the secondary bus to the last devfn on the
-             * subordinate bus.  The alias target depends on legacy versus
-             * express bridges, just as in pci_device_iommu_address_space().
-             * DeviceIDa vs DeviceIDb as per the AMD IOMMU spec.
-             */
-            uint16_t dev_id_a, dev_id_b;
-
-            dev_id_a = PCI_BUILD_BDF(sec, PCI_DEVFN(0, 0));
-
-            if (pci_is_express(dev) &&
-                pcie_cap_get_type(dev) == PCI_EXP_TYPE_PCI_BRIDGE) {
-                dev_id_b = dev_id_a;
-            } else {
-                dev_id_b = PCI_BUILD_BDF(pci_bus_num(bus), dev->devfn);
-            }
-
-            /* "Alias Start of Range" IVHD entry, type 0x43, 8 bytes */
-            build_append_int_noprefix(table_data, dev_id_a << 8 | 0x43, 4);
-            build_append_int_noprefix(table_data, dev_id_b << 8 | 0x0, 4);
-
-            /* "End of Range" IVHD entry, type 0x4 */
-            entry = PCI_BUILD_BDF(sub, PCI_DEVFN(31, 7)) << 8 | 0x4;
-            build_append_int_noprefix(table_data, entry, 4);
-        }
-    }
 }
 
-/* For all PCI host bridges, walk and insert IVHD entries */
-static int
-ivrs_host_bridges(Object *obj, void *opaque)
+/*
+ * Insert IVHD entry for device and recurse, insert alias, or insert range as
+ * necessary for the PCI topology.
+ */
+static void add_ivhd_entries_from_bus(PCIBus *bus, GArray *table_data)
 {
-    GArray *ivhd_blob = opaque;
+    uint8_t sub;
+    uint32_t entry;
+    PCIBus *child_bus;
+    PCIDevice *parent_dev;
 
-    if (object_dynamic_cast(obj, TYPE_PCI_HOST_BRIDGE)) {
-        PCIBus *bus = PCI_HOST_BRIDGE(obj)->bus;
-
-        if (bus && !pci_bus_bypass_iommu(bus)) {
-            pci_for_each_device_under_bus(bus, insert_ivhd, ivhd_blob);
-        }
+    if (!bus->parent_dev) {
+        goto select_devices;
     }
 
-    return 0;
+    parent_dev = bus->parent_dev;
+    sub = parent_dev->config[PCI_SUBORDINATE_BUS];
+
+    if (pci_bus_is_express(bus)) {
+        /*
+         * Walk the bus if there are subordinates, otherwise use a range
+         * to cover an entire leaf bus.  We could potentially also use a
+         * range for traversed buses, but we'd need to take care not to
+         * create both Select and Range entries covering the same device.
+         * This is easier and potentially more compact.
+         *
+         * An example bare metal system seems to use Select entries for
+         * root ports without a slot (ie. built-ins) and Range entries
+         * when there is a slot.  The same system also only hard-codes
+         * the alias range for an onboard PCIe-to-PCI bridge, apparently
+         * making no effort to support nested bridges.  We attempt to
+         * be more thorough here.
+         */
+        if (sub != pci_bus_num(bus)) {
+            goto select_devices;
+        }
+        /* "Start of Range" IVHD entry, type 0x3 */
+        entry = PCI_BUILD_BDF(sub, PCI_DEVFN(0, 0)) << 8 | 0x3;
+        build_append_int_noprefix(table_data, entry, 4);
+        /* "End of Range" IVHD entry, type 0x4 */
+        entry = PCI_BUILD_BDF(sub, PCI_DEVFN(31, 7)) << 8 | 0x4;
+        build_append_int_noprefix(table_data, entry, 4);
+
+        return;
+    } else {
+        /*
+         * If the secondary bus is conventional, then we need to create an
+         * Alias range for everything downstream.  The range covers the
+         * first devfn on the secondary bus to the last devfn on the
+         * subordinate bus.  The alias target depends on legacy versus
+         * express bridges, just as in pci_device_iommu_address_space().
+         * DeviceIDa vs DeviceIDb as per the AMD IOMMU spec.
+         */
+        uint16_t dev_id_a, dev_id_b;
+
+        dev_id_a = PCI_BUILD_BDF(pci_bus_num(bus), PCI_DEVFN(0, 0));
+
+        if (pci_is_express(parent_dev) &&
+            pcie_cap_get_type(parent_dev) == PCI_EXP_TYPE_PCI_BRIDGE) {
+            dev_id_b = dev_id_a;
+        } else {
+            dev_id_b = PCI_BUILD_BDF(pci_bus_num(pci_get_bus(parent_dev)),
+                                     parent_dev->devfn);
+        }
+
+        /* "Alias Start of Range" IVHD entry, type 0x43, 8 bytes */
+        build_append_int_noprefix(table_data, dev_id_a << 8 | 0x43, 4);
+        build_append_int_noprefix(table_data, dev_id_b << 8 | 0x0, 4);
+
+        /* "End of Range" IVHD entry, type 0x4 */
+        entry = PCI_BUILD_BDF(sub, PCI_DEVFN(31, 7)) << 8 | 0x4;
+        build_append_int_noprefix(table_data, entry, 4);
+        return;
+    }
+
+select_devices:
+    pci_for_each_device_under_bus(bus, insert_ivhd, table_data);
+
+    QLIST_FOREACH(child_bus, &bus->child, sibling) {
+        add_ivhd_entries_from_bus(child_bus, table_data);
+    }
 }
 
 /*
@@ -1904,8 +1910,8 @@ build_amd_iommu(GArray *table_data, BIOSLinker *linker, const char *oem_id,
      * blob further below.  Fall back to an entry covering all devices, which
      * is sufficient when no aliases are present.
      */
-    object_child_foreach_recursive(object_get_root(),
-                                   ivrs_host_bridges, ivhd_blob);
+    assert(s->root_bus);
+    add_ivhd_entries_from_bus(s->root_bus, ivhd_blob);
 
     if (!ivhd_blob->len) {
         /*
Signature · 1 line
MST
#33 lore raw

[PULL 27/78] vhost: release ownership on exit

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

A CPR target that has taken the device ownership in post_load() doesn't always reach vhost_dev_cleanup(): a target which has loaded the state but hasn't been started yet, e.g. one started with -S, may be quit normally, with the source being resumed instead. Devices aren't unrealized on quit, so the ownership stays with the dead process. The source still holds the FD, so the kernel never releases the device, and the source can't take it back after being resumed -> SET_OWNER fails with EBUSY.

Add an exit notifier to each vhost_dev, registered for its lifetime, that releases the ownership if still held. This covers every orderly exit. A target killed with SIGKILL or crashing still leaves the device unrecoverable.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost.h |  2 ++
 hw/virtio/vhost.c         | 20 ++++++++++++++++++++
 2 files changed, 22 insertions(+)
include/hw/virtio/vhost.h +2 -0
index 3b9ef86bac..eb693e3289 100644
@@ -123,6 +123,8 @@ struct vhost_dev {
      * when this vhost_dev goes away.  Set by the device.
      */
     bool owner_handed_over;
+    /* @exit_notifier: releases the ownership at exit, vhost_exit_notify() */
+    Notifier exit_notifier;
     /*
      * @migration_notifier: releases the ownership when an incoming
      * migration fails, vhost_migration_notify()
hw/virtio/vhost.c +20 -0
index 7fd2883504..cbf6e82afb 100644
@@ -31,6 +31,7 @@
 #include "system/dma.h"
 #include "system/memory.h"
 #include "system/ramblock.h"
+#include "system/system.h"
 #include "trace.h"
 
 /* enabled until disconnected backend stabilizes */
@@ -90,6 +91,19 @@ static int vhost_migration_notify(NotifierWithReturn *notifier,
     return 0;
 }
 
+/*
+ * Release the ownership of the vhost devices still held at exit(), so that
+ * another process sharing the backend FDs (e.g. the CPR source) can take
+ * them over.
+ */
+static void vhost_exit_notify(Notifier *notifier, void *data)
+{
+    struct vhost_dev *hdev = container_of(notifier, struct vhost_dev,
+                                          exit_notifier);
+
+    vhost_dev_try_release_owner(hdev, "exit");
+}
+
 unsigned int vhost_get_max_memslots(void)
 {
     unsigned int max = UINT_MAX;
@@ -1860,6 +1874,8 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
     hdev->log_enabled = false;
     hdev->started = false;
     memory_listener_register(&hdev->memory_listener, &address_space_memory);
+    hdev->exit_notifier.notify = vhost_exit_notify;
+    qemu_add_exit_notifier(&hdev->exit_notifier);
     migration_add_notifier_modes(&hdev->migration_notifier,
                                  vhost_migration_notify,
                                  BIT(MIG_MODE_CPR_TRANSFER) |
@@ -1913,6 +1929,10 @@ void vhost_dev_cleanup(struct vhost_dev *hdev)
         memory_listener_unregister(&hdev->memory_listener);
         QLIST_REMOVE(hdev, entry);
     }
+    if (hdev->exit_notifier.notify) {
+        qemu_remove_exit_notifier(&hdev->exit_notifier);
+        hdev->exit_notifier.notify = NULL;
+    }
     migration_remove_notifier(&hdev->migration_notifier);
     migrate_del_blocker(&hdev->migration_blocker);
     g_free(hdev->mem);
Signature · 1 line
MST
#34 lore raw

[PULL 26/78] vhost: release ownership when the incoming migration fails

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

A CPR target that has taken the device ownership in post_load() doesn't always reach vhost_dev_cleanup(): a subsequent load error, e.g. a truncated migration stream footer, makes QEMU exit() right away, and the ownership stays with the dead process. The source still holds the FD, so the kernel never releases the device, and the source can't take it back after being resumed -> SET_OWNER fails with EBUSY.

Releasing the ownership at exit isn't enough either: with the return-path capability the target reports the failure to the source before exiting, and the source resumes right away, racing with the target's exit for the ownership. And with exit-on-error=false the target doesn't exit at all.

Add a migration notifier to each vhost_dev, registered for its lifetime for the CPR modes, that releases the ownership if still held on MIG_EVENT_INCOMING_FAILED. It runs before the failure is published in any way.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost.h |  6 ++++++
 hw/virtio/vhost.c         | 23 +++++++++++++++++++++++
 2 files changed, 29 insertions(+)
include/hw/virtio/vhost.h +6 -0
index efe1eed260..3b9ef86bac 100644
@@ -3,6 +3,7 @@
 
 #include "net/vhost_net.h"
 #include "hw/virtio/vhost-backend.h"
+#include "qemu/notify.h"
 #include "hw/virtio/virtio.h"
 #include "system/memory.h"
 
@@ -122,6 +123,11 @@ struct vhost_dev {
      * when this vhost_dev goes away.  Set by the device.
      */
     bool owner_handed_over;
+    /*
+     * @migration_notifier: releases the ownership when an incoming
+     * migration fails, vhost_migration_notify()
+     */
+    NotifierWithReturn migration_notifier;
     bool log_enabled;
     uint64_t log_size;
     Error *migration_blocker;
hw/virtio/vhost.c +23 -0
index 085e2ef6f1..7fd2883504 100644
@@ -26,6 +26,7 @@
 #include "hw/virtio/vhost-user.h"
 #include "hw/mem/memory-device.h"
 #include "migration/blocker.h"
+#include "migration/misc.h"
 #include "migration/qemu-file-types.h"
 #include "system/dma.h"
 #include "system/memory.h"
@@ -72,6 +73,23 @@ static void vhost_dev_try_release_owner(struct vhost_dev *hdev,
     }
 }
 
+/*
+ * A CPR target takes the device ownership in post_load().  If the load
+ * fails afterwards, release it before the source learns about the failure
+ * and resumes, so that the source can take the device back.
+ */
+static int vhost_migration_notify(NotifierWithReturn *notifier,
+                                  MigrationEvent *e, Error **errp)
+{
+    struct vhost_dev *hdev = container_of(notifier, struct vhost_dev,
+                                          migration_notifier);
+
+    if (e->type == MIG_EVENT_INCOMING_FAILED) {
+        vhost_dev_try_release_owner(hdev, "incoming migration failure");
+    }
+    return 0;
+}
+
 unsigned int vhost_get_max_memslots(void)
 {
     unsigned int max = UINT_MAX;
@@ -1842,6 +1860,10 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
     hdev->log_enabled = false;
     hdev->started = false;
     memory_listener_register(&hdev->memory_listener, &address_space_memory);
+    migration_add_notifier_modes(&hdev->migration_notifier,
+                                 vhost_migration_notify,
+                                 BIT(MIG_MODE_CPR_TRANSFER) |
+                                 BIT(MIG_MODE_CPR_EXEC));
     QLIST_INSERT_HEAD(&vhost_devices, hdev, entry);
 
     /*
@@ -1891,6 +1913,7 @@ void vhost_dev_cleanup(struct vhost_dev *hdev)
         memory_listener_unregister(&hdev->memory_listener);
         QLIST_REMOVE(hdev, entry);
     }
+    migration_remove_notifier(&hdev->migration_notifier);
     migrate_del_blocker(&hdev->migration_blocker);
     g_free(hdev->mem);
     g_free(hdev->mem_sections);
Signature · 1 line
MST
#35 lore raw

[PULL 20/78] vhost: factor out vhost_dev_init_backend()

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

Split the first part of vhost_dev_init(): selecting the backend, calling its .vhost_init() and reading the supported features - into a new vhost_dev_init_backend() helper, and call it from vhost_dev_init().

This is in preparation for CPR restore of vhost-vsock, which needs to learn the backend's features at realize time to negotiate them when loading the incoming virtio state, but also must defer taking ownership of the device to post_load. vhost_dev_init_backend() does exactly the pre-ownership part.

As a result VHOST_SET_OWNER now follows the feature query rather than precedes it. This should be safe, as no backend requires ownership before VHOST_GET_FEATURES - the kernel and vdpa backends do not check ownership for it, and vhost-user already does query features from its .vhost_init() before set_owner().

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost.h | 22 ++++++++++++++++++++++
 hw/virtio/vhost.c         | 33 +++++++++++++++++++++++----------
 2 files changed, 45 insertions(+), 10 deletions(-)
include/hw/virtio/vhost.h +22 -0
index 0a322efec1..b2b644c0d1 100644
@@ -156,6 +156,28 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
                    VhostBackendType backend_type,
                    uint32_t busyloop_timeout, Error **errp);
 
+/**
+ * vhost_dev_init_backend() - set up the backend and query its features
+ * @hdev: the common vhost_dev structure
+ * @opaque: opaque ptr passed to backend (vhost/vhost-user/vdpa)
+ * @backend_type: type of backend
+ * @errp: error handle
+ *
+ * Select the backend, initialise the backend instance and read its supported
+ * features into @hdev, without issuing VHOST_SET_OWNER, setting up the
+ * virtqueues or registering the memory listener.  This is the part of
+ * vhost_dev_init() that precedes taking ownership; it can be used on its own
+ * so feature negotiation can happen before ownership is acquired (e.g. by CPR
+ * restore).
+ *
+ * On failure the backend may be left partially initialised; the caller must
+ * still call vhost_dev_cleanup() to release it.
+ *
+ * Return: 0 on success, non-zero on error while setting errp.
+ */
+int vhost_dev_init_backend(struct vhost_dev *hdev, void *opaque,
+                           VhostBackendType backend_type, Error **errp);
+
 /**
  * vhost_dev_cleanup() - tear down and cleanup vhost interface
  * @hdev: the common vhost_dev structure
hw/virtio/vhost.c +23 -10
index 68a8e40481..99561ac586 100644
@@ -1681,6 +1681,28 @@ static int vhost_dev_init_features(struct vhost_dev *hdev)
     return r;
 }
 
+int vhost_dev_init_backend(struct vhost_dev *hdev, void *opaque,
+                           VhostBackendType backend_type, Error **errp)
+{
+    int r;
+
+    r = vhost_set_backend_type(hdev, backend_type);
+    assert(r >= 0);
+
+    r = hdev->vhost_ops->vhost_init(hdev, opaque, errp);
+    if (r < 0) {
+        return r;
+    }
+
+    r = vhost_dev_init_features(hdev);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "vhost_init_features failed");
+        return r;
+    }
+
+    return 0;
+}
+
 int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
                    VhostBackendType backend_type, uint32_t busyloop_timeout,
                    Error **errp)
@@ -1693,10 +1715,7 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
     hdev->vdev = NULL;
     hdev->migration_blocker = NULL;
 
-    r = vhost_set_backend_type(hdev, backend_type);
-    assert(r >= 0);
-
-    r = hdev->vhost_ops->vhost_init(hdev, opaque, errp);
+    r = vhost_dev_init_backend(hdev, opaque, backend_type, errp);
     if (r < 0) {
         goto fail;
     }
@@ -1707,12 +1726,6 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
         goto fail;
     }
 
-    r = vhost_dev_init_features(hdev);
-    if (r < 0) {
-        error_setg_errno(errp, -r, "vhost_init_features failed");
-        goto fail;
-    }
-
     limit = hdev->vhost_ops->vhost_memslots_limit(hdev);
     if (limit < MEMORY_DEVICES_SAFE_MAX_MEMSLOTS &&
         memory_devices_memslot_auto_decision_active()) {
Signature · 1 line
MST
#36 lore raw

[PULL 42/78] hw/pci-bridge: Make speed and width for PCIe ports non-experimental

In reply to Michael S. Tsirkin

From: David Vrabel <[email protected]>

For guests with passthrough PCIe devices, in order to make the guest PCIe topology have the same apparent bandwidth as the underlying hardware we need to be able to set the capabilities (speed and width) of the virtual root, upstream, and downstream ports.

Promote the existing experimental x-speed and x-width options to non-experimental speed and width.

Message-ID: <[email protected]>

---
 hw/pci-bridge/gen_pcie_downstream_port.c | 4 ++--
 hw/pci-bridge/gen_pcie_root_port.c       | 4 ++--
 hw/pci-bridge/gen_pcie_upstream_port.c   | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)
hw/pci-bridge/gen_pcie_downstream_port.c +2 -2
index d9c182e881..d00bbd55f0 100644
@@ -159,9 +159,9 @@ static const VMStateDescription vmstate_gen_pcie_downstream_port = {
 static const Property gen_pcie_downstream_port_props[] = {
     DEFINE_PROP_BIT(COMPAT_PROP_PCP, PCIDevice, cap_present,
                     QEMU_PCIE_SLTCAP_PCP_BITNR, true),
-    DEFINE_PROP_PCIE_LINK_SPEED("x-speed", PCIESlot,
+    DEFINE_PROP_PCIE_LINK_SPEED("speed", PCIESlot,
                                 speed, PCIE_LINK_SPEED_64),
-    DEFINE_PROP_PCIE_LINK_WIDTH("x-width", PCIESlot,
+    DEFINE_PROP_PCIE_LINK_WIDTH("width", PCIESlot,
                                 width, PCIE_LINK_WIDTH_32),
 };
 
hw/pci-bridge/gen_pcie_root_port.c +2 -2
index 5434d693d9..d3ace7f0db 100644
@@ -129,9 +129,9 @@ static const Property gen_rp_props[] = {
                      res_reserve.mem_pref_32, -1),
     DEFINE_PROP_SIZE("pref64-reserve", GenPCIERootPort,
                      res_reserve.mem_pref_64, -1),
-    DEFINE_PROP_PCIE_LINK_SPEED("x-speed", PCIESlot,
+    DEFINE_PROP_PCIE_LINK_SPEED("speed", PCIESlot,
                                 speed, PCIE_LINK_SPEED_16),
-    DEFINE_PROP_PCIE_LINK_WIDTH("x-width", PCIESlot,
+    DEFINE_PROP_PCIE_LINK_WIDTH("width", PCIESlot,
                                 width, PCIE_LINK_WIDTH_32),
 };
 
hw/pci-bridge/gen_pcie_upstream_port.c +2 -2
index e86eff099f..61ae3a294a 100644
@@ -134,9 +134,9 @@ static const VMStateDescription vmstate_gen_pcie_upstream_port = {
 };
 
 static const Property gen_pcie_upstream_port_props[] = {
-    DEFINE_PROP_PCIE_LINK_SPEED("x-speed", GenPCIEUpstreamPort,
+    DEFINE_PROP_PCIE_LINK_SPEED("speed", GenPCIEUpstreamPort,
                                 speed, PCIE_LINK_SPEED_64),
-    DEFINE_PROP_PCIE_LINK_WIDTH("x-width", GenPCIEUpstreamPort,
+    DEFINE_PROP_PCIE_LINK_WIDTH("width", GenPCIEUpstreamPort,
                                 width, PCIE_LINK_WIDTH_32),
 };
 
Signature · 1 line
MST
#37 lore raw

[PULL 59/78] virtio-snd: don't use virtio_snd_pcm_get_stream() in virtio_snd_post_load()

In reply to Michael S. Tsirkin

From: Alexander Mikhalitsyn <[email protected]>

Coverity suspects nullptr-deref, because we don't check a return value of virtio_snd_pcm_get_stream() call in virtio_snd_post_load().

While, techically this code has no issue it's better to change it and as Peter has pointed out [1] just follow the same pattern as in other places of virtio-snd code:

    for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
        VirtIOSoundPCMStream *stream = &vsnd->streams[i];
        ...
    }

this way it's perfectly clear that an index is always within the bounds.

Resolves: Coverity CID 1687010

Message-ID: <[email protected]>

---
 hw/audio/virtio-snd.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
hw/audio/virtio-snd.c +1 -2
index 6df7ea0eb0..fb22d79949 100644
@@ -1448,9 +1448,8 @@ static int virtio_snd_post_load(VirtIODevice *vdev)
     uint32_t i;
 
     for (i = 0; i < s->snd_conf.streams; i++) {
-        struct VirtIOSoundPCMStream *stream;
+        VirtIOSoundPCMStream *stream = &s->streams[i];
 
-        stream = virtio_snd_pcm_get_stream(s, i);
         if (virtio_snd_pcm_state_prepared(stream->state)) {
             virtio_snd_pcm_open(stream);
 
Signature · 1 line
MST
#38 lore raw

[PULL 15/78] vhost-vsock: block CPR migration modes

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

CPR migration with a vhost-vsock device currently crashes the target QEMU. The device ownership is not handed over, so while the source QEMU still holds the guest CID hashed in the kernel, the target's freshly opened vhost-vsock device fails to set the same CID in realize():

qemu-system-x86_64: -device vhost-vsock-pci,id=vsock0,guest-cid=3:
vhost-vsock: unable to set guest cid: Address already in use

Add an unconditional migration blocker for the CPR modes, so that such a migration fails early and gracefully instead. The following patches implement the actual vhost-vsock CPR support, and the blocker is lifted once it's fully working.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost-vsock.h |  1 +
 hw/virtio/vhost-vsock.c         | 27 +++++++++++++++++++++++----
 2 files changed, 24 insertions(+), 4 deletions(-)
include/hw/virtio/vhost-vsock.h +1 -0
index 84f4e727c7..a964d57e1b 100644
@@ -29,6 +29,7 @@ struct VHostVSock {
     /*< private >*/
     VHostVSockCommon parent;
     VHostVSockConf conf;
+    Error *migration_blocker;   /* CPR migration is not supported */
 
     /*< public >*/
 };
hw/virtio/vhost-vsock.c +23 -4
index da244eb165..503d248a61 100644
@@ -20,6 +20,8 @@
 #include "hw/core/qdev-properties.h"
 #include "hw/virtio/vhost-vsock.h"
 #include "monitor/monitor.h"
+#include "migration/blocker.h"
+#include "migration/misc.h"
 
 static void vhost_vsock_get_config(VirtIODevice *vdev, uint8_t *config)
 {
@@ -140,25 +142,38 @@ static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    /*
+     * CPR migration of a vhost-vsock device is not supported yet: the
+     * device ownership is not handed over, so the target fails to set
+     * up its device.  Fail the migration early and gracefully instead.
+     */
+    error_setg(&vsock->migration_blocker,
+               "vhost-vsock: CPR migration is not supported");
+    if (migrate_add_blocker_modes(&vsock->migration_blocker,
+                                  BIT(MIG_MODE_CPR_TRANSFER) |
+                                  BIT(MIG_MODE_CPR_EXEC), errp) < 0) {
+        return;
+    }
+
     if (vsock->conf.vhostfd) {
         vhostfd = monitor_fd_param(monitor_cur(), vsock->conf.vhostfd, errp);
         if (vhostfd == -1) {
             error_prepend(errp, "vhost-vsock: unable to parse vhostfd: ");
-            return;
+            goto err_blocker;
         }
 
         if (!qemu_set_blocking(vhostfd, false, errp)) {
-            return;
+            goto err_blocker;
         }
     } else {
         vhostfd = open("/dev/vhost-vsock", O_RDWR);
         if (vhostfd < 0) {
             error_setg_file_open(errp, errno, "/dev/vhost-vsock");
-            return;
+            goto err_blocker;
         }
 
         if (!qemu_set_blocking(vhostfd, false, errp)) {
-            return;
+            goto err_blocker;
         }
     }
 
@@ -187,16 +202,20 @@ err_vhost_dev:
     vhost_dev_cleanup(&vvc->vhost_dev);
 err_virtio:
     vhost_vsock_common_unrealize(vdev);
+err_blocker:
+    migrate_del_blocker(&vsock->migration_blocker);
 }
 
 static void vhost_vsock_device_unrealize(DeviceState *dev)
 {
     VHostVSockCommon *vvc = VHOST_VSOCK_COMMON(dev);
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VHostVSock *vsock = VHOST_VSOCK(dev);
 
     /* This will stop vhost backend if appropriate. */
     vhost_vsock_set_status(vdev, 0);
 
+    migrate_del_blocker(&vsock->migration_blocker);
     vhost_dev_cleanup(&vvc->vhost_dev);
     vhost_vsock_common_unrealize(vdev);
 }
Signature · 1 line
MST
#39 lore raw

[PULL 72/78] intel_iommu_accel: Add PRQ injection for passthrough device

In reply to Michael S. Tsirkin

From: Zhenzhong Duan <[email protected]>

When the guest enables the PRQ in vIOMMU, allocate a FAULTQ object so that host-side recoverable fault events can be received and propagated back to the guest.

Install an event handler on the FAULTQ fd to read and propagate host generated recoverable fault events to the guest.

The handler runs in QEMU's main loop, using a non-blocking fd registered via qemu_set_fd_handler().

Message-ID: <[email protected]>

---
 hw/i386/intel_iommu_accel.h    |   2 +
 hw/i386/intel_iommu_internal.h |   3 +
 hw/i386/intel_iommu.c          |  14 ++-
 hw/i386/intel_iommu_accel.c    | 168 +++++++++++++++++++++++++++++++--
 hw/i386/trace-events           |   1 +
 5 files changed, 177 insertions(+), 11 deletions(-)
hw/i386/intel_iommu_accel.h +2 -0
index 46c1a29409..e0319749a5 100644
@@ -18,6 +18,8 @@ typedef struct VTDAccelPASIDCacheEntry {
     VTDPASIDEntry pasid_entry;
     uint32_t pasid;
     uint32_t fs_hwpt_id;
+    uint32_t fault_id;
+    int fault_fd;
     QLIST_ENTRY(VTDAccelPASIDCacheEntry) next;
 } VTDAccelPASIDCacheEntry;
 
hw/i386/intel_iommu_internal.h +3 -0
index df7a0efa6e..4792035668 100644
@@ -787,4 +787,7 @@ int vtd_dev_to_context_entry(IntelIOMMUState *s, uint8_t bus_num,
 VTDAddressSpace *vtd_get_as_by_sid(IntelIOMMUState *s, uint16_t sid);
 int vtd_dev_get_pe_from_pasid(IntelIOMMUState *s, PCIBus *bus, uint8_t devfn,
                               uint32_t pasid, VTDPASIDEntry *pe);
+int vtd_pri_request_page(PCIBus *bus, void *opaque, int devfn, uint32_t pasid,
+                         bool priv_req, bool exec_req, hwaddr addr, bool lpig,
+                         uint16_t prgi, bool is_read, bool is_write);
 #endif
hw/i386/intel_iommu.c +9 -5
index 3686ca8ba7..adb8c79d57 100644
@@ -5383,11 +5383,15 @@ static int vtd_pri_perform_implicit_invalidation(VTDAddressSpace *vtd_as,
     return ret;
 }
 
-/* Page Request Descriptor : 7.4.1.1 */
-static int vtd_pri_request_page(PCIBus *bus, void *opaque, int devfn,
-                                uint32_t pasid, bool priv_req, bool exec_req,
-                                hwaddr addr, bool lpig, uint16_t prgi,
-                                bool is_read, bool is_write)
+/*
+ * Page Request Descriptor : 7.4.1.1
+ *
+ * Because it is facing the emulated device models, so this should be PCI
+ * PASID instead of the pasids used within vIOMMU.
+ */
+int vtd_pri_request_page(PCIBus *bus, void *opaque, int devfn, uint32_t pasid,
+                         bool priv_req, bool exec_req, hwaddr addr, bool lpig,
+                         uint16_t prgi, bool is_read, bool is_write)
 {
     IntelIOMMUState *s = opaque;
     VTDAddressSpace *vtd_as;
hw/i386/intel_iommu_accel.c +162 -6

226 lines not on this page · raw message

hw/i386/trace-events +1 -0
index a1a50d0910..ad84d106a4 100644
@@ -77,6 +77,7 @@ vtd_reset_exit(void) ""
 vtd_device_attach_hwpt(uint32_t dev_id, uint32_t pasid, uint32_t hwpt_id, int ret) "dev_id %d pasid %d hwpt_id %d, ret: %d"
 vtd_device_detach_hwpt(uint32_t dev_id, uint32_t pasid, int ret) "dev_id %d pasid %d ret: %d"
 vtd_device_reattach_def_hwpt(uint32_t dev_id, uint32_t pasid, uint32_t hwpt_id, int ret) "dev_id %d pasid %d hwpt_id %d, ret: %d"
+vtd_read_fs_faultq(uint32_t fault_id, uint32_t fault_fd, ssize_t ret) "fault_id %d fault_fd %d ret: %zd"
 
 # amd_iommu.c
 amdvi_evntlog_fail(uint64_t addr, uint32_t head) "error: fail to write at addr 0x%"PRIx64" +  offset 0x%"PRIx32
Signature · 1 line
MST
#40 lore raw

[PULL 69/78] intel_iommu_accel: Guard VTDAccelPASIDCacheEntry definition

In reply to Michael S. Tsirkin

From: Zhenzhong Duan <[email protected]>

Move the VTDAccelPASIDCacheEntry typedef structure inside the CONFIG_VTD_ACCEL ifdef block.

When CONFIG_VTD_ACCEL is disabled, this structure is entirely unused throughout the code. Moving the layout under the existing preprocessor guard prevents exposing dead type definitions and ensures the header remains clean when building without VTD acceleration support.

Message-ID: <[email protected]>

---
 hw/i386/intel_iommu_accel.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/i386/intel_iommu_accel.h +1 -1
index 4a9003c92d..46c1a29409 100644
@@ -12,6 +12,7 @@
 #define HW_I386_INTEL_IOMMU_ACCEL_H
 #include CONFIG_DEVICES
 
+#ifdef CONFIG_VTD_ACCEL
 typedef struct VTDAccelPASIDCacheEntry {
     VTDHostIOMMUDevice *vtd_hiod;
     VTDPASIDEntry pasid_entry;
@@ -20,7 +21,6 @@ typedef struct VTDAccelPASIDCacheEntry {
     QLIST_ENTRY(VTDAccelPASIDCacheEntry) next;
 } VTDAccelPASIDCacheEntry;
 
-#ifdef CONFIG_VTD_ACCEL
 bool vtd_check_hiod_accel(IntelIOMMUState *s, VTDHostIOMMUDevice *vtd_hiod,
                           Error **errp);
 VTDHostIOMMUDevice *vtd_find_hiod_iommufd(VTDAddressSpace *as);
Signature · 1 line
MST
#41 lore raw

[PULL 07/78] net/tap: move vmstate initialization to new_tap()

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

Otherwise, incoming_fds case in net_init_tap() is not covered, and the mechanism will not work for outgoing local migration, if VM start life from incoming local migration.

Fixes 98a0c2127070 ("net/tap: disable read polling for stopped VM")

Message-ID: <[email protected]>

---
 net/tap.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
net/tap.c +3 -3
index 8039b4e388..9f829f49c1 100644
@@ -623,6 +623,9 @@ static TAPState *new_tap(NetClientState *peer,
 
     s->queue_index = queue_index;
 
+    s->enable_poll_on_resume = false;
+    s->vmstate = qemu_add_vm_change_state_handler(tap_vm_state_change, s);
+
     if (has_permit_local_migration) {
         s->permit_local_migration = permit_local_migration;
     }
@@ -968,9 +971,6 @@ static bool net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
     int sndbuf =
         (tap->has_sndbuf && tap->sndbuf) ? MIN(tap->sndbuf, INT_MAX) : INT_MAX;
 
-    s->enable_poll_on_resume = false;
-    s->vmstate = qemu_add_vm_change_state_handler(tap_vm_state_change, s);
-
     if (!tap_set_sndbuf(fd, sndbuf, sndbuf_required ? errp : NULL) &&
         sndbuf_required) {
         goto failed;
Signature · 1 line
MST
#42 lore raw

[PULL 55/78] hw/arm: remove pci-bridge/pci_expander_bridge.h from virt.c

In reply to Michael S. Tsirkin

From: Daniel P. Berrangé <[email protected]>

This include is redundant as the function is declares is not referenced.

Message-ID: <[email protected]>

---
 hw/arm/virt.c | 1 -
 1 file changed, 1 deletion(-)
hw/arm/virt.c +0 -1
index 0d56197dac..3ab3ad8c3b 100644
@@ -58,7 +58,6 @@
 #include "qemu/module.h"
 #include "hw/pci/pci_bus.h"
 #include "hw/pci-host/gpex.h"
-#include "hw/pci-bridge/pci_expander_bridge.h"
 #include "hw/virtio/virtio-pci.h"
 #include "hw/core/sysbus-fdt.h"
 #include "hw/core/platform-bus.h"
Signature · 1 line
MST
#43 lore raw

[PULL 52/78] tests/functional/x86_64/test_vhost_user_bridge: Eliminate stdbuf usage

In reply to Michael S. Tsirkin

From: Yodel Eldar <[email protected]>

Usage of stdbuf triggers a runtime error when vhost-user-bridge is built with ASAN. Now that vhost-user-bridge writes debug output to stderr, stdbuf is obviated; remove its invocation.

Message ID of relevant thread:
<[email protected]>

Message-ID: <[email protected]>

---
 tests/functional/x86_64/test_vhost_user_bridge.py | 6 ------
 1 file changed, 6 deletions(-)
tests/functional/x86_64/test_vhost_user_bridge.py +0 -6
index c36c625420..5e1db48884 100755
@@ -76,12 +76,6 @@ def configure_vm(self, ud_socket_path, lport, rport, hostfwd_port, tftpdir):
     def assemble_vubr_args(self, vubr_path, ud_socket_path, lport, rport):
         vubr_args = []
 
-        if (stdbuf_path := which("stdbuf")) is None:
-            self.log.info("Could not find stdbuf: vhost-user-bridge "
-                          "log lines may appear out of order")
-        else:
-            vubr_args += [stdbuf_path, "-o0", "-e0"]
-
         vubr_args += [vubr_path, "-u", f"{ud_socket_path}",
                       "-l", f"127.0.0.1:{lport}", "-r", f"127.0.0.1:{rport}"]
 
Signature · 1 line
MST
#44 lore raw

[PULL 64/78] intel_iommu: Add support for get_x86_iommu callback

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

Return X86IOMMUState during get_x86_iommu callback.

Message-ID: <[email protected]>

---
 hw/i386/intel_iommu.c | 8 ++++++++
 1 file changed, 8 insertions(+)
hw/i386/intel_iommu.c +8 -0
index 350d2b7753..3686ca8ba7 100644
@@ -5485,6 +5485,13 @@ out:
     return ret;
 }
 
+static int vtd_get_x86_iommu(void *opaque, void **x86_iommu)
+{
+    IntelIOMMUState *s = opaque;
+    *x86_iommu = X86_IOMMU_DEVICE(s);
+    return 0;
+}
+
 static void vtd_init_iotlb_notifier(PCIBus *bus, void *opaque, int devfn,
                                     IOMMUNotifier *n, IOMMUNotify fn,
                                     void *user_opaque)
@@ -5559,6 +5566,7 @@ static PCIIOMMUOps vtd_iommu_ops = {
     .pri_unregister_notifier = vtd_pri_unregister_notifier,
     .pri_request_page = vtd_pri_request_page,
     .get_viommu_flags = vtd_get_viommu_flags,
+    .get_x86_iommu = vtd_get_x86_iommu,
 };
 
 static bool vtd_decide_config(IntelIOMMUState *s, Error **errp)
Signature · 1 line
MST
#45 lore raw

[PULL 50/78] virtio-qmp: Fix out-of-bounds vqs index in vhost queue status

In reply to Michael S. Tsirkin

From: Li RongQing <[email protected]>

qmp_x_query_virtio_vhost_queue_status() validates the queue number against the vhost device's global range (queue < hdev->vq_index + hdev->nvqs), confirming that 'queue' is a global index. But it then indexes hdev->vqs[] directly with that global index.

hdev->vqs is allocated as g_new0(struct vhost_virtqueue, hdev->nvqs), so valid local indices are 0..nvqs-1. When hdev->vq_index > 0 (multiqueue vhost-net queue pairs other than the first), every access hdev->vqs[queue] reads past the end of the array.

Use queue - hdev->vq_index to compute the local index, consistent with the rest of vhost.c (e.g. vhost_virtqueue_pending/mask).

Fixes 07536ddda73a ("qmp: add QMP commands for virtio/vhost queue-status")

Message-ID: <[email protected]>

---
 hw/virtio/virtio-qmp.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)
hw/virtio/virtio-qmp.c +10 -9
index 8b7cd7a310..8d190c4c70 100644
@@ -789,6 +789,7 @@ VirtVhostQueueStatus *qmp_x_query_virtio_vhost_queue_status(const char *path,
 
     VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
     struct vhost_dev *hdev = vdc->get_vhost(vdev);
+    int vq = queue - hdev->vq_index;
 
     if (queue < hdev->vq_index || queue >= hdev->vq_index + hdev->nvqs) {
         error_setg(errp, "Invalid vhost virtqueue number %d", queue);
@@ -797,15 +798,15 @@ VirtVhostQueueStatus *qmp_x_query_virtio_vhost_queue_status(const char *path,
 
     status = g_new0(VirtVhostQueueStatus, 1);
     status->name = g_strdup(vdev->name);
-    status->kick = hdev->vqs[queue].kick;
-    status->call = hdev->vqs[queue].call;
-    status->num = hdev->vqs[queue].num;
-    status->desc_phys = hdev->vqs[queue].desc_phys;
-    status->desc_size = hdev->vqs[queue].desc_size;
-    status->avail_phys = hdev->vqs[queue].avail_phys;
-    status->avail_size = hdev->vqs[queue].avail_size;
-    status->used_phys = hdev->vqs[queue].used_phys;
-    status->used_size = hdev->vqs[queue].used_size;
+    status->kick = hdev->vqs[vq].kick;
+    status->call = hdev->vqs[vq].call;
+    status->num = hdev->vqs[vq].num;
+    status->desc_phys = hdev->vqs[vq].desc_phys;
+    status->desc_size = hdev->vqs[vq].desc_size;
+    status->avail_phys = hdev->vqs[vq].avail_phys;
+    status->avail_size = hdev->vqs[vq].avail_size;
+    status->used_phys = hdev->vqs[vq].used_phys;
+    status->used_size = hdev->vqs[vq].used_size;
 
     return status;
 }
Signature · 1 line
MST
#46 lore raw

[PULL 05/78] net/tap: tap_post_load(): poll only on success

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We shouldn't call tap_read_poll() if s->fd < 0. Let's move tap_read_poll() to the end, after check and vhost initialization.

Fixes c4d884bb5444 ("net/tap: support local migration with virtio-net")

Message-ID: <[email protected]>

---
 net/tap.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
net/tap.c +1 -2
index d71e3dd117..8039b4e388 100644
@@ -490,8 +490,6 @@ static bool tap_post_load(void *opaque, int version_id, Error **errp)
     ERRP_GUARD();
     TAPState *s = opaque;
 
-    tap_read_poll(s, true);
-
     if (s->fd < 0) {
         error_setg(errp, "FD was not loaded during incoming migration");
         return false;
@@ -503,6 +501,7 @@ static bool tap_post_load(void *opaque, int version_id, Error **errp)
         return false;
     }
 
+    tap_read_poll(s, true);
     return true;
 }
 
Signature · 1 line
MST
#47 lore raw

[PULL 47/78] vhost: fix off-by-one in vhost_svq_next_desc with IN_ORDER

In reply to Michael S. Tsirkin

From: Li RongQing <[email protected]>

vhost_svq_next_desc checks id against vring.num before incrementing, so when id == vring.num - 1 the function returns vring.num instead of
0. This causes vhost_svq_vring_write_descs to write descs[num-1].next = num (an invalid descriptor index) and, on the next iteration, to access descs[num] — which lies past the descriptor ring and into the avail ring, corrupting it.

The bug triggers whenever an IN_ORDER descriptor chain wraps around the end of the SVQ ring.

Fix by incrementing id first, then checking for wrap-around.

Fixes 485c9e69ef6e ("vhost: add in_order feature to shadow virtqueue")

Message-ID: <[email protected]>

---
 hw/virtio/vhost-shadow-virtqueue.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/virtio/vhost-shadow-virtqueue.c +1 -1
index c8831d52be..0fae35ba1a 100644
@@ -161,7 +161,7 @@ static uint16_t vhost_svq_next_desc(const VhostShadowVirtqueue *svq,
                                     uint16_t id)
 {
     if (virtio_vdev_has_feature(svq->vdev, VIRTIO_F_IN_ORDER)) {
-        return (id == svq->vring.num) ? 0 : ++id;
+        return (++id == svq->vring.num) ? 0 : id;
     } else {
         return svq->desc_state[id].next;
     }
Signature · 1 line
MST
#48 lore raw

[PULL 28/78] vhost-vsock: hand off device ownership across CPR

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

The previous patches reuse the source vhost FD on the destination, however both source and target still call vhost_dev_init() (VHOST_SET_OWNER) in realize(). For cpr-transfer the destination realizes while the source still owns the shared FD, so its SET_OWNER would fail - ownership has to be handed over explicitly.

Do this through the device's CPR vmstate hooks. Namely, release device ownership in pre_save, reclaim in post_load, re-acquire on restart:

- .pre_save() releases ownership on the source (VHOST_RESET_OWNER) once the VM is stopped, for the FD-preserving CPR modes (cpr-transfer and cpr-exec).

- .realize(), for an incoming CPR, only sets up the virtio device and queries the backend features by calling vhost_dev_init_backend(). It doesn't take device ownership and doesn't touch the VQs which still-running source might use. The full init is deferred to .post_load().

- .post_load() reclaims it on the destination: the full vhost_dev_init() (VHOST_SET_OWNER) on the preserved FD, plus sets the guest cid, before the device is started at vm_start. It also marks the ownership as handed over ('owner_handed_over'), so that it's released if this target goes away.

- .set_status() refuses to start a device whose init hasn't completed yet (incoming CPR before post_load).

- .set_status() re-acquires ownership before starting the device if pre_save released it and this QEMU is about to run the guest again:
the migration failed, or it completed and the management resumed the source (e.g. cpr-transfer target died).

With the handoff in place, lift the CPR blocker.

Note: this requires kernel side support of VHOST_RESET_OWNER operation for vhost-vsock. Otherwise CPR attempt fails in .pre_save().

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost-vsock.h |   1 -
 hw/virtio/vhost-vsock.c         | 157 ++++++++++++++++++++++++++------
 2 files changed, 127 insertions(+), 31 deletions(-)
include/hw/virtio/vhost-vsock.h +0 -1
index a964d57e1b..84f4e727c7 100644
@@ -29,7 +29,6 @@ struct VHostVSock {
     /*< private >*/
     VHostVSockCommon parent;
     VHostVSockConf conf;
-    Error *migration_blocker;   /* CPR migration is not supported */
 
     /*< public >*/
 };
hw/virtio/vhost-vsock.c +127 -30

251 lines not on this page · raw message

Signature · 1 line
MST
#49 lore raw

[PULL 62/78] x86-iommu: Introduce x86_iommu_device_get() for x86-iommu retrieval

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

kvm_arch_fixup_msi_route() and kvm_arch_add_msi_route_post() use x86_iommu_get_default() whenever an IOMMU is present. This selects the correct unit only when a single IOMMU is attached to the system bus.

The following patches allow an IOMMU to attach to a different bus and isolate the subset of devices below it. Each IOMMU then serves only part of the PCI topology, so interrupt remapping must determine the IOMMU that actually serves a given device.

Add x86_iommu_device_get() to locate the IOMMU bus for a PCI device and retrieve its X86IOMMUState through a new get_x86_iommu callback.

Message-ID: <[email protected]>

---
 include/hw/i386/x86-iommu.h |  6 ++++++
 include/hw/pci/pci.h        | 12 ++++++++++++
 hw/i386/x86-iommu-stub.c    |  5 +++++
 hw/i386/x86-iommu.c         | 16 ++++++++++++++++
 4 files changed, 39 insertions(+)
include/hw/i386/x86-iommu.h +6 -0
index 0a1e1ba725..eb575271e9 100644
@@ -134,6 +134,12 @@ struct X86IOMMU_MSIMessage {
  */
 X86IOMMUState *x86_iommu_get_default(void);
 
+/**
+ * x86_iommu_device_get - get IOMMU device for given PCI device
+ * @return: pointer to default IOMMU device
+ */
+X86IOMMUState *x86_iommu_device_get(PCIDevice *dev);
+
 /**
  * x86_iommu_iec_register_notifier - register IEC (Interrupt Entry
  *                                   Cache) notifiers
include/hw/pci/pci.h +12 -0
index 6520886855..359331102f 100644
@@ -700,6 +700,18 @@ typedef struct PCIIOMMUOps {
      * Returns: the guest physical address of the MSI doorbell.
      */
     uint64_t (*get_msi_direct_gpa)(PCIBus *bus, void *opaque, int devfn);
+
+    /**
+     * @get_x86_iommu: get parent x86-iommu for given hardware (AMD/Intel)
+     *
+     * Callback is required if IOMMU defines x86->int_remap()
+     *
+     * @opaque: the data passed to pci_setup_iommu()
+     *
+     * @x86_iommu: A pointer to store the x86-iommu object
+     */
+    int (*get_x86_iommu)(void *opaque, void **x86_iommu);
+
 } PCIIOMMUOps;
 
 bool pci_device_get_iommu_bus_devfn(PCIDevice *dev, PCIBus **piommu_bus,
hw/i386/x86-iommu-stub.c +5 -0
index 781b5ff922..b0383d2290 100644
@@ -32,6 +32,11 @@ X86IOMMUState *x86_iommu_get_default(void)
     return NULL;
 }
 
+X86IOMMUState *x86_iommu_device_get(PCIDevice *dev)
+{
+    return NULL;
+}
+
 bool x86_iommu_ir_supported(X86IOMMUState *s)
 {
     return false;
hw/i386/x86-iommu.c +16 -0
index a57445404e..601db5cfea 100644
@@ -26,6 +26,7 @@
 #include "qemu/error-report.h"
 #include "trace.h"
 #include "system/kvm.h"
+#include "hw/pci/pci_bus.h"
 
 void x86_iommu_iec_register_notifier(X86IOMMUState *iommu,
                                      iec_notify_fn fn, void *data)
@@ -90,6 +91,21 @@ X86IOMMUState *x86_iommu_get_default(void)
     return NULL;
 }
 
+X86IOMMUState *x86_iommu_device_get(PCIDevice *dev)
+{
+    PCIBus *iommu_bus;
+    X86IOMMUState *iommu = NULL;
+
+    pci_device_get_iommu_bus_devfn(dev, &iommu_bus, NULL, NULL);
+
+    if (iommu_bus && iommu_bus->iommu_ops->get_x86_iommu) {
+        iommu_bus->iommu_ops->get_x86_iommu(iommu_bus->iommu_opaque,
+                                            (void **)&iommu);
+    }
+
+    return iommu;
+}
+
 static void x86_iommu_realize(DeviceState *dev, Error **errp)
 {
     X86IOMMUState *x86_iommu = X86_IOMMU_DEVICE(dev);
Signature · 1 line
MST
#50 lore raw

[PULL 74/78] intel_iommu_accel: Teardown FAULTQ resources in bottom half

In reply to Michael S. Tsirkin

From: Zhenzhong Duan <[email protected]>

When a PASID entry becomes invalid, all resources allocated for that entry, including the FAULTQ object and fault_fd, must be released.

Calling qemu_set_fd_handler() detaches the fault_fd I/O read handler and wakes up the main loop thread from poll(). However, a race window exists where iommufd_backend_free_id(fault_id) can be invoked before poll() exits and releases its reference to the fault_fd file. In this scenario, freeing the FAULTQ object returns -EBUSY because the opened fault_fd file still holds an active reference to the FAULTQ object.

Defer FAULTQ resource teardown to a bottom half. This ensures poll() has fully exited and dropped the file reference before the fault_fd is closed and the FAULTQ object ID is freed. Additionally, keep the IOMMUFD backend FD open via iommufd_backend_connect() across the asynchronous bottom half execution to ensure FAULTQ object freed successfully.

Message-ID: <[email protected]>

---
 hw/i386/intel_iommu_accel.c | 59 +++++++++++++++++++++++++++++++++++--
 1 file changed, 56 insertions(+), 3 deletions(-)
hw/i386/intel_iommu_accel.c +56 -3
index 243501f137..7db8319ad4 100644
@@ -271,17 +271,70 @@ free_faultq:
     return false;
 }
 
+typedef struct IOMMUFaultQueue {
+    IOMMUFDBackend *iommufd;
+    uint32_t id;
+    int fd;
+    QLIST_HEAD(, VTDPRQEntry) vtd_prq_list;
+} IOMMUFaultQueue;
+
+static void faultq_teardown_bh(void *opaque)
+{
+    IOMMUFaultQueue *fq = opaque;
+    VTDPRQEntry *prqe, *next;
+
+    QLIST_FOREACH_SAFE(prqe, &fq->vtd_prq_list, next, next) {
+        QLIST_REMOVE(prqe, next);
+        g_free(prqe);
+    }
+
+    qemu_set_fd_handler(fq->fd, NULL, NULL, NULL);
+    close(fq->fd);
+    iommufd_backend_free_id(fq->iommufd, fq->id);
+    iommufd_backend_disconnect(fq->iommufd);
+
+    g_free(fq);
+}
+
 static void vtd_destroy_old_fs_faultq(VTDAccelPASIDCacheEntry *vtd_pce)
 {
+    HostIOMMUDeviceIOMMUFD *idev =
+        HOST_IOMMU_DEVICE_IOMMUFD(vtd_pce->vtd_hiod->hiod);
+    IOMMUFaultQueue *fq;
+
     if (vtd_pce->fault_fd < 0) {
         return;
     }
 
-    qemu_set_fd_handler(vtd_pce->fault_fd, NULL, NULL, NULL);
-    vtd_destroy_fs_faultq(vtd_pce->vtd_hiod, vtd_pce->fault_id,
-                          vtd_pce->fault_fd);
+    fq = g_malloc(sizeof(*fq));
+    fq->iommufd = idev->iommufd;
+    fq->fd = vtd_pce->fault_fd;
+    fq->id = vtd_pce->fault_id;
     vtd_pce->fault_id = 0;
     vtd_pce->fault_fd = -1;
+    fq->vtd_prq_list.lh_first = vtd_pce->vtd_prq_list.lh_first;
+    QLIST_INIT(&vtd_pce->vtd_prq_list);
+
+    /*
+     * During a VFIO device hot-unplug, if this is the last device connected
+     * to the IOMMUFDBackend, the backend user count (iommufd->users) will
+     * drop to zero, triggering iommufd_backend_disconnect() which closes
+     * the backend's opened container FD.
+     *
+     * Since the teardown of this fault queue (faultq_teardown_bh) is
+     * scheduled asynchronously as a bottom half, we must temporarily
+     * increment the connection user count of the iommufd backend here.
+     * This guarantees the backend connection remains open and its opened
+     * FD remains active until the async BH completes.
+     *
+     * Incrementing the user count is guaranteed to succeed here because the
+     * VFIO device is still registered as an active user of the backend at
+     * this point in the teardown sequence.
+     */
+    iommufd_backend_connect(fq->iommufd, &error_abort);
+
+    aio_bh_schedule_oneshot(iohandler_get_aio_context(),
+                            faultq_teardown_bh, fq);
 }
 
 static void vtd_setup_fs_faultq(VTDAccelPASIDCacheEntry *vtd_pce,
Signature · 1 line
MST
#51 lore raw

[PULL 68/78] amd_iommu: Allow IOMMU to attachment on non-system (pcie.0) bus

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

Allow the AMD IOMMU to attach on the pcie-root-port using pci device's "bus" property. This helps in isolating the set of devices by creating a pcie hierarchy under the pci expansion bus (pxb-pci). The IOMMU always tries to find the first root bus by traversing up the PCI hierarchy and serves all the devices under that bus.

Example1, for below PCI configuration, IOMMU will serve all the devices under pxb-pci (i.e. Device1 and Device2) since its a first root bus, but will not serve Device3 and Device3, effectively isolating the devices under pxb-pci bus.

pcie.0 -+- pxb-pci -+- pcie-root-port1 --- AMD IOMMU

        |           +- pcie-root-port2 --- Device1
        |           \- pcie-root-port3 --- Device2
        +- Device3
        \- Device4

Example2, for below PCI configuration, IOMMU will serve all the devices Device[1-4], since first root bus in the parent hierarchy is pcie.0

pcie.0 -+- pcie-root-port1 --- AMD IOMMU

        +- pcie-root-port2 --- Device1
        +- pcie-root-port3 --- Device2
        +- Device3
        \- Device4

AMD IOMMU uses this root bus to register pci IOMMU ops and build the IVRS table.

Message-ID: <[email protected]>

---
 hw/i386/amd_iommu.c | 55 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 51 insertions(+), 4 deletions(-)
hw/i386/amd_iommu.c +51 -4
index 14ea8274b2..2d5c7b5cdf 100644
@@ -24,6 +24,7 @@
 #include "hw/i386/pc.h"
 #include "hw/pci/msi.h"
 #include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_bridge.h"
 #include "migration/vmstate.h"
 #include "amd_iommu.h"
 #include "qapi/error.h"
@@ -2680,6 +2681,50 @@ static const VMStateDescription vmstate_amdvi_sysbus_migratable = {
      }
 };
 
+/* Go up in the PCI hierarchy and find the root bus pcie.0 or pxb-pci */
+static PCIBus *get_root_bus(struct PCIBus *bus)
+{
+    while (bus) {
+
+        if (!pci_bus_is_express(bus)) {
+            error_report("attached to non PCI express bus %s",
+                         bus->qbus.name);
+            goto err_out;
+        }
+
+        if (bus->iommu_ops) {
+            error_report("different IOMMU already serves bus %s",
+                         bus->qbus.name);
+            goto err_out;
+        }
+
+        if (pci_bus_is_root(bus) &&
+            object_dynamic_cast(OBJECT(bus)->parent, TYPE_PCI_HOST_BRIDGE)) {
+
+            if (bus->parent_dev &&
+                !object_dynamic_cast(OBJECT(bus), TYPE_PXB_PCIE_BUS)) {
+
+                    error_report("only supports PXB-PCI as extra root bus");
+                    goto err_out;
+            }
+            /* We found the valid root bus so return it to the user*/
+            goto out;
+        }
+
+        if (bus->parent_dev) {
+            bus = pci_get_bus(bus->parent_dev);
+        } else {
+            error_report("could not find valid PCIE bus");
+            goto err_out;
+        }
+    };
+
+err_out:
+    exit(EXIT_FAILURE);
+out:
+    return bus;
+}
+
 static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
 {
     DeviceClass *dc = (DeviceClass *) object_get_class(OBJECT(dev));
@@ -2687,7 +2732,7 @@ static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
     MachineState *ms = MACHINE(qdev_get_machine());
     PCMachineState *pcms = PC_MACHINE(ms);
     X86MachineState *x86ms = X86_MACHINE(ms);
-    PCIBus *bus = pcms->pcibus;
+    PCIBus *iommu_bus;
 
     if (s->pci_id) {
         PCIDevice *pdev = NULL;
@@ -2703,17 +2748,19 @@ static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
             return;
         }
 
+        iommu_bus = pci_get_bus(pdev);
         s->pci = AMD_IOMMU_PCI(pdev);
         dc->vmsd = &vmstate_amdvi_sysbus_migratable;
     } else {
+        iommu_bus = pcms->pcibus;
         s->pci = AMD_IOMMU_PCI(object_new(TYPE_AMD_IOMMU_PCI));
         /* This device should take care of IOMMU PCI properties */
-        if (!qdev_realize(DEVICE(s->pci), &bus->qbus, errp)) {
+        if (!qdev_realize(DEVICE(s->pci), &iommu_bus->qbus, errp)) {
             return;
         }
     }
 
-    s->root_bus = bus;
+    s->root_bus = get_root_bus(iommu_bus);
 
     s->iotlb = g_hash_table_new_full(amdvi_iotlb_hash,
                                      amdvi_iotlb_equal, g_free, g_free);
@@ -2767,7 +2814,7 @@ static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
         }
     }
 
-    pci_setup_iommu(bus, &amdvi_iommu_ops, s);
+    pci_setup_iommu(s->root_bus, &amdvi_iommu_ops, s);
     amdvi_init(s);
 }
 
Signature · 1 line
MST
#52 lore raw

[PULL 78/78] virtio-net: add mtu-from-tap property for automatic MTU detection

In reply to Michael S. Tsirkin

From: Nariman Sayed <[email protected]>

Currently, exposing the host tap device's MTU to the guest via VIRTIO_NET_F_MTU requires manually specifying host_mtu= on the virtio-net-pci device, duplicating the MTU already configured on the host's tap interface.

Add a new mtu-from-tap boolean property (default off, preserving existing behavior). When enabled, the netdev peer's MTU is queried via the generic qemu_get_mtu() and used to set net_conf.mtu and enable VIRTIO_NET_F_MTU, regardless of the backend's type.

This detection must happen in virtio_net_device_realize(), before virtio_net_set_config_size() computes the virtio config space size from host_features. Performing the detection later (e.g. in the get_features_ex() callback, which runs after the NIC and its config space are already sized) results in the mtu config field falling outside the sized config space, and the guest reading back an unmapped garbage value (0xffff) instead of the intended MTU.

Since qemu_new_nic() has not yet been called at this point in realize(), the peer is accessed via n->nic_conf.peers.ncs[0] (populated during property parsing, before realize() runs, via the netdev= property) rather than through n->nic.

Message-ID: <[email protected]>

---
 include/hw/virtio/virtio-net.h |  1 +
 hw/net/virtio-net.c            | 10 ++++++++++
 2 files changed, 11 insertions(+)
include/hw/virtio/virtio-net.h +1 -0
index 8c967760c2..fed37245be 100644
@@ -52,6 +52,7 @@ typedef struct virtio_net_conf
     uint16_t rx_queue_size;
     uint16_t tx_queue_size;
     uint16_t mtu;
+    bool mtu_from_tap;
     int32_t speed;
     char *duplex_str;
     uint8_t duplex;
hw/net/virtio-net.c +10 -0
index 4794496b96..54e42cfec9 100644
@@ -4006,6 +4006,15 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp)
         n->host_features |= (1ULL << VIRTIO_NET_F_MTU);
     }
 
+    if (n->net_conf.mtu_from_tap) {
+        NetClientState *peer = n->nic_conf.peers.ncs[0];
+        int mtu = qemu_get_mtu(peer);
+        if (mtu > 0) {
+            n->net_conf.mtu = mtu;
+            n->host_features |= (1ULL << VIRTIO_NET_F_MTU);
+        }
+    }
+
     if (n->net_conf.duplex_str) {
         if (strncmp(n->net_conf.duplex_str, "half", 5) == 0) {
             n->net_conf.duplex = DUPLEX_HALF;
@@ -4386,6 +4395,7 @@ static const Property virtio_net_properties[] = {
     DEFINE_PROP_UINT16("tx_queue_size", VirtIONet, net_conf.tx_queue_size,
                        VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE),
     DEFINE_PROP_UINT16("host_mtu", VirtIONet, net_conf.mtu, 0),
+    DEFINE_PROP_BOOL("mtu-from-tap", VirtIONet, net_conf.mtu_from_tap, false),
     DEFINE_PROP_INT32("speed", VirtIONet, net_conf.speed, SPEED_UNKNOWN),
     DEFINE_PROP_STRING("duplex", VirtIONet, net_conf.duplex_str),
     DEFINE_PROP_BOOL("failover", VirtIONet, failover, false),
Signature · 1 line
MST
#53 lore raw

[PULL 53/78] pcie_aer: fix pci_set_long overflow into PCI_EXP_LNKCAP

In reply to Michael S. Tsirkin

From: Li RongQing <[email protected]>

pcie_aer_inject_uncor_error() wrote inj->devsta (a uint16_t) into PCI_EXP_DEVSTA using pci_set_long(), which writes 4 bytes. PCI_EXP_DEVSTA is a 16-bit register at offset 0x0a; the extra 2 bytes written by pci_set_long() land at offset 0x0c, the low half of PCI_EXP_LNKCAP, zeroing the link speed/width capability fields on every uncorrectable error injection.

Use pci_set_word() instead, matching the correctable error path in pcie_aer_inject_cor_error().

Fixes 34e65944c035 ("pcie/aer: helper functions for pcie aer capability")

Message-ID: <[email protected]>

---
 hw/pci/pcie_aer.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/pci/pcie_aer.c +1 -1
index 22497b1fd6..43b14c1f22 100644
@@ -589,7 +589,7 @@ static bool pcie_aer_inject_uncor_error(PCIEAERInject *inj, bool is_fatal)
     if (inj->unsupported_request) {
         inj->devsta |= PCI_EXP_DEVSTA_URD;
     }
-    pci_set_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVSTA, inj->devsta);
+    pci_set_word(dev->config + dev->exp.exp_cap + PCI_EXP_DEVSTA, inj->devsta);
 
     if (inj->aer_cap) {
         uint32_t mask = pci_get_long(inj->aer_cap + PCI_ERR_UNCOR_MASK);
Signature · 1 line
MST
#54 lore raw

[PULL 70/78] backends/iommufd: Introduce iommufd_backend_alloc_faultq

In reply to Michael S. Tsirkin

From: Zhenzhong Duan <[email protected]>

Add a new helper for IOMMU_FAULT_QUEUE_ALLOC ioctl to allocate a fault handling object which will be used in hwpt allocation.

Message-ID: <[email protected]>

---
 include/system/iommufd.h |  3 +++
 backends/iommufd.c       | 21 +++++++++++++++++++++
 backends/trace-events    |  1 +
 3 files changed, 25 insertions(+)
include/system/iommufd.h +3 -0
index 17bf1a3727..36b34b4899 100644
@@ -106,6 +106,9 @@ bool iommufd_backend_alloc_veventq(IOMMUFDBackend *be, uint32_t viommu_id,
                                    uint32_t *out_veventq_id,
                                    uint32_t *out_veventq_fd, Error **errp);
 
+bool iommufd_backend_alloc_faultq(IOMMUFDBackend *be, uint32_t *out_fault_id,
+                                  uint32_t *out_fault_fd, Error **errp);
+
 bool iommufd_backend_alloc_hw_queue(IOMMUFDBackend *be, uint32_t viommu_id,
                                     uint32_t queue_type, uint32_t index,
                                     uint64_t addr, uint64_t length,
backends/iommufd.c +21 -0
index 45fd2e44c7..457b74a558 100644
@@ -556,6 +556,27 @@ bool iommufd_backend_alloc_veventq(IOMMUFDBackend *be, uint32_t viommu_id,
     return true;
 }
 
+bool iommufd_backend_alloc_faultq(IOMMUFDBackend *be, uint32_t *out_fault_id,
+                                  uint32_t *out_fault_fd, Error **errp)
+{
+    int ret, fd = be->fd;
+    struct iommu_fault_alloc cmd = {
+        .size = sizeof(cmd),
+    };
+
+    ret = ioctl(fd, IOMMU_FAULT_QUEUE_ALLOC, &cmd);
+    trace_iommufd_backend_alloc_faultq(fd, cmd.out_fault_id, cmd.out_fault_fd,
+                                       ret);
+    if (ret) {
+        error_setg_errno(errp, errno, "Failed to allocate fault queue");
+        return false;
+    }
+
+    *out_fault_id = cmd.out_fault_id;
+    *out_fault_fd = cmd.out_fault_fd;
+    return true;
+}
+
 bool iommufd_backend_alloc_hw_queue(IOMMUFDBackend *be, uint32_t viommu_id,
                                     uint32_t queue_type, uint32_t index,
                                     uint64_t addr, uint64_t length,
backends/trace-events +1 -0
index 9b7b9b7d68..b1c1d89dd8 100644
@@ -17,6 +17,7 @@ iommufd_backend_unmap_dma_non_exist(int iommufd, uint32_t ioas, uint64_t iova, u
 iommufd_backend_unmap_dma(int iommufd, uint32_t ioas, uint64_t iova, uint64_t size, int ret) " iommufd=%d ioas=%d iova=0x%"PRIx64" size=0x%"PRIx64" (%d)"
 iommufd_backend_alloc_ioas(int iommufd, uint32_t ioas) " iommufd=%d ioas=%d"
 iommufd_backend_alloc_hwpt(int iommufd, uint32_t dev_id, uint32_t pt_id, uint32_t flags, uint32_t hwpt_type, uint32_t len, uint64_t data_ptr, uint32_t out_hwpt_id, int ret) " iommufd=%d dev_id=%u pt_id=%u flags=0x%x hwpt_type=%u len=%u data_ptr=0x%"PRIx64" out_hwpt=%u (%d)"
+iommufd_backend_alloc_faultq(int iommufd, uint32_t fault_id, uint32_t fault_fd, int ret) " iommufd=%d fault_id=%u fault_fd=%u (%d)"
 iommufd_backend_free_id(int iommufd, uint32_t id, int ret) " iommufd=%d id=%d (%d)"
 iommufd_backend_set_dirty(int iommufd, uint32_t hwpt_id, bool start, int ret) " iommufd=%d hwpt=%u enable=%d (%d)"
 iommufd_backend_get_dirty_bitmap(int iommufd, uint32_t hwpt_id, uint64_t iova, uint64_t size, uint64_t flags, uint64_t page_size, int ret) " iommufd=%d hwpt=%u iova=0x%"PRIx64" size=0x%"PRIx64" flags=0x%"PRIx64" page_size=0x%"PRIx64" (%d)"
Signature · 1 line
MST
#55 lore raw

[PULL 63/78] amd_iommu: Add support for get_x86_iommu callback

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

Return X86IOMMUState during get_x86_iommu callback.

Message-ID: <[email protected]>

---
 hw/i386/amd_iommu.c | 8 ++++++++
 1 file changed, 8 insertions(+)
hw/i386/amd_iommu.c +8 -0
index 817320c51c..b2df0e9533 100644
@@ -2472,8 +2472,16 @@ static AddressSpace *amdvi_host_dma_iommu(PCIBus *bus, void *opaque, int devfn)
     return &amdvi_dev_as->as;
 }
 
+static int amdvi_get_x86_iommu(void *opaque, void **x86_iommu)
+{
+    AMDVIState *s = opaque;
+    *x86_iommu = X86_IOMMU_DEVICE(s);
+    return 0;
+}
+
 static const PCIIOMMUOps amdvi_iommu_ops = {
     .get_address_space = amdvi_host_dma_iommu,
+    .get_x86_iommu = amdvi_get_x86_iommu,
 };
 
 static const MemoryRegionOps mmio_mem_ops = {
Signature · 1 line
MST
#56 lore raw

[PULL 23/78] vhost: add vhost_dev_is_initialized() helper

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

The upcoming CPR support for vhost-vsock defers the full vhost_dev_init() to .post_load(). On an incoming CPR .realize() only sets up the backend, so we need a way to tell a fully initialized vhost device from a partially initialized one.

Add an explicit 'bool initialized' field, set once vhost_dev_init() succeeds, and cleared only in vhost_dev_cleanup() (by memset(0)). Also add a public vhost_dev_is_initialized() helper.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost.h | 14 ++++++++++++++
 hw/virtio/vhost.c         |  2 ++
 2 files changed, 16 insertions(+)
include/hw/virtio/vhost.h +14 -0
index 8127a9a5a2..73ea130d7e 100644
@@ -109,6 +109,8 @@ struct vhost_dev {
     uint64_t backend_cap;
     /* @started: is the vhost device started? */
     bool started;
+    /* @initialized: has the full vhost_dev_init() completed? */
+    bool initialized;
     bool log_enabled;
     uint64_t log_size;
     Error *migration_blocker;
@@ -248,6 +250,18 @@ static inline bool vhost_dev_is_started(struct vhost_dev *hdev)
     return hdev->started;
 }
 
+/**
+ * vhost_dev_is_initialized() - report init status of vhost device
+ * @hdev: common vhost_dev structure
+ *
+ * Return true if the full vhost_dev_init() has completed successfully
+ * (and vhost_dev_cleanup() has not run since).
+ */
+static inline bool vhost_dev_is_initialized(struct vhost_dev *hdev)
+{
+    return hdev->initialized;
+}
+
 static inline int vhost_dev_set_vring_enable(struct vhost_dev *hdev, int enable)
 {
     if (!hdev->vhost_ops->vhost_set_vring_enable) {
hw/virtio/vhost.c +2 -0
index 56fa434588..c01ccc800c 100644
@@ -1834,6 +1834,8 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
         goto fail;
     }
 
+    hdev->initialized = true;
+
     trace_vhost_dev_init_out(hdev);
 
     return 0;
Signature · 1 line
MST
#57 lore raw

[PULL 76/78] net/tap: add tap_fd_get_mtu() to query host tap MTU

In reply to Michael S. Tsirkin

From: Nariman Sayed <[email protected]>

Add a helper function to read the host tap device's configured MTU via SIOCGIFMTU. Since this ioctl requires a socket rather than the tap character device fd itself, a separate control socket is opened to perform the query, following the same pattern used by net/can/can_socketcan.c.

This is implemented for Linux only, with stub implementations returning -1 for BSD, Solaris, and the tap-stub backend, matching the existing pattern used by tap_fd_get_ifname() and tap_fd_set_steering_ebpf() in those files.

This is a preparatory patch; the function is not yet called from anywhere.

Message-ID: <[email protected]>

---
 net/tap_int.h     |  1 +
 net/tap-bsd.c     |  5 +++++
 net/tap-linux.c   | 30 ++++++++++++++++++++++++++++++
 net/tap-solaris.c |  5 +++++
 net/tap-stub.c    |  5 +++++
 5 files changed, 46 insertions(+)
net/tap_int.h +1 -0
index dc4f484006..360d61c889 100644
@@ -45,6 +45,7 @@ int tap_fd_set_vnet_be(int fd, int vnet_is_be);
 int tap_fd_enable(int fd);
 int tap_fd_disable(int fd);
 int tap_fd_get_ifname(int fd, char *ifname);
+int tap_fd_get_mtu(int fd);
 int tap_fd_set_steering_ebpf(int fd, int prog_fd);
 
 #endif /* NET_TAP_INT_H */
net/tap-bsd.c +5 -0
index c39daf9385..17cedccf9e 100644
@@ -264,6 +264,11 @@ int tap_fd_get_ifname(int fd, char *ifname)
     return -1;
 }
 
+int tap_fd_get_mtu(int fd)
+{
+    return -1;
+}
+
 int tap_fd_set_steering_ebpf(int fd, int prog_fd)
 {
     return -1;
net/tap-linux.c +30 -0
index 3cd7d26710..23a10151c1 100644
@@ -34,6 +34,7 @@
 #include "qapi/error.h"
 #include "qemu/error-report.h"
 #include "qemu/cutils.h"
+#include "qemu/sockets.h"
 
 #define PATH_NET_TUN "/dev/net/tun"
 
@@ -346,6 +347,35 @@ int tap_fd_get_ifname(int fd, char *ifname)
     return 0;
 }
 
+int tap_fd_get_mtu(int fd)
+{
+    struct ifreq ifr;
+    int s, ret, mtu;
+
+    memset(&ifr, 0, sizeof(ifr));
+    if (tap_fd_get_ifname(fd, ifr.ifr_name) != 0) {
+        return -1;
+    }
+
+    s = qemu_socket(AF_INET, SOCK_DGRAM, 0);
+    if (s < 0) {
+        error_report("could not create control socket: %s",
+                      strerror(errno));
+        return -1;
+    }
+
+    ret = ioctl(s, SIOCGIFMTU, &ifr);
+    if (ret != 0) {
+        error_report("SIOCGIFMTU ioctl() failed: %s", strerror(errno));
+        close(s);
+        return -1;
+    }
+
+    mtu = ifr.ifr_mtu;
+    close(s);
+    return mtu;
+}
+
 int tap_fd_set_steering_ebpf(int fd, int prog_fd)
 {
     if (ioctl(fd, TUNSETSTEERINGEBPF, (void *) &prog_fd) != 0) {
net/tap-solaris.c +5 -0
index 2c6c7af15e..f91a317a9f 100644
@@ -293,6 +293,11 @@ int tap_fd_get_ifname(int fd, char *ifname)
     return -1;
 }
 
+int tap_fd_get_mtu(int fd)
+{
+    return -1;
+}
+
 int tap_fd_set_steering_ebpf(int fd, int prog_fd)
 {
     return -1;
net/tap-stub.c +5 -0
index 6aa60d96ad..756ce882a7 100644
@@ -91,6 +91,11 @@ int tap_fd_get_ifname(int fd, char *ifname)
     return -1;
 }
 
+int tap_fd_get_mtu(int fd)
+{
+    return -1;
+}
+
 int tap_fd_set_steering_ebpf(int fd, int prog_fd)
 {
     return -1;
Signature · 1 line
MST
#58 lore raw

[PULL 16/78] vhost: add vhost_reset_owner op

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

Add a VhostOps callback issuing VHOST_RESET_OWNER, wired up for the kernel backend. The CPR (checkpoint-restore) path uses it to release device ownership on the source so the destination can reclaim it later with VHOST_SET_OWNER.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost-backend.h | 2 ++
 hw/virtio/vhost-kernel.c          | 6 ++++++
 2 files changed, 8 insertions(+)
include/hw/virtio/vhost-backend.h +2 -0
index daa979a1aa..fb580aa417 100644
@@ -104,6 +104,7 @@ typedef int (*vhost_get_features_op)(struct vhost_dev *dev,
                                      uint64_t *features);
 typedef int (*vhost_set_backend_cap_op)(struct vhost_dev *dev);
 typedef int (*vhost_set_owner_op)(struct vhost_dev *dev);
+typedef int (*vhost_reset_owner_op)(struct vhost_dev *dev);
 typedef int (*vhost_reset_device_op)(struct vhost_dev *dev);
 typedef int (*vhost_get_vq_index_op)(struct vhost_dev *dev, int idx);
 typedef int (*vhost_set_vring_enable_op)(struct vhost_dev *dev,
@@ -205,6 +206,7 @@ typedef struct VhostOps {
     vhost_get_features_op vhost_get_features;
     vhost_set_backend_cap_op vhost_set_backend_cap;
     vhost_set_owner_op vhost_set_owner;
+    vhost_reset_owner_op vhost_reset_owner;
     vhost_reset_device_op vhost_reset_device;
     vhost_get_vq_index_op vhost_get_vq_index;
     vhost_set_vring_enable_op vhost_set_vring_enable;
hw/virtio/vhost-kernel.c +6 -0
index 3390b48c6f..55d316b88e 100644
@@ -261,6 +261,11 @@ static int vhost_kernel_set_owner(struct vhost_dev *dev)
     return vhost_kernel_call(dev, VHOST_SET_OWNER, NULL);
 }
 
+static int vhost_kernel_reset_owner(struct vhost_dev *dev)
+{
+    return vhost_kernel_call(dev, VHOST_RESET_OWNER, NULL);
+}
+
 static int vhost_kernel_get_vq_index(struct vhost_dev *dev, int idx)
 {
     assert(idx >= dev->vq_index && idx < dev->vq_index + dev->nvqs);
@@ -385,6 +390,7 @@ const VhostOps kernel_ops = {
         .vhost_get_features_ex = vhost_kernel_get_features,
         .vhost_set_backend_cap = vhost_kernel_set_backend_cap,
         .vhost_set_owner = vhost_kernel_set_owner,
+        .vhost_reset_owner = vhost_kernel_reset_owner,
         .vhost_get_vq_index = vhost_kernel_get_vq_index,
         .vhost_vsock_set_guest_cid = vhost_kernel_vsock_set_guest_cid,
         .vhost_vsock_set_running = vhost_kernel_vsock_set_running,
Signature · 1 line
MST
#59 lore raw

[PULL 48/78] pcie_sriov: fix wrong write width and off-by-one in pf_reset

In reply to Michael S. Tsirkin

From: Li RongQing <[email protected]>

pcie_sriov_pf_reset() restores the VF BAR type registers using pci_set_quad() with a 4-byte stride. Since each VF BAR register is 4 bytes wide, the 8-byte write also covers the following BAR register. Although subsequent iterations overwrite most of the unintended writes, the access is incorrect and the final iteration can write beyond the VF BAR registers.

SR-IOV defines six VF BAR registers and does not provide a VF ROM BAR. However, several SR-IOV code paths use PCI_NUM_REGIONS, which includes the ROM slot and therefore has a value of seven.

In particular, the final iteration in pcie_sriov_pf_reset() currently writes a quadword at the offset of the seventh region. This starts at the byte immediately following the six VF BAR registers and can overwrite subsequent fields in the SR-IOV capability.

Use pci_set_long() to match the 4-byte VF BAR register width, and use PCI_SRIOV_NUM_BARS when iterating over SR-IOV VF BARs. This also makes the BAR count consistent with the assertion in pcie_sriov_pf_init_vf_bar().

Fixes 19e55471d4e8 ("pcie_sriov: Allow user to create SR-IOV device")
Fixes c8bc4db403e1 ("pcie_sriov: Reset SR-IOV extended capability")

Message-ID: <[email protected]>

---
 hw/pci/pcie_sriov.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
hw/pci/pcie_sriov.c +5 -5
index 69930c7b8c..6a748c3626 100644
@@ -229,7 +229,7 @@ void pcie_sriov_pf_init_vf_bar(PCIDevice *dev, int region_num,
 
     assert(sriov_cap > 0);
     assert(region_num >= 0);
-    assert(region_num < PCI_NUM_REGIONS);
+    assert(region_num < PCI_SRIOV_NUM_BARS);
     assert(region_num != PCI_ROM_SLOT);
 
     wmask = ~(size - 1);
@@ -308,7 +308,7 @@ int16_t pcie_sriov_pf_init_from_user_created_vfs(PCIDevice *dev,
             return -1;
         }
 
-        for (size_t j = 0; j < PCI_NUM_REGIONS; j++) {
+        for (size_t j = 0; j < PCI_SRIOV_NUM_BARS; j++) {
             if (vfs[i]->io_regions[j].size != vfs[0]->io_regions[j].size ||
                 vfs[i]->io_regions[j].type != vfs[0]->io_regions[j].type) {
                 error_setg(errp, "inconsistent SR-IOV BARs");
@@ -344,7 +344,7 @@ int16_t pcie_sriov_pf_init_from_user_created_vfs(PCIDevice *dev,
     dev->exp.sriov_pf.vf = vfs;
     dev->exp.sriov_pf.vf_user_created = true;
 
-    for (i = 0; i < PCI_NUM_REGIONS; i++) {
+    for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
         PCIIORegion *region = &vfs[0]->io_regions[i];
 
         if (region->size) {
@@ -457,8 +457,8 @@ void pcie_sriov_pf_reset(PCIDevice *dev)
      */
     pci_set_word(dev->config + sriov_cap + PCI_SRIOV_SYS_PGSIZE, 0x1);
 
-    for (uint16_t i = 0; i < PCI_NUM_REGIONS; i++) {
-        pci_set_quad(dev->config + sriov_cap + PCI_SRIOV_BAR + i * 4,
+    for (uint16_t i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
+        pci_set_long(dev->config + sriov_cap + PCI_SRIOV_BAR + i * 4,
                      dev->exp.sriov_pf.vf_bar_type[i]);
     }
 }
Signature · 1 line
MST
#60 lore raw

[PULL 71/78] backends/iommufd: Extend iommufd_backend_alloc_hwpt() with fault_id

In reply to Michael S. Tsirkin

From: Zhenzhong Duan <[email protected]>

We should set flags IOMMU_HWPT_FAULT_ID_VALID when pass in a fault object id in the caller.

Message-ID: <[email protected]>

---
 include/system/iommufd.h    | 4 ++--
 backends/iommufd.c          | 7 ++++---
 hw/arm/smmuv3-accel.c       | 6 +++---
 hw/i386/intel_iommu_accel.c | 2 +-
 hw/vfio/iommufd.c           | 2 +-
 backends/trace-events       | 2 +-
 6 files changed, 12 insertions(+), 11 deletions(-)
include/system/iommufd.h +2 -2
index 36b34b4899..79b8dabfb6 100644
@@ -90,8 +90,8 @@ bool iommufd_backend_get_device_info(IOMMUFDBackend *be, uint32_t devid,
 bool iommufd_backend_alloc_hwpt(IOMMUFDBackend *be, uint32_t dev_id,
                                 uint32_t pt_id, uint32_t flags,
                                 uint32_t data_type, uint32_t data_len,
-                                void *data_ptr, uint32_t *out_hwpt,
-                                Error **errp);
+                                void *data_ptr, uint32_t fault_id,
+                                uint32_t *out_hwpt, Error **errp);
 bool iommufd_backend_alloc_viommu(IOMMUFDBackend *be, uint32_t dev_id,
                                   uint32_t viommu_type, uint32_t hwpt_id,
                                   void *data_ptr, uint32_t data_len,
backends/iommufd.c +4 -3
index 457b74a558..ecf4caee1a 100644
@@ -317,8 +317,8 @@ int iommufd_backend_unmap_dma(IOMMUFDBackend *be, uint32_t ioas_id,
 bool iommufd_backend_alloc_hwpt(IOMMUFDBackend *be, uint32_t dev_id,
                                 uint32_t pt_id, uint32_t flags,
                                 uint32_t data_type, uint32_t data_len,
-                                void *data_ptr, uint32_t *out_hwpt,
-                                Error **errp)
+                                void *data_ptr, uint32_t fault_id,
+                                uint32_t *out_hwpt, Error **errp)
 {
     int ret, fd = be->fd;
     struct iommu_hwpt_alloc alloc_hwpt = {
@@ -329,11 +329,12 @@ bool iommufd_backend_alloc_hwpt(IOMMUFDBackend *be, uint32_t dev_id,
         .data_type = data_type,
         .data_len = data_len,
         .data_uptr = (uintptr_t)data_ptr,
+        .fault_id = fault_id,
     };
 
     ret = ioctl(fd, IOMMU_HWPT_ALLOC, &alloc_hwpt);
     trace_iommufd_backend_alloc_hwpt(fd, dev_id, pt_id, flags, data_type,
-                                     data_len, (uintptr_t)data_ptr,
+                                     data_len, (uintptr_t)data_ptr, fault_id,
                                      alloc_hwpt.out_hwpt_id, ret);
     if (ret) {
         error_setg_errno(errp, errno, "Failed to allocate hwpt");
hw/arm/smmuv3-accel.c +3 -3
index 91aca1aa14..66e6a5011a 100644
@@ -268,7 +268,7 @@ smmuv3_accel_dev_alloc_translate(SMMUv3AccelDevice *accel_dev, STE *ste,
                                     accel->viommu->viommu_id, flags,
                                     IOMMU_HWPT_DATA_ARM_SMMUV3,
                                     sizeof(nested_data), &nested_data,
-                                    &hwpt_id, errp)) {
+                                    0, &hwpt_id, errp)) {
             return NULL;
     }
 
@@ -632,14 +632,14 @@ smmuv3_accel_alloc_viommu(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *hiodi,
     if (!iommufd_backend_alloc_hwpt(hiodi->iommufd, hiodi->devid, viommu_id,
                                     0, IOMMU_HWPT_DATA_ARM_SMMUV3,
                                     sizeof(abort_data), &abort_data,
-                                    &accel->abort_hwpt_id, errp)) {
+                                    0, &accel->abort_hwpt_id, errp)) {
         goto free_viommu;
     }
 
     if (!iommufd_backend_alloc_hwpt(hiodi->iommufd, hiodi->devid, viommu_id,
                                     0, IOMMU_HWPT_DATA_ARM_SMMUV3,
                                     sizeof(bypass_data), &bypass_data,
-                                    &accel->bypass_hwpt_id, errp)) {
+                                    0, &accel->bypass_hwpt_id, errp)) {
         goto free_abort_hwpt;
     }
 
hw/i386/intel_iommu_accel.c +1 -1
index c3677775e9..c8f2835204 100644
@@ -100,7 +100,7 @@ static bool vtd_create_fs_hwpt(VTDHostIOMMUDevice *vtd_hiod,
     return iommufd_backend_alloc_hwpt(hiodi->iommufd, hiodi->devid,
                                       hiodi->hwpt_id, flags,
                                       IOMMU_HWPT_DATA_VTD_S1, sizeof(vtd), &vtd,
-                                      fs_hwpt_id, errp);
+                                      0, fs_hwpt_id, errp);
 }
 
 static void vtd_destroy_old_fs_hwpt(VTDAccelPASIDCacheEntry *vtd_pce)
hw/vfio/iommufd.c +1 -1
index de482128bf..90ddb54f4c 100644
@@ -486,7 +486,7 @@ static bool iommufd_cdev_autodomains_get(VFIODevice *vbasedev,
     if (!iommufd_backend_alloc_hwpt(iommufd, vbasedev->devid,
                                     container->ioas_id, flags,
                                     IOMMU_HWPT_DATA_NONE, 0, NULL,
-                                    &hwpt_id, errp)) {
+                                    0, &hwpt_id, errp)) {
         return false;
     }
 
backends/trace-events +1 -1
index b1c1d89dd8..2f242feb78 100644
@@ -16,7 +16,7 @@ iommufd_backend_map_file_dma(int iommufd, uint32_t ioas, uint64_t iova, uint64_t
 iommufd_backend_unmap_dma_non_exist(int iommufd, uint32_t ioas, uint64_t iova, uint64_t size, int ret) " Unmap nonexistent mapping: iommufd=%d ioas=%d iova=0x%"PRIx64" size=0x%"PRIx64" (%d)"
 iommufd_backend_unmap_dma(int iommufd, uint32_t ioas, uint64_t iova, uint64_t size, int ret) " iommufd=%d ioas=%d iova=0x%"PRIx64" size=0x%"PRIx64" (%d)"
 iommufd_backend_alloc_ioas(int iommufd, uint32_t ioas) " iommufd=%d ioas=%d"
-iommufd_backend_alloc_hwpt(int iommufd, uint32_t dev_id, uint32_t pt_id, uint32_t flags, uint32_t hwpt_type, uint32_t len, uint64_t data_ptr, uint32_t out_hwpt_id, int ret) " iommufd=%d dev_id=%u pt_id=%u flags=0x%x hwpt_type=%u len=%u data_ptr=0x%"PRIx64" out_hwpt=%u (%d)"
+iommufd_backend_alloc_hwpt(int iommufd, uint32_t dev_id, uint32_t pt_id, uint32_t flags, uint32_t hwpt_type, uint32_t len, uint64_t data_ptr, uint32_t fault_id, uint32_t out_hwpt_id, int ret) " iommufd=%d dev_id=%u pt_id=%u flags=0x%x hwpt_type=%u len=%u data_ptr=0x%"PRIx64" fault_id=%u out_hwpt=%u (%d)"
 iommufd_backend_alloc_faultq(int iommufd, uint32_t fault_id, uint32_t fault_fd, int ret) " iommufd=%d fault_id=%u fault_fd=%u (%d)"
 iommufd_backend_free_id(int iommufd, uint32_t id, int ret) " iommufd=%d id=%d (%d)"
 iommufd_backend_set_dirty(int iommufd, uint32_t hwpt_id, bool start, int ret) " iommufd=%d hwpt=%u enable=%d (%d)"
Signature · 1 line
MST
#61 lore raw

[PULL 31/78] tests/functional: cover a cpr-transfer target going away with vhost-vsock

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

A cpr-transfer target takes the vsock device over in post_load(). Make sure the source gets it back when the target goes away afterwards. Three cases, in the first two the target is made to fail the load by lacking a device the source has:

- the target exits on the failure (default exit-on-error); the source is stopped beforehand, so it stays paused until we 'cont' it once the target is gone;

- the target stays alive (exit-on-error=false) and the source resumes

    on its own (return-path capability);

- the target loads the state while the source is stopped, stays paused and is quit normally; the source is resumed instead.

In all cases a host<->guest transfer must then work on the source.

The transfer helpers get a payload size parameter, since the plain checks don't need the 512M of the stress cases, and the target start code is moved into a helper shared with the existing cpr-transfer test.

Message-ID: <[email protected]>

---
 .../functional/x86_64/test_vhost_vsock_cpr.py | 178 ++++++++++++++----
 1 file changed, 140 insertions(+), 38 deletions(-)
tests/functional/x86_64/test_vhost_vsock_cpr.py +140 -38
index 035307fc82..3ac0a80c5e 100755
@@ -24,6 +24,7 @@
 VSOCK_PORT = 5000
 
 TRANSFER_SIZE = 512 * 1024 * 1024    # file size for host<->guest transfer
+CHECK_SIZE = 16 * 1024 * 1024        # enough to just check that vsock works
 CPR_INTERVAL = 0.2                   # interval between CPR ops
 
 VHOST_SET_OWNER = 0xAF01      # _IO(VHOST_VIRTIO, 0x01)
@@ -71,11 +72,6 @@ def setUp(self):
                                'qcow2', '-b', self.ASSET_DISKIMAGE.fetch(),
                                '-F', 'qcow2', self.disk])
 
-        self.payload = self.scratch_file('payload')
-        with open(self.payload, 'wb') as f:
-            for _ in range(TRANSFER_SIZE >> 20):
-                f.write(os.urandom(1 << 20))
-
         self.sock_dir = self.socket_dir().name
         self.cpr_count = 0
 
@@ -121,12 +117,16 @@ def attempt():
                 return None
         return self.wait_for('QMP to come up', attempt)
 
-    def wait_migration(self, qmp):
-        def completed():
+    def wait_migration(self, qmp, expect='completed'):
+        def finished():
             status = qmp.cmd('query-migrate').get('status')
-            self.assertNotEqual(status, 'failed', 'migration failed')
-            return status == 'completed'
-        self.wait_for('migration completion', completed)
+            if status in ('completed', 'failed'):
+                if expect:
+                    self.assertEqual(status, expect,
+                                     'unexpected migration result')
+                return True
+            return False
+        self.wait_for('migration completion', finished)
 
     def wait_running(self, qmp_path):
         # Make sure we're dealing with the new QEMU after CPR
@@ -139,13 +139,37 @@ def wait_running(self, qmp_path):
             except QMP_GONE:
                 pass
 
-    def boot(self):
+    def boot(self, *extra_args):
         self.guest = self.vm
-        self.qmp_path = self.prepare_vm(self.guest, 'source')
+        self.qmp_path = self.prepare_vm(self.guest, 'source', *extra_args)
         self.guest.launch()
         self.wait_for_console_pattern('login:')
         exec_command_and_wait_for_pattern(self, 'root', 'root@localhost:~#')
 
+    def start_target(self, name, main_incoming=None):
+        """
+        Start a cpr-transfer target listening on the CPR channel.  Return
+        the VM, its QMP socket path and the channels for 'migrate'.
+        """
+        mig_sock = os.path.join(self.sock_dir, f'{name}-mig.sock')
+        cpr_sock = os.path.join(self.sock_dir, f'{name}-cpr.sock')
+        channels = [
+            {'channel-type': 'main',
+             'addr': {'transport': 'socket', 'type': 'unix',
+                      'path': mig_sock}},
+            {'channel-type': 'cpr',
+             'addr': {'transport': 'socket', 'type': 'unix',
+                      'path': cpr_sock}}]
+
+        target = self.get_vm(name=name)
+        qmp_path = self.prepare_vm(
+            target, name, '-incoming', main_incoming or f'unix:{mig_sock}',
+            '-incoming', 'cpr,addr.transport=socket,addr.type=unix,'
+                         f'addr.path={cpr_sock}')
+        target.launch()
+        self.wait_for('the CPR socket', lambda: os.path.exists(cpr_sock))
+        return target, qmp_path, channels
+
     def transfer(self, guest_cmd, host_cmd, cpr):
         """
         Run a socat transfer between the guest and the host, and keep
@@ -160,21 +184,26 @@ def transfer(self, guest_cmd, host_cmd, cpr):
         exec_command_and_wait_for_pattern(self, 'wait %1; echo RC_$?',
                                           'RC_0', vm=self.guest)
 
-    def transfer_both_ways(self, cpr):
+    def transfer_both_ways(self, cpr, size=TRANSFER_SIZE):
         """
-        Send the payload to a file on the guest's disk, get it back into
-        another file on the host, then compare.
+        Send a random payload to a file on the guest's disk, get it back
+        into another file on the host, then compare.
         """
+        payload = self.scratch_file('payload')
+        with open(payload, 'wb') as f:
+            for _ in range(size >> 20):
+                f.write(os.urandom(1 << 20))
+
         echo = self.scratch_file('echo')
         connect = f'VSOCK-CONNECT:{GUEST_CID}:{VSOCK_PORT},retry=10'
 
         self.transfer(f'socat -u VSOCK-LISTEN:{VSOCK_PORT} CREATE:/root/data',
-                      ['socat', '-u', f'FILE:{self.payload}', connect], cpr)
+                      ['socat', '-u', f'FILE:{payload}', connect], cpr)
         self.transfer(f'socat -u FILE:/root/data VSOCK-LISTEN:{VSOCK_PORT}',
                       ['socat', '-u', connect, f'CREATE:{echo}'], cpr)
 
         self.log.info('%d CPR migrations performed', self.cpr_count)
-        self.assertTrue(filecmp.cmp(self.payload, echo, shallow=False),
+        self.assertTrue(filecmp.cmp(payload, echo, shallow=False),
                         'the data came back different')
 
     def test_cpr_transfer(self):
@@ -184,29 +213,11 @@ def test_cpr_transfer(self):
         it and shutting down the source
         """
         def cpr():
-            n = self.cpr_count
-            mig_sock = os.path.join(self.sock_dir, f'mig{n}.sock')
-            cpr_sock = os.path.join(self.sock_dir, f'cpr{n}.sock')
-            target = self.get_vm(name=f'target{n}')
-            target_qmp_path = self.prepare_vm(
-                target, f'target{n}',
-                '-incoming', f'unix:{mig_sock}',
-                '-incoming', 'cpr,addr.transport=socket,addr.type=unix,'
-                             f'addr.path={cpr_sock}')
-
-            # The target listens on the CPR channel: wait for it to be there
-            target.launch()
-            self.wait_for('the CPR socket', lambda: os.path.exists(cpr_sock))
-
+            target, target_qmp_path, channels = self.start_target(
+                f'target{self.cpr_count}')
             with self.qmp_connect(self.qmp_path) as qmp:
                 qmp.cmd('migrate-set-parameters', mode='cpr-transfer')
-                qmp.cmd('migrate', channels=[
-                    {'channel-type': 'main',
-                     'addr': {'transport': 'socket', 'type': 'unix',
-                              'path': mig_sock}},
-                    {'channel-type': 'cpr',
-                     'addr': {'transport': 'socket', 'type': 'unix',
-                              'path': cpr_sock}}])
+                qmp.cmd('migrate', channels=channels)
                 self.wait_migration(qmp)
             self.wait_running(target_qmp_path)
 
@@ -217,6 +228,97 @@ def cpr():
         self.boot()
         self.transfer_both_ways(cpr)
 
+    def test_cpr_transfer_dead_target(self):
+        """
+        A cpr-transfer target which exits on a load error after having
+        taken the vsock device over must release it on exit, so that the
+        source can be resumed afterwards.  The target lacks a device the
+        source has.  The source is stopped beforehand, so it stays paused
+        on failure until we 'cont' it.
+        """
+        self.boot('-device', 'virtio-rng-pci')
+        target, _, channels = self.start_target('target')
+
+        with self.qmp_connect(self.qmp_path) as qmp:
+            qmp.cmd('stop')
+            qmp.cmd('migrate-set-parameters', mode='cpr-transfer')
+            qmp.cmd('migrate', channels=channels)
+            self.wait_migration(qmp, expect=None)
+            self.wait_for('the target to exit',
+                          lambda: not target.is_running())
+            self.assertEqual(target.exitcode(), 1,
+                             'the target did not exit on the load error')
+            qmp.cmd('cont')
+
+        self.wait_running(self.qmp_path)
+        self.transfer_both_ways(lambda: None, size=CHECK_SIZE)
+
+    def test_cpr_transfer_target_quit(self):
+        """
+        A cpr-transfer target which has loaded the state but hasn't been
+        started yet may quit normally, and the source resume instead.
+        The target doesn't unrealize its devices on quit, so it must
+        release the vsock device on exit.
+        """
+        self.boot()
+        target, target_qmp_path, channels = self.start_target(
+            'target', main_incoming='defer')
+
+        with self.qmp_connect(self.qmp_path) as qmp:
+            qmp.cmd('stop')
+            qmp.cmd('migrate-set-parameters', mode='cpr-transfer')
+            qmp.cmd('migrate', channels=channels)
+
+            target_qmp = self.qmp_connect(target_qmp_path)
+            target_qmp.cmd('migrate-incoming', channels=channels[:1])
+            self.wait_migration(qmp)
+            self.wait_migration(target_qmp)
+            self.assertEqual(target_qmp.cmd('query-status')['status'],
+                             'paused')
+            try:
+                target_qmp.cmd('quit')
+                target_qmp.close()
+            except QMP_GONE:
+                pass
+
+            self.wait_for('the target to quit',
+                          lambda: not target.is_running())
+            self.assertEqual(target.exitcode(), 0, 'the target did not quit')
+            qmp.cmd('cont')
+
+        self.wait_running(self.qmp_path)
+        self.transfer_both_ways(lambda: None, size=CHECK_SIZE)
+
+    def test_cpr_transfer_failed_target(self):
+        """
+        A cpr-transfer target which fails to load the state after having
+        taken the vsock device over must give it back to the source.
+        The target lacks a device the source has, stays alive on failure
+        (exit-on-error=false), and the source resumes on its own
+        (return-path).  Then vsock must still work on the source.
+        """
+        self.boot('-device', 'virtio-rng-pci')
+        target, target_qmp_path, channels = self.start_target(
+            'target', main_incoming='defer')
+
+        with self.qmp_connect(self.qmp_path) as qmp:
+            qmp.cmd('migrate-set-parameters', mode='cpr-transfer')
+            qmp.cmd('migrate-set-capabilities', capabilities=[
+                {'capability': 'return-path', 'state': True}])
+            qmp.cmd('migrate', channels=channels)
+
+            # The target opens its monitor once it has read the CPR state
+            with self.qmp_connect(target_qmp_path) as target_qmp:
+                target_qmp.cmd('migrate-incoming', channels=channels[:1],
+                               **{'exit-on-error': False})
+                self.wait_migration(qmp, expect='failed')
+                self.wait_migration(target_qmp, expect='failed')
+
+        # The source resumes by itself, and vsock must work on it
+        self.wait_running(self.qmp_path)
+        self.transfer_both_ways(lambda: None, size=CHECK_SIZE)
+        target.shutdown()
+
     def test_cpr_exec(self):
         """
         Perform consecutive cpr-exec migration during vsock file transfer,
Signature · 1 line
MST
#62 lore raw

[PULL 12/78] migration: forbid snapshots in CPR migration modes

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

The savevm/loadvm operation doesn't make any sense in any of the CPR modes. Saving VM state implies saving its RAM entirely, while the whole point of CPR is avoiding that: in cpr-transfer and cpr-exec modes RAM is preserved in place and never put into the migration stream (see migrate_ram_is_ignored() in migration/ram.c). As a result, a snapshot taken with such a mode set has no RAM in it, and loading a snapshot with such a mode set fails in the RAM loader.

Currently nothing stops these operations from firing with a CPR mode still set, e.g. after an earlier CPR attempt. Reject them early in migrate_can_snapshot(), which guards 'savevm'/'loadvm' HMP commands as well as 'snapshot-save'/'snapshot-load' QMP commands.

Message-ID: <[email protected]>

---
 migration/options.c | 7 +++++++
 1 file changed, 7 insertions(+)
migration/options.c +7 -0
index 46d040fc3f..92eda90114 100644
@@ -560,8 +560,15 @@ static bool migrate_incoming_started(void)
 bool migrate_can_snapshot(Error **errp)
 {
     MigrationState *s = migrate_get_current();
+    MigMode mode = migrate_mode();
     int i;
 
+    if (mode != MIG_MODE_NORMAL) {
+        error_setg(errp, "Snapshots are not compatible with migration mode %s",
+                   MigMode_str(mode));
+        return false;
+    }
+
     for (i = 0; i < check_caps_savevm.size; i++) {
         int incomp_cap = check_caps_savevm.caps[i];
 
Signature · 1 line
MST
#63 lore raw

[PULL 75/78] hw/virtio/virtio-mem: Fix error message on invalid NUMA node

In reply to Michael S. Tsirkin

From: Gavin Shan <[email protected]>

Add space as the separator between "exceeds" and "the number of numa nodes:". Otherwise, we will see error message with twisted format like below.

  qemu-system-aarch64: -device virtio-mem-pci,id=vmem1,bus=pcie.3, \
  memdev=virtio-mem1-mem,requested-size=0,node=1: 'node' property  \
  has value '1', which exceedsthe number of numa nodes: 1

Message-ID: <[email protected]>

---
 hw/virtio/virtio-mem.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
hw/virtio/virtio-mem.c +1 -1
index 7130ed852d..ee6a0c206a 100644
@@ -846,7 +846,7 @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp)
     if ((nb_numa_nodes && vmem->node >= nb_numa_nodes) ||
         (!nb_numa_nodes && vmem->node)) {
         error_setg(errp, "'%s' property has value '%" PRIu32 "', which exceeds"
-                   "the number of numa nodes: %d", VIRTIO_MEM_NODE_PROP,
+                   " the number of numa nodes: %d", VIRTIO_MEM_NODE_PROP,
                    vmem->node, nb_numa_nodes ? nb_numa_nodes : 1);
         return;
     }
Signature · 1 line
MST
#64 lore raw

[PULL 54/78] MAINTAINERS: Update Junjie Cao's email address

In reply to Michael S. Tsirkin

From: Junjie Cao <[email protected]>

Update my email address to [email protected] for future correspondence. I'll continue contributing and reviewing CXL patches.

Message-ID: <[email protected]>

---
 MAINTAINERS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
MAINTAINERS +1 -1
index b36582c9d0..d7ec12ac39 100644
@@ -3382,7 +3382,7 @@ F: docs/devel/migration/CPR.rst
 
 Compute Express Link
 M: Jonathan Cameron <[email protected]>
-R: Junjie Cao <[email protected]>
+R: Junjie Cao <[email protected]>
 R: Anisa Su <[email protected]>
 L: [email protected]
 S: Supported
Signature · 1 line
MST
#65 lore raw

[PULL 43/78] hw/pci-bridge: Default the generic PCIe root port as Gen6 capable

In reply to Michael S. Tsirkin

From: David Vrabel <[email protected]>

As part of making virtual PCIe topology having the same apparent bandwidth as a the underlying hardware, make the generic PCIe root port support maximum speed so by default it doesn't appear to be a bottleneck.

This also makes the three generic PCIe ports (root, upstream, and downstream) have the same link speed/width capabilities.

Message-ID: <[email protected]>

---
 hw/core/machine.c                  | 1 +
 hw/pci-bridge/gen_pcie_root_port.c | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
hw/core/machine.c +1 -0
index d9ac4b419a..4da24f014a 100644
@@ -43,6 +43,7 @@
 GlobalProperty hw_compat_11_1[] = {
     { "sysbus-ehci-usb", "x-migrate-fetch-addr-64bit", "off" },
     { "pci-ehci-usb", "x-migrate-fetch-addr-64bit", "off" },
+    { "pcie-root-port", "speed", "16" },
 };
 const size_t hw_compat_11_1_len = G_N_ELEMENTS(hw_compat_11_1);
 
hw/pci-bridge/gen_pcie_root_port.c +1 -1
index d3ace7f0db..8cabeadac8 100644
@@ -130,7 +130,7 @@ static const Property gen_rp_props[] = {
     DEFINE_PROP_SIZE("pref64-reserve", GenPCIERootPort,
                      res_reserve.mem_pref_64, -1),
     DEFINE_PROP_PCIE_LINK_SPEED("speed", PCIESlot,
-                                speed, PCIE_LINK_SPEED_16),
+                                speed, PCIE_LINK_SPEED_64),
     DEFINE_PROP_PCIE_LINK_WIDTH("width", PCIESlot,
                                 width, PCIE_LINK_WIDTH_32),
 };
Signature · 1 line
MST
#66 lore raw

[PULL 32/78] qapi/virtio: Restore backend features in x-query-virtio-status

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

Commit 1ee0bc0c9bf0 removed vhost_dev.backend_features and its representation in VhostStatus. It also removed the assignment to VirtioStatus.backend_features, although both that field and its source, VirtIODevice.backend_features_ex, still exist.

The resulting NULL pointer causes HMP info virtio-status and QMP x-query-virtio-status to crash. This affects virtio devices even without an active vhost backend.

Restore the assignment from VirtIODevice.backend_features_ex.

Fixes 1ee0bc0c9bf0 ("vhost: drop backend_features field")

Message-ID: <[email protected]>

---
 hw/virtio/virtio-qmp.c | 2 ++
 1 file changed, 2 insertions(+)
hw/virtio/virtio-qmp.c +2 -0
index 5aaeebdd96..8b7cd7a310 100644
@@ -713,6 +713,8 @@ VirtioStatus *qmp_x_query_virtio_status(const char *path, Error **errp)
                                                  vdev->guest_features_ex);
     status->host_features = qmp_decode_features(vdev->device_id,
                                                 vdev->host_features_ex);
+    status->backend_features = qmp_decode_features(vdev->device_id,
+                                                   vdev->backend_features_ex);
 
     switch (vdev->device_endian) {
     case VIRTIO_DEVICE_ENDIAN_LITTLE:
Signature · 1 line
MST
#67 lore raw

[PULL 40/78] pcie_sriov: register user created virtual function before realizing it

In reply to Michael S. Tsirkin

From: Daniel Paziyski <[email protected]>

There are two ways of creating virtual functions: by using the sriov-pf device parameter (this way, creating a user created VF and binding it to a PF), or by using a device specific parameter, where the device manually creates a given amount of VFs.

When a PCI device is realized, the device specific realize function is called first, and then the pcie_sriov_register_device function is called, which checks whether the device that has been created is a user created VF, and if it is the case and the device allows this kind of VFs, it inserts it into a hashmap, with the key being the ID of the PF, and the items being arrays of VFs.

User created VFs are instantiated independently, and later, when the PF calls pcie_sriov_pf_init_from_user_created_vfs, it discovers its VFs from the hashmap mentioned previously, and sets up in their PCIDevice.ex.sriov_pf structure the pointer to the PF. This means that, during realization, VFs have no access to the PF.

Device created VFs are instantiated during or after PF realization using the pcie_sriov_pf_init function, which sets up for the VFs the pointer to the PF before their realization, so when they're realized, they can access the PF with no issues.

The problem here is that pcie_sriov_register_device is called after the realization, and not before. This means that when a user created VF is created for a device which does not support user created VFs, but supports device created VFs, the realize function will notice that a VF is being created, and may try to access the PF, causing a null pointer dereference fault.

Fix this by placing the user created VF check and registering before the realization. This way, incorrectly created user created VFs will be noticed, and device creation will be aborted.

Additionally, remove from the pcie_sriov_register_device function the top check. It seems that this check is done to error out if the PF failed for some reason to initialize its list of user created VFs. However, in such situations, the pcie_sriov_pf_init_from_user_created_vfs function will error during realization, and it will be caught before the check is done at all. Moreover, since now pcie_sriov_register_device is called before realization, the check will always fail for PFs with user created VFs, because the list will be populated during realization.

The rest of the function though, correctly errors out if a user created VF is created for an unsupported device type, if a VF is created for a non-PCIe device, or if the PF is already instantiated, with now the advantage being that the check is done before the VF instantiation.

Moreover, move the unregistering of a device's acpi-index and the removal of a VF from its PF list from pci_qdev_unrealize to do_pci_unregister_device. We do the former so that the acpi-index is unclaimed on user created VF creation error, and the latter to not have extra code on the device specific realization error path.

When instantiating a user created VF for a NVME controller:

Command line:

    qemu-system-x86_64 -device nvme-subsys,id=subsys0 \
    -device nvme,id=vctrl0,sriov-pf=ctrl0,subsys=subsys0 \
    -device nvme,id=ctrl0,subsys=subsys0,serial=s

ASAN splat:

../hw/nvme/ctrl.c:9613:28: runtime error: member access within null pointer of type 'struct NvmeCtrl' AddressSanitizer:DEADLYSIGNAL
=================================================================
==91050==ERROR: AddressSanitizer: SEGV on unknown address 0x000000001cf0 (pc 0x7f79a8573dcd bp 0x7ffd622c1bc0 sp 0x7ffd622c1b68 T0) ==91050==The signal is caused by a READ memory access.

    #0 0x7f79a8573dcd  (/usr/lib/libc.so.6+0x173dcd) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e)
    #1 0x5644c6c0266e in nvme_realize ../hw/nvme/ctrl.c:9613
    #2 0x5644c6c67213 in pci_qdev_realize ../hw/pci/pci.c:2316
    #3 0x5644c7a2cfc1 in device_set_realized ../hw/core/qdev.c:514
    #4 0x5644c7a4f767 in property_set_bool ../qom/object.c:2484
    #5 0x5644c7a48d0b in object_property_set ../qom/object.c:1548
    #6 0x5644c7a568a5 in object_property_set_qobject ../qom/qom-qobject.c:28
    #7 0x5644c7a49385 in object_property_set_bool ../qom/object.c:1618
    #8 0x5644c7a2aeb0 in qdev_realize ../hw/core/qdev.c:277
    #9 0x5644c735f29f in qdev_device_add_from_qdict ../system/qdev-monitor.c:740
    #10 0x5644c735f3ab in qdev_device_add ../system/qdev-monitor.c:758
    #11 0x5644c72b1901 in device_init_func ../system/vl.c:1217
    #12 0x5644c829d4a3 in qemu_opts_foreach ../util/qemu-option.c:1148
    #13 0x5644c72bc33e in qemu_create_cli_devices ../system/vl.c:2762
    #14 0x5644c72bcaa1 in qmp_x_exit_preconfig ../system/vl.c:2822
    #15 0x5644c72c3241 in qemu_init ../system/vl.c:3862
    #16 0x5644c801abf8 in main ../system/main.c:71
    #17 0x7f79a8427780  (/usr/lib/libc.so.6+0x27780) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e)
    #18 0x7f79a84278b8 in __libc_start_main (/usr/lib/libc.so.6+0x278b8) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e)
    #19 0x5644c5f0a1f4 in _start (BuildId: 8483f952216d9e345c3744300d0aefa38feb50d7)

==91050==Register values:

rax = 0x00007e49988640f0  rbx = 0x00007e49988640f0  rcx = 0x00000fc9b3104828  rdx = 0x0000000000000058
rdi = 0x00007e49988640f0  rsi = 0x0000000000001cf0  rbp = 0x00007ffd622c1bc0  rsp = 0x00007ffd622c1b68
 r8 = 0x00000fc9b3104829   r9 = 0x00000fc9b3104828  r10 = 0x00000fc9b310481e  r11 = 0x00000fc9b310481e
r12 = 0x0000000000001cf0  r13 = 0x00000f6f32e78578  r14 = 0x00000000ffffffff  r15 = 0x00007ffd622c1c10

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/usr/lib/libc.so.6+0x173dcd) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e) ==91050==ABORTING

Fixes 19e55471d4e8 ("pcie_sriov: Allow user to create SR-IOV device")

Message-ID: <[email protected]>

---
 hw/pci/pci.c        | 42 ++++++++++++++++++++++++------------------
 hw/pci/pcie_sriov.c |  6 ------
 2 files changed, 24 insertions(+), 24 deletions(-)
hw/pci/pci.c +24 -18
index 0efb4eb4bb..2d2fe02c68 100644
@@ -1227,6 +1227,19 @@ static void pci_config_free(PCIDevice *pci_dev)
     g_free(pci_dev->used);
 }
 
+static void pci_unregister_acpi_index(PCIDevice *pci_dev)
+{
+    if (pci_dev->acpi_index) {
+        GSequence *used_indexes = pci_acpi_index_list();
+
+        g_sequence_remove(g_sequence_lookup(used_indexes,
+                          GINT_TO_POINTER(pci_dev->acpi_index),
+                          g_cmp_uint32, NULL));
+
+        pci_dev->acpi_index = 0;
+    }
+}
+
 static void do_pci_unregister_device(PCIDevice *pci_dev)
 {
     pci_get_bus(pci_dev)->devices[pci_dev->devfn] = NULL;
@@ -1240,6 +1253,9 @@ static void do_pci_unregister_device(PCIDevice *pci_dev)
                                     &pci_dev->bus_master_enable_region);
     }
     address_space_destroy(&pci_dev->bus_master_as);
+
+    pcie_sriov_unregister_device(pci_dev);
+    pci_unregister_acpi_index(pci_dev);
 }
 
 /* Extract PCIReqIDCache into BDF format */
@@ -1491,7 +1507,6 @@ static void pci_qdev_unrealize(DeviceState *dev)
 
     pci_unregister_io_regions(pci_dev);
     pci_del_option_rom(pci_dev);
-    pcie_sriov_unregister_device(pci_dev);
 
     if (pc->exit) {
         pc->exit(pci_dev);
@@ -1501,17 +1516,6 @@ static void pci_qdev_unrealize(DeviceState *dev)
     do_pci_unregister_device(pci_dev);
 
     pci_dev->msi_trigger = NULL;
-
-    /*
-     * clean up acpi-index so it could reused by another device
-     */
-    if (pci_dev->acpi_index) {
-        GSequence *used_indexes = pci_acpi_index_list();
-
-        g_sequence_remove(g_sequence_lookup(used_indexes,
-                          GINT_TO_POINTER(pci_dev->acpi_index),
-                          g_cmp_uint32, NULL));
-    }
 }
 
 void pci_register_bar(PCIDevice *pci_dev, int region_num,
@@ -2326,8 +2330,15 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
     pci_dev = do_pci_register_device(pci_dev,
                                      object_get_typename(OBJECT(qdev)),
                                      pci_dev->devfn, errp);
-    if (pci_dev == NULL)
+    if (pci_dev == NULL) {
+        pci_unregister_acpi_index(pci_dev);
         return;
+    }
+
+    if (!pcie_sriov_register_device(pci_dev, errp)) {
+        do_pci_unregister_device(pci_dev);
+        return;
+    }
 
     if (pc->realize) {
         pc->realize(pci_dev, &local_err);
@@ -2338,11 +2349,6 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         }
     }
 
-    if (!pcie_sriov_register_device(pci_dev, errp)) {
-        pci_qdev_unrealize(DEVICE(pci_dev));
-        return;
-    }
-
     /*
      * A PCIe Downstream Port that do not have ARI Forwarding enabled must
      * associate only Device 0 with the device attached to the bus
hw/pci/pcie_sriov.c +0 -6
index c41ac95bee..69930c7b8c 100644
@@ -357,12 +357,6 @@ int16_t pcie_sriov_pf_init_from_user_created_vfs(PCIDevice *dev,
 
 bool pcie_sriov_register_device(PCIDevice *dev, Error **errp)
 {
-    if (!dev->exp.sriov_pf.vf && dev->qdev.id &&
-        pfs && g_hash_table_contains(pfs, dev->qdev.id)) {
-        error_setg(errp, "attaching user-created SR-IOV VF unsupported");
-        return false;
-    }
-
     if (dev->sriov_pf) {
         PCIDevice *pci_pf;
         GPtrArray *pf;
Signature · 1 line
MST
#68 lore raw

Re: [PULL 40/78] pcie_sriov: register user created virtual function before realizing it

Hello Michael,

I think you have queued this patch for pulling on accident. There is a bug here, which is yet to be solved, as you have pointed out in

https://lore.kernel.org/qemu-devel/[email protected]/T/#mca344ac148a62107cdc298e97e6713b6657b5c55

Please, hold off this patch for a while, until I come up with a fix soon.

Thank you,
Daniel

Quoted 218 lines · Michael S. Tsirkin

From: Daniel Paziyski <[email protected]>

There are two ways of creating virtual functions: by using the sriov-pf device parameter (this way, creating a user created VF and binding it to a PF), or by using a device specific parameter, where the device manually creates a given amount of VFs.

When a PCI device is realized, the device specific realize function is called first, and then the pcie_sriov_register_device function is called, which checks whether the device that has been created is a user created VF, and if it is the case and the device allows this kind of VFs, it inserts it into a hashmap, with the key being the ID of the PF, and the items being arrays of VFs.

User created VFs are instantiated independently, and later, when the PF calls pcie_sriov_pf_init_from_user_created_vfs, it discovers its VFs from the hashmap mentioned previously, and sets up in their PCIDevice.ex.sriov_pf structure the pointer to the PF. This means that, during realization, VFs have no access to the PF.

Device created VFs are instantiated during or after PF realization using the pcie_sriov_pf_init function, which sets up for the VFs the pointer to the PF before their realization, so when they're realized, they can access the PF with no issues.

The problem here is that pcie_sriov_register_device is called after the realization, and not before. This means that when a user created VF is created for a device which does not support user created VFs, but supports device created VFs, the realize function will notice that a VF is being created, and may try to access the PF, causing a null pointer dereference fault.

Fix this by placing the user created VF check and registering before the realization. This way, incorrectly created user created VFs will be noticed, and device creation will be aborted.

Additionally, remove from the pcie_sriov_register_device function the top check. It seems that this check is done to error out if the PF failed for some reason to initialize its list of user created VFs. However, in such situations, the pcie_sriov_pf_init_from_user_created_vfs function will error during realization, and it will be caught before the check is done at all. Moreover, since now pcie_sriov_register_device is called before realization, the check will always fail for PFs with user created VFs, because the list will be populated during realization.

The rest of the function though, correctly errors out if a user created VF is created for an unsupported device type, if a VF is created for a non-PCIe device, or if the PF is already instantiated, with now the advantage being that the check is done before the VF instantiation.

Moreover, move the unregistering of a device's acpi-index and the removal of a VF from its PF list from pci_qdev_unrealize to do_pci_unregister_device. We do the former so that the acpi-index is unclaimed on user created VF creation error, and the latter to not have extra code on the device specific realization error path.

When instantiating a user created VF for a NVME controller:

Command line:

    qemu-system-x86_64 -device nvme-subsys,id=subsys0 \
    -device nvme,id=vctrl0,sriov-pf=ctrl0,subsys=subsys0 \
    -device nvme,id=ctrl0,subsys=subsys0,serial=s

ASAN splat:

../hw/nvme/ctrl.c:9613:28: runtime error: member access within null pointer of type 'struct NvmeCtrl' AddressSanitizer:DEADLYSIGNAL
=================================================================
==91050==ERROR: AddressSanitizer: SEGV on unknown address 0x000000001cf0 (pc 0x7f79a8573dcd bp 0x7ffd622c1bc0 sp 0x7ffd622c1b68 T0) ==91050==The signal is caused by a READ memory access.

    #0 0x7f79a8573dcd  (/usr/lib/libc.so.6+0x173dcd) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e)
    #1 0x5644c6c0266e in nvme_realize ../hw/nvme/ctrl.c:9613
    #2 0x5644c6c67213 in pci_qdev_realize ../hw/pci/pci.c:2316
    #3 0x5644c7a2cfc1 in device_set_realized ../hw/core/qdev.c:514
    #4 0x5644c7a4f767 in property_set_bool ../qom/object.c:2484
    #5 0x5644c7a48d0b in object_property_set ../qom/object.c:1548
    #6 0x5644c7a568a5 in object_property_set_qobject ../qom/qom-qobject.c:28
    #7 0x5644c7a49385 in object_property_set_bool ../qom/object.c:1618
    #8 0x5644c7a2aeb0 in qdev_realize ../hw/core/qdev.c:277
    #9 0x5644c735f29f in qdev_device_add_from_qdict ../system/qdev-monitor.c:740
    #10 0x5644c735f3ab in qdev_device_add ../system/qdev-monitor.c:758
    #11 0x5644c72b1901 in device_init_func ../system/vl.c:1217
    #12 0x5644c829d4a3 in qemu_opts_foreach ../util/qemu-option.c:1148
    #13 0x5644c72bc33e in qemu_create_cli_devices ../system/vl.c:2762
    #14 0x5644c72bcaa1 in qmp_x_exit_preconfig ../system/vl.c:2822
    #15 0x5644c72c3241 in qemu_init ../system/vl.c:3862
    #16 0x5644c801abf8 in main ../system/main.c:71
    #17 0x7f79a8427780  (/usr/lib/libc.so.6+0x27780) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e)
    #18 0x7f79a84278b8 in __libc_start_main (/usr/lib/libc.so.6+0x278b8) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e)
    #19 0x5644c5f0a1f4 in _start (BuildId: 8483f952216d9e345c3744300d0aefa38feb50d7)

==91050==Register values:

rax = 0x00007e49988640f0  rbx = 0x00007e49988640f0  rcx = 0x00000fc9b3104828  rdx = 0x0000000000000058
rdi = 0x00007e49988640f0  rsi = 0x0000000000001cf0  rbp = 0x00007ffd622c1bc0  rsp = 0x00007ffd622c1b68
 r8 = 0x00000fc9b3104829   r9 = 0x00000fc9b3104828  r10 = 0x00000fc9b310481e  r11 = 0x00000fc9b310481e
r12 = 0x0000000000001cf0  r13 = 0x00000f6f32e78578  r14 = 0x00000000ffffffff  r15 = 0x00007ffd622c1c10

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/usr/lib/libc.so.6+0x173dcd) (BuildId: 1fa174a830cef40a5b2388add4318ee2795f573e) ==91050==ABORTING

Fixes 19e55471d4e8 ("pcie_sriov: Allow user to create SR-IOV device")

Message-ID: <[email protected]>

---
 hw/pci/pci.c        | 42 ++++++++++++++++++++++++------------------
 hw/pci/pcie_sriov.c |  6 ------
 2 files changed, 24 insertions(+), 24 deletions(-)
hw/pci/pci.c +24 -18
index 0efb4eb4bb..2d2fe02c68 100644
@@ -1227,6 +1227,19 @@ static void pci_config_free(PCIDevice *pci_dev)
     g_free(pci_dev->used);
 }
 
+static void pci_unregister_acpi_index(PCIDevice *pci_dev)
+{
+    if (pci_dev->acpi_index) {
+        GSequence *used_indexes = pci_acpi_index_list();
+
+        g_sequence_remove(g_sequence_lookup(used_indexes,
+                          GINT_TO_POINTER(pci_dev->acpi_index),
+                          g_cmp_uint32, NULL));
+
+        pci_dev->acpi_index = 0;
+    }
+}
+
 static void do_pci_unregister_device(PCIDevice *pci_dev)
 {
     pci_get_bus(pci_dev)->devices[pci_dev->devfn] = NULL;
@@ -1240,6 +1253,9 @@ static void do_pci_unregister_device(PCIDevice *pci_dev)
                                     &pci_dev->bus_master_enable_region);
     }
     address_space_destroy(&pci_dev->bus_master_as);
+
+    pcie_sriov_unregister_device(pci_dev);
+    pci_unregister_acpi_index(pci_dev);
 }
 
 /* Extract PCIReqIDCache into BDF format */
@@ -1491,7 +1507,6 @@ static void pci_qdev_unrealize(DeviceState *dev)
 
     pci_unregister_io_regions(pci_dev);
     pci_del_option_rom(pci_dev);
-    pcie_sriov_unregister_device(pci_dev);
 
     if (pc->exit) {
         pc->exit(pci_dev);
@@ -1501,17 +1516,6 @@ static void pci_qdev_unrealize(DeviceState *dev)
     do_pci_unregister_device(pci_dev);
 
     pci_dev->msi_trigger = NULL;
-
-    /*
-     * clean up acpi-index so it could reused by another device
-     */
-    if (pci_dev->acpi_index) {
-        GSequence *used_indexes = pci_acpi_index_list();
-
-        g_sequence_remove(g_sequence_lookup(used_indexes,
-                          GINT_TO_POINTER(pci_dev->acpi_index),
-                          g_cmp_uint32, NULL));
-    }
 }
 
 void pci_register_bar(PCIDevice *pci_dev, int region_num,
@@ -2326,8 +2330,15 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
     pci_dev = do_pci_register_device(pci_dev,
                                      object_get_typename(OBJECT(qdev)),
                                      pci_dev->devfn, errp);
-    if (pci_dev == NULL)
+    if (pci_dev == NULL) {
+        pci_unregister_acpi_index(pci_dev);
         return;
+    }
+
+    if (!pcie_sriov_register_device(pci_dev, errp)) {
+        do_pci_unregister_device(pci_dev);
+        return;
+    }
 
     if (pc->realize) {
         pc->realize(pci_dev, &local_err);
@@ -2338,11 +2349,6 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         }
     }
 
-    if (!pcie_sriov_register_device(pci_dev, errp)) {
-        pci_qdev_unrealize(DEVICE(pci_dev));
-        return;
-    }
-
     /*
      * A PCIe Downstream Port that do not have ARI Forwarding enabled must
      * associate only Device 0 with the device attached to the bus
hw/pci/pcie_sriov.c +0 -7
index c41ac95bee..69930c7b8c 100644
@@ -357,12 +357,6 @@ int16_t pcie_sriov_pf_init_from_user_created_vfs(PCIDevice *dev,
 
 bool pcie_sriov_register_device(PCIDevice *dev, Error **errp)
 {
-    if (!dev->exp.sriov_pf.vf && dev->qdev.id &&
-        pfs && g_hash_table_contains(pfs, dev->qdev.id)) {
-        error_setg(errp, "attaching user-created SR-IOV VF unsupported");
-        return false;
-    }
-
     if (dev->sriov_pf) {
         PCIDevice *pci_pf;
         GPtrArray *pf;
--

MST

#69 lore raw

Re: [PULL 40/78] pcie_sriov: register user created virtual function before realizing it

Quoted 8 lines · Daniel Paziyski

Hello Michael,

I think you have queued this patch for pulling on accident. There is a bug here, which is yet to be solved, as you have pointed out in

https://lore.kernel.org/qemu-devel/[email protected]/T/#mca344ac148a62107cdc298e97e6713b6657b5c55

Please, hold off this patch for a while, until I come up with a fix soon.

Am I right this buggy version of the patch has just landed in the master branch, instead of the fixed v2 one?

Here's the difference between what's in current master and the v2:

hw/pci/pci.c +7 -4
index d4187293b1f..6242204a52c 100644
@@ -2310,10 +2310,8 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         pci_dev->cap_present |= QEMU_PCIE_CAP_CXL;
     }
 
-    pci_dev = do_pci_register_device(pci_dev,
-                                     object_get_typename(OBJECT(qdev)),
-                                     pci_dev->devfn, errp);
-    if (pci_dev == NULL) {
+    if (!do_pci_register_device(pci_dev, object_get_typename(OBJECT(qdev)),
+                                pci_dev->devfn, errp)) {
         pci_unregister_acpi_index(pci_dev);
         return;
     }
@@ -2332,6 +2330,11 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         }
     }
 
+    if (!pcie_sriov_check_after_realize(pci_dev, errp)) {
+        pci_qdev_unrealize(DEVICE(pci_dev));
+        return;
+    }
+
     /*
      * A PCIe Downstream Port that do not have ARI Forwarding enabled must
      * associate only Device 0 with the device attached to the bus

~

Thanks,

/mjt

#70 lore raw

Re: [PULL 40/78] pcie_sriov: register user created virtual function before realizing it

Quoted 33 lines · Michael Tokarev
Quoted 8 lines · Daniel Paziyski

Hello Michael,

I think you have queued this patch for pulling on accident. There is a bug here, which is yet to be solved, as you have pointed out in

https://lore.kernel.org/qemu-devel/[email protected]/T/#mca344ac148a62107cdc298e97e6713b6657b5c55

Please, hold off this patch for a while, until I come up with a fix soon.

Am I right this buggy version of the patch has just landed in the master branch, instead of the fixed v2 one?

Here's the difference between what's in current master and the v2:

hw/pci/pci.c +2 -4
index d4187293b1f..6242204a52c 100644
@@ -2310,10 +2310,8 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         pci_dev->cap_present |= QEMU_PCIE_CAP_CXL;
     }
 
-    pci_dev = do_pci_register_device(pci_dev,
-                                     object_get_typename(OBJECT(qdev)),
-                                     pci_dev->devfn, errp);
-    if (pci_dev == NULL) {
+    if (!do_pci_register_device(pci_dev, object_get_typename(OBJECT(qdev)),
+                                pci_dev->devfn, errp)) {
         pci_unregister_acpi_index(pci_dev);
         return;
     }
@@ -2332,6 +2330,11 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         }

hw/pci/pci.c static void pci_qdev_realize(DeviceState *qdev, Error **errp) · line 2340

Quoted 10 lines
hw/pci/pci.c +5 -0
     }
 
+    if (!pcie_sriov_check_after_realize(pci_dev, errp)) {
+        pci_qdev_unrealize(DEVICE(pci_dev));
+        return;
+    }
+
     /*
      * A PCIe Downstream Port that do not have ARI Forwarding enabled must
      * associate only Device 0 with the device attached to the bus

Yes sorry, pls post a fix as a patch.

Quoted 5 lines

~

Thanks,

/mjt

#71 lore raw

Re: [PULL 40/78] pcie_sriov: register user created virtual function before realizing it

Quoted 37 lines · Michael S. Tsirkin
Quoted 35 lines · Michael Tokarev
Quoted 8 lines · Daniel Paziyski

Hello Michael,

I think you have queued this patch for pulling on accident. There is a bug here, which is yet to be solved, as you have pointed out in

https://lore.kernel.org/qemu-devel/[email protected]/T/#mca344ac148a62107cdc298e97e6713b6657b5c55

Please, hold off this patch for a while, until I come up with a fix soon.

Am I right this buggy version of the patch has just landed in the master branch, instead of the fixed v2 one?

Here's the difference between what's in current master and the v2:

hw/pci/pci.c +2 -4
index d4187293b1f..6242204a52c 100644
@@ -2310,10 +2310,8 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         pci_dev->cap_present |= QEMU_PCIE_CAP_CXL;
     }
 
-    pci_dev = do_pci_register_device(pci_dev,
-                                     object_get_typename(OBJECT(qdev)),
-                                     pci_dev->devfn, errp);
-    if (pci_dev == NULL) {
+    if (!do_pci_register_device(pci_dev, object_get_typename(OBJECT(qdev)),
+                                pci_dev->devfn, errp)) {
         pci_unregister_acpi_index(pci_dev);
         return;
     }
@@ -2332,6 +2330,11 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         }
     }
 
Quoted 12 lines

hw/pci/pci.c static void pci_qdev_realize(DeviceState *qdev, Error **errp) · line 2340

Quoted 8 lines
hw/pci/pci.c +5 -0
+    if (!pcie_sriov_check_after_realize(pci_dev, errp)) {
+        pci_qdev_unrealize(DEVICE(pci_dev));
+        return;
+    }
+
     /*
      * A PCIe Downstream Port that do not have ARI Forwarding enabled must
      * associate only Device 0 with the device attached to the bus

Yes sorry, pls post a fix as a patch.

If you are referring to me, do you want me to make a v3 fixing master, or will you revert
the patch in master and include the v2 (which is already in the lists) when you request pull?

fwiw, here's the link to v2:
https://lore.kernel.org/qemu-devel/[email protected]/

Daniel

Quoted 6 lines
Quoted 5 lines

~

Thanks,

/mjt

#72 lore raw

Re: [PULL 40/78] pcie_sriov: register user created virtual function before realizing it

Quoted 52 lines · Daniel Paziyski
Quoted 37 lines · Michael S. Tsirkin
Quoted 35 lines · Michael Tokarev
Quoted 8 lines · Daniel Paziyski

Hello Michael,

I think you have queued this patch for pulling on accident. There is a bug here, which is yet to be solved, as you have pointed out in

https://lore.kernel.org/qemu-devel/[email protected]/T/#mca344ac148a62107cdc298e97e6713b6657b5c55

Please, hold off this patch for a while, until I come up with a fix soon.

Am I right this buggy version of the patch has just landed in the master branch, instead of the fixed v2 one?

Here's the difference between what's in current master and the v2:

hw/pci/pci.c +2 -4
index d4187293b1f..6242204a52c 100644
@@ -2310,10 +2310,8 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         pci_dev->cap_present |= QEMU_PCIE_CAP_CXL;
     }
 
-    pci_dev = do_pci_register_device(pci_dev,
-                                     object_get_typename(OBJECT(qdev)),
-                                     pci_dev->devfn, errp);
-    if (pci_dev == NULL) {
+    if (!do_pci_register_device(pci_dev, object_get_typename(OBJECT(qdev)),
+                                pci_dev->devfn, errp)) {
         pci_unregister_acpi_index(pci_dev);
         return;
     }
@@ -2332,6 +2330,11 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp)
         }
     }
 
Quoted 12 lines

hw/pci/pci.c static void pci_qdev_realize(DeviceState *qdev, Error **errp) · line 2340

Quoted 8 lines
hw/pci/pci.c +5 -0
+    if (!pcie_sriov_check_after_realize(pci_dev, errp)) {
+        pci_qdev_unrealize(DEVICE(pci_dev));
+        return;
+    }
+
     /*
      * A PCIe Downstream Port that do not have ARI Forwarding enabled must
      * associate only Device 0 with the device attached to the bus

Yes sorry, pls post a fix as a patch.

Quoted 4 lines

If you are referring to me, do you want me to make a v3 fixing master, or will you revert
the patch in master and include the v2 (which is already in the lists) when you request pull?

Please send a patch which takes current master to the state it should be in. We usually don't do revert-and-reapply unless it's a big obvious failure where we're not yet sure of the fix.

(Coverity points out part of this error as CID 1689947, calling pci_unregister_acpi_index() on a NULL pointer).

thanks
-- PMM

#73 lore raw

[PULL 49/78] util/event_notifier: more strict API

In reply to Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We have .initialized in EventNotifier to track was it actually initialized, and we have a comment that @fd passed to event_notifier_init_fd() must be an eventfd object.

Still, there are two specific users, which ignore these things, and use -1 as specific value storing in notifier, to mark it "uninitialized".

Let's make strict API:

- store only valid FDs in initialized notifier
- _set() asserts that passed fd is valid
- _get() asserts that notifier is initialized (actually, except the two specific cases we rework, all other callers are not prepared to notifier being uninitialized when _get() is called. In this case _get() returns 0 (which is a valid fd), and caller will mistakenly use it, which is worse than abort())
- for two specific cases, implement additional getter for .initialized field itself
- zeroed notifier is uninitialized(.initialized is false)

Message-ID: <[email protected]>

---
 include/qemu/event_notifier.h      |  1 +
 hw/remote/iohub.c                  | 11 +++++-----
 hw/virtio/vhost-shadow-virtqueue.c | 32 +++++++++++++++++-------------
 util/event_notifier-posix.c        | 10 ++++++++++
 4 files changed, 35 insertions(+), 19 deletions(-)
include/qemu/event_notifier.h +1 -0
index 8a4ff308e1..820efe7722 100644
@@ -39,6 +39,7 @@ int event_notifier_test_and_clear(EventNotifier *);
 void event_notifier_init_fd(EventNotifier *, int fd);
 int event_notifier_get_fd(const EventNotifier *);
 int event_notifier_get_wfd(const EventNotifier *);
+bool event_notifier_initialized(const EventNotifier *e);
 #else
 HANDLE event_notifier_get_handle(EventNotifier *);
 #endif
hw/remote/iohub.c +6 -5
index 988d3285cc..ab698d9f03 100644
@@ -28,8 +28,6 @@ void remote_iohub_init(RemoteIOHubState *iohub)
     for (pirq = 0; pirq < REMOTE_IOHUB_NB_PIRQS; pirq++) {
         qemu_mutex_init(&iohub->irq_level_lock[pirq]);
         iohub->irq_level[pirq] = 0;
-        event_notifier_init_fd(&iohub->irqfds[pirq], -1);
-        event_notifier_init_fd(&iohub->resamplefds[pirq], -1);
     }
 }
 
@@ -85,9 +83,12 @@ void process_set_irqfd_msg(PCIDevice *pci_dev, MPQemuMsg *msg)
 
     pirq = remote_iohub_map_irq(pci_dev, intx);
 
-    if (event_notifier_get_fd(&iohub->irqfds[pirq]) != -1) {
-        qemu_set_fd_handler(event_notifier_get_fd(&iohub->resamplefds[pirq]),
-                            NULL, NULL, NULL);
+    if (event_notifier_initialized(&iohub->irqfds[pirq])) {
+        if (event_notifier_initialized(&iohub->resamplefds[pirq])) {
+            qemu_set_fd_handler(
+                event_notifier_get_fd(&iohub->resamplefds[pirq]),
+                NULL, NULL, NULL);
+        }
         event_notifier_cleanup(&iohub->irqfds[pirq]);
         event_notifier_cleanup(&iohub->resamplefds[pirq]);
         memset(&iohub->token[pirq], 0, sizeof(ResampleToken));
hw/virtio/vhost-shadow-virtqueue.c +18 -14
index 0fae35ba1a..c97bd9f93f 100644
@@ -751,6 +751,15 @@ static void vhost_svq_handle_call(EventNotifier *n)
     vhost_svq_flush(svq, true);
 }
 
+static void event_notifier_set_or_zero(EventNotifier *e, int fd)
+{
+    if (fd == VHOST_FILE_UNBIND) {
+        memset(e, 0, sizeof(*e));
+    } else {
+        event_notifier_init_fd(e, fd);
+    }
+}
+
 /**
  * Set the call notifier for the SVQ to call the guest
  *
@@ -761,17 +770,13 @@ static void vhost_svq_handle_call(EventNotifier *n)
  */
 void vhost_svq_set_svq_call_fd(VhostShadowVirtqueue *svq, int call_fd)
 {
-    if (call_fd == VHOST_FILE_UNBIND) {
-        /*
-         * Fail event_notifier_set if called handling device call.
-         *
-         * SVQ still needs device notifications, since it needs to keep
-         * forwarding used buffers even with the unbind.
-         */
-        memset(&svq->svq_call, 0, sizeof(svq->svq_call));
-    } else {
-        event_notifier_init_fd(&svq->svq_call, call_fd);
-    }
+    /*
+     * Fail event_notifier_set if called handling device call.
+     *
+     * SVQ still needs device notifications, since it needs to keep
+     * forwarding used buffers even with the unbind.
+     */
+    event_notifier_set_or_zero(&svq->svq_call, call_fd);
 }
 
 /**
@@ -814,14 +819,14 @@ size_t vhost_svq_device_area_size(const VhostShadowVirtqueue *svq)
 void vhost_svq_set_svq_kick_fd(VhostShadowVirtqueue *svq, int svq_kick_fd)
 {
     EventNotifier *svq_kick = &svq->svq_kick;
-    bool poll_stop = VHOST_FILE_UNBIND != event_notifier_get_fd(svq_kick);
+    bool poll_stop = event_notifier_initialized(svq_kick);
     bool poll_start = svq_kick_fd != VHOST_FILE_UNBIND;
 
     if (poll_stop) {
         event_notifier_set_handler(svq_kick, NULL);
     }
 
-    event_notifier_init_fd(svq_kick, svq_kick_fd);
+    event_notifier_set_or_zero(&svq->svq_kick, svq_kick_fd);
     /*
      * event_notifier_set_handler already checks for guest's notifications if
      * they arrive at the new file descriptor in the switch, so there is no
@@ -928,7 +933,6 @@ VhostShadowVirtqueue *vhost_svq_new(const VhostShadowVirtqueueOps *ops,
 {
     VhostShadowVirtqueue *svq = g_new0(VhostShadowVirtqueue, 1);
 
-    event_notifier_init_fd(&svq->svq_kick, VHOST_FILE_UNBIND);
     svq->ops = ops;
     svq->ops_opaque = ops_opaque;
     return svq;
util/event_notifier-posix.c +10 -0
index 83fdbb96bb..f595945af7 100644
@@ -27,6 +27,7 @@
  */
 void event_notifier_init_fd(EventNotifier *e, int fd)
 {
+    assert(fd >= 0);
     e->rfd = fd;
     e->wfd = fd;
     e->initialized = true;
@@ -96,14 +97,23 @@ void event_notifier_cleanup(EventNotifier *e)
 
 int event_notifier_get_fd(const EventNotifier *e)
 {
+    assert(e->initialized);
+    assert(e->rfd >= 0);
     return e->rfd;
 }
 
 int event_notifier_get_wfd(const EventNotifier *e)
 {
+    assert(e->initialized);
+    assert(e->wfd >= 0);
     return e->wfd;
 }
 
+bool event_notifier_initialized(const EventNotifier *e)
+{
+    return e->initialized;
+}
+
 int event_notifier_set(EventNotifier *e)
 {
     static const uint64_t value = 1;
Signature · 1 line
MST
#74 lore raw

Re: [PULL 49/78] util/event_notifier: more strict API

Quoted 12 lines · Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We have .initialized in EventNotifier to track was it actually initialized, and we have a comment that @fd passed to event_notifier_init_fd() must be an eventfd object.

Still, there are two specific users, which ignore these things, and use -1 as specific value storing in notifier, to mark it "uninitialized".

Let's make strict API:

Quoted 7 lines

- store only valid FDs in initialized notifier
- _set() asserts that passed fd is valid
- _get() asserts that notifier is initialized (actually, except the two specific cases we rework, all other callers are not prepared to notifier being uninitialized when _get() is called. In this case _get() returns 0 (which is a valid fd), and caller will mistakenly use it, which is worse than abort())

Well, there are other cases that would require rework not addressed here, as this commit breaks (at least) vhost-vsock-ccw and virtio-net-ccw (for vhost=on) on an s390x host. I'll look into that, but was not prepared for such a late surprise.

Quoted 162 lines

- for two specific cases, implement additional getter for .initialized field itself
- zeroed notifier is uninitialized(.initialized is false)

Message-ID: <[email protected]>

---
  include/qemu/event_notifier.h      |  1 +
  hw/remote/iohub.c                  | 11 +++++-----
  hw/virtio/vhost-shadow-virtqueue.c | 32 +++++++++++++++++-------------
  util/event_notifier-posix.c        | 10 ++++++++++
  4 files changed, 35 insertions(+), 19 deletions(-)
include/qemu/event_notifier.h +1 -0
index 8a4ff308e1..820efe7722 100644
@@ -39,6 +39,7 @@ int event_notifier_test_and_clear(EventNotifier *);
  void event_notifier_init_fd(EventNotifier *, int fd);
  int event_notifier_get_fd(const EventNotifier *);
  int event_notifier_get_wfd(const EventNotifier *);
+bool event_notifier_initialized(const EventNotifier *e);
  #else
  HANDLE event_notifier_get_handle(EventNotifier *);
  #endif
hw/remote/iohub.c +6 -5
index 988d3285cc..ab698d9f03 100644
@@ -28,8 +28,6 @@ void remote_iohub_init(RemoteIOHubState *iohub)
      for (pirq = 0; pirq < REMOTE_IOHUB_NB_PIRQS; pirq++) {
          qemu_mutex_init(&iohub->irq_level_lock[pirq]);
          iohub->irq_level[pirq] = 0;
-        event_notifier_init_fd(&iohub->irqfds[pirq], -1);
-        event_notifier_init_fd(&iohub->resamplefds[pirq], -1);
      }
  }
 
@@ -85,9 +83,12 @@ void process_set_irqfd_msg(PCIDevice *pci_dev, MPQemuMsg *msg)
 
      pirq = remote_iohub_map_irq(pci_dev, intx);
 
-    if (event_notifier_get_fd(&iohub->irqfds[pirq]) != -1) {
-        qemu_set_fd_handler(event_notifier_get_fd(&iohub->resamplefds[pirq]),
-                            NULL, NULL, NULL);
+    if (event_notifier_initialized(&iohub->irqfds[pirq])) {
+        if (event_notifier_initialized(&iohub->resamplefds[pirq])) {
+            qemu_set_fd_handler(
+                event_notifier_get_fd(&iohub->resamplefds[pirq]),
+                NULL, NULL, NULL);
+        }
          event_notifier_cleanup(&iohub->irqfds[pirq]);
          event_notifier_cleanup(&iohub->resamplefds[pirq]);
          memset(&iohub->token[pirq], 0, sizeof(ResampleToken));
hw/virtio/vhost-shadow-virtqueue.c +18 -14
index 0fae35ba1a..c97bd9f93f 100644
@@ -751,6 +751,15 @@ static void vhost_svq_handle_call(EventNotifier *n)
      vhost_svq_flush(svq, true);
  }
 
+static void event_notifier_set_or_zero(EventNotifier *e, int fd)
+{
+    if (fd == VHOST_FILE_UNBIND) {
+        memset(e, 0, sizeof(*e));
+    } else {
+        event_notifier_init_fd(e, fd);
+    }
+}
+
  /**
   * Set the call notifier for the SVQ to call the guest
   *
@@ -761,17 +770,13 @@ static void vhost_svq_handle_call(EventNotifier *n)
   */
  void vhost_svq_set_svq_call_fd(VhostShadowVirtqueue *svq, int call_fd)
  {
-    if (call_fd == VHOST_FILE_UNBIND) {
-        /*
-         * Fail event_notifier_set if called handling device call.
-         *
-         * SVQ still needs device notifications, since it needs to keep
-         * forwarding used buffers even with the unbind.
-         */
-        memset(&svq->svq_call, 0, sizeof(svq->svq_call));
-    } else {
-        event_notifier_init_fd(&svq->svq_call, call_fd);
-    }
+    /*
+     * Fail event_notifier_set if called handling device call.
+     *
+     * SVQ still needs device notifications, since it needs to keep
+     * forwarding used buffers even with the unbind.
+     */
+    event_notifier_set_or_zero(&svq->svq_call, call_fd);
  }
 
  /**
@@ -814,14 +819,14 @@ size_t vhost_svq_device_area_size(const VhostShadowVirtqueue *svq)
  void vhost_svq_set_svq_kick_fd(VhostShadowVirtqueue *svq, int svq_kick_fd)
  {
      EventNotifier *svq_kick = &svq->svq_kick;
-    bool poll_stop = VHOST_FILE_UNBIND != event_notifier_get_fd(svq_kick);
+    bool poll_stop = event_notifier_initialized(svq_kick);
      bool poll_start = svq_kick_fd != VHOST_FILE_UNBIND;
 
      if (poll_stop) {
          event_notifier_set_handler(svq_kick, NULL);
      }
 
-    event_notifier_init_fd(svq_kick, svq_kick_fd);
+    event_notifier_set_or_zero(&svq->svq_kick, svq_kick_fd);
      /*
       * event_notifier_set_handler already checks for guest's notifications if
       * they arrive at the new file descriptor in the switch, so there is no
@@ -928,7 +933,6 @@ VhostShadowVirtqueue *vhost_svq_new(const VhostShadowVirtqueueOps *ops,
  {
      VhostShadowVirtqueue *svq = g_new0(VhostShadowVirtqueue, 1);
 
-    event_notifier_init_fd(&svq->svq_kick, VHOST_FILE_UNBIND);
      svq->ops = ops;
      svq->ops_opaque = ops_opaque;
      return svq;
util/event_notifier-posix.c +10 -0
index 83fdbb96bb..f595945af7 100644
@@ -27,6 +27,7 @@
   */
  void event_notifier_init_fd(EventNotifier *e, int fd)
  {
+    assert(fd >= 0);
      e->rfd = fd;
      e->wfd = fd;
      e->initialized = true;
@@ -96,14 +97,23 @@ void event_notifier_cleanup(EventNotifier *e)
 
  int event_notifier_get_fd(const EventNotifier *e)
  {
+    assert(e->initialized);
+    assert(e->rfd >= 0);
      return e->rfd;
  }
 
  int event_notifier_get_wfd(const EventNotifier *e)
  {
+    assert(e->initialized);
+    assert(e->wfd >= 0);
      return e->wfd;
  }
 
+bool event_notifier_initialized(const EventNotifier *e)
+{
+    return e->initialized;
+}
+
  int event_notifier_set(EventNotifier *e)
  {
      static const uint64_t value = 1;
#75 lore raw

Re: [PULL 49/78] util/event_notifier: more strict API

Quoted 23 lines · Eric Farman
Quoted 12 lines · Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We have .initialized in EventNotifier to track was it actually initialized, and we have a comment that @fd passed to event_notifier_init_fd() must be an eventfd object.

Still, there are two specific users, which ignore these things, and use -1 as specific value storing in notifier, to mark it "uninitialized".

Let's make strict API:

Quoted 7 lines

- store only valid FDs in initialized notifier
- _set() asserts that passed fd is valid
- _get() asserts that notifier is initialized (actually, except the two specific cases we rework, all other callers are not prepared to notifier being uninitialized when _get() is called. In this case _get() returns 0 (which is a valid fd), and caller will mistakenly use it, which is worse than abort())

Quoted 1 line

Well, there are other cases that would require rework not addressed here, as this commit breaks (at least) vhost-vsock-ccw and virtio-net- ccw (for vhost=on) on an s390x host. I'll look into that, but was not prepared for such a late surprise.

I sent a fix [0] for the assertion that shows up with this particular patch in some of the common configurations, but it still leaves VDPA broken on s390x (which was also working before this commit). So, not a good idea.

I just posted a variation [1] of that which restores VDPA functionality. Would that be doable? Or can we get the assertions here removed until we can adhere to the API restriction?

[0] https://lore.kernel.org/qemu-devel/[email protected]/
[1] https://lore.kernel.org/qemu-devel/[email protected]/

Quoted 158 lines
Quoted 157 lines

- for two specific cases, implement additional getter for .initialized field itself
- zeroed notifier is uninitialized(.initialized is false)

Message-ID: <[email protected]>
---
  include/qemu/event_notifier.h      |  1 + hw/remote/iohub.c                  | 11 +++++----- hw/virtio/vhost-shadow-virtqueue.c | 32 +++++++++++++++++------------- util/event_notifier-posix.c        | 10 ++++++++++

  4 files changed, 35 insertions(+), 19 deletions(-)
include/qemu/event_notifier.h +1 -0
index 8a4ff308e1..820efe7722 100644
@@ -39,6 +39,7 @@ int event_notifier_test_and_clear(EventNotifier *);
   void event_notifier_init_fd(EventNotifier *, int fd);
   int event_notifier_get_fd(const EventNotifier *);
   int event_notifier_get_wfd(const EventNotifier *);
+bool event_notifier_initialized(const EventNotifier *e);
   #else
   HANDLE event_notifier_get_handle(EventNotifier *);
   #endif
hw/remote/iohub.c +6 -5
index 988d3285cc..ab698d9f03 100644
@@ -28,8 +28,6 @@ void remote_iohub_init(RemoteIOHubState *iohub)
       for (pirq = 0; pirq < REMOTE_IOHUB_NB_PIRQS; pirq++) {
           qemu_mutex_init(&iohub->irq_level_lock[pirq]);
           iohub->irq_level[pirq] = 0;
-        event_notifier_init_fd(&iohub->irqfds[pirq], -1);
-        event_notifier_init_fd(&iohub->resamplefds[pirq], -1);
       }
   }
@@ -85,9 +83,12 @@ void process_set_irqfd_msg(PCIDevice *pci_dev, 
 MPQemuMsg *msg)
       pirq = remote_iohub_map_irq(pci_dev, intx);
-    if (event_notifier_get_fd(&iohub->irqfds[pirq]) != -1) {
-        qemu_set_fd_handler(event_notifier_get_fd(&iohub- 
 >resamplefds[pirq]),
-                            NULL, NULL, NULL);
+    if (event_notifier_initialized(&iohub->irqfds[pirq])) {
+        if (event_notifier_initialized(&iohub->resamplefds[pirq])) {
+            qemu_set_fd_handler(
+                event_notifier_get_fd(&iohub->resamplefds[pirq]),
+                NULL, NULL, NULL);
+        }
           event_notifier_cleanup(&iohub->irqfds[pirq]);
           event_notifier_cleanup(&iohub->resamplefds[pirq]);
           memset(&iohub->token[pirq], 0, sizeof(ResampleToken));
hw/virtio/vhost-shadow-virtqueue.c +18 -14
index 0fae35ba1a..c97bd9f93f 100644
@@ -751,6 +751,15 @@ static void vhost_svq_handle_call(EventNotifier *n)
       vhost_svq_flush(svq, true);
   }
+static void event_notifier_set_or_zero(EventNotifier *e, int fd)
+{
+    if (fd == VHOST_FILE_UNBIND) {
+        memset(e, 0, sizeof(*e));
+    } else {
+        event_notifier_init_fd(e, fd);
+    }
+}
+
   /**
    * Set the call notifier for the SVQ to call the guest
    *
@@ -761,17 +770,13 @@ static void vhost_svq_handle_call(EventNotifier *n)
    */
   void vhost_svq_set_svq_call_fd(VhostShadowVirtqueue *svq, int call_fd)
   {
-    if (call_fd == VHOST_FILE_UNBIND) {
-        /*
-         * Fail event_notifier_set if called handling device call.
-         *
-         * SVQ still needs device notifications, since it needs to keep
-         * forwarding used buffers even with the unbind.
-         */
-        memset(&svq->svq_call, 0, sizeof(svq->svq_call));
-    } else {
-        event_notifier_init_fd(&svq->svq_call, call_fd);
-    }
+    /*
+     * Fail event_notifier_set if called handling device call.
+     *
+     * SVQ still needs device notifications, since it needs to keep
+     * forwarding used buffers even with the unbind.
+     */
+    event_notifier_set_or_zero(&svq->svq_call, call_fd);
   }
   /**
@@ -814,14 +819,14 @@ size_t vhost_svq_device_area_size(const 
 VhostShadowVirtqueue *svq)
   void vhost_svq_set_svq_kick_fd(VhostShadowVirtqueue *svq, int svq_kick_fd)
   {
       EventNotifier *svq_kick = &svq->svq_kick;
-    bool poll_stop = VHOST_FILE_UNBIND != 
 event_notifier_get_fd(svq_kick);
+    bool poll_stop = event_notifier_initialized(svq_kick);
       bool poll_start = svq_kick_fd != VHOST_FILE_UNBIND;
       if (poll_stop) {
           event_notifier_set_handler(svq_kick, NULL);
       }
-    event_notifier_init_fd(svq_kick, svq_kick_fd);
+    event_notifier_set_or_zero(&svq->svq_kick, svq_kick_fd);
       /*
        * event_notifier_set_handler already checks for guest's notifications if
        * they arrive at the new file descriptor in the switch, so there is no
@@ -928,7 +933,6 @@ VhostShadowVirtqueue *vhost_svq_new(const 
 VhostShadowVirtqueueOps *ops,
   {
       VhostShadowVirtqueue *svq = g_new0(VhostShadowVirtqueue, 1);
-    event_notifier_init_fd(&svq->svq_kick, VHOST_FILE_UNBIND);
       svq->ops = ops;
       svq->ops_opaque = ops_opaque;
       return svq;
util/event_notifier-posix.c +10 -0
index 83fdbb96bb..f595945af7 100644
@@ -27,6 +27,7 @@
    */
   void event_notifier_init_fd(EventNotifier *e, int fd)
   {
+    assert(fd >= 0);
       e->rfd = fd;
       e->wfd = fd;
       e->initialized = true;
@@ -96,14 +97,23 @@ void event_notifier_cleanup(EventNotifier *e)
   int event_notifier_get_fd(const EventNotifier *e)
   {
+    assert(e->initialized);
+    assert(e->rfd >= 0);
       return e->rfd;
   }
   int event_notifier_get_wfd(const EventNotifier *e)
   {
+    assert(e->initialized);
+    assert(e->wfd >= 0);
       return e->wfd;
   }
+bool event_notifier_initialized(const EventNotifier *e)
+{
+    return e->initialized;
+}
+
   int event_notifier_set(EventNotifier *e)
   {
       static const uint64_t value = 1;
#76 lore raw

Re: [PULL 49/78] util/event_notifier: more strict API

Quoted 32 lines · Eric Farman
Quoted 23 lines · Eric Farman
Quoted 12 lines · Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We have .initialized in EventNotifier to track was it actually initialized, and we have a comment that @fd passed to event_notifier_init_fd() must be an eventfd object.

Still, there are two specific users, which ignore these things, and use -1 as specific value storing in notifier, to mark it "uninitialized".

Let's make strict API:

Quoted 7 lines

- store only valid FDs in initialized notifier
- _set() asserts that passed fd is valid
- _get() asserts that notifier is initialized (actually, except the two specific cases we rework, all other callers are not prepared to notifier being uninitialized when _get() is called. In this case _get() returns 0 (which is a valid fd), and caller will mistakenly use it, which is worse than abort())

Quoted 4 lines

Well, there are other cases that would require rework not addressed here, as this commit breaks (at least) vhost-vsock-ccw and virtio-net- ccw (for vhost=on) on an s390x host. I'll look into that, but was not prepared for such a late surprise.

Quoted 8 lines

I sent a fix [0] for the assertion that shows up with this particular patch in some of the common configurations, but it still leaves VDPA broken on s390x (which was also working before this commit). So, not a good idea.

I just posted a variation [1] of that which restores VDPA functionality. Would that be doable? Or can we get the assertions here removed until we can adhere to the API restriction?

If this change regressed something, and we don't have an immediately obvious fix for that, we should probably revert this change. Can we do that without too much trouble?

thanks
-- PMM

#77 lore raw

Re: [PULL 49/78] util/event_notifier: more strict API

Quoted 38 lines · Peter Maydell
Quoted 37 lines · Eric Farman
Quoted 23 lines · Eric Farman
Quoted 12 lines · Michael S. Tsirkin

From: Vladimir Sementsov-Ogievskiy <[email protected]>

We have .initialized in EventNotifier to track was it actually initialized, and we have a comment that @fd passed to event_notifier_init_fd() must be an eventfd object.

Still, there are two specific users, which ignore these things, and use -1 as specific value storing in notifier, to mark it "uninitialized".

Let's make strict API:

Quoted 7 lines

- store only valid FDs in initialized notifier
- _set() asserts that passed fd is valid
- _get() asserts that notifier is initialized (actually, except the two specific cases we rework, all other callers are not prepared to notifier being uninitialized when _get() is called. In this case _get() returns 0 (which is a valid fd), and caller will mistakenly use it, which is worse than abort())

Quoted 4 lines

Well, there are other cases that would require rework not addressed here, as this commit breaks (at least) vhost-vsock-ccw and virtio-net- ccw (for vhost=on) on an s390x host. I'll look into that, but was not prepared for such a late surprise.

I sent a fix [0] for the assertion that shows up with this particular patch in some of the common configurations, but it still leaves VDPA broken on s390x (which was also working before this commit). So, not a good idea.

Quoted 7 lines
Quoted 3 lines

I just posted a variation [1] of that which restores VDPA functionality. Would that be doable? Or can we get the assertions here removed until we can adhere to the API restriction?

If this change regressed something, and we don't have an immediately obvious fix for that, we should probably revert this change. Can we do that without too much trouble?

I don't know about the other parts of this commit that were changed in advance of the API change performed here, but from my chair it's fine to either revert this commit or remove the assertions that it added. I'll send a patch for the latter, in case that's preferred.

Thanks,
Eric

#78 lore raw

[PULL 30/78] tests/functional: add vhost-vsock CPR test with a live guest

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

The qtests only cover the QEMU side of the vhost-vsock handoff. Add a functional test which runs an actual host<->guest vsock transfer across a series of CPR migrations, for both cpr-transfer and cpr-exec:

- the guest is booted on top of a qcow2 overlay shared by all the QEMU

    instances it goes through;

- the host sends a 512M file of random data to the guest with socat, where it's stored on disk, then gets it back the same way; a CPR migration is performed every 0.2s while a transfer is in progress;

- with cpr-transfer each migration starts a fresh target QEMU and retires the source; with cpr-exec the same process is re-exec'ed over and over;

- the file received back must be identical to the one sent, and both socat instances in the guest must exit cleanly.

The test needs KVM, socat and /dev/vhost-vsock on the host, and a kernel supporting VHOST_RESET_OWNER for vhost-vsock. If any of those requirements are missing the test is skipped.

Message-ID: <[email protected]>

---
 tests/functional/x86_64/meson.build           |   2 +
 .../functional/x86_64/test_vhost_vsock_cpr.py | 245 ++++++++++++++++++
 2 files changed, 247 insertions(+)
 create mode 100755 tests/functional/x86_64/test_vhost_vsock_cpr.py
tests/functional/x86_64/meson.build +2 -0
index 6254149239..bcdb1ee558 100644
@@ -7,6 +7,7 @@ test_x86_64_timeouts = {
   'netdev_ethtool' : 180,
   'replay' : 480,
   'virtio_balloon': 120,
+  'vhost_vsock_cpr': 300,
 }
 
 tests_x86_64_system_quick = [
@@ -52,4 +53,5 @@ tests_x86_64_system_thorough = [
   'virtio_gpu',
   'rebuild_vmfd',
   'tap_migration',
+  'vhost_vsock_cpr',
 ]
tests/functional/x86_64/test_vhost_vsock_cpr.py new file +245 -0

245 lines not on this page · raw message

Signature · 1 line
MST
#79 lore raw

[PULL 45/78] vdpa-dev: implement guest_notifier_mask/pending

In reply to Michael S. Tsirkin

From: Li Zhaoxin <[email protected]>

Forward guest notifier masking to the vhost layer via VHOST_SET_VRING_CALL, so the KVM irqfd stays installed across MSI-X mask/unmask cycles instead of being torn down and re-added each time.

Message-ID: <[email protected]>

---
 hw/virtio/vdpa-dev.c | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)
hw/virtio/vdpa-dev.c +25 -5
index 7e5cc040e9..cd7ce2e381 100644
@@ -267,11 +267,7 @@ static int vhost_vdpa_device_start(VirtIODevice *vdev, Error **errp)
     }
     s->started = true;
 
-    /*
-     * guest_notifier_mask/pending not used yet, so just unmask
-     * everything here. virtio-pci will do the right thing by
-     * enabling/disabling irqfd.
-     */
+    /* Point every vring call fd at its guest notifier. */
     for (i = 0; i < s->dev.nvqs; i++) {
         vhost_virtqueue_mask(&s->dev, vdev, i, false);
     }
@@ -285,6 +281,28 @@ err_host_notifiers:
     return ret;
 }
 
+static void vhost_vdpa_device_guest_notifier_mask(VirtIODevice *vdev, int idx,
+                                                   bool mask)
+{
+    VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev);
+
+    if (idx == VIRTIO_CONFIG_IRQ_IDX) {
+        return;
+    }
+    vhost_virtqueue_mask(&s->dev, vdev, idx, mask);
+}
+
+static bool vhost_vdpa_device_guest_notifier_pending(VirtIODevice *vdev,
+                                                    int idx)
+{
+    VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev);
+
+    if (idx == VIRTIO_CONFIG_IRQ_IDX) {
+        return false;
+    }
+    return vhost_virtqueue_pending(&s->dev, idx);
+}
+
 static void vhost_vdpa_device_stop(VirtIODevice *vdev)
 {
     VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev);
@@ -376,6 +394,8 @@ static void vhost_vdpa_device_class_init(ObjectClass *klass, const void *data)
     vdc->get_features = vhost_vdpa_device_get_features;
     vdc->set_status = vhost_vdpa_device_set_status;
     vdc->get_vhost = vhost_vdpa_device_get_vhost;
+    vdc->guest_notifier_mask = vhost_vdpa_device_guest_notifier_mask;
+    vdc->guest_notifier_pending = vhost_vdpa_device_guest_notifier_pending;
 }
 
 static void vhost_vdpa_device_instance_init(Object *obj)
Signature · 1 line
MST
#80 lore raw

[PULL 61/78] amd_iommu: Assign ioapic_as only when interrupt remapping is supported.

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

When interrupt remapping is disabled (intremap=off), the IOAPIC should use the system address space instead of the IOMMU address space. Currently, the IOAPIC is unconditionally assigned to the IOMMU address space, which causes IOAPIC-triggered interrupts to be incorrectly translated by the IOMMU even when interrupt remapping is disabled.

Message-ID: <[email protected]>

---
 hw/i386/amd_iommu.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
hw/i386/amd_iommu.c +10 -2
index 56e7815412..817320c51c 100644
@@ -2733,8 +2733,16 @@ static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
     memory_region_add_subregion_overlap(&s->mr_sys, AMDVI_INT_ADDR_FIRST,
                                         &s->mr_ir, 1);
 
-    /* Pseudo address space under root PCI bus. */
-    x86ms->ioapic_as = amdvi_host_dma_iommu(bus, s, AMDVI_IOAPIC_SB_DEVID);
+    /**
+     * Pseudo address space under root PCI bus.
+     * The linux kernel disables the intremap when it cannot find IOAPIC under
+     * AMD IOMMU IVRS, hence create a ioapic_as with root bus even though AMD
+     * IOMMU is not serving devices attached to root bus.
+     */
+    if (x86_iommu_ir_supported(X86_IOMMU_DEVICE(s))) {
+        x86ms->ioapic_as = amdvi_host_dma_iommu(pcms->pcibus, s,
+                                                AMDVI_IOAPIC_SB_DEVID);
+    }
 
     if (kvm_enabled() && x86ms->apic_id_limit > 255 && !s->xtsup) {
         error_report("AMD IOMMU with x2APIC configuration requires xtsup=on");
Signature · 1 line
MST
#81 lore raw

[PULL 14/78] tests/qtest/migration: add test for snapshot rejection in CPR modes

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

Snapshots are refused whenever a CPR migration mode is set. Add a test which sets each of the CPR modes in turn and checks that 'savevm' and 'loadvm' fail with the corresponding error, then switches back to 'normal' mode and checks that the error is gone.

Message-ID: <[email protected]>

---
 tests/qtest/migration/cpr-tests.c | 40 +++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
tests/qtest/migration/cpr-tests.c +40 -0
index 0bec753b4a..05c2ac1336 100644
@@ -290,6 +290,44 @@ static void test_mode_exec(char *name, MigrateCommon *args)
     test_cpr_exec(args);
 }
 
+/* Snapshots must be refused while a CPR migration mode is set */
+static void test_mode_no_snapshot(char *name, MigrateCommon *args)
+{
+    static const char *const modes[] = {
+        "cpr-reboot", "cpr-transfer", "cpr-exec",
+    };
+    QTestState *vm = qtest_init("-nodefaults -machine none");
+    char *out;
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(modes); i++) {
+        g_autofree char *expected = g_strdup_printf(
+            "Snapshots are not compatible with migration mode %s", modes[i]);
+
+        migrate_set_parameter_str(vm, "mode", modes[i]);
+
+        out = qtest_hmp(vm, "savevm snap");
+        g_assert_nonnull(strstr(out, expected));
+        g_free(out);
+
+        out = qtest_hmp(vm, "loadvm snap");
+        g_assert_nonnull(strstr(out, expected));
+        g_free(out);
+    }
+
+    /*
+     * The check should no longer fire in normal mode.  savevm still fails
+     * (there's no block device for storing the snapshot).  We just check
+     * that it's not the mode error any more.
+     */
+    migrate_set_parameter_str(vm, "mode", "normal");
+    out = qtest_hmp(vm, "savevm snap");
+    g_assert_null(strstr(out, "not compatible with migration mode"));
+    g_free(out);
+
+    qtest_quit(vm);
+}
+
 void migration_test_add_cpr(MigrationTestEnv *env)
 {
     tmpfs = env->tmpfs;
@@ -300,6 +338,8 @@ void migration_test_add_cpr(MigrationTestEnv *env)
         return;
     }
 
+    migration_test_add("/migration/mode/no-snapshot", test_mode_no_snapshot);
+
     /*
      * Our CI system has problems with shared memory.
      * Don't run this test until we find a workaround.
Signature · 1 line
MST
#82 lore raw

[PULL 77/78] net: add generic get_mtu NetClientInfo callback

In reply to Michael S. Tsirkin

From: Nariman Sayed <[email protected]>

Add a get_mtu callback to NetClientInfo, along with a qemu_get_mtu() wrapper following the existing qemu_has_ufo()-style pattern, so that device models can query a netdev backend's host MTU generically without needing to know or check its backend type.

Wire this up for the tap backend via tap_get_mtu(), which is now static and only referenced through net_tap_info.get_mtu, matching the existing pattern used by tap_has_ufo() and similar callbacks.

This allows other backends (e.g. vhost-user, vhost-vdpa) to implement get_mtu in the future without requiring changes to any code that calls qemu_get_mtu().

Message-ID: <[email protected]>

---
 include/net/net.h | 3 +++
 net/net.c         | 9 +++++++++
 net/tap.c         | 8 ++++++++
 3 files changed, 20 insertions(+)
include/net/net.h +3 -0
index fdf6a2ddd4..de4f03c301 100644
@@ -73,6 +73,7 @@ typedef bool (HasVnetHdr)(NetClientState *);
 typedef bool (HasVnetHdrLen)(NetClientState *, int);
 typedef void (SetOffload)(NetClientState *, const NetOffloads *);
 typedef int (GetVnetHdrLen)(NetClientState *);
+typedef int (GetMtu)(NetClientState *);
 typedef void (SetVnetHdrLen)(NetClientState *, int);
 typedef bool (GetVnetHashSupportedTypes)(NetClientState *, uint32_t *);
 typedef int (SetVnetLE)(NetClientState *, bool);
@@ -113,6 +114,7 @@ typedef struct NetClientInfo {
     NetCheckPeerType *check_peer_type;
     IsWaitIncoming *is_wait_incoming;
     GetVHostNet *get_vhost_net;
+    GetMtu *get_mtu;
 } NetClientInfo;
 
 struct NetClientState {
@@ -214,6 +216,7 @@ bool qemu_has_uso(NetClientState *nc);
 bool qemu_has_tunnel(NetClientState *nc);
 bool qemu_has_vnet_hdr(NetClientState *nc);
 bool qemu_has_vnet_hdr_len(NetClientState *nc, int len);
+int qemu_get_mtu(NetClientState *nc);
 void qemu_set_offload(NetClientState *nc, const NetOffloads *ol);
 int qemu_get_vnet_hdr_len(NetClientState *nc);
 void qemu_set_vnet_hdr_len(NetClientState *nc, int len);
net/net.c +9 -0
index d7fa637ce5..bf7e5d9b12 100644
@@ -514,6 +514,15 @@ bool qemu_has_ufo(NetClientState *nc)
     return nc->info->has_ufo(nc);
 }
 
+int qemu_get_mtu(NetClientState *nc)
+{
+    if (!nc || !nc->info->get_mtu) {
+        return -1;
+    }
+
+    return nc->info->get_mtu(nc);
+}
+
 bool qemu_has_uso(NetClientState *nc)
 {
     if (!nc || !nc->info->has_uso) {
net/tap.c +8 -0
index acccabca56..665dee800d 100644
@@ -446,6 +446,13 @@ int tap_get_fd(NetClientState *nc)
     return s->fd;
 }
 
+static int tap_get_mtu(NetClientState *nc)
+{
+    TAPState *s = container_of(nc, TAPState, nc);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
+    return tap_fd_get_mtu(s->fd);
+}
+
 /*
  * tap_get_vhost_net() can return NULL if a tap net-device backend is
  * created with 'vhost=off' option, 'vhostforce=off' or no vhost or
@@ -607,6 +614,7 @@ static NetClientInfo net_tap_info = {
     .set_steering_ebpf = tap_set_steering_ebpf,
     .is_wait_incoming = tap_is_wait_incoming,
     .get_vhost_net = tap_get_vhost_net,
+    .get_mtu = tap_get_mtu,
 };
 
 static TAPState *new_tap(NetClientState *peer,
Signature · 1 line
MST
#83 lore raw

[PULL 17/78] vhost-vsock: don't reset connections during CPR

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

Migration target with a vhost-vsock device in the .post_load() hook issues VIRTIO_VSOCK_EVENT_TRANSPORT_RESET event to the guest. This results into the guest tearing down all its vsock connections. That reset exists because after a normal migration the cid may change.

However it's not true for CPR-style migration. In this case cid remains the same, and we want the connections to persist. Thus, let's customize the .post_load() hook to skip the common transport reset part in this case.

Message-ID: <[email protected]>

---
 hw/virtio/vhost-vsock.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)
hw/virtio/vhost-vsock.c +16 -1
index 503d248a61..c582ac9fcc 100644
@@ -20,6 +20,7 @@
 #include "hw/core/qdev-properties.h"
 #include "hw/virtio/vhost-vsock.h"
 #include "monitor/monitor.h"
+#include "migration/cpr.h"
 #include "migration/blocker.h"
 #include "migration/misc.h"
 
@@ -110,6 +111,20 @@ static uint64_t vhost_vsock_get_features(VirtIODevice *vdev,
     return vhost_vsock_common_get_features(vdev, requested_features, errp);
 }
 
+static int vhost_vsock_post_load(void *opaque, int version_id)
+{
+    /*
+     * Only reset vsock connections for non-CPR migration.  For CPR the
+     * guest cid is unchanged, and the cid-change reset would otherwise
+     * tear the vsock connections down.
+     */
+    if (!cpr_is_incoming()) {
+        return vhost_vsock_common_post_load(opaque, version_id);
+    }
+
+    return 0;
+}
+
 static const VMStateDescription vmstate_virtio_vhost_vsock = {
     .name = "virtio-vhost_vsock",
     .minimum_version_id = VHOST_VSOCK_SAVEVM_VERSION,
@@ -119,7 +134,7 @@ static const VMStateDescription vmstate_virtio_vhost_vsock = {
         VMSTATE_END_OF_LIST()
     },
     .pre_save = vhost_vsock_common_pre_save,
-    .post_load = vhost_vsock_common_post_load,
+    .post_load = vhost_vsock_post_load,
 };
 
 static void vhost_vsock_device_realize(DeviceState *dev, Error **errp)
Signature · 1 line
MST
#84 lore raw

[PULL 51/78] contrib/vhost-user-bridge: Use stderr for debug output

In reply to Michael S. Tsirkin

From: Yodel Eldar <[email protected]>

Since vhost-user-bridge is solely used for testing, and logging to stderr ensures proper interleaving of debug output in automated testing, modify the DPRINT macro to write to stderr instead of stdout.

Message ID of relevant thread:
<[email protected]>

Message-ID: <[email protected]>

---
 contrib/vhost-user-bridge/vhost-user-bridge.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
contrib/vhost-user-bridge/vhost-user-bridge.c +1 -1
index 3f0fd0fa49..a6f7ec226c 100644
@@ -41,7 +41,7 @@
 #define DPRINT(...) \
     do { \
         if (VHOST_USER_BRIDGE_DEBUG) { \
-            printf(__VA_ARGS__); \
+            fprintf(stderr, __VA_ARGS__); \
         } \
     } while (0)
 
Signature · 1 line
MST
#85 lore raw

[PULL 22/78] vhost: add vhost_dev_set_owner() / vhost_dev_reset_owner() helpers

In reply to Michael S. Tsirkin

From: Andrey Drobyshev <[email protected]>

Wrap the set_owner/reset_owner backend ops in dev-level helpers, matching other vhost_dev_* wrappers, so device code can take or release ownership without reaching into vhost_ops directly. vhost_dev_init() now uses vhost_dev_set_owner(). Both return -ENOSYS if the backend has no such op.

No functional change. These are used by the following vhost-vsock patch to hand a device between owners during CPR.

Message-ID: <[email protected]>

---
 include/hw/virtio/vhost.h | 25 +++++++++++++++++++++++++
 hw/virtio/vhost.c         | 20 +++++++++++++++++++-
 2 files changed, 44 insertions(+), 1 deletion(-)
include/hw/virtio/vhost.h +25 -0
index b2b644c0d1..8127a9a5a2 100644
@@ -178,6 +178,31 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
 int vhost_dev_init_backend(struct vhost_dev *hdev, void *opaque,
                            VhostBackendType backend_type, Error **errp);
 
+/**
+ * vhost_dev_set_owner() - take ownership of a vhost device
+ * @hdev: the common vhost_dev structure
+ *
+ * Issue VHOST_SET_OWNER on a device that has already been set up, e.g. to
+ * complete a CPR handoff on the destination, or to take the device back on
+ * the source after a failed CPR.
+ *
+ * Return: 0 on success, -ENOSYS if the backend has no such op, negative
+ * errno on failure.
+ */
+int vhost_dev_set_owner(struct vhost_dev *hdev);
+
+/**
+ * vhost_dev_reset_owner() - release ownership of a vhost device
+ * @hdev: the common vhost_dev structure
+ *
+ * Issue VHOST_RESET_OWNER so that another process can take the device over
+ * with vhost_dev_set_owner(), e.g. on the CPR source once the VM is stopped.
+ *
+ * Return: 0 on success, -ENOSYS if the backend has no such op, negative
+ * errno on failure.
+ */
+int vhost_dev_reset_owner(struct vhost_dev *hdev);
+
 /**
  * vhost_dev_cleanup() - tear down and cleanup vhost interface
  * @hdev: the common vhost_dev structure
hw/virtio/vhost.c +19 -1
index dfdfd49da3..56fa434588 100644
@@ -1703,6 +1703,24 @@ int vhost_dev_init_backend(struct vhost_dev *hdev, void *opaque,
     return 0;
 }
 
+int vhost_dev_set_owner(struct vhost_dev *hdev)
+{
+    assert(hdev->vhost_ops);
+    if (!hdev->vhost_ops->vhost_set_owner) {
+        return -ENOSYS;
+    }
+    return hdev->vhost_ops->vhost_set_owner(hdev);
+}
+
+int vhost_dev_reset_owner(struct vhost_dev *hdev)
+{
+    assert(hdev->vhost_ops);
+    if (!hdev->vhost_ops->vhost_reset_owner) {
+        return -ENOSYS;
+    }
+    return hdev->vhost_ops->vhost_reset_owner(hdev);
+}
+
 int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
                    VhostBackendType backend_type, uint32_t busyloop_timeout,
                    Error **errp)
@@ -1720,7 +1738,7 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
         goto fail;
     }
 
-    r = hdev->vhost_ops->vhost_set_owner(hdev);
+    r = vhost_dev_set_owner(hdev);
     if (r < 0) {
         error_setg_errno(errp, -r, "vhost_set_owner failed");
         goto fail;
Signature · 1 line
MST
#86 lore raw

[PULL 66/78] amd_iommu: Introduce root bus

In reply to Michael S. Tsirkin

From: Sairaj Kodilkar <[email protected]>

Following patch will create IVRS entries starting from the root bus

Message-ID: <[email protected]>

---
 hw/i386/amd_iommu.h | 3 +++
 hw/i386/amd_iommu.c | 2 ++
 2 files changed, 5 insertions(+)
hw/i386/amd_iommu.h +3 -0
index c5b346cc35..948d8611b1 100644
@@ -388,6 +388,9 @@ struct AMDVIState {
 
     /* DMA address translation */
     bool dma_remap;
+
+    /* root bus to register IOMMU ops */
+    PCIBus *root_bus;
 };
 
 uint64_t amdvi_extended_feature_register(AMDVIState *s);
hw/i386/amd_iommu.c +2 -0
index b2df0e9533..14ea8274b2 100644
@@ -2713,6 +2713,8 @@ static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
         }
     }
 
+    s->root_bus = bus;
+
     s->iotlb = g_hash_table_new_full(amdvi_iotlb_hash,
                                      amdvi_iotlb_equal, g_free, g_free);
 
Signature · 1 line
MST
#87 lore raw

[PULL 73/78] intel_iommu_accel: Accept PRQ response for passthrough device

In reply to Michael S. Tsirkin

From: Zhenzhong Duan <[email protected]>

Propagate guest's PRQ response to host by writing to fault_fd. Create a new VTDPRQEntry to cache cookie for each fault group, this cookie is used to mark the fault group on host side.

If there is either PRQ overflow or full error condition, propagate a success response to host for LPIG request with the PRQ requests dropped. For other error conditions, propagate response of invalid request.

Message-ID: <[email protected]>

---
 hw/i386/intel_iommu_accel.h   |  12 ++++
 include/hw/i386/intel_iommu.h |   6 ++
 hw/i386/intel_iommu.c         |   4 ++
 hw/i386/intel_iommu_accel.c   | 102 +++++++++++++++++++++++++++++++---
 hw/i386/trace-events          |   1 +
 5 files changed, 118 insertions(+), 7 deletions(-)
hw/i386/intel_iommu_accel.h +12 -0
index e0319749a5..0cc22b2cbb 100644
@@ -20,6 +20,7 @@ typedef struct VTDAccelPASIDCacheEntry {
     uint32_t fs_hwpt_id;
     uint32_t fault_id;
     int fault_fd;
+    QLIST_HEAD(, VTDPRQEntry) vtd_prq_list;
     QLIST_ENTRY(VTDAccelPASIDCacheEntry) next;
 } VTDAccelPASIDCacheEntry;
 
@@ -31,6 +32,9 @@ void vtd_flush_host_piotlb_all_locked(IntelIOMMUState *s, uint16_t domain_id,
                                       uint64_t npages, bool ih);
 void vtd_accel_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info);
 void vtd_accel_pasid_cache_reset(IntelIOMMUState *s);
+bool vtd_accel_propagate_page_group_response(IntelIOMMUState *s,
+                                             uint16_t rid, uint32_t pasid,
+                                             IOMMUPRIResponse *response);
 void vtd_iommu_ops_update_accel(PCIIOMMUOps *ops);
 #else
 static inline bool vtd_check_hiod_accel(IntelIOMMUState *s,
@@ -69,6 +73,14 @@ static inline void vtd_accel_pasid_cache_reset(IntelIOMMUState *s)
 {
 }
 
+static inline
+bool vtd_accel_propagate_page_group_response(IntelIOMMUState *s,
+                                             uint16_t rid, uint32_t pasid,
+                                             IOMMUPRIResponse *response)
+{
+    return false;
+}
+
 static inline void vtd_iommu_ops_update_accel(PCIIOMMUOps *ops)
 {
 }
include/hw/i386/intel_iommu.h +6 -0
index 1842ba5840..5d44eac0ed 100644
@@ -100,6 +100,12 @@ typedef struct VTDPASIDCacheEntry {
     bool valid;
 } VTDPASIDCacheEntry;
 
+typedef struct VTDPRQEntry {
+    uint32_t grpid;
+    uint32_t cookie;
+    QLIST_ENTRY(VTDPRQEntry) next;
+} VTDPRQEntry;
+
 struct VTDAddressSpace {
     PCIBus *bus;
     uint8_t devfn;
hw/i386/intel_iommu.c +4 -0
index adb8c79d57..b7da5d086d 100644
@@ -3380,6 +3380,10 @@ static bool vtd_process_page_group_response_desc(IntelIOMMUState *s,
         response.response_code = IOMMU_PRI_RESP_FAILURE;
     }
 
+    if (vtd_accel_propagate_page_group_response(s, rid, pasid, &response)) {
+        return true;
+    }
+
     if (vtd_dev_as->pri_notifier) {
         vtd_dev_as->pri_notifier->notify(vtd_dev_as->pri_notifier, &response);
     }
hw/i386/intel_iommu_accel.c +95 -7

133 lines not on this page · raw message

hw/i386/trace-events +1 -0
index ad84d106a4..7bf4cddd38 100644
@@ -78,6 +78,7 @@ vtd_device_attach_hwpt(uint32_t dev_id, uint32_t pasid, uint32_t hwpt_id, int re
 vtd_device_detach_hwpt(uint32_t dev_id, uint32_t pasid, int ret) "dev_id %d pasid %d ret: %d"
 vtd_device_reattach_def_hwpt(uint32_t dev_id, uint32_t pasid, uint32_t hwpt_id, int ret) "dev_id %d pasid %d hwpt_id %d, ret: %d"
 vtd_read_fs_faultq(uint32_t fault_id, uint32_t fault_fd, ssize_t ret) "fault_id %d fault_fd %d ret: %zd"
+vtd_write_fs_faultq(uint32_t fault_id, uint32_t fault_fd, uint32_t cookie, uint32_t code, ssize_t ret) "fault_id %d fault_fd %d cookie %d code %d ret: %zd"
 
 # amd_iommu.c
 amdvi_evntlog_fail(uint64_t addr, uint32_t head) "error: fail to write at addr 0x%"PRIx64" +  offset 0x%"PRIx32
Signature · 1 line
MST
#88 lore raw
In reply to Michael S. Tsirkin
Quoted 15 lines · Michael S. Tsirkin

The following changes since commit 29b1dc631d6ac6bfc39464e0be8bf4cad556a591:

   Merge tag 'block-pull-request' ofhttps://gitlab.com/stefanha/qemu into staging (2026-10-27 20:02:57 -0500)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

for you to fetch changes up to c155e7e9e025b60ce17e26a7c382d86f3f9c9fee:

   virtio-net: add mtu-from-tap property for automatic MTU detection (2026-10-28 13:11:16 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu, acpi: features, fixes

Quoted 8 lines

multiboot can again load 64 bit images vdpa-dev now implements guest_notifier_mask/pending pci bridge can now configure the negotiated bandwidth tap can now auto-detect host mtu and expose to guest amd iommu can now attach on non system bus vhost-user spec now has its own mailing list

Fixes, cleanups all over the place.

New failures:

https://gitlab.com/qemu-project/qemu/-/jobs/17577211343#L867
https://gitlab.com/qemu-project/qemu/-/jobs/17576922092#L185

Use of sudo in containers where it is not installed (functional-system-centos, functional-system-opensuse).

r~

#89 lore raw
Quoted 27 lines · Richard Henderson
Quoted 15 lines · Michael S. Tsirkin

The following changes since commit 29b1dc631d6ac6bfc39464e0be8bf4cad556a591:

   Merge tag 'block-pull-request' ofhttps://gitlab.com/stefanha/qemu into staging (2026-10-27 20:02:57 -0500)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

for you to fetch changes up to c155e7e9e025b60ce17e26a7c382d86f3f9c9fee:

   virtio-net: add mtu-from-tap property for automatic MTU detection (2026-10-28 13:11:16 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu, acpi: features, fixes

Quoted 8 lines

multiboot can again load 64 bit images vdpa-dev now implements guest_notifier_mask/pending pci bridge can now configure the negotiated bandwidth tap can now auto-detect host mtu and expose to guest amd iommu can now attach on non system bus vhost-user spec now has its own mailing list

Fixes, cleanups all over the place.

New failures:

Quoted 8 lines

https://gitlab.com/qemu-project/qemu/-/jobs/17577211343#L867
https://gitlab.com/qemu-project/qemu/-/jobs/17576922092#L185

Use of sudo in containers where it is not installed (functional-system-centos, functional-system-opensuse).

r~

I dropped the test patch, thanks!
head at 592bb52d87a1a07172656a91cf5c38f63b5f6c60 now.

Vladimir, pls look into fixing all this up.

Signature · 1 line
MST
#90 lore raw
Quoted 31 lines · Michael S. Tsirkin
Quoted 30 lines · Richard Henderson
Quoted 15 lines · Michael S. Tsirkin

The following changes since commit 29b1dc631d6ac6bfc39464e0be8bf4cad556a591:

    Merge tag 'block-pull-request' ofhttps://gitlab.com/stefanha/qemu into staging (2026-10-27 20:02:57 -0500)

are available in the Git repository at:

https://git.kernel.org/pub/scm/virt/kvm/mst/qemu.git tags/for_upstream

for you to fetch changes up to c155e7e9e025b60ce17e26a7c382d86f3f9c9fee:

    virtio-net: add mtu-from-tap property for automatic MTU detection (2026-10-28 13:11:16 -0400)

----------------------------------------------------------------
pci, vhost, virtio, iommu, acpi: features, fixes

Quoted 8 lines

multiboot can again load 64 bit images vdpa-dev now implements guest_notifier_mask/pending pci bridge can now configure the negotiated bandwidth tap can now auto-detect host mtu and expose to guest amd iommu can now attach on non system bus vhost-user spec now has its own mailing list

Fixes, cleanups all over the place.

New failures:

https://gitlab.com/qemu-project/qemu/-/jobs/17577211343#L867
https://gitlab.com/qemu-project/qemu/-/jobs/17576922092#L185

Quoted 8 lines
Quoted 5 lines

Use of sudo in containers where it is not installed (functional-system-centos, functional-system-opensuse).

r~

I dropped the test patch, thanks!
head at 592bb52d87a1a07172656a91cf5c38f63b5f6c60 now.

Applied, thanks.

r~