Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Fuad Tabba <fuad.tabba@linux.dev>
To: Marc Zyngier <maz@kernel.org>, Oliver Upton <oupton@kernel.org>
Cc: Will Deacon <will@kernel.org>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Sean Christopherson <seanjc@google.com>,
	Shuah Khan <shuah@kernel.org>, Joey Gouly <joey.gouly@arm.com>,
	Steffen Eiden <seiden@linux.ibm.com>,
	Suzuki K Poulose <suzuki.poulose@arm.com>,
	Zenghui Yu <yuzenghui@huawei.com>,
	Vincent Donnefort <vdonnefort@google.com>,
	Quentin Perret <qperret@google.com>,
	Fuad Tabba <tabba@google.com>,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, linux-kselftest@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH 13/16] KVM: arm64: selftests: Add pkvm_irq interrupt test
Date: Wed,  4 Nov 2026 08:48:39 +0000	[thread overview]
Message-ID: <20261104084842.132944-14-fuad.tabba@linux.dev> (raw)
In-Reply-To: <20261104084842.132944-1-fuad.tabba@linux.dev>

The host emulates a protected guest's vGIC, so the list registers are
copied between the host and EL2 each time the host runs the vCPU. If the
copy back is lost, the host never sees the guest's EOI and presents the
interrupt again. EL2 keeps its own copy of the guest's virtual timer
state, which the host can't change, and restores it each time the host
runs the vCPU.

Check that the virtual timer's PPI arrives exactly once, with CVAL and
CTL kept across exits before and while it's pending, that SGIs pass
between two vCPUs, and that the timer wakes a vCPU in WFI.

Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
 tools/testing/selftests/kvm/Makefile.kvm     |   1 +
 tools/testing/selftests/kvm/arm64/pkvm_irq.c | 268 +++++++++++++++++++
 2 files changed, 269 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/arm64/pkvm_irq.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 1e40e793aa6f3..13998964facd5 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -192,6 +192,7 @@ TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
 TEST_GEN_PROGS_arm64 += arm64/page_fault_test
 TEST_GEN_PROGS_arm64 += arm64/pkvm_host_abi
 TEST_GEN_PROGS_arm64 += arm64/pkvm_id_regs
+TEST_GEN_PROGS_arm64 += arm64/pkvm_irq
 TEST_GEN_PROGS_arm64 += arm64/pkvm_mem_share
 TEST_GEN_PROGS_arm64 += arm64/pkvm_psci
 TEST_GEN_PROGS_arm64 += arm64/pkvm_smccc
