Complete ARM64 Caretaker vCPU execution loop, detach-time state serialization, and LUO integration in arch/arm64/kvm/caretaker.c.
Signed-off-by: Pasha Tatashin <[email protected]> --- arch/arm64/kvm/Kconfig | 1 + arch/arm64/kvm/Makefile | 12 ++ arch/arm64/kvm/caretaker.c | 243 ++++++++++++++++++++++++++++++++++++- arch/arm64/kvm/kvm_luo.c | 103 ++++++++++++++++ 4 files changed, 357 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig index fd25b3c0d8ca..9c43010873a3 100644 --- a/arch/arm64/kvm/Kconfig +++ b/arch/arm64/kvm/Kconfig @@ -38,6 +38,7 @@ menuconfig KVM select GUEST_PERF_EVENTS if PERF_EVENTS select KVM_GUEST_MEMFD select HAVE_KVM_ARCH_VCPU_PRESERVE + select HAVE_KVM_ARCH_CARETAKER help Support hosting virtualized guest machines. diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile index d5e888d1bd50..58550a0a8160 100644 --- a/arch/arm64/kvm/Makefile +++ b/arch/arm64/kvm/Makefile @@ -12,6 +12,18 @@ obj-$(CONFIG_KVM) += hyp/ CFLAGS_sys_regs.o += -Wno-override-init CFLAGS_handle_exit.o += -Wno-override-init +KASAN_SANITIZE_caretaker.o := n +KCSAN_SANITIZE_caretaker.o := n +UBSAN_SANITIZE_caretaker.o := n +KCOV_INSTRUMENT_caretaker.o := n +CFLAGS_REMOVE_caretaker.o = $(CC_FLAGS_FTRACE) +CFLAGS_caretaker.o += $(call cc-option,-mbranch-protection=none) \ + -fno-stack-protector \ + $(call cc-option,-ftrivial-auto-var-init=uninitialized) \ + $(call cc-option,-fno-jump-tables) +AFLAGS_caretaker_vmenter.o += -D__KVM_VHE_HYPERVISOR__ + +kvm-$(CONFIG_KVM_CARETAKER) += caretaker.o caretaker_vmenter.o kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \ inject_fault.o va_layout.o handle_exit.o config.o \ diff --git a/arch/arm64/kvm/caretaker.c b/arch/arm64/kvm/caretaker.c index 6980c1f6b998..8cd73099a60a 100644 --- a/arch/arm64/kvm/caretaker.c +++ b/arch/arm64/kvm/caretaker.c @@ -193,6 +193,9 @@ int arm64_kvm_caretaker_preserve(struct kvm_vcpu *vcpu, cap->ctx.vtcr_el2 = mmu->vtcr; cap->ctx.vttbr_el2 = kvm_get_vttbr(mmu); + if (irqchip_in_kernel(vcpu->kvm) && !vgic_initialized(vcpu->kvm)) + kvm_vgic_map_resources(vcpu->kvm); + if (vgic_initialized(vcpu->kvm)) { int i; @@ -504,8 +507,8 @@ __caretaker_text static void caretaker_vgic_v3_save(struct vgic_v3_cpu_if *cpu_i used_lrs = min3(used_lrs, max_lrs, (unsigned int)VGIC_V3_MAX_LRS); for (i = 0; i < used_lrs; i++) { - cpu_if->vgic_lr[i] = __gic_v3_get_lr(i); - __gic_v3_set_lr(0, i); + cpu_if->vgic_lr[i] = caretaker_gic_v3_get_lr(i); + caretaker_gic_v3_set_lr(0, i); } cpu_if->vgic_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2); @@ -921,3 +924,239 @@ static void arm64_caretaker_sync_vcpu(struct kvm_vcpu *vcpu, kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu); } +static __caretaker_text void +arm64_caretaker_op_pre_run(void *data) +{ + struct caretaker_arm64_page *cap = data; + + local_daif_mask(); + arm64_caretaker_save_ptrauth(&cap->ptrauth_keys); + + /* Pre-job: load guest context */ + cpu_preserved_inval(cap); + + arm64_caretaker_load_sysregs(&cap->ctx.ctxt); + + if (cap->ctx.vgic_initialized) + caretaker_vgic_v3_restore(cap); + + write_sysreg(cap->ctx.cntvoff_el2, cntvoff_el2); + write_sysreg_el0(cap->ctx.cntv_cval_el0, SYS_CNTV_CVAL); + write_sysreg_el0(cap->ctx.cntv_ctl_el0, SYS_CNTV_CTL); + isb(); + + if (cap->ctx.vtcr_el2 && cap->ctx.vttbr_el2) { + write_sysreg(cap->ctx.vtcr_el2, vtcr_el2); + write_sysreg(cap->ctx.vttbr_el2, vttbr_el2); + asm(ALTERNATIVE("nop", "isb", ARM64_WORKAROUND_SPECULATIVE_AT)); + __tlbi(vmalle1); + asm volatile("ic iallu"); + dsb(nsh); + isb(); + } + + write_sysreg(CPACR_EL1_FPEN_EL0EN | CPACR_EL1_FPEN_EL1EN | + CPACR_EL1_ZEN_EL0EN | CPACR_EL1_ZEN_EL1EN, + cpacr_el1); + isb(); + + fpsimd_load_state(&cap->ctx.ctxt.fp_regs); + + gicv3_caretaker_enable_sgi(); + write_sysreg_s(ICC_CTLR_EL1_EOImode_drop, SYS_ICC_CTLR_EL1); + write_sysreg_s(ICC_SRE_EL1_SRE, SYS_ICC_SRE_EL1); + write_sysreg_s(0, SYS_ICC_BPR1_EL1); + gicv3_caretaker_clear_active_priorities(); + write_sysreg_s(ICC_PMR_EL1_MASK, SYS_ICC_PMR_EL1); + write_sysreg_s(ICC_IGRPEN1_EL1_MASK, SYS_ICC_IGRPEN1_EL1); + caretaker_gic_drain_iar(); + dsb(sy); + isb(); +} + +static __caretaker_text void +arm64_caretaker_op_post_run(void *data) +{ + struct cpu_preserved_stack_context *sctx; + struct caretaker_arm64_page *cap = data; + int cpu = cap->abi.cb.pcpu_id; + phys_addr_t pgd_pa; + + /* Post-run: restore host hypervisor mode then save guest context */ + write_sysreg_s(0, SYS_CNTHP_CTL_EL2); + caretaker_restore_host_el2(); + + fpsimd_save_state(&cap->ctx.ctxt.fp_regs); + + cap->ctx.cntv_cval_el0 = read_sysreg_el0(SYS_CNTV_CVAL); + cap->ctx.cntv_ctl_el0 = read_sysreg_el0(SYS_CNTV_CTL); + + if (cap->ctx.vgic_initialized) + caretaker_vgic_v3_save(&cap->ctx.vgic_v3); + + arm64_caretaker_save_sysregs(&cap->ctx.ctxt); + + sctx = cpu_preserved_get_stack_context(); + if (sctx && sctx->session_pgd_pa) + pgd_pa = sctx->session_pgd_pa; + else + pgd_pa = cpu_preserved_get_pgd(cpu); + + if (!pgd_pa) + pgd_pa = READ_ONCE(arm64_caretaker_pgd_pa); + + write_sysreg(0, ttbr0_el1); + if (pgd_pa && read_sysreg(ttbr1_el1) != pgd_pa) { + write_sysreg(pgd_pa, ttbr1_el1); + isb(); + arm64_flush_host_tlb_local(); + } + + cpu_preserved_clean(cap); + + if (smp_load_acquire(&cap->abi.cb.state) >= KVM_CARETAKER_STOPPING || + kvm_caretaker_should_exit(&cap->vcpu)) { + local_daif_mask(); + isb(); + + caretaker_gic_drain_iar(); + gicv3_caretaker_clear_active_priorities(); + write_sysreg_s(0, SYS_ICC_IGRPEN1_EL1); + write_sysreg_s(0, SYS_ICC_PMR_EL1); + write_sysreg_s(0, SYS_ICC_BPR1_EL1); + gicv3_caretaker_clear_sgi(); + dsb(sy); + isb(); + } + + arm64_caretaker_detach_serialize(cap); + + arm64_caretaker_restore_ptrauth(&cap->ptrauth_keys); + caretaker_restore_host_el2(); +} + +static struct kvm_caretaker_ops arm64_caretaker_ops __cpu_preserved_data = { + .enter_guest = arm64_caretaker_op_enter, + .decode_exit = arm64_caretaker_op_decode_exit, + .handle_arch_exit = arm64_caretaker_op_handle_exit, + .advance_rip = arm64_caretaker_op_advance_rip, + .arm_timer = arm64_caretaker_op_arm_timer, + .disarm_timer = arm64_caretaker_op_disarm_timer, + .pre_run = arm64_caretaker_op_pre_run, + .post_run = arm64_caretaker_op_post_run, +}; + +static __caretaker_text enum oncore_exit_reason +caretaker_arch_run_page(struct caretaker_arm64_page *cap, u64 deadline_ticks) +{ + enum oncore_exit_reason reason; + int cpu; + + if (!cap) + return ONCORE_EXIT_ERROR; + + cpu_preserved_inval(&cap->abi.cb); + cpu = cap->abi.cb.pcpu_id; + if (cpu < 0 || cpu >= ARRAY_SIZE(arm64_caretaker_faults)) + cpu = arm64_caretaker_get_pcpu(); + + cap->abi.cb.pcpu_id = cpu; + if (cmpxchg(&cap->abi.cb.state, KVM_CARETAKER_PAUSED, + KVM_CARETAKER_RUNNING) != KVM_CARETAKER_PAUSED || + cpu_preserved_should_exit(cpu)) { + arm64_caretaker_detach_serialize(cap); + smp_store_release(&cap->abi.cb.state, KVM_CARETAKER_STOPPED); + cpu_preserved_clean(&cap->abi.cb); + return ONCORE_EXIT_ATTACH_SIGNALED; + } + cpu_preserved_clean(&cap->abi.cb); + + cap->vcpu.ops = &arm64_caretaker_ops; + + reason = kvm_caretaker_vcpu_run(&cap->vcpu, deadline_ticks); + + cpu_preserved_inval(&cap->abi.cb); + if (reason == ONCORE_EXIT_ATTACH_SIGNALED || + reason == ONCORE_EXIT_ERROR || + cpu_preserved_should_exit(cpu) || + cmpxchg(&cap->abi.cb.state, KVM_CARETAKER_RUNNING, + KVM_CARETAKER_PAUSED) != KVM_CARETAKER_RUNNING) { + reason = ONCORE_EXIT_ATTACH_SIGNALED; + arm64_caretaker_detach_serialize(cap); + smp_store_release(&cap->abi.cb.state, KVM_CARETAKER_STOPPED); + cpu_preserved_clean(&cap->abi.cb); + } else { + cpu_preserved_clean(&cap->abi.cb); + } + + return reason; +} + +__caretaker_text enum oncore_exit_reason +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_preserve() + * installs it with oncore_job_set_data() before activating the job. + */ + if (!cb) + return ONCORE_EXIT_ERROR; + + return caretaker_arch_run_page(container_of(cb, + struct caretaker_arm64_page, + abi.cb), + deadline_ticks); +} + +void kvm_arch_vcpu_luo_pre_retrieve_caretaker(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser) +{ + if ((ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) && ser->cb.phys) { + struct kvm_caretaker_arch_ser *abi = phys_to_virt(ser->cb.phys); + int pcpu = abi->cb.pcpu_id; + + if (pcpu >= 0) { + kvm_caretaker_wait_for_attach(&abi->cb, pcpu); + cpu_preserved_inval(abi); + if (ser->arch_state.phys) { + struct kvm_vcpu_arch_ser *state = + phys_to_virt(ser->arch_state.phys); + + cpu_preserved_inval(state); + cpu_preserved_inval_sz(state, + struct_size(state, sysregs, + state->num_sysregs)); + } + } + } +} + +void kvm_arch_vcpu_luo_attach_caretaker(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser) +{ + if ((ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) && ser->cb.phys) { + struct kvm_caretaker_arch_ser *abi = phys_to_virt(ser->cb.phys); + + arm64_caretaker_sync_vcpu(vcpu, abi); + } + + kvm_caretaker_post_attach_vcpu(vcpu); +} + +void arm64_kvm_caretaker_unpreserve(struct kvm_vcpu_ser *ser) +{ + if (ser->cb.phys) { + kho_unpreserve_free(phys_to_virt(ser->cb.phys)); + ser->cb.phys = 0; + } +} + +void arm64_kvm_caretaker_finish(struct kvm_vcpu_ser *ser) +{ + if (ser->cb.phys) { + kho_restore_free(phys_to_virt(ser->cb.phys)); + ser->cb.phys = 0; + } +} diff --git a/arch/arm64/kvm/kvm_luo.c b/arch/arm64/kvm/kvm_luo.c index 59c56836cda8..ae8baef49f97 100644 --- a/arch/arm64/kvm/kvm_luo.c +++ b/arch/arm64/kvm/kvm_luo.c @@ -6,6 +6,7 @@ * ARM64 KVM LUO preservation and retrieval handlers. */ +#include <linux/cpu_preserve.h> #include <linux/kexec_handover.h> #include <linux/kho/abi/kvm_arm64.h> #include <linux/kvm_host.h> @@ -17,15 +18,100 @@ #include <kvm/arm_arch_timer.h> #include <kvm/arm_vgic.h> +#include "caretaker.h" #include "sys_regs.h" #include "vgic/vgic.h" +#ifdef CONFIG_KVM_CARETAKER +struct arm64_stage2_kho_walk { + struct kvm_kho_folios_ser *kp; + unsigned int count; +}; + +static int stage2_kho_visitor(const struct kvm_pgtable_visit_ctx *ctx, + enum kvm_pgtable_walk_flags visit) +{ + struct arm64_stage2_kho_walk *w = ctx->arg; + + if (kvm_pte_valid(ctx->old) && ctx->level != KVM_PGTABLE_LAST_LEVEL && + FIELD_GET(KVM_PTE_TYPE, ctx->old) == KVM_PTE_TYPE_TABLE) { + u64 phys = kvm_pte_to_phys(ctx->old); + struct page *p = phys_to_page(phys); + + if (!p) + return 0; + + if (!w->kp) { + w->count++; + return 0; + } + + if (w->kp->nr_folios >= w->count) + return -ENOSPC; + + if (kho_preserve_folio(page_folio(p))) + return -ENOMEM; + + w->kp->folios_pa[w->kp->nr_folios++] = phys; + } + return 0; +} +#endif + int kvm_arch_vm_luo_preserve(struct kvm *kvm, struct kvm_luo_ser *ser) { +#ifdef CONFIG_KVM_CARETAKER + struct kvm_s2_mmu *mmu = &kvm->arch.mmu; + struct arm64_stage2_kho_walk walk = {}; + struct kvm_pgtable_walker walker = { + .cb = stage2_kho_visitor, + .flags = KVM_PGTABLE_WALK_TABLE_PRE, + .arg = &walk, + }; + struct kvm_kho_folios_ser *kp; + int ret; +#endif + ser->type = kvm_phys_shift(&kvm->arch.mmu); if (kvm_vm_is_protected(kvm)) ser->type |= KVM_VM_TYPE_ARM_PROTECTED; +#ifdef CONFIG_KVM_CARETAKER + kvm->caretaker_vm = NULL; + + if (mmu->pgd_phys) + walk.count++; + if (mmu->pgt) { + ret = kvm_pgtable_walk(mmu->pgt, 0, BIT(mmu->pgt->ia_bits), &walker); + if (ret) + return ret; + } + + kp = kvm_kho_folios_alloc(walk.count); + if (IS_ERR(kp)) + return PTR_ERR(kp); + walk.kp = kp; + + if (mmu->pgd_phys) { + ret = kho_preserve_folio(page_folio(phys_to_page(mmu->pgd_phys))); + if (ret) { + kvm_kho_folios_unpreserve(kp); + return ret; + } + kp->folios_pa[kp->nr_folios++] = mmu->pgd_phys; + } + + if (mmu->pgt) { + ret = kvm_pgtable_walk(mmu->pgt, 0, BIT(mmu->pgt->ia_bits), &walker); + if (ret) { + kvm_kho_folios_unpreserve(kp); + return ret; + } + } + + kvm->kho_folios = kp; + KHOSER_STORE_PTR(ser->kho_folios, kp); +#endif return 0; } @@ -36,6 +122,10 @@ int kvm_arch_vm_luo_retrieve(struct kvm *kvm, struct kvm_luo_ser *ser) void kvm_arch_vm_luo_unpreserve(struct kvm *kvm, struct kvm_luo_ser *ser) { +#ifdef CONFIG_KVM_CARETAKER + if (kvm) + kvm->caretaker_vm = NULL; +#endif } void kvm_arch_vm_luo_finish(struct kvm_luo_ser *ser) @@ -119,6 +209,17 @@ int kvm_arch_vcpu_luo_preserve(struct kvm_vcpu *vcpu, struct kvm_vcpu_ser *ser) kfree(indices); KHOSER_STORE_PTR(ser->arch_state, state); + + if (ser->flags & KVM_VCPU_LUO_FLAG_CARETAKER) { + int ret = arm64_kvm_caretaker_preserve(vcpu, ser); + + if (ret) { + kho_unpreserve_free(state); + ser->arch_state.phys = 0; + return ret; + } + } + return 0; } @@ -186,6 +287,7 @@ int kvm_arch_vcpu_luo_retrieve(struct kvm_vcpu *vcpu, struct kvm_vcpu_ser *ser) void kvm_arch_vcpu_luo_unpreserve(struct kvm_vcpu_ser *ser) { + arm64_kvm_caretaker_unpreserve(ser); if (ser->arch_state.phys) { kho_unpreserve_free(phys_to_virt(ser->arch_state.phys)); ser->arch_state.phys = 0; @@ -194,6 +296,7 @@ void kvm_arch_vcpu_luo_unpreserve(struct kvm_vcpu_ser *ser) void kvm_arch_vcpu_luo_finish(struct kvm_vcpu_ser *ser) { + arm64_kvm_caretaker_finish(ser); if (ser->arch_state.phys) { kho_restore_free(phys_to_virt(ser->arch_state.phys)); ser->arch_state.phys = 0; -- 2.55.0.1082.g2b9226bbc0-goog

