mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-18 22:14:16 +00:00
KVM: arm64: nv: Plumb handling of GICv3 EL2 accesses
Wire the handling of all GICv3 EL2 registers, and provide emulation for all the non memory-backed registers (ICC_SRE_EL2, ICH_VTR_EL2, ICH_MISR_EL2, ICH_ELRSR_EL2, and ICH_EISR_EL2). Signed-off-by: Marc Zyngier <maz@kernel.org> Link: https://lore.kernel.org/r/20250225172930.1850838-7-maz@kernel.org Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
This commit is contained in:
parent
182f159694
commit
96c2f03311
4 changed files with 224 additions and 2 deletions
|
@ -23,7 +23,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
|
|||
vgic/vgic-v3.o vgic/vgic-v4.o \
|
||||
vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \
|
||||
vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
|
||||
vgic/vgic-its.o vgic/vgic-debug.o
|
||||
vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o
|
||||
|
||||
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
|
||||
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/irqchip/arm-gic-v3.h>
|
||||
|
||||
#include <asm/arm_pmuv3.h>
|
||||
#include <asm/cacheflush.h>
|
||||
|
@ -531,7 +532,13 @@ static bool access_gic_sre(struct kvm_vcpu *vcpu,
|
|||
if (p->is_write)
|
||||
return ignore_write(vcpu, p);
|
||||
|
||||
p->regval = vcpu->arch.vgic_cpu.vgic_v3.vgic_sre;
|
||||
if (p->Op1 == 4) { /* ICC_SRE_EL2 */
|
||||
p->regval = (ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE |
|
||||
ICC_SRE_EL1_DIB | ICC_SRE_EL1_DFB);
|
||||
} else { /* ICC_SRE_EL1 */
|
||||
p->regval = vcpu->arch.vgic_cpu.vgic_v3.vgic_sre;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2426,6 +2433,59 @@ static bool access_zcr_el2(struct kvm_vcpu *vcpu,
|
|||
vq = SYS_FIELD_GET(ZCR_ELx, LEN, p->regval) + 1;
|
||||
vq = min(vq, vcpu_sve_max_vq(vcpu));
|
||||
vcpu_write_sys_reg(vcpu, vq - 1, ZCR_EL2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool access_gic_vtr(struct kvm_vcpu *vcpu,
|
||||
struct sys_reg_params *p,
|
||||
const struct sys_reg_desc *r)
|
||||
{
|
||||
if (p->is_write)
|
||||
return write_to_read_only(vcpu, p, r);
|
||||
|
||||
p->regval = kvm_vgic_global_state.ich_vtr_el2;
|
||||
p->regval &= ~(ICH_VTR_EL2_DVIM |
|
||||
ICH_VTR_EL2_A3V |
|
||||
ICH_VTR_EL2_IDbits);
|
||||
p->regval |= ICH_VTR_EL2_nV4;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool access_gic_misr(struct kvm_vcpu *vcpu,
|
||||
struct sys_reg_params *p,
|
||||
const struct sys_reg_desc *r)
|
||||
{
|
||||
if (p->is_write)
|
||||
return write_to_read_only(vcpu, p, r);
|
||||
|
||||
p->regval = vgic_v3_get_misr(vcpu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool access_gic_eisr(struct kvm_vcpu *vcpu,
|
||||
struct sys_reg_params *p,
|
||||
const struct sys_reg_desc *r)
|
||||
{
|
||||
if (p->is_write)
|
||||
return write_to_read_only(vcpu, p, r);
|
||||
|
||||
p->regval = vgic_v3_get_eisr(vcpu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool access_gic_elrsr(struct kvm_vcpu *vcpu,
|
||||
struct sys_reg_params *p,
|
||||
const struct sys_reg_desc *r)
|
||||
{
|
||||
if (p->is_write)
|
||||
return write_to_read_only(vcpu, p, r);
|
||||
|
||||
p->regval = vgic_v3_get_elrsr(vcpu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3102,7 +3162,40 @@ static const struct sys_reg_desc sys_reg_descs[] = {
|
|||
EL2_REG(RVBAR_EL2, access_rw, reset_val, 0),
|
||||
{ SYS_DESC(SYS_RMR_EL2), undef_access },
|
||||
|
||||
EL2_REG_VNCR(ICH_AP0R0_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP0R1_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP0R2_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP0R3_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP1R0_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP1R1_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP1R2_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_AP1R3_EL2, reset_val, 0),
|
||||
|
||||
{ SYS_DESC(SYS_ICC_SRE_EL2), access_gic_sre },
|
||||
|
||||
EL2_REG_VNCR(ICH_HCR_EL2, reset_val, 0),
|
||||
{ SYS_DESC(SYS_ICH_VTR_EL2), access_gic_vtr },
|
||||
{ SYS_DESC(SYS_ICH_MISR_EL2), access_gic_misr },
|
||||
{ SYS_DESC(SYS_ICH_EISR_EL2), access_gic_eisr },
|
||||
{ SYS_DESC(SYS_ICH_ELRSR_EL2), access_gic_elrsr },
|
||||
EL2_REG_VNCR(ICH_VMCR_EL2, reset_val, 0),
|
||||
|
||||
EL2_REG_VNCR(ICH_LR0_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR1_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR2_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR3_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR4_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR5_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR6_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR7_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR8_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR9_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR10_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR11_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR12_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR13_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR14_EL2, reset_val, 0),
|
||||
EL2_REG_VNCR(ICH_LR15_EL2, reset_val, 0),
|
||||
|
||||
EL2_REG(CONTEXTIDR_EL2, access_rw, reset_val, 0),
|
||||
EL2_REG(TPIDR_EL2, access_rw, reset_val, 0),
|
||||
|
|
125
arch/arm64/kvm/vgic/vgic-v3-nested.c
Normal file
125
arch/arm64/kvm/vgic/vgic-v3-nested.c
Normal file
|
@ -0,0 +1,125 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/kvm.h>
|
||||
#include <linux/kvm_host.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <kvm/arm_vgic.h>
|
||||
|
||||
#include <asm/kvm_arm.h>
|
||||
#include <asm/kvm_emulate.h>
|
||||
#include <asm/kvm_nested.h>
|
||||
|
||||
#include "vgic.h"
|
||||
|
||||
#define ICH_LRN(n) (ICH_LR0_EL2 + (n))
|
||||
|
||||
struct mi_state {
|
||||
u16 eisr;
|
||||
u16 elrsr;
|
||||
bool pend;
|
||||
};
|
||||
|
||||
/*
|
||||
* Nesting GICv3 support
|
||||
*
|
||||
* System register emulation:
|
||||
*
|
||||
* We get two classes of registers:
|
||||
*
|
||||
* - those backed by memory (LRs, APRs, HCR, VMCR): L1 can freely access
|
||||
* them, and L0 doesn't see a thing.
|
||||
*
|
||||
* - those that always trap (ELRSR, EISR, MISR): these are status registers
|
||||
* that are built on the fly based on the in-memory state.
|
||||
*
|
||||
* Only L1 can access the ICH_*_EL2 registers. A non-NV L2 obviously cannot,
|
||||
* and a NV L2 would either access the VNCR page provided by L1 (memory
|
||||
* based registers), or see the access redirected to L1 (registers that
|
||||
* trap) thanks to NV being set by L1.
|
||||
*/
|
||||
|
||||
static bool lr_triggers_eoi(u64 lr)
|
||||
{
|
||||
return !(lr & (ICH_LR_STATE | ICH_LR_HW)) && (lr & ICH_LR_EOI);
|
||||
}
|
||||
|
||||
static void vgic_compute_mi_state(struct kvm_vcpu *vcpu, struct mi_state *mi_state)
|
||||
{
|
||||
u16 eisr = 0, elrsr = 0;
|
||||
bool pend = false;
|
||||
|
||||
for (int i = 0; i < kvm_vgic_global_state.nr_lr; i++) {
|
||||
u64 lr = __vcpu_sys_reg(vcpu, ICH_LRN(i));
|
||||
|
||||
if (lr_triggers_eoi(lr))
|
||||
eisr |= BIT(i);
|
||||
if (!(lr & ICH_LR_STATE))
|
||||
elrsr |= BIT(i);
|
||||
pend |= (lr & ICH_LR_PENDING_BIT);
|
||||
}
|
||||
|
||||
mi_state->eisr = eisr;
|
||||
mi_state->elrsr = elrsr;
|
||||
mi_state->pend = pend;
|
||||
}
|
||||
|
||||
u16 vgic_v3_get_eisr(struct kvm_vcpu *vcpu)
|
||||
{
|
||||
struct mi_state mi_state;
|
||||
|
||||
vgic_compute_mi_state(vcpu, &mi_state);
|
||||
return mi_state.eisr;
|
||||
}
|
||||
|
||||
u16 vgic_v3_get_elrsr(struct kvm_vcpu *vcpu)
|
||||
{
|
||||
struct mi_state mi_state;
|
||||
|
||||
vgic_compute_mi_state(vcpu, &mi_state);
|
||||
return mi_state.elrsr;
|
||||
}
|
||||
|
||||
u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu)
|
||||
{
|
||||
struct mi_state mi_state;
|
||||
u64 reg = 0, hcr, vmcr;
|
||||
|
||||
hcr = __vcpu_sys_reg(vcpu, ICH_HCR_EL2);
|
||||
vmcr = __vcpu_sys_reg(vcpu, ICH_VMCR_EL2);
|
||||
|
||||
vgic_compute_mi_state(vcpu, &mi_state);
|
||||
|
||||
if (mi_state.eisr)
|
||||
reg |= ICH_MISR_EL2_EOI;
|
||||
|
||||
if (__vcpu_sys_reg(vcpu, ICH_HCR_EL2) & ICH_HCR_EL2_UIE) {
|
||||
int used_lrs = kvm_vgic_global_state.nr_lr;
|
||||
|
||||
used_lrs -= hweight16(mi_state.elrsr);
|
||||
reg |= (used_lrs <= 1) ? ICH_MISR_EL2_U : 0;
|
||||
}
|
||||
|
||||
if ((hcr & ICH_HCR_EL2_LRENPIE) && FIELD_GET(ICH_HCR_EL2_EOIcount_MASK, hcr))
|
||||
reg |= ICH_MISR_EL2_LRENP;
|
||||
|
||||
if ((hcr & ICH_HCR_EL2_NPIE) && !mi_state.pend)
|
||||
reg |= ICH_MISR_EL2_NP;
|
||||
|
||||
if ((hcr & ICH_HCR_EL2_VGrp0EIE) && (vmcr & ICH_VMCR_ENG0_MASK))
|
||||
reg |= ICH_MISR_EL2_VGrp0E;
|
||||
|
||||
if ((hcr & ICH_HCR_EL2_VGrp0DIE) && !(vmcr & ICH_VMCR_ENG0_MASK))
|
||||
reg |= ICH_MISR_EL2_VGrp0D;
|
||||
|
||||
if ((hcr & ICH_HCR_EL2_VGrp1EIE) && (vmcr & ICH_VMCR_ENG1_MASK))
|
||||
reg |= ICH_MISR_EL2_VGrp1E;
|
||||
|
||||
if ((hcr & ICH_HCR_EL2_VGrp1DIE) && !(vmcr & ICH_VMCR_ENG1_MASK))
|
||||
reg |= ICH_MISR_EL2_VGrp1D;
|
||||
|
||||
return reg;
|
||||
}
|
|
@ -389,6 +389,10 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu);
|
|||
void kvm_vgic_load(struct kvm_vcpu *vcpu);
|
||||
void kvm_vgic_put(struct kvm_vcpu *vcpu);
|
||||
|
||||
u16 vgic_v3_get_eisr(struct kvm_vcpu *vcpu);
|
||||
u16 vgic_v3_get_elrsr(struct kvm_vcpu *vcpu);
|
||||
u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
|
||||
|
||||
#define irqchip_in_kernel(k) (!!((k)->arch.vgic.in_kernel))
|
||||
#define vgic_initialized(k) ((k)->arch.vgic.initialized)
|
||||
#define vgic_ready(k) ((k)->arch.vgic.ready)
|
||||
|
|
Loading…
Add table
Reference in a new issue