diff --git a/tools/testing/selftests/kvm/arm64/pkvm_irq.c b/tools/testing/selftests/kvm/arm64/pkvm_irq.c
new file mode 100644
index 0000000000000..f92fa6f0de8de
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/pkvm_irq.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pkvm_irq - the virtual timer's PPI and SGIs in a protected VM.
+ *
+ * Copyright (C) 2026 Google LLC
+ * Author: Fuad Tabba <fuad.tabba@linux.dev>
+ */
+
+#include <pthread.h>
+
+#include <linux/bitfield.h>
+#include <linux/psci.h>
+#include <asm/cputype.h>
+
+#include "arch_timer.h"
+#include "gic.h"
+#include "gic_v3.h"
+#include "kvm_util.h"
+#include "pkvm.h"
+#include "processor.h"
+#include "test_util.h"
+
+#define TIMER_MS	10
+#define WAIT_MS		30000
+#define NR_EXITS	16
+#define SGI_ROUNDS	16
+
+#define SGI_PING	1
+#define SGI_PONG	2
+
+/* Set in the guest's copy by the host before the first run. */
+static u32 vtimer_irq;
+static u64 boot_entry;
+static u64 vcpu_mpidr[2];
+static u64 vcpu_ctx[2];
+
+static u32 irqs[2];
+static u32 last_intid[2];
+static u32 ready;
+
+#define wait_until(cond)							\
+do {										\
+	u64 __end = timer_get_cntct(VIRTUAL) + msec_to_cycles(WAIT_MS);	\
+										\
+	while (!(cond))								\
+		__GUEST_ASSERT(timer_get_cntct(VIRTUAL) < __end,		\
+			       "timed out waiting for %s", #cond);		\
+} while (0)
+
+static void guest_irq_handler(struct ex_regs *regs)
+{
+	u32 intid = gic_get_and_ack_irq();
+	u32 cpu = guest_get_vcpuid();
+
+	if (intid == IAR_SPURIOUS)
+		return;
+
+	if (intid == vtimer_irq) {
+		__GUEST_ASSERT(timer_get_ctl(VIRTUAL) & CTL_ISTATUS, "CNTV_CTL 0x%x",
+			       timer_get_ctl(VIRTUAL));
+		GUEST_ASSERT(timer_get_cntct(VIRTUAL) >= timer_get_cval(VIRTUAL));
+		/* Mask the timer before the EOI, or its level-sensitive PPI is taken again. */
+		timer_set_ctl(VIRTUAL, CTL_IMASK);
+	}
+
+	WRITE_ONCE(last_intid[cpu], intid);
+	gic_set_eoi(intid);
+	WRITE_ONCE(irqs[cpu], irqs[cpu] + 1);
+}
+
+static void guest_irq_init(unsigned int nr_cpus)
+{
+	local_irq_disable();
+	pkvm_guest_init();
+	gic_init(GIC_V3, nr_cpus);
+}
+
+static u64 start_vtimer(void)
+{
+	u64 cval = timer_get_cntct(VIRTUAL) + msec_to_cycles(TIMER_MS);
+
+	gic_irq_enable(vtimer_irq);
+	timer_set_cval(VIRTUAL, cval);
+	timer_set_ctl(VIRTUAL, CTL_ENABLE);
+	return cval;
+}
+
+static void guest_vtimer(void)
+{
+	u64 cval;
+	int i;
+
+	guest_irq_init(1);
+	cval = start_vtimer();
+
+	/* Exits before the timer fires, then with its interrupt pending but masked. */
+	for (i = 0; i < NR_EXITS; i++)
+		GUEST_SYNC(i);
+	wait_until(timer_get_ctl(VIRTUAL) & CTL_ISTATUS);
+	for (i = 0; i < NR_EXITS; i++)
+		GUEST_SYNC(i);
+
+	GUEST_ASSERT_EQ(timer_get_cval(VIRTUAL), cval);
+	GUEST_ASSERT_EQ(timer_get_ctl(VIRTUAL), CTL_ENABLE | CTL_ISTATUS);
+	GUEST_ASSERT_EQ(READ_ONCE(irqs[0]), 0);
+
+	local_irq_enable();
+	wait_until(READ_ONCE(irqs[0]));
+
+	/* The EOI must reach the host, or it presents the interrupt again. */
+	for (i = 0; i < NR_EXITS; i++)
+		GUEST_SYNC(i);
+	local_irq_disable();
+
+	GUEST_ASSERT_EQ(READ_ONCE(irqs[0]), 1);
+	GUEST_ASSERT_EQ(READ_ONCE(last_intid[0]), vtimer_irq);
+	GUEST_DONE();
+}
+
+static void send_sgi(int id, u32 intid)
+{
+	u64 mpidr = vcpu_mpidr[id];
+
+	/* The target reads this vCPU's counters once it has taken the SGI. */
+	dsb(ishst);
+	write_sysreg_s(FIELD_PREP(ICC_SGI1R_SGI_ID_MASK, intid) |
+		       FIELD_PREP(ICC_SGI1R_AFFINITY_1_MASK, MPIDR_AFFINITY_LEVEL(mpidr, 1)) |
+		       FIELD_PREP(ICC_SGI1R_TARGET_LIST_MASK, BIT(MPIDR_AFFINITY_LEVEL(mpidr, 0))),
+		       SYS_ICC_SGI1R_EL1);
+	isb();
+}
+
+static void guest_sgi_primary(void)
+{
+	u32 k;
+
+	guest_irq_init(2);
+	gic_irq_enable(SGI_PONG);
+
+	GUEST_ASSERT_EQ(pkvm_hvc(PSCI_0_2_FN64_CPU_ON, vcpu_mpidr[1], boot_entry, vcpu_ctx[1]),
+			PSCI_RET_SUCCESS);
+	wait_until(READ_ONCE(ready));
+
+	local_irq_enable();
+	for (k = 1; k <= SGI_ROUNDS; k++) {
+		send_sgi(1, SGI_PING);
+
+		/* vCPU 1 sends PONG only once it has taken PING, so neither is in flight. */
+		wait_until(READ_ONCE(irqs[0]) >= k);
+		GUEST_ASSERT_EQ(READ_ONCE(irqs[0]), k);
+		GUEST_ASSERT_EQ(READ_ONCE(irqs[1]), k);
+		GUEST_ASSERT_EQ(READ_ONCE(last_intid[0]), SGI_PONG);
+		GUEST_ASSERT_EQ(READ_ONCE(last_intid[1]), SGI_PING);
+	}
+	local_irq_disable();
+
+	GUEST_DONE();
+}
+
+/* The primary's guest_irq_init() set up ucall and the distributor. */
+static void guest_sgi_secondary(void)
+{
+	u32 k;
+
+	gic_init(GIC_V3, 2);
+	gic_irq_enable(SGI_PING);
+	local_irq_enable();
+	WRITE_ONCE(ready, 1);
+
+	for (k = 1; k <= SGI_ROUNDS; k++) {
+		wait_until(READ_ONCE(irqs[1]) >= k);
+		send_sgi(0, SGI_PONG);
+	}
+	local_irq_disable();
+
+	GUEST_DONE();
+}
+
+static void guest_wfi(void)
+{
+	guest_irq_init(1);
+	start_vtimer();
+
+	/* WFI can complete before the timer fires, so wait in it until the handler has run. */
+	while (!READ_ONCE(irqs[0])) {
+		wfi();
+		local_irq_enable();
+		isb(); /* handle IRQ */
+		local_irq_disable();
+	}
+
+	GUEST_ASSERT_EQ(READ_ONCE(irqs[0]), 1);
+	GUEST_ASSERT_EQ(READ_ONCE(last_intid[0]), vtimer_irq);
+	GUEST_DONE();
+}
+
+static void irq_vm_setup(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	vm_install_exception_handler(vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
+	write_guest_global(vm, vtimer_irq, vcpu_get_vtimer_irq(vcpu));
+}
+
+static void test_one_vcpu(void *guest_code)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create_with_one_vcpu(&vcpu, guest_code);
+	irq_vm_setup(vm, vcpu);
+
+	pkvm_run_loop(vcpu, NULL);
+
+	kvm_vm_free(vm);
+}
+
+static void wire_mpidr(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+	u64 mpidr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_MPIDR_EL1));
+
+	write_guest_global(vm, vcpu_mpidr[vcpu->id], mpidr & MPIDR_HWID_BITMASK);
+}
+
+static void *vcpu_thread(void *vcpu)
+{
+	pkvm_run_loop(vcpu, NULL);
+	return NULL;
+}
+
+static void test_sgi(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	pthread_t thread;
+	struct kvm_vm *vm;
+	gpa_t ctx;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+	irq_vm_setup(vm, vcpus[0]);
+
+	pkvm_vcpu_setup_boot(vcpus[0], guest_sgi_primary);
+	wire_mpidr(vm, vcpus[0]);
+	wire_mpidr(vm, vcpus[1]);
+	write_guest_global(vm, boot_entry, pkvm_idmap_gpa(vm));
+	ctx = pkvm_vcpu_setup_boot_secondary(vcpus[1], guest_sgi_secondary);
+	write_guest_global(vm, vcpu_ctx[1], ctx);
+
+	pthread_create(&thread, NULL, vcpu_thread, vcpus[1]);
+	pkvm_run_loop(vcpus[0], NULL);
+	pthread_join(thread, NULL);
+
+	kvm_vm_free(vm);
+}
+
+int main(void)
+{
+	TEST_REQUIRE(pkvm_supported());
+
+	ksft_print_header();
+	ksft_set_plan(3);
+
+	test_one_vcpu(guest_vtimer);
+	ksft_test_result_pass("virtual timer PPI across exits\n");
+	test_sgi();
+	ksft_test_result_pass("SGIs between two vCPUs: %d rounds\n", SGI_ROUNDS);
+	test_one_vcpu(guest_wfi);
+	ksft_test_result_pass("the virtual timer wakes a vCPU waiting in WFI\n");
+
+	ksft_finished();
+}
-- 
2.47.3



  parent reply	other threads:[~2026-11-04  8:50 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04  8:48 [PATCH 00/16] KVM: arm64: selftests: Tests for protected VMs Fuad Tabba
2026-11-04  8:48 ` [PATCH 01/16] KVM: arm64: selftests: Inject an abort before a vCPU's first run Fuad Tabba
2026-11-04  8:48 ` [PATCH 02/16] KVM: selftests: Allow arm64 tests to create protected VMs Fuad Tabba
2026-11-04  8:48 ` [PATCH 03/16] tools: arm-smccc: Add the pKVM memory sharing function IDs Fuad Tabba
2026-11-04  8:48 ` [PATCH 04/16] KVM: selftests: Export the ucall pool's guest physical address Fuad Tabba
2026-11-04  8:48 ` [PATCH 05/16] KVM: arm64: selftests: Add protected-VM boot trampoline Fuad Tabba
2026-11-04  8:48 ` [PATCH 06/16] KVM: arm64: selftests: Add protected-VM library helpers Fuad Tabba
2026-11-04  8:48 ` [PATCH 07/16] KVM: arm64: selftests: Add pkvm_host_abi host-interface test Fuad Tabba
2026-11-04  8:48 ` [PATCH 08/16] KVM: arm64: selftests: Add pkvm_psci multi-vCPU PSCI test Fuad Tabba
2026-11-04  8:48 ` [PATCH 09/16] KVM: arm64: selftests: Add pkvm_mem_share hypercall test Fuad Tabba
2026-11-04  8:48 ` [PATCH 10/16] KVM: arm64: selftests: Add pkvm_smccc " Fuad Tabba
2026-11-04  8:48 ` [PATCH 11/16] KVM: arm64: selftests: Add pkvm_id_regs register and trap test Fuad Tabba
2026-11-04  8:48 ` [PATCH 12/16] KVM: arm64: selftests: Add pkvm_vcpu_regs register-state test Fuad Tabba
2026-11-04  8:48 ` Fuad Tabba [this message]
2026-11-04  8:48 ` [PATCH 14/16] KVM: arm64: selftests: Add pkvm_isolation host-access test Fuad Tabba
2026-11-04  8:48 ` [PATCH 15/16] KVM: arm64: selftests: Add pkvm_lifecycle teardown test Fuad Tabba
2026-11-04  8:48 ` [PATCH 16/16] KVM: arm64: selftests: Add pkvm_exceptions debug and SError test Fuad Tabba
2026-11-04 14:10 ` [PATCH 00/16] KVM: arm64: selftests: Tests for protected VMs Joey Gouly

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261104084842.132944-14-fuad.tabba@linux.dev \
    --to=fuad.tabba@linux.dev \
    --cc=joey.gouly@arm.com \
    --cc=kvm@vger.kernel.org \
    --cc=kvmarm@lists.linux.dev \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=maz@kernel.org \
    --cc=oupton@kernel.org \
    --cc=pbonzini@redhat.com \
    --cc=qperret@google.com \
    --cc=seanjc@google.com \
    --cc=seiden@linux.ibm.com \
    --cc=shuah@kernel.org \
    --cc=suzuki.poulose@arm.com \
    --cc=tabba@google.com \
    --cc=vdonnefort@google.com \
    --cc=will@kernel.org \
    --cc=yuzenghui@huawei.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox