Implement standalone Caretaker GICv3 ICH_* register save/restore and virtual timer deadline emulation in arch/arm64/kvm/caretaker.c.
Signed-off-by: Pasha Tatashin <[email protected]> --- arch/arm64/kvm/caretaker.c | 521 +++++++++++++++++++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 arch/arm64/kvm/caretaker.c diff --git a/arch/arm64/kvm/caretaker.c b/arch/arm64/kvm/caretaker.c new file mode 100644 index 000000000000..65c11fe8c2fa --- /dev/null +++ b/arch/arm64/kvm/caretaker.c @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + */ +#include <linux/cpu_preserve.h> +#include <linux/delay.h> +#include <linux/irqchip/arm-gic-v3.h> +#include <linux/kexec_handover.h> +#include <linux/kho/abi/kvm.h> +#include <linux/kvm_host.h> +#include <linux/objtool.h> +#include <linux/oncore.h> +#include <linux/sched.h> + +#include <asm/barrier.h> +#include <asm/caretaker.h> +#include <asm/cpu_ops.h> +#include <linux/cpufeature.h> +#include <asm/cputype.h> +#include <asm/fpsimd.h> +#include <asm/kernel-pgtable.h> +#include <linux/kexec.h> +#include <asm/kvm_emulate.h> +#include <asm/kvm_hyp.h> +#include <asm/kvm_mmu.h> +#include <asm/kvm_pgtable.h> +#include <asm/kvm_ptrauth.h> +#include <linux/mmu_context.h> +#include <linux/pgtable.h> +#include <asm/smp_plat.h> +#include <asm/sysreg.h> +#include <asm/tlbflush.h> +#include <asm/vectors.h> + +#include <kvm/arm_arch_timer.h> +#include <kvm/arm_vgic.h> + +#include "caretaker.h" + +struct caretaker_fault_info { + u64 elr; + u64 esr; + u64 far; + u64 count; +}; + +static struct caretaker_fault_info arm64_caretaker_faults[NR_CPUS] __cpu_preserved_data; + +void __caretaker_text +arm64_caretaker_handle_invalid(u64 elr, u64 esr, u64 far) +{ + int cpu = arm64_caretaker_get_pcpu(); + + if (cpu >= 0 && cpu < ARRAY_SIZE(arm64_caretaker_faults)) { + arm64_caretaker_faults[cpu].elr = elr; + arm64_caretaker_faults[cpu].esr = esr; + arm64_caretaker_faults[cpu].far = far; + arm64_caretaker_faults[cpu].count++; + } + + while (1) { + if (cpu_preserved_should_exit(cpu)) { + cpu_preserved_set_dead(cpu); + arch_cpu_preserved_park_finish(cpu); + } + arch_cpu_preserved_park_wait(); + } +} + +__caretaker_text static inline void +arm64_caretaker_load_sysregs(struct kvm_cpu_context *ctxt) +{ + u64 mpidr = ctxt_sys_reg(ctxt, MPIDR_EL1); + u64 midr = read_cpuid_id(); + + write_sysreg(midr, vpidr_el2); + write_sysreg(mpidr, vmpidr_el2); + + write_sysreg_el1(ctxt_sys_reg(ctxt, SCTLR_EL1), SYS_SCTLR); + write_sysreg_el1(ctxt_sys_reg(ctxt, CPACR_EL1), SYS_CPACR); + write_sysreg_el1(ctxt_sys_reg(ctxt, TTBR0_EL1), SYS_TTBR0); + write_sysreg_el1(ctxt_sys_reg(ctxt, TTBR1_EL1), SYS_TTBR1); + write_sysreg_el1(ctxt_sys_reg(ctxt, TCR_EL1), SYS_TCR); + if (cpus_have_final_cap(ARM64_HAS_TCR2)) { + write_sysreg_el1(ctxt_sys_reg(ctxt, TCR2_EL1), SYS_TCR2); + if (cpus_have_final_cap(ARM64_HAS_S1PIE)) { + write_sysreg_el1(ctxt_sys_reg(ctxt, PIR_EL1), SYS_PIR); + write_sysreg_el1(ctxt_sys_reg(ctxt, PIRE0_EL1), SYS_PIRE0); + } + } + if (cpus_have_final_cap(ARM64_HAS_SCTLR2)) + write_sysreg_el1(ctxt_sys_reg(ctxt, SCTLR2_EL1), SYS_SCTLR2); + write_sysreg_el1(ctxt_sys_reg(ctxt, ESR_EL1), SYS_ESR); + write_sysreg_el1(ctxt_sys_reg(ctxt, AFSR0_EL1), SYS_AFSR0); + write_sysreg_el1(ctxt_sys_reg(ctxt, AFSR1_EL1), SYS_AFSR1); + write_sysreg_el1(ctxt_sys_reg(ctxt, FAR_EL1), SYS_FAR); + write_sysreg_el1(ctxt_sys_reg(ctxt, MAIR_EL1), SYS_MAIR); + write_sysreg_el1(ctxt_sys_reg(ctxt, VBAR_EL1), SYS_VBAR); + write_sysreg_el1(ctxt_sys_reg(ctxt, CONTEXTIDR_EL1), SYS_CONTEXTIDR); + write_sysreg_el1(ctxt_sys_reg(ctxt, AMAIR_EL1), SYS_AMAIR); + write_sysreg_el1(ctxt_sys_reg(ctxt, CNTKCTL_EL1), SYS_CNTKCTL); + write_sysreg(ctxt_sys_reg(ctxt, PAR_EL1), par_el1); + write_sysreg(ctxt_sys_reg(ctxt, TPIDR_EL1), tpidr_el1); + write_sysreg(ctxt_sys_reg(ctxt, TPIDR_EL0), tpidr_el0); + write_sysreg(ctxt_sys_reg(ctxt, TPIDRRO_EL0), tpidrro_el0); + write_sysreg(ctxt_sys_reg(ctxt, SP_EL1), sp_el1); + write_sysreg_el1(ctxt_sys_reg(ctxt, ELR_EL1), SYS_ELR); + write_sysreg_el1(ctxt_sys_reg(ctxt, SPSR_EL1), SYS_SPSR); + write_sysreg(ctxt_sys_reg(ctxt, MDSCR_EL1), mdscr_el1); +} + +__caretaker_text static inline void +arm64_caretaker_save_sysregs(struct kvm_cpu_context *ctxt) +{ + ctxt_sys_reg(ctxt, SCTLR_EL1) = read_sysreg_el1(SYS_SCTLR); + ctxt_sys_reg(ctxt, CPACR_EL1) = read_sysreg_el1(SYS_CPACR); + ctxt_sys_reg(ctxt, TTBR0_EL1) = read_sysreg_el1(SYS_TTBR0); + ctxt_sys_reg(ctxt, TTBR1_EL1) = read_sysreg_el1(SYS_TTBR1); + ctxt_sys_reg(ctxt, TCR_EL1) = read_sysreg_el1(SYS_TCR); + if (cpus_have_final_cap(ARM64_HAS_TCR2)) { + ctxt_sys_reg(ctxt, TCR2_EL1) = read_sysreg_el1(SYS_TCR2); + if (cpus_have_final_cap(ARM64_HAS_S1PIE)) { + ctxt_sys_reg(ctxt, PIR_EL1) = read_sysreg_el1(SYS_PIR); + ctxt_sys_reg(ctxt, PIRE0_EL1) = read_sysreg_el1(SYS_PIRE0); + } + } + if (cpus_have_final_cap(ARM64_HAS_SCTLR2)) + ctxt_sys_reg(ctxt, SCTLR2_EL1) = read_sysreg_el1(SYS_SCTLR2); + ctxt_sys_reg(ctxt, ESR_EL1) = read_sysreg_el1(SYS_ESR); + ctxt_sys_reg(ctxt, AFSR0_EL1) = read_sysreg_el1(SYS_AFSR0); + ctxt_sys_reg(ctxt, AFSR1_EL1) = read_sysreg_el1(SYS_AFSR1); + ctxt_sys_reg(ctxt, FAR_EL1) = read_sysreg_el1(SYS_FAR); + ctxt_sys_reg(ctxt, MAIR_EL1) = read_sysreg_el1(SYS_MAIR); + ctxt_sys_reg(ctxt, VBAR_EL1) = read_sysreg_el1(SYS_VBAR); + ctxt_sys_reg(ctxt, CONTEXTIDR_EL1) = read_sysreg_el1(SYS_CONTEXTIDR); + ctxt_sys_reg(ctxt, AMAIR_EL1) = read_sysreg_el1(SYS_AMAIR); + ctxt_sys_reg(ctxt, CNTKCTL_EL1) = read_sysreg_el1(SYS_CNTKCTL); + ctxt_sys_reg(ctxt, PAR_EL1) = read_sysreg_par(); + ctxt_sys_reg(ctxt, TPIDR_EL1) = read_sysreg(tpidr_el1); + ctxt_sys_reg(ctxt, TPIDR_EL0) = read_sysreg(tpidr_el0); + ctxt_sys_reg(ctxt, TPIDRRO_EL0) = read_sysreg(tpidrro_el0); + ctxt_sys_reg(ctxt, SP_EL1) = read_sysreg(sp_el1); + ctxt_sys_reg(ctxt, ELR_EL1) = read_sysreg_el1(SYS_ELR); + ctxt_sys_reg(ctxt, SPSR_EL1) = read_sysreg_el1(SYS_SPSR); + ctxt_sys_reg(ctxt, MDSCR_EL1) = read_sysreg(mdscr_el1); +} + +int arm64_kvm_caretaker_preserve(struct kvm_vcpu *vcpu, + struct kvm_vcpu_ser *ser) +{ + struct kvm_s2_mmu *mmu = vcpu->arch.hw_mmu ? + vcpu->arch.hw_mmu : &vcpu->kvm->arch.mmu; + struct oncore_session *sess = oncore_job_session(vcpu->caretaker.job); + struct arch_timer_context *vtimer; + struct caretaker_arm64_page *head; + struct caretaker_arm64_page *cap; + + if (!has_vhe() || is_protected_kvm_enabled() || vcpu_has_nv(vcpu)) + return -EOPNOTSUPP; + /* + * Same predicate as kvm_arch_vcpu_luo_preserve() (kvm_luo.c) and + * kvm_arm_copy_sys_reg_indices() (sys_regs.c): what matters is whether + * an in-kernel irqchip exists at all, not whether it has been + * initialised yet. vgic_initialized() would let a created-but-not-ready + * in-kernel VGICv2 through, and the caretaker cannot emulate one. + */ + if (irqchip_in_kernel(vcpu->kvm) && + vcpu->kvm->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V3) + return -EOPNOTSUPP; + if (kvm_has_mte(vcpu->kvm) || kvm_has_s1poe(vcpu->kvm) || + vcpu_has_sve(vcpu) || vcpu_has_ptrauth(vcpu) || + test_bit(KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS, &vcpu->kvm->arch.flags)) + return -EOPNOTSUPP; + + cap = kho_alloc_preserve(sizeof(*cap)); + if (IS_ERR(cap)) { + pr_err("caretaker arm64: failed to allocate preserved page\n"); + return -ENOMEM; + } + oncore_session_map_buffer(sess, cap, sizeof(*cap)); + + memset(cap, 0, sizeof(*cap)); + + kvm_caretaker_init_common_vcpu(&cap->vcpu, &cap->abi.cb, vcpu, cap, + sizeof(*cap), NULL, cap); + + /* Copy architectural execution state */ + cap->ctx.ctxt = vcpu->arch.ctxt; + cap->ctx.fault = vcpu->arch.fault; + cap->ctx.hcr_el2 = vcpu->arch.hcr_el2; + cap->ctx.mdcr_el2 = vcpu->arch.mdcr_el2; + cap->ctx.cflags = vcpu->arch.cflags; + + cap->ctx.vtcr_el2 = mmu->vtcr; + cap->ctx.vttbr_el2 = kvm_get_vttbr(mmu); + + if (vgic_initialized(vcpu->kvm)) { + int i; + + cap->ctx.vgic_initialized = true; + cap->ctx.vgic_v3 = vcpu->arch.vgic_cpu.vgic_v3; + + for (i = 0; ; i++) { + phys_addr_t rpa; + unsigned long rva; + size_t rsize; + + if (gicv3_caretaker_get_redist_region(i, &rpa, &rva, &rsize)) + break; + oncore_session_map_range(sess, rpa, rva, rsize, + pgprot_device(PAGE_KERNEL)); + } + } + + vtimer = vcpu_vtimer(vcpu); + cap->ctx.cntvoff_el2 = timer_get_offset(vtimer); + cap->ctx.cntv_cval_el0 = timer_get_cval(vtimer); + cap->ctx.cntv_ctl_el0 = timer_get_ctl(vtimer); + + head = vcpu->kvm->caretaker_vm; + if (!head) { + cap->next_vcpu = cap; + vcpu->kvm->caretaker_vm = cap; + } else { + struct caretaker_arm64_page *peer; + + cap->next_vcpu = head->next_vcpu; + head->next_vcpu = cap; + cpu_preserved_clean(head); + + for (peer = cap->next_vcpu; peer != cap; peer = peer->next_vcpu) + oncore_session_map_buffer(sess, peer, sizeof(*peer)); + } + + 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 (ser->arch_state.phys) { + struct kvm_vcpu_arch_ser *state = + phys_to_virt(ser->arch_state.phys); + size_t sz = struct_size(state, sysregs, state->num_sysregs); + + cap->arch_state = state; + oncore_session_map_buffer(sess, state, sz); + } + + ser->cb.phys = virt_to_phys(cap); + + cpu_preserved_clean(cap); + + return 0; +} + +static __always_inline void arm64_caretaker_save_ptrauth(struct arm64_caretaker_ptrauth_keys *k) +{ + if (!arm64_caretaker_has_ptrauth) + return; + + k->apia_lo = read_sysreg_s(SYS_APIAKEYLO_EL1); + k->apia_hi = read_sysreg_s(SYS_APIAKEYHI_EL1); + k->apib_lo = read_sysreg_s(SYS_APIBKEYLO_EL1); + k->apib_hi = read_sysreg_s(SYS_APIBKEYHI_EL1); + k->apda_lo = read_sysreg_s(SYS_APDAKEYLO_EL1); + k->apda_hi = read_sysreg_s(SYS_APDAKEYHI_EL1); + k->apdb_lo = read_sysreg_s(SYS_APDBKEYLO_EL1); + k->apdb_hi = read_sysreg_s(SYS_APDBKEYHI_EL1); + k->apga_lo = read_sysreg_s(SYS_APGAKEYLO_EL1); + k->apga_hi = read_sysreg_s(SYS_APGAKEYHI_EL1); +} + +static __always_inline void +arm64_caretaker_restore_ptrauth(const struct arm64_caretaker_ptrauth_keys *k) +{ + if (!arm64_caretaker_has_ptrauth) + return; + + write_sysreg_s(k->apia_lo, SYS_APIAKEYLO_EL1); + write_sysreg_s(k->apia_hi, SYS_APIAKEYHI_EL1); + write_sysreg_s(k->apib_lo, SYS_APIBKEYLO_EL1); + write_sysreg_s(k->apib_hi, SYS_APIBKEYHI_EL1); + write_sysreg_s(k->apda_lo, SYS_APDAKEYLO_EL1); + write_sysreg_s(k->apda_hi, SYS_APDAKEYHI_EL1); + write_sysreg_s(k->apdb_lo, SYS_APDBKEYLO_EL1); + write_sysreg_s(k->apdb_hi, SYS_APDBKEYHI_EL1); + write_sysreg_s(k->apga_lo, SYS_APGAKEYLO_EL1); + write_sysreg_s(k->apga_hi, SYS_APGAKEYHI_EL1); + isb(); +} + +#define __caretaker_apr_save(_v, _reg) ((_v) = read_sysreg_s(_reg)) +#define __caretaker_apr_restore(_v, _reg) write_sysreg_s((_v), _reg) + +/* + * ICH_AP0R<n>_EL2 and ICH_AP1R<n>_EL2 encode <n> in the instruction, so the + * accesses cannot be put in a loop. Generate the ladder for either group and + * either direction from one pattern instead of writing it out four times. + * + * @_dir: save or restore + * @_grp: 0 or 1, selecting ICH_AP0R<n>_EL2 or ICH_AP1R<n>_EL2 + */ +#define caretaker_vgic_v3_apr(_dir, _grp, _regs, _nr_pre_bits) \ +do { \ + switch (_nr_pre_bits) { \ + case 7: \ + __caretaker_apr_##_dir((_regs)[3], SYS_ICH_AP##_grp##R3_EL2); \ + __caretaker_apr_##_dir((_regs)[2], SYS_ICH_AP##_grp##R2_EL2); \ + fallthrough; \ + case 6: \ + __caretaker_apr_##_dir((_regs)[1], SYS_ICH_AP##_grp##R1_EL2); \ + fallthrough; \ + default: \ + __caretaker_apr_##_dir((_regs)[0], SYS_ICH_AP##_grp##R0_EL2); \ + } \ +} while (0) + +/* + * Acknowledge and retire whichever Group 1 interrupt is pending on this CPU. + * EOImode is 1 (priority drop and deactivation are separate), so a real INTID + * needs both an EOIR1 and a DIR write. The special INTIDs (1020-1023) mean + * "nothing pending" and must not be written back. + */ +__caretaker_text static void caretaker_gic_drain_iar(void) +{ + u32 iar = read_sysreg_s(SYS_ICC_IAR1_EL1); + + if (iar < GIC_SPECIAL_INTID_START) { + write_sysreg_s(iar, SYS_ICC_EOIR1_EL1); + write_sysreg_s(iar, SYS_ICC_DIR_EL1); + } +} + +/* + * Put EL2 back into host (VHE) configuration. Both vector bases are pointed + * at the caretaker's own hyp vectors: while a preserved core is running a + * guest the caretaker owns EL2, and the kernel that installed kvm_hyp_vector + * may no longer exist. + */ +__caretaker_text static void caretaker_restore_host_el2(void) +{ + write_sysreg_hcr(HCR_HOST_VHE_FLAGS); + write_sysreg((unsigned long)caretaker_hyp_vector, vbar_el1); + write_sysreg_s((unsigned long)caretaker_hyp_vector, SYS_VBAR_EL2); + dsb(sy); + isb(); +} + +__caretaker_text static u64 caretaker_gic_v3_get_lr(unsigned int lr) +{ + switch (lr & 0xf) { + case 0: + return read_gicreg(ICH_LR0_EL2); + case 1: + return read_gicreg(ICH_LR1_EL2); + case 2: + return read_gicreg(ICH_LR2_EL2); + case 3: + return read_gicreg(ICH_LR3_EL2); + case 4: + return read_gicreg(ICH_LR4_EL2); + case 5: + return read_gicreg(ICH_LR5_EL2); + case 6: + return read_gicreg(ICH_LR6_EL2); + case 7: + return read_gicreg(ICH_LR7_EL2); + case 8: + return read_gicreg(ICH_LR8_EL2); + case 9: + return read_gicreg(ICH_LR9_EL2); + case 10: + return read_gicreg(ICH_LR10_EL2); + case 11: + return read_gicreg(ICH_LR11_EL2); + case 12: + return read_gicreg(ICH_LR12_EL2); + case 13: + return read_gicreg(ICH_LR13_EL2); + case 14: + return read_gicreg(ICH_LR14_EL2); + default: + return read_gicreg(ICH_LR15_EL2); + } +} + +__caretaker_text static void caretaker_gic_v3_set_lr(u64 val, unsigned int lr) +{ + switch (lr & 0xf) { + case 0: + write_gicreg(val, ICH_LR0_EL2); + break; + case 1: + write_gicreg(val, ICH_LR1_EL2); + break; + case 2: + write_gicreg(val, ICH_LR2_EL2); + break; + case 3: + write_gicreg(val, ICH_LR3_EL2); + break; + case 4: + write_gicreg(val, ICH_LR4_EL2); + break; + case 5: + write_gicreg(val, ICH_LR5_EL2); + break; + case 6: + write_gicreg(val, ICH_LR6_EL2); + break; + case 7: + write_gicreg(val, ICH_LR7_EL2); + break; + case 8: + write_gicreg(val, ICH_LR8_EL2); + break; + case 9: + write_gicreg(val, ICH_LR9_EL2); + break; + case 10: + write_gicreg(val, ICH_LR10_EL2); + break; + case 11: + write_gicreg(val, ICH_LR11_EL2); + break; + case 12: + write_gicreg(val, ICH_LR12_EL2); + break; + case 13: + write_gicreg(val, ICH_LR13_EL2); + break; + case 14: + write_gicreg(val, ICH_LR14_EL2); + break; + default: + write_gicreg(val, ICH_LR15_EL2); + break; + } +} + +__caretaker_text static void caretaker_vgic_v3_restore(struct caretaker_arm64_page *cap) +{ + struct vgic_v3_cpu_if *cpu_if = &cap->ctx.vgic_v3; + unsigned int used_lrs = cpu_if->used_lrs; + u64 vtr = read_sysreg_s(SYS_ICH_VTR_EL2); + unsigned int max_lrs = FIELD_GET(ICH_VTR_EL2_ListRegs, vtr) + 1; + u32 nr_pre_bits = FIELD_GET(ICH_VTR_EL2_PREbits, vtr) + 1; + unsigned int i; + + /* Drain any pending software-generated SGIs into empty LRs */ + if (cap->ctx.pending_sgis) { + for (i = 0; i < max_lrs && cap->ctx.pending_sgis; i++) { + if (i >= used_lrs || (cpu_if->vgic_lr[i] & ICH_LR_STATE) == 0) { + int sgi = __ffs(cap->ctx.pending_sgis); + + cap->ctx.pending_sgis &= ~BIT(sgi); + cpu_if->vgic_lr[i] = ((u64)sgi & 0xf) | + ICH_LR_PENDING_BIT | + ICH_LR_GROUP | + ((u64)GIC_DEFAULT_SGI_PRIO << + ICH_LR_PRIORITY_SHIFT); + if (i >= used_lrs) + used_lrs = i + 1; + } + } + cpu_if->used_lrs = used_lrs; + } + + write_sysreg_s(cpu_if->vgic_vmcr, SYS_ICH_VMCR_EL2); + + caretaker_vgic_v3_apr(restore, 0, cpu_if->vgic_ap0r, nr_pre_bits); + caretaker_vgic_v3_apr(restore, 1, cpu_if->vgic_ap1r, nr_pre_bits); + + write_sysreg_s(cpu_if->vgic_hcr | ICH_HCR_EL2_En, SYS_ICH_HCR_EL2); + + used_lrs = min3(used_lrs, max_lrs, (unsigned int)VGIC_V3_MAX_LRS); + + for (i = 0; i < used_lrs; i++) + caretaker_gic_v3_set_lr(cpu_if->vgic_lr[i], i); + isb(); +} + +__caretaker_text static void caretaker_vgic_v3_save(struct vgic_v3_cpu_if *cpu_if) +{ + u64 vtr = read_sysreg_s(SYS_ICH_VTR_EL2); + unsigned int max_lrs = FIELD_GET(ICH_VTR_EL2_ListRegs, vtr) + 1; + u32 nr_pre_bits = FIELD_GET(ICH_VTR_EL2_PREbits, vtr) + 1; + unsigned int used_lrs = cpu_if->used_lrs; + unsigned int 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_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2); + + caretaker_vgic_v3_apr(save, 0, cpu_if->vgic_ap0r, nr_pre_bits); + caretaker_vgic_v3_apr(save, 1, cpu_if->vgic_ap1r, nr_pre_bits); + + write_sysreg_s(0, SYS_ICH_HCR_EL2); + isb(); +} + -- 2.55.0.1082.g2b9226bbc0-goog

