Complete the x86 Caretaker vCPU execution loop, detach-time state serialization, and LUO integration in arch/x86/kvm/caretaker.c.
Signed-off-by: Pasha Tatashin <[email protected]> --- arch/x86/kvm/Kconfig | 1 + arch/x86/kvm/Makefile | 3 +- arch/x86/kvm/caretaker.c | 230 ++++++++++++++++++++++++++++++++++++++- arch/x86/kvm/caretaker.h | 12 +- arch/x86/kvm/kvm_luo.c | 30 +++++ 5 files changed, 270 insertions(+), 6 deletions(-) diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig index bae79fded6ff..2d1cb82ac3f1 100644 --- a/arch/x86/kvm/Kconfig +++ b/arch/x86/kvm/Kconfig @@ -22,6 +22,7 @@ config KVM_X86 select KVM_COMMON select KVM_ELIDE_TLB_FLUSH_IF_YOUNG select KVM_MMU_LOCKLESS_AGING + select HAVE_KVM_ARCH_CARETAKER select HAVE_KVM_IRQCHIP select HAVE_KVM_PFNCACHE select HAVE_KVM_DIRTY_RING_TSO diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile index 2cf0f1f2a59b..11e67c072258 100644 --- a/arch/x86/kvm/Makefile +++ b/arch/x86/kvm/Makefile @@ -7,7 +7,8 @@ include $(srctree)/virt/kvm/Makefile.kvm kvm-y += x86.o emulate.o irq.o lapic.o cpuid.o msrs.o pmu.o regs.o \ mtrr.o debugfs.o mmu/mmu.o mmu/page_track.o mmu/spte.o -kvm-$(CONFIG_LIVEUPDATE) += kvm_luo.o +kvm-$(CONFIG_LIVEUPDATE) += kvm_luo.o mmu/kho.o +kvm-$(CONFIG_KVM_CARETAKER) += caretaker.o kvm-$(CONFIG_X86_64) += mmu/tdp_iter.o mmu/tdp_mmu.o kvm-$(CONFIG_KVM_IOAPIC) += i8259.o i8254.o ioapic.o diff --git a/arch/x86/kvm/caretaker.c b/arch/x86/kvm/caretaker.c index dd2b2d582b69..c43913e94a9d 100644 --- a/arch/x86/kvm/caretaker.c +++ b/arch/x86/kvm/caretaker.c @@ -39,6 +39,7 @@ struct caretaker_x86_host_state { struct desc_ptr orig_idt; unsigned long orig_cr2; + unsigned long orig_cr8; unsigned long orig_fs_base; unsigned long orig_gs_base; unsigned long orig_kernel_gs_base; @@ -53,6 +54,8 @@ static void kvm_x86_caretaker_init_idt(gate_desc *idt); static void kvm_x86_caretaker_init_gdt_tss(struct desc_struct *gdt, struct x86_hw_tss *tss, unsigned long stack_top); +static enum oncore_exit_reason __cpu_preserved_text +kvm_x86_caretaker_run_page(struct caretaker_x86_page *cxp, u64 deadline_ticks); /* * A preserved page is handed over by physical address. The SME/SEV C-bit is @@ -75,21 +78,129 @@ cxp_from_cb(struct kvm_caretaker_cb_ser *cb) return container_of(cb, struct caretaker_x86_page, abi.cb); } -static const struct kvm_x86_caretaker_ops *kvm_x86_caretaker_ops __cpu_preserved_data; +static const struct kvm_x86_caretaker_ops *kvm_x86_caretaker_host_ops; +static const struct kvm_x86_caretaker_runtime_ops *kvm_x86_caretaker_ops __cpu_preserved_data; void kvm_x86_caretaker_register_ops(const struct kvm_x86_caretaker_ops *ops) { - WRITE_ONCE(kvm_x86_caretaker_ops, ops); + WRITE_ONCE(kvm_x86_caretaker_host_ops, ops); + WRITE_ONCE(kvm_x86_caretaker_ops, ops ? ops->runtime : NULL); + cpu_preserved_clean(&kvm_x86_caretaker_ops); } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_caretaker_register_ops); void kvm_x86_caretaker_unregister_ops(const struct kvm_x86_caretaker_ops *ops) { - if (kvm_x86_caretaker_ops == ops) + if (kvm_x86_caretaker_host_ops == ops) { + WRITE_ONCE(kvm_x86_caretaker_host_ops, NULL); WRITE_ONCE(kvm_x86_caretaker_ops, NULL); + cpu_preserved_clean(&kvm_x86_caretaker_ops); + } } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_caretaker_unregister_ops); +enum oncore_exit_reason __cpu_preserved_text +kvm_arch_vcpu_caretaker_run(void *data, u64 deadline_ticks) +{ + struct kvm_caretaker_cb_ser *cb = data; + + /* + * @data is always a struct kvm_caretaker_cb_ser: + * kvm_caretaker_vcpu_post_preserve() installs it with + * oncore_job_set_data() before activating the job. + */ + if (!cb) + return ONCORE_EXIT_ERROR; + + return kvm_x86_caretaker_run_page(cxp_from_cb(cb), deadline_ticks); +} + +static void kvm_arch_vcpu_caretaker_init(struct kvm_vcpu *vcpu) +{ + if (kvm_x86_caretaker_host_ops && kvm_x86_caretaker_host_ops->init) + kvm_x86_caretaker_host_ops->init(vcpu); +} + +int kvm_arch_vcpu_caretaker_preserve(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser, + struct kvm_vcpu_arch_ser *state, size_t size) +{ + struct kvm_caretaker_arch_ser *abi; + + kvm_arch_vcpu_caretaker_init(vcpu); + if (!vcpu->caretaker.cb) + return -ENOMEM; + + ser->cb.phys = virt_to_phys(vcpu->caretaker.cb); + abi = phys_to_virt(ser->cb.phys); + container_of(abi, struct caretaker_x86_page, abi)->arch_state = state; + cpu_preserved_map_buffer(state, size); + + return 0; +} + +static void kvm_x86_caretaker_signal_attach(struct kvm_vcpu *vcpu, u64 cb_pa) +{ + struct kvm_caretaker_arch_ser *abi; + struct kvm_caretaker_cb_ser *cb; + int target_pcpu; + u32 apic_id; + + if (!cb_pa) + return; + + abi = caretaker_pa_to_va(cb_pa); + cb = &abi->cb; + target_pcpu = cb->pcpu_id; + + if (cpu_is_preserved(target_pcpu)) { + apic_id = apic->cpu_present_to_apicid(target_pcpu); + if (apic_id == BAD_APICID) + apic_id = cpuid_to_apicid[target_pcpu]; + if (apic_id == BAD_APICID) + apic_id = abi->apic_id ? abi->apic_id : target_pcpu; + if (apic_id != BAD_APICID && apic_id != (u32)-1 && apic_id != 0) + per_cpu(x86_cpu_to_apicid, target_pcpu) = apic_id; + } + + kvm_caretaker_wait_for_attach(cb, target_pcpu); + if (vcpu) + vcpu->cpu = -1; +} + +static void kvm_x86_caretaker_attach(struct kvm_vcpu *vcpu, u64 cb_pa) +{ + const struct kvm_x86_caretaker_ops *ops = kvm_x86_caretaker_host_ops; + + if (cb_pa) { + struct kvm_caretaker_arch_ser *abi = caretaker_pa_to_va(cb_pa); + + vcpu_load(vcpu); + if (ops && ops->sync_vcpu) + ops->sync_vcpu(vcpu, abi); + vcpu_put(vcpu); + } +} + +void kvm_arch_vcpu_luo_pre_retrieve_caretaker(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser) +{ + if (!ser || !ser->cb.phys || !(ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER)) + return; + + kvm_x86_caretaker_signal_attach(vcpu, ser->cb.phys); +} + +void kvm_arch_vcpu_luo_attach_caretaker(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser) +{ + if (!ser || !ser->cb.phys || !(ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER)) + return; + + kvm_x86_caretaker_attach(vcpu, ser->cb.phys); + kvm_caretaker_post_attach_vcpu(vcpu); +} + static bool caretaker_x86_has_tsc_deadline __cpu_preserved_data; static u32 caretaker_x86_lapic_timer_period __cpu_preserved_data; static u32 caretaker_x86_tsc_khz __cpu_preserved_data; @@ -289,6 +400,8 @@ kvm_x86_caretaker_save_host_state(struct caretaker_x86_host_state *host, store_idt(&host->orig_idt); host->orig_cr2 = native_read_cr2(); + asm volatile("mov %%cr8, %0" : "=r" (host->orig_cr8)); + asm volatile("mov %0, %%cr8" : : "r" (0UL) : "memory"); /* * MSR_FS_BASE is in the guest-writable passthrough set below, so it * has to be saved here or a guest WRMSR to it survives the run and @@ -334,6 +447,7 @@ kvm_x86_caretaker_restore_host_state(const struct caretaker_x86_host_state *host * to be zero leaves the *guest's* value live in the host MSR. */ native_write_cr2(host->orig_cr2); + asm volatile("mov %0, %%cr8" : : "r" (host->orig_cr8) : "memory"); native_wrmsrq(MSR_FS_BASE, host->orig_fs_base); native_wrmsrq(MSR_GS_BASE, host->orig_gs_base); native_wrmsrq(MSR_KERNEL_GS_BASE, host->orig_kernel_gs_base); @@ -853,6 +967,87 @@ kvm_x86_caretaker_handle_exit(void *data, struct kvm_caretaker_exit *exit) } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_caretaker_handle_exit); +__caretaker_text static void +caretaker_restore_guest_fpu(struct caretaker_x86_page *cxp, + struct kvm_vcpu_arch_ser *state) +{ + union fpregs_state *xstate; + u64 rfbm; + + if (!cxp || !state || !cxp->save_guest_fpu) + return; + + xstate = (union fpregs_state *)state->xsave.region; + rfbm = state->xcrs.xcrs[0].value | XFEATURE_MASK_FPSSE; + + if (native_read_cr0() & X86_CR0_TS) + asm volatile("clts" : : : "memory"); + + asm volatile("1: xrstor64 %[buf]\n\t" + "2:\n\t" + _ASM_EXTABLE(1b, 2b) + : + : [buf] "m" (*xstate), + "a" ((u32)rfbm), "d" ((u32)(rfbm >> 32)) + : "memory"); +} + +STACK_FRAME_NON_STANDARD(kvm_x86_caretaker_run_page); + +static enum oncore_exit_reason __cpu_preserved_text +kvm_x86_caretaker_run_page(struct caretaker_x86_page *cxp, u64 deadline_ticks) +{ + const struct kvm_x86_caretaker_runtime_ops *ops = kvm_x86_caretaker_ops; + enum oncore_exit_reason reason = ONCORE_EXIT_QUANTUM_EXPIRED; + struct cpu_preserved_stack_context *sctx; + struct caretaker_x86_host_state host_state; + int pcpu; + + if (!cxp || !ops) + return ONCORE_EXIT_ERROR; + + sctx = cpu_preserved_get_stack_context(); + if (sctx && sctx->cpu >= 0 && sctx->cpu < CONFIG_NR_CPUS) + pcpu = sctx->cpu; + else + pcpu = cxp->abi.cb.pcpu_id; + cxp->abi.cb.pcpu_id = pcpu; + + if (cmpxchg(&cxp->abi.cb.state, KVM_CARETAKER_PAUSED, + KVM_CARETAKER_RUNNING) != KVM_CARETAKER_PAUSED || + kvm_caretaker_should_exit(&cxp->vcpu)) { + smp_store_release(&cxp->abi.cb.state, KVM_CARETAKER_STOPPED); + return ONCORE_EXIT_ATTACH_SIGNALED; + } + + /* Save host context, switch to Caretaker descriptors and CR3 */ + kvm_x86_caretaker_save_host_state(&host_state, cxp); + + if (cxp->arch_state) + caretaker_restore_guest_fpu(cxp, cxp->arch_state); + + cxp->vcpu.ops = &ops->common; + + reason = kvm_caretaker_vcpu_run(&cxp->vcpu, deadline_ticks); + + iret_to_self(); + + if (ops->detach_serialize && cxp->arch_state) + ops->detach_serialize(cxp, cxp->arch_state); + + kvm_x86_caretaker_restore_host_state(&host_state, pcpu); + + if (reason == ONCORE_EXIT_ATTACH_SIGNALED || + kvm_caretaker_should_exit(&cxp->vcpu) || + cmpxchg(&cxp->abi.cb.state, KVM_CARETAKER_RUNNING, + KVM_CARETAKER_PAUSED) != KVM_CARETAKER_RUNNING) { + reason = ONCORE_EXIT_ATTACH_SIGNALED; + smp_store_release(&cxp->abi.cb.state, KVM_CARETAKER_STOPPED); + } + + return reason; +} + __caretaker_text void kvm_x86_caretaker_arm_timer(u64 deadline_ticks) { if (!deadline_ticks) @@ -903,3 +1098,32 @@ __caretaker_text void kvm_x86_caretaker_disarm_timer(void) } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_caretaker_disarm_timer); +void kvm_arch_vcpu_caretaker_unpreserve(struct kvm_vcpu_ser *ser) +{ + if (ser->cb.phys) { + struct kvm_caretaker_arch_ser *abi = caretaker_pa_to_va(ser->cb.phys); + + kvm_x86_caretaker_unpreserve_pages(abi); + kho_unpreserve_free(abi); + ser->cb.phys = 0; + } +} + +void kvm_arch_vcpu_caretaker_finish(struct kvm_vcpu_ser *ser) +{ + if (ser->cb.phys) { + struct kvm_caretaker_arch_ser *abi = caretaker_pa_to_va(ser->cb.phys); + u32 i; + + for (i = 0; i < abi->nr_preserved_pages; i++) { + phys_addr_t pa = __sme_clr(abi->preserved_pages_pa[i]); + struct page *page = kho_restore_pages(pa, 1); + + if (page) + __free_pages(page, 0); + } + abi->nr_preserved_pages = 0; + kho_restore_free(abi); + ser->cb.phys = 0; + } +} diff --git a/arch/x86/kvm/caretaker.h b/arch/x86/kvm/caretaker.h index 9dd8815d414b..8c0fc336be6b 100644 --- a/arch/x86/kvm/caretaker.h +++ b/arch/x86/kvm/caretaker.h @@ -217,11 +217,19 @@ void kvm_x86_caretaker_unregister_ops(const struct kvm_x86_caretaker_ops *ops); struct kvm_vcpu_ser; #ifdef CONFIG_KVM_CARETAKER -void kvm_arch_vcpu_caretaker_init(struct kvm_vcpu *vcpu); +int kvm_arch_vcpu_caretaker_preserve(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser, + struct kvm_vcpu_arch_ser *state, size_t size); void kvm_arch_vcpu_caretaker_unpreserve(struct kvm_vcpu_ser *ser); void kvm_arch_vcpu_caretaker_finish(struct kvm_vcpu_ser *ser); #else -static inline void kvm_arch_vcpu_caretaker_init(struct kvm_vcpu *vcpu) {} +static inline int kvm_arch_vcpu_caretaker_preserve(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser, + struct kvm_vcpu_arch_ser *state, + size_t size) +{ + return 0; +} static inline void kvm_arch_vcpu_caretaker_unpreserve(struct kvm_vcpu_ser *ser) {} static inline void kvm_arch_vcpu_caretaker_finish(struct kvm_vcpu_ser *ser) {} #endif diff --git a/arch/x86/kvm/kvm_luo.c b/arch/x86/kvm/kvm_luo.c index 899b193812bb..ff6acf3acb52 100644 --- a/arch/x86/kvm/kvm_luo.c +++ b/arch/x86/kvm/kvm_luo.c @@ -20,9 +20,11 @@ #include <linux/mem_encrypt.h> #include <asm/virt.h> +#include "caretaker.h" #include "cpuid.h" #include "fpu.h" #include "lapic.h" +#include "mmu.h" #include "msrs.h" #include "pmu.h" #include "regs.h" @@ -30,7 +32,23 @@ int kvm_arch_vm_luo_preserve(struct kvm *kvm, struct kvm_luo_ser *ser) { + int ret; + ser->type = kvm->arch.vm_type; + + /* + * Shadow/TDP page tables are a VM-wide resource: an orphaned vCPU keeps + * running the guest out of them while the VM is detached, so they must + * survive the kexec. Preserve them once here rather than once per vCPU + * from the caretaker init hook -- the walk is O(size of the guest's page + * tables) and holds mmu_lock for write, so repeating it per vCPU is both + * redundant and a scalability problem on large guests. + */ + ret = kvm_mmu_preserve_kho(kvm); + if (ret) + return ret; + + KHOSER_STORE_PTR(ser->kho_folios, kvm->kho_folios); return 0; } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_arch_vm_luo_preserve); @@ -185,6 +203,16 @@ int kvm_arch_vcpu_luo_preserve(struct kvm_vcpu *vcpu, struct kvm_vcpu_ser *ser) vcpu_put(vcpu); KHOSER_STORE_PTR(ser->arch_state, state); + + if (ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) { + int err = kvm_arch_vcpu_caretaker_preserve(vcpu, ser, state, size); + + if (err) { + kho_unpreserve_free(state); + return err; + } + } + return 0; } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_arch_vcpu_luo_preserve); @@ -315,6 +343,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_arch_vcpu_luo_retrieve); void kvm_arch_vcpu_luo_unpreserve(struct kvm_vcpu_ser *ser) { + kvm_arch_vcpu_caretaker_unpreserve(ser); if (ser->arch_state.phys) { struct kvm_vcpu_arch_ser *state = phys_to_virt(__sme_clr(ser->arch_state.phys)); @@ -327,6 +356,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_arch_vcpu_luo_unpreserve); void kvm_arch_vcpu_luo_finish(struct kvm_vcpu_ser *ser) { + kvm_arch_vcpu_caretaker_finish(ser); if (ser->arch_state.phys) { struct kvm_vcpu_arch_ser *state = phys_to_virt(__sme_clr(ser->arch_state.phys)); -- 2.55.0.1082.g2b9226bbc0-goog

