From: Konstantin Shkolnyy <kshk@linux.ibm.com>
To: mjrosato@linux.ibm.com
Cc: alifm@linux.ibm.com, farman@linux.ibm.com,
richard.henderson@linaro.org, iii@linux.ibm.com,
david@kernel.org, cohuck@redhat.com, pasic@linux.ibm.com,
borntraeger@linux.ibm.com, qemu-s390x@nongnu.org,
qemu-devel@nongnu.org, Konstantin Shkolnyy <kshk@linux.ibm.com>
Subject: [PATCH v7 14/15] s390x/pci: Implement migration for emulated devices
Date: Tue, 18 Aug 2026 12:25:20 -0500 [thread overview]
Message-ID: <20260818172521.223460-15-kshk@linux.ibm.com> (raw)
In-Reply-To: <20260818172521.223460-1-kshk@linux.ibm.com>
Implement zPCI device state migration, consequently enabling migration
of VMs that have emulated PCI devices, whether virtio or not.
Migration is allowed for devices whose function handle has the
FH_SHM_EMUL bit set. For these devices QEMU will save and restore the
state of its zPCI emulator.
This will enable emulated PCI migration starting with s390-ccw-virtio-11.2.
Passthrough devices will continue to block migration.
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
Signed-off-by: Konstantin Shkolnyy <kshk@linux.ibm.com>
---
hw/s390x/s390-pci-bus.c | 195 +++++++++++++++++++++++++++++++-
hw/s390x/s390-pci-inst.c | 2 +-
hw/s390x/s390-virtio-ccw.c | 4 +
include/hw/s390x/s390-pci-bus.h | 4 +
4 files changed, 199 insertions(+), 6 deletions(-)
diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c
index a94e24a2ac..4ac845328f 100644
--- a/hw/s390x/s390-pci-bus.c
+++ b/hw/s390x/s390-pci-bus.c
@@ -26,6 +26,7 @@
#include "hw/pci/pci_bridge.h"
#include "hw/pci/msi.h"
#include "exec/cpu-common.h"
+#include "migration/blocker.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "system/physmem.h"
@@ -34,6 +35,11 @@
#include "trace.h"
+static const Property phb_props[] = {
+ DEFINE_PROP_BOOL("x-zpci-emul-dev-migr-enabled", S390pciState,
+ emul_dev_migr_enabled, true),
+};
+
S390pciState *s390_get_phb(void)
{
static S390pciState *phb;
@@ -1129,6 +1135,46 @@ static int s390_pci_interp_plug(S390pciState *s, S390PCIBusDevice *pbdev)
return 0;
}
+static int s390_set_emul_dev_migration_blocker(S390PCIBusDevice *pbdev,
+ S390pciState *s, Error **errp)
+{
+ if (s->emul_dev_migr_enabled) {
+ return 0;
+ }
+ error_setg(&pbdev->emul_dev_migr_blocker,
+ "Migration blocked on this machine type by emulated zPCI device "
+ "fh 0x%x uid %d fid %d", pbdev->fh, pbdev->uid, pbdev->fid);
+ return migrate_add_blocker(&pbdev->emul_dev_migr_blocker, errp);
+}
+
+static void s390_clear_emul_dev_migration_blocker(S390PCIBusDevice *pbdev)
+{
+ if (pbdev->emul_dev_migr_blocker) {
+ migrate_del_blocker(&pbdev->emul_dev_migr_blocker);
+ }
+}
+
+static int s390_set_passthrough_migration_blocker(S390PCIBusDevice *pbdev,
+ Error **errp)
+{
+ pbdev->passthrough_migr_blocker = NULL;
+
+ if (pbdev->fh & FH_SHM_EMUL) {
+ return 0;
+ }
+ error_setg(&pbdev->passthrough_migr_blocker,
+ "Migration blocked by passthrough zPCI device "
+ "fh 0x%x uid %d fid %d", pbdev->fh, pbdev->uid, pbdev->fid);
+ return migrate_add_blocker(&pbdev->passthrough_migr_blocker, errp);
+}
+
+static void s390_clear_passthrough_migration_blocker(S390PCIBusDevice *pbdev)
+{
+ if (pbdev->passthrough_migr_blocker) {
+ migrate_del_blocker(&pbdev->passthrough_migr_blocker);
+ }
+}
+
static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
Error **errp)
{
@@ -1242,9 +1288,20 @@ static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
pbdev->rtr_avail = false;
}
+ if (s390_set_passthrough_migration_blocker(pbdev, errp) != 0) {
+ return;
+ }
+
+ if (s390_set_emul_dev_migration_blocker(pbdev, s, errp) != 0) {
+ s390_clear_passthrough_migration_blocker(pbdev);
+ return;
+ }
+
if (s390_pci_msix_init(pbdev) && !pbdev->interp) {
error_setg(errp, "MSI-X support is mandatory "
"in the S390 architecture");
+ s390_clear_emul_dev_migration_blocker(pbdev);
+ s390_clear_passthrough_migration_blocker(pbdev);
return;
}
@@ -1284,6 +1341,9 @@ static void s390_pcihost_unplug(HotplugHandler *hotplug_dev, DeviceState *dev,
return;
}
+ s390_clear_emul_dev_migration_blocker(pbdev);
+ s390_clear_passthrough_migration_blocker(pbdev);
+
s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED,
pbdev->fh, pbdev->fid);
bus = pci_get_bus(pci_dev);
@@ -1451,6 +1511,7 @@ static void s390_pcihost_class_init(ObjectClass *klass, const void *data)
hc->unplug_request = s390_pcihost_unplug_request;
hc->unplug = s390_pcihost_unplug;
msi_nonbroken = true;
+ device_class_set_props(dc, phb_props);
}
static const TypeInfo s390_pcihost_info = {
@@ -1464,10 +1525,33 @@ static const TypeInfo s390_pcihost_info = {
}
};
+/* Return a unique bus "path" for zpci device */
+static char *s390_pci_bus_get_dev_path(DeviceState *dev)
+{
+ S390PCIBusDevice *pbdev = S390_PCI_DEVICE(dev);
+ return g_strdup_printf("uid-%04x", pbdev->uid);
+}
+
+static void s390_pcibus_class_init(ObjectClass *oc, const void *data)
+{
+ BusClass *bc = BUS_CLASS(oc);
+ bc->get_dev_path = s390_pci_bus_get_dev_path;
+}
+
static const TypeInfo s390_pcibus_info = {
.name = TYPE_S390_PCI_BUS,
.parent = TYPE_BUS,
.instance_size = sizeof(S390PCIBus),
+ /*
+ * Implement get_dev_path() to provide each zpci device with a unique
+ * stable UID-based bus "path". The "path" is used as part of idstr in the
+ * migration stream, making idstr unique and instance_id always 0.
+ * For migration to succeed, (idstr+instance_id) must match those generated
+ * during QEMU start. Without unique idstr, QEMU will generate variable
+ * instance_id to distinquish devices, and that instance_id can change
+ * if a device is unplugged and plugged back, preventing migration.
+ */
+ .class_init = s390_pcibus_class_init,
};
static uint16_t s390_pci_generate_uid(S390pciState *s)
@@ -1613,13 +1697,114 @@ static const Property s390_pci_device_properties[] = {
true),
};
-static const VMStateDescription s390_pci_device_vmstate = {
- .name = TYPE_S390_PCI_DEVICE,
+static int s390_pci_device_pre_load(void *opaque)
+{
+ S390PCIBusDevice *pbdev = S390_PCI_DEVICE(opaque);
+ S390PCIBusDevice *found_pbdev;
+
+ /*
+ * Because state loading can change pbdev->idx make sure pbdev is removed
+ * from the table before that happens. The table type used stores a pointer
+ * to pbdev->idx and becomes corrupt if idx is changed from outside. But be
+ * careful to not remove instead another pbdev whose state might have been
+ * loaded earlier and that got assigned this idx value and had therefore
+ * already replaced our pbdev in the table. post_load() will reinsert our
+ * pbdev into the table.
+ */
+ found_pbdev = g_hash_table_lookup(s390_get_phb()->zpci_table, &pbdev->idx);
+ assert(found_pbdev);
+ if (found_pbdev == pbdev) {
+ g_hash_table_remove(s390_get_phb()->zpci_table, &pbdev->idx);
+ }
+
+ return 0;
+}
+
+static int s390_pci_device_post_load(void *opaque, int version_id)
+{
+ S390PCIBusDevice *pbdev = S390_PCI_DEVICE(opaque);
+
+ /*
+ * Now that pbdev->idx has been loaded, use it to place pbdev back into
+ * the table. This may replace a different not-yet-state-loaded pbdev,
+ * but pre_load() handles this case.
+ */
+ g_hash_table_replace(s390_get_phb()->zpci_table, &pbdev->idx, pbdev);
+
+ /*
+ * Regenerate IOMMU state, including IOTLB contents and QEMU memory regions.
+ */
+ if (pbdev->iommu_enabled) {
+ assert(pbdev->iommu);
+ if (s390_pci_is_translation_enabled(pbdev->g_iota)) {
+ s390_pci_iommu_enable(pbdev);
+ s390_pci_ioat_replay(pbdev);
+ } else {
+ s390_pci_iommu_direct_map_enable(pbdev);
+ }
+ }
+
/*
- * TODO: add state handling here, so migration works at least with
- * emulated pci devices on s390x
+ * Guest sets fmb_addr by mpcifc.ZPCI_MOD_FC_SET_MEASURE instruction,
+ * whose handler consequently starts fmb_timer. We may need to restart it.
*/
- .unmigratable = 1,
+ if (pbdev->fmb_addr) {
+ assert(!pbdev->fmb_timer);
+ assert(pbdev->pci_group);
+ pbdev->fmb_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ fmb_update, pbdev);
+ timer_mod(pbdev->fmb_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+ pbdev->pci_group->zpci_group.mui);
+ }
+ return 0;
+}
+
+static const VMStateDescription s390_pci_device_vmstate = {
+ .name = TYPE_S390_PCI_DEVICE,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_load = s390_pci_device_pre_load,
+ .post_load = s390_pci_device_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(state, S390PCIBusDevice),
+ VMSTATE_UINT16(uid, S390PCIBusDevice),
+ VMSTATE_UINT32(idx, S390PCIBusDevice),
+ VMSTATE_UINT32(fh, S390PCIBusDevice),
+ VMSTATE_UINT32(fid, S390PCIBusDevice),
+ VMSTATE_BOOL(fid_defined, S390PCIBusDevice),
+ VMSTATE_UINT64(fmb_addr, S390PCIBusDevice),
+ VMSTATE_UINT32(fmb.format, S390PCIBusDevice),
+ VMSTATE_UINT32(fmb.sample, S390PCIBusDevice),
+ VMSTATE_UINT64(fmb.last_update, S390PCIBusDevice),
+ VMSTATE_UINT64_ARRAY(fmb.counter, S390PCIBusDevice,
+ ARRAY_SIZE(((S390PCIBusDevice *)0)->fmb.counter)),
+ VMSTATE_UINT64(fmb.fmt0.dma_rbytes, S390PCIBusDevice),
+ VMSTATE_UINT64(fmb.fmt0.dma_wbytes, S390PCIBusDevice),
+ VMSTATE_UINT8(isc, S390PCIBusDevice),
+ VMSTATE_UINT16(noi, S390PCIBusDevice),
+ VMSTATE_UINT8(sum, S390PCIBusDevice),
+ VMSTATE_UINT8(pft, S390PCIBusDevice),
+ VMSTATE_UINT64(routes.adapter.ind_addr, S390PCIBusDevice),
+ VMSTATE_UINT64(routes.adapter.summary_addr, S390PCIBusDevice),
+ VMSTATE_UINT64(routes.adapter.ind_offset, S390PCIBusDevice),
+ VMSTATE_UINT32(routes.adapter.summary_offset, S390PCIBusDevice),
+ VMSTATE_UINT32(routes.adapter.adapter_id, S390PCIBusDevice),
+ VMSTATE_BOOL(iommu_enabled, S390PCIBusDevice),
+ VMSTATE_UINT64(g_iota, S390PCIBusDevice),
+ VMSTATE_UINT64(pba, S390PCIBusDevice),
+ VMSTATE_UINT64(pal, S390PCIBusDevice),
+ VMSTATE_UINT64(max_dma_limit, S390PCIBusDevice),
+ VMSTATE_PTR_TO_IND_ADDR(summary_ind, S390PCIBusDevice),
+ VMSTATE_PTR_TO_IND_ADDR(indicator, S390PCIBusDevice),
+ VMSTATE_BOOL(pci_unplug_request_processed, S390PCIBusDevice),
+ VMSTATE_BOOL(unplug_requested, S390PCIBusDevice),
+ VMSTATE_BOOL(interp, S390PCIBusDevice),
+ VMSTATE_BOOL(forwarding_assist, S390PCIBusDevice),
+ VMSTATE_BOOL(aif, S390PCIBusDevice),
+ VMSTATE_BOOL(rtr_avail, S390PCIBusDevice),
+ VMSTATE_END_OF_LIST()
+ }
};
static void s390_pci_device_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/s390x/s390-pci-inst.c b/hw/s390x/s390-pci-inst.c
index f93db10c81..6b0742d143 100644
--- a/hw/s390x/s390-pci-inst.c
+++ b/hw/s390x/s390-pci-inst.c
@@ -1122,7 +1122,7 @@ static int fmb_do_update(S390PCIBusDevice *pbdev, int offset, uint64_t val,
return ret;
}
-static void fmb_update(void *opaque)
+void fmb_update(void *opaque)
{
S390PCIBusDevice *pbdev = opaque;
int64_t t = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index 06e5def909..b22c27b066 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -939,8 +939,12 @@ static void ccw_machine_11_1_instance_options(MachineState *machine)
static void ccw_machine_11_1_class_options(MachineClass *mc)
{
+ static GlobalProperty compat[] = {
+ { TYPE_S390_PCI_HOST_BRIDGE, "x-zpci-emul-dev-migr-enabled", "off" },
+ };
ccw_machine_11_2_class_options(mc);
compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+ compat_props_add(mc->compat_props, compat, G_N_ELEMENTS(compat));
}
DEFINE_CCW_MACHINE(11, 1);
diff --git a/include/hw/s390x/s390-pci-bus.h b/include/hw/s390x/s390-pci-bus.h
index 17ecf3e0da..66c1f04ead 100644
--- a/include/hw/s390x/s390-pci-bus.h
+++ b/include/hw/s390x/s390-pci-bus.h
@@ -340,6 +340,8 @@ struct S390PCIBusDevice {
uint16_t uid;
uint32_t idx;
uint32_t fh;
+ Error *emul_dev_migr_blocker;
+ Error *passthrough_migr_blocker;
uint32_t fid;
bool fid_defined;
uint64_t fmb_addr;
@@ -393,6 +395,7 @@ struct S390pciState {
QTAILQ_HEAD(, S390PCIDMACount) zpci_dma_limit;
QTAILQ_HEAD(, S390PCIGroup) zpci_groups;
uint8_t next_sim_grp;
+ bool emul_dev_migr_enabled;
};
S390pciState *s390_get_phb(void);
@@ -418,5 +421,6 @@ S390PCIBusDevice *s390_pci_find_dev_by_pci(S390pciState *s,
S390PCIBusDevice *s390_pci_find_next_avail_dev(S390pciState *s,
S390PCIBusDevice *pbdev);
void s390_pci_ism_reset(void);
+void fmb_update(void *opaque);
#endif
--
2.34.1
next prev parent reply other threads:[~2026-08-18 17:27 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 17:25 [PATCH v7 00/15] s390x/pci: Implement migration for emulated devices Konstantin Shkolnyy
2026-08-18 17:25 ` [PATCH v7 01/15] s390x/pci: implement IOMMU replay Konstantin Shkolnyy
2026-08-18 17:25 ` [PATCH v7 02/15] s390x/pci: Create function to contain translation status check Konstantin Shkolnyy
2026-08-24 21:27 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 03/15] s390x/pci: Move iommu_mr from S390PCIIOMMU to S390PCIBusDevice Konstantin Shkolnyy
2026-08-24 20:50 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 04/15] s390x/pci: Move dm_mr " Konstantin Shkolnyy
2026-08-24 20:55 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 05/15] s390x/pci: Move iotlb " Konstantin Shkolnyy
2026-08-24 21:12 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 06/15] s390x/pci: Remove a ptr to S390PCIBusDevice from S390PCIIOMMU Konstantin Shkolnyy
2026-08-24 21:15 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 07/15] s390x/pci: Move/rename enabled from S390PCIIOMMU to S390PCIBusDevice Konstantin Shkolnyy
2026-08-24 21:18 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 08/15] s390x/pci: Move dma_limit " Konstantin Shkolnyy
2026-08-24 21:28 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 09/15] s390x/pci: Move g_iota " Konstantin Shkolnyy
2026-08-24 21:32 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 10/15] s390x/pci: Move pba " Konstantin Shkolnyy
2026-08-24 21:36 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 11/15] s390x/pci: Move pal " Konstantin Shkolnyy
2026-08-24 21:42 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 12/15] s390x/pci: Move max_dma_limit " Konstantin Shkolnyy
2026-08-24 21:58 ` Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 13/15] s390x/pci: Add a comment explaining S390PCIIOMMU purpose Konstantin Shkolnyy
2026-08-24 22:00 ` Matthew Rosato
2026-08-18 17:25 ` Konstantin Shkolnyy [this message]
2026-08-24 22:35 ` [PATCH v7 14/15] s390x/pci: Implement migration for emulated devices Matthew Rosato
2026-08-18 17:25 ` [PATCH v7 15/15] s390x/pci: Create function to contain fmb_timer start Konstantin Shkolnyy
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260818172521.223460-15-kshk@linux.ibm.com \
--to=kshk@linux.ibm.com \
--cc=alifm@linux.ibm.com \
--cc=borntraeger@linux.ibm.com \
--cc=cohuck@redhat.com \
--cc=david@kernel.org \
--cc=farman@linux.ibm.com \
--cc=iii@linux.ibm.com \
--cc=mjrosato@linux.ibm.com \
--cc=pasic@linux.ibm.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-s390x@nongnu.org \
--cc=richard.henderson@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).