Commit cd4f6ee9 authored by Marc Zyngier's avatar Marc Zyngier Committed by Oliver Upton
Browse files

KVM: arm64: GICv3: Handle deactivation via ICV_DIR_EL1 traps



Deactivation via ICV_DIR_EL1 is both relatively straightforward
(we have the interrupt that needs deactivation) and really awkward.

The main issue is that the interrupt may either be in an LR on
another CPU, or ourside of any LR.

In the former case, we process the deactivation is if ot was
a write to GICD_CACTIVERn, which is already implemented as a big
hammer IPI'ing all vcpus. In the latter case, we just perform
a normal deactivation, similar to what we do for EOImode==0.

Another annoying aspect is that we need to tell the CPU owning
the interrupt that its ap_list needs laudering. We use a brand new
vcpu request to that effect.

Note that this doesn't address deactivation via the GICV MMIO view,
which will be taken care of in a later change.

Tested-by: default avatarFuad Tabba <tabba@google.com>
Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
Tested-by: default avatarMark Brown <broonie@kernel.org>
Link: https://msgid.link/20251120172540.2267180-29-maz@kernel.org


Signed-off-by: default avatarOliver Upton <oupton@kernel.org>
parent 3cfd59f8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@
#define KVM_REQ_NESTED_S2_UNMAP		KVM_ARCH_REQ(8)
#define KVM_REQ_GUEST_HYP_IRQ_PENDING	KVM_ARCH_REQ(9)
#define KVM_REQ_MAP_L1_VNCR_EL2		KVM_ARCH_REQ(10)
#define KVM_REQ_VGIC_PROCESS_UPDATE	KVM_ARCH_REQ(11)

#define KVM_DIRTY_LOG_MANUAL_CAPS   (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
				     KVM_DIRTY_LOG_INITIALLY_SET)
+4 −0
Original line number Diff line number Diff line
@@ -1041,6 +1041,10 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
		 */
		kvm_check_request(KVM_REQ_IRQ_PENDING, vcpu);

		/* Process interrupts deactivated through a trap */
		if (kvm_check_request(KVM_REQ_VGIC_PROCESS_UPDATE, vcpu))
			kvm_vgic_process_async_update(vcpu);

		if (kvm_check_request(KVM_REQ_RECORD_STEAL, vcpu))
			kvm_update_stolen_time(vcpu);

+3 −0
Original line number Diff line number Diff line
@@ -1247,6 +1247,9 @@ int __vgic_v3_perform_cpuif_access(struct kvm_vcpu *vcpu)
	case SYS_ICC_DIR_EL1:
		if (unlikely(is_read))
			return 0;
		/* Full exit if required to handle overflow deactivation... */
		if (vcpu->arch.vgic_cpu.vgic_v3.vgic_hcr & ICH_HCR_EL2_TDIR)
			return 0;
		fn = __vgic_v3_write_dir;
		break;
	case SYS_ICC_RPR_EL1:
+17 −2
Original line number Diff line number Diff line
@@ -666,6 +666,21 @@ static bool access_gic_sre(struct kvm_vcpu *vcpu,
	return true;
}

static bool access_gic_dir(struct kvm_vcpu *vcpu,
			   struct sys_reg_params *p,
			   const struct sys_reg_desc *r)
{
	if (!kvm_has_gicv3(vcpu->kvm))
		return undef_access(vcpu, p, r);

	if (!p->is_write)
		return undef_access(vcpu, p, r);

	vgic_v3_deactivate(vcpu, p->regval);

	return true;
}

static bool trap_raz_wi(struct kvm_vcpu *vcpu,
			struct sys_reg_params *p,
			const struct sys_reg_desc *r)
@@ -3370,7 +3385,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
	{ SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
	{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
	{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
	{ SYS_DESC(SYS_ICC_DIR_EL1), undef_access },
	{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
	{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
	{ SYS_DESC(SYS_ICC_SGI1R_EL1), access_gic_sgi },
	{ SYS_DESC(SYS_ICC_ASGI1R_EL1), access_gic_sgi },
@@ -4495,7 +4510,7 @@ static const struct sys_reg_desc cp15_regs[] = {
	{ CP15_SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
	{ CP15_SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
	{ CP15_SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
	{ CP15_SYS_DESC(SYS_ICC_DIR_EL1), undef_access },
	{ CP15_SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
	{ CP15_SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
	{ CP15_SYS_DESC(SYS_ICC_IAR1_EL1), undef_access },
	{ CP15_SYS_DESC(SYS_ICC_EOIR1_EL1), undef_access },
+85 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <asm/kvm_mmu.h>
#include <asm/kvm_asm.h>

#include "vgic-mmio.h"
#include "vgic.h"

static bool group0_trap;
@@ -171,6 +172,90 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu)
	cpuif->used_lrs = 0;
}

void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val)
{
	struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
	struct vgic_v3_cpu_if *cpuif = &vgic_cpu->vgic_v3;
	struct kvm_vcpu *target_vcpu = NULL;
	struct vgic_irq *irq;
	unsigned long flags;
	bool mmio = false;
	u64 lr = 0;

	/*
	 * We only deal with DIR when EOIMode==1, and only for SGI,
	 * PPI or SPI.
	 */
	if (!(cpuif->vgic_vmcr & ICH_VMCR_EOIM_MASK) ||
	    val >= vcpu->kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)
		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;

	/*
	 * EOIMode=1: we must rely on traps to handle deactivate of
	 * overflowing interrupts, as there is no ordering guarantee and
	 * EOIcount isn't being incremented. Priority drop will have taken
	 * place, as ICV_EOIxR_EL1 only affects the APRs and not the LRs.
	 *
	 * Three possibities:
	 *
	 * - The irq is not queued on any CPU, and there is nothing to
	 *   do,
	 *
	 * - Or the irq is in an LR, meaning that its state is not
	 *   directly observable. Treat it bluntly by making it as if
	 *   this was a write to GICD_ICACTIVER, which will force an
	 *   exit on all vcpus. If it hurts, don't do that.
	 *
	 * - Or the irq is active, but not in an LR, and we can
	 *   directly deactivate it by building a pseudo-LR, fold it,
	 *   and queue a request to prune the resulting ap_list,
	 */
	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;
		}

		/* (with a Dalek voice) DEACTIVATE!!!! */
		lr = vgic_v3_compute_lr(vcpu, irq) & ~ICH_LR_ACTIVE_BIT;
	}

	if (lr & ICH_LR_HW)
		vgic_v3_deactivate_phys(FIELD_GET(ICH_LR_PHYS_ID_MASK, lr));

	vgic_v3_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);
}

/* Requires the irq to be locked already */
static u64 vgic_v3_compute_lr(struct kvm_vcpu *vcpu, struct vgic_irq *irq)
{
Loading