2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00

Merge branch 'kvm-arm64/gcie-legacy' into kvmarm/next

* kvm-arm64/gcie-legacy:
  : Support for GICv3 emulation on GICv5, courtesy of Sascha Bischoff
  :
  : FEAT_GCIE_LEGACY adds the necessary hardware for GICv5 systems to
  : support the legacy GICv3 for VMs, including a backwards-compatible VGIC
  : implementation that we all know and love.
  :
  : As a starting point for GICv5 enablement in KVM, enable + use the
  : GICv3-compatible feature when running VMs on GICv5 hardware.
  KVM: arm64: gic-v5: Probe for GICv5
  KVM: arm64: gic-v5: Support GICv3 compat
  arm64/sysreg: Add ICH_VCTLR_EL2
  irqchip/gic-v5: Populate struct gic_kvm_info
  irqchip/gic-v5: Skip deactivate for forwarded PPI interrupts

Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
This commit is contained in:
Oliver Upton 2025-07-26 08:50:06 -07:00
commit 1f315e99bd
10 changed files with 191 additions and 13 deletions

View File

@ -23,7 +23,8 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-v3.o vgic/vgic-v4.o \ vgic/vgic-v3.o vgic/vgic-v4.o \
vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \ vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \
vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \ vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
vgic/vgic-v5.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o

View File

