Implement Caretaker guest memory mapping, control block transitions, and architecture ops registration in virt/kvm/caretaker.c.
Signed-off-by: Pasha Tatashin <[email protected]> --- virt/kvm/caretaker.c | 452 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 virt/kvm/caretaker.c diff --git a/virt/kvm/caretaker.c b/virt/kvm/caretaker.c new file mode 100644 index 000000000000..b4a203562727 --- /dev/null +++ b/virt/kvm/caretaker.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + * + * Core KVM Caretaker common execution engine and lifecycle management. + */ + +/** + * DOC: KVM Caretaker Architecture and Lifecycle + * + * Overview: Orphaned Virtual Machines During Live Update + * ------------------------------------------------------ + * During a host kernel Live Update (kexec), the userspace VMM and the outgoing + * Linux kernel tear down and vanish while the incoming kernel boots and a new + * userspace VMM process adopts the preserved state. Throughout this handover + * window, preserved virtual machines are temporarily "orphaned" from any full + * host operating system or userspace VMM. + * + * Instead of freezing all guest vCPUs in RAM for the duration of kexec, the KVM + * Caretaker framework keeps preserved guest vCPUs actively executing on + * preserved physical CPUs (CONFIG_LIVEUPDATE_CPU) scheduled by the On-Core + * runtime (CONFIG_LIVEUPDATE_ONCORE). When a Live Update session has preserved + * physical CPUs attached, Caretaker detaches each vCPU into a self-contained + * KHO-preserved runtime page and schedules it on the preserved cores + * (%KVM_VCPU_LUO_FLAG_CARETAKER). If a session has zero preserved physical + * CPUs, Caretaker transparently falls back to RAM-only vCPU preservation. + * + * System Layering + * --------------- + * Orphaned VM execution is structured across four layers: + * + * 1. Physical CPU Preservation (kernel/liveupdate/cpu_preserve.c): + * Isolates physical CPU cores from Linux hotplug teardown on dedicated + * KHO-preserved stacks (&struct cpu_preserved_stack_context), switches them + * to isolated page tables (&struct cpu_preserved_as) mapping only + * .text.cpu_preserved and .data.cpu_preserved outside KHO Scratch memory, + * and provides cache-coherency and cross-CPU wake primitives. + * + * 2. On-Core Session & Scheduler (kernel/liveupdate/oncore.c): + * Groups preserved physical CPUs and an isolated address space per + * &struct liveupdate_session, and runs a lockless round-robin FIFO + * scheduler (oncore_cpu_schedule_loop()) that time-slices M workload jobs + * across N preserved physical CPUs (supporting both 1:1 dedicated pinning + * and M > N oversubscription). + * + * 3. Common KVM Caretaker Engine: + * Implements the architecture-neutral vCPU quantum loop + * (kvm_caretaker_vcpu_run()), normalized VM-exit dispatch + * (kvm_caretaker_dispatch_exit()), guest idle handling (HLT/PAUSE/WFI), + * unhandled-exit stall parking (%ONCORE_EXIT_STALL), lockless control-block + * state transitions (&struct kvm_caretaker_cb_ser), and KVM LUO lifecycle + * hooks. + * + * 4. Architecture & Vendor Backends (arch/x86/kvm/ and arch/arm64/kvm/): + * Implement &struct kvm_caretaker_ops (guest entry/exit assembly, hardware + * timer programming, and VMCS/VMCB/EL2 context management) and self-contained + * on-core emulation for performance-critical exits (early UART console, + * CPUID, MSRs, RDTSC, GICv3 CPU interface / SGI delivery, and architectural + * timers) while keeping guest EPT/NPT/Stage-2 page tables live in hardware. + * + * Orphaned vCPU Lifecycle and State Machine + * ----------------------------------------- + * Each vCPU's KHO-preserved control block (&struct kvm_caretaker_cb_ser) + * transitions through four phases across a live update: + * + * 1. Pre-Preserve & Activation (Outgoing Kernel -- LUO Prepare/Freeze): + * - kvm_caretaker_vcpu_pre_preserve() submits an &struct oncore_job for + * kvm_arch_vcpu_caretaker_run() to the session's least-loaded preserved + * physical CPU and sets %KVM_VCPU_LUO_FLAG_CARETAKER. + * - kvm_arch_vcpu_luo_preserve() allocates the architecture Caretaker page, + * preserves stage-2/TDP MMU page tables, captures guest register and + * virtualization hardware state, and calls kvm_caretaker_init_common_vcpu() + * to map the runtime page into the session's isolated PGD and initialize + * @cb->state to %KVM_CARETAKER_PAUSED. + * - kvm_caretaker_vcpu_post_preserve() binds @cb to the job, cleans @cb to + * PoC, and calls oncore_session_activate_job() to enqueue the job and kick + * the assigned preserved physical CPU. + * + * 2. Orphaned On-Core Execution (Across Kexec Handover): + * - The preserved CPU invokes kvm_arch_vcpu_caretaker_run(), which + * atomically transitions @cb->state from %KVM_CARETAKER_PAUSED to + * %KVM_CARETAKER_RUNNING and calls kvm_caretaker_vcpu_run(). + * - kvm_caretaker_vcpu_run() arms the preemption timer for @deadline_ticks + * and repeatedly enters the guest (ops->enter_guest()), decodes exits + * (ops->decode_exit()), and dispatches on-core handlers. + * - When the time quantum expires (%ONCORE_EXIT_QUANTUM_EXPIRED), the guest + * executes HLT/WFI (%ONCORE_EXIT_YIELD_IDLE), or an exit cannot be + * emulated on-core (%ONCORE_EXIT_STALL, leaving RIP/PC on the faulting + * instruction), the backend serializes live hardware state into + * @ser->arch_state, transitions @cb->state back to %KVM_CARETAKER_PAUSED, + * and returns to the On-Core scheduler. + * + * 3. Re-Attachment & Adoption (Incoming Kernel -- LUO Retrieve/Finish): + * - During LUO retrieve, kvm_caretaker_vcpu_pre_retrieve() invokes + * kvm_arch_vcpu_luo_pre_retrieve_caretaker(), which calls + * kvm_caretaker_wait_for_attach(). + * - Fast path: if @cb->state is %KVM_CARETAKER_PAUSED, a single cmpxchg() + * transitions it to %KVM_CARETAKER_STOPPED immediately. + * - Slow path: if @cb->state is %KVM_CARETAKER_RUNNING, it transitions to + * %KVM_CARETAKER_STOPPING and sends an IPI kick to force a VM exit. The + * preserved CPU observes kvm_caretaker_should_exit(), serializes final + * guest state into @ser->arch_state, publishes %KVM_CARETAKER_STOPPED, and + * exits with %ONCORE_EXIT_ATTACH_SIGNALED. + * - kvm_arch_vcpu_luo_retrieve() and kvm_caretaker_vcpu_retrieve() then + * load the updated state into the incoming &struct kvm_vcpu and resume + * normal KVM execution. + * + * 4. Cancellation / Rollback (Outgoing Kernel -- LUO Unpreserve): + * - If live update is cancelled before kexec, kvm_caretaker_vcpu_unpreserve() + * executes the same attach handshake to stop the vCPU on the preserved + * core, synchronizes any guest state changes back into the outgoing + * &struct kvm_vcpu, and cancels the job via oncore_session_cancel_job(). + */ + +#include <linux/cpu_preserve.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kho/abi/kvm.h> +#include <linux/kvm_caretaker.h> +#include <linux/kvm_host.h> +#include <linux/liveupdate.h> +#include <linux/objtool.h> +#include <linux/oncore.h> + +/** + * kvm_caretaker_vcpu_is_attached - Check whether a vCPU has attached back to host KVM + * @vcpu: Target KVM vCPU. + * + * Return: %true if @vcpu is not running under Caretaker (control block is %NULL + * or in %KVM_CARETAKER_STOPPED), %false if still owned by Caretaker. + */ +bool kvm_caretaker_vcpu_is_attached(struct kvm_vcpu *vcpu) +{ + return kvm_caretaker_is_stopped(vcpu->caretaker.cb); +} + +/** + * kvm_caretaker_init_common_vcpu - Initialize common Caretaker vCPU runtime state + * @cvcpu: Common Caretaker vCPU descriptor to initialize. + * @cb: KHO-preserved Caretaker control block embedded in the arch page. + * @vcpu: Host KVM vCPU being preserved. + * @runtime_va: Virtual address of the architecture Caretaker runtime page. + * @runtime_size: Size in bytes of @runtime_va. + * @ops: Architecture operations table (&struct kvm_caretaker_ops). + * @arch_data: Architecture context pointer passed to @ops callbacks. + * + * Initializes @cb in %KVM_CARETAKER_PAUSED state with the preserved physical + * CPU ID assigned to @vcpu->caretaker.job, maps the runtime buffer into the + * On-Core session's isolated page tables, and allocates KHO telemetry state. + */ +void kvm_caretaker_init_common_vcpu(struct kvm_caretaker_vcpu *cvcpu, + struct kvm_caretaker_cb_ser *cb, + struct kvm_vcpu *vcpu, + void *runtime_va, + size_t runtime_size, + const struct kvm_caretaker_ops *ops, + void *arch_data) +{ + struct oncore_session *sess = oncore_job_session(vcpu->caretaker.job); + + cb->state = KVM_CARETAKER_PAUSED; + cb->pcpu_id = oncore_job_cpu(vcpu->caretaker.job); + cb->vcpu_id = vcpu->vcpu_id; + cb->reserved = 0; + cb->telemetry.phys = 0; + + cvcpu->cb = cb; + cvcpu->ops = ops; + cvcpu->arch_data = arch_data ? arch_data : cvcpu; + cvcpu->telemetry = NULL; + + vcpu->caretaker.cb = cb; + + oncore_session_map_buffer(sess, runtime_va, runtime_size); + kvm_caretaker_telemetry_init(cvcpu, sess); +} + +/** + * kvm_caretaker_should_exit - Check whether the Caretaker vCPU loop must exit for attachment + * @cvcpu: Common Caretaker vCPU descriptor. + * + * Invalidates cache lines for @cvcpu->cb and checks whether the host kernel has + * requested attachment (%KVM_CARETAKER_STOPPING / %KVM_CARETAKER_STOPPED) or + * whether the underlying preserved physical CPU is exiting its workload loop. + * + * Return: %true if the vCPU must immediately exit guest execution and serialize + * its state for host attachment, %false otherwise. + */ +bool __cpu_preserved_text +kvm_caretaker_should_exit(struct kvm_caretaker_vcpu *cvcpu) +{ + struct cpu_preserved_stack_context *sctx; + u32 st; + + cpu_preserved_inval(cvcpu->cb); + + st = READ_ONCE(cvcpu->cb->state); + if (st != KVM_CARETAKER_PAUSED && st != KVM_CARETAKER_RUNNING) + return true; + + sctx = cpu_preserved_get_stack_context(); + return cpu_preserved_should_exit(sctx ? sctx->cpu : cvcpu->cb->pcpu_id); +} + +static bool __cpu_preserved_text +kvm_caretaker_dispatch_exit(struct kvm_caretaker_vcpu *cvcpu, + struct kvm_caretaker_exit *exit) +{ + switch (exit->type) { + case KVM_CARETAKER_EXIT_IDLE: + cpu_relax(); + exit->rip += exit->insn_len; + return false; + + case KVM_CARETAKER_EXIT_PREEMPT_TIMER: + case KVM_CARETAKER_EXIT_UNHANDLED: + case KVM_CARETAKER_EXIT_UNKNOWN: + return false; + + case KVM_CARETAKER_EXIT_CONSOLE: + case KVM_CARETAKER_EXIT_CROSS_VCPU: + case KVM_CARETAKER_EXIT_INSN_STEP: + case KVM_CARETAKER_EXIT_ARCH: + default: + if (cvcpu->ops->handle_arch_exit) + return cvcpu->ops->handle_arch_exit(cvcpu->arch_data, + exit); + return false; + } +} +STACK_FRAME_NON_STANDARD(kvm_caretaker_dispatch_exit); + +static int __cpu_preserved_text +kvm_caretaker_enter_guest(struct kvm_caretaker_vcpu *cvcpu) +{ + void *arch_data = cvcpu->arch_data; + int ret; + + ret = cvcpu->ops->enter_guest(arch_data); + kvm_caretaker_telemetry_run(cvcpu); + if (ret && !kvm_caretaker_should_exit(cvcpu)) { + cpu_relax(); + ret = cvcpu->ops->enter_guest(arch_data); + kvm_caretaker_telemetry_run(cvcpu); + } + if (ret) + kvm_caretaker_telemetry_stall(cvcpu, (u32)ret, 0); + + return ret; +} +STACK_FRAME_NON_STANDARD(kvm_caretaker_enter_guest); + +static bool __cpu_preserved_text +kvm_caretaker_handle_exit(struct kvm_caretaker_vcpu *cvcpu, + enum oncore_exit_reason *reason) +{ + const struct kvm_caretaker_ops *ops = cvcpu->ops; + struct kvm_caretaker_exit exit __uninitialized; + void *arch_data = cvcpu->arch_data; + bool handled; + + cpu_preserved_memset(&exit, 0, sizeof(exit)); + if (ops->decode_exit) + ops->decode_exit(arch_data, &exit); + + kvm_caretaker_telemetry_exit(cvcpu, &exit); + if (kvm_caretaker_should_exit(cvcpu)) + return false; + + handled = kvm_caretaker_dispatch_exit(cvcpu, &exit); + if (ops->advance_rip) + ops->advance_rip(arch_data, exit.rip); + if (handled) + return true; + + /* + * A preemption-timer exit is the normal end of a time slice, so leave + * @reason alone for it. For anything else nothing emulated the exit + * and RIP was left on the faulting instruction, so re-entering the + * guest would take the exact same exit again. Tell the scheduler the + * job is stuck rather than letting it look like an expired time slice, + * so that it backs off instead of spinning on VM entry/exit until the + * incoming kernel reclaims the vCPU. + */ + if (exit.type == KVM_CARETAKER_EXIT_IDLE) { + *reason = ONCORE_EXIT_YIELD_IDLE; + } else if (exit.type != KVM_CARETAKER_EXIT_PREEMPT_TIMER) { + *reason = ONCORE_EXIT_STALL; + kvm_caretaker_telemetry_stall(cvcpu, exit.raw_reason, exit.rip); + } + + return false; +} +STACK_FRAME_NON_STANDARD(kvm_caretaker_handle_exit); + +/** + * kvm_caretaker_vcpu_run - Common hardware vCPU execution loop for Caretaker + * @cvcpu: Common Caretaker vCPU descriptor. + * @deadline_ticks: Hardware counter deadline for the current scheduling quantum. + * + * Arms the hardware preemption timer for @deadline_ticks and repeatedly enters + * the guest via @cvcpu->ops->enter_guest(), decodes VM exits, and dispatches + * on-core exit handlers until the time slice expires, the guest yields on + * HLT/WFI, the incoming kernel signals attachment, or an unhandled exit stalls + * the vCPU. + * + * Context: Preserved physical CPU (__cpu_preserved_text) with IRQs disabled. + * Return: &enum oncore_exit_reason indicating why the vCPU left the loop. + */ +enum oncore_exit_reason __cpu_preserved_text +kvm_caretaker_vcpu_run(struct kvm_caretaker_vcpu *cvcpu, u64 deadline_ticks) +{ + struct cpu_preserved_stack_context *sctx = cpu_preserved_get_stack_context(); + enum oncore_exit_reason reason = ONCORE_EXIT_QUANTUM_EXPIRED; + const struct kvm_caretaker_ops *ops = cvcpu->ops; + void *arch_data = cvcpu->arch_data; + + if (sctx) + cvcpu->cb->pcpu_id = sctx->cpu; + + if (kvm_caretaker_should_exit(cvcpu)) + return ONCORE_EXIT_ATTACH_SIGNALED; + + if (ops->pre_run) + ops->pre_run(arch_data); + + if (ops->arm_timer) + ops->arm_timer(arch_data, deadline_ticks); + + while (!kvm_caretaker_should_exit(cvcpu)) { + if (arch_oncore_read_counter() >= deadline_ticks) + break; + + if (kvm_caretaker_enter_guest(cvcpu)) { + reason = ONCORE_EXIT_ERROR; + break; + } + + if (!kvm_caretaker_handle_exit(cvcpu, &reason)) + break; + } + + if (ops->disarm_timer) + ops->disarm_timer(arch_data); + + if (ops->post_run) + ops->post_run(arch_data); + + kvm_caretaker_telemetry_flush(cvcpu); + + if (kvm_caretaker_should_exit(cvcpu)) + return ONCORE_EXIT_ATTACH_SIGNALED; + + return reason; +} +STACK_FRAME_NON_STANDARD(kvm_caretaker_vcpu_run); + +#define KVM_CARETAKER_ATTACH_TIMEOUT_US 2000000 +#define KVM_CARETAKER_ATTACH_STEP_US 10 +#define KVM_CARETAKER_ATTACH_KICK_STEPS 100 + +static bool kvm_caretaker_try_stop(struct kvm_caretaker_cb_ser *cb) +{ + cpu_preserved_inval(cb); + + if (READ_ONCE(cb->state) == KVM_CARETAKER_STOPPED) + return true; + + if (cmpxchg(&cb->state, KVM_CARETAKER_PAUSED, + KVM_CARETAKER_STOPPED) == KVM_CARETAKER_PAUSED) { + cpu_preserved_clean(cb); + return true; + } + + return false; +} + +/** + * kvm_caretaker_wait_for_attach - Stop a Caretaker vCPU and wait for state serialization + * @cb: KHO-preserved Caretaker control block. + * @pcpu: Logical ID of the preserved physical CPU running the vCPU. + * + * Synchronizes with the preserved physical CPU executing @cb so that the vCPU + * exits guest mode, serializes its final architectural state into KHO memory, + * and reaches %KVM_CARETAKER_STOPPED before the host kernel reads back the + * serialized state. + * + * Return: 0 on success, or -ETIMEDOUT if the preserved CPU failed to stop. + */ +int kvm_caretaker_wait_for_attach(struct kvm_caretaker_cb_ser *cb, int pcpu) +{ + int i; + + if (!cb || kvm_caretaker_try_stop(cb)) + return 0; + + if (!cpu_is_preserved(pcpu)) { + WRITE_ONCE(cb->state, KVM_CARETAKER_STOPPED); + cpu_preserved_clean(cb); + /* Ensure state update is visible before returning to caller */ + smp_wmb(); + return 0; + } + + /* + * Slow path: the vCPU is actively executing a quantum on the preserved + * physical CPU (%KVM_CARETAKER_RUNNING). Request a stop by moving it + * to %KVM_CARETAKER_STOPPING, send an IPI kick to force a VM exit, and + * spin until the preserved CPU finishes detach_serialize() and publishes + * %KVM_CARETAKER_STOPPED. + */ + cmpxchg(&cb->state, KVM_CARETAKER_RUNNING, KVM_CARETAKER_STOPPING); + cpu_preserved_clean(cb); + /* Order state update before kicking and polling the preserved CPU */ + smp_mb(); + + for (i = 0; i < KVM_CARETAKER_ATTACH_TIMEOUT_US / KVM_CARETAKER_ATTACH_STEP_US; i++) { + if (i % KVM_CARETAKER_ATTACH_KICK_STEPS == 0) + arch_cpu_preserved_kick(pcpu); + if (kvm_caretaker_try_stop(cb)) + return 0; + udelay(KVM_CARETAKER_ATTACH_STEP_US); + } + + pr_warn("kvm: caretaker attach handshake timed out for pCPU %d\n", pcpu); + return -ETIMEDOUT; +} +STACK_FRAME_NON_STANDARD(kvm_caretaker_wait_for_attach); + +/** + * kvm_caretaker_post_attach_vcpu - Finalize host vCPU state after Caretaker attachment + * @vcpu: KVM vCPU that has just re-attached from Caretaker. + * + * Resets @vcpu->mode and @vcpu->cpu, reports Caretaker execution telemetry to + * the kernel log and vCPU debugfs snapshot, marks the control block stopped, + * and clears @vcpu->caretaker.cb. + */ +void kvm_caretaker_post_attach_vcpu(struct kvm_vcpu *vcpu) +{ + /* Ensure vCPU mode update is globally visible before clearing cpu */ + smp_store_mb(vcpu->mode, EXITING_GUEST_MODE); + vcpu->cpu = -1; + + if (vcpu->caretaker.cb) { + kvm_caretaker_telemetry_report(vcpu, vcpu->caretaker.cb); + kvm_caretaker_stop(vcpu->caretaker.cb); + vcpu->caretaker.cb = NULL; + } +} + -- 2.55.0.1082.g2b9226bbc0-goog

