Integrate Caretaker vCPU preservation, re-attachment, and cancellation
with KVM LUO handlers and enable CONFIG_KVM_CARETAKER build rules.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 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


Reply via email to