From 196ac2544107121f64eeae5dccd2e72e72d55ab5 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Mon, 17 Nov 2025 06:54:28 -0500 Subject: [PATCH 01/38] vhost-user: ancilliary -> ancillary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My dictionary says the former spelling is incorrect. Message-ID: <52339e58-4366-4b7c-872f-b28e05370a5d@linaro.org> Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Gustavo Romero Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- docs/interop/vhost-user.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/interop/vhost-user.rst b/docs/interop/vhost-user.rst index 93a9c8df2b..93f756d3f4 100644 --- a/docs/interop/vhost-user.rst +++ b/docs/interop/vhost-user.rst @@ -411,8 +411,8 @@ in the ancillary data: * ``VHOST_USER_SET_INFLIGHT_FD`` (if ``VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD``) * ``VHOST_USER_SET_DEVICE_STATE_FD`` -When sending file descriptors in ancilliary data, *front-end* should -associate the ancilliary data with a ``sendmsg`` operation (or +When sending file descriptors in ancillary data, *front-end* should +associate the ancillary data with a ``sendmsg`` operation (or equivalent) that sends bytes starting with the first byte of the message header. *back-end* can therefore expect that file descriptors will only be received in the first ``recvmsg`` operation for a message From 6d8dd5ec084732a9933925aa2c4fdff9a1aae7ef Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 27 Oct 2025 10:24:18 +0900 Subject: [PATCH 02/38] pci/shpc: Do not unparent in instance_finalize() Children are automatically unparented so manually unparenting is unnecessary. Worse, automatic unparenting happens before the instance_finalize() callback of the parent gets called, so object_unparent() calls in the callback will refer to objects that are already unparented, which is semantically incorrect. Signed-off-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Message-ID: <20251027-shpc-v1-1-00e9b20a355d@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Michael S. Tsirkin Message-Id: <20251027-shpc-v1-1-00e9b20a355d@rsg.ci.i.u-tokyo.ac.jp> --- hw/pci/shpc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/pci/shpc.c b/hw/pci/shpc.c index aac6f2d034..938602866d 100644 --- a/hw/pci/shpc.c +++ b/hw/pci/shpc.c @@ -735,7 +735,6 @@ void shpc_free(PCIDevice *d) if (!shpc) { return; } - object_unparent(OBJECT(&shpc->mmio)); g_free(shpc->config); g_free(shpc->cmask); g_free(shpc->wmask); From c6caeed118ff069c720fb98d1f5d2bae70b05881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Mon, 27 Oct 2025 12:23:06 +0100 Subject: [PATCH 03/38] hw/pci-host: Set DEVICE_CATEGORY_BRIDGE once in parent class_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need to set DEVICE_CATEGORY_BRIDGE for each device implementing TYPE_PCI_HOST_BRIDGE: set it once in the parent. Signed-off-by: Philippe Mathieu-Daudé Reviewed-by: Marc-André Lureau Reviewed-by: Michael S. Tsirkin Message-ID: <20251027112306.57634-1-philmd@linaro.org> Signed-off-by: Michael S. Tsirkin Message-Id: <20251027112306.57634-1-philmd@linaro.org> --- hw/pci-host/articia.c | 1 - hw/pci-host/aspeed_pcie.c | 1 - hw/pci-host/designware.c | 3 --- hw/pci-host/gpex.c | 2 -- hw/pci-host/grackle.c | 1 - hw/pci-host/gt64120.c | 1 - hw/pci-host/mv64361.c | 1 - hw/pci-host/pnv_phb.c | 1 - hw/pci-host/ppce500.c | 1 - hw/pci-host/q35.c | 1 - hw/pci-host/raven.c | 1 - hw/pci-host/remote.c | 1 - hw/pci-host/sabre.c | 1 - hw/pci-host/uninorth.c | 4 ---- hw/pci-host/xilinx-pcie.c | 2 -- hw/pci/pci_host.c | 1 + 16 files changed, 1 insertion(+), 22 deletions(-) diff --git a/hw/pci-host/articia.c b/hw/pci-host/articia.c index 1881e03d58..04623dfd84 100644 --- a/hw/pci-host/articia.c +++ b/hw/pci-host/articia.c @@ -200,7 +200,6 @@ static void articia_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = articia_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); } /* TYPE_ARTICIA_PCI_HOST */ diff --git a/hw/pci-host/aspeed_pcie.c b/hw/pci-host/aspeed_pcie.c index 83a1c7075c..8eeba1f9f3 100644 --- a/hw/pci-host/aspeed_pcie.c +++ b/hw/pci-host/aspeed_pcie.c @@ -350,7 +350,6 @@ static void aspeed_pcie_rc_class_init(ObjectClass *klass, const void *data) dc->desc = "ASPEED PCIe RC"; dc->realize = aspeed_pcie_rc_realize; dc->fw_name = "pci"; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); hc->root_bus_path = aspeed_pcie_rc_root_bus_path; device_class_set_props(dc, aspeed_pcie_rc_props); diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c index 019e025382..00c9449c4d 100644 --- a/hw/pci-host/designware.c +++ b/hw/pci-host/designware.c @@ -593,8 +593,6 @@ static void designware_pcie_root_class_init(ObjectClass *klass, PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); - k->vendor_id = PCI_VENDOR_ID_SYNOPSYS; k->device_id = 0xABCD; k->revision = 0; @@ -736,7 +734,6 @@ static void designware_pcie_host_class_init(ObjectClass *klass, hc->root_bus_path = designware_pcie_host_root_bus_path; dc->realize = designware_pcie_host_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; dc->vmsd = &vmstate_designware_pcie_host; } diff --git a/hw/pci-host/gpex.c b/hw/pci-host/gpex.c index b5074c05c0..e66784ce51 100644 --- a/hw/pci-host/gpex.c +++ b/hw/pci-host/gpex.c @@ -200,7 +200,6 @@ static void gpex_host_class_init(ObjectClass *klass, const void *data) hc->root_bus_path = gpex_host_root_bus_path; dc->realize = gpex_host_realize; dc->unrealize = gpex_host_unrealize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; device_class_set_props(dc, gpex_host_properties); } @@ -242,7 +241,6 @@ static void gpex_root_class_init(ObjectClass *klass, const void *data) PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->desc = "QEMU generic PCIe host bridge"; dc->vmsd = &vmstate_gpex_root; k->vendor_id = PCI_VENDOR_ID_REDHAT; diff --git a/hw/pci-host/grackle.c b/hw/pci-host/grackle.c index 9a58f0e9b5..b0db7870b0 100644 --- a/hw/pci-host/grackle.c +++ b/hw/pci-host/grackle.c @@ -140,7 +140,6 @@ static void grackle_class_init(ObjectClass *klass, const void *data) dc->realize = grackle_realize; device_class_set_props(dc, grackle_properties); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; sbc->explicit_ofw_unit_address = grackle_ofw_unit_address; } diff --git a/hw/pci-host/gt64120.c b/hw/pci-host/gt64120.c index d361c457ed..566f928304 100644 --- a/hw/pci-host/gt64120.c +++ b/hw/pci-host/gt64120.c @@ -1298,7 +1298,6 @@ static void gt64120_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); device_class_set_props(dc, gt64120_properties); dc->realize = gt64120_realize; device_class_set_legacy_reset(dc, gt64120_reset); diff --git a/hw/pci-host/mv64361.c b/hw/pci-host/mv64361.c index ef1c77563e..495a82befd 100644 --- a/hw/pci-host/mv64361.c +++ b/hw/pci-host/mv64361.c @@ -108,7 +108,6 @@ static void mv64361_pcihost_class_init(ObjectClass *klass, const void *data) dc->realize = mv64361_pcihost_realize; device_class_set_props(dc, mv64361_pcihost_props); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); } static const TypeInfo mv64361_pcihost_info = { diff --git a/hw/pci-host/pnv_phb.c b/hw/pci-host/pnv_phb.c index 85fcc3b686..0b556d1bf5 100644 --- a/hw/pci-host/pnv_phb.c +++ b/hw/pci-host/pnv_phb.c @@ -202,7 +202,6 @@ static void pnv_phb_class_init(ObjectClass *klass, const void *data) hc->root_bus_path = pnv_phb_root_bus_path; dc->realize = pnv_phb_realize; device_class_set_props(dc, pnv_phb_properties); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->user_creatable = true; } diff --git a/hw/pci-host/ppce500.c b/hw/pci-host/ppce500.c index 76623f78c4..67180ba5f0 100644 --- a/hw/pci-host/ppce500.c +++ b/hw/pci-host/ppce500.c @@ -516,7 +516,6 @@ static void e500_pcihost_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = e500_pcihost_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); device_class_set_props(dc, pcihost_properties); dc->vmsd = &vmstate_ppce500_pci; } diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c index bf56229051..b353d3e1e6 100644 --- a/hw/pci-host/q35.c +++ b/hw/pci-host/q35.c @@ -194,7 +194,6 @@ static void q35_host_class_init(ObjectClass *klass, const void *data) device_class_set_props(dc, q35_host_props); /* Reason: needs to be wired up by pc_q35_init */ dc->user_creatable = false; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; } diff --git a/hw/pci-host/raven.c b/hw/pci-host/raven.c index c50061996c..b3c2678667 100644 --- a/hw/pci-host/raven.c +++ b/hw/pci-host/raven.c @@ -296,7 +296,6 @@ static void raven_pcihost_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->realize = raven_pcihost_realizefn; dc->fw_name = "pci"; } diff --git a/hw/pci-host/remote.c b/hw/pci-host/remote.c index feaaa9adaa..9ea95fac6e 100644 --- a/hw/pci-host/remote.c +++ b/hw/pci-host/remote.c @@ -55,7 +55,6 @@ static void remote_pcihost_class_init(ObjectClass *klass, const void *data) dc->realize = remote_pcihost_realize; dc->user_creatable = false; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; } diff --git a/hw/pci-host/sabre.c b/hw/pci-host/sabre.c index b3f57dca7d..cd2328ad53 100644 --- a/hw/pci-host/sabre.c +++ b/hw/pci-host/sabre.c @@ -505,7 +505,6 @@ static void sabre_class_init(ObjectClass *klass, const void *data) dc->realize = sabre_realize; device_class_set_legacy_reset(dc, sabre_reset); device_class_set_props(dc, sabre_properties); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; sbc->explicit_ofw_unit_address = sabre_ofw_unit_address; } diff --git a/hw/pci-host/uninorth.c b/hw/pci-host/uninorth.c index d39546b6f4..10972de694 100644 --- a/hw/pci-host/uninorth.c +++ b/hw/pci-host/uninorth.c @@ -435,7 +435,6 @@ static void pci_unin_main_class_init(ObjectClass *klass, const void *data) dc->realize = pci_unin_main_realize; device_class_set_props(dc, pci_unin_main_pci_host_props); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; sbc->explicit_ofw_unit_address = pci_unin_main_ofw_unit_address; } @@ -453,7 +452,6 @@ static void pci_u3_agp_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = pci_u3_agp_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); } static const TypeInfo pci_u3_agp_info = { @@ -469,7 +467,6 @@ static void pci_unin_agp_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = pci_unin_agp_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); } static const TypeInfo pci_unin_agp_info = { @@ -485,7 +482,6 @@ static void pci_unin_internal_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = pci_unin_internal_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); } static const TypeInfo pci_unin_internal_info = { diff --git a/hw/pci-host/xilinx-pcie.c b/hw/pci-host/xilinx-pcie.c index 86c20377d1..40f625b61d 100644 --- a/hw/pci-host/xilinx-pcie.c +++ b/hw/pci-host/xilinx-pcie.c @@ -172,7 +172,6 @@ static void xilinx_pcie_host_class_init(ObjectClass *klass, const void *data) hc->root_bus_path = xilinx_pcie_host_root_bus_path; dc->realize = xilinx_pcie_host_realize; - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->fw_name = "pci"; device_class_set_props(dc, xilinx_pcie_host_props); } @@ -291,7 +290,6 @@ static void xilinx_pcie_root_class_init(ObjectClass *klass, const void *data) PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); - set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); dc->desc = "Xilinx AXI-PCIe Host Bridge"; k->vendor_id = PCI_VENDOR_ID_XILINX; k->device_id = 0x7021; diff --git a/hw/pci/pci_host.c b/hw/pci/pci_host.c index 05f1475dc7..91e3885c7f 100644 --- a/hw/pci/pci_host.c +++ b/hw/pci/pci_host.c @@ -245,6 +245,7 @@ static void pci_host_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_props(dc, pci_host_properties_common); dc->vmsd = &vmstate_pcihost; + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); } static const TypeInfo pci_host_type_info = { From cabeac1246724ae12eb935c2b2c06777675c5035 Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:41 +0000 Subject: [PATCH 04/38] intel_iommu: Add an IOMMU index for pre-translated addresses Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-2-clement.mathieu--drif@eviden.com> --- hw/i386/intel_iommu.c | 13 +++++++++++++ hw/i386/intel_iommu_internal.h | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index e8a6f50a5a..80b21a6468 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -5601,6 +5601,17 @@ static const TypeInfo vtd_info = { .class_init = vtd_class_init, }; +static int vtd_attrs_to_index(IOMMUMemoryRegion *iommu_mr, MemTxAttrs attrs) +{ + return attrs.address_type == PCI_AT_TRANSLATED ? + VTD_IDX_TRANSLATED : VTD_IDX_UNTRANSLATED; +} + +static int vtd_num_indexes(IOMMUMemoryRegion *iommu) +{ + return VTD_IDX_COUNT; +} + static void vtd_iommu_memory_region_class_init(ObjectClass *klass, const void *data) { @@ -5609,6 +5620,8 @@ static void vtd_iommu_memory_region_class_init(ObjectClass *klass, imrc->translate = vtd_iommu_translate; imrc->notify_flag_changed = vtd_iommu_notify_flag_changed; imrc->replay = vtd_iommu_replay; + imrc->attrs_to_index = vtd_attrs_to_index; + imrc->num_indexes = vtd_num_indexes; } static const TypeInfo vtd_iommu_memory_region_info = { diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h index a2ca79f925..be757c290d 100644 --- a/hw/i386/intel_iommu_internal.h +++ b/hw/i386/intel_iommu_internal.h @@ -688,6 +688,13 @@ typedef struct VTDPIOTLBInvInfo { /* Bits to decide the offset for each level */ #define VTD_LEVEL_BITS 9 +/* IOMMU Index */ +typedef enum VTDIOMMUIndex { + VTD_IDX_UNTRANSLATED = 0, /* Default */ + VTD_IDX_TRANSLATED = 1, + VTD_IDX_COUNT = 2, /* Number of supported indexes */ +} VTDIOMMUIndex; + typedef struct VTDHostIOMMUDevice { IntelIOMMUState *iommu_state; PCIBus *bus; From 05f858cb67e4965db77f3fe223e6110ff3ca0dde Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:42 +0000 Subject: [PATCH 05/38] intel_iommu: Support memory operations with pre-translated addresses Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-3-clement.mathieu--drif@eviden.com> --- hw/i386/intel_iommu.c | 118 +++++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 37 deletions(-) diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 80b21a6468..7e7c31cb55 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -3985,6 +3985,25 @@ static void vtd_mem_write(void *opaque, hwaddr addr, } } +static void vtd_prepare_identity_entry(hwaddr addr, IOMMUAccessFlags perm, + uint32_t pasid, IOMMUTLBEntry *iotlb) +{ + iotlb->iova = addr & VTD_PAGE_MASK_4K; + iotlb->translated_addr = addr & VTD_PAGE_MASK_4K; + iotlb->addr_mask = ~VTD_PAGE_MASK_4K; + iotlb->perm = perm; + iotlb->pasid = pasid; +} + +static inline void vtd_prepare_error_entry(IOMMUTLBEntry *entry) +{ + entry->iova = 0; + entry->translated_addr = 0; + entry->addr_mask = ~VTD_PAGE_MASK_4K; + entry->perm = IOMMU_NONE; + entry->pasid = PCI_NO_PASID; +} + static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr, IOMMUAccessFlags flag, int iommu_idx) { @@ -3996,16 +4015,29 @@ static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr, .pasid = vtd_as->pasid, }; bool success; + bool is_write = flag & IOMMU_WO; if (likely(s->dmar_enabled)) { - success = vtd_do_iommu_translate(vtd_as, vtd_as->bus, vtd_as->devfn, - addr, flag & IOMMU_WO, &iotlb); + /* Only support translated requests in scalable mode */ + if (iommu_idx == VTD_IDX_TRANSLATED && s->root_scalable) { + if (vtd_as->pasid == PCI_NO_PASID) { + vtd_prepare_identity_entry(addr, IOMMU_RW, PCI_NO_PASID, + &iotlb); + success = true; + } else { + vtd_prepare_error_entry(&iotlb); + error_report_once("%s: translated request with PASID not " + "allowed (pasid=0x%" PRIx32 ")", __func__, + vtd_as->pasid); + success = false; + } + } else { + success = vtd_do_iommu_translate(vtd_as, vtd_as->bus, vtd_as->devfn, + addr, is_write, &iotlb); + } } else { /* DMAR disabled, passthrough, use 4k-page*/ - iotlb.iova = addr & VTD_PAGE_MASK_4K; - iotlb.translated_addr = addr & VTD_PAGE_MASK_4K; - iotlb.addr_mask = ~VTD_PAGE_MASK_4K; - iotlb.perm = IOMMU_RW; + vtd_prepare_identity_entry(addr, IOMMU_RW, vtd_as->pasid, &iotlb); success = true; } @@ -4414,6 +4446,37 @@ static int vtd_int_remap(X86IOMMUState *iommu, MSIMessage *src, src, dst, sid, false); } +static void vtd_report_sid_ir_illegal_access(IntelIOMMUState *s, uint16_t sid, + uint32_t pasid, hwaddr addr, + bool is_write) +{ + uint8_t bus_n = VTD_SID_TO_BUS(sid); + uint8_t devfn = VTD_SID_TO_DEVFN(sid); + bool is_fpd_set = false; + VTDContextEntry ce; + + /* Try out best to fetch FPD, we can't do anything more */ + if (vtd_dev_to_context_entry(s, bus_n, devfn, &ce) == 0) { + is_fpd_set = ce.lo & VTD_CONTEXT_ENTRY_FPD; + if (!is_fpd_set && s->root_scalable) { + vtd_ce_get_pasid_fpd(s, &ce, &is_fpd_set, pasid); + } + } + + vtd_report_fault(s, VTD_FR_SM_INTERRUPT_ADDR, is_fpd_set, sid, addr, + is_write, pasid != PCI_NO_PASID, pasid); +} + +static void vtd_report_ir_illegal_access(VTDAddressSpace *vtd_as, + hwaddr addr, bool is_write) +{ + uint8_t bus_n = pci_bus_num(vtd_as->bus); + uint16_t sid = PCI_BUILD_BDF(bus_n, vtd_as->devfn); + + vtd_report_sid_ir_illegal_access(vtd_as->iommu_state, sid, vtd_as->pasid, + addr, is_write); +} + static MemTxResult vtd_mem_ir_read(void *opaque, hwaddr addr, uint64_t *data, unsigned size, MemTxAttrs attrs) @@ -4425,9 +4488,11 @@ static MemTxResult vtd_mem_ir_write(void *opaque, hwaddr addr, uint64_t value, unsigned size, MemTxAttrs attrs) { + IntelIOMMUState *s = opaque; int ret = 0; MSIMessage from = {}, to = {}; uint16_t sid = X86_IOMMU_SID_INVALID; + uint32_t pasid; from.address = (uint64_t) addr + VTD_INTERRUPT_ADDR_FIRST; from.data = (uint32_t) value; @@ -4435,9 +4500,16 @@ static MemTxResult vtd_mem_ir_write(void *opaque, hwaddr addr, if (!attrs.unspecified) { /* We have explicit Source ID */ sid = attrs.requester_id; + pasid = attrs.pid != 0 ? attrs.pid : PCI_NO_PASID; + + if (attrs.address_type == PCI_AT_TRANSLATED && + sid != X86_IOMMU_SID_INVALID) { + vtd_report_sid_ir_illegal_access(s, sid, pasid, from.address, true); + return MEMTX_ERROR; + } } - ret = vtd_interrupt_remap_msi(opaque, &from, &to, sid, true); + ret = vtd_interrupt_remap_msi(s, &from, &to, sid, true); if (ret) { /* Drop this interrupt */ return MEMTX_ERROR; @@ -4462,30 +4534,6 @@ static const MemoryRegionOps vtd_mem_ir_ops = { }, }; -static void vtd_report_ir_illegal_access(VTDAddressSpace *vtd_as, - hwaddr addr, bool is_write) -{ - IntelIOMMUState *s = vtd_as->iommu_state; - uint8_t bus_n = pci_bus_num(vtd_as->bus); - uint16_t sid = PCI_BUILD_BDF(bus_n, vtd_as->devfn); - bool is_fpd_set = false; - VTDContextEntry ce; - - assert(vtd_as->pasid != PCI_NO_PASID); - - /* Try out best to fetch FPD, we can't do anything more */ - if (vtd_dev_to_context_entry(s, bus_n, vtd_as->devfn, &ce) == 0) { - is_fpd_set = ce.lo & VTD_CONTEXT_ENTRY_FPD; - if (!is_fpd_set && s->root_scalable) { - vtd_ce_get_pasid_fpd(s, &ce, &is_fpd_set, vtd_as->pasid); - } - } - - vtd_report_fault(s, VTD_FR_SM_INTERRUPT_ADDR, - is_fpd_set, sid, addr, is_write, - true, vtd_as->pasid); -} - static MemTxResult vtd_mem_ir_fault_read(void *opaque, hwaddr addr, uint64_t *data, unsigned size, MemTxAttrs attrs) @@ -5145,14 +5193,10 @@ static IOMMUTLBEntry vtd_iommu_ats_do_translate(IOMMUMemoryRegion *iommu, if (vtd_is_interrupt_addr(addr)) { vtd_report_ir_illegal_access(vtd_as, addr, flags & IOMMU_WO); + vtd_prepare_error_entry(&entry); entry.target_as = &address_space_memory; - entry.iova = 0; - entry.translated_addr = 0; - entry.addr_mask = ~VTD_PAGE_MASK_4K; - entry.perm = IOMMU_NONE; - entry.pasid = PCI_NO_PASID; } else { - entry = vtd_iommu_translate(iommu, addr, flags, 0); + entry = vtd_iommu_translate(iommu, addr, flags, VTD_IDX_UNTRANSLATED); } return entry; From b7e31028a303129c12a7f35e380804a0c8b93bcd Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:43 +0000 Subject: [PATCH 06/38] pcie: Add a function to check if pasid privileged mode is enabled Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-4-clement.mathieu--drif@eviden.com> --- hw/pci/pcie.c | 21 ++++++++++++++++----- include/hw/pci/pcie.h | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c index c481c16c0f..50fc4aa8eb 100644 --- a/hw/pci/pcie.c +++ b/hw/pci/pcie.c @@ -1340,6 +1340,16 @@ void pcie_pri_init(PCIDevice *dev, uint16_t offset, uint32_t outstanding_pr_cap, dev->exp.pri_cap = offset; } +static inline bool pcie_pasid_check_ctrl_bit_enabled(const PCIDevice *dev, + uint16_t mask) +{ + if (!pci_is_express(dev) || !dev->exp.pasid_cap) { + return false; + } + return (pci_get_word(dev->config + dev->exp.pasid_cap + PCI_PASID_CTRL) & + mask) != 0; +} + uint32_t pcie_pri_get_req_alloc(const PCIDevice *dev) { if (!pcie_pri_enabled(dev)) { @@ -1359,11 +1369,12 @@ bool pcie_pri_enabled(const PCIDevice *dev) bool pcie_pasid_enabled(const PCIDevice *dev) { - if (!pci_is_express(dev) || !dev->exp.pasid_cap) { - return false; - } - return (pci_get_word(dev->config + dev->exp.pasid_cap + PCI_PASID_CTRL) & - PCI_PASID_CTRL_ENABLE) != 0; + return pcie_pasid_check_ctrl_bit_enabled(dev, PCI_PASID_CTRL_ENABLE); +} + +bool pcie_pasid_priv_enabled(PCIDevice *dev) +{ + return pcie_pasid_check_ctrl_bit_enabled(dev, PCI_PASID_CTRL_PRIV); } bool pcie_ats_enabled(const PCIDevice *dev) diff --git a/include/hw/pci/pcie.h b/include/hw/pci/pcie.h index fc02aeb169..d4e065db82 100644 --- a/include/hw/pci/pcie.h +++ b/include/hw/pci/pcie.h @@ -165,5 +165,6 @@ void pcie_pri_init(PCIDevice *dev, uint16_t offset, uint32_t outstanding_pr_cap, uint32_t pcie_pri_get_req_alloc(const PCIDevice *dev); bool pcie_pri_enabled(const PCIDevice *dev); bool pcie_pasid_enabled(const PCIDevice *dev); +bool pcie_pasid_priv_enabled(PCIDevice *dev); bool pcie_ats_enabled(const PCIDevice *dev); #endif /* QEMU_PCIE_H */ From ccd162e5be9dbd08e4f523d612f3ec72a2cba00a Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:44 +0000 Subject: [PATCH 07/38] pci: Block ATS requests when privileged mode is disabled Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-5-clement.mathieu--drif@eviden.com> --- hw/pci/pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index 9035caca92..90d6d71efd 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -3171,6 +3171,10 @@ ssize_t pci_ats_request_translation(PCIDevice *dev, uint32_t pasid, return -EPERM; } + if (priv_req && !pcie_pasid_priv_enabled(dev)) { + return -EPERM; + } + pci_device_get_iommu_bus_devfn(dev, &iommu_bus, &bus, &devfn); if (iommu_bus && iommu_bus->iommu_ops->ats_request_translation) { return iommu_bus->iommu_ops->ats_request_translation(bus, From e08a72d2179a6f0da2509d49082105f16ded580f Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:45 +0000 Subject: [PATCH 08/38] intel_iommu: Handle insufficient permissions during translation requests ATS translations should not fail when the write permission is not set. Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-6-clement.mathieu--drif@eviden.com> --- hw/i386/intel_iommu.c | 14 ++++++++------ hw/i386/intel_iommu_internal.h | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 7e7c31cb55..6ba1c1676b 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -1998,7 +1998,7 @@ static int vtd_iova_to_fspte(IntelIOMMUState *s, VTDContextEntry *ce, uint64_t iova, bool is_write, uint64_t *fsptep, uint32_t *fspte_level, bool *reads, bool *writes, uint8_t aw_bits, - uint32_t pasid) + uint32_t pasid, int iommu_idx) { dma_addr_t addr = vtd_get_iova_pgtbl_base(s, ce, pasid); uint32_t offset; @@ -2039,7 +2039,8 @@ static int vtd_iova_to_fspte(IntelIOMMUState *s, VTDContextEntry *ce, *reads = true; *writes = (*writes) && (fspte & VTD_FS_RW); - if (is_write && !(fspte & VTD_FS_RW)) { + /* ATS should not fail when the write permission is not set */ + if (is_write && !(fspte & VTD_FS_RW) && iommu_idx != VTD_IDX_ATS) { return -VTD_FR_SM_WRITE; } if (vtd_fspte_nonzero_rsvd(fspte, *fspte_level)) { @@ -2098,7 +2099,7 @@ static void vtd_report_fault(IntelIOMMUState *s, */ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus, uint8_t devfn, hwaddr addr, bool is_write, - IOMMUTLBEntry *entry) + IOMMUTLBEntry *entry, int iommu_idx) { IntelIOMMUState *s = vtd_as->iommu_state; VTDContextEntry ce; @@ -2204,7 +2205,8 @@ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus, if (s->fsts && s->root_scalable) { ret_fr = vtd_iova_to_fspte(s, &ce, addr, is_write, &pte, &level, - &reads, &writes, s->aw_bits, pasid); + &reads, &writes, s->aw_bits, pasid, + iommu_idx); pgtt = VTD_SM_PASID_ENTRY_FST; } else { ret_fr = vtd_iova_to_sspte(s, &ce, addr, is_write, &pte, &level, @@ -4033,7 +4035,7 @@ static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr, } } else { success = vtd_do_iommu_translate(vtd_as, vtd_as->bus, vtd_as->devfn, - addr, is_write, &iotlb); + addr, is_write, &iotlb, iommu_idx); } } else { /* DMAR disabled, passthrough, use 4k-page*/ @@ -5196,7 +5198,7 @@ static IOMMUTLBEntry vtd_iommu_ats_do_translate(IOMMUMemoryRegion *iommu, vtd_prepare_error_entry(&entry); entry.target_as = &address_space_memory; } else { - entry = vtd_iommu_translate(iommu, addr, flags, VTD_IDX_UNTRANSLATED); + entry = vtd_iommu_translate(iommu, addr, flags, VTD_IDX_ATS); } return entry; diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h index be757c290d..18d50191d2 100644 --- a/hw/i386/intel_iommu_internal.h +++ b/hw/i386/intel_iommu_internal.h @@ -692,7 +692,8 @@ typedef struct VTDPIOTLBInvInfo { typedef enum VTDIOMMUIndex { VTD_IDX_UNTRANSLATED = 0, /* Default */ VTD_IDX_TRANSLATED = 1, - VTD_IDX_COUNT = 2, /* Number of supported indexes */ + VTD_IDX_ATS = 2, + VTD_IDX_COUNT = 3, /* Number of supported indexes */ } VTDIOMMUIndex; typedef struct VTDHostIOMMUDevice { From 0acb2b193b01665300a1ab5007a144fdaa6e340d Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:45 +0000 Subject: [PATCH 09/38] intel_iommu: Minimal handling of privileged ATS request The purpose of this commit is not to support privileged requests but to prevent devices from doing things they wouldn't be able to do with real hardware. We simply block privileged requests when the SRS ecap is not set and abort when the ecap is present. For now, its not worth implementing support for privileged requests because the kernel does not support it. (https://lore.kernel.org/linux-iommu/20230411064815.31456-11-baolu.lu@linux.intel.com/) However, we may consider working on it depending on how the development goes in the kernel. Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-7-clement.mathieu--drif@eviden.com> --- hw/i386/intel_iommu.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 6ba1c1676b..77adeed0cc 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -5190,13 +5190,27 @@ static IOMMUTLBEntry vtd_iommu_ats_do_translate(IOMMUMemoryRegion *iommu, hwaddr addr, IOMMUAccessFlags flags) { - IOMMUTLBEntry entry; + IOMMUTLBEntry entry = { .target_as = &address_space_memory }; VTDAddressSpace *vtd_as = container_of(iommu, VTDAddressSpace, iommu); + IntelIOMMUState *s = vtd_as->iommu_state; + + /* Guard that makes sure we avoid weird behaviors */ + if ((flags & IOMMU_PRIV) && (s->ecap & VTD_ECAP_SRS)) { + error_report_once("Privileged ATS not supported"); + abort(); + } if (vtd_is_interrupt_addr(addr)) { - vtd_report_ir_illegal_access(vtd_as, addr, flags & IOMMU_WO); vtd_prepare_error_entry(&entry); - entry.target_as = &address_space_memory; + vtd_report_ir_illegal_access(vtd_as, addr, flags & IOMMU_WO); + } else if ((flags & IOMMU_PRIV) && !(s->ecap & VTD_ECAP_SRS)) { + /* + * For translation-request-with-PASID with PR=1, remapping hardware + * not supporting supervisor requests (SRS=0 in the Extended + * Capability Register) forces R=W=E=0 in addition to setting PRIV=1. + */ + vtd_prepare_error_entry(&entry); + entry.perm = IOMMU_PRIV; } else { entry = vtd_iommu_translate(iommu, addr, flags, VTD_IDX_ATS); } From 952e1a6e90eb22a6deffa4e980277b135e9d077f Mon Sep 17 00:00:00 2001 From: CLEMENT MATHIEU--DRIF Date: Wed, 29 Oct 2025 10:51:46 +0000 Subject: [PATCH 10/38] intel_iommu: Add a CLI option to enable SVM Signed-off-by: Clement Mathieu--Drif Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251029105137.1097933-8-clement.mathieu--drif@eviden.com> --- hw/i386/intel_iommu.c | 45 ++++++++++++++++++++++++---------- hw/i386/intel_iommu_internal.h | 6 ++++- include/hw/i386/intel_iommu.h | 1 + 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 77adeed0cc..dd00079a40 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -2862,8 +2862,10 @@ static bool vtd_inv_desc_reserved_check(IntelIOMMUState *s, static bool vtd_process_wait_desc(IntelIOMMUState *s, VTDInvDesc *inv_desc) { - uint64_t mask[4] = {VTD_INV_DESC_WAIT_RSVD_LO, VTD_INV_DESC_WAIT_RSVD_HI, - VTD_INV_DESC_ALL_ONE, VTD_INV_DESC_ALL_ONE}; + uint64_t mask[4] = { + VTD_INV_DESC_WAIT_RSVD_LO(s->ecap), VTD_INV_DESC_WAIT_RSVD_HI, + VTD_INV_DESC_ALL_ONE, VTD_INV_DESC_ALL_ONE + }; bool ret = true; if (!vtd_inv_desc_reserved_check(s, inv_desc, mask, false, @@ -4186,6 +4188,7 @@ static const Property vtd_properties[] = { DEFINE_PROP_BOOL("x-flts", IntelIOMMUState, fsts, FALSE), DEFINE_PROP_BOOL("snoop-control", IntelIOMMUState, snoop_control, false), DEFINE_PROP_BOOL("x-pasid-mode", IntelIOMMUState, pasid, false), + DEFINE_PROP_BOOL("svm", IntelIOMMUState, svm, false), DEFINE_PROP_BOOL("dma-drain", IntelIOMMUState, dma_drain, true), DEFINE_PROP_BOOL("stale-tm", IntelIOMMUState, stale_tm, false), DEFINE_PROP_BOOL("fs1gp", IntelIOMMUState, fs1gp, true), @@ -5096,6 +5099,10 @@ static void vtd_init(IntelIOMMUState *s) vtd_spte_rsvd_large[3] &= ~VTD_SPTE_SNP; } + if (s->svm) { + s->ecap |= VTD_ECAP_PRS | VTD_ECAP_PDS | VTD_ECAP_NWFS; + } + vtd_reset_caches(s); /* Define registers with default values and bit semantics */ @@ -5573,6 +5580,29 @@ static bool vtd_decide_config(IntelIOMMUState *s, Error **errp) return false; } + if (s->svm) { + if (!x86_iommu->dt_supported) { + error_setg(errp, "Need to set device IOTLB for svm"); + return false; + } + + if (!s->fsts) { + error_setg(errp, "Need to set flts for svm"); + return false; + } + + if (!x86_iommu->dma_translation) { + error_setg(errp, "Need to set dma-translation for svm"); + return false; + } + + if (!s->pasid) { + error_setg(errp, "Need to set PASID support for svm"); + return false; + } + } + + return true; } @@ -5583,17 +5613,6 @@ static void vtd_realize(DeviceState *dev, Error **errp) X86MachineState *x86ms = X86_MACHINE(ms); PCIBus *bus = pcms->pcibus; IntelIOMMUState *s = INTEL_IOMMU_DEVICE(dev); - X86IOMMUState *x86_iommu = X86_IOMMU_DEVICE(s); - - if (s->pasid && x86_iommu->dt_supported) { - /* - * PASID-based-Device-TLB Invalidate Descriptor is not - * implemented and it requires support from vhost layer which - * needs to be implemented in the future. - */ - error_setg(errp, "PASID based device IOTLB is not supported"); - return; - } if (!vtd_decide_config(s, errp)) { return; diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h index 18d50191d2..11a53aa369 100644 --- a/hw/i386/intel_iommu_internal.h +++ b/hw/i386/intel_iommu_internal.h @@ -194,8 +194,10 @@ #define VTD_ECAP_PRS (1ULL << 29) #define VTD_ECAP_MHMV (15ULL << 20) #define VTD_ECAP_SRS (1ULL << 31) +#define VTD_ECAP_NWFS (1ULL << 33) #define VTD_ECAP_PSS (7ULL << 35) /* limit: MemTxAttrs::pid */ #define VTD_ECAP_PASID (1ULL << 40) +#define VTD_ECAP_PDS (1ULL << 42) #define VTD_ECAP_SMTS (1ULL << 43) #define VTD_ECAP_SSTS (1ULL << 46) #define VTD_ECAP_FSTS (1ULL << 47) @@ -417,7 +419,9 @@ typedef union VTDPRDesc VTDPRDesc; #define VTD_INV_DESC_WAIT_IF (1ULL << 4) #define VTD_INV_DESC_WAIT_FN (1ULL << 6) #define VTD_INV_DESC_WAIT_DATA_SHIFT 32 -#define VTD_INV_DESC_WAIT_RSVD_LO 0Xfffff180ULL +#define VTD_INV_DESC_WAIT_RSVD_LO(ecap) (0Xfffff100ULL | \ + (((ecap) & VTD_ECAP_PDS) \ + ? 0 : (1 << 7))) #define VTD_INV_DESC_WAIT_RSVD_HI 3ULL /* Masks for Context-cache Invalidation Descriptor */ diff --git a/include/hw/i386/intel_iommu.h b/include/hw/i386/intel_iommu.h index 6c61fd39c7..d600e5495f 100644 --- a/include/hw/i386/intel_iommu.h +++ b/include/hw/i386/intel_iommu.h @@ -275,6 +275,7 @@ struct IntelIOMMUState { bool scalable_mode; /* RO - is Scalable Mode supported? */ bool fsts; /* RO - is first stage translation supported? */ bool snoop_control; /* RO - is SNP filed supported? */ + bool svm; /* RO - is SVA/SVM supported? */ dma_addr_t root; /* Current root table pointer */ bool root_scalable; /* Type of root table (scalable or not) */ From 23a4edb6dbe3dd5223326bdf03ae955321669b5c Mon Sep 17 00:00:00 2001 From: Daniil Tatianin Date: Thu, 13 Nov 2025 11:58:42 +0300 Subject: [PATCH 11/38] virtio/vhost: don't consider non-MAP_SHARED regions public Just having a file descriptor is not enough to consider a memory region public. If QEMU didn't map it as MAP_SHARED (in case of share=off), guest writes to this region won't be visible to the vhost-user backend, thus causing it to read all zeroes or garbage. Make sure we don't pass such regions and include that to our definition of what a private region is. Signed-off-by: Daniil Tatianin Fixes: 552b25229c ("vhost: Rework memslot filtering and fix "used_memslot" tracking") Acked-by: Stefano Garzarella Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251113085842.323745-1-d-tatianin@yandex-team.ru> --- hw/virtio/vhost.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c index 31e9704cdc..c6a5928cb1 100644 --- a/hw/virtio/vhost.c +++ b/hw/virtio/vhost.c @@ -592,11 +592,13 @@ static bool vhost_section(struct vhost_dev *dev, MemoryRegionSection *section) /* * Some backends (like vhost-user) can only handle memory regions * that have an fd (can be mapped into a different process). Filter - * the ones without an fd out, if requested. - * - * TODO: we might have to limit to MAP_SHARED as well. + * the ones without an fd out, if requested. Also make sure that + * this region is mapped as shared so that the vhost backend can + * observe modifications to this region, otherwise we consider it + * private. */ - if (memory_region_get_fd(section->mr) < 0 && + if ((memory_region_get_fd(section->mr) < 0 || + !qemu_ram_is_shared(section->mr->ram_block)) && dev->vhost_ops->vhost_backend_no_private_memslots && dev->vhost_ops->vhost_backend_no_private_memslots(dev)) { trace_vhost_reject_section(mr->name, 2); From fd3a2c601ab4a1bdb669e4c584b364e00a978702 Mon Sep 17 00:00:00 2001 From: Wafer Xie Date: Wed, 19 Nov 2025 21:24:52 +0800 Subject: [PATCH 12/38] vdpa: fix vhost-vdpa suspended state not be shared MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When stopping a vhost-vdpa device, only the first queue pair is marked as suspended, while the remaining queues are not updated to the suspended state. As a result, when stopping a multi-queue vhost-vdpa device, the following error message will be printed. qemu-system-x86_64:vhost VQ 2 ring restore failed: -1: Operation not permitted (1) qemu-system-x86_64:vhost VQ 3 ring restore failed: -1: Operation not permitted (1) So move v->suspended to v->shared, and then all the vhost_vdpa devices cannot have different suspended states. Fixes: 0bb302a9960a ("vdpa: add vhost_vdpa_suspend") Suggested-by: Eugenio Pérez Acked-by: Eugenio Pérez Acked-by: Jason Wang Signed-off-by: Wafer Xie Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Acked-by: Jason Wang Message-Id: <20251119132452.3117-1-wafer@jaguarmicro.com> --- hw/virtio/vhost-vdpa.c | 6 +++--- include/hw/virtio/vhost-vdpa.h | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/hw/virtio/vhost-vdpa.c b/hw/virtio/vhost-vdpa.c index 7061b6e1a3..2f8f11df86 100644 --- a/hw/virtio/vhost-vdpa.c +++ b/hw/virtio/vhost-vdpa.c @@ -905,7 +905,7 @@ static int vhost_vdpa_reset_device(struct vhost_dev *dev) memory_listener_unregister(&v->shared->listener); v->shared->listener_registered = false; - v->suspended = false; + v->shared->suspended = false; return 0; } @@ -1354,7 +1354,7 @@ static void vhost_vdpa_suspend(struct vhost_dev *dev) if (unlikely(r)) { error_report("Cannot suspend: %s(%d)", g_strerror(errno), errno); } else { - v->suspended = true; + v->shared->suspended = true; return; } } @@ -1481,7 +1481,7 @@ static int vhost_vdpa_get_vring_base(struct vhost_dev *dev, return 0; } - if (!v->suspended) { + if (!v->shared->suspended) { /* * Cannot trust in value returned by device, let vhost recover used * idx from guest. diff --git a/include/hw/virtio/vhost-vdpa.h b/include/hw/virtio/vhost-vdpa.h index 449bf5c840..80ff670e23 100644 --- a/include/hw/virtio/vhost-vdpa.h +++ b/include/hw/virtio/vhost-vdpa.h @@ -76,6 +76,12 @@ typedef struct vhost_vdpa_shared { /* SVQ switching is in progress, or already completed? */ SVQTransitionState svq_switching; + + /* + * Device suspended successfully. + * The vhost_vdpa devices cannot have different suspended states. + */ + bool suspended; } VhostVDPAShared; typedef struct vhost_vdpa { @@ -83,8 +89,6 @@ typedef struct vhost_vdpa { uint32_t address_space_id; uint64_t acked_features; bool shadow_vqs_enabled; - /* Device suspended successfully */ - bool suspended; VhostVDPAShared *shared; GPtrArray *shadow_vqs; const VhostShadowVirtqueueOps *shadow_vq_ops; From 1ab76ad7608d4b3da66e4886067b116120433716 Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Tue, 2 Dec 2025 00:17:59 +1000 Subject: [PATCH 13/38] acpi/ghes: Automate data block cleanup in acpi_ghes_memory_errors() Use g_auto_ptr() to automate data block cleanup in the function so that it won't be a burden to us. Suggested-by: Markus Armbruster Signed-off-by: Gavin Shan Reviewed-by: Markus Armbruster Reviewed-by: Igor Mammedov Reviewed-by: Mauro Carvalho Chehab Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251201141803.2386129-2-gshan@redhat.com> --- hw/acpi/ghes.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/hw/acpi/ghes.c b/hw/acpi/ghes.c index 5445dc11bd..4e6903a8d3 100644 --- a/hw/acpi/ghes.c +++ b/hw/acpi/ghes.c @@ -566,9 +566,7 @@ int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, 0xED, 0x7C, 0x83, 0xB1); Error *err = NULL; int data_length; - GArray *block; - - block = g_array_new(false, true /* clear */, 1); + g_autoptr(GArray) block = g_array_new(false, true /* clear */, 1); data_length = ACPI_GHES_DATA_LENGTH + ACPI_GHES_MEM_CPER_LENGTH; /* @@ -586,8 +584,6 @@ int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, /* Report the error */ ghes_record_cper_errors(ags, block->data, block->len, source_id, &err); - g_array_free(block, true); - if (err) { error_report_err(err); return -1; From 27814d0e63f911432f493de2d74c4d030010afc0 Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Tue, 2 Dec 2025 00:18:00 +1000 Subject: [PATCH 14/38] acpi/ghes: Abort in acpi_ghes_memory_errors() if necessary The function hw/acpi/ghes-stub.c::acpi_ghes_memory_errors() shouldn't be called by any one. Take g_assert_not_reached() as a clearer indication. Suggested-by: Markus Armbruster Signed-off-by: Gavin Shan Reviewed-by: Igor Mammedov Reviewed-by: Mauro Carvalho Chehab Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251201141803.2386129-3-gshan@redhat.com> --- hw/acpi/ghes-stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/acpi/ghes-stub.c b/hw/acpi/ghes-stub.c index 40f660c246..b54f1b093c 100644 --- a/hw/acpi/ghes-stub.c +++ b/hw/acpi/ghes-stub.c @@ -14,7 +14,7 @@ int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, uint64_t physical_address) { - return -1; + g_assert_not_reached(); } AcpiGhesState *acpi_ghes_get_state(void) From 0352e85fe5d9233f1a3099eab207b54078a16cb9 Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Tue, 2 Dec 2025 00:18:01 +1000 Subject: [PATCH 15/38] target/arm/kvm: Exit on error from acpi_ghes_memory_errors() A core dump is no sense as there isn't programming bugs related to errors from acpi_ghes_memory_errors(). Exit instead of abort when the function returns errors, and the excessive error message is also dropped. Suggested-by: Igor Mammedov Suggested-by: Markus Armbruster Signed-off-by: Gavin Shan Reviewed-by: Igor Mammedov Reviewed-by: Mauro Carvalho Chehab Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251201141803.2386129-4-gshan@redhat.com> --- target/arm/kvm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/target/arm/kvm.c b/target/arm/kvm.c index 0828e8b87b..b83f1d5e4f 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -2477,8 +2477,7 @@ void kvm_arch_on_sigbus_vcpu(CPUState *c, int code, void *addr) paddr)) { kvm_inject_arm_sea(c); } else { - error_report("failed to record the error"); - abort(); + exit(1); } } return; From b2782920e039a1d6bc0eff03a0cad316ec10a8e5 Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Tue, 2 Dec 2025 00:18:02 +1000 Subject: [PATCH 16/38] acpi/ghes: Bail early on error from get_ghes_source_offsets() In ghes_record_cper_errors(), get_ghes_source_offsets() can return a error initialized by error_setg(). Without bailing on this error, it can call into the second error_setg() due to the unexpected value returned from the read acknowledgement register. The second error_setg() can trigger assert(*errp == NULL) in its callee error_setv(), which isn't expected. Bail early in ghes_record_cper_errors() when error is received from get_ghes_source_offsets() to avoid the unexpected behavior. Signed-off-by: Gavin Shan Reviewed-by: Jonathan Cameron Reviewed-by: Markus Armbruster Reviewed-by: Igor Mammedov Reviewed-by: Mauro Carvalho Chehab Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251201141803.2386129-5-gshan@redhat.com> --- hw/acpi/ghes.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hw/acpi/ghes.c b/hw/acpi/ghes.c index 4e6903a8d3..d51d4bd466 100644 --- a/hw/acpi/ghes.c +++ b/hw/acpi/ghes.c @@ -444,7 +444,7 @@ static void get_hw_error_offsets(uint64_t ghes_addr, *read_ack_register_addr = ghes_addr + sizeof(uint64_t); } -static void get_ghes_source_offsets(uint16_t source_id, +static bool get_ghes_source_offsets(uint16_t source_id, uint64_t hest_addr, uint64_t *cper_addr, uint64_t *read_ack_start_addr, @@ -475,7 +475,7 @@ static void get_ghes_source_offsets(uint16_t source_id, /* For now, we only know the size of GHESv2 table */ if (type != ACPI_GHES_SOURCE_GENERIC_ERROR_V2) { error_setg(errp, "HEST: type %d not supported.", type); - return; + return false; } /* Compare CPER source ID at the GHESv2 structure */ @@ -489,7 +489,7 @@ static void get_ghes_source_offsets(uint16_t source_id, } if (i == num_sources) { error_setg(errp, "HEST: Source %d not found.", source_id); - return; + return false; } /* Navigate through table address pointers */ @@ -509,6 +509,8 @@ static void get_ghes_source_offsets(uint16_t source_id, cpu_physical_memory_read(hest_read_ack_addr, read_ack_start_addr, sizeof(*read_ack_start_addr)); *read_ack_start_addr = le64_to_cpu(*read_ack_start_addr); + + return true; } NotifierList acpi_generic_error_notifiers = @@ -527,9 +529,10 @@ void ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, if (!ags->use_hest_addr) { get_hw_error_offsets(le64_to_cpu(ags->hw_error_le), &cper_addr, &read_ack_register_addr); - } else { - get_ghes_source_offsets(source_id, le64_to_cpu(ags->hest_addr_le), - &cper_addr, &read_ack_register_addr, errp); + } else if (!get_ghes_source_offsets(source_id, + le64_to_cpu(ags->hest_addr_le), + &cper_addr, &read_ack_register_addr, errp)) { + return; } cpu_physical_memory_read(read_ack_register_addr, From 0771574c2f985b917d6d323ac417bb256af76346 Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Tue, 2 Dec 2025 00:18:03 +1000 Subject: [PATCH 17/38] acpi/ghes: Use error_fatal in acpi_ghes_memory_errors() Use error_fatal in acpi_ghes_memory_errors() so that the caller needn't explicitly call exit(). The return value of acpi_ghes_memory_errors() and ghes_record_cper_errors() is changed to 'bool' indicating an error has been raised, to be compatible with what's documented in error.h. Suggested-by: Igor Mammedov Suggested-by: Markus Armbruster Signed-off-by: Gavin Shan Reviewed-by: Jonathan Cameron Reviewed-by: Markus Armbruster Reviewed-by: Igor Mammedov Reviewed-by: Mauro Carvalho Chehab Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251201141803.2386129-6-gshan@redhat.com> --- hw/acpi/ghes-stub.c | 4 ++-- hw/acpi/ghes.c | 26 ++++++++++---------------- include/hw/acpi/ghes.h | 6 +++--- target/arm/kvm.c | 9 +++------ 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/hw/acpi/ghes-stub.c b/hw/acpi/ghes-stub.c index b54f1b093c..5f9313cce9 100644 --- a/hw/acpi/ghes-stub.c +++ b/hw/acpi/ghes-stub.c @@ -11,8 +11,8 @@ #include "qemu/osdep.h" #include "hw/acpi/ghes.h" -int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, - uint64_t physical_address) +bool acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, + uint64_t physical_address, Error **errp) { g_assert_not_reached(); } diff --git a/hw/acpi/ghes.c b/hw/acpi/ghes.c index d51d4bd466..c42f1721c4 100644 --- a/hw/acpi/ghes.c +++ b/hw/acpi/ghes.c @@ -516,14 +516,14 @@ static bool get_ghes_source_offsets(uint16_t source_id, NotifierList acpi_generic_error_notifiers = NOTIFIER_LIST_INITIALIZER(acpi_generic_error_notifiers); -void ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, +bool ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, uint16_t source_id, Error **errp) { uint64_t cper_addr = 0, read_ack_register_addr = 0, read_ack_register; if (len > ACPI_GHES_MAX_RAW_DATA_LENGTH) { error_setg(errp, "GHES CPER record is too big: %zd", len); - return; + return false; } if (!ags->use_hest_addr) { @@ -532,7 +532,7 @@ void ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, } else if (!get_ghes_source_offsets(source_id, le64_to_cpu(ags->hest_addr_le), &cper_addr, &read_ack_register_addr, errp)) { - return; + return false; } cpu_physical_memory_read(read_ack_register_addr, @@ -543,7 +543,7 @@ void ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, error_setg(errp, "OSPM does not acknowledge previous error," " so can not record CPER for current error anymore"); - return; + return false; } read_ack_register = cpu_to_le64(0); @@ -558,16 +558,17 @@ void ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, cpu_physical_memory_write(cper_addr, cper, len); notifier_list_notify(&acpi_generic_error_notifiers, &source_id); + + return true; } -int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, - uint64_t physical_address) +bool acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, + uint64_t physical_address, Error **errp) { /* Memory Error Section Type */ const uint8_t guid[] = UUID_LE(0xA5BC1114, 0x6F64, 0x4EDE, 0xB8, 0x63, 0x3E, 0x83, \ 0xED, 0x7C, 0x83, 0xB1); - Error *err = NULL; int data_length; g_autoptr(GArray) block = g_array_new(false, true /* clear */, 1); @@ -584,15 +585,8 @@ int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, /* Build the memory section CPER for above new generic error data entry */ acpi_ghes_build_append_mem_cper(block, physical_address); - /* Report the error */ - ghes_record_cper_errors(ags, block->data, block->len, source_id, &err); - - if (err) { - error_report_err(err); - return -1; - } - - return 0; + return ghes_record_cper_errors(ags, block->data, block->len, + source_id, errp); } AcpiGhesState *acpi_ghes_get_state(void) diff --git a/include/hw/acpi/ghes.h b/include/hw/acpi/ghes.h index df2ecbf6e4..5b29aae4dd 100644 --- a/include/hw/acpi/ghes.h +++ b/include/hw/acpi/ghes.h @@ -98,9 +98,9 @@ void acpi_build_hest(AcpiGhesState *ags, GArray *table_data, const char *oem_id, const char *oem_table_id); void acpi_ghes_add_fw_cfg(AcpiGhesState *vms, FWCfgState *s, GArray *hardware_errors); -int acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, - uint64_t error_physical_addr); -void ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, +bool acpi_ghes_memory_errors(AcpiGhesState *ags, uint16_t source_id, + uint64_t error_physical_addr, Error **errp); +bool ghes_record_cper_errors(AcpiGhesState *ags, const void *cper, size_t len, uint16_t source_id, Error **errp); /** diff --git a/target/arm/kvm.c b/target/arm/kvm.c index b83f1d5e4f..3e35570f15 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -2473,12 +2473,9 @@ void kvm_arch_on_sigbus_vcpu(CPUState *c, int code, void *addr) */ if (code == BUS_MCEERR_AR) { kvm_cpu_synchronize_state(c); - if (!acpi_ghes_memory_errors(ags, ACPI_HEST_SRC_ID_SYNC, - paddr)) { - kvm_inject_arm_sea(c); - } else { - exit(1); - } + acpi_ghes_memory_errors(ags, ACPI_HEST_SRC_ID_SYNC, + paddr, &error_fatal); + kvm_inject_arm_sea(c); } return; } From 459cfb80a77f38b521a887338d05e82a9ea2f0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Mon, 1 Dec 2025 13:51:49 +0100 Subject: [PATCH 18/38] vhost: accept indirect descriptors in shadow virtqueue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The device is not able to generate indirect descriptors by itself, only the driver can do it. And SVQ is already able to handle them by reusing VirtQueue struct. Shadow VirtQueue just transform the indirect descriptors to chained buffers. This way we don't need to map a new buffer for the indirect table and handle its exhaustion etc. As they are only used in control plane and HW devices don't like indirect, we should not see significant downsides with these. Signed-off-by: Eugenio Pérez -- Tested functionally by forwarding the CVQ descriptors. Acked-by: Jason Wang Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251201125149.2151026-1-eperezma@redhat.com> --- hw/virtio/vhost-shadow-virtqueue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/virtio/vhost-shadow-virtqueue.c b/hw/virtio/vhost-shadow-virtqueue.c index 2481d49345..6242aeb69c 100644 --- a/hw/virtio/vhost-shadow-virtqueue.c +++ b/hw/virtio/vhost-shadow-virtqueue.c @@ -34,6 +34,7 @@ bool vhost_svq_valid_features(uint64_t features, Error **errp) switch (b) { case VIRTIO_F_ANY_LAYOUT: case VIRTIO_RING_F_EVENT_IDX: + case VIRTIO_RING_F_INDIRECT_DESC: continue; case VIRTIO_F_ACCESS_PLATFORM: From fff77cfb8413190c6362b95203ef0973c83b50d2 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Thu, 4 Dec 2025 17:20:13 +0100 Subject: [PATCH 19/38] virtio-dmabuf: Ensure UUID persistence for hash table insertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In `virtio_add_resource` function, the UUID used as a key for `g_hash_table_insert` was temporary, which could lead to invalid lookups when accessed later. This patch ensures that the UUID remains valid by duplicating it into a newly allocated memory space. The value is then inserted into the hash table with this persistent UUID key to ensure that the key stored in the hash table remains valid as long as the hash table entry exists. Fixes: faefdba847 ("hw/display: introduce virtio-dmabuf") Signed-off-by: Dorinda Bassey Reviewed-by: Stefano Garzarella Reviewed-by: Albert Esteve Reviewed-by: Marc-André Lureau Reviewed-by: Jim MacArthur Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251204162129.262745-1-dbassey@redhat.com> --- hw/display/virtio-dmabuf.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/display/virtio-dmabuf.c b/hw/display/virtio-dmabuf.c index 3dba4577ca..5e0395be77 100644 --- a/hw/display/virtio-dmabuf.c +++ b/hw/display/virtio-dmabuf.c @@ -35,11 +35,13 @@ static bool virtio_add_resource(QemuUUID *uuid, VirtioSharedObject *value) if (resource_uuids == NULL) { resource_uuids = g_hash_table_new_full(qemu_uuid_hash, uuid_equal_func, - NULL, + g_free, g_free); } if (g_hash_table_lookup(resource_uuids, uuid) == NULL) { - g_hash_table_insert(resource_uuids, uuid, value); + g_hash_table_insert(resource_uuids, + g_memdup2(uuid, sizeof(*uuid)), + value); } else { result = false; } From 623db856476806124e9ae45fbc39e75012261570 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Thu, 4 Dec 2025 18:26:57 +0100 Subject: [PATCH 20/38] virtio: Fix crash when sriov-pf is set for non-PCI-Express device Setting the sriov-pf property on devices that aren't PCI Express causes an assertion failure: $ qemu-system-x86_64 \ -blockdev null-co,node-name=null \ -blockdev null-co,node-name=null2 \ -device virtio-blk,drive=null,id=pf \ -device virtio-blk,sriov-pf=pf,drive=null2 qemu-system-x86_64: ../hw/pci/pcie.c:1062: void pcie_add_capability(PCIDevice *, uint16_t, uint8_t, uint16_t, uint16_t): Assertion `offset >= PCI_CONFIG_SPACE_SIZE' failed. This is because proxy->last_pcie_cap_offset is only initialised to a non-zero value in virtio_pci_realize() if it's a PCI Express device, and then virtio_pci_device_plugged() still tries to use it. To fix this, just skip the SR-IOV code for !pci_is_express(). Then the next thing pci_qdev_realize() does is call pcie_sriov_register_device(), which returns the appropriate error. Cc: qemu-stable@nongnu.org Fixes: d0c280d3fac6 ('pcie_sriov: Make a PCI device with user-created VF ARI-capable') Signed-off-by: Kevin Wolf Reviewed-by: Stefan Hajnoczi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251204172657.174391-1-kwolf@redhat.com> --- hw/virtio/virtio-pci.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index b273eb2691..1e8f90df34 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -2183,15 +2183,17 @@ static void virtio_pci_device_plugged(DeviceState *d, Error **errp) PCI_BASE_ADDRESS_SPACE_IO, &proxy->bar); } - if (pci_is_vf(&proxy->pci_dev)) { - pcie_ari_init(&proxy->pci_dev, proxy->last_pcie_cap_offset); - proxy->last_pcie_cap_offset += PCI_ARI_SIZEOF; - } else { - res = pcie_sriov_pf_init_from_user_created_vfs( - &proxy->pci_dev, proxy->last_pcie_cap_offset, errp); - if (res > 0) { - proxy->last_pcie_cap_offset += res; - virtio_add_feature(&vdev->host_features, VIRTIO_F_SR_IOV); + if (pci_is_express(&proxy->pci_dev)) { + if (pci_is_vf(&proxy->pci_dev)) { + pcie_ari_init(&proxy->pci_dev, proxy->last_pcie_cap_offset); + proxy->last_pcie_cap_offset += PCI_ARI_SIZEOF; + } else { + res = pcie_sriov_pf_init_from_user_created_vfs( + &proxy->pci_dev, proxy->last_pcie_cap_offset, errp); + if (res > 0) { + proxy->last_pcie_cap_offset += res; + virtio_add_feature(&vdev->host_features, VIRTIO_F_SR_IOV); + } } } } From f73e5ed9bc4cfacf041323a6b40a85e6b6459b75 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 5 Dec 2025 15:57:18 +0100 Subject: [PATCH 21/38] pcie_sriov: Fix PCI_SRIOV_* accesses in pcie_sriov_pf_exit() PCI_SRIOV_* are offsets into the SR-IOV capability, not into the PCI config space. pcie_sriov_pf_exit() erroneously takes them as the latter, which makes it read PCI_HEADER_TYPE and PCI_BIST when it tries to read PCI_SRIOV_TOTAL_VF. In many cases we're lucky enough that the PCI config space will be 0 there, so we just skip the whole for loop, but this isn't guaranteed. For example, setting the multifunction bit on the PF and then doing a 'device_del' on it will get a larger number and cause a segfault. Fix this and access the real PCI_SRIOV_* fields in the capability. Cc: qemu-stable@nongnu.org Fixes: 19e55471d4e8 ('pcie_sriov: Allow user to create SR-IOV device') Signed-off-by: Kevin Wolf Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251205145718.55136-1-kwolf@redhat.com> --- hw/pci/pcie_sriov.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index 34e0875d21..c41ac95bee 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -195,14 +195,17 @@ bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset, void pcie_sriov_pf_exit(PCIDevice *dev) { + uint8_t *cfg; + if (dev->exp.sriov_cap == 0) { return; } + cfg = dev->config + dev->exp.sriov_cap; if (dev->exp.sriov_pf.vf_user_created) { uint16_t ven_id = pci_get_word(dev->config + PCI_VENDOR_ID); - uint16_t total_vfs = pci_get_word(dev->config + PCI_SRIOV_TOTAL_VF); - uint16_t vf_dev_id = pci_get_word(dev->config + PCI_SRIOV_VF_DID); + uint16_t total_vfs = pci_get_word(cfg + PCI_SRIOV_TOTAL_VF); + uint16_t vf_dev_id = pci_get_word(cfg + PCI_SRIOV_VF_DID); unregister_vfs(dev); @@ -213,8 +216,6 @@ void pcie_sriov_pf_exit(PCIDevice *dev) pci_config_set_device_id(dev->exp.sriov_pf.vf[i]->config, vf_dev_id); } } else { - uint8_t *cfg = dev->config + dev->exp.sriov_cap; - unparent_vfs(dev, pci_get_word(cfg + PCI_SRIOV_TOTAL_VF)); } } From 66cf169e29b06dca104c5a229fba0da4ce33599c Mon Sep 17 00:00:00 2001 From: Igor Mammedov Date: Thu, 11 Dec 2025 17:54:54 +0100 Subject: [PATCH 22/38] q35: Fix migration of SMRAM state When migrating, dst QEMU by default has SMRAM unlocked, and since wmask is not migrated, the migrated value of MCH_HOST_BRIDGE_F_SMBASE in config space fall to prey of mch_update_smbase_smram() ... if (pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] == 0xff) { *reg = 0x00; and is getting cleared and leads to unlocked smram on dst even if on source it's been locked. As Andrey has pointed out [1], we should derive wmask from config and not other way around. Drop offending chunk and resync wmask based on MCH_HOST_BRIDGE_F_SMBASE register value. That would preserve the register during migration and set smram regions into corresponding state. What that changes is: that it would let guest write junk values in register (with no apparent effect) until it's stumbles upon reserved 0x1 [|] 0x2 values, at which point it would be only possible to lock register and trigger switch to SMRAM blackhole in CPU AS. While at it, fix up test by removing junk discard before negotiation hunk. PS2: Instead of adding a dedicated post_load handler for it, reuse mch_update->mch_update_smbase_smram call chain that is called on write/reset/post_load to be consistent with how we handle mch registers. PS3: for prosterity here is erro message Andrey got due to this bug: qemu: vfio_container_dma_map(0x..., 0x0, 0xa0000, 0x....) = -22 (Invalid argument) qemu: hardware error: vfio: DMA mapping failed, unable to continue 1) https://patchew.org/QEMU/20251203180851.6390-1-arbn@yandex-team.com/ Fixes: f404220e279c ("q35: implement 128K SMRAM at default SMBASE address") Reported-by: Andrey Ryabinin Signed-off-by: Igor Mammedov Reviewed-by: Andrey Ryabinin Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251211165454.288476-1-imammedo@redhat.com> --- hw/pci-host/q35.c | 27 ++++++++++++--------------- tests/qtest/q35-test.c | 6 ------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c index b353d3e1e6..e85e4227b3 100644 --- a/hw/pci-host/q35.c +++ b/hw/pci-host/q35.c @@ -431,30 +431,27 @@ static void mch_update_smbase_smram(MCHPCIState *mch) } if (*reg == MCH_HOST_BRIDGE_F_SMBASE_QUERY) { - pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] = - MCH_HOST_BRIDGE_F_SMBASE_LCK; + pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] = MCH_HOST_BRIDGE_F_SMBASE_LCK; *reg = MCH_HOST_BRIDGE_F_SMBASE_IN_RAM; return; } /* - * default/reset state, discard written value - * which will disable SMRAM balackhole at SMBASE + * reg value can come from register write/reset/migration source, + * update wmask to be in sync with it regardless of source */ - if (pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] == 0xff) { - *reg = 0x00; + if (*reg == MCH_HOST_BRIDGE_F_SMBASE_IN_RAM) { + pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] = MCH_HOST_BRIDGE_F_SMBASE_LCK; + return; + } + if (*reg & MCH_HOST_BRIDGE_F_SMBASE_LCK) { + /* lock register at 0x2 and disable all writes */ + pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] = 0; + *reg = MCH_HOST_BRIDGE_F_SMBASE_LCK; } + lck = *reg & MCH_HOST_BRIDGE_F_SMBASE_LCK; memory_region_transaction_begin(); - if (*reg & MCH_HOST_BRIDGE_F_SMBASE_LCK) { - /* disable all writes */ - pd->wmask[MCH_HOST_BRIDGE_F_SMBASE] &= - ~MCH_HOST_BRIDGE_F_SMBASE_LCK; - *reg = MCH_HOST_BRIDGE_F_SMBASE_LCK; - lck = true; - } else { - lck = false; - } memory_region_set_enabled(&mch->smbase_blackhole, lck); memory_region_set_enabled(&mch->smbase_window, lck); memory_region_transaction_commit(); diff --git a/tests/qtest/q35-test.c b/tests/qtest/q35-test.c index 62fff49fc8..4e3a4457f6 100644 --- a/tests/qtest/q35-test.c +++ b/tests/qtest/q35-test.c @@ -206,12 +206,6 @@ static void test_smram_smbase_lock(void) qtest_writeb(qts, SMBASE, SMRAM_TEST_PATTERN); g_assert_cmpint(qtest_readb(qts, SMBASE), ==, SMRAM_TEST_PATTERN); - /* check that writing junk to 0x9c before before negotiating is ignored */ - for (i = 0; i < 0xff; i++) { - qpci_config_writeb(pcidev, MCH_HOST_BRIDGE_F_SMBASE, i); - g_assert(qpci_config_readb(pcidev, MCH_HOST_BRIDGE_F_SMBASE) == 0); - } - /* enable SMRAM at SMBASE */ qpci_config_writeb(pcidev, MCH_HOST_BRIDGE_F_SMBASE, 0xff); g_assert(qpci_config_readb(pcidev, MCH_HOST_BRIDGE_F_SMBASE) == 0x01); From f4cc404c823b17c89c16e232ebebeaa51823be70 Mon Sep 17 00:00:00 2001 From: Haixu Cui Date: Tue, 16 Dec 2025 11:21:21 +0800 Subject: [PATCH 23/38] standard-headers: Update virtio_spi.h from Linux v6.18-rc3 Synced virtio_spi.h from upstream Linux kernelusing scripts/update-linux-headers.sh. Source: - Linux commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa (v6.18-rc3) - Author: Linus Torvalds - Date: Sun Oct 26 15:59:49 2025 -0700 This update ensures QEMU's standard headers are consistent with the latest virtio SPI definitions from Linux v6.18-rc3. Signed-off-by: Haixu Cui Reviewed-by: Manos Pitsidianakis Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251216032122.1316684-2-quic_haixcui@quicinc.com> --- include/standard-headers/linux/virtio_spi.h | 181 ++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 include/standard-headers/linux/virtio_spi.h diff --git a/include/standard-headers/linux/virtio_spi.h b/include/standard-headers/linux/virtio_spi.h new file mode 100644 index 0000000000..54e570fb4a --- /dev/null +++ b/include/standard-headers/linux/virtio_spi.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (C) 2023 OpenSynergy GmbH + * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef _LINUX_VIRTIO_VIRTIO_SPI_H +#define _LINUX_VIRTIO_VIRTIO_SPI_H + +#include "standard-headers/linux/types.h" +#include "standard-headers/linux/virtio_config.h" +#include "standard-headers/linux/virtio_ids.h" +#include "standard-headers/linux/virtio_types.h" + +/* Sample data on trailing clock edge */ +#define VIRTIO_SPI_CPHA _BITUL(0) +/* Clock is high when IDLE */ +#define VIRTIO_SPI_CPOL _BITUL(1) +/* Chip Select is active high */ +#define VIRTIO_SPI_CS_HIGH _BITUL(2) +/* Transmit LSB first */ +#define VIRTIO_SPI_MODE_LSB_FIRST _BITUL(3) +/* Loopback mode */ +#define VIRTIO_SPI_MODE_LOOP _BITUL(4) + +/** + * struct virtio_spi_config - All config fields are read-only for the + * Virtio SPI driver + * @cs_max_number: maximum number of chipselect the host SPI controller + * supports. + * @cs_change_supported: indicates if the host SPI controller supports to toggle + * chipselect after each transfer in one message: + * 0: unsupported, chipselect will be kept in active state throughout the + * message transaction; + * 1: supported. + * Note: Message here contains a sequence of SPI transfers. + * @tx_nbits_supported: indicates the supported number of bit for writing: + * bit 0: DUAL (2-bit transfer), 1 for supported + * bit 1: QUAD (4-bit transfer), 1 for supported + * bit 2: OCTAL (8-bit transfer), 1 for supported + * other bits are reserved as 0, 1-bit transfer is always supported. + * @rx_nbits_supported: indicates the supported number of bit for reading: + * bit 0: DUAL (2-bit transfer), 1 for supported + * bit 1: QUAD (4-bit transfer), 1 for supported + * bit 2: OCTAL (8-bit transfer), 1 for supported + * other bits are reserved as 0, 1-bit transfer is always supported. + * @bits_per_word_mask: mask indicating which values of bits_per_word are + * supported. If not set, no limitation for bits_per_word. + * @mode_func_supported: indicates the following features are supported or not: + * bit 0-1: CPHA feature + * 0b00: invalid, should support as least one CPHA setting + * 0b01: supports CPHA=0 only + * 0b10: supports CPHA=1 only + * 0b11: supports CPHA=0 and CPHA=1. + * bit 2-3: CPOL feature + * 0b00: invalid, should support as least one CPOL setting + * 0b01: supports CPOL=0 only + * 0b10: supports CPOL=1 only + * 0b11: supports CPOL=0 and CPOL=1. + * bit 4: chipselect active high feature, 0 for unsupported and 1 for + * supported, chipselect active low is supported by default. + * bit 5: LSB first feature, 0 for unsupported and 1 for supported, + * MSB first is supported by default. + * bit 6: loopback mode feature, 0 for unsupported and 1 for supported, + * normal mode is supported by default. + * @max_freq_hz: the maximum clock rate supported in Hz unit, 0 means no + * limitation for transfer speed. + * @max_word_delay_ns: the maximum word delay supported, in nanoseconds. + * A value of 0 indicates that word delay is unsupported. + * Each transfer may consist of a sequence of words. + * @max_cs_setup_ns: the maximum delay supported after chipselect is asserted, + * in ns unit, 0 means delay is not supported to introduce after chipselect is + * asserted. + * @max_cs_hold_ns: the maximum delay supported before chipselect is deasserted, + * in ns unit, 0 means delay is not supported to introduce before chipselect + * is deasserted. + * @max_cs_incative_ns: maximum delay supported after chipselect is deasserted, + * in ns unit, 0 means delay is not supported to introduce after chipselect is + * deasserted. + */ +struct virtio_spi_config { + uint8_t cs_max_number; + uint8_t cs_change_supported; +#define VIRTIO_SPI_RX_TX_SUPPORT_DUAL _BITUL(0) +#define VIRTIO_SPI_RX_TX_SUPPORT_QUAD _BITUL(1) +#define VIRTIO_SPI_RX_TX_SUPPORT_OCTAL _BITUL(2) + uint8_t tx_nbits_supported; + uint8_t rx_nbits_supported; + uint32_t bits_per_word_mask; +#define VIRTIO_SPI_MF_SUPPORT_CPHA_0 _BITUL(0) +#define VIRTIO_SPI_MF_SUPPORT_CPHA_1 _BITUL(1) +#define VIRTIO_SPI_MF_SUPPORT_CPOL_0 _BITUL(2) +#define VIRTIO_SPI_MF_SUPPORT_CPOL_1 _BITUL(3) +#define VIRTIO_SPI_MF_SUPPORT_CS_HIGH _BITUL(4) +#define VIRTIO_SPI_MF_SUPPORT_LSB_FIRST _BITUL(5) +#define VIRTIO_SPI_MF_SUPPORT_LOOPBACK _BITUL(6) + uint32_t mode_func_supported; + uint32_t max_freq_hz; + uint32_t max_word_delay_ns; + uint32_t max_cs_setup_ns; + uint32_t max_cs_hold_ns; + uint32_t max_cs_inactive_ns; +}; + +/** + * struct spi_transfer_head - virtio SPI transfer descriptor + * @chip_select_id: chipselect index the SPI transfer used. + * @bits_per_word: the number of bits in each SPI transfer word. + * @cs_change: whether to deselect device after finishing this transfer + * before starting the next transfer, 0 means cs keep asserted and + * 1 means cs deasserted then asserted again. + * @tx_nbits: bus width for write transfer. + * 0,1: bus width is 1, also known as SINGLE + * 2 : bus width is 2, also known as DUAL + * 4 : bus width is 4, also known as QUAD + * 8 : bus width is 8, also known as OCTAL + * other values are invalid. + * @rx_nbits: bus width for read transfer. + * 0,1: bus width is 1, also known as SINGLE + * 2 : bus width is 2, also known as DUAL + * 4 : bus width is 4, also known as QUAD + * 8 : bus width is 8, also known as OCTAL + * other values are invalid. + * @reserved: for future use. + * @mode: SPI transfer mode. + * bit 0: CPHA, determines the timing (i.e. phase) of the data + * bits relative to the clock pulses.For CPHA=0, the + * "out" side changes the data on the trailing edge of the + * preceding clock cycle, while the "in" side captures the data + * on (or shortly after) the leading edge of the clock cycle. + * For CPHA=1, the "out" side changes the data on the leading + * edge of the current clock cycle, while the "in" side + * captures the data on (or shortly after) the trailing edge of + * the clock cycle. + * bit 1: CPOL, determines the polarity of the clock. CPOL=0 is a + * clock which idles at 0, and each cycle consists of a pulse + * of 1. CPOL=1 is a clock which idles at 1, and each cycle + * consists of a pulse of 0. + * bit 2: CS_HIGH, if 1, chip select active high, else active low. + * bit 3: LSB_FIRST, determines per-word bits-on-wire, if 0, MSB + * first, else LSB first. + * bit 4: LOOP, loopback mode. + * @freq: the transfer speed in Hz. + * @word_delay_ns: delay to be inserted between consecutive words of a + * transfer, in ns unit. + * @cs_setup_ns: delay to be introduced after CS is asserted, in ns + * unit. + * @cs_delay_hold_ns: delay to be introduced before CS is deasserted + * for each transfer, in ns unit. + * @cs_change_delay_inactive_ns: delay to be introduced after CS is + * deasserted and before next asserted, in ns unit. + */ +struct spi_transfer_head { + uint8_t chip_select_id; + uint8_t bits_per_word; + uint8_t cs_change; + uint8_t tx_nbits; + uint8_t rx_nbits; + uint8_t reserved[3]; + uint32_t mode; + uint32_t freq; + uint32_t word_delay_ns; + uint32_t cs_setup_ns; + uint32_t cs_delay_hold_ns; + uint32_t cs_change_delay_inactive_ns; +}; + +/** + * struct spi_transfer_result - virtio SPI transfer result + * @result: Transfer result code. + * VIRTIO_SPI_TRANS_OK: Transfer successful. + * VIRTIO_SPI_PARAM_ERR: Parameter error. + * VIRTIO_SPI_TRANS_ERR: Transfer error. + */ +struct spi_transfer_result { +#define VIRTIO_SPI_TRANS_OK 0 +#define VIRTIO_SPI_PARAM_ERR 1 +#define VIRTIO_SPI_TRANS_ERR 2 + uint8_t result; +}; + +#endif /* #ifndef _LINUX_VIRTIO_VIRTIO_SPI_H */ From 6ab7b84fc248e99fc0d27981596216f202f0a568 Mon Sep 17 00:00:00 2001 From: Haixu Cui Date: Tue, 16 Dec 2025 11:21:22 +0800 Subject: [PATCH 24/38] virtio-spi: Add vhost-user-spi device support This patch introduces support for vhost-user-spi and vhost-user-spi-pci devices in QEMU, enabling virtio-based SPI communication via the vhost-user protocol. The implementation follows the virtio-spi specification and leverages the upstream virtio-spi driver in Linux. Relevant references: - Virtio SPI specification: https://github.com/oasis-tcs/virtio-spec/tree/master/device-types/spi - Linux virtio-spi driver: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/spi/spi-virtio.c?h=v6.18-rc3 - vhost-user-spi daemon: https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-spi Example usage with rust-vmm vhost-user-spi daemon: Start the vhost-user-spi daemon: vhost-device-spi --socket-path=vspi.sock --socket-count=1 \ --device "/dev/spidev0.0" Launch QEMU with: qemu-system-aarch64 -m 1G \ -chardev socket,path=/home/root/vspi.sock0,id=vspi \ -device vhost-user-spi-device,chardev=vspi,id=spi \ -object memory-backend-file,id=mem,size=1G,mem-path=/dev/shm,share=on \ -numa node,memdev=mem Signed-off-by: Haixu Cui Reviewed-by: Manos Pitsidianakis Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251216032122.1316684-3-quic_haixcui@quicinc.com> --- MAINTAINERS | 6 ++ docs/system/devices/virtio/vhost-user.rst | 3 + hw/virtio/Kconfig | 5 ++ hw/virtio/meson.build | 3 + hw/virtio/vhost-user-spi-pci.c | 69 +++++++++++++++++++++++ hw/virtio/vhost-user-spi.c | 65 +++++++++++++++++++++ hw/virtio/virtio.c | 4 +- include/hw/virtio/vhost-user-spi.h | 25 ++++++++ 8 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 hw/virtio/vhost-user-spi-pci.c create mode 100644 hw/virtio/vhost-user-spi.c create mode 100644 include/hw/virtio/vhost-user-spi.h diff --git a/MAINTAINERS b/MAINTAINERS index 9b7ed4fccb..d049270777 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2556,6 +2556,12 @@ F: hw/virtio/vhost-user-scmi* F: include/hw/virtio/vhost-user-scmi.h F: tests/qtest/libqos/virtio-scmi.* +vhost-user-spi +M: Haixu Cui +S: Maintained +F: include/hw/virtio/vhost-user-spi.h +F: hw/virtio/vhost-user-spi* + virtio-crypto M: Gonglei S: Supported diff --git a/docs/system/devices/virtio/vhost-user.rst b/docs/system/devices/virtio/vhost-user.rst index f556a840e9..2806d81ca2 100644 --- a/docs/system/devices/virtio/vhost-user.rst +++ b/docs/system/devices/virtio/vhost-user.rst @@ -58,6 +58,9 @@ platform details for what sort of virtio bus to use. * - vhost-user-vsock - Socket based communication - `vhost-device-vsock `_ + * - vhost-user-spi + - Proxy spi devices to host + - `vhost-device-spi `_ The referenced *daemons* are not exhaustive, any conforming backend implementing the device and using the vhost-user protocol should work. diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig index 10f5c53ac0..8895682c61 100644 --- a/hw/virtio/Kconfig +++ b/hw/virtio/Kconfig @@ -127,6 +127,11 @@ config VHOST_USER_SCMI default y depends on VIRTIO && VHOST_USER && ARM +config VHOST_USER_SPI + bool + default y + depends on VIRTIO && VHOST_USER + config VHOST_USER_TEST bool default y diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index affd66887d..6675b63ce6 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -28,6 +28,7 @@ if have_vhost system_virtio_ss.add(when: 'CONFIG_VHOST_USER_RNG', if_true: files('vhost-user-rng.c')) system_virtio_ss.add(when: 'CONFIG_VHOST_USER_SND', if_true: files('vhost-user-snd.c')) system_virtio_ss.add(when: 'CONFIG_VHOST_USER_INPUT', if_true: files('vhost-user-input.c')) + system_virtio_ss.add(when: 'CONFIG_VHOST_USER_SPI', if_true: files('vhost-user-spi.c')) # PCI Stubs system_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_TEST'], @@ -42,6 +43,8 @@ if have_vhost if_true: files('vhost-user-snd-pci.c')) system_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_INPUT'], if_true: files('vhost-user-input-pci.c')) + system_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_SPI'], + if_true: files('vhost-user-spi-pci.c')) endif if have_vhost_vdpa system_virtio_ss.add(files('vhost-vdpa.c')) diff --git a/hw/virtio/vhost-user-spi-pci.c b/hw/virtio/vhost-user-spi-pci.c new file mode 100644 index 0000000000..f813b824b6 --- /dev/null +++ b/hw/virtio/vhost-user-spi-pci.c @@ -0,0 +1,69 @@ +/* + * Vhost-user spi virtio device PCI glue + * + * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/qdev-properties.h" +#include "hw/virtio/vhost-user-spi.h" +#include "hw/virtio/virtio-pci.h" + +struct VHostUserSPIPCI { + VirtIOPCIProxy parent_obj; + VHostUserSPI vdev; +}; + +typedef struct VHostUserSPIPCI VHostUserSPIPCI; + +#define TYPE_VHOST_USER_SPI_PCI "vhost-user-spi-pci-base" + +DECLARE_INSTANCE_CHECKER(VHostUserSPIPCI, VHOST_USER_SPI_PCI, + TYPE_VHOST_USER_SPI_PCI) + +static void vhost_user_spi_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) +{ + VHostUserSPIPCI *dev = VHOST_USER_SPI_PCI(vpci_dev); + DeviceState *vdev = DEVICE(&dev->vdev); + + vpci_dev->nvectors = 1; + qdev_realize(vdev, BUS(&vpci_dev->bus), errp); +} + +static void vhost_user_spi_pci_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); + k->realize = vhost_user_spi_pci_realize; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; + pcidev_k->device_id = 0; /* Set by virtio-pci based on virtio id */ + pcidev_k->revision = 0x00; + pcidev_k->class_id = PCI_CLASS_COMMUNICATION_OTHER; +} + +static void vhost_user_spi_pci_instance_init(Object *obj) +{ + VHostUserSPIPCI *dev = VHOST_USER_SPI_PCI(obj); + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VHOST_USER_SPI); +} + +static const VirtioPCIDeviceTypeInfo vhost_user_spi_pci_info = { + .base_name = TYPE_VHOST_USER_SPI_PCI, + .non_transitional_name = "vhost-user-spi-pci", + .instance_size = sizeof(VHostUserSPIPCI), + .instance_init = vhost_user_spi_pci_instance_init, + .class_init = vhost_user_spi_pci_class_init, +}; + +static void vhost_user_spi_pci_register(void) +{ + virtio_pci_types_register(&vhost_user_spi_pci_info); +} + +type_init(vhost_user_spi_pci_register); diff --git a/hw/virtio/vhost-user-spi.c b/hw/virtio/vhost-user-spi.c new file mode 100644 index 0000000000..707f96c250 --- /dev/null +++ b/hw/virtio/vhost-user-spi.c @@ -0,0 +1,65 @@ +/* + * Vhost-user spi virtio device + * + * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/core/qdev-properties.h" +#include "hw/virtio/virtio-bus.h" +#include "hw/virtio/vhost-user-spi.h" +#include "qemu/error-report.h" +#include "standard-headers/linux/virtio_ids.h" +#include "standard-headers/linux/virtio_spi.h" + +static const Property vspi_properties[] = { + DEFINE_PROP_CHR("chardev", VHostUserBase, chardev), +}; + +static void vspi_realize(DeviceState *dev, Error **errp) +{ + VHostUserBase *vub = VHOST_USER_BASE(dev); + VHostUserBaseClass *vubc = VHOST_USER_BASE_GET_CLASS(dev); + + /* Fixed for SPI */ + vub->virtio_id = VIRTIO_ID_SPI; + vub->num_vqs = 1; + vub->vq_size = 4; + vub->config_size = sizeof(struct virtio_spi_config); + + vubc->parent_realize(dev, errp); +} + +static const VMStateDescription vu_spi_vmstate = { + .name = "vhost-user-spi", + .unmigratable = 1, +}; + +static void vu_spi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VHostUserBaseClass *vubc = VHOST_USER_BASE_CLASS(klass); + + dc->vmsd = &vu_spi_vmstate; + device_class_set_props(dc, vspi_properties); + device_class_set_parent_realize(dc, vspi_realize, + &vubc->parent_realize); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo vu_spi_info = { + .name = TYPE_VHOST_USER_SPI, + .parent = TYPE_VHOST_USER_BASE, + .instance_size = sizeof(VHostUserSPI), + .class_init = vu_spi_class_init, +}; + +static void vu_spi_register_types(void) +{ + type_register_static(&vu_spi_info); +} + +type_init(vu_spi_register_types) diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 3dc9423eae..77ca54e520 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -48,6 +48,7 @@ #include "standard-headers/linux/virtio_iommu.h" #include "standard-headers/linux/virtio_mem.h" #include "standard-headers/linux/virtio_vsock.h" +#include "standard-headers/linux/virtio_spi.h" /* * Maximum size of virtio device config space @@ -196,7 +197,8 @@ const char *virtio_device_names[] = { [VIRTIO_ID_PARAM_SERV] = "virtio-param-serv", [VIRTIO_ID_AUDIO_POLICY] = "virtio-audio-pol", [VIRTIO_ID_BT] = "virtio-bluetooth", - [VIRTIO_ID_GPIO] = "virtio-gpio" + [VIRTIO_ID_GPIO] = "virtio-gpio", + [VIRTIO_ID_SPI] = "virtio-spi" }; static const char *virtio_id_to_name(uint16_t device_id) diff --git a/include/hw/virtio/vhost-user-spi.h b/include/hw/virtio/vhost-user-spi.h new file mode 100644 index 0000000000..a1a65820cd --- /dev/null +++ b/include/hw/virtio/vhost-user-spi.h @@ -0,0 +1,25 @@ +/* + * Vhost-user spi virtio device + * + * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_VHOST_USER_SPI_H +#define QEMU_VHOST_USER_SPI_H + +#include "hw/virtio/virtio.h" +#include "hw/virtio/vhost.h" +#include "hw/virtio/vhost-user.h" +#include "hw/virtio/vhost-user-base.h" + +#define TYPE_VHOST_USER_SPI "vhost-user-spi-device" + +OBJECT_DECLARE_SIMPLE_TYPE(VHostUserSPI, VHOST_USER_SPI) + +struct VHostUserSPI { + VHostUserBase parent_obj; +}; + +#endif /* QEMU_VHOST_USER_SPI_H */ From 91c6438caffc880e999a7312825479685d659b44 Mon Sep 17 00:00:00 2001 From: zhenwei pi Date: Sun, 21 Dec 2025 10:43:20 +0800 Subject: [PATCH 25/38] hw/virtio/virtio-crypto: verify asym request size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The total lenght of request is limited by cryptodev config, verify it to avoid unexpected request from guest. Fixes: CVE-2025-14876 Fixes: 0e660a6f90a ("crypto: Introduce RSA algorithm") Reported-by: 이재영 Signed-off-by: zhenwei pi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251221024321.143196-2-zhenwei.pi@linux.dev> --- hw/virtio/virtio-crypto.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hw/virtio/virtio-crypto.c b/hw/virtio/virtio-crypto.c index cbd1810fbc..6fceb39681 100644 --- a/hw/virtio/virtio-crypto.c +++ b/hw/virtio/virtio-crypto.c @@ -767,11 +767,18 @@ virtio_crypto_handle_asym_req(VirtIOCrypto *vcrypto, uint32_t len; uint8_t *src = NULL; uint8_t *dst = NULL; + uint64_t max_len; asym_op_info = g_new0(CryptoDevBackendAsymOpInfo, 1); src_len = ldl_le_p(&req->para.src_data_len); dst_len = ldl_le_p(&req->para.dst_data_len); + max_len = (uint64_t)src_len + dst_len; + if (unlikely(max_len > vcrypto->conf.max_size)) { + virtio_error(vdev, "virtio-crypto asym request is too large"); + goto err; + } + if (src_len > 0) { src = g_malloc0(src_len); len = iov_to_buf(iov, out_num, 0, src, src_len); From 7b913094c703641a0442bb1d1165323a019c591c Mon Sep 17 00:00:00 2001 From: zhenwei pi Date: Sun, 21 Dec 2025 10:43:21 +0800 Subject: [PATCH 26/38] cryptodev-builtin: Limit the maximum size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This backend driver is used for demonstration purposes only, unlimited size leads QEMU OOM. Fixes: CVE-2025-14876 Fixes: 1653a5f3fc7 ("cryptodev: introduce a new cryptodev backend") Reported-by: 이재영 Signed-off-by: zhenwei pi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251221024321.143196-3-zhenwei.pi@linux.dev> --- backends/cryptodev-builtin.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/backends/cryptodev-builtin.c b/backends/cryptodev-builtin.c index 0414c01e06..55a3fbd27b 100644 --- a/backends/cryptodev-builtin.c +++ b/backends/cryptodev-builtin.c @@ -53,6 +53,8 @@ typedef struct CryptoDevBackendBuiltinSession { #define CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN 512 #define CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN 64 +/* demonstration purposes only, use a limited size to avoid QEMU OOM */ +#define CRYPTODEV_BUITLIN_MAX_REQUEST_SIZE (1024 * 1024) struct CryptoDevBackendBuiltin { CryptoDevBackend parent_obj; @@ -98,12 +100,7 @@ static void cryptodev_builtin_init( 1u << QCRYPTODEV_BACKEND_SERVICE_TYPE_MAC; backend->conf.cipher_algo_l = 1u << VIRTIO_CRYPTO_CIPHER_AES_CBC; backend->conf.hash_algo = 1u << VIRTIO_CRYPTO_HASH_SHA1; - /* - * Set the Maximum length of crypto request. - * Why this value? Just avoid to overflow when - * memory allocation for each crypto request. - */ - backend->conf.max_size = LONG_MAX - sizeof(CryptoDevBackendOpInfo); + backend->conf.max_size = CRYPTODEV_BUITLIN_MAX_REQUEST_SIZE; backend->conf.max_cipher_key_len = CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN; backend->conf.max_auth_key_len = CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN; cryptodev_builtin_init_akcipher(backend); From f0754cd386af3f2f68d35235f50c1426a4d0d3e2 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Brucker Date: Tue, 30 Dec 2025 15:48:45 +0000 Subject: [PATCH 27/38] MAINTAINERS: Update VIOT maintainer Unfortunately I can't contribute to QEMU for the time being, but Eric has offered to take on maintainership of the ACPI VIOT. Thank you! Signed-off-by: Jean-Philippe Brucker Reviewed-by: Eric Auger Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20251230154844.325364-2-jean-philippe@linaro.org> --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index d049270777..ebd27cd7f3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2197,7 +2197,7 @@ S: Maintained F: hw/riscv/virt-acpi-build.c ACPI/VIOT -M: Jean-Philippe Brucker +M: Eric Auger S: Supported F: hw/acpi/viot.c F: hw/acpi/viot.h From e27194e087aede62dbe3d2805c6f1aa30d3465df Mon Sep 17 00:00:00 2001 From: Joelle van Dyne Date: Sat, 3 Jan 2026 13:43:59 -0800 Subject: [PATCH 28/38] virtio-gpu-virgl: correct parent for blob memory region When `owner` == `mr`, `object_unparent` will crash: object_unparent(mr) -> object_property_del_child(mr, mr) -> object_finalize_child_property(mr, name, mr) -> object_unref(mr) -> object_finalize(mr) -> object_property_del_all(mr) -> object_finalize_child_property(mr, name, mr) -> object_unref(mr) -> fail on g_assert(obj->ref > 0) However, passing a different `owner` to `memory_region_init` does not work. `memory_region_ref` has an optimization where it takes a ref only on the owner. That means when flatviews are created, it does not take a ref on the region and you can get a UAF from `flatview_destroy` called from RCU. The correct fix therefore is to use `NULL` as the name which will set the `owner` but not the `parent` (which is still NULL). This allows us to use `memory_region_ref` on itself while not having to rely on unparent for cleanup. Signed-off-by: Joelle van Dyne Reviewed-by: Akihiko Odaki Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260103214400.71694-1-j@getutm.app> --- hw/display/virtio-gpu-virgl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c index 07f6355ad6..6a83fb63c8 100644 --- a/hw/display/virtio-gpu-virgl.c +++ b/hw/display/virtio-gpu-virgl.c @@ -120,7 +120,7 @@ virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g, vmr->g = g; mr = &vmr->mr; - memory_region_init_ram_ptr(mr, OBJECT(mr), "blob", size, data); + memory_region_init_ram_ptr(mr, OBJECT(mr), NULL, size, data); memory_region_add_subregion(&b->hostmem, offset, mr); memory_region_set_enabled(mr, true); @@ -186,7 +186,7 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g, /* memory region owns self res->mr object and frees it by itself */ memory_region_set_enabled(mr, false); memory_region_del_subregion(&b->hostmem, mr); - object_unparent(OBJECT(mr)); + object_unref(OBJECT(mr)); } return 0; From efd581a8cd4405ca183ecd017072b0c878802d69 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Tue, 6 Jan 2026 16:38:59 +0800 Subject: [PATCH 29/38] virtio-pmem: ignore empty queue notifications virtio_pmem_flush() treats a NULL return from virtqueue_pop() as a fatal error and calls virtio_error(), which puts the device into NEEDS_RESET. However, virtqueue handlers can be invoked when no element is available, so an empty queue should be handled as a benign no-op. With a Linux guest this avoids spurious NEEDS_RESET and the resulting -EIO propagation (e.g. EXT4 journal abort and remount-ro). Signed-off-by: Li Chen Acked-by: Michael S. Tsirkin Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260106083859.380338-1-me@linux.beauty> --- hw/virtio/virtio-pmem.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/virtio/virtio-pmem.c b/hw/virtio/virtio-pmem.c index 5381d59282..c3b3299c9c 100644 --- a/hw/virtio/virtio-pmem.c +++ b/hw/virtio/virtio-pmem.c @@ -73,7 +73,6 @@ static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq) trace_virtio_pmem_flush_request(); req_data = virtqueue_pop(vq, sizeof(VirtIODeviceRequest)); if (!req_data) { - virtio_error(vdev, "virtio-pmem missing request data"); return; } From 3560b51979577afc3ab937fd8b02597515bdfbae Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 13 Jan 2026 09:52:02 +0800 Subject: [PATCH 30/38] virtio-gpu: fix error handling in virgl_cmd_resource_create_blob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix inverted error check in virgl_cmd_resource_create_blob() that causes the function to return error when virtio_gpu_create_mapping_iov() succeeds. virtio_gpu_create_mapping_iov() returns 0 on success and negative values on error. The check 'if (!ret)' incorrectly treats success (ret=0) as an error condition, causing the function to fail when it should succeed. Change the condition to 'if (ret != 0)' to properly detect errors. Fixes: 7c092f17ccee ("virtio-gpu: Handle resource blob commands") Signed-off-by: Honglei Huang Reviewed-by: Akihiko Odaki Reviewed-by: Dmitry Osipenko Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260113015203.3643608-2-honghuan@amd.com> --- hw/display/virtio-gpu-virgl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c index 6a83fb63c8..741728cabb 100644 --- a/hw/display/virtio-gpu-virgl.c +++ b/hw/display/virtio-gpu-virgl.c @@ -705,7 +705,7 @@ static void virgl_cmd_resource_create_blob(VirtIOGPU *g, ret = virtio_gpu_create_mapping_iov(g, cblob.nr_entries, sizeof(cblob), cmd, &res->base.addrs, &res->base.iov, &res->base.iov_cnt); - if (!ret) { + if (ret != 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; } From 12a2d5ca4561e63731274382ec90e3d12f726394 Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 13 Jan 2026 09:52:03 +0800 Subject: [PATCH 31/38] virtio-gpu: use consistent error checking for virtio_gpu_create_mapping_iov MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify error checking style for virtio_gpu_create_mapping_iov() across the codebase to improve consistency and readability. virtio_gpu_create_mapping_iov() returns 0 on success and negative values on error. The original code used inconsistent patterns for checking errors: - Some used 'if (ret != 0)' in virtio-gpu-virgl.c and virtio-gpu.c - Some used 'CHECK(!ret, cmd)' in virtio-gpu-rutabaga.c For if-statement checks, change to 'if (ret < 0)' which is the preferred QEMU coding convention for functions that return 0 on success and negative on error. This makes the return value convention immediately clear to code readers. For CHECK macro usage in virtio-gpu-rutabaga.c, keep the original 'CHECK(!ret, cmd)' pattern as it is more concise and consistent with other error checks in the same file. Updated locations: - hw/display/virtio-gpu-virgl.c: virgl_resource_attach_backing() - hw/display/virtio-gpu-virgl.c: virgl_cmd_resource_create_blob() - hw/display/virtio-gpu.c: virtio_gpu_resource_create_blob() - hw/display/virtio-gpu.c: virtio_gpu_resource_attach_backing() Signed-off-by: Honglei Huang Reviewed-by: Markus Armbruster Reviewed-by: Akihiko Odaki Reviewed-by: Dmitry Osipenko Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260113015203.3643608-3-honghuan@amd.com> --- hw/display/virtio-gpu-virgl.c | 4 ++-- hw/display/virtio-gpu.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c index 741728cabb..ecf8494f36 100644 --- a/hw/display/virtio-gpu-virgl.c +++ b/hw/display/virtio-gpu-virgl.c @@ -561,7 +561,7 @@ static void virgl_resource_attach_backing(VirtIOGPU *g, ret = virtio_gpu_create_mapping_iov(g, att_rb.nr_entries, sizeof(att_rb), cmd, NULL, &res_iovs, &res_niov); - if (ret != 0) { + if (ret < 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; } @@ -705,7 +705,7 @@ static void virgl_cmd_resource_create_blob(VirtIOGPU *g, ret = virtio_gpu_create_mapping_iov(g, cblob.nr_entries, sizeof(cblob), cmd, &res->base.addrs, &res->base.iov, &res->base.iov_cnt); - if (ret != 0) { + if (ret < 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; } diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index f23eec6862..643e91ca2a 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -354,7 +354,7 @@ static void virtio_gpu_resource_create_blob(VirtIOGPU *g, ret = virtio_gpu_create_mapping_iov(g, cblob.nr_entries, sizeof(cblob), cmd, &res->addrs, &res->iov, &res->iov_cnt); - if (ret != 0) { + if (ret < 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; g_free(res); return; @@ -933,7 +933,7 @@ virtio_gpu_resource_attach_backing(VirtIOGPU *g, ret = virtio_gpu_create_mapping_iov(g, ab.nr_entries, sizeof(ab), cmd, &res->addrs, &res->iov, &res->iov_cnt); - if (ret != 0) { + if (ret < 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; } From e7985b6a5652979460d28008f3ab8cd4166b6e55 Mon Sep 17 00:00:00 2001 From: Alexandr Moshkov Date: Thu, 15 Jan 2026 13:11:01 +0500 Subject: [PATCH 32/38] vhost-user.rst: specify vhost-user back-end action on GET_VRING_BASE By default, we assume that server need to wait all inflight IO on GET_VRING_BASE. However, this fact is not recorded anywhere in the documentation. So, add this info in rst. Signed-off-by: Alexandr Moshkov Reviewed-by: Stefan Hajnoczi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260115081103.655749-2-dtalexundeer@yandex-team.ru> --- docs/interop/vhost-user.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/interop/vhost-user.rst b/docs/interop/vhost-user.rst index 93f756d3f4..505f8d3a33 100644 --- a/docs/interop/vhost-user.rst +++ b/docs/interop/vhost-user.rst @@ -1250,7 +1250,8 @@ Front-end message types When and as long as all of a device's vrings are stopped, it is *suspended*, see :ref:`Suspended device state - `. + `. The back-end must complete all inflight I/O + requests for the specified vring before stopping it. The request payload's *num* field is currently reserved and must be set to 0. From e0822e6085aecc15e2cfb2914d8cd827abae0249 Mon Sep 17 00:00:00 2001 From: Alexandr Moshkov Date: Thu, 15 Jan 2026 13:11:03 +0500 Subject: [PATCH 33/38] vhost-user: introduce protocol feature for skip drain on GET_VRING_BASE Add vhost-user protocol feature VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT Now on GET_VRING_BASE this feature can control whether to wait for in-flight requests to complete or not. Also we have to validate that this feature will be enabled only when qemu and back-end supports in-flight buffer and in-flight migration It will be helpfull in future for in-flight requests migration in vhost-user devices. Update docs, add ref to label for inflight-io-tracking Signed-off-by: Alexandr Moshkov Reviewed-by: Stefan Hajnoczi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260115081103.655749-3-dtalexundeer@yandex-team.ru> --- docs/interop/vhost-user.rst | 59 +++++++++++++++++++++------------- hw/virtio/vhost-user.c | 7 ++++ include/hw/virtio/vhost-user.h | 2 ++ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/docs/interop/vhost-user.rst b/docs/interop/vhost-user.rst index 505f8d3a33..137c9f3669 100644 --- a/docs/interop/vhost-user.rst +++ b/docs/interop/vhost-user.rst @@ -743,6 +743,8 @@ negotiated, back-end can send file descriptors (at most 8 descriptors in each message) to front-end via ancillary data using this fd communication channel. +.. _inflight_io_tracking: + Inflight I/O tracking --------------------- @@ -1040,26 +1042,27 @@ Protocol features .. code:: c - #define VHOST_USER_PROTOCOL_F_MQ 0 - #define VHOST_USER_PROTOCOL_F_LOG_SHMFD 1 - #define VHOST_USER_PROTOCOL_F_RARP 2 - #define VHOST_USER_PROTOCOL_F_REPLY_ACK 3 - #define VHOST_USER_PROTOCOL_F_MTU 4 - #define VHOST_USER_PROTOCOL_F_BACKEND_REQ 5 - #define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN 6 - #define VHOST_USER_PROTOCOL_F_CRYPTO_SESSION 7 - #define VHOST_USER_PROTOCOL_F_PAGEFAULT 8 - #define VHOST_USER_PROTOCOL_F_CONFIG 9 - #define VHOST_USER_PROTOCOL_F_BACKEND_SEND_FD 10 - #define VHOST_USER_PROTOCOL_F_HOST_NOTIFIER 11 - #define VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD 12 - #define VHOST_USER_PROTOCOL_F_RESET_DEVICE 13 - #define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14 - #define VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS 15 - #define VHOST_USER_PROTOCOL_F_STATUS 16 - #define VHOST_USER_PROTOCOL_F_XEN_MMAP 17 - #define VHOST_USER_PROTOCOL_F_SHARED_OBJECT 18 - #define VHOST_USER_PROTOCOL_F_DEVICE_STATE 19 + #define VHOST_USER_PROTOCOL_F_MQ 0 + #define VHOST_USER_PROTOCOL_F_LOG_SHMFD 1 + #define VHOST_USER_PROTOCOL_F_RARP 2 + #define VHOST_USER_PROTOCOL_F_REPLY_ACK 3 + #define VHOST_USER_PROTOCOL_F_MTU 4 + #define VHOST_USER_PROTOCOL_F_BACKEND_REQ 5 + #define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN 6 + #define VHOST_USER_PROTOCOL_F_CRYPTO_SESSION 7 + #define VHOST_USER_PROTOCOL_F_PAGEFAULT 8 + #define VHOST_USER_PROTOCOL_F_CONFIG 9 + #define VHOST_USER_PROTOCOL_F_BACKEND_SEND_FD 10 + #define VHOST_USER_PROTOCOL_F_HOST_NOTIFIER 11 + #define VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD 12 + #define VHOST_USER_PROTOCOL_F_RESET_DEVICE 13 + #define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14 + #define VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS 15 + #define VHOST_USER_PROTOCOL_F_STATUS 16 + #define VHOST_USER_PROTOCOL_F_XEN_MMAP 17 + #define VHOST_USER_PROTOCOL_F_SHARED_OBJECT 18 + #define VHOST_USER_PROTOCOL_F_DEVICE_STATE 19 + #define VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT 20 Front-end message types ----------------------- @@ -1250,12 +1253,24 @@ Front-end message types When and as long as all of a device's vrings are stopped, it is *suspended*, see :ref:`Suspended device state - `. The back-end must complete all inflight I/O - requests for the specified vring before stopping it. + `. The request payload's *num* field is currently reserved and must be set to 0. + By default, the back-end must complete all inflight I/O requests for the + specified vring before stopping it. + + If the ``VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT`` protocol + feature has been negotiated, the back-end may suspend in-flight I/O + requests and record them as described in :ref:`Inflight I/O tracking + ` instead of completing them before stopping the vring. + How to suspend an in-flight request depends on the implementation of the back-end + but it typically can be done by aborting or cancelling the underlying I/O + request. The ``VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT`` + protocol feature must only be neogotiated if + ``VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD`` is also negotiated. + ``VHOST_USER_SET_VRING_KICK`` :id: 12 :equivalent ioctl: ``VHOST_SET_VRING_KICK`` diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c index 63fa9a1b4b..bb8f8eab77 100644 --- a/hw/virtio/vhost-user.c +++ b/hw/virtio/vhost-user.c @@ -2225,6 +2225,13 @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque, } } + if (!u->user->supports_inflight_migration || + !virtio_has_feature(protocol_features, + VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD)) { + protocol_features &= ~(1ULL << + VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT); + } + /* final set of protocol features */ dev->protocol_features = protocol_features; err = vhost_user_set_protocol_features(dev, dev->protocol_features); diff --git a/include/hw/virtio/vhost-user.h b/include/hw/virtio/vhost-user.h index 55d5725ef8..53fe996686 100644 --- a/include/hw/virtio/vhost-user.h +++ b/include/hw/virtio/vhost-user.h @@ -32,6 +32,7 @@ enum VhostUserProtocolFeature { /* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */ VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18, VHOST_USER_PROTOCOL_F_DEVICE_STATE = 19, + VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT = 20, VHOST_USER_PROTOCOL_F_MAX }; @@ -68,6 +69,7 @@ typedef struct VhostUserState { GPtrArray *notifiers; int memory_slots; bool supports_config; + bool supports_inflight_migration; } VhostUserState; /** From f6fdd8b2bdb2705adf05b21006cc29d6c123fea3 Mon Sep 17 00:00:00 2001 From: Alexandr Moshkov Date: Thu, 15 Jan 2026 13:11:05 +0500 Subject: [PATCH 34/38] vmstate: introduce VMSTATE_VBUFFER_UINT64 This is an analog of VMSTATE_VBUFFER_UINT32 macro, but for uint64 type. Signed-off-by: Alexandr Moshkov Acked-by: Peter Xu Reviewed-by: Stefan Hajnoczi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260115081103.655749-4-dtalexundeer@yandex-team.ru> --- include/migration/vmstate.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index ed9095a466..89f9f49d20 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -706,6 +706,16 @@ extern const VMStateInfo vmstate_info_qlist; .offset = offsetof(_state, _field), \ } +#define VMSTATE_VBUFFER_UINT64(_field, _state, _version, _test, _field_size) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, uint64_t),\ + .info = &vmstate_info_buffer, \ + .flags = VMS_VBUFFER | VMS_POINTER, \ + .offset = offsetof(_state, _field), \ +} + #define VMSTATE_VBUFFER_ALLOC_UINT32(_field, _state, _version, \ _test, _field_size) { \ .name = (stringify(_field)), \ From 3a80ff0721b641f9c70af0d416c5c9e171c29aa3 Mon Sep 17 00:00:00 2001 From: Alexandr Moshkov Date: Thu, 15 Jan 2026 13:11:07 +0500 Subject: [PATCH 35/38] vhost: add vmstate for inflight region with inner buffer Prepare for future inflight region migration for vhost-user-blk. We need to migrate size, queue_size, and inner buffer. So firstly it migrate size and queue_size fields, then allocate memory for buffer with migrated size, then migrate inner buffer itself. Signed-off-by: Alexandr Moshkov Reviewed-by: Stefan Hajnoczi Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260115081103.655749-5-dtalexundeer@yandex-team.ru> --- hw/virtio/vhost.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c index c6a5928cb1..52801c1796 100644 --- a/hw/virtio/vhost.c +++ b/hw/virtio/vhost.c @@ -1918,6 +1918,62 @@ void vhost_get_features_ex(struct vhost_dev *hdev, } } +static bool vhost_inflight_buffer_pre_load(void *opaque, Error **errp) +{ + struct vhost_inflight *inflight = opaque; + + int fd = -1; + void *addr = qemu_memfd_alloc("vhost-inflight", inflight->size, + F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL, + &fd, errp); + if (*errp) { + return -ENOMEM; + } + + inflight->offset = 0; + inflight->addr = addr; + inflight->fd = fd; + + return true; +} + +const VMStateDescription vmstate_vhost_inflight_region_buffer = { + .name = "vhost-inflight-region/buffer", + .pre_load_errp = vhost_inflight_buffer_pre_load, + .fields = (const VMStateField[]) { + VMSTATE_VBUFFER_UINT64(addr, struct vhost_inflight, 0, NULL, size), + VMSTATE_END_OF_LIST() + } +}; + +static bool vhost_inflight_region_post_load(void *opaque, + int version_id, + Error **errp) +{ + struct vhost_inflight *inflight = opaque; + + if (inflight->addr == NULL) { + error_setg(errp, "inflight buffer subsection has not been loaded"); + return false; + } + + return true; +} + +const VMStateDescription vmstate_vhost_inflight_region = { + .name = "vhost-inflight-region", + .post_load_errp = vhost_inflight_region_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT64(size, struct vhost_inflight), + VMSTATE_UINT16(queue_size, struct vhost_inflight), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription * const []) { + &vmstate_vhost_inflight_region_buffer, + NULL + } +}; + void vhost_ack_features_ex(struct vhost_dev *hdev, const int *feature_bits, const uint64_t *features) { From 3dfd711ea9eeda8a0b44f73a65e19f3c1f7d428d Mon Sep 17 00:00:00 2001 From: Alexandr Moshkov Date: Thu, 15 Jan 2026 13:11:09 +0500 Subject: [PATCH 36/38] vhost-user-blk: support inter-host inflight migration During inter-host migration, waiting for disk requests to be drained in the vhost-user backend can incur significant downtime. This can be avoided if QEMU migrates the inflight region in vhost-user-blk. Thus, during the qemu migration, with feature flag the vhost-user back-end can immediately stop vrings, so all in-flight requests will be migrated to another host. Signed-off-by: Alexandr Moshkov Reviewed-by: Stefan Hajnoczi Reviewed-by: Raphael Norwitz Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260115081103.655749-6-dtalexundeer@yandex-team.ru> --- hw/block/vhost-user-blk.c | 27 +++++++++++++++++++++++++++ include/hw/virtio/vhost-user-blk.h | 1 + include/hw/virtio/vhost.h | 6 ++++++ 3 files changed, 34 insertions(+) diff --git a/hw/block/vhost-user-blk.c b/hw/block/vhost-user-blk.c index 4d81d2dc34..c151e83677 100644 --- a/hw/block/vhost-user-blk.c +++ b/hw/block/vhost-user-blk.c @@ -353,6 +353,7 @@ static int vhost_user_blk_connect(DeviceState *dev, Error **errp) vhost_dev_set_config_notifier(&s->dev, &blk_ops); s->vhost_user.supports_config = true; + s->vhost_user.supports_inflight_migration = s->inflight_migration; ret = vhost_dev_init(&s->dev, &s->vhost_user, VHOST_BACKEND_TYPE_USER, 0, errp); if (ret < 0) { @@ -568,6 +569,26 @@ static struct vhost_dev *vhost_user_blk_get_vhost(VirtIODevice *vdev) return &s->dev; } +static bool vhost_user_blk_inflight_needed(void *opaque) +{ + struct VHostUserBlk *s = opaque; + + bool inflight_migration = virtio_has_feature(s->dev.protocol_features, + VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT); + + return inflight_migration; +} + +static const VMStateDescription vmstate_vhost_user_blk_inflight = { + .name = "vhost-user-blk/inflight", + .version_id = 1, + .needed = vhost_user_blk_inflight_needed, + .fields = (const VMStateField[]) { + VMSTATE_VHOST_INFLIGHT_REGION(inflight, VHostUserBlk), + VMSTATE_END_OF_LIST() + }, +}; + static const VMStateDescription vmstate_vhost_user_blk = { .name = "vhost-user-blk", .minimum_version_id = 1, @@ -576,6 +597,10 @@ static const VMStateDescription vmstate_vhost_user_blk = { VMSTATE_VIRTIO_DEVICE, VMSTATE_END_OF_LIST() }, + .subsections = (const VMStateDescription * const []) { + &vmstate_vhost_user_blk_inflight, + NULL + } }; static const Property vhost_user_blk_properties[] = { @@ -591,6 +616,8 @@ static const Property vhost_user_blk_properties[] = { VIRTIO_BLK_F_WRITE_ZEROES, true), DEFINE_PROP_BOOL("skip-get-vring-base-on-force-shutdown", VHostUserBlk, skip_get_vring_base_on_force_shutdown, false), + DEFINE_PROP_BOOL("inflight-migration", VHostUserBlk, + inflight_migration, false), }; static void vhost_user_blk_class_init(ObjectClass *klass, const void *data) diff --git a/include/hw/virtio/vhost-user-blk.h b/include/hw/virtio/vhost-user-blk.h index 8158d4673d..1e41a2bcdf 100644 --- a/include/hw/virtio/vhost-user-blk.h +++ b/include/hw/virtio/vhost-user-blk.h @@ -52,6 +52,7 @@ struct VHostUserBlk { bool started_vu; bool skip_get_vring_base_on_force_shutdown; + bool inflight_migration; }; #endif diff --git a/include/hw/virtio/vhost.h b/include/hw/virtio/vhost.h index 08bbb4dfe9..89817bd848 100644 --- a/include/hw/virtio/vhost.h +++ b/include/hw/virtio/vhost.h @@ -554,4 +554,10 @@ static inline int vhost_load_backend_state(struct vhost_dev *dev, QEMUFile *f, } #endif +extern const VMStateDescription vmstate_vhost_inflight_region; +#define VMSTATE_VHOST_INFLIGHT_REGION(_field, _state) \ + VMSTATE_STRUCT_POINTER(_field, _state, \ + vmstate_vhost_inflight_region, \ + struct vhost_inflight) + #endif From 87f8e5a71d061964c9bfa4d6e02db47f54dd61f7 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Fri, 2 Jan 2026 15:47:30 +0000 Subject: [PATCH 37/38] hw/cxl: Check for overflow on santize media as both base and offset 64bit. The both the size and base of a media sanitize operation are both provided by the VM, an overflow is possible which may result in checks on valid range passing when they should not. Close that by checking for overflow on the addition. Fixes: 40ab4ed10775 ("hw/cxl/cxl-mailbox-utils: Media operations Sanitize and Write Zeros commands CXL r3.2(8.2.10.9.5.3)") Closes: https://lore.kernel.org/qemu-devel/CAFEAcA8Rqop+ju0fuxN+0T57NBG+bep80z45f6pY0ci2fz_G3A@mail.gmail.com/ Reported-by: Peter Maydell Signed-off-by: Jonathan Cameron Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260102154731.474859-2-Jonathan.Cameron@huawei.com> --- hw/cxl/cxl-mailbox-utils.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c index 6cfdd98168..cf1d048d99 100644 --- a/hw/cxl/cxl-mailbox-utils.c +++ b/hw/cxl/cxl-mailbox-utils.c @@ -1875,7 +1875,7 @@ static uint64_t get_dc_size(CXLType3Dev *ct3d, MemoryRegion **dc_mr) static int validate_dpa_addr(CXLType3Dev *ct3d, uint64_t dpa_addr, size_t length) { - uint64_t vmr_size, pmr_size, dc_size; + uint64_t vmr_size, pmr_size, dc_size, dpa_end; if ((dpa_addr % CXL_CACHE_LINE_SIZE) || (length % CXL_CACHE_LINE_SIZE) || @@ -1887,7 +1887,12 @@ static int validate_dpa_addr(CXLType3Dev *ct3d, uint64_t dpa_addr, pmr_size = get_pmr_size(ct3d, NULL); dc_size = get_dc_size(ct3d, NULL); - if (dpa_addr + length > vmr_size + pmr_size + dc_size) { + /* sanitize 64 bit values coming from guest */ + if (uadd64_overflow(dpa_addr, length, &dpa_end)) { + return -EINVAL; + } + + if (dpa_end > vmr_size + pmr_size + dc_size) { return -EINVAL; } From 25465c0e1fd74d2118dfec03912f2595eeb497d7 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Fri, 2 Jan 2026 15:47:31 +0000 Subject: [PATCH 38/38] hw/cxl: Take into account how many media operations are requested for param check Whilst the spec doesn't speak to it directly my assumption is that a request for more operations than exist should result in an invalid input error return. Fixes: 77a8e9fe0ecb ("hw/cxl/cxl-mailbox-utils: Add support for Media operations discovery commands cxl r3.2 (8.2.10.9.5.3)") Closes: https://lore.kernel.org/qemu-devel/CAFEAcA-p5wZkNxK7wNVq_3PAzEE-muOd1Def-0O-FSpck4DrBQ@mail.gmail.com/ Reported-by: Peter Maydell Signed-off-by: Jonathan Cameron Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Message-Id: <20260102154731.474859-3-Jonathan.Cameron@huawei.com> --- hw/cxl/cxl-mailbox-utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c index cf1d048d99..9b99d44a80 100644 --- a/hw/cxl/cxl-mailbox-utils.c +++ b/hw/cxl/cxl-mailbox-utils.c @@ -2011,7 +2011,7 @@ static CXLRetCode media_operations_discovery(uint8_t *payload_in, * sub class command. */ if (media_op_in_disc_pl->dpa_range_count || - start_index > ARRAY_SIZE(media_op_matrix)) { + start_index + num_ops > ARRAY_SIZE(media_op_matrix)) { return CXL_MBOX_INVALID_INPUT; }