@ -296,12 +296,19 @@ void __vgic_v3_activate_traps(struct vgic_v3_cpu_if *cpu_if)
} }
/* /*
* Prevent the guest from touching the ICC_SRE_EL1 system * GICv5 BET0 FEAT_GCIE_LEGACY doesn't include ICC_SRE_EL2. This is due
* register. Note that this may not have any effect, as * to be relaxed in a future spec release, at which point this in
* ICC_SRE_EL2.Enable being RAO/WI is a valid implementation. * condition can be dropped.
*/ */
write_gicreg(read_gicreg(ICC_SRE_EL2) & ~ICC_SRE_EL2_ENABLE, if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
ICC_SRE_EL2); /*
* Prevent the guest from touching the ICC_SRE_EL1 system
* register. Note that this may not have any effect, as
* ICC_SRE_EL2.Enable being RAO/WI is a valid implementation.
*/
write_gicreg(read_gicreg(ICC_SRE_EL2) & ~ICC_SRE_EL2_ENABLE,
ICC_SRE_EL2);
}
/* /*
* If we need to trap system registers, we must write * If we need to trap system registers, we must write
@ -322,8 +329,14 @@ void __vgic_v3_deactivate_traps(struct vgic_v3_cpu_if *cpu_if)
cpu_if->vgic_vmcr = read_gicreg(ICH_VMCR_EL2); cpu_if->vgic_vmcr = read_gicreg(ICH_VMCR_EL2);
} }
val = read_gicreg(ICC_SRE_EL2); /*
write_gicreg(val | ICC_SRE_EL2_ENABLE, ICC_SRE_EL2); * Can be dropped in the future when GICv5 spec is relaxed. See comment
* above.
*/
if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
val = read_gicreg(ICC_SRE_EL2);
write_gicreg(val | ICC_SRE_EL2_ENABLE, ICC_SRE_EL2);
}
if (!cpu_if->vgic_sre) { if (!cpu_if->vgic_sre) {
/* Make sure ENABLE is set at EL2 before setting SRE at EL1 */ /* Make sure ENABLE is set at EL2 before setting SRE at EL1 */
@ -423,9 +436,19 @@ void __vgic_v3_init_lrs(void)
*/ */
u64 __vgic_v3_get_gic_config(void) u64 __vgic_v3_get_gic_config(void)
{ {
u64 val, sre = read_gicreg(ICC_SRE_EL1); u64 val, sre;
unsigned long flags = 0; unsigned long flags = 0;
/*
* In compat mode, we cannot access ICC_SRE_EL1 at any EL
* other than EL1 itself; just return the
* ICH_VTR_EL2. ICC_IDR0_EL1 is only implemented on a GICv5
* system, so we first check if we have GICv5 support.
*/
if (cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
return read_gicreg(ICH_VTR_EL2);
sre = read_gicreg(ICC_SRE_EL1);
/* /*
* To check whether we have a MMIO-based (GICv2 compatible) * To check whether we have a MMIO-based (GICv2 compatible)
* CPU interface, we need to disable the system register * CPU interface, we need to disable the system register
@ -471,6 +494,16 @@ u64 __vgic_v3_get_gic_config(void)
return val; return val;
} }
static void __vgic_v3_compat_mode_enable(void)
{
if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
return;
sysreg_clear_set_s(SYS_ICH_VCTLR_EL2, 0, ICH_VCTLR_EL2_V3);
/* Wait for V3 to become enabled */
isb();
}
static u64 __vgic_v3_read_vmcr(void) static u64 __vgic_v3_read_vmcr(void)
{ {
return read_gicreg(ICH_VMCR_EL2); return read_gicreg(ICH_VMCR_EL2);
@ -490,6 +523,8 @@ void __vgic_v3_save_vmcr_aprs(struct vgic_v3_cpu_if *cpu_if)
void __vgic_v3_restore_vmcr_aprs(struct vgic_v3_cpu_if *cpu_if) void __vgic_v3_restore_vmcr_aprs(struct vgic_v3_cpu_if *cpu_if)
{ {
__vgic_v3_compat_mode_enable();
/* /*
* If dealing with a GICv2 emulation on GICv3, VMCR_EL2.VFIQen * If dealing with a GICv2 emulation on GICv3, VMCR_EL2.VFIQen
* is dependent on ICC_SRE_EL1.SRE, and we have to perform the * is dependent on ICC_SRE_EL1.SRE, and we have to perform the

View File

@ -1813,7 +1813,7 @@ static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, CSV3, IMP); val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, CSV3, IMP);
} }
if (kvm_vgic_global_state.type == VGIC_V3) { if (vgic_is_v3(vcpu->kvm)) {
val &= ~ID_AA64PFR0_EL1_GIC_MASK; val &= ~ID_AA64PFR0_EL1_GIC_MASK;
val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, GIC, IMP); val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, GIC, IMP);
} }
@ -1955,6 +1955,14 @@ static int set_id_aa64pfr0_el1(struct kvm_vcpu *vcpu,
(vcpu_has_nv(vcpu) && !FIELD_GET(ID_AA64PFR0_EL1_EL2, user_val))) (vcpu_has_nv(vcpu) && !FIELD_GET(ID_AA64PFR0_EL1_EL2, user_val)))
return -EINVAL; return -EINVAL;
/*
* If we are running on a GICv5 host and support FEAT_GCIE_LEGACY, then
* we support GICv3. Fail attempts to do anything but set that to IMP.
*/
if (vgic_is_v3_compat(vcpu->kvm) &&
FIELD_GET(ID_AA64PFR0_EL1_GIC_MASK, user_val) != ID_AA64PFR0_EL1_GIC_IMP)
return -EINVAL;
return set_id_reg(vcpu, rd, user_val); return set_id_reg(vcpu, rd, user_val);
} }

View File

@ -674,10 +674,12 @@ void kvm_vgic_init_cpu_hardware(void)
* We want to make sure the list registers start out clear so that we * We want to make sure the list registers start out clear so that we
* only have the program the used registers. * only have the program the used registers.
*/ */
if (kvm_vgic_global_state.type == VGIC_V2) if (kvm_vgic_global_state.type == VGIC_V2) {
vgic_v2_init_lrs(); vgic_v2_init_lrs();
else } else if (kvm_vgic_global_state.type == VGIC_V3 ||
kvm_vgic_global_state.has_gcie_v3_compat) {
kvm_call_hyp(__vgic_v3_init_lrs); kvm_call_hyp(__vgic_v3_init_lrs);
}
} }
/** /**
@ -722,6 +724,9 @@ int kvm_vgic_hyp_init(void)
kvm_info("GIC system register CPU interface enabled\n"); kvm_info("GIC system register CPU interface enabled\n");
} }
break; break;
case GIC_V5:
ret = vgic_v5_probe(gic_kvm_info);
break;
default: default:
ret = -ENODEV; ret = -ENODEV;
} }

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <kvm/arm_vgic.h>
#include <linux/irqchip/arm-vgic-info.h>
#include "vgic.h"
/*
* Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
* Currently only supports GICv3-based VMs on a GICv5 host, and hence only
* registers a VGIC_V3 device.
*/
int vgic_v5_probe(const struct gic_kvm_info *info)
{
u64 ich_vtr_el2;
int ret;
if (!info->has_gcie_v3_compat)
return -ENODEV;
kvm_vgic_global_state.type = VGIC_V5;
kvm_vgic_global_state.has_gcie_v3_compat = true;
/* We only support v3 compat mode - use vGICv3 limits */
kvm_vgic_global_state.max_gic_vcpus = VGIC_V3_MAX_CPUS;
kvm_vgic_global_state.vcpu_base = 0;
kvm_vgic_global_state.vctrl_base = NULL;
kvm_vgic_global_state.can_emulate_gicv2 = false;
kvm_vgic_global_state.has_gicv4 = false;
kvm_vgic_global_state.has_gicv4_1 = false;
ich_vtr_el2 = kvm_call_hyp_ret(__vgic_v3_get_gic_config);
kvm_vgic_global_state.ich_vtr_el2 = (u32)ich_vtr_el2;
/*
* The ListRegs field is 5 bits, but there is an architectural
* maximum of 16 list registers. Just ignore bit 4...
*/
kvm_vgic_global_state.nr_lr = (ich_vtr_el2 & 0xf) + 1;
ret = kvm_register_vgic_device(KVM_DEV_TYPE_ARM_VGIC_V3);
if (ret) {
kvm_err("Cannot register GICv3-legacy KVM device.\n");
return ret;
}
static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
kvm_info("GCIE legacy system register CPU interface\n");
return 0;
}

View File

@ -308,6 +308,8 @@ int vgic_init(struct kvm *kvm);
void vgic_debug_init(struct kvm *kvm); void vgic_debug_init(struct kvm *kvm);
void vgic_debug_destroy(struct kvm *kvm); void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
static inline int vgic_v3_max_apr_idx(struct kvm_vcpu *vcpu) static inline int vgic_v3_max_apr_idx(struct kvm_vcpu *vcpu)
{ {
struct vgic_cpu *cpu_if = &vcpu->arch.vgic_cpu; struct vgic_cpu *cpu_if = &vcpu->arch.vgic_cpu;
@ -389,6 +391,17 @@ void vgic_v3_put_nested(struct kvm_vcpu *vcpu);
void vgic_v3_handle_nested_maint_irq(struct kvm_vcpu *vcpu); void vgic_v3_handle_nested_maint_irq(struct kvm_vcpu *vcpu);
void vgic_v3_nested_update_mi(struct kvm_vcpu *vcpu); void vgic_v3_nested_update_mi(struct kvm_vcpu *vcpu);
static inline bool vgic_is_v3_compat(struct kvm *kvm)
{
return cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF) &&
kvm_vgic_global_state.has_gcie_v3_compat;
}
static inline bool vgic_is_v3(struct kvm *kvm)
{
return kvm_vgic_global_state.type == VGIC_V3 || vgic_is_v3_compat(kvm);
}
int vgic_its_debug_init(struct kvm_device *dev); int vgic_its_debug_init(struct kvm_device *dev);
void vgic_its_debug_destroy(struct kvm_device *dev); void vgic_its_debug_destroy(struct kvm_device *dev);

View File

@ -4530,6 +4530,12 @@ Field 1 U
Field 0 EOI Field 0 EOI
EndSysreg EndSysreg
Sysreg ICH_VCTLR_EL2 3 4 12 11 4
Res0 63:2
Field 1 V3
Field 0 En
EndSysreg
Sysreg CONTEXTIDR_EL2 3 4 13 0 1 Sysreg CONTEXTIDR_EL2 3 4 13 0 1
Fields CONTEXTIDR_ELx Fields CONTEXTIDR_ELx
EndSysreg EndSysreg

View File

@ -13,6 +13,7 @@
#include <linux/irqchip.h> #include <linux/irqchip.h>
#include <linux/irqchip/arm-gic-v5.h> #include <linux/irqchip/arm-gic-v5.h>
#include <linux/irqchip/arm-vgic-info.h>
#include <asm/cpufeature.h> #include <asm/cpufeature.h>
#include <asm/exception.h> #include <asm/exception.h>
@ -222,6 +223,12 @@ static void gicv5_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)
static void gicv5_ppi_irq_eoi(struct irq_data *d) static void gicv5_ppi_irq_eoi(struct irq_data *d)
{ {
/* Skip deactivate for forwarded PPI interrupts */
if (irqd_is_forwarded_to_vcpu(d)) {
gic_insn(0, CDEOI);
return;
}
gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_PPI); gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_PPI);
} }
@ -503,6 +510,16 @@ static bool gicv5_ppi_irq_is_level(irq_hw_number_t hwirq)
return !!(read_ppi_sysreg_s(hwirq, PPI_HM) & bit); return !!(read_ppi_sysreg_s(hwirq, PPI_HM) & bit);
} }
static int gicv5_ppi_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu)
{
if (vcpu)
irqd_set_forwarded_to_vcpu(d);
else
irqd_clr_forwarded_to_vcpu(d);
return 0;
}
static const struct irq_chip gicv5_ppi_irq_chip = { static const struct irq_chip gicv5_ppi_irq_chip = {
.name = "GICv5-PPI", .name = "GICv5-PPI",
.irq_mask = gicv5_ppi_irq_mask, .irq_mask = gicv5_ppi_irq_mask,
@ -510,6 +527,7 @@ static const struct irq_chip gicv5_ppi_irq_chip = {
.irq_eoi = gicv5_ppi_irq_eoi, .irq_eoi = gicv5_ppi_irq_eoi,
.irq_get_irqchip_state = gicv5_ppi_irq_get_irqchip_state, .irq_get_irqchip_state = gicv5_ppi_irq_get_irqchip_state,
.irq_set_irqchip_state = gicv5_ppi_irq_set_irqchip_state, .irq_set_irqchip_state = gicv5_ppi_irq_set_irqchip_state,
.irq_set_vcpu_affinity = gicv5_ppi_irq_set_vcpu_affinity,
.flags = IRQCHIP_SKIP_SET_WAKE | .flags = IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND, IRQCHIP_MASK_ON_SUSPEND,
}; };
@ -1041,6 +1059,36 @@ static void gicv5_set_cpuif_idbits(void)
} }
} }
#ifdef CONFIG_KVM
static struct gic_kvm_info gic_v5_kvm_info __initdata;
static bool __init gicv5_cpuif_has_gcie_legacy(void)
{
u64 idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
return !!FIELD_GET(ICC_IDR0_EL1_GCIE_LEGACY, idr0);
}
static void __init gic_of_setup_kvm_info(struct device_node *node)
{
gic_v5_kvm_info.type = GIC_V5;
gic_v5_kvm_info.has_gcie_v3_compat = gicv5_cpuif_has_gcie_legacy();
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
if (!gic_v5_kvm_info.maint_irq) {
pr_warn("cannot find GICv5 virtual CPU interface maintenance interrupt\n");
return;
}
vgic_set_kvm_info(&gic_v5_kvm_info);
}
#else
static inline void __init gic_of_setup_kvm_info(struct device_node *node)
{
}
#endif // CONFIG_KVM
static int __init gicv5_of_init(struct device_node *node, struct device_node *parent) static int __init gicv5_of_init(struct device_node *node, struct device_node *parent)
{ {
int ret = gicv5_irs_of_probe(node); int ret = gicv5_irs_of_probe(node);
@ -1073,6 +1121,8 @@ static int __init gicv5_of_init(struct device_node *node, struct device_node *pa
gicv5_irs_its_probe(); gicv5_irs_its_probe();
gic_of_setup_kvm_info(node);
return 0; return 0;
out_int: out_int:

View File

@ -38,6 +38,7 @@
enum vgic_type { enum vgic_type {
VGIC_V2, /* Good ol' GICv2 */ VGIC_V2, /* Good ol' GICv2 */
VGIC_V3, /* New fancy GICv3 */ VGIC_V3, /* New fancy GICv3 */
VGIC_V5, /* Newer, fancier GICv5 */
}; };
/* same for all guests, as depending only on the _host's_ GIC model */ /* same for all guests, as depending only on the _host's_ GIC model */
@ -77,9 +78,12 @@ struct vgic_global {
/* Pseudo GICv3 from outer space */ /* Pseudo GICv3 from outer space */
bool no_hw_deactivation; bool no_hw_deactivation;
/* GIC system register CPU interface */ /* GICv3 system register CPU interface */
struct static_key_false gicv3_cpuif; struct static_key_false gicv3_cpuif;
/* GICv3 compat mode on a GICv5 host */
bool has_gcie_v3_compat;
u32 ich_vtr_el2; u32 ich_vtr_el2;
}; };

View File

@ -15,6 +15,8 @@ enum gic_type {
GIC_V2, GIC_V2,
/* Full GICv3, optionally with v2 compat */ /* Full GICv3, optionally with v2 compat */
GIC_V3, GIC_V3,
/* Full GICv5, optionally with v3 compat */
GIC_V5,
}; };
struct gic_kvm_info { struct gic_kvm_info {
@ -34,6 +36,8 @@ struct gic_kvm_info {
bool has_v4_1; bool has_v4_1;
/* Deactivation impared, subpar stuff */ /* Deactivation impared, subpar stuff */
bool no_hw_deactivation; bool no_hw_deactivation;
/* v3 compat support (GICv5 hosts, only) */
bool has_gcie_v3_compat;
}; };
#ifdef CONFIG_KVM #ifdef CONFIG_KVM