When a preserved vCPU executes inside the Caretaker during a host live update blackout window, standard kernel tracing and KVM exit counters are unavailable because the host kernel is being replaced via kexec. If a guest vCPU hits an unhandled exit and parks, or experiences unexpected exit patterns during the transition, the incoming kernel has no visibility into what occurred while the vCPU was detached.
Add optional cross-kexec Caretaker execution telemetry behind CONFIG_KVM_CARETAKER_DEBUG. During detached execution, the Caretaker records run counts, exit counts, stall counts, the last exit reason and guest PC, and stall diagnostics into a KHO-preserved telemetry buffer referenced from struct kvm_caretaker_cb_ser. When the incoming kernel reclaims the vCPU, it logs a summary to dmesg and exposes the preserved telemetry under /sys/kernel/debug/kvm/<vm>/vcpu<N>/caretaker_telemetry. Signed-off-by: Pasha Tatashin <[email protected]> --- include/linux/kvm_caretaker.h | 97 +++++++++++++++++++++++++++ virt/kvm/Kconfig | 10 +++ virt/kvm/Makefile.kvm | 1 + virt/kvm/caretaker_debug.c | 120 ++++++++++++++++++++++++++++++++++ virt/kvm/kvm_main.c | 1 + 5 files changed, 229 insertions(+) create mode 100644 virt/kvm/caretaker_debug.c diff --git a/include/linux/kvm_caretaker.h b/include/linux/kvm_caretaker.h index 0d4bdde7b1c3..f889d17212b0 100644 --- a/include/linux/kvm_caretaker.h +++ b/include/linux/kvm_caretaker.h @@ -108,11 +108,13 @@ struct kvm_caretaker_ops { * @cb: Pointer to KHO-preserved Caretaker control block (&struct kvm_caretaker_cb_ser). * @ops: Architecture operations vector (&struct kvm_caretaker_ops). * @arch_data: Architecture-specific runtime context passed to @ops callbacks. + * @telemetry: Pointer to KHO-preserved telemetry counters, or %NULL. */ struct kvm_caretaker_vcpu { struct kvm_caretaker_cb_ser *cb; const struct kvm_caretaker_ops *ops; void *arch_data; + struct kvm_caretaker_telemetry_ser *telemetry; }; struct kvm_kho_folios_ser *kvm_kho_folios_alloc(unsigned int max_folios); @@ -260,11 +262,106 @@ void kvm_caretaker_vcpu_finish(struct kvm_vcpu *vcpu, struct liveupdate_session *session, struct kvm_vcpu_ser *ser); +#ifdef CONFIG_KVM_CARETAKER_DEBUG +void kvm_caretaker_telemetry_init(struct kvm_caretaker_vcpu *cvcpu, + struct oncore_session *sess); +void kvm_caretaker_telemetry_report(struct kvm_vcpu *vcpu, + struct kvm_caretaker_cb_ser *cb); +void kvm_caretaker_telemetry_free(struct kvm_vcpu_ser *ser, bool is_incoming); +void kvm_caretaker_create_vcpu_debugfs(struct kvm_vcpu *vcpu, + struct dentry *debugfs_dentry); + +/** + * kvm_caretaker_telemetry_run - Record a guest entry in Caretaker telemetry + * @cvcpu: Common Caretaker vCPU descriptor. + */ +static __always_inline __caretaker_text void +kvm_caretaker_telemetry_run(struct kvm_caretaker_vcpu *cvcpu) +{ + if (cvcpu->telemetry) + cvcpu->telemetry->total_runs++; +} + +/** + * kvm_caretaker_telemetry_exit - Record a decoded VM exit in Caretaker telemetry + * @cvcpu: Common Caretaker vCPU descriptor. + * @exit: Decoded VM exit descriptor. + */ +static __always_inline __caretaker_text void +kvm_caretaker_telemetry_exit(struct kvm_caretaker_vcpu *cvcpu, + const struct kvm_caretaker_exit *exit) +{ + if (cvcpu->telemetry) { + cvcpu->telemetry->total_exits++; + cvcpu->telemetry->last_exit_reason = exit->raw_reason; + cvcpu->telemetry->last_exit_rip = exit->rip; + } +} + +/** + * kvm_caretaker_telemetry_stall - Record an unhandled stall exit in Caretaker telemetry + * @cvcpu: Common Caretaker vCPU descriptor. + * @reason: Raw hardware exit reason or entry failure code. + * @rip: Guest instruction pointer at stall (0 if entry failure). + */ +static __always_inline __caretaker_text void +kvm_caretaker_telemetry_stall(struct kvm_caretaker_vcpu *cvcpu, + u64 reason, u64 rip) +{ + if (cvcpu->telemetry) { + cvcpu->telemetry->stall_count++; + cvcpu->telemetry->last_exit_reason = reason; + cvcpu->telemetry->stall_exit_reason = reason; + if (rip) + cvcpu->telemetry->stall_exit_rip = rip; + } +} + +/** + * kvm_caretaker_telemetry_flush - Clean Caretaker telemetry counters to PoC + * @cvcpu: Common Caretaker vCPU descriptor. + */ +static __always_inline __caretaker_text void +kvm_caretaker_telemetry_flush(struct kvm_caretaker_vcpu *cvcpu) +{ + if (cvcpu->telemetry) + cpu_preserved_clean_sz(cvcpu->telemetry, + sizeof(*cvcpu->telemetry)); +} +#else +static inline void kvm_caretaker_telemetry_init(struct kvm_caretaker_vcpu *cvcpu, + struct oncore_session *sess) {} + +static inline void kvm_caretaker_telemetry_report(struct kvm_vcpu *vcpu, + struct kvm_caretaker_cb_ser *cb) {} + +static inline void kvm_caretaker_telemetry_free(struct kvm_vcpu_ser *ser, + bool is_incoming) {} + +static inline void kvm_caretaker_create_vcpu_debugfs(struct kvm_vcpu *vcpu, + struct dentry *debugfs_dentry) {} +static __always_inline void +kvm_caretaker_telemetry_run(struct kvm_caretaker_vcpu *cvcpu) {} +static __always_inline void +kvm_caretaker_telemetry_exit(struct kvm_caretaker_vcpu *cvcpu, + const struct kvm_caretaker_exit *exit) {} +static __always_inline void +kvm_caretaker_telemetry_stall(struct kvm_caretaker_vcpu *cvcpu, + u64 reason, u64 rip) {} +static __always_inline void +kvm_caretaker_telemetry_flush(struct kvm_caretaker_vcpu *cvcpu) {} +#endif + #else /* !CONFIG_KVM_CARETAKER */ #define __caretaker_text #define __caretaker_data +struct dentry; + +static inline void kvm_caretaker_create_vcpu_debugfs(struct kvm_vcpu *vcpu, + struct dentry *debugfs_dentry) {} + static inline bool kvm_caretaker_is_stopped(const struct kvm_caretaker_cb_ser *cb) { return true; diff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig index 165e0c85b8aa..bee73dd079c6 100644 --- a/virt/kvm/Kconfig +++ b/virt/kvm/Kconfig @@ -153,3 +153,13 @@ config KVM_CARETAKER without pausing the guest. If unsure, say N. + +config KVM_CARETAKER_DEBUG + bool "Caretaker execution telemetry and debug reporting" + depends on KVM_CARETAKER + default n + help + Enable Caretaker telemetry collection and reporting of VM exits, + runs, and stall diagnostics via debugfs and dmesg upon vCPU resume + following a host kexec live update or cancelled live update session. + If unsure, say N. diff --git a/virt/kvm/Makefile.kvm b/virt/kvm/Makefile.kvm index ae1fcf9861b7..157d0bf6132d 100644 --- a/virt/kvm/Makefile.kvm +++ b/virt/kvm/Makefile.kvm @@ -15,6 +15,7 @@ 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 +kvm-$(CONFIG_KVM_CARETAKER_DEBUG) += $(KVM)/caretaker_debug.o KASAN_SANITIZE_$(KVM)/caretaker.o := n KCSAN_SANITIZE_$(KVM)/caretaker.o := n UBSAN_SANITIZE_$(KVM)/caretaker.o := n diff --git a/virt/kvm/caretaker_debug.c b/virt/kvm/caretaker_debug.c new file mode 100644 index 000000000000..8905938bfec3 --- /dev/null +++ b/virt/kvm/caretaker_debug.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + * + * KVM Caretaker execution telemetry and debugfs reporting. + */ + +#include <linux/cpu_preserve.h> +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/kexec_handover.h> +#include <linux/kho/abi/kvm.h> +#include <linux/kvm_caretaker.h> +#include <linux/kvm_host.h> +#include <linux/oncore.h> +#include <linux/seq_file.h> + +void kvm_caretaker_telemetry_init(struct kvm_caretaker_vcpu *cvcpu, + struct oncore_session *sess) +{ + struct kvm_caretaker_telemetry_ser *tel; + + tel = kho_alloc_preserve(sizeof(*tel)); + if (IS_ERR_OR_NULL(tel)) + return; + + memset(tel, 0, sizeof(*tel)); + oncore_session_map_buffer(sess, tel, sizeof(*tel)); + cpu_preserved_clean_sz(tel, sizeof(*tel)); + cvcpu->telemetry = tel; + KHOSER_STORE_PTR(cvcpu->cb->telemetry, tel); +} + +void kvm_caretaker_telemetry_report(struct kvm_vcpu *vcpu, + struct kvm_caretaker_cb_ser *cb) +{ + struct kvm_caretaker_telemetry_ser *tel; + + if (!vcpu || !cb) + return; + + cpu_preserved_inval(cb); + tel = KHOSER_LOAD_PTR(cb->telemetry); + if (!tel) + return; + + cpu_preserved_inval(tel); + vcpu->caretaker.last_telemetry = *tel; + + pr_info("caretaker: vcpu %d resume: runs=%llu exits=%llu stalls=%llu last_exit=0x%llx (%llu) rip=0x%llx\n", + vcpu->vcpu_id, tel->total_runs, tel->total_exits, + tel->stall_count, tel->last_exit_reason, + tel->last_exit_reason, tel->last_exit_rip); + if (tel->stall_count) { + pr_info("caretaker: vcpu %d stall info: reason=0x%llx (%llu) rip=0x%llx\n", + vcpu->vcpu_id, tel->stall_exit_reason, + tel->stall_exit_reason, tel->stall_exit_rip); + } +} + +void kvm_caretaker_telemetry_free(struct kvm_vcpu_ser *ser, bool is_incoming) +{ + struct kvm_caretaker_telemetry_ser *tel; + struct kvm_caretaker_cb_ser *cb; + + if (!ser || !(ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER)) + return; + + cb = KHOSER_LOAD_PTR(ser->cb); + tel = cb ? KHOSER_LOAD_PTR(cb->telemetry) : NULL; + if (tel) { + cb->telemetry.phys = 0; + if (is_incoming) + kho_restore_free(tel); + else + kho_unpreserve_free(tel); + } +} + +static int caretaker_telemetry_show(struct seq_file *m, void *v) +{ + struct kvm_vcpu *vcpu = m->private; + const struct kvm_caretaker_telemetry_ser *t = + &vcpu->caretaker.last_telemetry; + struct kvm_caretaker_cb_ser *cb = READ_ONCE(vcpu->caretaker.cb); + + if (cb) { + struct kvm_caretaker_telemetry_ser *live; + + cpu_preserved_inval(cb); + live = KHOSER_LOAD_PTR(cb->telemetry); + if (live) { + cpu_preserved_inval(live); + t = live; + } + } + + seq_printf(m, "runs: %llu\n", t->total_runs); + seq_printf(m, "exits: %llu\n", t->total_exits); + seq_printf(m, "stalls: %llu\n", t->stall_count); + seq_printf(m, "last_exit_reason: 0x%llx (%llu)\n", + t->last_exit_reason, t->last_exit_reason); + seq_printf(m, "last_exit_rip: 0x%llx\n", t->last_exit_rip); + seq_printf(m, "stall_exit_reason: 0x%llx (%llu)\n", + t->stall_exit_reason, t->stall_exit_reason); + seq_printf(m, "stall_exit_rip: 0x%llx\n", t->stall_exit_rip); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(caretaker_telemetry); + +void kvm_caretaker_create_vcpu_debugfs(struct kvm_vcpu *vcpu, + struct dentry *debugfs_dentry) +{ + if (!debugfs_dentry) + return; + + debugfs_create_file("caretaker_telemetry", 0444, debugfs_dentry, vcpu, + &caretaker_telemetry_fops); +} diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 4f661c5d2ee2..84b4b96d7916 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4156,6 +4156,7 @@ static void kvm_create_vcpu_debugfs(struct kvm_vcpu *vcpu) debugfs_create_file("pid", 0444, debugfs_dentry, vcpu, &vcpu_get_pid_fops); + kvm_caretaker_create_vcpu_debugfs(vcpu, debugfs_dentry); kvm_arch_create_vcpu_debugfs(vcpu, debugfs_dentry); } #endif -- 2.55.0.1082.g2b9226bbc0-goog

