KVM: arm64: GICv2: Handle deactivation via GICV_DIR traps

Add the plumbing of GICv2 interrupt deactivation via GICV_DIR.
This requires adding a new device so that we can easily decode
the DIR address.

The deactivation itself is very similar to the GICv3 version.

Tested-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Tested-by: Mark Brown <broonie@kernel.org>
Link: https://msgid.link/20251120172540.2267180-39-maz@kernel.org
Signed-off-by: Oliver Upton <oupton@kernel.org>
pull/1354/merge
Marc Zyngier 2025-11-20 17:25:28 +00:00 committed by Oliver Upton
parent 281c6c06e2
commit 255de897e7
5 changed files with 112 additions and 0 deletions

View File

@ -359,6 +359,16 @@ static void vgic_mmio_write_vcpuif(struct kvm_vcpu *vcpu,
vgic_set_vmcr(vcpu, &vmcr);
}
static void vgic_mmio_write_dir(struct kvm_vcpu *vcpu,
gpa_t addr, unsigned int len,
unsigned long val)
{
if (kvm_vgic_global_state.type == VGIC_V2)
vgic_v2_deactivate(vcpu, val);
else
vgic_v3_deactivate(vcpu, val);
}
static unsigned long vgic_mmio_read_apr(struct kvm_vcpu *vcpu,
gpa_t addr, unsigned int len)
{
@ -482,6 +492,10 @@ static const struct vgic_register_region vgic_v2_cpu_registers[] = {
REGISTER_DESC_WITH_LENGTH(GIC_CPU_IDENT,
vgic_mmio_read_vcpuif, vgic_mmio_write_vcpuif, 4,
VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH_UACCESS(GIC_CPU_DEACTIVATE,
vgic_mmio_read_raz, vgic_mmio_write_dir,
vgic_mmio_read_raz, vgic_mmio_uaccess_write_wi,
4, VGIC_ACCESS_32bit),
};
unsigned int vgic_v2_init_dist_iodev(struct vgic_io_device *dev)
@ -494,6 +508,16 @@ unsigned int vgic_v2_init_dist_iodev(struct vgic_io_device *dev)
return SZ_4K;
}
unsigned int vgic_v2_init_cpuif_iodev(struct vgic_io_device *dev)
{
dev->regions = vgic_v2_cpu_registers;
dev->nr_regions = ARRAY_SIZE(vgic_v2_cpu_registers);
kvm_iodevice_init(&dev->dev, &kvm_io_gic_ops);
return KVM_VGIC_V2_CPU_SIZE;
}
int vgic_v2_has_attr_regs(struct kvm_device *dev, struct kvm_device_attr *attr)
{
const struct vgic_register_region *region;

View File

@ -213,6 +213,7 @@ void vgic_write_irq_line_level_info(struct kvm_vcpu *vcpu, u32 intid,
const u32 val);
unsigned int vgic_v2_init_dist_iodev(struct vgic_io_device *dev);
unsigned int vgic_v2_init_cpuif_iodev(struct vgic_io_device *dev);
unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev);

View File

@ -9,6 +9,7 @@
#include <kvm/arm_vgic.h>
#include <asm/kvm_mmu.h>
#include "vgic-mmio.h"
#include "vgic.h"
static inline void vgic_v2_write_lr(int lr, u32 val)
@ -147,6 +148,79 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu)
cpuif->used_lrs = 0;
}
void vgic_v2_deactivate(struct kvm_vcpu *vcpu, u32 val)
{
struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
struct vgic_v2_cpu_if *cpuif = &vgic_cpu->vgic_v2;
struct kvm_vcpu *target_vcpu = NULL;
bool mmio = false;
struct vgic_irq *irq;
unsigned long flags;
u64 lr = 0;
u8 cpuid;
/* Snapshot CPUID, and remove it from the INTID */
cpuid = FIELD_GET(GENMASK_ULL(12, 10), val);
val &= ~GENMASK_ULL(12, 10);
/* We only deal with DIR when EOIMode==1 */
if (!(cpuif->vgic_vmcr & GICH_VMCR_EOI_MODE_MASK))
return;
/* Make sure we're in the same context as LR handling */
local_irq_save(flags);
irq = vgic_get_vcpu_irq(vcpu, val);
if (WARN_ON_ONCE(!irq))
goto out;
/* See the corresponding v3 code for the rationale */
scoped_guard(raw_spinlock, &irq->irq_lock) {
target_vcpu = irq->vcpu;
/* Not on any ap_list? */
if (!target_vcpu)
goto put;
/*
* Urgh. We're deactivating something that we cannot
* observe yet... Big hammer time.
*/
if (irq->on_lr) {
mmio = true;
goto put;
}
/* SGI: check that the cpuid matches */
if (val < VGIC_NR_SGIS && irq->active_source != cpuid) {
target_vcpu = NULL;
goto put;
}
/* (with a Dalek voice) DEACTIVATE!!!! */
lr = vgic_v2_compute_lr(vcpu, irq) & ~GICH_LR_ACTIVE_BIT;
}
if (lr & GICH_LR_HW)
writel_relaxed(FIELD_GET(GICH_LR_PHYSID_CPUID, lr),
kvm_vgic_global_state.gicc_base + GIC_CPU_DEACTIVATE);
vgic_v2_fold_lr(vcpu, lr);
put:
vgic_put_irq(vcpu->kvm, irq);
out:
local_irq_restore(flags);
if (mmio)
vgic_mmio_write_cactive(vcpu, (val / 32) * 4, 4, BIT(val % 32));
/* Force the ap_list to be pruned */
if (target_vcpu)
kvm_make_request(KVM_REQ_VGIC_PROCESS_UPDATE, target_vcpu);
}
static u32 vgic_v2_compute_lr(struct kvm_vcpu *vcpu, struct vgic_irq *irq)
{
u32 val = irq->intid;
@ -346,6 +420,7 @@ static bool vgic_v2_check_base(gpa_t dist_base, gpa_t cpu_base)
int vgic_v2_map_resources(struct kvm *kvm)
{
struct vgic_dist *dist = &kvm->arch.vgic;
unsigned int len;
int ret = 0;
if (IS_VGIC_ADDR_UNDEF(dist->vgic_dist_base) ||
@ -369,6 +444,16 @@ int vgic_v2_map_resources(struct kvm *kvm)
return ret;
}
len = vgic_v2_init_cpuif_iodev(&dist->cpuif_iodev);
dist->cpuif_iodev.base_addr = dist->vgic_cpu_base;
dist->cpuif_iodev.iodev_type = IODEV_CPUIF;
dist->cpuif_iodev.redist_vcpu = NULL;
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, dist->vgic_cpu_base,
len, &dist->cpuif_iodev.dev);
if (ret)
return ret;
if (!static_branch_unlikely(&vgic_v2_cpuif_trap)) {
ret = kvm_phys_addr_ioremap(kvm, dist->vgic_cpu_base,
kvm_vgic_global_state.vcpu_base,

View File

@ -277,6 +277,7 @@ int vgic_check_iorange(struct kvm *kvm, phys_addr_t ioaddr,
void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu);
void vgic_v2_populate_lr(struct kvm_vcpu *vcpu, struct vgic_irq *irq, int lr);
void vgic_v2_deactivate(struct kvm_vcpu *vcpu, u32 val);
void vgic_v2_clear_lr(struct kvm_vcpu *vcpu, int lr);
void vgic_v2_configure_hcr(struct kvm_vcpu *vcpu, struct ap_list_summary *als);
int vgic_v2_has_attr_regs(struct kvm_device *dev, struct kvm_device_attr *attr);

View File

@ -287,6 +287,7 @@ struct vgic_dist {
struct vgic_irq *spis;
struct vgic_io_device dist_iodev;
struct vgic_io_device cpuif_iodev;
bool has_its;
bool table_write_in_progress;