* [PATCH v6 01/27] vfio/pci: Factor out the reset logic in VFIO PCI device close path
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 02/27] vfio: Export various helpers from VFIO Vipin Sharma
` (25 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
vfio_pci_core_disable() is a very long function doing multiple things
like exiting runtime power management, setting device to D0 power,
clearing bus master, clearing irqs, freeing eventfds, releasing device
regions, unmapping PCI bars, and performing device/bus resets.
The upcoming VFIO Liveupdate feature will also need to utilize same
reset logic.
Extract device reset code to a new helper function,
vfio_pci_core_try_reset(), and update vfio_pci_core_disable() to use
this new function.
No functional change intended.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/pci/vfio_pci_core.c | 100 ++++++++++++++++---------------
1 file changed, 53 insertions(+), 47 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 6757054e9d87..7b003c3e5a3a 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -680,9 +680,60 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
}
EXPORT_SYMBOL_GPL(vfio_pci_core_enable);
-void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
+static void vfio_pci_core_try_reset(struct vfio_pci_core_device *vdev)
{
+ struct pci_dev *pdev = vdev->pdev;
struct pci_dev *bridge;
+
+ vdev->needs_reset = true;
+
+ /*
+ * If we have saved state, restore it. If we can reset the device,
+ * even better. Resetting with current state seems better than
+ * nothing, but saving and restoring current state without reset
+ * is just busy work.
+ */
+ if (pci_load_and_free_saved_state(pdev, &vdev->pci_saved_state)) {
+ pci_info(pdev, "%s: Couldn't reload saved state\n", __func__);
+
+ if (!vdev->reset_works)
+ return;
+
+ pci_save_state(pdev);
+ }
+
+ /*
+ * Disable INTx and MSI, presumably to avoid spurious interrupts
+ * during reset. Stolen from pci_reset_function()
+ */
+ pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+
+ /*
+ * Try to get the locks ourselves to prevent a deadlock. The
+ * success of this is dependent on being able to lock the device,
+ * which is not always possible.
+ * We can not use the "try" reset interface here, which will
+ * overwrite the previously restored configuration information.
+ */
+ if (vdev->reset_works) {
+ bridge = pci_upstream_bridge(pdev);
+ if (bridge && !pci_dev_trylock(bridge))
+ goto out_restore_state;
+ if (pci_dev_trylock(pdev)) {
+ if (!__pci_reset_function_locked(pdev))
+ vdev->needs_reset = false;
+ pci_dev_unlock(pdev);
+ }
+ if (bridge)
+ pci_dev_unlock(bridge);
+ }
+
+out_restore_state:
+ pci_restore_state(pdev);
+}
+
+void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
+{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_dummy_resource *dummy_res, *tmp;
struct vfio_pci_ioeventfd *ioeventfd, *ioeventfd_tmp;
@@ -757,54 +808,9 @@ void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
kfree(dummy_res);
}
- vdev->needs_reset = true;
-
vfio_pci_zdev_close_device(vdev);
- /*
- * If we have saved state, restore it. If we can reset the device,
- * even better. Resetting with current state seems better than
- * nothing, but saving and restoring current state without reset
- * is just busy work.
- */
- if (pci_load_and_free_saved_state(pdev, &vdev->pci_saved_state)) {
- pci_info(pdev, "%s: Couldn't reload saved state\n", __func__);
-
- if (!vdev->reset_works)
- goto out;
-
- pci_save_state(pdev);
- }
-
- /*
- * Disable INTx and MSI, presumably to avoid spurious interrupts
- * during reset. Stolen from pci_reset_function()
- */
- pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
-
- /*
- * Try to get the locks ourselves to prevent a deadlock. The
- * success of this is dependent on being able to lock the device,
- * which is not always possible.
- * We can not use the "try" reset interface here, which will
- * overwrite the previously restored configuration information.
- */
- if (vdev->reset_works) {
- bridge = pci_upstream_bridge(pdev);
- if (bridge && !pci_dev_trylock(bridge))
- goto out_restore_state;
- if (pci_dev_trylock(pdev)) {
- if (!__pci_reset_function_locked(pdev))
- vdev->needs_reset = false;
- pci_dev_unlock(pdev);
- }
- if (bridge)
- pci_dev_unlock(bridge);
- }
-
-out_restore_state:
- pci_restore_state(pdev);
-out:
+ vfio_pci_core_try_reset(vdev);
pci_disable_device(pdev);
vfio_pci_dev_set_try_reset(vdev->vdev.dev_set);
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 02/27] vfio: Export various helpers from VFIO
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 01/27] vfio/pci: Factor out the reset logic in VFIO PCI device close path Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 03/27] liveupdate: Export symbols needed by modules Vipin Sharma
` (24 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
The upcoming VFIO live update feature needs to access various helper
functions for its logic which are not exported by default.
Export vfio_device_from_file() for other modules to use it, and make
vfio_pci_ops non-static so it can be accessed within the vfio-pci
module. Remove static from their declarations and add them in
include/linux/vfio.h and drivers/vfio/pci/vfio_pci_priv.h header files.
No functional change intended.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/pci/vfio_pci.c | 2 +-
drivers/vfio/pci/vfio_pci_priv.h | 2 ++
drivers/vfio/vfio_main.c | 3 ++-
include/linux/vfio.h | 1 +
4 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c
index 830369ff878d..2ec600768007 100644
--- a/drivers/vfio/pci/vfio_pci.c
+++ b/drivers/vfio/pci/vfio_pci.c
@@ -146,7 +146,7 @@ static int vfio_pci_init_dev(struct vfio_device *core_vdev)
return vfio_pci_core_init_dev(core_vdev);
}
-static const struct vfio_device_ops vfio_pci_ops = {
+const struct vfio_device_ops vfio_pci_ops = {
.name = "vfio-pci",
.init = vfio_pci_init_dev,
.release = vfio_pci_core_release_dev,
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index 4e7162234a2e..1634135de3f8 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -11,6 +11,8 @@
/* Cap maximum number of ioeventfds per device (arbitrary) */
#define VFIO_PCI_IOEVENTFD_MAX 1000
+extern const struct vfio_device_ops vfio_pci_ops;
+
struct vfio_pci_ioeventfd {
struct list_head next;
struct vfio_pci_core_device *vdev;
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index 423ead48aafe..73a5c4f3751c 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -1497,7 +1497,7 @@ const struct file_operations vfio_device_fops = {
#endif
};
-static struct vfio_device *vfio_device_from_file(struct file *file)
+struct vfio_device *vfio_device_from_file(struct file *file)
{
struct vfio_device_file *df = file->private_data;
@@ -1505,6 +1505,7 @@ static struct vfio_device *vfio_device_from_file(struct file *file)
return NULL;
return df->device;
}
+EXPORT_SYMBOL_GPL(vfio_device_from_file);
/**
* vfio_file_is_valid - True if the file is valid vfio file
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index 45f08986359e..de3fa6e6bf2a 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -379,6 +379,7 @@ static inline bool vfio_file_has_dev(struct file *file, struct vfio_device *devi
bool vfio_file_is_valid(struct file *file);
bool vfio_file_enforced_coherent(struct file *file);
void vfio_file_set_kvm(struct file *file, struct kvm *kvm);
+struct vfio_device *vfio_device_from_file(struct file *file);
#define VFIO_PIN_PAGES_MAX_ENTRIES (PAGE_SIZE/sizeof(unsigned long))
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 03/27] liveupdate: Export symbols needed by modules
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 01/27] vfio/pci: Factor out the reset logic in VFIO PCI device close path Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 02/27] vfio: Export various helpers from VFIO Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 04/27] vfio/pci: Register a file handler with Live Update Orchestrator Vipin Sharma
` (23 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Export liveupdate_register_file_handler() and
liveupdate_unregister_file_handler(). All of these will be used by
vfio-pci in a subsequent commit, which can be built as a module.
Reviewed-by: Samiullah Khawaja <skhawaja@google.com>
Reviewed-by: Pranjal Shrivastava <praan@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
kernel/liveupdate/luo_file.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index dbae0715220b..6aaa9a531428 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -916,6 +916,7 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
up_write(&luo_register_rwlock);
return err;
}
+EXPORT_SYMBOL_GPL(liveupdate_register_file_handler);
/**
* liveupdate_unregister_file_handler - Unregister a liveupdate file handler
@@ -933,6 +934,7 @@ void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
}
+EXPORT_SYMBOL_GPL(liveupdate_unregister_file_handler);
/**
* liveupdate_get_token_outgoing - Get the token for a preserved file.
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 04/27] vfio/pci: Register a file handler with Live Update Orchestrator
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (2 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 03/27] liveupdate: Export symbols needed by modules Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 05/27] PCI: liveupdate: Add helpers to preserve struct pci_saved_state Vipin Sharma
` (22 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Register a live update file handler for vfio-pci device files. Add stub
implementations of all required callbacks so that registration does not
fail. Register it with both live update framework and PCI subsystem.
This file handler will be extended in subsequent commits to enable a
device bound to vfio-pci to run without interruption while the host is
going through a kexec Live Update.
Put this support behind a new Kconfig VFIO_PCI_LIVEUPDATE. Initial
support is added for no-iommu model vfio-pci devices.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
MAINTAINERS | 1 +
drivers/vfio/pci/Kconfig | 11 ++++
drivers/vfio/pci/Makefile | 1 +
drivers/vfio/pci/vfio_pci.c | 11 +++-
drivers/vfio/pci/vfio_pci_liveupdate.c | 75 ++++++++++++++++++++++++++
drivers/vfio/pci/vfio_pci_priv.h | 14 +++++
include/linux/kho/abi/vfio_pci.h | 28 ++++++++++
7 files changed, 140 insertions(+), 1 deletion(-)
create mode 100644 drivers/vfio/pci/vfio_pci_liveupdate.c
create mode 100644 include/linux/kho/abi/vfio_pci.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 5d1b58271989..167aa9d66ce8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28662,6 +28662,7 @@ F: Documentation/ABI/testing/debugfs-vfio
F: Documentation/ABI/testing/sysfs-devices-vfio-dev
F: Documentation/driver-api/vfio.rst
F: drivers/vfio/
+F: include/linux/kho/abi/vfio_pci.h
F: include/linux/vfio.h
F: include/linux/vfio_pci_core.h
F: include/uapi/linux/vfio.h
diff --git a/drivers/vfio/pci/Kconfig b/drivers/vfio/pci/Kconfig
index 296bf01e185e..e8285ad93a69 100644
--- a/drivers/vfio/pci/Kconfig
+++ b/drivers/vfio/pci/Kconfig
@@ -42,6 +42,17 @@ config VFIO_PCI_IGD
and LPC bridge config space.
To enable Intel IGD assignment through vfio-pci, say Y.
+
+config VFIO_PCI_LIVEUPDATE
+ bool "VFIO PCI support for Live Update in no-iommu mode"
+ depends on PCI_LIVEUPDATE && VFIO_NOIOMMU
+ help
+ Support for preserving devices bound to vfio-pci in no-iommu mode
+ across a Live Update. It enables a device to continue working (DMA)
+ while host kernel is going through a kexec live update.
+
+ If you don't know what to do here, say N.
+
endif
config VFIO_PCI_ZDEV_KVM
diff --git a/drivers/vfio/pci/Makefile b/drivers/vfio/pci/Makefile
index 6138f1bf241d..bf296ab1c967 100644
--- a/drivers/vfio/pci/Makefile
+++ b/drivers/vfio/pci/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_VFIO_PCI_CORE) += vfio-pci-core.o
vfio-pci-y := vfio_pci.o
vfio-pci-$(CONFIG_VFIO_PCI_IGD) += vfio_pci_igd.o
+vfio-pci-$(CONFIG_VFIO_PCI_LIVEUPDATE) += vfio_pci_liveupdate.o
obj-$(CONFIG_VFIO_PCI) += vfio-pci.o
obj-$(CONFIG_MLX5_VFIO_PCI) += mlx5/
diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c
index 2ec600768007..9d48b250a5e8 100644
--- a/drivers/vfio/pci/vfio_pci.c
+++ b/drivers/vfio/pci/vfio_pci.c
@@ -278,10 +278,14 @@ static int __init vfio_pci_init(void)
{
int ret;
+ ret = vfio_pci_liveupdate_init();
+ if (ret)
+ return ret;
+
/* Register and scan for devices */
ret = pci_register_driver(&vfio_pci_driver);
if (ret)
- return ret;
+ goto err_liveupdate_cleanup;
vfio_pci_fill_ids();
@@ -289,12 +293,17 @@ static int __init vfio_pci_init(void)
pr_warn("device denylist disabled.\n");
return 0;
+
+err_liveupdate_cleanup:
+ vfio_pci_liveupdate_cleanup();
+ return ret;
}
module_init(vfio_pci_init);
static void __exit vfio_pci_cleanup(void)
{
pci_unregister_driver(&vfio_pci_driver);
+ vfio_pci_liveupdate_cleanup();
}
module_exit(vfio_pci_cleanup);
diff --git a/drivers/vfio/pci/vfio_pci_liveupdate.c b/drivers/vfio/pci/vfio_pci_liveupdate.c
new file mode 100644
index 000000000000..8fd5af4f4b83
--- /dev/null
+++ b/drivers/vfio/pci/vfio_pci_liveupdate.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Vipin Sharma <vipinsh@google.com>
+ * David Matlack <dmatlack@google.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/kho/abi/vfio_pci.h>
+#include <linux/liveupdate.h>
+#include <linux/module.h>
+
+#include "vfio_pci_priv.h"
+
+static bool vfio_pci_liveupdate_can_preserve(struct liveupdate_file_handler *handler,
+ struct file *file)
+{
+ return false;
+}
+
+static int vfio_pci_liveupdate_preserve(struct liveupdate_file_op_args *args)
+{
+ return -EOPNOTSUPP;
+}
+
+static void vfio_pci_liveupdate_unpreserve(struct liveupdate_file_op_args *args)
+{
+}
+
+static int vfio_pci_liveupdate_retrieve(struct liveupdate_file_op_args *args)
+{
+ return -EOPNOTSUPP;
+}
+
+static void vfio_pci_liveupdate_finish(struct liveupdate_file_op_args *args)
+{
+}
+
+static const struct liveupdate_file_ops vfio_pci_liveupdate_file_ops = {
+ .can_preserve = vfio_pci_liveupdate_can_preserve,
+ .preserve = vfio_pci_liveupdate_preserve,
+ .unpreserve = vfio_pci_liveupdate_unpreserve,
+ .retrieve = vfio_pci_liveupdate_retrieve,
+ .finish = vfio_pci_liveupdate_finish,
+ .owner = THIS_MODULE,
+};
+
+static struct liveupdate_file_handler vfio_pci_liveupdate_fh = {
+ .ops = &vfio_pci_liveupdate_file_ops,
+ .compatible = VFIO_PCI_LUO_FH_COMPATIBLE,
+};
+
+int __init vfio_pci_liveupdate_init(void)
+{
+ int ret;
+
+ ret = liveupdate_register_file_handler(&vfio_pci_liveupdate_fh);
+ if (ret)
+ return ret == -EOPNOTSUPP ? 0 : ret;
+
+ ret = pci_liveupdate_register_flb(&vfio_pci_liveupdate_fh);
+ if (ret)
+ liveupdate_unregister_file_handler(&vfio_pci_liveupdate_fh);
+
+ return ret;
+}
+
+void vfio_pci_liveupdate_cleanup(void)
+{
+ pci_liveupdate_unregister_flb(&vfio_pci_liveupdate_fh);
+ liveupdate_unregister_file_handler(&vfio_pci_liveupdate_fh);
+}
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index 1634135de3f8..872b01bcb5a3 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -148,4 +148,18 @@ static inline void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev,
}
#endif
+#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
+int __init vfio_pci_liveupdate_init(void);
+void vfio_pci_liveupdate_cleanup(void);
+#else
+static inline int vfio_pci_liveupdate_init(void)
+{
+ return 0;
+}
+
+static inline void vfio_pci_liveupdate_cleanup(void)
+{
+}
+#endif /* CONFIG_VFIO_PCI_LIVEUPDATE */
+
#endif
diff --git a/include/linux/kho/abi/vfio_pci.h b/include/linux/kho/abi/vfio_pci.h
new file mode 100644
index 000000000000..67e8199eec59
--- /dev/null
+++ b/include/linux/kho/abi/vfio_pci.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Vipin Sharma <vipinsh@google.com>
+ * David Matlack <dmatlack@google.com>
+ */
+
+#ifndef _LINUX_LIVEUPDATE_ABI_VFIO_PCI_H
+#define _LINUX_LIVEUPDATE_ABI_VFIO_PCI_H
+
+/**
+ * DOC: VFIO PCI Live Update ABI
+ *
+ * VFIO uses the ABI defined below for preserving device files across a kexec
+ * reboot using LUO.
+ *
+ * Device metadata is serialized into memory which is then handed to the next
+ * kernel via KHO.
+ *
+ * This interface is a contract. Any modification to any of the serialization
+ * structs defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the VFIO_PCI_LUO_FH_COMPATIBLE string.
+ */
+
+#define VFIO_PCI_LUO_FH_COMPATIBLE "vfio-pci-v1"
+
+#endif /* _LINUX_LIVEUPDATE_ABI_VFIO_PCI_H */
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 05/27] PCI: liveupdate: Add helpers to preserve struct pci_saved_state
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (3 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 04/27] vfio/pci: Register a file handler with Live Update Orchestrator Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 06/27] vfio: Add helpers to identify device files that can be preserved Vipin Sharma
` (21 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Drivers that keep a device running across Live Update need to hand the
state they captured with pci_store_saved_state() to the next kernel,
since the next kernel never sees the device in that state. VFIO, for
example, captures it at first open and uses it to restore the device
when the last user closes it.
struct pci_saved_state is private to the PCI core and its in-memory
layout is not a stable ABI. Add helpers to serialize it into a
caller-provided buffer and rebuild it in the next kernel with a stable
struct pci_saved_state_ser ABI. Using caller-provided buffers allows
drivers to embed the serialized saved state directly in their own
KHO-preserved data with a single allocation and lifetime.
Validate the serialized buffer and verify that the saved standard
configuration header matches the live device before rebuilding the
state: the vendor and device IDs must match, only Type 0 (Normal)
headers are accepted, and BAR0-5 and the expansion ROM BAR address must
equal the current hardware values. This ensures that restoring the
saved state cannot write stale BAR addresses if resources were
reassigned in the new kernel. Only the address bits of the ROM BAR are
compared, since its enable and validation status bits may legitimately
differ.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
Documentation/PCI/liveupdate.rst | 3 +
drivers/pci/liveupdate.c | 223 +++++++++++++++++++++++++++++++
drivers/pci/pci.c | 5 -
drivers/pci/pci.h | 6 +
include/linux/kho/abi/pci.h | 47 +++++++
include/linux/pci_liveupdate.h | 7 +
6 files changed, 286 insertions(+), 5 deletions(-)
diff --git a/Documentation/PCI/liveupdate.rst b/Documentation/PCI/liveupdate.rst
index 96b1d7f5df3a..b6cd9621b3b3 100644
--- a/Documentation/PCI/liveupdate.rst
+++ b/Documentation/PCI/liveupdate.rst
@@ -25,6 +25,9 @@ Live Update ABI
.. kernel-doc:: include/linux/kho/abi/pci.h
:doc: PCI File-Lifecycle Bound (FLB) Live Update ABI
+.. kernel-doc:: include/linux/kho/abi/pci.h
+ :doc: PCI Saved State Live Update ABI
+
.. kernel-doc:: include/linux/kho/abi/pci.h
:internal:
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 6f92d4ddca2b..363453b06243 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -1036,3 +1036,226 @@ void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
+
+static const struct pci_cap_saved_data *pci_saved_cap_next(const struct pci_cap_saved_data *cap)
+{
+ return (const void *)cap + sizeof(*cap) + cap->size;
+}
+
+/**
+ * pci_liveupdate_saved_state_ser_size() - Size needed to serialize a saved state
+ * @state: Saved state returned by pci_store_saved_state(), or NULL.
+ *
+ * Return: Number of bytes needed by pci_liveupdate_serialize_saved_state(), or
+ * 0 if @state is NULL.
+ */
+size_t pci_liveupdate_saved_state_ser_size(const struct pci_saved_state *state)
+{
+ const struct pci_cap_saved_data *cap;
+ size_t size;
+
+ if (!state)
+ return 0;
+
+ size = sizeof(struct pci_saved_state_ser);
+ for (cap = state->cap; cap->size; cap = pci_saved_cap_next(cap))
+ size += sizeof(struct pci_cap_saved_data_ser) + cap->size;
+
+ return size;
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_saved_state_ser_size);
+
+/**
+ * pci_liveupdate_serialize_saved_state() - Serialize a struct pci_saved_state
+ * @state: Saved state returned by pci_store_saved_state().
+ * @buf: Destination buffer.
+ * @size: Size of @buf, must equal pci_liveupdate_saved_state_ser_size(@state).
+ *
+ * Drivers that keep a device running across Live Update can use this to hand
+ * the state captured by pci_store_saved_state() (e.g. before the device was
+ * handed to userspace) to the next kernel, which never sees the device in that
+ * state.
+ *
+ * Return: 0 on success, -EINVAL if the arguments are invalid.
+ */
+int pci_liveupdate_serialize_saved_state(const struct pci_saved_state *state,
+ void *buf, size_t size)
+{
+ const struct pci_cap_saved_data *cap;
+ struct pci_saved_state_ser *ser = buf;
+ size_t off;
+ u32 nr = 0;
+
+ if (!state || !buf || size != pci_liveupdate_saved_state_ser_size(state))
+ return -EINVAL;
+
+ memset(buf, 0, size);
+ ser->size = size;
+ memcpy(ser->config_space, state->config_space, sizeof(ser->config_space));
+
+ off = sizeof(*ser);
+ for (cap = state->cap; cap->size; cap = pci_saved_cap_next(cap)) {
+ struct pci_cap_saved_data_ser *cap_ser = buf + off;
+
+ cap_ser->cap_nr = cap->cap_nr;
+ cap_ser->extended = cap->cap_extended ? 1 : 0;
+ cap_ser->size = cap->size;
+ memcpy(cap_ser->data, cap->data, cap->size);
+
+ off += sizeof(*cap_ser) + cap->size;
+ nr++;
+ }
+
+ ser->nr_caps = nr;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_serialize_saved_state);
+
+/* Read the byte at config space offset @where from a saved config header. */
+static u8 pci_saved_config_byte(const u32 *config_space, int where)
+{
+ return config_space[where / 4] >> ((where % 4) * 8);
+}
+
+/*
+ * Compare the bits in @mask of the config space dword at offset @where with
+ * the saved config header.
+ */
+static int pci_validate_saved_config_dword(struct pci_dev *dev,
+ const u32 *config_space, int where,
+ u32 mask)
+{
+ u32 val;
+
+ if (pci_read_config_dword(dev, where, &val) ||
+ (val & mask) != (config_space[where / 4] & mask))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int pci_validate_saved_config_header(struct pci_dev *dev,
+ const u32 *config_space)
+{
+ u8 hdr_type;
+ int bar;
+
+ if (config_space[PCI_VENDOR_ID / 4] !=
+ ((u32)dev->device << 16 | dev->vendor))
+ return -EINVAL;
+
+ /* Only Type 0 (Normal) configuration headers are supported. */
+ hdr_type = pci_saved_config_byte(config_space, PCI_HEADER_TYPE) &
+ PCI_HEADER_TYPE_MASK;
+ if (hdr_type != PCI_HEADER_TYPE_NORMAL ||
+ dev->hdr_type != PCI_HEADER_TYPE_NORMAL)
+ return -EINVAL;
+
+ /*
+ * Verify that BAR0-5 and the expansion ROM BAR address match the
+ * current hardware values so that restoring the saved state cannot
+ * write stale BAR addresses if resources were reassigned in the new
+ * kernel.
+ */
+ for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) {
+ if (pci_validate_saved_config_dword(dev, config_space,
+ PCI_BASE_ADDRESS_0 + bar * 4,
+ ~0U))
+ return -EINVAL;
+ }
+
+ /*
+ * Only the address bits of the ROM BAR are compared, since its enable
+ * and validation status bits may legitimately differ.
+ */
+ return pci_validate_saved_config_dword(dev, config_space,
+ PCI_ROM_ADDRESS,
+ PCI_ROM_ADDRESS_MASK);
+}
+
+/**
+ * pci_liveupdate_deserialize_saved_state() - Rebuild a struct pci_saved_state
+ * @dev: The PCI device the saved state belongs to.
+ * @buf: Buffer written by pci_liveupdate_serialize_saved_state().
+ * @size: Size of @buf in bytes.
+ *
+ * Validates @buf against the PCI Saved State Live Update ABI and @dev's
+ * standard configuration header, and allocates a struct pci_saved_state that
+ * can be passed to pci_load_saved_state() and freed with kfree(). @buf is not
+ * modified or freed.
+ *
+ * Return: Newly allocated struct pci_saved_state on success, or an ERR_PTR() on
+ * failure (-EINVAL if @buf is malformed or incompatible with @dev, -EOPNOTSUPP
+ * if @buf uses an unsupported flag, -ENOMEM on allocation failure).
+ */
+struct pci_saved_state *pci_liveupdate_deserialize_saved_state(struct pci_dev *dev,
+ const void *buf,
+ size_t size)
+{
+ const struct pci_saved_state_ser *ser = buf;
+ struct pci_cap_saved_data *cap;
+ struct pci_saved_state *state;
+ size_t off, state_size;
+ u32 i;
+
+ if (!dev || !buf || size < sizeof(*ser))
+ return ERR_PTR(-EINVAL);
+
+ if (ser->size != size)
+ return ERR_PTR(-EINVAL);
+
+ if (ser->flags)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ if (pci_validate_saved_config_header(dev, ser->config_space))
+ return ERR_PTR(-EINVAL);
+
+ off = sizeof(*ser);
+ state_size = sizeof(*state) + sizeof(*cap);
+
+ for (i = 0; i < ser->nr_caps; i++) {
+ const struct pci_cap_saved_data_ser *cap_ser;
+ size_t entry_size;
+
+ if (size - off < sizeof(*cap_ser))
+ return ERR_PTR(-EINVAL);
+
+ cap_ser = buf + off;
+ if (!cap_ser->size || cap_ser->size > PCI_CFG_SPACE_EXP_SIZE ||
+ cap_ser->extended > 1)
+ return ERR_PTR(-EINVAL);
+
+ entry_size = sizeof(*cap_ser) + cap_ser->size;
+ if (size - off < entry_size)
+ return ERR_PTR(-EINVAL);
+
+ off += entry_size;
+ state_size += sizeof(*cap) + cap_ser->size;
+ }
+
+ if (off != size)
+ return ERR_PTR(-EINVAL);
+
+ state = kzalloc(state_size, GFP_KERNEL);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(state->config_space, ser->config_space, sizeof(state->config_space));
+
+ off = sizeof(*ser);
+ cap = state->cap;
+ for (i = 0; i < ser->nr_caps; i++) {
+ const struct pci_cap_saved_data_ser *cap_ser = buf + off;
+
+ cap->cap_nr = cap_ser->cap_nr;
+ cap->cap_extended = cap_ser->extended;
+ cap->size = cap_ser->size;
+ memcpy(cap->data, cap_ser->data, cap_ser->size);
+
+ off += sizeof(*cap_ser) + cap_ser->size;
+ cap = (void *)cap + sizeof(*cap) + cap->size;
+ }
+
+ return state;
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_deserialize_saved_state);
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index e5b960ca26e6..192f6766cc31 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -2032,11 +2032,6 @@ void pci_restore_state(struct pci_dev *dev)
}
EXPORT_SYMBOL(pci_restore_state);
-struct pci_saved_state {
- u32 config_space[16];
- struct pci_cap_saved_data cap[];
-};
-
/**
* pci_store_saved_state - Allocate and return an opaque struct containing
* the device saved state.
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 037c1674f164..d10df3203725 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -244,6 +244,12 @@ struct pci_cap_saved_data {
u32 data[];
};
+/* Opaque to drivers, see pci_store_saved_state() */
+struct pci_saved_state {
+ u32 config_space[16];
+ struct pci_cap_saved_data cap[];
+};
+
struct pci_cap_saved_state {
struct hlist_node next;
struct pci_cap_saved_data cap;
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index c2e3a5949f73..3d0822f2067d 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -93,4 +93,51 @@ struct pci_ser {
u64 devices;
} __packed;
+/**
+ * DOC: PCI Saved State Live Update ABI
+ *
+ * Drivers that keep a PCI device running across a Live Update can hand the
+ * state returned by pci_store_saved_state() to the next kernel by embedding a
+ * struct pci_saved_state_ser in their own preserved data, produced and parsed
+ * by the PCI core via pci_liveupdate_serialize_saved_state() and
+ * pci_liveupdate_deserialize_saved_state().
+ *
+ * Like the FLB data above, this layout is a contract between kernels and is
+ * extended by appending fields and defining bits in ``flags`` for features the
+ * next kernel must understand, while recording the size of each entry. Unlike
+ * the FLB data, it is embedded in a driver's preserved state rather than
+ * referenced from struct pci_ser, and validation failures are reported to the
+ * calling driver rather than panicking.
+ */
+
+/**
+ * struct pci_cap_saved_data_ser - A saved capability in a struct pci_saved_state_ser
+ * @cap_nr: Capability ID (PCI_CAP_ID_* or PCI_EXT_CAP_ID_*).
+ * @extended: 1 if @cap_nr is an extended capability ID, 0 otherwise.
+ * @size: Size of @data in bytes, never 0.
+ * @data: The saved registers.
+ */
+struct pci_cap_saved_data_ser {
+ u16 cap_nr;
+ u16 extended;
+ u32 size;
+ u8 data[];
+} __packed;
+
+/**
+ * struct pci_saved_state_ser - Serialized struct pci_saved_state
+ * @size: Size in bytes of this struct, including all entries in @caps.
+ * @flags: Features a kernel must support to restore the state. None defined.
+ * @nr_caps: Number of entries in @caps.
+ * @config_space: The saved standard configuration header.
+ * @caps: @nr_caps struct pci_cap_saved_data_ser entries.
+ */
+struct pci_saved_state_ser {
+ u32 size;
+ u32 flags;
+ u32 nr_caps;
+ u32 config_space[16];
+ u8 caps[];
+} __packed;
+
#endif /* _LINUX_KHO_ABI_PCI_H */
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index bb79348769b7..cfff200bce36 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -32,6 +32,7 @@ struct pci_liveupdate {
};
struct pci_dev;
+struct pci_saved_state;
#ifdef CONFIG_PCI_LIVEUPDATE
int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
@@ -40,6 +41,12 @@ int pci_liveupdate_preserve(struct pci_dev *dev);
void pci_liveupdate_unpreserve(struct pci_dev *dev);
void pci_liveupdate_finish(struct pci_dev *dev);
bool pci_liveupdate_is_incoming(struct pci_dev *dev);
+size_t pci_liveupdate_saved_state_ser_size(const struct pci_saved_state *state);
+int pci_liveupdate_serialize_saved_state(const struct pci_saved_state *state,
+ void *buf, size_t size);
+struct pci_saved_state *pci_liveupdate_deserialize_saved_state(struct pci_dev *dev,
+ const void *buf,
+ size_t size);
#else
static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
{
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 06/27] vfio: Add helpers to identify device files that can be preserved
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (4 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 05/27] PCI: liveupdate: Add helpers to preserve struct pci_saved_state Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 07/27] vfio/pci: Preserve vfio-pci device files across Live Update Vipin Sharma
` (20 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Add two helpers needed by vfio-pci Live Update support:
1. vfio_file_is_bound_cdev(): checks whether a specific file is a VFIO
cdev device file that has been bound to an iommufd. Only the bound
cdev file keeps the device open; the device-level cdev_opened flag
cannot distinguish between the bound cdev file and a second, unbound
cdev file opened for the same device.
2. vfio_device_is_noiommu(): move the existing helper out of line and
export it so vfio-pci can enforce that only devices in no-IOMMU mode
are preserved across Live Update.
No functional change intended for existing callers.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/device_cdev.c | 20 ++++++++++++++++++++
drivers/vfio/vfio.h | 10 ----------
drivers/vfio/vfio_main.c | 18 ++++++++++++++++++
include/linux/vfio.h | 10 ++++++++++
4 files changed, 48 insertions(+), 10 deletions(-)
diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index 1d9515c967b0..23b229468151 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -200,6 +200,26 @@ void vfio_df_unbind_iommufd(struct vfio_device_file *df)
vfio_device_unblock_group(device);
}
+/**
+ * vfio_file_is_bound_cdev - True if the file is a VFIO cdev file bound to an
+ * iommufd
+ * @file: File to check
+ *
+ * Return: True if @file is a VFIO cdev device file on which
+ * VFIO_DEVICE_BIND_IOMMUFD has succeeded, false otherwise.
+ */
+bool vfio_file_is_bound_cdev(struct file *file)
+{
+ struct vfio_device_file *df = file->private_data;
+
+ if (file->f_op != &vfio_device_fops)
+ return false;
+
+ /* Paired with smp_store_release() in vfio_df_ioctl_bind_iommufd() */
+ return !df->group && smp_load_acquire(&df->access_granted);
+}
+EXPORT_SYMBOL_GPL(vfio_file_is_bound_cdev);
+
int vfio_df_ioctl_attach_pt(struct vfio_device_file *df,
struct vfio_device_attach_iommufd_pt __user *arg)
{
diff --git a/drivers/vfio/vfio.h b/drivers/vfio/vfio.h
index 7728bc99b63d..12557d45dfee 100644
--- a/drivers/vfio/vfio.h
+++ b/drivers/vfio/vfio.h
@@ -185,16 +185,6 @@ static inline void vfio_group_cleanup(void)
#endif /* CONFIG_VFIO_GROUP */
-static inline bool vfio_device_is_noiommu(struct vfio_device *vdev)
-{
-#if IS_ENABLED(CONFIG_VFIO_GROUP)
- if (vdev->group && vdev->group->type == VFIO_NO_IOMMU)
- return true;
-#endif
-
- return IS_ENABLED(CONFIG_IOMMUFD_NOIOMMU) && vdev->noiommu;
-}
-
#if IS_ENABLED(CONFIG_VFIO_CONTAINER)
/**
* struct vfio_iommu_driver_ops - VFIO IOMMU driver callbacks
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index 73a5c4f3751c..291460102cd1 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -1507,6 +1507,24 @@ struct vfio_device *vfio_device_from_file(struct file *file)
}
EXPORT_SYMBOL_GPL(vfio_device_from_file);
+/**
+ * vfio_device_is_noiommu - True if the VFIO device is registered in no-IOMMU
+ * mode
+ * @vdev: The VFIO device to check
+ *
+ * Return: True if @vdev is registered in no-IOMMU mode, false otherwise.
+ */
+bool vfio_device_is_noiommu(struct vfio_device *vdev)
+{
+#if IS_ENABLED(CONFIG_VFIO_GROUP)
+ if (vdev->group && vdev->group->type == VFIO_NO_IOMMU)
+ return true;
+#endif
+
+ return IS_ENABLED(CONFIG_IOMMUFD_NOIOMMU) && vdev->noiommu;
+}
+EXPORT_SYMBOL_GPL(vfio_device_is_noiommu);
+
/**
* vfio_file_is_valid - True if the file is valid vfio file
* @file: VFIO group file or VFIO device file
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index de3fa6e6bf2a..3cfbf1d73479 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -380,6 +380,7 @@ bool vfio_file_is_valid(struct file *file);
bool vfio_file_enforced_coherent(struct file *file);
void vfio_file_set_kvm(struct file *file, struct kvm *kvm);
struct vfio_device *vfio_device_from_file(struct file *file);
+bool vfio_device_is_noiommu(struct vfio_device *vdev);
#define VFIO_PIN_PAGES_MAX_ENTRIES (PAGE_SIZE/sizeof(unsigned long))
@@ -431,4 +432,13 @@ int vfio_virqfd_enable(void *opaque, int (*handler)(void *, void *),
void vfio_virqfd_disable(struct virqfd **pvirqfd);
void vfio_virqfd_flush_thread(struct virqfd **pvirqfd);
+#if IS_ENABLED(CONFIG_VFIO_DEVICE_CDEV)
+bool vfio_file_is_bound_cdev(struct file *file);
+#else
+static inline bool vfio_file_is_bound_cdev(struct file *file)
+{
+ return false;
+}
+#endif /* IS_ENABLED(CONFIG_VFIO_DEVICE_CDEV) */
+
#endif /* VFIO_H */
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 07/27] vfio/pci: Preserve vfio-pci device files across Live Update
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (5 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 06/27] vfio: Add helpers to identify device files that can be preserved Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 08/27] vfio: Introduce vfio_find_device() helper Vipin Sharma
` (19 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Implement the Live Update file handler callbacks to preserve a vfio-pci
device in noiommu mode across a Live Update while keeping it running
across kexec. Subsequent commits will enable userspace to retrieve and
take over this file after the Live Update.
State about each device is serialized into struct vfio_pci_core_device_ser
using Kexec-Handover (KHO) to preserve the pages holding the struct and
the Live Update Orchestrator (LUO) to preserve the physical address of
the struct.
Key design notes and constraints:
* Noiommu-only policy and bound cdev requirement: can_preserve() verifies
that the preserved file is a bound vfio-pci cdev file descriptor via
vfio_file_is_bound_cdev(), and preserve() requires the device to be in
noiommu mode via vfio_device_is_noiommu() (returning -EOPNOTSUPP
otherwise). CONFIG_VFIO_PCI_LIVEUPDATE now depends on
CONFIG_VFIO_DEVICE_CDEV and CONFIG_VFIO_NOIOMMU.
* Serialized state: During preserve(), serialize the PCI domain, BDF,
config space size, reset_works flag, and the initial pci_saved_state
captured when the device was first opened. During freeze(), snapshot
the virtualized PCI config space (vconfig) and capability map
(pci_config_map).
* Keep-running freeze and unfreeze: Do not reset the device, clear
PCI_COMMAND_MASTER, zap BAR mappings, or revoke dma-bufs during
freeze(). Validate that VFIO interrupts are disabled, that the device
is in PCI_D0 (and not in VFIO runtime PM low-power mode), and that
SR-IOV is not enabled, as VFs are not preserved and the next kernel
disables SR-IOV on the running device. Then mask physical INTx
(recording the prior state) and mark the device frozen. If the
Live Update is aborted after freeze(), unfreeze() restores the physical
INTx state and clears liveupdate_frozen so the existing FD remains
usable.
* TOCTOU synchronization: vfio_pci_dev_is_frozen() is checked locklessly
on entry to the ioctl, read/write and mmap paths, and re-checked under
a lock that freeze() holds before any operation that could change the
device or vconfig: under vdev->igate in vfio_pci_ioctl_set_irqs() and
around every config-space write in vfio_pci_config_rw_single(), under
vdev->memory_lock in vfio_pci_runtime_pm_entry(),
vfio_pci_ioctl_reset() and the SR-IOV enable path of
vfio_pci_core_sriov_configure(), and under dev_set->lock in
vfio_pci_dev_set_hot_reset(). A racing operation thus either completes
before freeze() takes its snapshot or fails, while existing BAR mmaps,
ioeventfds and dma-bufs keep working. Serializing config writes on
igate covers the PCI_COMMAND, INTx disable emulation, power state and
FLR paths at one point (the INTx disable emulation now uses the
igate-held __vfio_pci_intx_mask() and __vfio_pci_intx_unmask()
helpers), and also serializes the irq_type and virq_disabled accesses
of config writes with VFIO_DEVICE_SET_IRQS.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/pci/Kconfig | 2 +-
drivers/vfio/pci/vfio_pci_config.c | 19 +-
drivers/vfio/pci/vfio_pci_core.c | 49 +++-
drivers/vfio/pci/vfio_pci_intrs.c | 4 +-
drivers/vfio/pci/vfio_pci_liveupdate.c | 303 ++++++++++++++++++++++++-
drivers/vfio/pci/vfio_pci_priv.h | 13 ++
include/linux/kho/abi/vfio_pci.h | 52 ++++-
include/linux/vfio_pci_core.h | 4 +
8 files changed, 434 insertions(+), 12 deletions(-)
diff --git a/drivers/vfio/pci/Kconfig b/drivers/vfio/pci/Kconfig
index e8285ad93a69..71289d8908f6 100644
--- a/drivers/vfio/pci/Kconfig
+++ b/drivers/vfio/pci/Kconfig
@@ -45,7 +45,7 @@ config VFIO_PCI_IGD
config VFIO_PCI_LIVEUPDATE
bool "VFIO PCI support for Live Update in no-iommu mode"
- depends on PCI_LIVEUPDATE && VFIO_NOIOMMU
+ depends on PCI_LIVEUPDATE && VFIO_NOIOMMU && VFIO_DEVICE_CDEV
help
Support for preserving devices bound to vfio-pci in no-iommu mode
across a Live Update. It enables a device to continue working (DMA)
diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c
index 9914f3ac69ae..1f1ac7a798aa 100644
--- a/drivers/vfio/pci/vfio_pci_config.c
+++ b/drivers/vfio/pci/vfio_pci_config.c
@@ -644,10 +644,10 @@ static int vfio_basic_config_write(struct vfio_pci_core_device *vdev, int pos,
if (virt_intx_disable && !vdev->virq_disabled) {
vdev->virq_disabled = true;
- vfio_pci_intx_mask(vdev);
+ __vfio_pci_intx_mask(vdev);
} else if (!virt_intx_disable && vdev->virq_disabled) {
vdev->virq_disabled = false;
- vfio_pci_intx_unmask(vdev);
+ __vfio_pci_intx_unmask(vdev);
}
}
@@ -1957,7 +1957,20 @@ ssize_t vfio_pci_config_rw_single(struct vfio_pci_core_device *vdev,
if (copy_from_user(&val, buf, count))
return -EFAULT;
- ret = perm->writefn(vdev, *ppos, count, perm, offset, val);
+ /*
+ * Serialize with Live Update freeze(), which holds igate, so a
+ * write that passed the lockless frozen check in vfio_pci_rw()
+ * cannot change the device or vconfig after freeze() has
+ * snapshotted vconfig and masked INTx. The INTx disable
+ * emulation in vfio_basic_config_write() relies on igate too.
+ */
+ mutex_lock(&vdev->igate);
+ if (vfio_pci_dev_is_frozen(vdev))
+ ret = -EIO;
+ else
+ ret = perm->writefn(vdev, *ppos, count, perm, offset,
+ val);
+ mutex_unlock(&vdev->igate);
} else {
if (perm->readfn) {
ret = perm->readfn(vdev, *ppos, count,
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 7b003c3e5a3a..5f8c882d5778 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -369,6 +369,8 @@ int vfio_pci_set_power_state(struct vfio_pci_core_device *vdev, pci_power_t stat
return ret;
}
+static void vfio_pci_zap_bars(struct vfio_pci_core_device *vdev);
+
static int vfio_pci_runtime_pm_entry(struct vfio_pci_core_device *vdev,
struct eventfd_ctx *efdctx)
{
@@ -376,7 +378,12 @@ static int vfio_pci_runtime_pm_entry(struct vfio_pci_core_device *vdev,
* The vdev power related flags are protected with 'memory_lock'
* semaphore.
*/
- vfio_pci_zap_and_down_write_memory_lock(vdev);
+ down_write(&vdev->memory_lock);
+ if (vfio_pci_dev_is_frozen(vdev)) {
+ up_write(&vdev->memory_lock);
+ return -EIO;
+ }
+ vfio_pci_zap_bars(vdev);
vfio_pci_dma_buf_move(vdev, true);
if (vdev->pm_runtime_engaged) {
@@ -1142,6 +1149,9 @@ int vfio_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
struct pci_dev *pdev = vdev->pdev;
int i, ret;
+ if (vfio_pci_dev_is_frozen(vdev))
+ return -EIO;
+
switch (info->index) {
case VFIO_PCI_CONFIG_REGION_INDEX:
info->offset = VFIO_PCI_INDEX_TO_OFFSET(info->index);
@@ -1309,9 +1319,15 @@ static int vfio_pci_ioctl_set_irqs(struct vfio_pci_core_device *vdev,
mutex_lock(&vdev->igate);
+ if (vfio_pci_dev_is_frozen(vdev)) {
+ ret = -EIO;
+ goto out_unlock;
+ }
+
ret = vfio_pci_set_irqs_ioctl(vdev, hdr.flags, hdr.index, hdr.start,
hdr.count, data);
+out_unlock:
mutex_unlock(&vdev->igate);
kfree(data);
@@ -1326,7 +1342,12 @@ static int vfio_pci_ioctl_reset(struct vfio_pci_core_device *vdev,
if (!vdev->reset_works)
return -EINVAL;
- vfio_pci_zap_and_down_write_memory_lock(vdev);
+ down_write(&vdev->memory_lock);
+ if (vfio_pci_dev_is_frozen(vdev)) {
+ up_write(&vdev->memory_lock);
+ return -EIO;
+ }
+ vfio_pci_zap_bars(vdev);
/*
* This function can be invoked while the power state is non-D0. If
@@ -1565,6 +1586,9 @@ long vfio_pci_core_ioctl(struct vfio_device *core_vdev, unsigned int cmd,
container_of(core_vdev, struct vfio_pci_core_device, vdev);
void __user *uarg = (void __user *)arg;
+ if (vfio_pci_dev_is_frozen(vdev))
+ return -EIO;
+
switch (cmd) {
case VFIO_DEVICE_GET_INFO:
return vfio_pci_ioctl_get_info(vdev, uarg);
@@ -1619,6 +1643,9 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags,
struct vfio_pci_core_device *vdev =
container_of(device, struct vfio_pci_core_device, vdev);
+ if (vfio_pci_dev_is_frozen(vdev))
+ return -EIO;
+
switch (flags & VFIO_DEVICE_FEATURE_MASK) {
case VFIO_DEVICE_FEATURE_LOW_POWER_ENTRY:
return vfio_pci_core_pm_entry(vdev, flags, arg, argsz);
@@ -1645,6 +1672,9 @@ static ssize_t vfio_pci_rw(struct vfio_pci_core_device *vdev, char __user *buf,
unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos);
int ret;
+ if (vfio_pci_dev_is_frozen(vdev))
+ return -EIO;
+
if (index >= VFIO_PCI_NUM_REGIONS + vdev->num_regions)
return -EINVAL;
@@ -1828,6 +1858,9 @@ int vfio_pci_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma
u64 phys_len, req_len, pgoff, req_start;
void __iomem *bar_io;
+ if (vfio_pci_dev_is_frozen(vdev))
+ return -EIO;
+
index = vma->vm_pgoff >> (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT);
if (index >= VFIO_PCI_NUM_REGIONS + vdev->num_regions)
@@ -2393,6 +2426,13 @@ int vfio_pci_core_sriov_configure(struct vfio_pci_core_device *vdev,
goto out_del;
down_write(&vdev->memory_lock);
+ /* A device frozen for Live Update must not change. */
+ if (vfio_pci_dev_is_frozen(vdev)) {
+ up_write(&vdev->memory_lock);
+ pm_runtime_put(&pdev->dev);
+ ret = -EBUSY;
+ goto out_del;
+ }
vfio_pci_set_power_state(vdev, PCI_D0);
vdev->sriov_active = true;
up_write(&vdev->memory_lock);
@@ -2537,6 +2577,11 @@ static int vfio_pci_dev_set_hot_reset(struct vfio_device_set *dev_set,
list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list) {
bool owned;
+ if (vfio_pci_dev_is_frozen(vdev)) {
+ ret = -EBUSY;
+ break;
+ }
+
/*
* Test whether all the affected devices can be reset by the
* user.
diff --git a/drivers/vfio/pci/vfio_pci_intrs.c b/drivers/vfio/pci/vfio_pci_intrs.c
index 64f80f64ff57..07ae0caec44b 100644
--- a/drivers/vfio/pci/vfio_pci_intrs.c
+++ b/drivers/vfio/pci/vfio_pci_intrs.c
@@ -99,7 +99,7 @@ static void vfio_send_intx_eventfd(void *opaque, void *data)
}
/* Returns true if the INTx vfio_pci_irq_ctx.masked value is changed. */
-static bool __vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
+bool __vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
@@ -203,7 +203,7 @@ static int vfio_pci_intx_unmask_handler(void *opaque, void *data)
return ret;
}
-static void __vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
+void __vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
{
struct vfio_pci_irq_ctx *ctx = vfio_irq_ctx_get(vdev, 0);
diff --git a/drivers/vfio/pci/vfio_pci_liveupdate.c b/drivers/vfio/pci/vfio_pci_liveupdate.c
index 8fd5af4f4b83..ac77241ffb1e 100644
--- a/drivers/vfio/pci/vfio_pci_liveupdate.c
+++ b/drivers/vfio/pci/vfio_pci_liveupdate.c
@@ -6,28 +6,325 @@
* David Matlack <dmatlack@google.com>
*/
+/**
+ * DOC: VFIO PCI Preservation via LUO
+ *
+ * VFIO PCI devices in noiommu mode can be preserved over a kexec using the Live
+ * Update Orchestrator (LUO) file preservation while keeping the device running
+ * and performing DMA across the kexec transition. This allows userspace (such
+ * as a VMM or userspace driver) to transfer an in-use device to the next kernel
+ * without resetting it.
+ *
+ * .. note::
+ * This support is enabled by ``CONFIG_VFIO_PCI_LIVEUPDATE`` and is limited
+ * to devices in noiommu mode (see Restrictions below).
+ *
+ * Usage Example
+ * =============
+ *
+ * VFIO PCI devices can be preserved across a kexec by preserving the bound
+ * character device file associated with the device in a LUO session::
+ *
+ * device_fd = open("/dev/vfio/devices/noiommu-vfioX", O_RDWR);
+ * ioctl(device_fd, VFIO_DEVICE_BIND_IOMMUFD, &bind);
+ * ...
+ * ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, { ..., device_fd, ...});
+ *
+ * .. note::
+ * LUO will hold an extra reference to the device file for as long as it is
+ * preserved, so there is no way for the file to be destroyed or the device
+ * to be unbound from the vfio-pci driver while it is preserved.
+ *
+ * Retrieving the file after kexec is not yet supported.
+ *
+ * Restrictions
+ * ============
+ *
+ * The kernel imposes the following restrictions when preserving VFIO devices:
+ *
+ * * The device must be bound to the ``vfio-pci`` driver (not a variant driver)
+ * and registered in **noiommu** mode (``vfio.enable_unsafe_noiommu_mode=1``
+ * with no IOMMU group attached to the device). Attempting to preserve an
+ * IOMMU-backed device fails with ``-EOPNOTSUPP``.
+ *
+ * * The device file descriptor must be obtained by opening the VFIO character
+ * device (``/dev/vfio/devices/noiommu-vfioX``) and not via
+ * ``VFIO_GROUP_GET_DEVICE_FD``, and ``VFIO_DEVICE_BIND_IOMMUFD`` must have
+ * completed on that file descriptor before it is preserved.
+ *
+ * * ``CONFIG_VFIO_PCI_ZDEV_KVM`` must not be enabled. This may be relaxed in
+ * the future.
+ *
+ * * The device must not be an Intel display device or SR-IOV Virtual Function
+ * (VF). This may be relaxed in the future.
+ *
+ * * The device must meet the following conditions prior to kexec, without
+ * which the ``reboot(LINUX_REBOOT_CMD_KEXEC)`` syscall (to initiate the
+ * kexec) will fail with ``-EINVAL`` and abort the Live Update:
+ *
+ * - All VFIO interrupts on the device must be disabled.
+ * - The device must be in the ``PCI_D0`` power state and not in VFIO
+ * low-power mode (``VFIO_DEVICE_FEATURE_LOW_POWER_ENTRY``).
+ * - SR-IOV must not be enabled on the device.
+ *
+ * In addition, the device must meet all of the restrictions imposed by the
+ * core PCI layer documented at :doc:`/PCI/liveupdate`.
+ *
+ * Preservation Behavior
+ * =====================
+ *
+ * During ``LIVEUPDATE_SESSION_PRESERVE_FD``, VFIO serializes the device's PCI
+ * domain and BDF, its ``reset_works`` capability flag, and the initial
+ * ``struct pci_saved_state`` captured when the device was first opened.
+ *
+ * During the ``reboot(LINUX_REBOOT_CMD_KEXEC)`` freeze phase, VFIO validates
+ * that interrupts are disabled and the device is in ``PCI_D0``, snapshots the
+ * virtualized PCI configuration space (``vconfig``) and capability map
+ * (``pci_config_map``), masks physical INTx (`PCI_COMMAND_INTX_DISABLE`), and
+ * marks the device frozen so subsequent userspace accesses return ``-EIO``.
+ *
+ * VFIO ioctls and config-space writes that race with the freeze phase are
+ * serialized with it: those that could change the device or ``vconfig``
+ * either complete before the snapshot is taken or fail with ``-EIO``
+ * (``-EBUSY`` for ``VFIO_DEVICE_PCI_HOT_RESET``). Userspace should still
+ * quiesce them before invoking ``reboot(LINUX_REBOOT_CMD_KEXEC)``. Existing
+ * BAR mmaps, ioeventfds and dma-bufs keep working while the device is frozen
+ * because the device keeps running.
+ *
+ * The device is **not** reset and ``PCI_COMMAND_MASTER`` is left untouched so
+ * the device continues running and performing DMA across kexec. If the Live
+ * Update is aborted after freeze (for example, if a later file fails to freeze),
+ * the ``unfreeze()`` callback restores the physical INTx mask and clears the
+ * frozen state, leaving the existing file descriptor immediately usable again.
+ *
+ * DMA Memory Ordering Contract
+ * ============================
+ *
+ * Because a preserved noiommu device continues performing DMA using physical
+ * addresses across kexec, any memory buffer targeted by device DMA must remain
+ * at the same physical address in both kernels and must not be freed to the
+ * page allocator while the device can still access it:
+ *
+ * * When using a LUO-preserved ``memfd``, userspace **must** preserve the
+ * ``memfd`` in the **same** LUO session as the VFIO device and **before**
+ * preserving the VFIO device. LUO finishes files within a session in reverse
+ * preservation order (LIFO), guaranteeing that if the device is not taken
+ * over in the new kernel, VFIO's ``finish()`` callback stops bus mastering
+ * and resets the device before the ``memfd``'s ``finish()`` callback releases
+ * its folios. Cross-session ordering is the responsibility of userspace.
+ *
+ * * After retrieving a preserved ``memfd`` in the new kernel, its folios are
+ * ordinary unpinned shmem page cache until pinned again; userspace must keep
+ * the ``memfd`` alive and re-pin/map it before finishing the session, and
+ * must not rely on a ``memfd`` whose ``LIVEUPDATE_SESSION_RETRIEVE_FD`` call
+ * failed (failed ``memfd`` retrieval frees its folios immediately).
+ */
+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cleanup.h>
#include <linux/errno.h>
+#include <linux/kexec_handover.h>
#include <linux/kho/abi/vfio_pci.h>
#include <linux/liveupdate.h>
#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/pci.h>
+#include <linux/pci_liveupdate.h>
+#include <linux/string.h>
+#include <linux/vfio.h>
#include "vfio_pci_priv.h"
static bool vfio_pci_liveupdate_can_preserve(struct liveupdate_file_handler *handler,
struct file *file)
{
- return false;
+ struct vfio_device *device = vfio_device_from_file(file);
+ struct vfio_pci_core_device *vdev;
+ struct pci_dev *pdev;
+
+ if (!device)
+ return false;
+
+ if (device->ops != &vfio_pci_ops)
+ return false;
+
+ guard(mutex)(&device->dev_set->lock);
+
+ /* Live Update support is limited to bound cdev files. */
+ if (!vfio_file_is_bound_cdev(file))
+ return false;
+
+ vdev = container_of(device, struct vfio_pci_core_device, vdev);
+ pdev = vdev->pdev;
+
+ /*
+ * Don't support specialized vfio-pci devices for now since they haven't
+ * been tested.
+ */
+ if (IS_ENABLED(CONFIG_VFIO_PCI_ZDEV_KVM) || vfio_pci_is_intel_display(pdev))
+ return false;
+
+ /* Only non-VFs are supported for now. */
+ if (pdev->is_virtfn)
+ return false;
+
+ return true;
}
static int vfio_pci_liveupdate_preserve(struct liveupdate_file_op_args *args)
{
- return -EOPNOTSUPP;
+ struct vfio_device *device = vfio_device_from_file(args->file);
+ struct vfio_pci_core_device_ser *ser;
+ struct vfio_pci_core_device *vdev;
+ size_t saved_size = 0, total_size;
+ struct pci_dev *pdev;
+ u16 cfg_size;
+ int err;
+
+ if (!vfio_device_is_noiommu(device))
+ return -EOPNOTSUPP;
+
+ vdev = container_of(device, struct vfio_pci_core_device, vdev);
+ pdev = vdev->pdev;
+ cfg_size = pdev->cfg_size;
+
+ if (cfg_size != PCI_CFG_SPACE_SIZE && cfg_size != PCI_CFG_SPACE_EXP_SIZE)
+ return -EINVAL;
+
+ guard(mutex)(&device->dev_set->lock);
+
+ if (!vfio_file_is_bound_cdev(args->file))
+ return -EINVAL;
+
+ if (vdev->pci_saved_state) {
+ saved_size = pci_liveupdate_saved_state_ser_size(vdev->pci_saved_state);
+ if (!saved_size)
+ return -EINVAL;
+ }
+
+ total_size = size_add(sizeof(*ser),
+ size_add(size_mul(2, cfg_size), saved_size));
+ if (total_size > U32_MAX)
+ return -EOVERFLOW;
+
+ ser = kho_alloc_preserve(total_size);
+ if (IS_ERR(ser))
+ return PTR_ERR(ser);
+
+ if (saved_size) {
+ void *dst = ser->data + 2 * (size_t)cfg_size;
+
+ err = pci_liveupdate_serialize_saved_state(vdev->pci_saved_state,
+ dst, saved_size);
+ if (err) {
+ kho_unpreserve_free(ser);
+ return err;
+ }
+ }
+
+ err = pci_liveupdate_preserve(pdev);
+ if (err) {
+ kho_unpreserve_free(ser);
+ return err;
+ }
+
+ ser->domain = pci_domain_nr(pdev->bus);
+ ser->bdf = pci_dev_id(pdev);
+ ser->cfg_size = cfg_size;
+ ser->size = total_size;
+ ser->flags = vdev->reset_works ? VFIO_PCI_SER_RESET_WORKS : 0;
+ ser->saved_state_size = saved_size;
+ ser->reserved = 0;
+
+ args->serialized_data = virt_to_phys(ser);
+ return 0;
}
static void vfio_pci_liveupdate_unpreserve(struct liveupdate_file_op_args *args)
{
+ struct vfio_device *device = vfio_device_from_file(args->file);
+
+ pci_liveupdate_unpreserve(to_pci_dev(device->dev));
+ kho_unpreserve_free(phys_to_virt(args->serialized_data));
+}
+
+static int vfio_pci_liveupdate_freeze(struct liveupdate_file_op_args *args)
+{
+ struct vfio_pci_core_device_ser *ser = phys_to_virt(args->serialized_data);
+ struct vfio_device *device = vfio_device_from_file(args->file);
+ struct vfio_pci_core_device *vdev;
+ struct pci_dev *pdev;
+ u16 cmd;
+
+ vdev = container_of(device, struct vfio_pci_core_device, vdev);
+ pdev = vdev->pdev;
+
+ guard(mutex)(&device->dev_set->lock);
+ guard(mutex)(&vdev->igate);
+ guard(rwsem_write)(&vdev->memory_lock);
+
+ if (!vdev->vconfig || !vdev->pci_config_map ||
+ ser->cfg_size != pdev->cfg_size)
+ return -EINVAL;
+
+ /*
+ * Userspace must disable interrupts on the device prior to freeze so
+ * that the device does not send any interrupts until new interrupt
+ * handlers have been established by the next kernel.
+ */
+ if (vdev->irq_type != VFIO_PCI_NUM_IRQS) {
+ pci_err(pdev, "Freeze failed! Interrupts are still enabled.\n");
+ return -EINVAL;
+ }
+
+ if (pdev->current_state != PCI_D0 || vdev->pm_runtime_engaged) {
+ pci_err(pdev, "Freeze failed! Device not in D0 state.\n");
+ return -EINVAL;
+ }
+
+ /*
+ * VFs are not preserved, and the next kernel disables SR-IOV on the
+ * device while it keeps running. vfio_pci_core_sriov_configure() sets
+ * sriov_active under memory_lock, and does not enable SR-IOV once the
+ * device is frozen.
+ */
+ if (vdev->sriov_active) {
+ pci_err(pdev, "Freeze failed! SR-IOV is enabled.\n");
+ return -EINVAL;
+ }
+
+ memcpy(ser->data, vdev->vconfig, ser->cfg_size);
+ memcpy(ser->data + ser->cfg_size, vdev->pci_config_map, ser->cfg_size);
+
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ vdev->liveupdate_intx_disabled = !!(cmd & PCI_COMMAND_INTX_DISABLE);
+ if (!vdev->liveupdate_intx_disabled)
+ pci_intx(pdev, 0);
+
+ /*
+ * Disable ioctl, read, write and mmap access to the device while it is
+ * frozen. If the Live Update is aborted, unfreeze() re-enables access.
+ */
+ smp_store_release(&vdev->liveupdate_frozen, true);
+ return 0;
+}
+
+static void vfio_pci_liveupdate_unfreeze(struct liveupdate_file_op_args *args)
+{
+ struct vfio_device *device = vfio_device_from_file(args->file);
+ struct vfio_pci_core_device *vdev =
+ container_of(device, struct vfio_pci_core_device, vdev);
+
+ guard(mutex)(&device->dev_set->lock);
+ guard(mutex)(&vdev->igate);
+ guard(rwsem_write)(&vdev->memory_lock);
+
+ if (!vdev->liveupdate_intx_disabled)
+ pci_intx(vdev->pdev, 1);
+
+ /* Pairs with smp_load_acquire() in vfio_pci_dev_is_frozen(). */
+ smp_store_release(&vdev->liveupdate_frozen, false);
}
static int vfio_pci_liveupdate_retrieve(struct liveupdate_file_op_args *args)
@@ -43,6 +340,8 @@ static const struct liveupdate_file_ops vfio_pci_liveupdate_file_ops = {
.can_preserve = vfio_pci_liveupdate_can_preserve,
.preserve = vfio_pci_liveupdate_preserve,
.unpreserve = vfio_pci_liveupdate_unpreserve,
+ .freeze = vfio_pci_liveupdate_freeze,
+ .unfreeze = vfio_pci_liveupdate_unfreeze,
.retrieve = vfio_pci_liveupdate_retrieve,
.finish = vfio_pci_liveupdate_finish,
.owner = THIS_MODULE,
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index 872b01bcb5a3..2c732cb1c189 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -25,7 +25,9 @@ struct vfio_pci_ioeventfd {
bool test_mem;
};
+bool __vfio_pci_intx_mask(struct vfio_pci_core_device *vdev);
bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev);
+void __vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev);
void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev);
int vfio_pci_eventfd_replace_locked(struct vfio_pci_core_device *vdev,
@@ -151,6 +153,12 @@ static inline void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev,
#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
int __init vfio_pci_liveupdate_init(void);
void vfio_pci_liveupdate_cleanup(void);
+
+static inline bool vfio_pci_dev_is_frozen(struct vfio_pci_core_device *vdev)
+{
+ /* Written in VFIO PCI Liveupdate during freeze()/unfreeze() */
+ return smp_load_acquire(&vdev->liveupdate_frozen);
+}
#else
static inline int vfio_pci_liveupdate_init(void)
{
@@ -160,6 +168,11 @@ static inline int vfio_pci_liveupdate_init(void)
static inline void vfio_pci_liveupdate_cleanup(void)
{
}
+
+static inline bool vfio_pci_dev_is_frozen(struct vfio_pci_core_device *vdev)
+{
+ return false;
+}
#endif /* CONFIG_VFIO_PCI_LIVEUPDATE */
#endif
diff --git a/include/linux/kho/abi/vfio_pci.h b/include/linux/kho/abi/vfio_pci.h
index 67e8199eec59..725043a44b55 100644
--- a/include/linux/kho/abi/vfio_pci.h
+++ b/include/linux/kho/abi/vfio_pci.h
@@ -9,14 +9,19 @@
#ifndef _LINUX_LIVEUPDATE_ABI_VFIO_PCI_H
#define _LINUX_LIVEUPDATE_ABI_VFIO_PCI_H
+#include <linux/bits.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+
/**
* DOC: VFIO PCI Live Update ABI
*
* VFIO uses the ABI defined below for preserving device files across a kexec
* reboot using LUO.
*
- * Device metadata is serialized into memory which is then handed to the next
- * kernel via KHO.
+ * Device metadata, virtualized PCI configuration space, VFIO's capability map,
+ * and the initial PCI saved state captured when the device was first opened are
+ * serialized into memory which is then handed to the next kernel via KHO.
*
* This interface is a contract. Any modification to any of the serialization
* structs defined here constitutes a breaking change. Such changes require
@@ -25,4 +30,47 @@
#define VFIO_PCI_LUO_FH_COMPATIBLE "vfio-pci-v1"
+/**
+ * define VFIO_PCI_SER_RESET_WORKS - Device supports function reset.
+ *
+ * Set in &vfio_pci_core_device_ser.flags when the preserved device had
+ * ``vdev->reset_works == true`` prior to kexec.
+ */
+#define VFIO_PCI_SER_RESET_WORKS BIT(0)
+
+#define VFIO_PCI_SER_KNOWN_FLAGS VFIO_PCI_SER_RESET_WORKS
+
+/**
+ * struct vfio_pci_core_device_ser - Serialized state of a single VFIO PCI
+ * device.
+ *
+ * @domain: The device's PCI domain number (segment).
+ * @bdf: The device's PCI bus, device, and function number.
+ * @cfg_size: Size in bytes of the device's PCI configuration space
+ * (``PCI_CFG_SPACE_SIZE`` or ``PCI_CFG_SPACE_EXP_SIZE``).
+ * @size: Total size in bytes of this serialized buffer, equal to
+ * ``sizeof(struct vfio_pci_core_device_ser) + 2 * @cfg_size + @saved_state_size``.
+ * @flags: Bitmask of ``VFIO_PCI_SER_*`` flags; unknown bits are rejected.
+ * @saved_state_size: Size in bytes of the serialized &struct pci_saved_state_ser
+ * blob stored in @data after ``config_map``, or 0 if none.
+ * @reserved: Reserved for future use; must be 0.
+ * @data: Variable-length payload containing, in order:
+ * 1. ``u8 vconfig[@cfg_size]`` - VFIO virtualized config space snapshot
+ * captured during ``freeze()``.
+ * 2. ``u8 config_map[@cfg_size]`` - VFIO capability map snapshot
+ * captured during ``freeze()``.
+ * 3. ``u8 saved_state[@saved_state_size]`` - serialized
+ * &struct pci_saved_state_ser captured during ``preserve()``.
+ */
+struct vfio_pci_core_device_ser {
+ u32 domain;
+ u16 bdf;
+ u16 cfg_size;
+ u32 size;
+ u32 flags;
+ u32 saved_state_size;
+ u32 reserved;
+ u8 data[];
+} __packed;
+
#endif /* _LINUX_LIVEUPDATE_ABI_VFIO_PCI_H */
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index 9a1674c152aa..6bae7581f4f4 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -149,6 +149,10 @@ struct vfio_pci_core_device {
struct notifier_block nb;
struct rw_semaphore memory_lock;
struct list_head dmabufs;
+#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
+ bool liveupdate_frozen;
+ bool liveupdate_intx_disabled;
+#endif
};
enum vfio_pci_io_width {
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 08/27] vfio: Introduce vfio_find_device() helper
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (6 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 07/27] vfio/pci: Preserve vfio-pci device files across Live Update Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 09/27] vfio: Refactor vfio_device_fops_cdev_open() Vipin Sharma
` (18 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Introduce vfio_find_device() to discover a registered vfio_device from
vfio_device_class matching user-provided criteria.
To ensure safe lifecycle handling, vfio_find_device() acquires a
registration reference (device->refcount) via
vfio_device_try_get_registration() rather than relying solely on
device.kref taken in class_find_device(). Holding only kref allows
vendor modules to unload while external callers hold device pointers,
leading to a kernel panic on release. Callers must release the device
via vfio_device_put_registration().
Additionally, move refcount_set() prior to vfio_device_add() in
__vfio_register_dev() so device->refcount is initialized before the
device is published to class lookups, avoiding registration data races.
On vfio_device_add() failure, the error path drops the initial
registration reference and waits for completion to drain any reference
taken by a concurrent lookup before returning the error to the driver.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/vfio_main.c | 64 +++++++++++++++++++++++++++++++++++++---
include/linux/vfio.h | 2 ++
2 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index 291460102cd1..00b3bc0c5339 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -13,6 +13,7 @@
#include <linux/cdev.h>
#include <linux/compat.h>
#include <linux/device.h>
+#include <linux/device/class.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/iommu.h>
@@ -383,17 +384,20 @@ static int __vfio_register_dev(struct vfio_device *device,
goto err_out;
}
- ret = vfio_device_add(device);
- if (ret)
- goto err_out;
-
/* Refcounting can't start until the driver calls register */
refcount_set(&device->refcount, 1);
+ ret = vfio_device_add(device);
+ if (ret)
+ goto err_add;
+
vfio_device_group_register(device);
vfio_device_debugfs_init(device);
return 0;
+err_add:
+ vfio_device_put_registration(device);
+ wait_for_completion(&device->comp);
err_out:
vfio_device_remove_group(device);
return ret;
@@ -1845,6 +1849,58 @@ int vfio_dma_rw(struct vfio_device *device, dma_addr_t iova, void *data,
}
EXPORT_SYMBOL(vfio_dma_rw);
+struct vfio_device_match_data {
+ const void *data;
+ device_match_t match;
+};
+
+static int vfio_device_match_fn(struct device *dev, const void *data)
+{
+ struct vfio_device *vdev = container_of(dev, struct vfio_device, device);
+ const struct vfio_device_match_data *mdata = data;
+
+ if (mdata->match(dev, mdata->data)) {
+ if (vfio_device_try_get_registration(vdev))
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * vfio_find_device - Find a registered VFIO device matching search criteria
+ * @data: Context data passed to the @match function
+ * @match: Device match callback function
+ *
+ * Finds a registered VFIO device in the vfio_device_class matching the search
+ * criteria specified by @match.
+ *
+ * On success, this function acquires a registration reference on the matched
+ * device via vfio_device_try_get_registration(). This pins the device and
+ * prevents its vendor driver module from unloading or unbinding while the
+ * reference is held.
+ *
+ * Return: Pointer to the matching vfio_device on success, or NULL if no
+ * matching device is found. The caller MUST release the acquired registration
+ * reference using vfio_device_put_registration() when finished with the device.
+ */
+struct vfio_device *vfio_find_device(const void *data, device_match_t match)
+{
+ struct vfio_device_match_data mdata = { .data = data, .match = match };
+ struct device *device;
+
+ device = class_find_device(&vfio_device_class, NULL, &mdata, vfio_device_match_fn);
+ if (!device)
+ return NULL;
+
+ /*
+ * Drop standard kref reference. Caller holds reference from
+ * vfio_device_try_get_registration().
+ */
+ put_device(device);
+ return container_of(device, struct vfio_device, device);
+}
+EXPORT_SYMBOL_GPL(vfio_find_device);
+
/*
* Module/class support
*/
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index 3cfbf1d73479..1c1e41de49b3 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -441,4 +441,6 @@ static inline bool vfio_file_is_bound_cdev(struct file *file)
}
#endif /* IS_ENABLED(CONFIG_VFIO_DEVICE_CDEV) */
+struct vfio_device *vfio_find_device(const void *data, device_match_t match);
+
#endif /* VFIO_H */
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 09/27] vfio: Refactor vfio_device_fops_cdev_open()
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (7 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 08/27] vfio: Introduce vfio_find_device() helper Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 10/27] vfio: Add API to open cdev device for Live Update restore Vipin Sharma
` (17 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Refactor vfio_device_fops_cdev_open() to use a static helper
vfio_device_cdev_open(). This helper will be used in subsequent
patches to support opening the device file during Live Update restore.
No functional change intended.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/device_cdev.c | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index 23b229468151..d83e99137e1a 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -20,14 +20,8 @@ void vfio_init_device_cdev(struct vfio_device *device)
device->cdev.owner = THIS_MODULE;
}
-/*
- * device access via the fd opened by this function is blocked until
- * .open_device() is called successfully during BIND_IOMMUFD.
- */
-int vfio_device_fops_cdev_open(struct inode *inode, struct file *filep)
+static int vfio_device_cdev_open(struct vfio_device *device, struct file *file)
{
- struct vfio_device *device = container_of(inode->i_cdev,
- struct vfio_device, cdev);
struct vfio_device_file *df;
int ret;
@@ -46,14 +40,14 @@ int vfio_device_fops_cdev_open(struct inode *inode, struct file *filep)
goto err_put_registration;
}
- filep->private_data = df;
+ file->private_data = df;
/*
* Use the pseudo fs inode on the device to link all mmaps
* to the same address space, allowing us to unmap all vmas
* associated to this device using unmap_mapping_range().
*/
- filep->f_mapping = device->inode->i_mapping;
+ file->f_mapping = device->inode->i_mapping;
return 0;
@@ -62,6 +56,18 @@ int vfio_device_fops_cdev_open(struct inode *inode, struct file *filep)
return ret;
}
+/*
+ * device access via the fd opened by this function is blocked until
+ * .open_device() is called successfully during BIND_IOMMUFD.
+ */
+int vfio_device_fops_cdev_open(struct inode *inode, struct file *file)
+{
+ struct vfio_device *device = container_of(inode->i_cdev,
+ struct vfio_device, cdev);
+
+ return vfio_device_cdev_open(device, file);
+}
+
static void vfio_df_get_kvm_safe(struct vfio_device_file *df)
{
spin_lock(&df->kvm_ref_lock);
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 10/27] vfio: Add API to open cdev device for Live Update restore
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (8 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 09/27] vfio: Refactor vfio_device_fops_cdev_open() Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 11/27] vfio/pci: Retrieve preserved device files after Live Update Vipin Sharma
` (16 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
During Live Update restore, VFIO PCI driver needs to recreate the device
file context that was preserved before kexec and return it to the user.
Provide an API for VFIO Live Update to get VFIO device file. Use an
anonymous inode when creating the file, since the retrieved device file
is not opened through any particular cdev inode, and the cdev inode does
not matter in practice.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/device_cdev.c | 39 ++++++++++++++++++++++++++++++++++++--
include/linux/vfio.h | 6 ++++++
2 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index d83e99137e1a..38e691eceab5 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -2,6 +2,7 @@
/*
* Copyright (c) 2023 Intel Corporation.
*/
+#include <linux/anon_inodes.h>
#include <linux/vfio.h>
#include <linux/iommufd.h>
@@ -20,9 +21,10 @@ void vfio_init_device_cdev(struct vfio_device *device)
device->cdev.owner = THIS_MODULE;
}
-static int vfio_device_cdev_open(struct vfio_device *device, struct file *file)
+static int vfio_device_cdev_open(struct vfio_device *device, struct file **filep)
{
struct vfio_device_file *df;
+ struct file *file = *filep;
int ret;
/* Paired with the put in vfio_device_fops_release() */
@@ -40,6 +42,24 @@ static int vfio_device_cdev_open(struct vfio_device *device, struct file *file)
goto err_put_registration;
}
+ /*
+ * Simulate opening the character device using an anonymous inode. The
+ * returned file has the same properties as a cdev file (e.g. operations
+ * are blocked until BIND_IOMMUFD is called).
+ */
+ if (!file) {
+ file = anon_inode_getfile_fmode("[vfio-device-liveupdate]",
+ &vfio_device_fops, NULL,
+ O_RDWR, FMODE_PREAD | FMODE_PWRITE);
+
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ goto err_free_device_file;
+ }
+
+ *filep = file;
+ }
+
file->private_data = df;
/*
@@ -51,11 +71,26 @@ static int vfio_device_cdev_open(struct vfio_device *device, struct file *file)
return 0;
+err_free_device_file:
+ kfree(df);
err_put_registration:
vfio_device_put_registration(device);
return ret;
}
+struct file *vfio_device_liveupdate_cdev_open(struct vfio_device *device)
+{
+ struct file *file = NULL;
+ int ret;
+
+ ret = vfio_device_cdev_open(device, &file);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return file;
+}
+EXPORT_SYMBOL_GPL(vfio_device_liveupdate_cdev_open);
+
/*
* device access via the fd opened by this function is blocked until
* .open_device() is called successfully during BIND_IOMMUFD.
@@ -65,7 +100,7 @@ int vfio_device_fops_cdev_open(struct inode *inode, struct file *file)
struct vfio_device *device = container_of(inode->i_cdev,
struct vfio_device, cdev);
- return vfio_device_cdev_open(device, file);
+ return vfio_device_cdev_open(device, &file);
}
static void vfio_df_get_kvm_safe(struct vfio_device_file *df)
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index 1c1e41de49b3..f46796b6724c 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -434,11 +434,17 @@ void vfio_virqfd_flush_thread(struct virqfd **pvirqfd);
#if IS_ENABLED(CONFIG_VFIO_DEVICE_CDEV)
bool vfio_file_is_bound_cdev(struct file *file);
+struct file *vfio_device_liveupdate_cdev_open(struct vfio_device *device);
#else
static inline bool vfio_file_is_bound_cdev(struct file *file)
{
return false;
}
+
+static inline struct file *vfio_device_liveupdate_cdev_open(struct vfio_device *device)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
#endif /* IS_ENABLED(CONFIG_VFIO_DEVICE_CDEV) */
struct vfio_device *vfio_find_device(const void *data, device_match_t match);
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 11/27] vfio/pci: Retrieve preserved device files after Live Update
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (9 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 10/27] vfio: Add API to open cdev device for Live Update restore Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 12/27] vfio: Enforce preserved devices are retrieved via LIVEUPDATE_SESSION_RETRIEVE_FD Vipin Sharma
` (15 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Enable userspace to retrieve preserved VFIO device files after a Live
Update by implementing the retrieve() and finish() file handler
callbacks.
During retrieve():
* Validate the handed-over struct vfio_pci_core_device_ser header,
sizes, and flags.
* Locate the registered vfio-pci device matching the serialized domain
and BDF (requiring device->ops == &vfio_pci_ops) under the device
lock. vfio-pci registers the vfio device before its probe completes,
and the probe holds that lock, so a device that is still being probed
is not returned.
* Require the device to be in noiommu mode in the new kernel (returning
-EOPNOTSUPP otherwise), still marked incoming by the PCI Live Update
core, and matching the serialized PCI config space size.
* Deserialize the inherited struct pci_saved_state and copy the
handed-over vconfig and pci_config_map into a kernel-owned struct
vfio_pci_liveupdate_incoming attached to vdev under dev_set->lock via
vfio_pci_liveupdate_attach_incoming(), leaving the KHO serialized
buffer read-only.
* Open and return an anonymous VFIO cdev file via
vfio_device_liveupdate_cdev_open().
Do not implement can_finish(): closing a retrieved LUO session invokes
finish, and refusing can_finish() would leak the session and leave
unclaimed devices running. Instead, finish() always completes:
* If the retrieved device was claimed by opening it via
VFIO_DEVICE_BIND_IOMMUFD (which consumes vdev->liveupdate_incoming;
subsequent patches replace the open-time reset with non-reset
take-over, reject opening incoming devices other than via retrieve(),
and protect incoming kept-running devices across runtime PM, dev_set
resets, and non-noiommu boots), finish() simply completes its PCI
Live Update participation.
* If the device was not claimed (never retrieved, retrieve() failed, or
retrieved but never bound via VFIO_DEVICE_BIND_IOMMUFD),
vfio_pci_core_liveupdate_reset() logs one pci_warn() naming the
device, clears PCI_COMMAND_MASTER, resets the function, and restores
the inherited pci_saved_state just as closing the device would. If
the device has a function reset but is not reset, e.g. because
pci_dev_trylock() contends with a concurrent driver unbind, latch
vdev->liveupdate_reset_pending so vfio_pci_core_unregister_device()
completes the function reset and state restoration via
vfio_pci_core_liveupdate_reset_locked() under the device lock held by
device_release_driver(). Opening the device after finish() resets it
like any open, so it drops the latch.
* If the device was not retrieved, look it up under pci_dev_lock(),
which waits for a vfio-pci probe in progress. If it is bound to
vfio-pci, finish it as above. If it has no driver, clear bus
mastering and reset it. Otherwise leave it to the bound driver.
* Call pci_liveupdate_finish() and kho_restore_free() on every path.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/pci/vfio_pci_core.c | 65 +++++
drivers/vfio/pci/vfio_pci_liveupdate.c | 342 ++++++++++++++++++++++++-
drivers/vfio/pci/vfio_pci_priv.h | 75 ++++++
include/linux/vfio_pci_core.h | 6 +
4 files changed, 484 insertions(+), 4 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 5f8c882d5778..60ada3bbb6ff 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -671,6 +671,8 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
vfio_pci_core_map_bars(vdev);
+ vfio_pci_liveupdate_claim_incoming(vdev);
+
return 0;
out_free_zdev:
@@ -739,6 +741,63 @@ static void vfio_pci_core_try_reset(struct vfio_pci_core_device *vdev)
pci_restore_state(pdev);
}
+/*
+ * Reset a device that was kept running across a Live Update but not taken over,
+ * from the Live Update finish() of the device. Return true if the device was
+ * reset, false if it could not be reset, e.g. because its device lock could
+ * not be taken.
+ */
+bool vfio_pci_core_liveupdate_reset(struct vfio_pci_core_device *vdev,
+ struct pci_saved_state *saved_state,
+ bool reset_works, const char *reason)
+{
+ struct pci_dev *pdev = vdev->pdev;
+
+ lockdep_assert_held(&vdev->vdev.dev_set->lock);
+
+ if (reason)
+ pci_warn(pdev,
+ "Resetting device not taken over after Live Update (%s)\n",
+ reason);
+
+ pci_clear_master(pdev);
+ if (!saved_state)
+ pci_save_state(pdev);
+ kfree(vdev->pci_saved_state);
+ vdev->pci_saved_state = saved_state;
+ vdev->reset_works = reset_works;
+ vfio_pci_core_try_reset(vdev);
+
+ return !vdev->needs_reset;
+}
+EXPORT_SYMBOL_GPL(vfio_pci_core_liveupdate_reset);
+
+/*
+ * Reset a device preserved across a Live Update from its remove() path, where
+ * the driver core holds the device lock. Save and restore the config space
+ * around the reset like vfio_pci_core_try_reset(), and block user config
+ * access while doing so. Like pci_reset_function_locked(), do not take the
+ * upstream bridge lock: remove() cannot wait for it without inverting the
+ * bridge -> device lock order, and the Live Update finish() of an unbound
+ * device holds it while it waits for the device lock held here.
+ */
+void vfio_pci_core_liveupdate_reset_locked(struct vfio_pci_core_device *vdev)
+{
+ struct pci_dev *pdev = vdev->pdev;
+
+ device_lock_assert(&pdev->dev);
+ lockdep_assert_held(&vdev->vdev.dev_set->lock);
+
+ pci_clear_master(pdev);
+ pci_cfg_access_lock(pdev);
+ pci_save_state(pdev);
+ pci_write_config_word(pdev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
+ if (!__pci_reset_function_locked(pdev))
+ vdev->needs_reset = false;
+ pci_restore_state(pdev);
+ pci_cfg_access_unlock(pdev);
+}
+
void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
@@ -2362,6 +2421,12 @@ void vfio_pci_core_unregister_device(struct vfio_pci_core_device *vdev)
vfio_unregister_group_dev(&vdev->vdev);
+ /*
+ * Must follow vfio_unregister_group_dev(), which waits for the
+ * references a Live Update finish() of the device may hold.
+ */
+ vfio_pci_liveupdate_unregister_dev(vdev);
+
vfio_pci_vf_uninit(vdev);
vfio_pci_vga_uninit(vdev);
diff --git a/drivers/vfio/pci/vfio_pci_liveupdate.c b/drivers/vfio/pci/vfio_pci_liveupdate.c
index ac77241ffb1e..f13c757b4567 100644
--- a/drivers/vfio/pci/vfio_pci_liveupdate.c
+++ b/drivers/vfio/pci/vfio_pci_liveupdate.c
@@ -35,7 +35,13 @@
* preserved, so there is no way for the file to be destroyed or the device
* to be unbound from the vfio-pci driver while it is preserved.
*
- * Retrieving the file after kexec is not yet supported.
+ * After kexec, the preserved VFIO device file can be retrieved from the session
+ * just like any other preserved file::
+ *
+ * ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg);
+ * device_fd = arg.fd;
+ * ...
+ * ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, ...);
*
* Restrictions
* ============
@@ -97,6 +103,36 @@
* the ``unfreeze()`` callback restores the physical INTx mask and clears the
* frozen state, leaving the existing file descriptor immediately usable again.
*
+ * Retrieval and Finish Behavior After Kexec
+ * =========================================
+ *
+ * After kexec, userspace must ensure the preserved PCI device is bound to
+ * ``vfio-pci`` in noiommu mode before invoking
+ * ``LIVEUPDATE_SESSION_RETRIEVE_FD`` (for example, via ``vfio-pci.ids=...`` on
+ * the kernel command line). A failed ``LIVEUPDATE_SESSION_RETRIEVE_FD`` call is
+ * cached permanently by LUO for that file.
+ *
+ * ``LIVEUPDATE_SESSION_RETRIEVE_FD`` validates the handed-over serialized state
+ * and returns a new unbound VFIO cdev file descriptor. The device is claimed on
+ * the first ``VFIO_DEVICE_BIND_IOMMUFD`` on that file descriptor.
+ *
+ * ``LIVEUPDATE_SESSION_FINISH`` (or closing a retrieved session file
+ * descriptor) always completes:
+ *
+ * * If the device was retrieved and claimed via ``VFIO_DEVICE_BIND_IOMMUFD``,
+ * ``finish()`` simply completes its Live Update participation.
+ * * If the device was not claimed (never retrieved, ``retrieve()`` failed, or
+ * retrieved but never bound via ``VFIO_DEVICE_BIND_IOMMUFD``), ``finish()``
+ * logs one ``pci_warn()`` naming the device, clears ``PCI_COMMAND_MASTER``,
+ * resets the function and restores the initial PCI saved state just as
+ * closing the device would, and finishes its Live Update participation so
+ * the device can be opened normally afterwards. If ``finish()`` cannot
+ * reset a device that has a function reset, for example because it cannot
+ * take the device lock while the device is being unbound from
+ * ``vfio-pci``, it still clears ``PCI_COMMAND_MASTER``, and the function
+ * reset is retried when the device is unbound, unless the device is opened
+ * (which resets it) before that.
+ *
* DMA Memory Ordering Contract
* ============================
*
@@ -110,8 +146,9 @@
* preserving the VFIO device. LUO finishes files within a session in reverse
* preservation order (LIFO), guaranteeing that if the device is not taken
* over in the new kernel, VFIO's ``finish()`` callback stops bus mastering
- * and resets the device before the ``memfd``'s ``finish()`` callback releases
- * its folios. Cross-session ordering is the responsibility of userspace.
+ * before the ``memfd``'s ``finish()`` callback releases its folios. It
+ * normally resets the device at that point too (see above for when it
+ * cannot). Cross-session ordering is the responsibility of userspace.
*
* * After retrieving a preserved ``memfd`` in the new kernel, its folios are
* ordinary unpinned shmem page cache until pinned again; userspace must keep
@@ -124,6 +161,7 @@
#include <linux/cleanup.h>
#include <linux/errno.h>
+#include <linux/file.h>
#include <linux/kexec_handover.h>
#include <linux/kho/abi/vfio_pci.h>
#include <linux/liveupdate.h>
@@ -131,6 +169,7 @@
#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/pci_liveupdate.h>
+#include <linux/slab.h>
#include <linux/string.h>
#include <linux/vfio.h>
@@ -327,13 +366,308 @@ static void vfio_pci_liveupdate_unfreeze(struct liveupdate_file_op_args *args)
smp_store_release(&vdev->liveupdate_frozen, false);
}
+static int vfio_pci_liveupdate_validate_ser(const struct vfio_pci_core_device_ser *ser)
+{
+ size_t expected;
+
+ if (ser->reserved)
+ return -EINVAL;
+
+ if (ser->flags & ~VFIO_PCI_SER_KNOWN_FLAGS)
+ return -EOPNOTSUPP;
+
+ if (ser->cfg_size != PCI_CFG_SPACE_SIZE &&
+ ser->cfg_size != PCI_CFG_SPACE_EXP_SIZE)
+ return -EINVAL;
+
+ expected = size_add(sizeof(*ser),
+ size_add(size_mul(2, ser->cfg_size),
+ ser->saved_state_size));
+ if (expected != ser->size)
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct pci_saved_state *
+vfio_pci_liveupdate_parse_saved_state(struct pci_dev *pdev,
+ const struct vfio_pci_core_device_ser *ser)
+{
+ const void *blob;
+
+ if (!ser->saved_state_size)
+ return NULL;
+
+ blob = ser->data + 2 * (size_t)ser->cfg_size;
+ return pci_liveupdate_deserialize_saved_state(pdev, blob,
+ ser->saved_state_size);
+}
+
+static int match_device(struct device *dev, const void *arg)
+{
+ struct vfio_device *device = container_of(dev, struct vfio_device, device);
+ const struct vfio_pci_core_device_ser *ser = arg;
+ struct pci_dev *pdev;
+
+ if (device->ops != &vfio_pci_ops)
+ return false;
+
+ pdev = dev_is_pci(device->dev) ? to_pci_dev(device->dev) : NULL;
+ if (!pdev)
+ return false;
+
+ return ser->bdf == pci_dev_id(pdev) &&
+ ser->domain == pci_domain_nr(pdev->bus);
+}
+
+/*
+ * vfio-pci makes the vfio device visible before its probe completes, and the
+ * probe runs under the device lock. Look the device up with that lock held, so
+ * that a device found here has completed its probe, and the reset done by
+ * finish() does not contend with the probe for that lock. Return the device
+ * with a registration reference held.
+ */
+static struct vfio_device *
+vfio_pci_liveupdate_find_probed(const struct vfio_pci_core_device_ser *ser)
+{
+ struct vfio_device *device;
+ struct pci_dev *pdev;
+
+ pdev = pci_get_domain_bus_and_slot(ser->domain, PCI_BUS_NUM(ser->bdf),
+ ser->bdf & 0xff);
+ if (!pdev)
+ return NULL;
+
+ device_lock(&pdev->dev);
+ device = vfio_find_device(ser, match_device);
+ device_unlock(&pdev->dev);
+ pci_dev_put(pdev);
+ return device;
+}
+
+static int
+vfio_pci_liveupdate_attach_incoming(struct vfio_pci_core_device *vdev,
+ struct vfio_pci_liveupdate_incoming *in)
+{
+ guard(mutex)(&vdev->vdev.dev_set->lock);
+
+ if (vdev->liveupdate_incoming || vdev->liveupdate_claimed ||
+ !pci_liveupdate_is_incoming(vdev->pdev))
+ return -EBUSY;
+
+ vdev->liveupdate_incoming = in;
+ return 0;
+}
+
static int vfio_pci_liveupdate_retrieve(struct liveupdate_file_op_args *args)
{
- return -EOPNOTSUPP;
+ struct pci_saved_state *saved_state = NULL;
+ const struct vfio_pci_core_device_ser *ser;
+ struct vfio_pci_liveupdate_incoming *in;
+ struct vfio_pci_core_device *vdev;
+ struct vfio_device *device;
+ struct pci_dev *pdev;
+ struct file *file;
+ size_t in_size;
+ int ret;
+
+ if (!args->serialized_data)
+ return -EINVAL;
+
+ ser = phys_to_virt(args->serialized_data);
+ ret = vfio_pci_liveupdate_validate_ser(ser);
+ if (ret)
+ return ret;
+
+ device = vfio_pci_liveupdate_find_probed(ser);
+ if (!device)
+ return -ENODEV;
+
+ vdev = container_of(device, struct vfio_pci_core_device, vdev);
+ pdev = vdev->pdev;
+
+ if (!vfio_device_is_noiommu(device)) {
+ ret = -EOPNOTSUPP;
+ goto out_put_dev;
+ }
+
+ if (!pci_liveupdate_is_incoming(pdev) ||
+ ser->cfg_size != pdev->cfg_size) {
+ ret = -EINVAL;
+ goto out_put_dev;
+ }
+
+ if (ser->saved_state_size) {
+ saved_state = vfio_pci_liveupdate_parse_saved_state(pdev, ser);
+ if (IS_ERR(saved_state)) {
+ ret = PTR_ERR(saved_state);
+ goto out_put_dev;
+ }
+ }
+
+ in_size = struct_size(in, data, size_mul(2, ser->cfg_size));
+ in = kzalloc(in_size, GFP_KERNEL);
+ if (!in) {
+ ret = -ENOMEM;
+ goto out_free_saved;
+ }
+
+ in->saved_state = saved_state;
+ in->reset_works = !!(ser->flags & VFIO_PCI_SER_RESET_WORKS);
+ in->cfg_size = ser->cfg_size;
+ memcpy(in->data, ser->data, 2 * (size_t)ser->cfg_size);
+
+ file = vfio_device_liveupdate_cdev_open(device);
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ goto out_free_in;
+ }
+
+ ret = vfio_pci_liveupdate_attach_incoming(vdev, in);
+ if (ret)
+ goto out_fput;
+
+ args->file = file;
+ vfio_device_put_registration(device);
+ return 0;
+
+out_fput:
+ fput(file);
+out_free_in:
+ kfree(in);
+out_free_saved:
+ kfree(saved_state);
+out_put_dev:
+ vfio_device_put_registration(device);
+ return ret;
+}
+
+static void vfio_pci_liveupdate_finish_vdev(struct vfio_pci_core_device *vdev,
+ const struct vfio_pci_core_device_ser *ser,
+ const char *reason)
+{
+ struct pci_dev *pdev = vdev->pdev;
+ struct pci_saved_state *saved_state = NULL;
+ bool reset_works = true;
+ bool was_reset;
+
+ scoped_guard(mutex, &vdev->vdev.dev_set->lock) {
+ if (!vdev->liveupdate_claimed &&
+ pci_liveupdate_is_incoming(pdev)) {
+ if (vdev->liveupdate_incoming) {
+ saved_state = vdev->liveupdate_incoming->saved_state;
+ vdev->liveupdate_incoming->saved_state = NULL;
+ reset_works = vdev->liveupdate_incoming->reset_works;
+ } else if (!vfio_pci_liveupdate_validate_ser(ser) &&
+ ser->cfg_size == pdev->cfg_size) {
+ reset_works = !!(ser->flags & VFIO_PCI_SER_RESET_WORKS);
+ saved_state = vfio_pci_liveupdate_parse_saved_state(pdev, ser);
+ if (IS_ERR(saved_state))
+ saved_state = NULL;
+ }
+
+ /*
+ * An unbind of the device waits for the reference held
+ * by finish() with the device lock held, so the reset
+ * only tries to take that lock. If the device has a
+ * function reset but was not reset, e.g. because that
+ * lock was taken, bus mastering is still cleared, and
+ * the function reset is retried when the device is
+ * unbound, unless the device is opened before that. See
+ * vfio_pci_liveupdate_unregister_dev() and
+ * vfio_pci_liveupdate_claim_incoming().
+ */
+ was_reset = vfio_pci_core_liveupdate_reset(vdev,
+ saved_state,
+ reset_works,
+ reason);
+ vdev->liveupdate_reset_pending = reset_works &&
+ !was_reset;
+ }
+ vfio_pci_liveupdate_free_incoming(vdev);
+ }
+
+ if (pci_liveupdate_is_incoming(pdev))
+ pci_liveupdate_finish(pdev);
+}
+
+/*
+ * Look up the vfio-pci device of a preserved device that was not retrieved,
+ * with the device lock held, like vfio_pci_liveupdate_find_probed(). If it is
+ * bound to vfio-pci, return the vfio-pci device with a registration reference
+ * held, so that the caller finishes it. Otherwise, finish the device here.
+ */
+static struct vfio_device *
+vfio_pci_liveupdate_finish_lookup(const struct vfio_pci_core_device_ser *ser)
+{
+ struct vfio_device *device = NULL;
+ struct pci_dev *pdev, *bridge;
+
+ pdev = pci_get_domain_bus_and_slot(ser->domain, PCI_BUS_NUM(ser->bdf),
+ ser->bdf & 0xff);
+ if (!pdev)
+ return NULL;
+
+ bridge = pci_upstream_bridge(pdev);
+ if (bridge)
+ pci_dev_lock(bridge);
+ pci_dev_lock(pdev);
+
+ if (pci_liveupdate_is_incoming(pdev)) {
+ if (!pdev->dev.driver) {
+ pci_warn(pdev,
+ "Resetting device not taken over after Live Update (not bound to vfio-pci)\n");
+ pci_clear_master(pdev);
+ pci_reset_function_locked(pdev);
+ pci_liveupdate_finish(pdev);
+ } else {
+ /*
+ * A driver is bound and its probe has completed, as it
+ * runs under the device lock. Holding the lock also
+ * prevents an unbind until the registration reference
+ * below is taken.
+ */
+ device = vfio_find_device(ser, match_device);
+ if (!device) {
+ pci_warn(pdev,
+ "Device not taken over after Live Update is bound to %s, not resetting it\n",
+ dev_driver_string(&pdev->dev));
+ pci_liveupdate_finish(pdev);
+ }
+ }
+ }
+
+ pci_dev_unlock(pdev);
+ if (bridge)
+ pci_dev_unlock(bridge);
+ pci_dev_put(pdev);
+ return device;
}
static void vfio_pci_liveupdate_finish(struct liveupdate_file_op_args *args)
{
+ struct vfio_pci_core_device_ser *ser = phys_to_virt(args->serialized_data);
+ struct vfio_pci_core_device *vdev;
+ struct vfio_device *device;
+ const char *reason;
+
+ if (args->retrieve_status > 0) {
+ device = vfio_device_from_file(args->file);
+ vdev = container_of(device, struct vfio_pci_core_device, vdev);
+ vfio_pci_liveupdate_finish_vdev(vdev, ser,
+ "retrieved but not opened");
+ } else {
+ reason = args->retrieve_status ? "retrieve failed" : "not retrieved";
+ device = vfio_pci_liveupdate_finish_lookup(ser);
+ if (device) {
+ vdev = container_of(device, struct vfio_pci_core_device,
+ vdev);
+ vfio_pci_liveupdate_finish_vdev(vdev, ser, reason);
+ vfio_device_put_registration(device);
+ }
+ }
+
+ kho_restore_free(ser);
}
static const struct liveupdate_file_ops vfio_pci_liveupdate_file_ops = {
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index 2c732cb1c189..d8670dc51bb4 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -150,6 +150,15 @@ static inline void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev,
}
#endif
+struct vfio_pci_liveupdate_incoming {
+ struct pci_saved_state *saved_state;
+ bool reset_works;
+ u16 cfg_size;
+ u8 data[];
+};
+
+void vfio_pci_core_liveupdate_reset_locked(struct vfio_pci_core_device *vdev);
+
#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
int __init vfio_pci_liveupdate_init(void);
void vfio_pci_liveupdate_cleanup(void);
@@ -159,6 +168,57 @@ static inline bool vfio_pci_dev_is_frozen(struct vfio_pci_core_device *vdev)
/* Written in VFIO PCI Liveupdate during freeze()/unfreeze() */
return smp_load_acquire(&vdev->liveupdate_frozen);
}
+
+static inline void
+vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
+{
+ struct vfio_pci_liveupdate_incoming *in = vdev->liveupdate_incoming;
+
+ if (!in)
+ return;
+
+ kfree(in->saved_state);
+ kfree(in);
+ vdev->liveupdate_incoming = NULL;
+}
+
+static inline void
+vfio_pci_liveupdate_claim_incoming(struct vfio_pci_core_device *vdev)
+{
+ /*
+ * A reset that finish() left for unbind is not needed once the device
+ * is opened after finish(): the open resets the device if it can, and
+ * so does the close.
+ */
+ vdev->liveupdate_reset_pending = false;
+
+ if (!vdev->liveupdate_incoming)
+ return;
+
+ vfio_pci_liveupdate_free_incoming(vdev);
+ vdev->liveupdate_claimed = true;
+}
+
+static inline void
+vfio_pci_liveupdate_unregister_dev(struct vfio_pci_core_device *vdev)
+{
+ struct pci_dev *pdev = vdev->pdev;
+
+ scoped_guard(mutex, &vdev->vdev.dev_set->lock) {
+ /*
+ * Complete a reset that finish() could not do, see
+ * vfio_pci_liveupdate_finish_vdev().
+ */
+ if (vdev->liveupdate_reset_pending) {
+ vfio_pci_core_liveupdate_reset_locked(vdev);
+ vdev->liveupdate_reset_pending = false;
+ }
+ vfio_pci_liveupdate_free_incoming(vdev);
+ }
+
+ if (pci_liveupdate_is_incoming(pdev))
+ pci_liveupdate_finish(pdev);
+}
#else
static inline int vfio_pci_liveupdate_init(void)
{
@@ -173,6 +233,21 @@ static inline bool vfio_pci_dev_is_frozen(struct vfio_pci_core_device *vdev)
{
return false;
}
+
+static inline void
+vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
+{
+}
+
+static inline void
+vfio_pci_liveupdate_claim_incoming(struct vfio_pci_core_device *vdev)
+{
+}
+
+static inline void
+vfio_pci_liveupdate_unregister_dev(struct vfio_pci_core_device *vdev)
+{
+}
#endif /* CONFIG_VFIO_PCI_LIVEUPDATE */
#endif
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index 6bae7581f4f4..f5c279d0df1e 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -152,6 +152,9 @@ struct vfio_pci_core_device {
#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
bool liveupdate_frozen;
bool liveupdate_intx_disabled;
+ bool liveupdate_claimed;
+ bool liveupdate_reset_pending;
+ struct vfio_pci_liveupdate_incoming *liveupdate_incoming;
#endif
};
@@ -197,6 +200,9 @@ int vfio_pci_core_match_token_uuid(struct vfio_device *core_vdev,
int vfio_pci_core_enable(struct vfio_pci_core_device *vdev);
void vfio_pci_core_disable(struct vfio_pci_core_device *vdev);
void vfio_pci_core_finish_enable(struct vfio_pci_core_device *vdev);
+bool vfio_pci_core_liveupdate_reset(struct vfio_pci_core_device *vdev,
+ struct pci_saved_state *saved_state,
+ bool reset_works, const char *reason);
pci_ers_result_t vfio_pci_core_aer_err_detected(struct pci_dev *pdev,
pci_channel_state_t state);
ssize_t vfio_pci_core_do_io_rw(struct vfio_pci_core_device *vdev, bool test_mem,
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 12/27] vfio: Enforce preserved devices are retrieved via LIVEUPDATE_SESSION_RETRIEVE_FD
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (10 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 11/27] vfio/pci: Retrieve preserved device files after Live Update Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 13/27] vfio/pci: Protect incoming preserved devices until they finish Live Update Vipin Sharma
` (14 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
From: David Matlack <dmatlack@google.com>
Enforce that files for incoming (preserved by previous kernel) VFIO
devices are retrieved via LIVEUPDATE_SESSION_RETRIEVE_FD rather than by
opening the corresponding VFIO character device or via
VFIO_GROUP_GET_DEVICE_FD.
Both of these methods would result in VFIO initializing the device
without access to the preserved state of the device passed by the
previous kernel.
Reviewed-by: Pranjal Shrivastava <praan@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Co-developed-by: Vipin Sharma <vipinsh@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/device_cdev.c | 8 ++++++++
drivers/vfio/group.c | 9 +++++++++
drivers/vfio/pci/vfio_pci_liveupdate.c | 6 ++++++
drivers/vfio/vfio.h | 18 ++++++++++++++++++
4 files changed, 41 insertions(+)
diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index 38e691eceab5..f0018eae3c4d 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -58,6 +58,14 @@ static int vfio_device_cdev_open(struct vfio_device *device, struct file **filep
}
*filep = file;
+ } else if (vfio_liveupdate_incoming_is_preserved(device)) {
+ /*
+ * Since it is live update preserved device, it must be
+ * retrieved via LIVEUPDATE_SESSION_RETRIEVE_FD instead of
+ * opening /dev/vfio/devices/vfioX.
+ */
+ ret = -EBUSY;
+ goto err_free_device_file;
}
file->private_data = df;
diff --git a/drivers/vfio/group.c b/drivers/vfio/group.c
index b2299e5bc6df..62b4eaabc829 100644
--- a/drivers/vfio/group.c
+++ b/drivers/vfio/group.c
@@ -316,6 +316,15 @@ static int vfio_group_ioctl_get_device_fd(struct vfio_group *group,
if (IS_ERR(device))
return PTR_ERR(device);
+ /*
+ * This device was preserved across a Live Update. Accessing it via
+ * VFIO_GROUP_GET_DEVICE_FD is not allowed.
+ */
+ if (vfio_liveupdate_incoming_is_preserved(device)) {
+ vfio_device_put_registration(device);
+ return -EBUSY;
+ }
+
fd = FD_ADD(O_CLOEXEC, vfio_device_open_file(device));
if (fd < 0)
vfio_device_put_registration(device);
diff --git a/drivers/vfio/pci/vfio_pci_liveupdate.c b/drivers/vfio/pci/vfio_pci_liveupdate.c
index f13c757b4567..64810010f983 100644
--- a/drivers/vfio/pci/vfio_pci_liveupdate.c
+++ b/drivers/vfio/pci/vfio_pci_liveupdate.c
@@ -43,6 +43,12 @@
* ...
* ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, ...);
*
+ * .. note::
+ * After kexec, if a device was preserved by the previous kernel, attempting
+ * to open a new file for the device via its character device
+ * (``/dev/vfio/devices/X``) or via ``VFIO_GROUP_GET_DEVICE_FD`` will fail
+ * with ``-EBUSY``.
+ *
* Restrictions
* ============
*
diff --git a/drivers/vfio/vfio.h b/drivers/vfio/vfio.h
index 12557d45dfee..e6ca4c3fda06 100644
--- a/drivers/vfio/vfio.h
+++ b/drivers/vfio/vfio.h
@@ -11,6 +11,7 @@
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/vfio.h>
+#include <linux/pci.h>
struct iommufd_ctx;
struct iommu_group;
@@ -446,4 +447,21 @@ static inline void vfio_device_debugfs_init(struct vfio_device *vdev) { }
static inline void vfio_device_debugfs_exit(struct vfio_device *vdev) { }
#endif /* CONFIG_VFIO_DEBUGFS */
+#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
+static inline bool vfio_liveupdate_incoming_is_preserved(struct vfio_device *device)
+{
+ struct device *d = device->dev;
+
+ if (dev_is_pci(d))
+ return pci_liveupdate_is_incoming(to_pci_dev(d));
+
+ return false;
+}
+#else
+static inline bool vfio_liveupdate_incoming_is_preserved(struct vfio_device *device)
+{
+ return false;
+}
+#endif /* CONFIG_VFIO_PCI_LIVEUPDATE */
+
#endif
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 13/27] vfio/pci: Protect incoming preserved devices until they finish Live Update
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (11 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 12/27] vfio: Enforce preserved devices are retrieved via LIVEUPDATE_SESSION_RETRIEVE_FD Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 14/27] vfio/pci: Take over preserved devices without resetting them Vipin Sharma
` (13 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Protect incoming preserved devices that were kept running across kexec
from being disturbed before userspace either claims them or finishes the
Live Update session:
* Runtime PM idle D3: In vfio_pci_init_dev(), when
pci_liveupdate_is_incoming(pdev) is true, latch
vdev->disable_idle_d3 = true and vdev->liveupdate_kept_running = true.
Without this, vfio_pci_core_register_device() drops the runtime PM
reference and allows the PCI runtime PM core to transition an idle
incoming device into D3hot, stopping bus mastering and potentially
triggering an internal reset on NoSoftRst- devices.
* Non-noiommu boot fallback: In vfio_pci_core_register_device(), after
vfio_register_group_dev() determines whether the device is in noiommu
mode, if an incoming kept-running device is not in noiommu mode in
this kernel, log a pci_warn(), clear PCI_COMMAND_MASTER immediately to
stop untranslated DMA, and record liveupdate_stopped_early so
finish() resets it without printing a duplicate warning.
* Bus/slot reset guards: Refuse vfio_pci_dev_set_needs_reset() and
vfio_pci_dev_set_hot_reset() (-EBUSY) while any device in the dev_set
has liveupdate_kept_running set, preventing another device in the same
dev_set from resetting an unclaimed kept-running device. When an
unclaimed device finishes Live Update and no device in its dev_set is
open, invoke vfio_pci_dev_set_try_reset() after clearing
liveupdate_kept_running so devices without function-level reset still
receive a bus/slot reset.
* Unbind before finish: In vfio_pci_core_unregister_device(), if a
still-unclaimed incoming device is unbound from vfio-pci before the
session finishes (or if a concurrent finish() deferred its function
reset via liveupdate_reset_pending), clear bus mastering, reset the
function and restore config state via
vfio_pci_core_liveupdate_reset_locked() under the device lock already
held by remove(), and call pci_liveupdate_finish().
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/pci/vfio_pci.c | 1 +
drivers/vfio/pci/vfio_pci_core.c | 19 +++++++-
drivers/vfio/pci/vfio_pci_liveupdate.c | 16 +++++--
drivers/vfio/pci/vfio_pci_priv.h | 62 ++++++++++++++++++++++++--
include/linux/vfio_pci_core.h | 3 +-
5 files changed, 91 insertions(+), 10 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c
index 9d48b250a5e8..d01b5cc9d4d2 100644
--- a/drivers/vfio/pci/vfio_pci.c
+++ b/drivers/vfio/pci/vfio_pci.c
@@ -139,6 +139,7 @@ static int vfio_pci_init_dev(struct vfio_device *core_vdev)
*/
vdev->nointxmask = nointxmask;
vdev->disable_idle_d3 = disable_idle_d3;
+ vfio_pci_liveupdate_init_dev(vdev);
#ifdef CONFIG_VFIO_PCI_VGA
vdev->disable_vga = disable_vga;
#endif
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 60ada3bbb6ff..828d620cf836 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -768,6 +768,15 @@ bool vfio_pci_core_liveupdate_reset(struct vfio_pci_core_device *vdev,
vdev->reset_works = reset_works;
vfio_pci_core_try_reset(vdev);
+ /*
+ * Like vfio_pci_core_disable(), try a bus or slot reset for the devices
+ * in the set that still need one, e.g. this device if it has no usable
+ * function reset, or others whose reset was deferred while this device
+ * was kept running. This device is not open, so none in the set may be.
+ */
+ if (!vfio_device_set_open_count(vdev->vdev.dev_set))
+ vfio_pci_dev_set_try_reset(vdev->vdev.dev_set);
+
return !vdev->needs_reset;
}
EXPORT_SYMBOL_GPL(vfio_pci_core_liveupdate_reset);
@@ -2399,6 +2408,8 @@ int vfio_pci_core_register_device(struct vfio_pci_core_device *vdev)
if (ret)
goto out_power;
+ vfio_pci_liveupdate_check_noiommu(vdev);
+
vfio_pci_core_debugfs_init(vdev);
return 0;
@@ -2642,7 +2653,8 @@ static int vfio_pci_dev_set_hot_reset(struct vfio_device_set *dev_set,
list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list) {
bool owned;
- if (vfio_pci_dev_is_frozen(vdev)) {
+ if (vfio_pci_dev_is_frozen(vdev) ||
+ vfio_pci_dev_is_kept_running(vdev)) {
ret = -EBUSY;
break;
}
@@ -2744,8 +2756,11 @@ static bool vfio_pci_dev_set_needs_reset(struct vfio_device_set *dev_set)
if (vfio_device_set_open_count(dev_set) > 1)
return false;
- list_for_each_entry(cur, &dev_set->device_list, vdev.dev_set_list)
+ list_for_each_entry(cur, &dev_set->device_list, vdev.dev_set_list) {
+ if (vfio_pci_dev_is_kept_running(cur))
+ return false;
needs_reset |= cur->needs_reset;
+ }
return needs_reset;
}
diff --git a/drivers/vfio/pci/vfio_pci_liveupdate.c b/drivers/vfio/pci/vfio_pci_liveupdate.c
index 64810010f983..ad757acac400 100644
--- a/drivers/vfio/pci/vfio_pci_liveupdate.c
+++ b/drivers/vfio/pci/vfio_pci_liveupdate.c
@@ -457,7 +457,7 @@ vfio_pci_liveupdate_attach_incoming(struct vfio_pci_core_device *vdev,
{
guard(mutex)(&vdev->vdev.dev_set->lock);
- if (vdev->liveupdate_incoming || vdev->liveupdate_claimed ||
+ if (vdev->liveupdate_incoming || !vdev->liveupdate_kept_running ||
!pci_liveupdate_is_incoming(vdev->pdev))
return -EBUSY;
@@ -558,8 +558,7 @@ static void vfio_pci_liveupdate_finish_vdev(struct vfio_pci_core_device *vdev,
bool was_reset;
scoped_guard(mutex, &vdev->vdev.dev_set->lock) {
- if (!vdev->liveupdate_claimed &&
- pci_liveupdate_is_incoming(pdev)) {
+ if (vdev->liveupdate_kept_running) {
if (vdev->liveupdate_incoming) {
saved_state = vdev->liveupdate_incoming->saved_state;
vdev->liveupdate_incoming->saved_state = NULL;
@@ -572,6 +571,17 @@ static void vfio_pci_liveupdate_finish_vdev(struct vfio_pci_core_device *vdev,
saved_state = NULL;
}
+ /*
+ * The device is stopped below, so stop shielding it from
+ * the dev_set resets, including the fallback done by
+ * vfio_pci_core_liveupdate_reset().
+ */
+ vdev->liveupdate_kept_running = false;
+
+ /* A device stopped early was logged when it was stopped. */
+ if (vdev->liveupdate_stopped_early)
+ reason = NULL;
+
/*
* An unbind of the device waits for the reference held
* by finish() with the device lock held, so the reset
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index d8670dc51bb4..39ea26402312 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -169,6 +169,40 @@ static inline bool vfio_pci_dev_is_frozen(struct vfio_pci_core_device *vdev)
return smp_load_acquire(&vdev->liveupdate_frozen);
}
+static inline bool vfio_pci_dev_is_kept_running(struct vfio_pci_core_device *vdev)
+{
+ return vdev->liveupdate_kept_running;
+}
+
+static inline void vfio_pci_liveupdate_init_dev(struct vfio_pci_core_device *vdev)
+{
+ struct pci_dev *pdev = to_pci_dev(vdev->vdev.dev);
+
+ if (pci_liveupdate_is_incoming(pdev)) {
+ vdev->disable_idle_d3 = true;
+ vdev->liveupdate_kept_running = true;
+ }
+}
+
+/*
+ * Called by probe once the device is registered, with the device lock held.
+ * retrieve() and finish() look the device up with that lock held, see
+ * vfio_pci_liveupdate_find_probed(), so they see the result of this check.
+ */
+static inline void
+vfio_pci_liveupdate_check_noiommu(struct vfio_pci_core_device *vdev)
+{
+ guard(mutex)(&vdev->vdev.dev_set->lock);
+
+ if (vdev->liveupdate_kept_running &&
+ !vfio_device_is_noiommu(&vdev->vdev)) {
+ pci_warn(vdev->pdev,
+ "Stopping device kept running across Live Update: not in noiommu mode\n");
+ pci_clear_master(vdev->pdev);
+ vdev->liveupdate_stopped_early = true;
+ }
+}
+
static inline void
vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
{
@@ -196,7 +230,7 @@ vfio_pci_liveupdate_claim_incoming(struct vfio_pci_core_device *vdev)
return;
vfio_pci_liveupdate_free_incoming(vdev);
- vdev->liveupdate_claimed = true;
+ vdev->liveupdate_kept_running = false;
}
static inline void
@@ -206,11 +240,17 @@ vfio_pci_liveupdate_unregister_dev(struct vfio_pci_core_device *vdev)
scoped_guard(mutex, &vdev->vdev.dev_set->lock) {
/*
- * Complete a reset that finish() could not do, see
- * vfio_pci_liveupdate_finish_vdev().
+ * Reset a device unbound before finish(), or whose reset
+ * finish() could not do, see vfio_pci_liveupdate_finish_vdev().
*/
- if (vdev->liveupdate_reset_pending) {
+ if (vdev->liveupdate_kept_running ||
+ vdev->liveupdate_reset_pending) {
+ if (vdev->liveupdate_kept_running &&
+ !vdev->liveupdate_stopped_early)
+ pci_warn(pdev,
+ "Resetting device not taken over after Live Update (vfio-pci unbound)\n");
vfio_pci_core_liveupdate_reset_locked(vdev);
+ vdev->liveupdate_kept_running = false;
vdev->liveupdate_reset_pending = false;
}
vfio_pci_liveupdate_free_incoming(vdev);
@@ -234,6 +274,20 @@ static inline bool vfio_pci_dev_is_frozen(struct vfio_pci_core_device *vdev)
return false;
}
+static inline bool vfio_pci_dev_is_kept_running(struct vfio_pci_core_device *vdev)
+{
+ return false;
+}
+
+static inline void vfio_pci_liveupdate_init_dev(struct vfio_pci_core_device *vdev)
+{
+}
+
+static inline void
+vfio_pci_liveupdate_check_noiommu(struct vfio_pci_core_device *vdev)
+{
+}
+
static inline void
vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
{
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index f5c279d0df1e..9bdbc8099bda 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -152,7 +152,8 @@ struct vfio_pci_core_device {
#ifdef CONFIG_VFIO_PCI_LIVEUPDATE
bool liveupdate_frozen;
bool liveupdate_intx_disabled;
- bool liveupdate_claimed;
+ bool liveupdate_kept_running;
+ bool liveupdate_stopped_early;
bool liveupdate_reset_pending;
struct vfio_pci_liveupdate_incoming *liveupdate_incoming;
#endif
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 14/27] vfio/pci: Take over preserved devices without resetting them
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (12 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 13/27] vfio/pci: Protect incoming preserved devices until they finish Live Update Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 15/27] docs: liveupdate: Add documentation for VFIO PCI Vipin Sharma
` (12 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
When userspace binds a retrieved VFIO cdev file descriptor via
VFIO_DEVICE_BIND_IOMMUFD after kexec, take over the running device in
vfio_pci_core_enable() via vfio_pci_liveupdate_take_over() without
resetting it or clearing bus mastering:
- Skip pci_clear_master(), pci_try_reset_function(), and
pci_save_state().
- Save PCI_COMMAND before pci_enable_device() and restore the memory and
I/O decode bits afterwards so pci_enable_device() does not re-enable
BAR decode (such as I/O port decode) that was disabled before kexec.
- Also restore PCI_COMMAND_INTX_DISABLE, which freeze() set before
kexec, when vfio-pci uses PCI 2.3 INTx masking for the device
(vdev->pci_2_3) or masks its broken INTx (vdev->nointx), so INTx stays
masked until userspace configures interrupts. Otherwise vfio-pci masks
INTx at the interrupt controller (e.g. with vfio_pci.nointxmask=1), so
leave DisINTx cleared by pci_enable_device() as a normal open does.
- Build pci_config_map via vfio_config_init(), verify that it matches
the preserved config map snapshot, and overlay the preserved vconfig
snapshot onto vdev->vconfig.
- Perform all fallible take-over setup before adopting the preserved
state. If take-over fails before the device is claimed, leave the
running device untouched (keeping bus mastering, decode bits, and
liveupdate_incoming intact) so userspace can retry binding the
retrieved FD or let the session finish/close reset the unclaimed
device.
- Once vfio_pci_liveupdate_take_over() succeeds, adopt the preserved
pci_saved_state and reset_works flag, clear liveupdate_kept_running,
and free the temporary incoming state; any subsequent error or final
device close follows the normal vfio_pci_core_disable() path and
resets the device.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
drivers/vfio/pci/vfio_pci_core.c | 117 +++++++++++++++++++++----
drivers/vfio/pci/vfio_pci_liveupdate.c | 23 ++++-
drivers/vfio/pci/vfio_pci_priv.h | 23 ++++-
3 files changed, 141 insertions(+), 22 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 828d620cf836..63f484c0654c 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -595,8 +595,68 @@ static const struct dev_pm_ops vfio_pci_core_pm_ops = {
NULL)
};
+/*
+ * Take over a device that was kept running across a Live Update. The device
+ * is enabled only after every step that can fail, because unwinding with
+ * pci_disable_device() would clear bus mastering on the running device. A
+ * failed take-over thus leaves the device untouched and can be retried.
+ */
+static int vfio_pci_liveupdate_take_over(struct vfio_pci_core_device *vdev,
+ struct vfio_pci_liveupdate_incoming *in)
+{
+ const u8 *vconfig = in->data;
+ const u8 *config_map = in->data + in->cfg_size;
+ struct pci_dev *pdev = vdev->pdev;
+ u16 orig, cmd, keep;
+ int ret;
+
+ if (in->cfg_size != pdev->cfg_size ||
+ memcmp(config_map, vdev->pci_config_map, in->cfg_size)) {
+ pci_warn(pdev,
+ "Config map mismatch during Live Update take-over\n");
+ return -EINVAL;
+ }
+
+ pci_read_config_word(pdev, PCI_COMMAND, &orig);
+ ret = pci_enable_device(pdev);
+
+ /*
+ * pci_enable_device() enables memory and I/O decode for all assigned
+ * BARs and clears PCI_COMMAND_INTX_DISABLE. Restore the decode settings
+ * the device was running with and keep INTx masked, as freeze() left
+ * it, until userspace configures interrupts. If vfio-pci does not use
+ * PCI 2.3 INTx masking for the device, it masks INTx at the interrupt
+ * controller instead, so leave PCI_COMMAND_INTX_DISABLE as a normal
+ * open does. If enabling failed, restore everything.
+ */
+ keep = PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
+ if (ret || vdev->pci_2_3 || vdev->nointx)
+ keep |= PCI_COMMAND_INTX_DISABLE;
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ if ((cmd ^ orig) & keep)
+ pci_write_config_word(pdev, PCI_COMMAND,
+ (cmd & ~keep) | (orig & keep));
+ if (ret)
+ return ret;
+
+ memcpy(vdev->vconfig, vconfig, in->cfg_size);
+ /*
+ * Redo the virtual BAR fixup: the snapshot may hold a BAR sizing write
+ * not fixed up yet, or a fixup half done by a config read racing with
+ * freeze(), as config reads do not take vdev->igate.
+ */
+ vdev->bardirty = true;
+ cmd = le16_to_cpu(*(__le16 *)&vdev->vconfig[PCI_COMMAND]);
+ vdev->virq_disabled = !!(cmd & PCI_COMMAND_INTX_DISABLE);
+ vdev->reset_works = in->reset_works;
+
+ return 0;
+}
+
int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
{
+ struct vfio_pci_liveupdate_incoming *in =
+ vfio_pci_liveupdate_incoming(vdev);
struct pci_dev *pdev = vdev->pdev;
int ret;
u16 cmd;
@@ -608,23 +668,30 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
return ret;
}
- /* Don't allow our initial saved state to include busmaster */
- pci_clear_master(pdev);
+ /*
+ * A device kept running across a Live Update is enabled later, without
+ * a reset, by vfio_pci_liveupdate_take_over().
+ */
+ if (!in) {
+ /* Don't allow our initial saved state to include busmaster */
+ pci_clear_master(pdev);
- ret = pci_enable_device(pdev);
- if (ret)
- goto out_power;
+ ret = pci_enable_device(pdev);
+ if (ret)
+ goto out_power;
- /* If reset fails because of the device lock, fail this path entirely */
- ret = pci_try_reset_function(pdev);
- if (ret == -EAGAIN)
- goto out_disable_device;
+ /* If reset fails because of the device lock, fail this path entirely */
+ ret = pci_try_reset_function(pdev);
+ if (ret == -EAGAIN)
+ goto out_disable_device;
- vdev->reset_works = !ret;
- pci_save_state(pdev);
- vdev->pci_saved_state = pci_store_saved_state(pdev);
- if (!vdev->pci_saved_state)
- pci_dbg(pdev, "%s: Couldn't store saved state\n", __func__);
+ vdev->reset_works = !ret;
+ pci_save_state(pdev);
+ vdev->pci_saved_state = pci_store_saved_state(pdev);
+ if (!vdev->pci_saved_state)
+ pci_dbg(pdev, "%s: Couldn't store saved state\n",
+ __func__);
+ }
if (likely(!vdev->nointxmask)) {
if (vfio_pci_nointx(pdev)) {
@@ -635,10 +702,12 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
vdev->pci_2_3 = pci_intx_mask_supported(pdev);
}
- pci_read_config_word(pdev, PCI_COMMAND, &cmd);
- if (vdev->pci_2_3 && (cmd & PCI_COMMAND_INTX_DISABLE)) {
- cmd &= ~PCI_COMMAND_INTX_DISABLE;
- pci_write_config_word(pdev, PCI_COMMAND, cmd);
+ if (!in) {
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ if (vdev->pci_2_3 && (cmd & PCI_COMMAND_INTX_DISABLE)) {
+ cmd &= ~PCI_COMMAND_INTX_DISABLE;
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+ }
}
ret = vfio_pci_zdev_open_device(vdev);
@@ -649,6 +718,12 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
if (ret)
goto out_free_zdev;
+ if (in) {
+ ret = vfio_pci_liveupdate_take_over(vdev, in);
+ if (ret)
+ goto out_free_config;
+ }
+
msix_pos = pdev->msix_cap;
if (msix_pos) {
u16 flags;
@@ -675,13 +750,17 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
return 0;
+out_free_config:
+ vfio_config_free(vdev);
out_free_zdev:
vfio_pci_zdev_close_device(vdev);
out_free_state:
kfree(vdev->pci_saved_state);
vdev->pci_saved_state = NULL;
out_disable_device:
- pci_disable_device(pdev);
+ /* A take-over enables the device only once nothing can fail. */
+ if (!in)
+ pci_disable_device(pdev);
out_power:
if (!vdev->disable_idle_d3)
pm_runtime_put(&pdev->dev);
diff --git a/drivers/vfio/pci/vfio_pci_liveupdate.c b/drivers/vfio/pci/vfio_pci_liveupdate.c
index ad757acac400..00c008117259 100644
--- a/drivers/vfio/pci/vfio_pci_liveupdate.c
+++ b/drivers/vfio/pci/vfio_pci_liveupdate.c
@@ -120,7 +120,28 @@
*
* ``LIVEUPDATE_SESSION_RETRIEVE_FD`` validates the handed-over serialized state
* and returns a new unbound VFIO cdev file descriptor. The device is claimed on
- * the first ``VFIO_DEVICE_BIND_IOMMUFD`` on that file descriptor.
+ * the first ``VFIO_DEVICE_BIND_IOMMUFD`` on that file descriptor without
+ * resetting it or clearing ``PCI_COMMAND_MASTER``:
+ *
+ * * ``vfio_pci_core_enable()`` skips ``pci_clear_master()``,
+ * ``pci_try_reset_function()``, and ``pci_save_state()``. It builds
+ * ``pci_config_map`` normally via ``vfio_config_init()`` and verifies that
+ * it matches the preserved snapshot before it enables the device. It keeps
+ * the memory and I/O decode settings and ``PCI_COMMAND_MASTER`` the device
+ * was running with, and keeps physical INTx masked until userspace
+ * configures interrupts. On devices for which vfio-pci does not use PCI
+ * 2.3 INTx masking (e.g. with ``vfio_pci.nointxmask=1``), vfio-pci masks
+ * INTx at the interrupt controller, so ``PCI_COMMAND_INTX_DISABLE`` is left
+ * as on a normal open. It then overlays the preserved ``vconfig`` snapshot
+ * and adopts the preserved ``pci_saved_state`` and ``reset_works`` flag for
+ * subsequent resets or device close.
+ *
+ * * If the take-over in ``vfio_pci_core_enable()`` fails,
+ * ``VFIO_DEVICE_BIND_IOMMUFD`` fails and leaves the device running
+ * untouched, and userspace can retry the bind. If the ioctl fails after
+ * the device was claimed, it closes the device, which resets it like any
+ * other close. A device that is never claimed is reset when the session
+ * is finished or closed, as described below.
*
* ``LIVEUPDATE_SESSION_FINISH`` (or closing a retrieved session file
* descriptor) always completes:
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index 39ea26402312..2549a5321fce 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -203,6 +203,12 @@ vfio_pci_liveupdate_check_noiommu(struct vfio_pci_core_device *vdev)
}
}
+static inline struct vfio_pci_liveupdate_incoming *
+vfio_pci_liveupdate_incoming(struct vfio_pci_core_device *vdev)
+{
+ return vdev->liveupdate_incoming;
+}
+
static inline void
vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
{
@@ -219,6 +225,8 @@ vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
static inline void
vfio_pci_liveupdate_claim_incoming(struct vfio_pci_core_device *vdev)
{
+ struct vfio_pci_liveupdate_incoming *in = vdev->liveupdate_incoming;
+
/*
* A reset that finish() left for unbind is not needed once the device
* is opened after finish(): the open resets the device if it can, and
@@ -226,10 +234,15 @@ vfio_pci_liveupdate_claim_incoming(struct vfio_pci_core_device *vdev)
*/
vdev->liveupdate_reset_pending = false;
- if (!vdev->liveupdate_incoming)
+ if (!in)
return;
- vfio_pci_liveupdate_free_incoming(vdev);
+ vdev->pci_saved_state = in->saved_state;
+ if (!vdev->pci_saved_state)
+ pci_dbg(vdev->pdev, "%s: Couldn't store saved state\n",
+ __func__);
+ kfree(in);
+ vdev->liveupdate_incoming = NULL;
vdev->liveupdate_kept_running = false;
}
@@ -288,6 +301,12 @@ vfio_pci_liveupdate_check_noiommu(struct vfio_pci_core_device *vdev)
{
}
+static inline struct vfio_pci_liveupdate_incoming *
+vfio_pci_liveupdate_incoming(struct vfio_pci_core_device *vdev)
+{
+ return NULL;
+}
+
static inline void
vfio_pci_liveupdate_free_incoming(struct vfio_pci_core_device *vdev)
{
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 15/27] docs: liveupdate: Add documentation for VFIO PCI
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (13 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 14/27] vfio/pci: Take over preserved devices without resetting them Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 16/27] vfio: selftests: Build liveupdate library in VFIO selftests Vipin Sharma
` (11 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
From: David Matlack <dmatlack@google.com>
Add documentation for preserving VFIO device files across a Live Update,
generated from the kernel-doc comments in the code.
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
Documentation/core-api/liveupdate.rst | 1 +
.../driver-api/vfio_pci_liveupdate.rst | 25 +++++++++++++++++++
MAINTAINERS | 1 +
3 files changed, 27 insertions(+)
create mode 100644 Documentation/driver-api/vfio_pci_liveupdate.rst
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 2bce2644eba2..20f1d2e20508 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -38,6 +38,7 @@ The following types of file descriptors can be preserved
:maxdepth: 1
../mm/memfd_preservation
+ ../driver-api/vfio_pci_liveupdate
Public API
==========
diff --git a/Documentation/driver-api/vfio_pci_liveupdate.rst b/Documentation/driver-api/vfio_pci_liveupdate.rst
new file mode 100644
index 000000000000..81077ea173d1
--- /dev/null
+++ b/Documentation/driver-api/vfio_pci_liveupdate.rst
@@ -0,0 +1,25 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+====================================
+VFIO PCI Device Preservation via LUO
+====================================
+
+.. kernel-doc:: drivers/vfio/pci/vfio_pci_liveupdate.c
+ :doc: VFIO PCI Preservation via LUO
+
+VFIO PCI Preservation ABI
+=========================
+
+.. kernel-doc:: include/linux/kho/abi/vfio_pci.h
+ :doc: VFIO PCI Live Update ABI
+
+.. kernel-doc:: include/linux/kho/abi/vfio_pci.h
+ :internal:
+
+See Also
+========
+
+- :doc:`/core-api/liveupdate`
+- :doc:`/core-api/kho/index`
+- :doc:`/PCI/liveupdate`
+- :doc:`/driver-api/vfio`
diff --git a/MAINTAINERS b/MAINTAINERS
index 167aa9d66ce8..2b62a0b8a48c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28661,6 +28661,7 @@ T: git https://github.com/awilliam/linux-vfio.git
F: Documentation/ABI/testing/debugfs-vfio
F: Documentation/ABI/testing/sysfs-devices-vfio-dev
F: Documentation/driver-api/vfio.rst
+F: Documentation/driver-api/vfio_pci_liveupdate.rst
F: drivers/vfio/
F: include/linux/kho/abi/vfio_pci.h
F: include/linux/vfio.h
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 16/27] vfio: selftests: Build liveupdate library in VFIO selftests
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (14 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 15/27] docs: liveupdate: Add documentation for VFIO PCI Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 17/27] vfio: selftests: Add vfio_pci_liveupdate_uapi_test Vipin Sharma
` (10 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Import and build liveupdate selftest library in VFIO selftests.
It allows using liveupdate ioctls in VFIO selftests
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
tools/testing/selftests/vfio/Makefile | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index 2c32c48db509..55e1e1843090 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -21,6 +21,7 @@ TEST_FILES += scripts/setup.sh
include ../lib.mk
include lib/libvfio.mk
+include ../liveupdate/lib/libliveupdate.mk
CFLAGS += -I$(top_srcdir)/tools/include
CFLAGS += -MD
@@ -29,14 +30,17 @@ CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += -pthread
-$(TEST_GEN_PROGS): $(OUTPUT)/%: $(OUTPUT)/%.o $(LIBVFIO_O)
- $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(LIBVFIO_O) $(LDLIBS) -o $@
+LIBS_O := $(LIBVFIO_O)
+LIBS_O += $(LIBLIVEUPDATE_O)
+
+$(TEST_GEN_PROGS): $(OUTPUT)/%: $(OUTPUT)/%.o $(LIBS_O)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(LIBS_O) $(LDLIBS) -o $@
TEST_GEN_PROGS_O = $(patsubst %, %.o, $(TEST_GEN_PROGS))
$(TEST_GEN_PROGS_O): $(OUTPUT)/%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
-TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_PROGS_O) $(LIBVFIO_O))
+TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_PROGS_O) $(LIBS_O))
-include $(TEST_DEP_FILES)
EXTRA_CLEAN += $(TEST_GEN_PROGS_O) $(TEST_DEP_FILES)
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 17/27] vfio: selftests: Add vfio_pci_liveupdate_uapi_test
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (15 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 16/27] vfio: selftests: Build liveupdate library in VFIO selftests Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 18/27] vfio: selftests: Initialize vfio_pci_device using a VFIO cdev FD Vipin Sharma
` (9 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
From: David Matlack <dmatlack@google.com>
Add a selftest to exercise preserving various VFIO files through
/dev/liveupdate. Only the VFIO cdev files of devices in noiommu mode can
be preserved. Ensure that preserving the cdev file of a device that is
not in noiommu mode fails, and that everything else (group-based device
files, group files, and container files) fails too.
In FIXTURE_SETUP, probe luo_session_preserve_fd() on the bound cdev FD
in a temporary session for iommufd variants and skip if LUO returns
-ENOENT, so the test skips cleanly when CONFIG_VFIO_PCI_LIVEUPDATE=n or
when the target device does not support VFIO PCI Live Update (e.g. an
SR-IOV VF).
Signed-off-by: David Matlack <dmatlack@google.com>
Co-developed-by: Vipin Sharma <vipinsh@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
tools/testing/selftests/vfio/Makefile | 1 +
.../vfio/vfio_pci_liveupdate_uapi_test.c | 126 ++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index 55e1e1843090..c2764fcef31d 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -13,6 +13,7 @@ TEST_GEN_PROGS += vfio_pci_device_test
TEST_GEN_PROGS += vfio_pci_device_init_perf_test
TEST_GEN_PROGS += vfio_pci_driver_test
TEST_GEN_PROGS += vfio_pci_sriov_uapi_test
+TEST_GEN_PROGS += vfio_pci_liveupdate_uapi_test
TEST_FILES += scripts/cleanup.sh
TEST_FILES += scripts/lib.sh
diff --git a/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c b/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c
new file mode 100644
index 000000000000..da25ea4e2b1e
--- /dev/null
+++ b/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <libliveupdate.h>
+#include <libvfio.h>
+#include <kselftest_harness.h>
+
+static const char *device_bdf;
+
+FIXTURE(vfio_pci_liveupdate_uapi_test) {
+ int luo_fd;
+ int session_fd;
+ struct iommu *iommu;
+ struct vfio_pci_device *device;
+};
+
+/*
+ * LUO fails with -ENOENT if no file handler accepts a file. For the bound VFIO
+ * cdev file of a device, that means that the kernel lacks VFIO PCI Live Update
+ * support (CONFIG_VFIO_PCI_LIVEUPDATE=n) or that vfio-pci does not support
+ * preserving the device (e.g. an SR-IOV VF).
+ */
+static bool device_preserve_supported(int luo_fd, int device_fd)
+{
+ int session_fd;
+ int ret;
+
+ session_fd = luo_create_session(luo_fd, "probe");
+ VFIO_ASSERT_GE(session_fd, 0);
+
+ ret = luo_session_preserve_fd(session_fd, device_fd, 0);
+
+ /* Closing the session unpreserves the device. */
+ VFIO_ASSERT_EQ(close(session_fd), 0);
+
+ return ret != -ENOENT;
+}
+
+FIXTURE_VARIANT(vfio_pci_liveupdate_uapi_test) {
+ const char *iommu_mode;
+};
+
+#define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode) \
+FIXTURE_VARIANT_ADD(vfio_pci_liveupdate_uapi_test, _iommu_mode) { \
+ .iommu_mode = #_iommu_mode, \
+}
+
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES();
+#undef FIXTURE_VARIANT_ADD_IOMMU_MODE
+
+FIXTURE_SETUP(vfio_pci_liveupdate_uapi_test)
+{
+ self->luo_fd = luo_open_device();
+ ASSERT_GT(self->luo_fd, 0);
+
+ self->session_fd = luo_create_session(self->luo_fd, "session");
+ ASSERT_GT(self->session_fd, 0);
+
+ self->iommu = iommu_init(variant->iommu_mode);
+ self->device = vfio_pci_device_init(device_bdf, self->iommu);
+
+ if (self->iommu->iommufd &&
+ !device_preserve_supported(self->luo_fd, self->device->fd))
+ SKIP(return, "Live Update of %s is not supported", device_bdf);
+}
+
+FIXTURE_TEARDOWN(vfio_pci_liveupdate_uapi_test)
+{
+ if (self->device)
+ vfio_pci_device_cleanup(self->device);
+ if (self->iommu)
+ iommu_cleanup(self->iommu);
+ if (self->session_fd > 0)
+ close(self->session_fd);
+ if (self->luo_fd > 0)
+ close(self->luo_fd);
+}
+
+TEST_F(vfio_pci_liveupdate_uapi_test, preserve_device)
+{
+ int ret;
+
+ ret = luo_session_preserve_fd(self->session_fd, self->device->fd, 0);
+
+ /*
+ * Only VFIO cdev files of devices in noiommu mode can be preserved;
+ * device files obtained through a VFIO group never can.
+ */
+ ASSERT_EQ(ret, self->iommu->iommufd ? -EOPNOTSUPP : -ENOENT);
+}
+
+TEST_F(vfio_pci_liveupdate_uapi_test, preserve_group_fails)
+{
+ int ret;
+
+ if (self->iommu->iommufd)
+ SKIP(return, "iommufd-mode does not have group files");
+
+ ret = luo_session_preserve_fd(self->session_fd, self->device->group_fd, 0);
+ ASSERT_EQ(ret, -ENOENT);
+}
+
+TEST_F(vfio_pci_liveupdate_uapi_test, preserve_container_fails)
+{
+ int ret;
+
+ if (self->iommu->iommufd)
+ SKIP(return, "iommufd-mode does not have container files");
+
+ ret = luo_session_preserve_fd(self->session_fd, self->iommu->container_fd, 0);
+ ASSERT_EQ(ret, -ENOENT);
+}
+
+int main(int argc, char *argv[])
+{
+ int fd;
+
+ fd = luo_open_device();
+ if (fd < 0)
+ ksft_exit_skip("open(%s) failed: %s, skipping\n",
+ LUO_DEVICE, strerror(errno));
+
+ close(fd);
+
+ device_bdf = vfio_selftests_get_bdf(&argc, argv);
+ return test_harness_run(argc, argv);
+}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 18/27] vfio: selftests: Initialize vfio_pci_device using a VFIO cdev FD
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (16 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 17/27] vfio: selftests: Add vfio_pci_liveupdate_uapi_test Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 19/27] vfio: selftests: Add Makefile support for TEST_GEN_PROGS_EXTENDED Vipin Sharma
` (8 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Use the given VFIO cdev FD to initialize vfio_pci_device in VFIO
selftests. Add the assertion to make sure that passed cdev FD is not
used with legacy VFIO APIs. If VFIO cdev FD is provided then do not open
the device instead use the FD for any interaction with the device.
This API will allow writing selftests where VFIO device FD is preserved
using liveupdate and retrieved later using liveupdate ioctl after kexec.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../lib/include/libvfio/vfio_pci_device.h | 3 +++
.../selftests/vfio/lib/vfio_pci_device.c | 25 ++++++++++++++-----
2 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
index e19bd94b8dd2..9a62bfb6b440 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
@@ -40,6 +40,9 @@ struct vfio_pci_device {
struct vfio_pci_device *vfio_pci_device_alloc(const char *bdf, struct iommu *iommu);
void vfio_pci_device_free(struct vfio_pci_device *device);
+struct vfio_pci_device *__vfio_pci_device_init(const char *bdf,
+ struct iommu *iommu,
+ int device_fd);
struct vfio_pci_device *vfio_pci_device_init(const char *bdf, struct iommu *iommu);
void vfio_pci_device_cleanup(struct vfio_pci_device *device);
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_device.c b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
index 4063a0e2b3df..af789c859586 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_device.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
@@ -457,9 +457,13 @@ void vfio_pci_cdev_open(struct vfio_pci_device *device, const char *bdf)
}
static void vfio_pci_iommufd_setup(struct vfio_pci_device *device,
- const char *bdf, const char *vf_token)
+ const char *bdf, int device_fd,
+ const char *vf_token)
{
- vfio_pci_cdev_open(device, bdf);
+ if (device_fd >= 0)
+ device->fd = device_fd;
+ else
+ vfio_pci_cdev_open(device, bdf);
vfio_device_bind_iommufd(device->fd, device->iommu->iommufd, vf_token);
vfio_device_attach_iommufd_pt(device->fd, device->iommu->ioas_id);
}
@@ -482,16 +486,20 @@ void vfio_pci_device_free(struct vfio_pci_device *device)
free(device);
}
-struct vfio_pci_device *vfio_pci_device_init(const char *bdf, struct iommu *iommu)
+struct vfio_pci_device *__vfio_pci_device_init(const char *bdf,
+ struct iommu *iommu,
+ int device_fd)
{
struct vfio_pci_device *device;
device = vfio_pci_device_alloc(bdf, iommu);
- if (iommu->mode->container_path)
+ if (iommu->mode->container_path) {
+ VFIO_ASSERT_EQ(device_fd, -1);
vfio_pci_container_setup(device, bdf, NULL);
- else
- vfio_pci_iommufd_setup(device, bdf, NULL);
+ } else {
+ vfio_pci_iommufd_setup(device, bdf, device_fd, NULL);
+ }
vfio_pci_device_setup(device);
vfio_pci_driver_probe(device);
@@ -499,6 +507,11 @@ struct vfio_pci_device *vfio_pci_device_init(const char *bdf, struct iommu *iomm
return device;
}
+struct vfio_pci_device *vfio_pci_device_init(const char *bdf, struct iommu *iommu)
+{
+ return __vfio_pci_device_init(bdf, iommu, /*device_fd=*/-1);
+}
+
void vfio_pci_device_cleanup(struct vfio_pci_device *device)
{
int i;
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 19/27] vfio: selftests: Add Makefile support for TEST_GEN_PROGS_EXTENDED
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (17 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 18/27] vfio: selftests: Initialize vfio_pci_device using a VFIO cdev FD Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 20/27] vfio: selftests: Add iommufd noiommu mode Vipin Sharma
` (7 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
From: David Matlack <dmatlack@google.com>
Add Makefile support for TEST_GEN_PROGS_EXTENDED targets. These tests
are not run by default.
TEST_GEN_PROGS_EXTENDED will be used for Live Update selftests in
subsequent commits. These selftests must be run manually because they
require the user/runner to perform additional actions, such as kexec,
during the test.
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
tools/testing/selftests/vfio/Makefile | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index c2764fcef31d..ece2a3618f2b 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -34,16 +34,18 @@ LDFLAGS += -pthread
LIBS_O := $(LIBVFIO_O)
LIBS_O += $(LIBLIVEUPDATE_O)
-$(TEST_GEN_PROGS): $(OUTPUT)/%: $(OUTPUT)/%.o $(LIBS_O)
+$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: $(OUTPUT)/%.o $(LIBS_O)
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(LIBS_O) $(LDLIBS) -o $@
-TEST_GEN_PROGS_O = $(patsubst %, %.o, $(TEST_GEN_PROGS))
-$(TEST_GEN_PROGS_O): $(OUTPUT)/%.o: %.c
+TESTS_O := $(patsubst %, %.o, $(TEST_GEN_PROGS))
+TESTS_O += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED))
+
+$(TESTS_O): $(OUTPUT)/%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
-TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_PROGS_O) $(LIBS_O))
+TEST_DEP_FILES = $(patsubst %.o, %.d, $(TESTS_O) $(LIBS_O))
-include $(TEST_DEP_FILES)
-EXTRA_CLEAN += $(TEST_GEN_PROGS_O) $(TEST_DEP_FILES)
+EXTRA_CLEAN += $(TESTS_O) $(TEST_DEP_FILES)
endif
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 20/27] vfio: selftests: Add iommufd noiommu mode
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (18 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 19/27] vfio: selftests: Add Makefile support for TEST_GEN_PROGS_EXTENDED Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 21/27] vfio: selftests: Add helpers to map DMA memory at IOVA == PA in " Vipin Sharma
` (6 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Add an opt-in iommufd_noiommu mode (MODE_IOMMUFD_NOIOMMU) for devices
bound to vfio-pci in noiommu mode. When a device is in noiommu mode, its
VFIO character device is named /dev/vfio/devices/noiommu-vfioX (opening
it requires CAP_SYS_RAWIO), and binding and attaching to an iommufd IOAS
use a software page table.
Add __vfio_pci_get_cdev_path(bdf, noiommu) to look up either the regular
or the noiommu cdev path and return NULL when none is present, and make
vfio_pci_cdev_open() select the cdev based on mode->noiommu.
Because a device in noiommu mode DMAs directly to physical addresses,
tests that program arbitrary or unmapped IOVAs would corrupt memory.
Keep MODE_IOMMUFD_NOIOMMU out of FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES()
so that only noiommu-aware tests opt into it.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../vfio/lib/include/libvfio/iommu.h | 17 ++++++++++
.../lib/include/libvfio/vfio_pci_device.h | 7 +++++
tools/testing/selftests/vfio/lib/iommu.c | 9 +++++-
.../selftests/vfio/lib/vfio_pci_device.c | 31 +++++++++++++------
4 files changed, 53 insertions(+), 11 deletions(-)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
index e9a3386a4719..e652b8caf5bb 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
@@ -13,6 +13,12 @@ struct iommu_mode {
const char *name;
const char *container_path;
unsigned long iommu_type;
+ /*
+ * For devices bound to vfio-pci in noiommu mode, i.e. not behind an
+ * IOMMU. Such devices DMA to the addresses programmed into them as
+ * physical addresses.
+ */
+ bool noiommu;
};
extern const char *default_iommu_mode;
@@ -67,6 +73,17 @@ struct iommu_iova_range *iommu_iova_ranges(struct iommu *iommu, u32 *nranges);
#define MODE_IOMMUFD_COMPAT_TYPE1V2 "iommufd_compat_type1v2"
#define MODE_IOMMUFD "iommufd"
+/*
+ * iommufd with a device bound to vfio-pci in noiommu mode
+ * (vfio.enable_unsafe_noiommu_mode=1 and CONFIG_IOMMUFD_NOIOMMU), whose cdev
+ * is /dev/vfio/devices/noiommu-vfioX. Opening it requires CAP_SYS_RAWIO.
+ *
+ * Not part of FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(): tests must opt in, since
+ * the device DMAs to IOVAs as physical addresses, and tests that DMA to
+ * arbitrary or unmapped IOVAs would corrupt memory.
+ */
+#define MODE_IOMMUFD_NOIOMMU "iommufd_noiommu"
+
/*
* Generator for VFIO selftests fixture variants that replicate across all
* possible IOMMU modes. Tests must define FIXTURE_VARIANT_ADD_IOMMU_MODE()
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
index 9a62bfb6b440..bfadeb362723 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
@@ -154,6 +154,13 @@ static inline bool vfio_pci_device_match(struct vfio_pci_device *device,
(device_id == vfio_pci_config_readw(device, PCI_DEVICE_ID));
}
+/*
+ * Return the path of the VFIO cdev of @bdf, or NULL if there is none. The cdev
+ * of a device bound to vfio-pci in noiommu mode is named noiommu-vfioX rather
+ * than vfioX, and @noiommu selects which of the two to look for. Free with
+ * free().
+ */
+const char *__vfio_pci_get_cdev_path(const char *bdf, bool noiommu);
const char *vfio_pci_get_cdev_path(const char *bdf);
void vfio_pci_group_setup(struct vfio_pci_device *device, const char *bdf);
diff --git a/tools/testing/selftests/vfio/lib/iommu.c b/tools/testing/selftests/vfio/lib/iommu.c
index b6f3c5c84e01..0df49646d016 100644
--- a/tools/testing/selftests/vfio/lib/iommu.c
+++ b/tools/testing/selftests/vfio/lib/iommu.c
@@ -22,7 +22,10 @@
const char *default_iommu_mode = MODE_IOMMUFD;
-/* Reminder: Keep in sync with FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(). */
+/*
+ * Reminder: Keep in sync with FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(), which
+ * deliberately leaves out the noiommu modes.
+ */
static const struct iommu_mode iommu_modes[] = {
{
.name = MODE_VFIO_TYPE1_IOMMU,
@@ -47,6 +50,10 @@ static const struct iommu_mode iommu_modes[] = {
{
.name = MODE_IOMMUFD,
},
+ {
+ .name = MODE_IOMMUFD_NOIOMMU,
+ .noiommu = true,
+ },
};
static const struct iommu_mode *lookup_iommu_mode(const char *iommu_mode)
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_device.c b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
index af789c859586..b0deef2b0e3f 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_device.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
@@ -380,35 +380,42 @@ static void vfio_pci_device_setup(struct vfio_pci_device *device)
device->msi_eventfds[i] = -1;
}
-const char *vfio_pci_get_cdev_path(const char *bdf)
+const char *__vfio_pci_get_cdev_path(const char *bdf, bool noiommu)
{
+ const char *prefix = noiommu ? "noiommu-vfio" : "vfio";
char dir_path[PATH_MAX];
struct dirent *entry;
- char *cdev_path;
+ char *cdev_path = NULL;
DIR *dir;
- cdev_path = calloc_assert(PATH_MAX, 1);
-
snprintf_assert(dir_path, sizeof(dir_path), "/sys/bus/pci/devices/%s/vfio-dev/", bdf);
dir = opendir(dir_path);
- VFIO_ASSERT_NOT_NULL(dir, "Failed to open directory %s\n", dir_path);
+ if (!dir)
+ return NULL;
while ((entry = readdir(dir)) != NULL) {
- /* Find the file that starts with "vfio" */
- if (strncmp("vfio", entry->d_name, 4))
+ if (strncmp(prefix, entry->d_name, strlen(prefix)))
continue;
+ cdev_path = calloc_assert(PATH_MAX, 1);
snprintf_assert(cdev_path, PATH_MAX, "/dev/vfio/devices/%s", entry->d_name);
break;
}
- VFIO_ASSERT_NE(cdev_path[0], 0, "Failed to find vfio cdev file.\n");
VFIO_ASSERT_EQ(closedir(dir), 0);
return cdev_path;
}
+const char *vfio_pci_get_cdev_path(const char *bdf)
+{
+ const char *cdev_path = __vfio_pci_get_cdev_path(bdf, false);
+
+ VFIO_ASSERT_NOT_NULL(cdev_path, "Failed to find vfio cdev file for %s\n", bdf);
+ return cdev_path;
+}
+
int __vfio_device_bind_iommufd(int device_fd, int iommufd, const char *vf_token)
{
struct vfio_device_bind_iommufd args = {
@@ -449,10 +456,14 @@ static void vfio_device_attach_iommufd_pt(int device_fd, u32 pt_id)
void vfio_pci_cdev_open(struct vfio_pci_device *device, const char *bdf)
{
- const char *cdev_path = vfio_pci_get_cdev_path(bdf);
+ bool noiommu = device->iommu->mode->noiommu;
+ const char *cdev_path = __vfio_pci_get_cdev_path(bdf, noiommu);
+
+ VFIO_ASSERT_NOT_NULL(cdev_path, "Failed to find %svfio cdev file for %s\n",
+ noiommu ? "noiommu-" : "", bdf);
device->fd = open(cdev_path, O_RDWR);
- VFIO_ASSERT_GE(device->fd, 0);
+ VFIO_ASSERT_GE(device->fd, 0, "open(%s) failed\n", cdev_path);
free((void *)cdev_path);
}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 21/27] vfio: selftests: Add helpers to map DMA memory at IOVA == PA in noiommu mode
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (19 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 20/27] vfio: selftests: Add iommufd noiommu mode Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 22/27] vfio: selftests: Add helpers for Live Update DMA memory and sysfs state Vipin Sharma
` (5 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Selftest drivers program the IOVA returned by to_iova() into the device.
Because a device in noiommu mode uses those addresses directly as
physical addresses, DMA buffers must be mapped at IOVA == PA so that
to_iova() returns the buffer's physical address without changing the
drivers.
Add __iommu_noiommu_get_pa() to wrap IOMMU_IOAS_NOIOMMU_GET_PA and
iommu_map_identity() to map a physically contiguous buffer at IOVA == PA
by first pinning it at a scratch IOVA (BIT_ULL(52), above the 52-bit
output address space of the noiommu page table), querying its physical
address and contiguity, mapping it at IOVA == PA, and then unmapping the
scratch range while keeping the pages pinned.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../vfio/lib/include/libvfio/iommu.h | 26 ++++++++
tools/testing/selftests/vfio/lib/iommu.c | 61 +++++++++++++++++++
2 files changed, 87 insertions(+)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
index e652b8caf5bb..d449813f9bc4 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
@@ -65,6 +65,32 @@ static inline void iommu_unmap_all(struct iommu *iommu)
int __iommu_hva2iova(struct iommu *iommu, void *vaddr, iova_t *iova);
iova_t iommu_hva2iova(struct iommu *iommu, void *vaddr);
+/*
+ * Look up the physical address @iova maps to in the noiommu IOAS of @iommu
+ * (IOMMU_IOAS_NOIOMMU_GET_PA, requires CAP_SYS_RAWIO). On success, *@length is
+ * the number of physically contiguous bytes from *@pa, at most @max_length.
+ */
+int __iommu_noiommu_get_pa(struct iommu *iommu, iova_t iova, u64 max_length,
+ u64 *pa, u64 *length);
+
+/*
+ * Map @region->size bytes at @region->vaddr at IOVA == PA, and set
+ * @region->iova to that PA. Devices in noiommu mode use the addresses that
+ * drivers program into them, i.e. to_iova(), as physical addresses, which such
+ * a mapping makes right.
+ *
+ * The buffer must be physically contiguous (e.g. a hugepage or a THP), and a
+ * noiommu device must be attached to @iommu already. The memory stays pinned,
+ * and so at the same physical address, until iommu_unmap(@region), which must
+ * not be called before the device stopped accessing it.
+ */
+int __iommu_map_identity(struct iommu *iommu, struct dma_region *region);
+
+static inline void iommu_map_identity(struct iommu *iommu, struct dma_region *region)
+{
+ VFIO_ASSERT_EQ(__iommu_map_identity(iommu, region), 0);
+}
+
struct iommu_iova_range *iommu_iova_ranges(struct iommu *iommu, u32 *nranges);
#define MODE_VFIO_TYPE1_IOMMU "vfio_type1_iommu"
diff --git a/tools/testing/selftests/vfio/lib/iommu.c b/tools/testing/selftests/vfio/lib/iommu.c
index 0df49646d016..46e9635cbfc5 100644
--- a/tools/testing/selftests/vfio/lib/iommu.c
+++ b/tools/testing/selftests/vfio/lib/iommu.c
@@ -11,6 +11,7 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
+#include <linux/bits.h>
#include <linux/limits.h>
#include <linux/mman.h>
#include <linux/types.h>
@@ -244,6 +245,66 @@ int __iommu_unmap_all(struct iommu *iommu, u64 *unmapped)
return 0;
}
+/*
+ * The noiommu IOAS page table translates to physical addresses of at most 52
+ * bits (drivers/iommu/iommufd/hwpt_noiommu.c), so a mapping at or above this
+ * IOVA never overlaps one at IOVA == PA.
+ */
+#define NOIOMMU_SCRATCH_IOVA BIT_ULL(52)
+
+int __iommu_noiommu_get_pa(struct iommu *iommu, iova_t iova, u64 max_length,
+ u64 *pa, u64 *length)
+{
+ struct iommu_ioas_noiommu_get_pa args = {
+ .size = sizeof(args),
+ .ioas_id = iommu->ioas_id,
+ .iova = iova,
+ .length = max_length,
+ };
+
+ if (!iommu->iommufd || !iommu->mode->noiommu)
+ return -EOPNOTSUPP;
+
+ if (ioctl(iommu->iommufd, IOMMU_IOAS_NOIOMMU_GET_PA, &args))
+ return -errno;
+
+ *pa = args.out_phys;
+ *length = args.length;
+ return 0;
+}
+
+int __iommu_map_identity(struct iommu *iommu, struct dma_region *region)
+{
+ struct dma_region scratch = {
+ .vaddr = region->vaddr,
+ .iova = NOIOMMU_SCRATCH_IOVA,
+ .size = region->size,
+ };
+ u64 pa, length;
+ int ret;
+
+ /* Pin the buffer with a scratch mapping to look up its physical address. */
+ ret = __iommu_map(iommu, &scratch);
+ if (ret)
+ return ret;
+
+ ret = __iommu_noiommu_get_pa(iommu, scratch.iova, scratch.size, &pa, &length);
+ if (!ret && length != region->size)
+ ret = -EINVAL; /* Not physically contiguous. */
+
+ /*
+ * Map it at IOVA == PA before dropping the scratch mapping, so that it
+ * stays pinned, and hence at the same physical address, throughout.
+ */
+ if (!ret) {
+ region->iova = pa;
+ ret = __iommu_map(iommu, region);
+ }
+
+ VFIO_ASSERT_EQ(__iommu_unmap(iommu, &scratch, NULL), 0);
+ return ret;
+}
+
static struct vfio_info_cap_header *next_cap_hdr(void *buf, u32 bufsz,
u32 *cap_offset)
{
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 22/27] vfio: selftests: Add helpers for Live Update DMA memory and sysfs state
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (20 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 21/27] vfio: selftests: Add helpers to map DMA memory at IOVA == PA in " Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 23/27] vfio: selftests: Add an attach() driver op Vipin Sharma
` (4 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Add shared libvfio helpers used by the Live Update kexec selftest:
- vfio_thp_memfd_alloc() and vfio_memfd_mmap_aligned() to allocate a
shmem memfd, map it at a 2MB-aligned virtual address, and back it
with 2MB THP folios via MADV_HUGEPAGE, write-fault population, and
MADV_COLLAPSE.
- __sysfs_iommu_group_get(), a variant of sysfs_iommu_group_get() that
returns an error instead of asserting when the device has no IOMMU
group.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../selftests/vfio/lib/include/libvfio.h | 3 ++
.../vfio/lib/include/libvfio/sysfs.h | 1 +
tools/testing/selftests/vfio/lib/libvfio.c | 54 +++++++++++++++++++
tools/testing/selftests/vfio/lib/sysfs.c | 22 ++++++--
4 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio.h b/tools/testing/selftests/vfio/lib/include/libvfio.h
index 07862b470777..1c245962525b 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio.h
@@ -33,4 +33,7 @@ char **vfio_selftests_get_bdfs(int *argc, char *argv[], int *nr_bdfs);
*/
void *mmap_reserve(size_t size, size_t align, size_t offset);
+int vfio_thp_memfd_alloc(size_t size, void **vaddr_out);
+void *vfio_memfd_mmap_aligned(int fd, size_t size, size_t align);
+
#endif /* SELFTESTS_VFIO_LIB_INCLUDE_LIBVFIO_H */
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h b/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h
index c9ab1ea8f5a9..b9d6f6f69f88 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/sysfs.h
@@ -6,6 +6,7 @@ int sysfs_sriov_totalvfs_get(const char *bdf);
int sysfs_sriov_numvfs_get(const char *bdf);
void sysfs_sriov_numvfs_set(const char *bdf, int numvfs);
char *sysfs_sriov_vf_bdf_get(const char *pf_bdf, int i);
+int __sysfs_iommu_group_get(const char *bdf, int *group);
int sysfs_iommu_group_get(const char *bdf);
char *sysfs_driver_get(const char *bdf);
diff --git a/tools/testing/selftests/vfio/lib/libvfio.c b/tools/testing/selftests/vfio/lib/libvfio.c
index 3a3d1ed635c1..00369fe7d074 100644
--- a/tools/testing/selftests/vfio/lib/libvfio.c
+++ b/tools/testing/selftests/vfio/lib/libvfio.c
@@ -1,14 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
#include <sys/mman.h>
#include <linux/align.h>
+#include <linux/mman.h>
+#include <linux/sizes.h>
#include "../../../kselftest.h"
#include <libvfio.h>
+#ifndef MADV_COLLAPSE
+#define MADV_COLLAPSE 25
+#endif
+
static bool is_bdf(const char *str)
{
unsigned int s, b, d, f;
@@ -101,3 +110,48 @@ void *mmap_reserve(size_t size, size_t align, size_t offset)
return map_align;
}
+
+void *vfio_memfd_mmap_aligned(int fd, size_t size, size_t align)
+{
+ void *vaddr;
+
+ vaddr = mmap_reserve(size, align, 0);
+ vaddr = mmap(vaddr, size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, fd, 0);
+ VFIO_ASSERT_NE(vaddr, MAP_FAILED);
+
+ return vaddr;
+}
+
+int vfio_thp_memfd_alloc(size_t size, void **vaddr_out)
+{
+ void *vaddr;
+ int fd;
+
+ VFIO_ASSERT_TRUE(IS_ALIGNED(size, SZ_2M));
+
+ fd = memfd_create("vfio-lu-dma", 0);
+ VFIO_ASSERT_GE(fd, 0);
+
+ VFIO_ASSERT_EQ(ftruncate(fd, size), 0);
+
+ vaddr = vfio_memfd_mmap_aligned(fd, size, SZ_2M);
+
+ if (madvise(vaddr, size, MADV_HUGEPAGE))
+ ksft_exit_skip("madvise(MADV_HUGEPAGE) failed: %s\n",
+ strerror(errno));
+
+ /*
+ * Write-fault the mapping rather than using fallocate() so that shmem
+ * allocates PMD-sized THP folios when shmem_enabled is always,
+ * within_size, or advise, then collapse in case shmem_enabled is never.
+ */
+ memset(vaddr, 0, size);
+
+ if (madvise(vaddr, size, MADV_COLLAPSE))
+ ksft_exit_skip("madvise(MADV_COLLAPSE) failed: %s\n",
+ strerror(errno));
+
+ *vaddr_out = vaddr;
+ return fd;
+}
diff --git a/tools/testing/selftests/vfio/lib/sysfs.c b/tools/testing/selftests/vfio/lib/sysfs.c
index 98a46a2543cd..219db6aaa101 100644
--- a/tools/testing/selftests/vfio/lib/sysfs.c
+++ b/tools/testing/selftests/vfio/lib/sysfs.c
@@ -115,14 +115,30 @@ char *sysfs_sriov_vf_bdf_get(const char *pf_bdf, int i)
return out_vf_bdf;
}
-int sysfs_iommu_group_get(const char *bdf)
+int __sysfs_iommu_group_get(const char *bdf, int *group)
{
+ char rl_path[PATH_MAX];
char path[PATH_MAX];
- int group;
+ char *end;
+ int ret;
snprintf_assert(path, PATH_MAX, "/sys/bus/pci/devices/%s/iommu_group", bdf);
- readlink_base(path, "%d", &group);
+ ret = readlink_safe(path, rl_path);
+ if (ret == -1)
+ return -errno;
+
+ errno = 0;
+ *group = strtol(basename(rl_path), &end, 10);
+ VFIO_ASSERT_EQ(errno, 0);
+ VFIO_ASSERT_EQ(*end, '\0');
+ return 0;
+}
+
+int sysfs_iommu_group_get(const char *bdf)
+{
+ int group;
+ VFIO_ASSERT_EQ(__sysfs_iommu_group_get(bdf, &group), 0);
return group;
}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 23/27] vfio: selftests: Add an attach() driver op
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (21 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 22/27] vfio: selftests: Add helpers for Live Update DMA memory and sysfs state Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 24/27] vfio: selftests: Test preserving noiommu devices in vfio_pci_liveupdate_uapi_test Vipin Sharma
` (3 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Add a new attach() op to vfio_pci_driver_ops and a corresponding
vfio_pci_driver_attach() wrapper to adopt a device that was already
initialized (and optionally has memcpy operations in flight) in another
process prior to a Live Update, without resetting or reprogramming the
hardware.
Implement attach() for the igb and dsa selftest drivers: refresh process-
local BAR pointers, verify that the device hardware state and descriptor
rings are still intact as left before kexec, re-enable MSI-X, and set
driver capability limits.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../selftests/vfio/lib/drivers/dsa/dsa.c | 35 +++++++++++++++--
.../selftests/vfio/lib/drivers/igb/igb.c | 39 +++++++++++++++++--
.../lib/include/libvfio/vfio_pci_driver.h | 17 ++++++++
.../selftests/vfio/lib/vfio_pci_driver.c | 22 ++++++++++-
4 files changed, 105 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/vfio/lib/drivers/dsa/dsa.c b/tools/testing/selftests/vfio/lib/drivers/dsa/dsa.c
index 19d9630b24c2..2764ed4c67f8 100644
--- a/tools/testing/selftests/vfio/lib/drivers/dsa/dsa.c
+++ b/tools/testing/selftests/vfio/lib/drivers/dsa/dsa.c
@@ -215,6 +215,16 @@ static void dsa_register_cache_init(struct vfio_pci_device *device)
dsa->max_copies_per_batch = min(dsa->max_copies_per_batch, ARRAY_SIZE(dsa->copy));
}
+static void dsa_set_limits(struct vfio_pci_device *device)
+{
+ struct dsa_state *dsa = to_dsa_state(device);
+
+ device->driver.max_memcpy_count =
+ dsa->max_batches * dsa->max_copies_per_batch;
+ device->driver.max_memcpy_size = 1UL << dsa->gen_cap.max_xfer_shift;
+ device->driver.msi = MSIX_VECTOR;
+}
+
static void dsa_init(struct vfio_pci_device *device)
{
struct dsa_state *dsa = to_dsa_state(device);
@@ -236,11 +246,27 @@ static void dsa_init(struct vfio_pci_device *device)
dsa_command(device, IDXD_CMD_ENABLE_WQ);
vfio_pci_msix_enable(device, MSIX_VECTOR, 1);
+ dsa_set_limits(device);
+}
- device->driver.max_memcpy_count =
- dsa->max_batches * dsa->max_copies_per_batch;
- device->driver.max_memcpy_size = 1UL << dsa->gen_cap.max_xfer_shift;
- device->driver.msi = MSIX_VECTOR;
+static void dsa_attach(struct vfio_pci_device *device)
+{
+ struct dsa_state *dsa = to_dsa_state(device);
+ union gensts_reg gensts;
+
+ VFIO_ASSERT_GE(device->driver.region.size, sizeof(*dsa));
+
+ /* The device must still be enabled as dsa_init() left it. */
+ gensts.bits = readl(device->bars[0].vaddr + IDXD_GENSTATS_OFFSET);
+ VFIO_ASSERT_EQ((u32)gensts.state, IDXD_DEVICE_STATE_ENABLED,
+ "Device was disabled or reset\n");
+ dsa_check_sw_err(device);
+
+ /* BAR0 is mapped at a different address now, refresh the pointers into it. */
+ dsa_register_cache_init(device);
+
+ vfio_pci_msix_enable(device, MSIX_VECTOR, 1);
+ dsa_set_limits(device);
}
static void dsa_remove(struct vfio_pci_device *device)
@@ -420,6 +446,7 @@ const struct vfio_pci_driver_ops dsa_ops = {
.name = "dsa",
.probe = dsa_probe,
.init = dsa_init,
+ .attach = dsa_attach,
.remove = dsa_remove,
.memcpy_start = dsa_memcpy_start,
.memcpy_wait = dsa_memcpy_wait,
diff --git a/tools/testing/selftests/vfio/lib/drivers/igb/igb.c b/tools/testing/selftests/vfio/lib/drivers/igb/igb.c
index fd9e05d77ea4..3f9f631a5977 100644
--- a/tools/testing/selftests/vfio/lib/drivers/igb/igb.c
+++ b/tools/testing/selftests/vfio/lib/drivers/igb/igb.c
@@ -224,6 +224,13 @@ static void igb_reset(struct igb *igb)
igb_write32(igb, E1000_IMC, 0xFFFFFFFF);
}
+static void igb_set_limits(struct vfio_pci_device *device)
+{
+ device->driver.max_memcpy_size = IGB_MAX_CHUNK_SIZE;
+ device->driver.max_memcpy_count = RING_SIZE - 1;
+ device->driver.msi = MSIX_VECTOR;
+}
+
/*
* Program the device into a usable state. Split out of igb_init() so it
* can be reused after a device reset to re-program the registers that
@@ -372,9 +379,7 @@ static void igb_hw_init(struct vfio_pci_device *device)
igb->tx_tail = 0;
igb->rx_tail = 0;
- device->driver.max_memcpy_size = IGB_MAX_CHUNK_SIZE;
- device->driver.max_memcpy_count = RING_SIZE - 1;
- device->driver.msi = MSIX_VECTOR;
+ igb_set_limits(device);
}
static void igb_init(struct vfio_pci_device *device)
@@ -400,6 +405,33 @@ static void igb_init(struct vfio_pci_device *device)
igb_hw_init(device);
}
+static void igb_attach(struct vfio_pci_device *device)
+{
+ struct igb *igb = to_igb_state(device);
+ u64 iova_tx, iova_rx;
+
+ VFIO_ASSERT_GE(device->driver.region.size, sizeof(struct igb));
+
+ /* BAR0 is mapped at a different address in this process. */
+ igb->bar0 = device->bars[0].vaddr;
+
+ /*
+ * The device must still use the rings and tails in the driver region,
+ * i.e. it was not reset and the region is mapped at the same IOVAs.
+ */
+ iova_tx = to_iova(device, igb->tx_ring);
+ iova_rx = to_iova(device, igb->rx_ring);
+ VFIO_ASSERT_EQ(igb_read32(igb, E1000_TDBAL(0)), (u32)iova_tx);
+ VFIO_ASSERT_EQ(igb_read32(igb, E1000_TDBAH(0)), (u32)(iova_tx >> 32));
+ VFIO_ASSERT_EQ(igb_read32(igb, E1000_RDBAL(0)), (u32)iova_rx);
+ VFIO_ASSERT_EQ(igb_read32(igb, E1000_RDBAH(0)), (u32)(iova_rx >> 32));
+ VFIO_ASSERT_EQ(igb_read32(igb, E1000_TDT(0)), igb->tx_tail);
+ VFIO_ASSERT_EQ(igb_read32(igb, E1000_RDT(0)), igb->rx_tail);
+
+ vfio_pci_msix_enable(device, MSIX_VECTOR, 1);
+ igb_set_limits(device);
+}
+
static void igb_remove(struct vfio_pci_device *device)
{
struct igb *igb = to_igb_state(device);
@@ -578,6 +610,7 @@ const struct vfio_pci_driver_ops igb_ops = {
.name = "igb",
.probe = igb_probe,
.init = igb_init,
+ .attach = igb_attach,
.remove = igb_remove,
.memcpy_start = igb_memcpy_start,
.memcpy_wait = igb_memcpy_wait,
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_driver.h b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_driver.h
index e5ada209b1d1..09603eacab4d 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_driver.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_driver.h
@@ -28,6 +28,21 @@ struct vfio_pci_driver_ops {
*/
void (*remove)(struct vfio_pci_device *device);
+ /**
+ * attach() - Adopt @device as init() and memcpy_start() left it, e.g.
+ * in another process before a Live Update, without resetting or
+ * reprogramming it. Memcpy operations started then keep going and can be
+ * waited for with memcpy_wait(). Optional.
+ *
+ * Only sets up what is specific to the calling process (pointers into the
+ * BARs kept in the driver state, MSI-X) and the driver limits.
+ *
+ * Must be called after device->driver.region, and any memory the device
+ * was told to DMA to, have been mapped at the same IOVAs as when init()
+ * and memcpy_start() were called.
+ */
+ void (*attach)(struct vfio_pci_device *device);
+
/**
* memcpy_start() - Kick off @count repeated memcpy operations from
* [@src, @src + @size) to [@dst, @dst + @size).
@@ -85,6 +100,8 @@ struct vfio_pci_driver {
void vfio_pci_driver_probe(struct vfio_pci_device *device);
void vfio_pci_driver_init(struct vfio_pci_device *device);
+void vfio_pci_driver_attach(struct vfio_pci_device *device,
+ bool memcpy_in_progress);
void vfio_pci_driver_remove(struct vfio_pci_device *device);
int vfio_pci_driver_memcpy(struct vfio_pci_device *device,
iova_t src, iova_t dst, u64 size);
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_driver.c b/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
index 5e65434d2318..b4be2070342f 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
@@ -40,7 +40,8 @@ static void vfio_check_driver_op(struct vfio_pci_driver *driver, void *op,
{
VFIO_ASSERT_NOT_NULL(driver->ops);
VFIO_ASSERT_NOT_NULL(op, "Driver has no %s()\n", op_name);
- VFIO_ASSERT_EQ(driver->initialized, op != driver->ops->init);
+ VFIO_ASSERT_EQ(driver->initialized,
+ op != driver->ops->init && op != driver->ops->attach);
VFIO_ASSERT_EQ(driver->memcpy_in_progress, op == driver->ops->memcpy_wait);
}
@@ -61,6 +62,25 @@ void vfio_pci_driver_init(struct vfio_pci_device *device)
driver->initialized = true;
}
+/*
+ * Take over @device as vfio_pci_driver_init() left it in another process, e.g.
+ * before a Live Update. Pass @memcpy_in_progress if memcpy operations were
+ * started there, to be able to wait for them with vfio_pci_driver_memcpy_wait().
+ */
+void vfio_pci_driver_attach(struct vfio_pci_device *device,
+ bool memcpy_in_progress)
+{
+ struct vfio_pci_driver *driver = &device->driver;
+
+ VFIO_ASSERT_NOT_NULL(driver->region.vaddr);
+ VFIO_CHECK_DRIVER_OP(driver, attach);
+
+ driver->ops->attach(device);
+
+ driver->initialized = true;
+ driver->memcpy_in_progress = memcpy_in_progress;
+}
+
void vfio_pci_driver_remove(struct vfio_pci_device *device)
{
struct vfio_pci_driver *driver = &device->driver;
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 24/27] vfio: selftests: Test preserving noiommu devices in vfio_pci_liveupdate_uapi_test
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (22 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 23/27] vfio: selftests: Add an attach() driver op Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 25/27] vfio: selftests: Add vfio_pci_liveupdate_kexec_test Vipin Sharma
` (2 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Add the iommufd_noiommu fixture variant to vfio_pci_liveupdate_uapi_test
and detect at startup whether the target device is bound to vfio-pci in
noiommu mode so that variants not matching the device cleanly skip, in
addition to skipping iommufd variants when probing
luo_session_preserve_fd() in FIXTURE_SETUP reports -ENOENT (e.g. on an
SR-IOV VF or when CONFIG_VFIO_PCI_LIVEUPDATE=n).
When the device is in noiommu mode, verify that preserving its bound
VFIO cdev FD succeeds, and add tests verifying that:
- preserving an unbound second cdev FD fails with -ENOENT without
affecting the bound FD (preserve_unbound_fd_fails),
- preserving a cdev FD that was never bound fails with -ENOENT
(preserve_never_bound_fails), and
- closing the Live Update session unpreserves the device so that it can
be preserved again in a new session (preserve_after_unpreserve).
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../vfio/vfio_pci_liveupdate_uapi_test.c | 103 +++++++++++++++++-
1 file changed, 102 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c b/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c
index da25ea4e2b1e..46e97caf1f54 100644
--- a/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c
+++ b/tools/testing/selftests/vfio/vfio_pci_liveupdate_uapi_test.c
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <fcntl.h>
+#include <linux/pci_regs.h>
#include <libliveupdate.h>
#include <libvfio.h>
#include <kselftest_harness.h>
static const char *device_bdf;
+static bool device_is_noiommu;
FIXTURE(vfio_pci_liveupdate_uapi_test) {
int luo_fd;
@@ -45,10 +48,19 @@ FIXTURE_VARIANT_ADD(vfio_pci_liveupdate_uapi_test, _iommu_mode) { \
}
FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES();
+FIXTURE_VARIANT_ADD_IOMMU_MODE(iommufd_noiommu);
#undef FIXTURE_VARIANT_ADD_IOMMU_MODE
FIXTURE_SETUP(vfio_pci_liveupdate_uapi_test)
{
+ bool variant_is_noiommu;
+
+ variant_is_noiommu = !strcmp(variant->iommu_mode, MODE_IOMMUFD_NOIOMMU);
+ if (variant_is_noiommu != device_is_noiommu)
+ SKIP(return, "%s does not apply to a %s device",
+ variant->iommu_mode,
+ device_is_noiommu ? "noiommu" : "non-noiommu");
+
self->luo_fd = luo_open_device();
ASSERT_GT(self->luo_fd, 0);
@@ -77,6 +89,7 @@ FIXTURE_TEARDOWN(vfio_pci_liveupdate_uapi_test)
TEST_F(vfio_pci_liveupdate_uapi_test, preserve_device)
{
+ int expected;
int ret;
ret = luo_session_preserve_fd(self->session_fd, self->device->fd, 0);
@@ -85,7 +98,84 @@ TEST_F(vfio_pci_liveupdate_uapi_test, preserve_device)
* Only VFIO cdev files of devices in noiommu mode can be preserved;
* device files obtained through a VFIO group never can.
*/
- ASSERT_EQ(ret, self->iommu->iommufd ? -EOPNOTSUPP : -ENOENT);
+ if (device_is_noiommu)
+ expected = 0;
+ else
+ expected = self->iommu->iommufd ? -EOPNOTSUPP : -ENOENT;
+
+ ASSERT_EQ(ret, expected);
+}
+
+TEST_F(vfio_pci_liveupdate_uapi_test, preserve_unbound_fd_fails)
+{
+ const char *cdev_path;
+ int unbound_fd;
+ int ret;
+
+ if (!self->iommu->iommufd)
+ SKIP(return, "Only iommufd modes use VFIO cdev files");
+
+ cdev_path = __vfio_pci_get_cdev_path(device_bdf, device_is_noiommu);
+ ASSERT_NE(cdev_path, NULL);
+
+ unbound_fd = open(cdev_path, O_RDWR);
+ ASSERT_GE(unbound_fd, 0);
+ free((void *)cdev_path);
+
+ /*
+ * A second cdev FD that has not been bound via VFIO_DEVICE_BIND_IOMMUFD
+ * must be rejected even while another FD for the same device is bound.
+ */
+ ret = luo_session_preserve_fd(self->session_fd, unbound_fd, 0);
+ ASSERT_EQ(ret, -ENOENT);
+ ASSERT_EQ(close(unbound_fd), 0);
+
+ /* Verify the failed attempt had no side effects on the bound FD. */
+ ret = luo_session_preserve_fd(self->session_fd, self->device->fd, 0);
+ ASSERT_EQ(ret, device_is_noiommu ? 0 : -EOPNOTSUPP);
+}
+
+TEST_F(vfio_pci_liveupdate_uapi_test, preserve_never_bound_fails)
+{
+ const char *cdev_path;
+ int unbound_fd;
+ int ret;
+
+ if (!self->iommu->iommufd)
+ SKIP(return, "Only iommufd modes use VFIO cdev files");
+
+ vfio_pci_device_cleanup(self->device);
+ self->device = NULL;
+
+ cdev_path = __vfio_pci_get_cdev_path(device_bdf, device_is_noiommu);
+ ASSERT_NE(cdev_path, NULL);
+
+ unbound_fd = open(cdev_path, O_RDWR);
+ ASSERT_GE(unbound_fd, 0);
+ free((void *)cdev_path);
+
+ ret = luo_session_preserve_fd(self->session_fd, unbound_fd, 0);
+ ASSERT_EQ(ret, -ENOENT);
+ ASSERT_EQ(close(unbound_fd), 0);
+}
+
+TEST_F(vfio_pci_liveupdate_uapi_test, preserve_after_unpreserve)
+{
+ u16 vendor_id;
+
+ if (!device_is_noiommu)
+ SKIP(return, "Only noiommu devices can be preserved");
+
+ ASSERT_EQ(luo_session_preserve_fd(self->session_fd, self->device->fd, 0), 0);
+
+ ASSERT_EQ(close(self->session_fd), 0);
+ self->session_fd = luo_create_session(self->luo_fd, "session2");
+ ASSERT_GT(self->session_fd, 0);
+
+ ASSERT_EQ(luo_session_preserve_fd(self->session_fd, self->device->fd, 0), 0);
+
+ vendor_id = vfio_pci_config_readw(self->device, PCI_VENDOR_ID);
+ ASSERT_NE(vendor_id, 0xffff);
}
TEST_F(vfio_pci_liveupdate_uapi_test, preserve_group_fails)
@@ -112,6 +202,7 @@ TEST_F(vfio_pci_liveupdate_uapi_test, preserve_container_fails)
int main(int argc, char *argv[])
{
+ const char *cdev_path;
int fd;
fd = luo_open_device();
@@ -122,5 +213,15 @@ int main(int argc, char *argv[])
close(fd);
device_bdf = vfio_selftests_get_bdf(&argc, argv);
+
+ cdev_path = __vfio_pci_get_cdev_path(device_bdf, true);
+ if (cdev_path) {
+ device_is_noiommu = true;
+ } else {
+ cdev_path = vfio_pci_get_cdev_path(device_bdf);
+ device_is_noiommu = false;
+ }
+ free((void *)cdev_path);
+
return test_harness_run(argc, argv);
}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 25/27] vfio: selftests: Add vfio_pci_liveupdate_kexec_test
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (23 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 24/27] vfio: selftests: Test preserving noiommu devices in vfio_pci_liveupdate_uapi_test Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 26/27] vfio: selftests: Verify that opening VFIO device fails during Live Update Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 27/27] vfio: selftests: Add continuous DMA to vfio_pci_liveupdate_kexec_test Vipin Sharma
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Add a selftest to exercise preserving a vfio-pci device in noiommu mode
across a Live Update. For now the test is simple and verifies that the
device file can be preserved, retrieved, and bound to iommufd in the new
kernel. Subsequent commits extend this test to verify that the device
cannot be opened through VFIO during the Live Update and that DMA
continues across it.
This test is added to TEST_GEN_PROGS_EXTENDED since it must be run
manually along with a kexec.
The kernel must be booted with vfio.enable_unsafe_noiommu_mode=1 and the
target device must be bound to vfio-pci in both kernels. In the new
kernel, the preserved device must be bound to vfio-pci without first
being bound to or unbound from any other driver. This can be done during
boot, e.g. with vfio-pci.ids=<vid>:<did> on the kernel command line, or
manually before running stage 2:
$ echo vfio-pci > /sys/bus/pci/devices/0000:00:04.0/driver_override
$ echo 0000:00:04.0 > /sys/bus/pci/drivers/vfio-pci/bind
To run this test manually:
$ ./vfio_pci_liveupdate_kexec_test -s 1 0000:00:04.0
$ kexec ... # NOTE: Exact method will be distro-dependent
$ ./vfio_pci_liveupdate_kexec_test -s 2 0000:00:04.0
Do not unbind the device from vfio-pci after kexec (e.g. by running
setup.sh on a device that is already bound to vfio-pci), as unbinding it
would reset it.
This test is considered passing if all commands exit with 0.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
tools/testing/selftests/vfio/Makefile | 4 +
.../vfio/vfio_pci_liveupdate_kexec_test.c | 102 ++++++++++++++++++
2 files changed, 106 insertions(+)
create mode 100644 tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index ece2a3618f2b..7010a243a0b7 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -15,6 +15,10 @@ TEST_GEN_PROGS += vfio_pci_driver_test
TEST_GEN_PROGS += vfio_pci_sriov_uapi_test
TEST_GEN_PROGS += vfio_pci_liveupdate_uapi_test
+# This test must be run manually since it requires the user/automation to
+# perform a kexec during the test.
+TEST_GEN_PROGS_EXTENDED += vfio_pci_liveupdate_kexec_test
+
TEST_FILES += scripts/cleanup.sh
TEST_FILES += scripts/lib.sh
TEST_FILES += scripts/run.sh
diff --git a/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c b/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
new file mode 100644
index 000000000000..4919175c1173
--- /dev/null
+++ b/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <fcntl.h>
+#include <libliveupdate.h>
+#include <libvfio.h>
+
+static const char *device_bdf;
+
+static char state_session[LIVEUPDATE_SESSION_NAME_LENGTH];
+static char device_session[LIVEUPDATE_SESSION_NAME_LENGTH];
+
+enum {
+ STATE_TOKEN,
+ DEVICE_TOKEN,
+};
+
+static void check_noiommu(const char *bdf)
+{
+ const char *cdev_path = __vfio_pci_get_cdev_path(bdf, true);
+ int fd;
+
+ if (!cdev_path)
+ ksft_exit_skip("Device %s is not bound to vfio-pci in noiommu mode\n",
+ bdf);
+
+ fd = open(cdev_path, O_RDWR);
+ if (fd < 0)
+ ksft_exit_skip("open(%s) failed: %s\n", cdev_path,
+ strerror(errno));
+
+ close(fd);
+ free((void *)cdev_path);
+}
+
+static void before_kexec(int luo_fd)
+{
+ struct vfio_pci_device *device;
+ struct iommu *iommu;
+ int session_fd;
+ int ret;
+
+ check_noiommu(device_bdf);
+
+ iommu = iommu_init(MODE_IOMMUFD_NOIOMMU);
+ device = vfio_pci_device_init(device_bdf, iommu);
+
+ create_state_file(luo_fd, state_session, STATE_TOKEN, /*next_stage=*/2);
+
+ session_fd = luo_create_session(luo_fd, device_session);
+ VFIO_ASSERT_GE(session_fd, 0);
+
+ ret = luo_session_preserve_fd(session_fd, device->fd, DEVICE_TOKEN);
+ VFIO_ASSERT_EQ(ret, 0);
+
+ close(luo_fd);
+ daemonize_and_wait();
+}
+
+static void after_kexec(int luo_fd, int state_session_fd)
+{
+ struct vfio_pci_device *device;
+ struct iommu *iommu;
+ int session_fd;
+ int device_fd;
+ int stage;
+
+ restore_and_read_stage(state_session_fd, STATE_TOKEN, &stage);
+ VFIO_ASSERT_EQ(stage, 2);
+
+ session_fd = luo_retrieve_session(luo_fd, device_session);
+ VFIO_ASSERT_GE(session_fd, 0);
+
+ device_fd = luo_session_retrieve_fd(session_fd, DEVICE_TOKEN);
+ VFIO_ASSERT_GE(device_fd, 0);
+
+ iommu = iommu_init(MODE_IOMMUFD_NOIOMMU);
+
+ /*
+ * This will invoke various ioctls on device_fd such as
+ * VFIO_DEVICE_GET_INFO. So this is a decent sanity test
+ * that LUO actually handed us back a valid VFIO device
+ * file and not something else.
+ */
+ device = __vfio_pci_device_init(device_bdf, iommu, device_fd);
+
+ VFIO_ASSERT_EQ(luo_session_finish(session_fd), 0);
+
+ vfio_pci_device_cleanup(device);
+ iommu_cleanup(iommu);
+}
+
+int main(int argc, char *argv[])
+{
+ device_bdf = vfio_selftests_get_bdf(&argc, argv);
+
+ snprintf_assert(device_session, sizeof(device_session),
+ "vfio-lu-device-%s", device_bdf);
+ snprintf_assert(state_session, sizeof(state_session),
+ "vfio-lu-state-%s", device_bdf);
+
+ return luo_test(argc, argv, state_session, before_kexec, after_kexec);
+}
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 26/27] vfio: selftests: Verify that opening VFIO device fails during Live Update
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (24 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 25/27] vfio: selftests: Add vfio_pci_liveupdate_kexec_test Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
2026-11-02 15:36 ` [PATCH v6 27/27] vfio: selftests: Add continuous DMA to vfio_pci_liveupdate_kexec_test Vipin Sharma
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Verify that opening a VFIO device through its cdev file and via
VFIO_GROUP_GET_DEVICE_FD both fail with -EBUSY if the device was
preserved across a Live Update. When a device file is preserved across a
Live Update, the file must be retrieved from /dev/liveupdate, not from
VFIO directly.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../vfio/vfio_pci_liveupdate_kexec_test.c | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c b/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
index 4919175c1173..9610fe593cf6 100644
--- a/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
+++ b/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <fcntl.h>
+#include <limits.h>
+#include <sys/ioctl.h>
+#include <linux/vfio.h>
#include <libliveupdate.h>
#include <libvfio.h>
@@ -56,6 +59,39 @@ static void before_kexec(int luo_fd)
daemonize_and_wait();
}
+static void check_open_vfio_device_fails(void)
+{
+ const char *cdev_path = __vfio_pci_get_cdev_path(device_bdf, true);
+ char group_path[PATH_MAX];
+ int group_fd;
+ int group;
+ int ret;
+
+ VFIO_ASSERT_NOT_NULL(cdev_path);
+
+ ret = open(cdev_path, O_RDWR);
+ VFIO_ASSERT_EQ(ret, -1);
+ VFIO_ASSERT_EQ(errno, EBUSY);
+ free((void *)cdev_path);
+
+ if (__sysfs_iommu_group_get(device_bdf, &group))
+ return;
+
+ snprintf_assert(group_path, sizeof(group_path), "/dev/vfio/noiommu-%d",
+ group);
+ group_fd = open(group_path, O_RDWR);
+ if (group_fd < 0) {
+ VFIO_ASSERT_EQ(errno, ENOENT);
+ return;
+ }
+
+ ret = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD, device_bdf);
+ VFIO_ASSERT_EQ(ret, -1);
+ VFIO_ASSERT_EQ(errno, EBUSY);
+
+ VFIO_ASSERT_EQ(close(group_fd), 0);
+}
+
static void after_kexec(int luo_fd, int state_session_fd)
{
struct vfio_pci_device *device;
@@ -64,6 +100,8 @@ static void after_kexec(int luo_fd, int state_session_fd)
int device_fd;
int stage;
+ check_open_vfio_device_fails();
+
restore_and_read_stage(state_session_fd, STATE_TOKEN, &stage);
VFIO_ASSERT_EQ(stage, 2);
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 27/27] vfio: selftests: Add continuous DMA to vfio_pci_liveupdate_kexec_test
2026-11-02 15:36 [PATCH v6 00/27] VFIO PCI Live Update (noiommu mode) Vipin Sharma
` (25 preceding siblings ...)
2026-11-02 15:36 ` [PATCH v6 26/27] vfio: selftests: Verify that opening VFIO device fails during Live Update Vipin Sharma
@ 2026-11-02 15:36 ` Vipin Sharma
26 siblings, 0 replies; 28+ messages in thread
From: Vipin Sharma @ 2026-11-02 15:36 UTC (permalink / raw)
To: kexec, linux-kernel, linux-doc, kvm, linux-mm, linux-kselftest,
linux-pci
Cc: ajayachandra, alex, amastro, ankita, apopple, bhelgaas, chrisl,
christian.koenig, corbet, dmatlack, graf, jacob.pan, jgg, jgg,
jrhilke, julianr, kees, kevin.tian, leon, leonro, lukas, mattev,
michal.winiarski, parav, pasha.tatashin, praan, pratyush,
rananta, rdunlap, rientjes, rodrigo.vivi, rppt, saeedm, schnelle,
skhan, skhawaja, vipinsh, vivek.kasireddy, witu, yanjun.zhu,
yi.l.liu
Extend vfio_pci_liveupdate_kexec_test to exercise continuous DMA across
a Live Update when the device is in noiommu mode.
Allocate a shmem memfd backed by three 2MB THP folios (for the driver
region, source buffer, and destination buffer), map each folio at
IOVA == PA via iommu_map_identity(), and preserve the memfd in the same
Live Update session before the device so that LUO's LIFO finish order
keeps the memory alive until after the device is finished.
Before kexec, initialize the selftest driver, run a sanity memcpy, kick
off a long-running DMA memcpy, and disable MSI-X so freeze() succeeds.
After kexec, take over the device via __vfio_pci_device_init(), retrieve
and map the preserved memfd verifying that all three 2MB folios stayed
at the exact same physical addresses, attach the driver without resetting
the device, wait for the pre-kexec memcpy to complete, and run another
memcpy without reinitializing the device.
Co-developed-by: David Matlack <dmatlack@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
.../vfio/vfio_pci_liveupdate_kexec_test.c | 266 +++++++++++++++++-
1 file changed, 257 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c b/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
index 9610fe593cf6..c33b62118523 100644
--- a/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
+++ b/tools/testing/selftests/vfio/vfio_pci_liveupdate_kexec_test.c
@@ -1,22 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <linux/sizes.h>
#include <linux/vfio.h>
+
#include <libliveupdate.h>
#include <libvfio.h>
+#define DRIVER_REGION_SIZE SZ_2M
+#define MEMCPY_FOLIO_SIZE SZ_2M
+#define NR_MEMFD_FOLIOS 3
+#define MEMFD_SIZE (NR_MEMFD_FOLIOS * SZ_2M)
+
+#define MEMCPY_TIMEOUT_S (30 * 60)
+
+static struct dma_region src_region;
+static struct dma_region dst_region;
+static void *memfd_vaddr;
+static u64 folio_pa[NR_MEMFD_FOLIOS];
+
static const char *device_bdf;
static char state_session[LIVEUPDATE_SESSION_NAME_LENGTH];
+static char data_session[LIVEUPDATE_SESSION_NAME_LENGTH];
static char device_session[LIVEUPDATE_SESSION_NAME_LENGTH];
enum {
STATE_TOKEN,
DEVICE_TOKEN,
+ MEMFD_TOKEN,
+ DATA_TOKEN,
};
+static u64 memcpy_size(struct vfio_pci_device *device)
+{
+ return min_t(u64, src_region.size, device->driver.max_memcpy_size);
+}
+
+static bool is_filled(const char *buf, u64 size, char c)
+{
+ return buf[0] == c && !memcmp(buf, buf + 1, size - 1);
+}
+
+static void dma_memcpy_one(struct vfio_pci_device *device, char c)
+{
+ u64 size = memcpy_size(device);
+ void *src = src_region.vaddr;
+ void *dst = dst_region.vaddr;
+
+ memset(src, c, size);
+ memset(dst, 0, size);
+
+ VFIO_ASSERT_EQ(vfio_pci_driver_memcpy(device, to_iova(device, src),
+ to_iova(device, dst), size), 0);
+ VFIO_ASSERT_TRUE(is_filled(dst, size, c));
+}
+
+static void dma_memcpy_start(struct vfio_pci_device *device)
+{
+ u64 size = memcpy_size(device);
+ void *src = src_region.vaddr;
+ void *dst = dst_region.vaddr;
+ u64 count;
+
+ /*
+ * Rough Math: If we assume the device will perform memcpy at a rate of
+ * 30GB/s then 7200GB of transfers will run for about 4 minutes.
+ */
+ count = (u64)7200 * SZ_1G / size;
+ count = min_t(u64, count, device->driver.max_memcpy_count);
+
+ memset(src, 'x', size);
+ memset(dst, 0, size);
+
+ ksft_print_msg("Starting %lu DMA memcpy operations of size 0x%lx\n",
+ count, size);
+ vfio_pci_driver_memcpy_start(device, to_iova(device, src),
+ to_iova(device, dst), size, count);
+}
+
+static void dma_memcpy_wait(struct vfio_pci_device *device)
+{
+ u64 size = memcpy_size(device);
+ void *src = src_region.vaddr;
+ void *dst = dst_region.vaddr;
+ struct timespec start, end;
+ long ms;
+
+ VFIO_ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &start), 0);
+
+ /* Fail rather than hang if the device stopped, e.g. as it was reset. */
+ alarm(MEMCPY_TIMEOUT_S);
+ VFIO_ASSERT_EQ(vfio_pci_driver_memcpy_wait(device), 0);
+ alarm(0);
+
+ VFIO_ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &end), 0);
+ ms = (end.tv_sec - start.tv_sec) * 1000 +
+ (end.tv_nsec - start.tv_nsec) / 1000000;
+
+ VFIO_ASSERT_TRUE(is_filled(src, size, 'x'));
+ VFIO_ASSERT_TRUE(is_filled(dst, size, 'x'));
+
+ ksft_print_msg("Pre-kexec DMA memcpy operations completed after %ld ms\n",
+ ms);
+}
+
static void check_noiommu(const char *bdf)
{
const char *cdev_path = __vfio_pci_get_cdev_path(bdf, true);
@@ -35,26 +133,162 @@ static void check_noiommu(const char *bdf)
free((void *)cdev_path);
}
+static void dma_memfd_regions_map(struct vfio_pci_device *device, int fd,
+ bool before_kexec)
+{
+ struct dma_region *regions[NR_MEMFD_FOLIOS] = {
+ &device->driver.region,
+ &src_region,
+ &dst_region,
+ };
+ int i;
+
+ memfd_vaddr = vfio_memfd_mmap_aligned(fd, MEMFD_SIZE, SZ_2M);
+
+ for (i = 0; i < NR_MEMFD_FOLIOS; i++) {
+ struct dma_region *r = regions[i];
+
+ r->vaddr = memfd_vaddr + (u64)i * SZ_2M;
+ r->size = SZ_2M;
+ iommu_map_identity(device->iommu, r);
+
+ if (before_kexec)
+ folio_pa[i] = r->iova;
+ else
+ VFIO_ASSERT_EQ(r->iova, folio_pa[i],
+ "Folio %d moved across kexec (0x%lx != 0x%lx)\n",
+ i, r->iova, folio_pa[i]);
+ }
+}
+
+static void dma_memfd_recheck_pa(struct vfio_pci_device *device)
+{
+ struct dma_region *regions[NR_MEMFD_FOLIOS] = {
+ &device->driver.region,
+ &src_region,
+ &dst_region,
+ };
+ int i;
+
+ for (i = 0; i < NR_MEMFD_FOLIOS; i++) {
+ u64 pa, len;
+
+ VFIO_ASSERT_EQ(__iommu_noiommu_get_pa(device->iommu,
+ regions[i]->iova, SZ_2M,
+ &pa, &len), 0);
+ VFIO_ASSERT_EQ(pa, folio_pa[i]);
+ VFIO_ASSERT_EQ(len, (u64)SZ_2M);
+ }
+}
+
+static void dma_memfd_regions_unmap(struct vfio_pci_device *device)
+{
+ iommu_unmap(device->iommu, &dst_region);
+ iommu_unmap(device->iommu, &src_region);
+ iommu_unmap(device->iommu, &device->driver.region);
+ VFIO_ASSERT_EQ(munmap(memfd_vaddr, MEMFD_SIZE), 0);
+ memfd_vaddr = NULL;
+}
+
+static void save_data_session(int luo_fd)
+{
+ char buf[256];
+ int data_fd;
+
+ data_fd = luo_create_session(luo_fd, data_session);
+ VFIO_ASSERT_GE(data_fd, 0);
+
+ snprintf_assert(buf, sizeof(buf), "0x%lx 0x%lx 0x%lx",
+ folio_pa[0], folio_pa[1], folio_pa[2]);
+ VFIO_ASSERT_EQ(create_and_preserve_memfd(data_fd, DATA_TOKEN, buf), 0);
+}
+
+static void load_data_session(int luo_fd)
+{
+ char buf[256] = {};
+ char *end, *cur;
+ int data_fd;
+ int mfd;
+ int i;
+
+ data_fd = luo_retrieve_session(luo_fd, data_session);
+ VFIO_ASSERT_GE(data_fd, 0);
+
+ mfd = luo_session_retrieve_fd(data_fd, DATA_TOKEN);
+ VFIO_ASSERT_GE(mfd, 0);
+ VFIO_ASSERT_GT(pread(mfd, buf, sizeof(buf) - 1, 0), 0);
+ VFIO_ASSERT_EQ(close(mfd), 0);
+
+ cur = buf;
+ for (i = 0; i < NR_MEMFD_FOLIOS; i++) {
+ errno = 0;
+ folio_pa[i] = strtoull(cur, &end, 0);
+ VFIO_ASSERT_EQ(errno, 0);
+ VFIO_ASSERT_NE(end, cur);
+ cur = end;
+ }
+
+ VFIO_ASSERT_EQ(luo_session_finish(data_fd), 0);
+ VFIO_ASSERT_EQ(close(data_fd), 0);
+}
+
static void before_kexec(int luo_fd)
{
struct vfio_pci_device *device;
struct iommu *iommu;
+ void *init_vaddr = NULL;
int session_fd;
+ int memfd;
int ret;
check_noiommu(device_bdf);
+ memfd = vfio_thp_memfd_alloc(MEMFD_SIZE, &init_vaddr);
+ if (memfd < 0)
+ ksft_exit_skip("Failed to allocate 2MB THP memfd: %s\n",
+ strerror(-memfd));
+ VFIO_ASSERT_EQ(munmap(init_vaddr, MEMFD_SIZE), 0);
+
iommu = iommu_init(MODE_IOMMUFD_NOIOMMU);
device = vfio_pci_device_init(device_bdf, iommu);
+ if (!device->driver.ops || !device->driver.ops->attach)
+ ksft_exit_skip("No driver that supports attach() found for %s\n",
+ device_bdf);
+
+ dma_memfd_regions_map(device, memfd, true);
+
create_state_file(luo_fd, state_session, STATE_TOKEN, /*next_stage=*/2);
session_fd = luo_create_session(luo_fd, device_session);
VFIO_ASSERT_GE(session_fd, 0);
+ /*
+ * Preserve the DMA memfd in the same session before the device so that
+ * LUO's LIFO finish order resets an unclaimed device before freeing
+ * the memfd folios.
+ */
+ ret = luo_session_preserve_fd(session_fd, memfd, MEMFD_TOKEN);
+ VFIO_ASSERT_EQ(ret, 0);
+
+ dma_memfd_recheck_pa(device);
+
ret = luo_session_preserve_fd(session_fd, device->fd, DEVICE_TOKEN);
VFIO_ASSERT_EQ(ret, 0);
+ vfio_pci_driver_init(device);
+ dma_memcpy_one(device, 'a');
+
+ dma_memcpy_start(device);
+
+ /*
+ * Disable interrupts on the device or freeze() will fail. The memcpy
+ * operations do not generate any.
+ */
+ vfio_pci_msix_disable(device);
+
+ save_data_session(luo_fd);
+
close(luo_fd);
daemonize_and_wait();
}
@@ -98,13 +332,16 @@ static void after_kexec(int luo_fd, int state_session_fd)
struct iommu *iommu;
int session_fd;
int device_fd;
+ int memfd;
int stage;
- check_open_vfio_device_fails();
-
restore_and_read_stage(state_session_fd, STATE_TOKEN, &stage);
VFIO_ASSERT_EQ(stage, 2);
+ load_data_session(luo_fd);
+
+ check_open_vfio_device_fails();
+
session_fd = luo_retrieve_session(luo_fd, device_session);
VFIO_ASSERT_GE(session_fd, 0);
@@ -112,16 +349,25 @@ static void after_kexec(int luo_fd, int state_session_fd)
VFIO_ASSERT_GE(device_fd, 0);
iommu = iommu_init(MODE_IOMMUFD_NOIOMMU);
-
- /*
- * This will invoke various ioctls on device_fd such as
- * VFIO_DEVICE_GET_INFO. So this is a decent sanity test
- * that LUO actually handed us back a valid VFIO device
- * file and not something else.
- */
device = __vfio_pci_device_init(device_bdf, iommu, device_fd);
+ memfd = luo_session_retrieve_fd(session_fd, MEMFD_TOKEN);
+ VFIO_ASSERT_GE(memfd, 0);
+
+ dma_memfd_regions_map(device, memfd, false);
+
+ vfio_pci_driver_attach(device, /*memcpy_in_progress=*/true);
+ dma_memcpy_wait(device);
+
+ /* The device was not reset, so it can be used without reinitializing it. */
+ dma_memcpy_one(device, 'y');
+
VFIO_ASSERT_EQ(luo_session_finish(session_fd), 0);
+ VFIO_ASSERT_EQ(close(session_fd), 0);
+
+ vfio_pci_driver_remove(device);
+ dma_memfd_regions_unmap(device);
+ VFIO_ASSERT_EQ(close(memfd), 0);
vfio_pci_device_cleanup(device);
iommu_cleanup(iommu);
@@ -133,6 +379,8 @@ int main(int argc, char *argv[])
snprintf_assert(device_session, sizeof(device_session),
"vfio-lu-device-%s", device_bdf);
+ snprintf_assert(data_session, sizeof(data_session),
+ "vfio-lu-data-%s", device_bdf);
snprintf_assert(state_session, sizeof(state_session),
"vfio-lu-state-%s", device_bdf);
--
2.56.0.317.g454f8302e1-goog
^ permalink raw reply [flat|nested] 28+ messages in thread