[RFC PATCH 30/46] KVM: caretaker: Integrate Caretaker vCPU detach, attach, and cancel with KVM
From: Pasha Tatashin
Date: Sun Sep 20 2026 - 15:52:32 EST
Integrate Caretaker vCPU preservation, re-attachment, and cancellation
with KVM LUO handlers and enable CONFIG_KVM_CARETAKER build rules.
Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
---
Documentation/liveupdate/vmm.rst | 15 +++
include/linux/kvm_caretaker.h | 1 +
virt/kvm/Kconfig | 28 +++++
virt/kvm/Makefile.kvm | 7 ++
virt/kvm/caretaker.c | 197 ++++++++++++++++++++++++++++++-
virt/kvm/kvm_luo.c | 70 ++++++++++-
virt/kvm/kvm_main.c | 7 ++
7 files changed, 318 insertions(+), 7 deletions(-)
diff --git a/Documentation/liveupdate/vmm.rst b/Documentation/liveupdate/vmm.rst
index b8e8fb2b63c2..a0b54d6f1220 100644
--- a/Documentation/liveupdate/vmm.rst
+++ b/Documentation/liveupdate/vmm.rst
@@ -100,8 +100,23 @@ VM & Guest_Memfd Preservation ABI
.. kernel-doc:: include/linux/kho/abi/kvm.h
:internal:
+KVM Caretaker (Orphaned VM Execution)
+=====================================
+
+.. kernel-doc:: virt/kvm/caretaker.c
+ :doc: KVM Caretaker Architecture and Lifecycle
+
+KVM Caretaker Core & Architecture API
+=====================================
+
+.. kernel-doc:: include/linux/kvm_caretaker.h
+
+.. kernel-doc:: virt/kvm/caretaker.c
+ :identifiers:
+
See Also
========
- :doc:`/core-api/liveupdate`
+- :doc:`/liveupdate/cpu_preservation`
- :doc:`/userspace-api/liveupdate`
diff --git a/include/linux/kvm_caretaker.h b/include/linux/kvm_caretaker.h
index 3e89738d7fdf..0d4bdde7b1c3 100644
--- a/include/linux/kvm_caretaker.h
+++ b/include/linux/kvm_caretaker.h
@@ -241,6 +241,7 @@ void kvm_arch_vcpu_luo_pre_retrieve_caretaker(struct kvm_vcpu *vcpu,
void kvm_arch_vcpu_luo_attach_caretaker(struct kvm_vcpu *vcpu,
struct kvm_vcpu_ser *ser);
+void kvm_caretaker_vm_pre_retrieve(void);
int kvm_caretaker_vcpu_pre_preserve(struct kvm_vcpu *vcpu,
struct liveupdate_session *session,
struct kvm_vcpu_ser *ser);
diff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig
index fcaf57377e73..165e0c85b8aa 100644
--- a/virt/kvm/Kconfig
+++ b/virt/kvm/Kconfig
@@ -125,3 +125,31 @@ config HAVE_KVM_ARCH_GMEM_INVALIDATE
config HAVE_KVM_ARCH_GMEM_POPULATE
bool
depends on KVM_GUEST_MEMFD
+
+config HAVE_KVM_ARCH_CARETAKER
+ bool
+
+config KVM_CARETAKER
+ bool "Orphaned VM Caretaker execution and lifecycle management"
+ depends on KVM
+ depends on LIVEUPDATE_CPU
+ depends on SMP
+ depends on HOTPLUG_CPU
+ depends on HAVE_KVM_ARCH_CARETAKER
+ select LIVEUPDATE_ONCORE
+ default LIVEUPDATE_CPU
+ help
+ Enable Caretaker CPU preservation and standalone execution for
+ orphaned virtual machines across host kernel live updates.
+
+ During a host kernel live update, user space hypervisors
+ and host services terminate, leaving running guest virtual
+ machines in an orphaned state. Caretaker keeps designated physical
+ CPUs running their respective guest vCPUs on-core in a standalone
+ execution loop throughout the entire live update transition.
+
+ Caretaker attaches to preserved CPUs via the cpu_preserve subsystem
+ and continues guest vCPU execution across the kexec live update
+ without pausing the guest.
+
+ If unsure, say N.
diff --git a/virt/kvm/Makefile.kvm b/virt/kvm/Makefile.kvm
index d30fca094c42..ae1fcf9861b7 100644
--- a/virt/kvm/Makefile.kvm
+++ b/virt/kvm/Makefile.kvm
@@ -14,3 +14,10 @@ kvm-$(CONFIG_HAVE_KVM_DIRTY_RING) += $(KVM)/dirty_ring.o
kvm-$(CONFIG_HAVE_KVM_PFNCACHE) += $(KVM)/pfncache.o
kvm-$(CONFIG_KVM_GUEST_MEMFD) += $(KVM)/guest_memfd.o
kvm-$(CONFIG_LIVEUPDATE_GUEST_MEMFD) += $(KVM)/guest_memfd_luo.o $(KVM)/kvm_luo.o
+kvm-$(CONFIG_KVM_CARETAKER) += $(KVM)/caretaker.o
+KASAN_SANITIZE_$(KVM)/caretaker.o := n
+KCSAN_SANITIZE_$(KVM)/caretaker.o := n
+UBSAN_SANITIZE_$(KVM)/caretaker.o := n
+KCOV_INSTRUMENT_$(KVM)/caretaker.o := n
+CFLAGS_REMOVE_$(KVM)/caretaker.o = $(CC_FLAGS_FTRACE)
+CFLAGS_$(KVM)/caretaker.o += -fno-stack-protector $(call cc-option,-ftrivial-auto-var-init=uninitialized) -fno-builtin-memset -fno-builtin-memcpy $(call cc-option,-fno-tree-loop-distribute-patterns) $(call cc-option,-fno-jump-tables)
diff --git a/virt/kvm/caretaker.c b/virt/kvm/caretaker.c
index b4a203562727..dac64c466d76 100644
--- a/virt/kvm/caretaker.c
+++ b/virt/kvm/caretaker.c
@@ -196,7 +196,7 @@ kvm_caretaker_should_exit(struct kvm_caretaker_vcpu *cvcpu)
cpu_preserved_inval(cvcpu->cb);
- st = READ_ONCE(cvcpu->cb->state);
+ st = smp_load_acquire(&cvcpu->cb->state);
if (st != KVM_CARETAKER_PAUSED && st != KVM_CARETAKER_RUNNING)
return true;
@@ -365,7 +365,7 @@ static bool kvm_caretaker_try_stop(struct kvm_caretaker_cb_ser *cb)
{
cpu_preserved_inval(cb);
- if (READ_ONCE(cb->state) == KVM_CARETAKER_STOPPED)
+ if (smp_load_acquire(&cb->state) == KVM_CARETAKER_STOPPED)
return true;
if (cmpxchg(&cb->state, KVM_CARETAKER_PAUSED,
@@ -397,10 +397,8 @@ int kvm_caretaker_wait_for_attach(struct kvm_caretaker_cb_ser *cb, int pcpu)
return 0;
if (!cpu_is_preserved(pcpu)) {
- WRITE_ONCE(cb->state, KVM_CARETAKER_STOPPED);
+ smp_store_release(&cb->state, KVM_CARETAKER_STOPPED);
cpu_preserved_clean(cb);
- /* Ensure state update is visible before returning to caller */
- smp_wmb();
return 0;
}
@@ -450,3 +448,192 @@ void kvm_caretaker_post_attach_vcpu(struct kvm_vcpu *vcpu)
}
}
+/**
+ * kvm_caretaker_vcpu_pre_preserve - Submit an On-Core job for a vCPU prior to arch preserve
+ * @vcpu: KVM vCPU being preserved.
+ * @session: Active Live Update session.
+ * @ser: Serialized KHO vCPU descriptor to populate.
+ *
+ * Submits a Caretaker job to @session before kvm_arch_vcpu_luo_preserve() runs.
+ * If @session has preserved physical CPUs, assigns the job to the least-loaded
+ * preserved CPU and sets %KVM_VCPU_LUO_FLAG_CARETAKER in @ser->flags so the
+ * architecture hook allocates and populates a Caretaker runtime page. If
+ * @session has no preserved physical CPUs, returns 0 without setting the flag
+ * so the vCPU is preserved in RAM only.
+ *
+ * Return: 0 on success, or a negative errno on job allocation failure.
+ */
+int kvm_caretaker_vcpu_pre_preserve(struct kvm_vcpu *vcpu,
+ struct liveupdate_session *session,
+ struct kvm_vcpu_ser *ser)
+{
+ struct oncore_job *job;
+
+ /*
+ * Submit with no data: the run callback's argument is the caretaker
+ * control block, which does not exist until the architecture's
+ * kvm_arch_vcpu_luo_preserve() has allocated it. It is installed with
+ * oncore_job_set_data() from _post_preserve(), before activation.
+ */
+ job = oncore_session_submit_job(session, kvm_arch_vcpu_caretaker_run,
+ NULL);
+ if (IS_ERR(job))
+ return PTR_ERR(job);
+ if (!job)
+ return 0;
+
+ vcpu->caretaker.job = job;
+ ser->flags |= KVM_VCPU_LUO_FLAG_CARETAKER;
+
+ return 0;
+}
+
+/**
+ * kvm_caretaker_vcpu_post_preserve - Activate the Caretaker On-Core job after arch preserve
+ * @vcpu: KVM vCPU being preserved.
+ * @session: Active Live Update session.
+ * @ser: Serialized KHO vCPU descriptor.
+ * @arch_err: Result of kvm_arch_vcpu_luo_preserve() (non-zero on failure).
+ *
+ * If @arch_err is non-zero, cancels and frees any job created in
+ * kvm_caretaker_vcpu_pre_preserve(). Otherwise, installs @vcpu->caretaker.cb
+ * as the job's run argument, flushes the control block to PoC, and activates
+ * the job on the session's runqueue so the preserved physical CPU begins
+ * executing the vCPU.
+ *
+ * Return: 0 on success, or @arch_err / negative errno on failure.
+ */
+int kvm_caretaker_vcpu_post_preserve(struct kvm_vcpu *vcpu,
+ struct liveupdate_session *session,
+ struct kvm_vcpu_ser *ser,
+ int arch_err)
+{
+ struct kvm_caretaker_cb_ser *cb = vcpu->caretaker.cb;
+ struct oncore_job *job = vcpu->caretaker.job;
+ int err;
+
+ if (arch_err) {
+ if (job)
+ oncore_session_cancel_job(session, job);
+ vcpu->caretaker.job = NULL;
+ vcpu->caretaker.cb = NULL;
+ return arch_err;
+ }
+
+ if (!(ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) || !cb)
+ return 0;
+
+ oncore_job_set_data(job, cb);
+
+ kvm_caretaker_pause(cb);
+ cpu_preserved_clean(cb);
+
+ err = oncore_session_activate_job(session, job);
+ if (err) {
+ oncore_session_cancel_job(session, job);
+ vcpu->caretaker.job = NULL;
+ kvm_caretaker_stop(cb);
+ vcpu->caretaker.cb = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * kvm_caretaker_vm_pre_retrieve - Stop Caretaker execution before retrieving VM state
+ *
+ * Detaches preserved physical CPU workloads before the incoming kernel creates
+ * the restored KVM VM instance so Caretaker execution stops immediately when
+ * userspace begins reclaiming the VM session.
+ */
+void kvm_caretaker_vm_pre_retrieve(void)
+{
+ int cpu;
+
+ for_each_cpu(cpu, cpu_get_preserved_mask())
+ cpu_preserved_detach_workload(cpu);
+}
+
+/**
+ * kvm_caretaker_vcpu_pre_retrieve - Stop Caretaker execution before retrieving vCPU state
+ * @vcpu: Incoming KVM vCPU being restored.
+ * @ser: Serialized KHO vCPU descriptor.
+ *
+ * Resolves @ser->cb and invokes kvm_arch_vcpu_luo_pre_retrieve_caretaker() so
+ * the preserved physical CPU stops guest execution and serializes its latest
+ * state into @ser->arch_state before kvm_arch_vcpu_luo_retrieve() reads it.
+ */
+void kvm_caretaker_vcpu_pre_retrieve(struct kvm_vcpu *vcpu,
+ struct kvm_vcpu_ser *ser)
+{
+ if (ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER)
+ vcpu->caretaker.cb = KHOSER_LOAD_PTR(ser->cb);
+
+ kvm_arch_vcpu_luo_pre_retrieve_caretaker(vcpu, ser);
+}
+
+/**
+ * kvm_caretaker_vcpu_retrieve - Complete Caretaker hardware attachment during vCPU retrieve
+ * @vcpu: Incoming KVM vCPU being restored.
+ * @ser: Serialized KHO vCPU descriptor.
+ *
+ * Invokes kvm_arch_vcpu_luo_attach_caretaker() after architectural register
+ * state has been restored into @vcpu.
+ */
+void kvm_caretaker_vcpu_retrieve(struct kvm_vcpu *vcpu,
+ struct kvm_vcpu_ser *ser)
+{
+ kvm_arch_vcpu_luo_attach_caretaker(vcpu, ser);
+}
+
+/**
+ * kvm_caretaker_vcpu_unpreserve - Roll back Caretaker execution on live update cancellation
+ * @vcpu: Outgoing KVM vCPU being unpreserved.
+ * @session: Live Update session being cancelled.
+ * @ser: Serialized KHO vCPU descriptor.
+ *
+ * Stops the vCPU on the preserved physical CPU, synchronizes any guest state
+ * updates back into the outgoing @vcpu, frees KHO telemetry buffers, and
+ * cancels the On-Core job.
+ */
+void kvm_caretaker_vcpu_unpreserve(struct kvm_vcpu *vcpu,
+ struct liveupdate_session *session,
+ struct kvm_vcpu_ser *ser)
+{
+ if (ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) {
+ kvm_arch_vcpu_luo_pre_retrieve_caretaker(vcpu, ser);
+ kvm_arch_vcpu_luo_retrieve(vcpu, ser);
+ kvm_arch_vcpu_luo_attach_caretaker(vcpu, ser);
+ kvm_caretaker_telemetry_free(ser, false);
+ }
+
+ if (vcpu->caretaker.job) {
+ oncore_session_cancel_job(session, vcpu->caretaker.job);
+ vcpu->caretaker.job = NULL;
+ }
+ vcpu->caretaker.cb = NULL;
+}
+
+/**
+ * kvm_caretaker_vcpu_finish - Release Caretaker KHO resources after live update completion
+ * @vcpu: Incoming KVM vCPU (or %NULL on retrieve failure cleanup).
+ * @session: Completed Live Update session.
+ * @ser: Serialized KHO vCPU descriptor.
+ *
+ * Ensures the Caretaker vCPU has detached and frees KHO-preserved telemetry
+ * buffers in the incoming kernel.
+ */
+void kvm_caretaker_vcpu_finish(struct kvm_vcpu *vcpu,
+ struct liveupdate_session *session,
+ struct kvm_vcpu_ser *ser)
+{
+ if (ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) {
+ kvm_arch_vcpu_luo_pre_retrieve_caretaker(vcpu, ser);
+ kvm_caretaker_telemetry_free(ser, true);
+ }
+
+ if (vcpu)
+ vcpu->caretaker.cb = NULL;
+}
+
diff --git a/virt/kvm/kvm_luo.c b/virt/kvm/kvm_luo.c
index f7e259bef4b7..fe30f2d41eb4 100644
--- a/virt/kvm/kvm_luo.c
+++ b/virt/kvm/kvm_luo.c
@@ -44,6 +44,7 @@
*/
#include <linux/liveupdate.h>
#include <linux/kvm_host.h>
+#include <linux/kvm_caretaker.h>
#include <linux/pagemap.h>
#include <linux/fdtable.h>
#include <linux/file.h>
@@ -55,6 +56,45 @@
#include <linux/kho/abi/kvm.h>
#include "kvm_mm.h"
+struct kvm_kho_folios_ser *kvm_kho_folios_alloc(unsigned int max_folios)
+{
+ struct kvm_kho_folios_ser *kp;
+ size_t sz = struct_size(kp, folios_pa, max_folios);
+
+ kp = kho_alloc_preserve(sz);
+ if (IS_ERR(kp))
+ return kp;
+
+ kp->nr_folios = 0;
+ return kp;
+}
+
+void kvm_kho_folios_unpreserve(struct kvm_kho_folios_ser *kp)
+{
+ unsigned int i;
+
+ if (!kp)
+ return;
+
+ for (i = 0; i < kp->nr_folios; i++)
+ kho_unpreserve_folio(page_folio(phys_to_page(kp->folios_pa[i])));
+
+ kho_unpreserve_free(kp);
+}
+
+void kvm_kho_folios_finish(struct kvm_kho_folios_ser *kp)
+{
+ unsigned int i;
+
+ if (!kp)
+ return;
+
+ for (i = 0; i < kp->nr_folios; i++)
+ kho_restore_free(phys_to_virt(kp->folios_pa[i]));
+
+ kho_restore_free(kp);
+}
+
static bool kvm_luo_can_preserve(struct liveupdate_file_handler *handler,
struct file *file)
{
@@ -81,6 +121,7 @@ static int kvm_luo_preserve(struct liveupdate_file_op_args *args)
* architecture that does not implement the hook.
*/
ser->type = 0;
+ ser->kho_folios.phys = 0;
err = kvm_arch_vm_luo_preserve(kvm, ser);
if (err) {
kho_unpreserve_free(ser);
@@ -104,6 +145,8 @@ static int kvm_luo_retrieve(struct liveupdate_file_op_args *args)
if (!args->serialized_data)
return -EINVAL;
+ kvm_caretaker_vm_pre_retrieve();
+
ser = phys_to_virt(args->serialized_data);
snprintf(fdname, sizeof(fdname), "%d",
@@ -130,6 +173,7 @@ static int kvm_luo_retrieve(struct liveupdate_file_op_args *args)
err_free_ser:
kvm_arch_vm_luo_finish(ser);
+ kvm_kho_folios_finish(KHOSER_LOAD_PTR(ser->kho_folios));
kho_restore_free(ser);
return err;
}
@@ -150,6 +194,9 @@ static void kvm_luo_unpreserve(struct liveupdate_file_op_args *args)
ser = phys_to_virt(args->serialized_data);
kvm_arch_vm_luo_unpreserve(kvm, ser);
+ if (kvm)
+ kvm->kho_folios = NULL;
+ kvm_kho_folios_unpreserve(KHOSER_LOAD_PTR(ser->kho_folios));
kho_unpreserve_free(ser);
}
@@ -165,6 +212,7 @@ static void kvm_luo_finish(struct liveupdate_file_op_args *args)
ser = phys_to_virt(args->serialized_data);
kvm_arch_vm_luo_finish(ser);
+ kvm_kho_folios_finish(KHOSER_LOAD_PTR(ser->kho_folios));
kho_restore_free(ser);
}
@@ -217,8 +265,15 @@ static int kvm_vcpu_luo_preserve(struct liveupdate_file_op_args *args)
ser->vcpu_id = vcpu->vcpu_id;
ser->flags = 0;
ser->vm_token = vm_token;
-
- err = kvm_arch_vcpu_luo_preserve(vcpu, ser);
+ ser->arch_state.phys = 0;
+ ser->cb.phys = 0;
+
+ err = kvm_caretaker_vcpu_pre_preserve(vcpu, args->session, ser);
+ if (!err) {
+ err = kvm_arch_vcpu_luo_preserve(vcpu, ser);
+ err = kvm_caretaker_vcpu_post_preserve(vcpu, args->session,
+ ser, err);
+ }
mutex_unlock(&vcpu->mutex);
if (err) {
kho_unpreserve_free(ser);
@@ -259,16 +314,19 @@ static int kvm_vcpu_luo_retrieve(struct liveupdate_file_op_args *args)
}
vcpu = file->private_data;
+ kvm_caretaker_vcpu_pre_retrieve(vcpu, ser);
err = kvm_arch_vcpu_luo_retrieve(vcpu, ser);
if (err) {
fput(file);
goto err_free_ser;
}
+ kvm_caretaker_vcpu_retrieve(vcpu, ser);
args->file = file;
return 0;
err_free_ser:
+ kvm_caretaker_vcpu_finish(NULL, args->session, ser);
kvm_arch_vcpu_luo_finish(ser);
kho_restore_free(ser);
return err;
@@ -276,18 +334,24 @@ static int kvm_vcpu_luo_retrieve(struct liveupdate_file_op_args *args)
static void kvm_vcpu_luo_unpreserve(struct liveupdate_file_op_args *args)
{
+ struct kvm_vcpu *vcpu = args->file ? args->file->private_data : NULL;
struct kvm_vcpu_ser *ser;
if (WARN_ON_ONCE(!args->serialized_data))
return;
ser = phys_to_virt(args->serialized_data);
+
+ if (vcpu)
+ kvm_caretaker_vcpu_unpreserve(vcpu, args->session, ser);
+
kvm_arch_vcpu_luo_unpreserve(ser);
kho_unpreserve_free(ser);
}
static void kvm_vcpu_luo_finish(struct liveupdate_file_op_args *args)
{
+ struct kvm_vcpu *vcpu = args->file ? args->file->private_data : NULL;
struct kvm_vcpu_ser *ser;
if (args->retrieve_status < 0)
@@ -297,6 +361,8 @@ static void kvm_vcpu_luo_finish(struct liveupdate_file_op_args *args)
return;
ser = phys_to_virt(args->serialized_data);
+
+ kvm_caretaker_vcpu_finish(vcpu, args->session, ser);
kvm_arch_vcpu_luo_finish(ser);
kho_restore_free(ser);
}
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index ae91123e1822..4f661c5d2ee2 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -4521,6 +4521,11 @@ static long kvm_vcpu_ioctl(struct file *filp,
put_pid(oldpid);
}
+
+ if (!kvm_caretaker_vcpu_is_attached(vcpu)) {
+ r = -EBUSY;
+ break;
+ }
vcpu->wants_to_run = !READ_ONCE(vcpu->run->immediate_exit__unsafe);
r = kvm_arch_vcpu_ioctl_run(vcpu);
vcpu->wants_to_run = false;
@@ -4989,6 +4994,8 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
#endif
case KVM_CAP_VCPU_PRESERVE:
return IS_ENABLED(CONFIG_HAVE_KVM_ARCH_VCPU_PRESERVE);
+ case KVM_CAP_CARETAKER:
+ return IS_ENABLED(CONFIG_KVM_CARETAKER);
default:
break;
}
--
2.55.0.1082.g2b9226bbc0-goog