Implement standalone Caretaker EL2 exception decoding, system register
trap handling, and stage-2 non-RAM IPA yield in arch/arm64/kvm/caretaker.c.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 arch/arm64/kvm/caretaker.c | 408 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 405 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/kvm/caretaker.c b/arch/arm64/kvm/caretaker.c
index 65c11fe8c2fa..6980c1f6b998 100644
--- a/arch/arm64/kvm/caretaker.c
+++ b/arch/arm64/kvm/caretaker.c
@@ -60,10 +60,8 @@ arm64_caretaker_handle_invalid(u64 elr, u64 esr, u64 far)
        }
 
        while (1) {
-               if (cpu_preserved_should_exit(cpu)) {
-                       cpu_preserved_set_dead(cpu);
+               if (cpu_preserved_should_exit(cpu))
                        arch_cpu_preserved_park_finish(cpu);
-               }
                arch_cpu_preserved_park_wait();
        }
 }
@@ -519,3 +517,407 @@ __caretaker_text static void 
caretaker_vgic_v3_save(struct vgic_v3_cpu_if *cpu_i
        isb();
 }
 
+__caretaker_text static void
+caretaker_arm64_inject_sgi(struct caretaker_arm64_page *target_cap, u32 sgi)
+{
+       int slot = -1;
+       int i;
+
+       if (!target_cap)
+               return;
+
+       cpu_preserved_inval(target_cap);
+
+       /* Check if the SGI is already pending or active */
+       for (i = 0; i < target_cap->ctx.vgic_v3.used_lrs; i++) {
+               u64 lr = target_cap->ctx.vgic_v3.vgic_lr[i];
+
+               if ((lr & ICH_LR_VIRTUAL_ID_MASK) == (sgi & 0xf) && (lr & 
ICH_LR_STATE))
+                       return;
+               if ((lr & ICH_LR_STATE) == 0 && slot < 0)
+                       slot = i;
+       }
+
+       if (slot < 0 && target_cap->ctx.vgic_v3.used_lrs < VGIC_V3_MAX_LRS) {
+               slot = target_cap->ctx.vgic_v3.used_lrs;
+               target_cap->ctx.vgic_v3.used_lrs++;
+       }
+
+       if (slot >= 0) {
+               target_cap->ctx.vgic_v3.vgic_lr[slot] =
+                       ((u64)sgi & 0xf) |
+                       ICH_LR_PENDING_BIT |
+                       ICH_LR_GROUP |
+                       (GIC_DEFAULT_SGI_PRIO << ICH_LR_PRIORITY_SHIFT);
+       } else {
+               target_cap->ctx.pending_sgis |= BIT(sgi & 0xf);
+       }
+
+       cpu_preserved_clean(target_cap);
+
+       /* If target vCPU is running on a remote physical CPU, kick it */
+       if (target_cap->abi.cb.pcpu_id >= 0 &&
+           target_cap->abi.cb.pcpu_id != arm64_caretaker_get_pcpu()) {
+               arch_cpu_preserved_kick(target_cap->abi.cb.pcpu_id);
+       }
+}
+
+__caretaker_text static void
+caretaker_arm64_handle_sgi(struct caretaker_arm64_page *src_cap, u64 reg)
+{
+       struct caretaker_arm64_page *target;
+       u32 sgi = FIELD_GET(ICC_SGI1R_SGI_ID_MASK, reg);
+       unsigned int i;
+
+       if (!src_cap || !src_cap->next_vcpu)
+               return;
+
+       if (reg & BIT_ULL(ICC_SGI1R_IRQ_ROUTING_MODE_BIT)) {
+               /* Broadcast to all other vCPUs */
+               for (target = src_cap->next_vcpu;
+                    target && target != src_cap;
+                    target = target->next_vcpu) {
+                       cpu_preserved_inval(target);
+                       caretaker_arm64_inject_sgi(target, sgi);
+               }
+       } else {
+               u64 aff3 = FIELD_GET(ICC_SGI1R_AFFINITY_3_MASK, reg);
+               u64 aff2 = FIELD_GET(ICC_SGI1R_AFFINITY_2_MASK, reg);
+               u64 aff1 = FIELD_GET(ICC_SGI1R_AFFINITY_1_MASK, reg);
+               u64 rs = FIELD_GET(ICC_SGI1R_RS_MASK, reg);
+               u64 cluster_mpidr = (aff3 << MPIDR_LEVEL_SHIFT(3)) |
+                                   (aff2 << MPIDR_LEVEL_SHIFT(2)) |
+                                   (aff1 << MPIDR_LEVEL_SHIFT(1));
+               u64 target_list = FIELD_GET(ICC_SGI1R_TARGET_LIST_MASK, reg);
+
+               for (i = 0; i < 16; i++) {
+                       u64 target_mpidr;
+
+                       if (!(target_list & BIT(i)))
+                               continue;
+
+                       target_mpidr = cluster_mpidr |
+                                      ((rs * 16 + i) << MPIDR_LEVEL_SHIFT(0));
+
+                       target = src_cap;
+                       do {
+                               cpu_preserved_inval(target);
+                               if ((ctxt_sys_reg(&target->ctx.ctxt, MPIDR_EL1) 
&
+                                    MPIDR_HWID_BITMASK) == target_mpidr) {
+                                       caretaker_arm64_inject_sgi(target, sgi);
+                                       break;
+                               }
+                               target = target->next_vcpu;
+                       } while (target && target != src_cap);
+               }
+       }
+}
+
+static __caretaker_text int arm64_caretaker_op_enter(void *data)
+{
+       struct caretaker_arm64_page *cap = data;
+       u64 guest_hcr;
+
+       gicv3_caretaker_clear_active_priorities();
+       write_sysreg_s(ICC_PMR_EL1_MASK, SYS_ICC_PMR_EL1);
+       write_sysreg_s(ICC_CTLR_EL1_EOImode_drop, SYS_ICC_CTLR_EL1);
+       write_sysreg_s(ICC_IGRPEN1_EL1_MASK, SYS_ICC_IGRPEN1_EL1);
+       pmr_sync();
+
+       guest_hcr = (cap->ctx.hcr_el2 | HCR_AMO | HCR_IMO | HCR_FMO | HCR_E2H) 
& ~HCR_TGE;
+       write_sysreg_hcr(guest_hcr);
+       isb();
+
+       cap->last_ret = caretaker_guest_enter(&cap->ctx);
+
+       write_sysreg_hcr(HCR_HOST_VHE_FLAGS);
+       isb();
+
+       return 0;
+}
+
+static __caretaker_text void
+arm64_caretaker_op_arm_timer(void *data, u64 deadline_ticks)
+{
+       if (deadline_ticks) {
+               write_sysreg_s(deadline_ticks, SYS_CNTHP_CVAL_EL2);
+               isb();
+               write_sysreg_s(1, SYS_CNTHP_CTL_EL2);
+       } else {
+               write_sysreg_s(0, SYS_CNTHP_CTL_EL2);
+       }
+       isb();
+}
+
+static __caretaker_text void
+arm64_caretaker_op_disarm_timer(void *data)
+{
+       write_sysreg_s(0, SYS_CNTHP_CTL_EL2);
+       isb();
+}
+
+static __caretaker_text void
+arm64_caretaker_op_decode_exit(void *data, struct kvm_caretaker_exit *exit)
+{
+       struct caretaker_arm64_page *cap = data;
+       u64 ret = cap->last_ret;
+
+       exit->rip = cap->ctx.ctxt.regs.pc;
+       exit->insn_len = 0;
+       exit->type = KVM_CARETAKER_EXIT_UNKNOWN;
+
+       if (ARM_EXCEPTION_CODE(ret) == ARM_EXCEPTION_IRQ) {
+               caretaker_gic_drain_iar();
+               gicv3_caretaker_clear_active_priorities();
+               dsb(sy);
+               isb();
+
+               exit->type = KVM_CARETAKER_EXIT_PREEMPT_TIMER;
+               return;
+       }
+
+       if (ARM_EXCEPTION_IS_TRAP(ret)) {
+               u64 esr = cap->ctx.fault.esr_el2;
+               u8 ec = ESR_ELx_EC(esr);
+
+               exit->insn_len = 4;
+
+               if (ec == ESR_ELx_EC_WFx) {
+                       exit->type = KVM_CARETAKER_EXIT_IDLE;
+                       return;
+               }
+
+               if (ec == ESR_ELx_EC_SYS64) {
+                       u32 iss = ESR_ELx_ISS(esr);
+                       u32 sys_op = iss & ESR_ELx_SYS64_ISS_SYS_OP_MASK;
+
+                       if (sys_op == ESR_ELx_SYS64_ISS_SYS_ICC_SGI1R_EL1 ||
+                           sys_op == ESR_ELx_SYS64_ISS_SYS_ICC_ASGI1R_EL1 ||
+                           sys_op == ESR_ELx_SYS64_ISS_SYS_ICC_SGI0R_EL1) {
+                               u32 rt = ESR_ELx_SYS64_ISS_RT(esr);
+                               u64 val = (rt < 31) ? 
cap->ctx.ctxt.regs.regs[rt] : 0;
+
+                               exit->type = KVM_CARETAKER_EXIT_CROSS_VCPU;
+                               exit->sgi.sgi_id = 
FIELD_GET(ICC_SGI1R_SGI_ID_MASK, val);
+                               exit->sgi.target_mask = val;
+                               return;
+                       }
+               }
+
+               if (ec == ESR_ELx_EC_DABT_LOW || ec == ESR_ELx_EC_IABT_LOW) {
+                       /*
+                        * Stage-2 abort on a non-RAM IPA (e.g. MMIO device).
+                        * Do not advance PC: yield this vCPU out of guest mode
+                        * so the incoming kernel's real KVM + VMM handles the
+                        * fault on re-attachment.
+                        */
+                       exit->type = KVM_CARETAKER_EXIT_IDLE;
+                       exit->insn_len = 0;
+                       return;
+               }
+
+               exit->type = KVM_CARETAKER_EXIT_ARCH;
+               exit->raw_reason = esr;
+               return;
+       }
+
+       exit->type = KVM_CARETAKER_EXIT_ARCH;
+}
+
+static __caretaker_text void
+arm64_caretaker_op_advance_rip(void *data, u64 next_rip)
+{
+       struct caretaker_arm64_page *cap = data;
+
+       cap->ctx.ctxt.regs.pc = next_rip;
+}
+
+static __caretaker_text bool
+arm64_caretaker_op_handle_exit(void *data, struct kvm_caretaker_exit *exit)
+{
+       struct caretaker_arm64_page *cap = data;
+
+       if (exit->type == KVM_CARETAKER_EXIT_CROSS_VCPU) {
+               caretaker_arm64_handle_sgi(cap, exit->sgi.target_mask);
+               exit->rip += exit->insn_len;
+               return true;
+       }
+
+       /*
+        * KVM_CARETAKER_EXIT_ARCH is the fallback type assigned by
+        * arm64_caretaker_op_decode_exit() to every trap it did not decode,
+        * including system register accesses.  Skipping the instruction here
+        * would leave the destination register holding whatever it held
+        * before the trap and bypass KVM's ID register sanitisation, with the
+        * guest none the wiser.
+        *
+        * Leave PC pointing at the trapping instruction and report the exit
+        * as unhandled so the vCPU parks until the incoming kernel reclaims
+        * it and full KVM handles the trap.
+        */
+       return false;
+}
+
+static __caretaker_text inline u64 arm64_sysreg_to_uapi_id(u32 reg)
+{
+       if (reg == SYS_CNTV_CVAL_EL0)
+               return KVM_REG_ARM_TIMER_CVAL;
+       return (KVM_REG_ARM64 | KVM_REG_SIZE_U64 |
+               KVM_REG_ARM64_SYSREG |
+               ((u64)sys_reg_Op0(reg) << KVM_REG_ARM64_SYSREG_OP0_SHIFT) |
+               ((u64)sys_reg_Op1(reg) << KVM_REG_ARM64_SYSREG_OP1_SHIFT) |
+               ((u64)sys_reg_CRn(reg) << KVM_REG_ARM64_SYSREG_CRN_SHIFT) |
+               ((u64)sys_reg_CRm(reg) << KVM_REG_ARM64_SYSREG_CRM_SHIFT) |
+               ((u64)sys_reg_Op2(reg) << KVM_REG_ARM64_SYSREG_OP2_SHIFT));
+}
+
+static __caretaker_text void
+arm64_caretaker_update_sysreg(struct kvm_vcpu_arch_ser *state,
+                             u32 reg, u64 val)
+{
+       u64 id = arm64_sysreg_to_uapi_id(reg);
+       u32 i;
+
+       for (i = 0; i < state->num_sysregs; i++) {
+               if (state->sysregs[i].id == id) {
+                       state->sysregs[i].addr = val;
+                       return;
+               }
+       }
+}
+
+static __caretaker_text void
+arm64_caretaker_detach_serialize(struct caretaker_arm64_page *cap)
+{
+       cap->abi.vgic_initialized = cap->ctx.vgic_initialized ? 1 : 0;
+       cap->abi.cntvoff_el2 = cap->ctx.cntvoff_el2;
+       cap->abi.hcr_el2 = cap->ctx.hcr_el2;
+       cap->abi.mdcr_el2 = cap->ctx.mdcr_el2;
+       cap->abi.cflags = (u32)cap->ctx.cflags;
+       if (cap->ctx.vgic_initialized) {
+               int i;
+
+               cap->abi.used_lrs = cap->ctx.vgic_v3.used_lrs;
+               cap->abi.vgic_hcr = cap->ctx.vgic_v3.vgic_hcr;
+               cap->abi.vgic_vmcr = cap->ctx.vgic_v3.vgic_vmcr;
+               for (i = 0; i < 4; i++) {
+                       cap->abi.vgic_ap0r[i] = cap->ctx.vgic_v3.vgic_ap0r[i];
+                       cap->abi.vgic_ap1r[i] = cap->ctx.vgic_v3.vgic_ap1r[i];
+               }
+               for (i = 0; i < 16; i++)
+                       cap->abi.vgic_lr[i] = cap->ctx.vgic_v3.vgic_lr[i];
+       }
+
+       if (cap->arch_state) {
+               struct kvm_vcpu_arch_ser *state = cap->arch_state;
+
+               cpu_preserved_memcpy(&state->regs.regs, &cap->ctx.ctxt.regs,
+                                    sizeof(state->regs.regs));
+               state->regs.sp_el1 = ctxt_sys_reg(&cap->ctx.ctxt, SP_EL1);
+               state->regs.elr_el1 = ctxt_sys_reg(&cap->ctx.ctxt, ELR_EL1);
+               state->regs.spsr[KVM_SPSR_EL1] = ctxt_sys_reg(&cap->ctx.ctxt, 
SPSR_EL1);
+               state->regs.spsr[KVM_SPSR_ABT] = cap->ctx.ctxt.spsr_abt;
+               state->regs.spsr[KVM_SPSR_UND] = cap->ctx.ctxt.spsr_und;
+               state->regs.spsr[KVM_SPSR_IRQ] = cap->ctx.ctxt.spsr_irq;
+               state->regs.spsr[KVM_SPSR_FIQ] = cap->ctx.ctxt.spsr_fiq;
+               cpu_preserved_memcpy(&state->regs.fp_regs, 
&cap->ctx.ctxt.fp_regs,
+                                    sizeof(state->regs.fp_regs));
+
+               arm64_caretaker_update_sysreg(state, SYS_SCTLR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
SCTLR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_CPACR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
CPACR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_TTBR0_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
TTBR0_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_TTBR1_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
TTBR1_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_TCR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
TCR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_ESR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
ESR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_AFSR0_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
AFSR0_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_AFSR1_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
AFSR1_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_FAR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
FAR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_MAIR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
MAIR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_VBAR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
VBAR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_CONTEXTIDR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
CONTEXTIDR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_AMAIR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
AMAIR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_CNTKCTL_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
CNTKCTL_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_PAR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
PAR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_TPIDR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
TPIDR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_TPIDR_EL0,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
TPIDR_EL0));
+               arm64_caretaker_update_sysreg(state, SYS_TPIDRRO_EL0,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
TPIDRRO_EL0));
+               arm64_caretaker_update_sysreg(state, SYS_SP_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
SP_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_ELR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
ELR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_SPSR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
SPSR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_MDSCR_EL1,
+                                             ctxt_sys_reg(&cap->ctx.ctxt, 
MDSCR_EL1));
+               arm64_caretaker_update_sysreg(state, SYS_CNTV_CVAL_EL0,
+                                             cap->ctx.cntv_cval_el0);
+               arm64_caretaker_update_sysreg(state, SYS_CNTV_CTL_EL0,
+                                             cap->ctx.cntv_ctl_el0);
+               cpu_preserved_clean_sz(state,
+                                      struct_size(state, sysregs, 
state->num_sysregs));
+       }
+
+       cpu_preserved_clean(&cap->abi);
+}
+
+static void arm64_caretaker_sync_vcpu(struct kvm_vcpu *vcpu,
+                                     void *data)
+{
+       struct arch_timer_context *vtimer = vcpu_vtimer(vcpu);
+       struct kvm_caretaker_arch_ser *abi = data;
+       u64 cval, ctl;
+       int t;
+
+       /* Sync handover ABI prefix back into incoming vcpu */
+       vcpu->arch.hcr_el2 = abi->hcr_el2;
+       vcpu->arch.mdcr_el2 = abi->mdcr_el2;
+       vcpu->arch.cflags = abi->cflags;
+
+       if (abi->vgic_initialized) {
+               int i;
+
+               vcpu->arch.vgic_cpu.vgic_v3.used_lrs = abi->used_lrs;
+               vcpu->arch.vgic_cpu.vgic_v3.vgic_hcr = abi->vgic_hcr;
+               vcpu->arch.vgic_cpu.vgic_v3.vgic_vmcr = abi->vgic_vmcr;
+               for (i = 0; i < 4; i++) {
+                       vcpu->arch.vgic_cpu.vgic_v3.vgic_ap0r[i] = 
abi->vgic_ap0r[i];
+                       vcpu->arch.vgic_cpu.vgic_v3.vgic_ap1r[i] = 
abi->vgic_ap1r[i];
+               }
+               for (i = 0; i < 16; i++)
+                       vcpu->arch.vgic_cpu.vgic_v3.vgic_lr[i] = 
abi->vgic_lr[i];
+       }
+
+       cval = __vcpu_sys_reg(vcpu, CNTV_CVAL_EL0);
+       ctl = __vcpu_sys_reg(vcpu, CNTV_CTL_EL0);
+       timer_set_offset(vtimer, abi->cntvoff_el2);
+       write_sysreg(abi->cntvoff_el2, cntvoff_el2);
+       write_sysreg_el0(cval, SYS_CNTV_CVAL);
+       write_sysreg_el0(ctl, SYS_CNTV_CTL);
+       isb();
+
+       vcpu_set_flag(vcpu, VCPU_INITIALIZED);
+
+       for (t = 0; t < NR_KVM_TIMERS; t++)
+               vcpu->arch.timer_cpu.timers[t].loaded = false;
+
+       kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
+}
+
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to