mirror of https://lore.kernel.org/lkml/
 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 08/16] KVM: arm64: selftests: Add pkvm_psci multi-vCPU PSCI test
Date: Wed,  4 Nov 2026 08:48:34 +0000	[thread overview]
Message-ID: <20261104084842.132944-9-fuad.tabba@linux.dev> (raw)
In-Reply-To: <20261104084842.132944-1-fuad.tabba@linux.dev>

EL2 handles a protected guest's CPU_ON and CPU_OFF itself, so the host
can't power on a vCPU the guest hasn't, or change where it boots. EL2
also handles PSCI_VERSION and PSCI_FEATURES, and returns NOT_SUPPORTED
for unknown calls. It forwards the rest to the host.

Test CPU_ON, CPU_OFF, AFFINITY_INFO, PSCI_VERSION, PSCI_FEATURES,
SYSTEM_OFF, SYSTEM_RESET and SYSTEM_RESET2. Also check that the host
can't make a second vCPU runnable without a CPU_ON from the guest. Run
the secondaries on their own threads to race CPU_ON against CPU_OFF and
against another CPU_ON. EL2 marks a vCPU off at its CPU_OFF and returns
to the host. Until the host powers that vCPU off, a CPU_ON of it returns
ALREADY_ON.

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

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 42e530e0e573b..aa256fb66d226 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -191,6 +191,7 @@ TEST_GEN_PROGS_arm64 += arm64/external_aborts
 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_psci
 TEST_GEN_PROGS_arm64 += arm64/psci_test
 TEST_GEN_PROGS_arm64 += arm64/sea_to_user
 TEST_GEN_PROGS_arm64 += arm64/set_id_regs
diff --git a/tools/testing/selftests/kvm/arm64/pkvm_psci.c b/tools/testing/selftests/kvm/arm64/pkvm_psci.c
new file mode 100644
index 0000000000000..07a50af9c9ad9
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/pkvm_psci.c
@@ -0,0 +1,632 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pkvm_psci - PSCI in a protected VM, including CPU_ON racing CPU_OFF and CPU_ON.
+ *
+ * Copyright (C) 2025 Google LLC
+ * Author: Fuad Tabba <fuad.tabba@linux.dev>
+ */
+
+#include <pthread.h>
+
+#include <linux/bitfield.h>
+#include <linux/psci.h>
+#include <asm/barrier.h>
+#include <asm/cputype.h>
+
+#include "arch_timer.h"
+#include "kvm_util.h"
+#include "pkvm.h"
+#include "processor.h"
+#include "test_util.h"
+
+#define MAX_VCPUS	3
+
+/* A well-formed MPIDR with no matching vCPU. */
+#define BOGUS_MPIDR	0xf
+
+/* The primary's GUEST_SYNC: the host runs the secondary, then resumes the primary. */
+#define SYNC_RUN_SECONDARY	1
+
+#define STRESS_BOOTS	50
+#define ALT_BOOTS	50
+#define HANDSHAKE_CYCLES	50
+#define RACE_ROUNDS	10
+
+#define WAIT_MS		30000
+
+/* An identity-mapped page for the boot context the primary writes. */
+#define ALT_MEMSLOT	(PKVM_IDMAP_MEMSLOT + 1)
+#define ALT_CTX		BIT_ULL(30)
+
+/* Set in the guest's copy by the host before the first run. */
+static u64 boot_entry;
+static u64 vcpu_mpidr[MAX_VCPUS];
+static u64 vcpu_ctx[MAX_VCPUS];
+
+static u32 boots;
+static u64 expected_ctx;
+static u64 booted_ctx;
+static u32 go_off;
+
+static u32 race_ready;
+static u32 race_go;
+static u32 race_done;
+static unsigned long race_ret[2];
+
+#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 unsigned long __psci_cpu_on(u64 mpidr, u64 entry, u64 ctx)
+{
+	return pkvm_hvc(PSCI_0_2_FN64_CPU_ON, mpidr, entry, ctx);
+}
+
+static unsigned long psci_cpu_on(int id)
+{
+	return __psci_cpu_on(vcpu_mpidr[id], boot_entry, vcpu_ctx[id]);
+}
+
+static unsigned long psci_affinity_info(int id)
+{
+	return pkvm_hvc(PSCI_0_2_FN64_AFFINITY_INFO, vcpu_mpidr[id], 0, 0);
+}
+
+static void psci_cpu_off(void)
+{
+	pkvm_hvc(PSCI_0_2_FN_CPU_OFF, 0, 0, 0);
+	GUEST_ASSERT(0);
+}
+
+static void guest_primary(void)
+{
+	pkvm_guest_init();
+
+	GUEST_ASSERT_EQ(pkvm_hvc(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0), PSCI_VERSION(1, 1));
+	GUEST_ASSERT_EQ(pkvm_hvc(PSCI_1_0_FN_PSCI_FEATURES, PSCI_0_2_FN64_CPU_ON, 0, 0),
+			PSCI_RET_SUCCESS);
+	GUEST_ASSERT_EQ(pkvm_hvc(PSCI_1_0_FN_PSCI_FEATURES, PSCI_0_2_FN_MIGRATE, 0, 0),
+			PSCI_RET_NOT_SUPPORTED);
+
+	GUEST_ASSERT_EQ(psci_affinity_info(1), PSCI_0_2_AFFINITY_LEVEL_OFF);
+	GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_SUCCESS);
+
+	/* The host handles AFFINITY_INFO, and returns ON for a CPU_ON target. */
+	GUEST_ASSERT_EQ(psci_affinity_info(1), PSCI_0_2_AFFINITY_LEVEL_ON);
+	GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_ON_PENDING);
+
+	GUEST_SYNC(SYNC_RUN_SECONDARY);
+
+	GUEST_ASSERT_EQ(psci_affinity_info(1), PSCI_0_2_AFFINITY_LEVEL_ON);
+	GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_ALREADY_ON);
+
+	GUEST_ASSERT_EQ(__psci_cpu_on(BOGUS_MPIDR, boot_entry, 0), PSCI_RET_INVALID_PARAMS);
+
+	GUEST_DONE();
+}
+
+/* The primary's pkvm_guest_init() set up ucall for the whole VM. */
+static void guest_secondary(void)
+{
+	GUEST_DONE();
+}
+
+static void guest_cpu_on_unregistered(void)
+{
+	pkvm_guest_init();
+	GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_INVALID_PARAMS);
+	GUEST_DONE();
+}
+
+static void guest_boot_and_done(void)
+{
+	pkvm_guest_init();
+	GUEST_DONE();
+}
+
+static void guest_system_off(void)
+{
+	pkvm_hvc(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
+	GUEST_ASSERT(0);
+}
+
+static void guest_system_reset(void)
+{
+	pkvm_hvc(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
+	GUEST_ASSERT(0);
+}
+
+static void guest_system_reset2_smc32(void)
+{
+	pkvm_hvc(PSCI_1_1_FN_SYSTEM_RESET2, PSCI_1_1_RESET_TYPE_SYSTEM_WARM_RESET, 0, 0);
+	GUEST_ASSERT(0);
+}
+
+static void guest_system_reset2_smc64(void)
+{
+	pkvm_hvc(PSCI_1_1_FN64_SYSTEM_RESET2, PSCI_1_1_RESET_TYPE_SYSTEM_WARM_RESET, 0, 0);
+	GUEST_ASSERT(0);
+}
+
+static void guest_stress_primary(void)
+{
+	unsigned long successes = 0;
+
+	pkvm_guest_init();
+	while (READ_ONCE(boots) < STRESS_BOOTS) {
+		unsigned long ret = psci_cpu_on(1);
+
+		__GUEST_ASSERT(ret == PSCI_RET_SUCCESS || ret == PSCI_RET_ALREADY_ON ||
+			       ret == PSCI_RET_ON_PENDING, "CPU_ON 0x%lx", ret);
+		successes += ret == PSCI_RET_SUCCESS;
+	}
+
+	/*
+	 * SUCCESS means the secondary ran. The last boot has completed and the
+	 * secondary stays on after it, so no CPU_ON and no boot is in flight.
+	 */
+	GUEST_ASSERT_EQ(successes, STRESS_BOOTS);
+	GUEST_DONE();
+}
+
+static void guest_stress_secondary(void)
+{
+	u32 k = READ_ONCE(boots) + 1;
+
+	WRITE_ONCE(boots, k);
+	if (k == STRESS_BOOTS)
+		GUEST_DONE();
+	psci_cpu_off();
+}
+
+static void alt_boot(u64 ctx)
+{
+	u32 k = READ_ONCE(boots) + 1;
+
+	GUEST_ASSERT_EQ(ctx, READ_ONCE(expected_ctx));
+	WRITE_ONCE(booted_ctx, ctx);
+	/* Publishes booted_ctx, and the primary changes expected_ctx only once it sees this. */
+	smp_store_release(&boots, k);
+	if (k == ALT_BOOTS)
+		GUEST_DONE();
+	psci_cpu_off();
+}
+
+static void guest_alt_secondary_host_ctx(void)
+{
+	alt_boot(vcpu_ctx[1]);
+}
+
+static void guest_alt_secondary_guest_ctx(void)
+{
+	alt_boot(ALT_CTX);
+}
+
+static void clean_to_poc(void *p, size_t size)
+{
+	u64 line = 4UL << SYS_FIELD_GET(CTR_EL0, DminLine, read_sysreg(ctr_el0));
+	u64 addr;
+
+	for (addr = (u64)p & ~(line - 1); addr < (u64)p + size; addr += line)
+		asm volatile("dc cvac, %0" : : "r"(addr) : "memory");
+	dsb(sy);
+}
+
+static void guest_alt_primary(void)
+{
+	struct pvm_boot_args *alt = (struct pvm_boot_args *)ALT_CTX;
+	bool guest_ctx = true;
+	unsigned long ret;
+	u64 ctx;
+	u32 k;
+
+	pkvm_guest_init();
+
+	/* The trampoline reads the context with the MMU off. */
+	*alt = *(struct pvm_boot_args *)vcpu_ctx[1];
+	alt->jump_tgt = (u64)guest_alt_secondary_guest_ctx;
+	clean_to_poc(alt, sizeof(*alt));
+
+	for (k = 1; k <= ALT_BOOTS; k++) {
+		/*
+		 * CPU_ON returns ALREADY_ON until the host has powered the secondary off.
+		 * Each attempt passes the other context, so a boot from the previous attempt's
+		 * context fails alt_boot()'s check.
+		 */
+		do {
+			guest_ctx = !guest_ctx;
+			ctx = guest_ctx ? ALT_CTX : vcpu_ctx[1];
+			WRITE_ONCE(expected_ctx, ctx);
+			ret = __psci_cpu_on(vcpu_mpidr[1], boot_entry, ctx);
+			__GUEST_ASSERT(ret == PSCI_RET_SUCCESS || ret == PSCI_RET_ALREADY_ON,
+				       "CPU_ON 0x%lx", ret);
+		} while (ret != PSCI_RET_SUCCESS);
+
+		/* Only the primary powers the secondary on, so no other boot is in flight. */
+		wait_until(smp_load_acquire(&boots) >= k);
+		GUEST_ASSERT_EQ(READ_ONCE(boots), k);
+		GUEST_ASSERT_EQ(READ_ONCE(booted_ctx), ctx);
+	}
+
+	GUEST_DONE();
+}
+
+static void guest_handshake_secondary(void)
+{
+	u32 k = READ_ONCE(boots) + 1;
+
+	WRITE_ONCE(boots, k);
+	if (k > HANDSHAKE_CYCLES)
+		GUEST_DONE();
+	wait_until(READ_ONCE(go_off) == k);
+	psci_cpu_off();
+}
+
+/* The secondary is ON until it has called CPU_OFF. */
+static bool is_off(int id)
+{
+	unsigned long ret = psci_affinity_info(id);
+
+	__GUEST_ASSERT(ret == PSCI_0_2_AFFINITY_LEVEL_ON || ret == PSCI_0_2_AFFINITY_LEVEL_OFF,
+		       "AFFINITY_INFO 0x%lx", ret);
+	return ret == PSCI_0_2_AFFINITY_LEVEL_OFF;
+}
+
+static void guest_handshake_primary(void)
+{
+	u32 k;
+
+	pkvm_guest_init();
+
+	for (k = 1; k <= HANDSHAKE_CYCLES; k++) {
+		GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_SUCCESS);
+
+		/* The secondary has booted and waits for go_off, so it is still ON. */
+		wait_until(READ_ONCE(boots) >= k);
+		GUEST_ASSERT_EQ(psci_affinity_info(1), PSCI_0_2_AFFINITY_LEVEL_ON);
+
+		WRITE_ONCE(go_off, k);
+
+		/* It is OFF and only the primary powers it on, so no boot is in flight. */
+		wait_until(is_off(1));
+		GUEST_ASSERT_EQ(READ_ONCE(boots), k);
+	}
+
+	/* One more boot lets the secondary finish instead of powering off. */
+	GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_SUCCESS);
+	GUEST_DONE();
+}
+
+static void guest_race_caller(void)
+{
+	WRITE_ONCE(race_ready, 1);
+	wait_until(READ_ONCE(race_go));
+	race_ret[1] = psci_cpu_on(2);
+	/* Publishes race_ret[1] to the primary. */
+	smp_store_release(&race_done, 1);
+	GUEST_DONE();
+}
+
+/* No CPU_OFF: a target that powered off would let the loser win too. */
+static void guest_race_target(void)
+{
+	WRITE_ONCE(boots, READ_ONCE(boots) + 1);
+	GUEST_DONE();
+}
+
+static void guest_race_primary(void)
+{
+	unsigned long loser;
+	int winner;
+
+	pkvm_guest_init();
+
+	/* vCPU 1 has never been on, and the primary is its only caller. */
+	GUEST_ASSERT_EQ(psci_cpu_on(1), PSCI_RET_SUCCESS);
+	wait_until(READ_ONCE(race_ready));
+
+	WRITE_ONCE(race_go, 1);
+	race_ret[0] = psci_cpu_on(2);
+
+	/* Both callers have returned, and vCPU 2 has booted and stays on. */
+	wait_until(smp_load_acquire(&race_done));
+	wait_until(READ_ONCE(boots));
+	GUEST_ASSERT_EQ(READ_ONCE(boots), 1);
+
+	winner = race_ret[0] == PSCI_RET_SUCCESS ? 0 : 1;
+	loser = race_ret[!winner];
+	GUEST_ASSERT_EQ(race_ret[winner], PSCI_RET_SUCCESS);
+	__GUEST_ASSERT(loser == PSCI_RET_ON_PENDING || loser == PSCI_RET_ALREADY_ON,
+		       "losing CPU_ON 0x%lx", loser);
+
+	GUEST_SYNC(loser);
+	GUEST_DONE();
+}
+
+static struct kvm_vcpu *secondary_vcpu;
+static u64 sync_arg;
+
+static void run_secondary_on_sync(struct kvm_vcpu *vcpu, struct ucall *uc)
+{
+	if (uc->args[1] == SYNC_RUN_SECONDARY)
+		pkvm_run_loop(secondary_vcpu, NULL);
+}
+
+static void wire_target(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);
+	write_guest_global(vm, boot_entry, pkvm_idmap_gpa(vm));
+}
+
+static void wire_secondary(struct kvm_vm *vm, struct kvm_vcpu *vcpu, void *guest_code)
+{
+	gpa_t ctx;
+
+	wire_target(vm, vcpu);
+	ctx = pkvm_vcpu_setup_boot_secondary(vcpu, guest_code);
+	write_guest_global(vm, vcpu_ctx[vcpu->id], ctx);
+}
+
+static void test_protocol(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+	secondary_vcpu = vcpus[1];
+
+	pkvm_vcpu_setup_boot(vcpus[0], guest_primary);
+	wire_secondary(vm, vcpus[1], guest_secondary);
+
+	pkvm_run_loop(vcpus[0], run_secondary_on_sync);
+
+	kvm_vm_free(vm);
+}
+
+/* vCPU 1 exists but never runs, so EL2 never creates its hyp vCPU. */
+static void test_cpu_on_unregistered(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+
+	pkvm_vcpu_setup_boot(vcpus[0], guest_cpu_on_unregistered);
+	wire_target(vm, vcpus[1]);
+
+	pkvm_run_loop(vcpus[0], NULL);
+
+	kvm_vm_free(vm);
+}
+
+static void assert_system_event(void *guest_code, u32 type, u64 flags)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_run *run;
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(1, 0, &vcpu);
+	pkvm_vcpu_setup_boot(vcpu, guest_code);
+
+	run = vcpu->run;
+	vcpu_run(vcpu);
+
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_SYSTEM_EVENT);
+	TEST_ASSERT_EQ(run->system_event.type, type);
+	TEST_ASSERT_EQ(run->system_event.data[0], flags);
+
+	kvm_vm_free(vm);
+}
+
+/* Only one vCPU may be RUNNABLE when EL2 creates its hyp vCPU: the primary. */
+static void assert_second_runnable_rejected(struct kvm_vcpu *vcpu)
+{
+	int ret = _vcpu_run(vcpu);
+
+	TEST_ASSERT(ret == -1 && errno == EINVAL,
+		    "second RUNNABLE vCPU: expected -1/EINVAL, got %d/%d", ret, errno);
+}
+
+static void test_forged_runnable(void)
+{
+	struct kvm_mp_state mps = { .mp_state = KVM_MP_STATE_RUNNABLE };
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+	pkvm_vcpu_setup_boot(vcpus[0], guest_boot_and_done);
+	pkvm_run_loop(vcpus[0], NULL);
+
+	vcpu_mp_state_set(vcpus[1], &mps);
+	assert_second_runnable_rejected(vcpus[1]);
+
+	kvm_vm_free(vm);
+}
+
+static void test_second_primary(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vcpu_init init;
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+	pkvm_vcpu_setup_boot(vcpus[0], guest_boot_and_done);
+	pkvm_run_loop(vcpus[0], NULL);
+
+	/* Re-INIT without POWER_OFF also leaves the vCPU RUNNABLE. */
+	kvm_get_default_vcpu_target(vm, &init);
+	init.features[0] |= BIT(KVM_ARM_VCPU_PSCI_0_2);
+	vcpu_ioctl(vcpus[1], KVM_ARM_VCPU_INIT, &init);
+	assert_second_runnable_rejected(vcpus[1]);
+
+	kvm_vm_free(vm);
+}
+
+/* Once EL2 holds a secondary's hyp vCPU off, the host rejects powering it on. */
+static void test_run_gate_off(void)
+{
+	struct kvm_mp_state mps = { .mp_state = KVM_MP_STATE_RUNNABLE };
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+	int ret;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+
+	pkvm_vcpu_setup_boot_secondary(vcpus[1], guest_boot_and_done);
+
+	ret = __vcpu_ioctl(vcpus[1], KVM_SET_MP_STATE, &mps);
+	TEST_ASSERT(ret == -1 && errno == EPERM,
+		    "forged RUNNABLE: expected -1/EPERM, got %d/%d", ret, errno);
+	mps.mp_state = KVM_MP_STATE_SUSPENDED;
+	ret = __vcpu_ioctl(vcpus[1], KVM_SET_MP_STATE, &mps);
+	TEST_ASSERT(ret == -1 && errno == EPERM,
+		    "forged SUSPENDED: expected -1/EPERM, got %d/%d", ret, errno);
+
+	kvm_vm_free(vm);
+}
+
+static void *vcpu_thread(void *vcpu)
+{
+	pkvm_run_loop(vcpu, NULL);
+	return NULL;
+}
+
+static void record_sync(struct kvm_vcpu *vcpu, struct ucall *uc)
+{
+	sync_arg = uc->args[1];
+}
+
+/* The secondary runs on its own thread, so a CPU_ON can race its CPU_OFF. */
+static void run_threaded(struct kvm_vcpu *vcpus[])
+{
+	pthread_t thread;
+
+	pthread_create(&thread, NULL, vcpu_thread, vcpus[1]);
+	pkvm_run_loop(vcpus[0], record_sync);
+	pthread_join(thread, NULL);
+}
+
+static void test_stress(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+
+	pkvm_vcpu_setup_boot(vcpus[0], guest_stress_primary);
+	wire_secondary(vm, vcpus[1], guest_stress_secondary);
+
+	run_threaded(vcpus);
+
+	kvm_vm_free(vm);
+}
+
+static void test_alternating_ctx(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ALT_CTX, ALT_MEMSLOT,
+				    vm_adjust_num_guest_pages(vm->mode, 1), 0);
+	virt_map(vm, ALT_CTX, ALT_CTX, 1);
+
+	pkvm_vcpu_setup_boot(vcpus[0], guest_alt_primary);
+	wire_secondary(vm, vcpus[1], guest_alt_secondary_host_ctx);
+
+	run_threaded(vcpus);
+
+	kvm_vm_free(vm);
+}
+
+static void test_handshake(void)
+{
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+
+	vm = pkvm_vm_create(2, 0, vcpus);
+
+	pkvm_vcpu_setup_boot(vcpus[0], guest_handshake_primary);
+	wire_secondary(vm, vcpus[1], guest_handshake_secondary);
+
+	run_threaded(vcpus);
+
+	kvm_vm_free(vm);
+}
+
+/* Returns the number of rounds whose loser got ON_PENDING: its CPU_ON overlapped the winner's. */
+static int test_concurrent_cpu_on(void)
+{
+	int i, on_pending = 0;
+
+	for (i = 0; i < RACE_ROUNDS; i++) {
+		struct kvm_vcpu *vcpus[3];
+		pthread_t target;
+		struct kvm_vm *vm;
+
+		vm = pkvm_vm_create(3, 0, vcpus);
+
+		pkvm_vcpu_setup_boot(vcpus[0], guest_race_primary);
+		wire_secondary(vm, vcpus[1], guest_race_caller);
+		wire_secondary(vm, vcpus[2], guest_race_target);
+
+		pthread_create(&target, NULL, vcpu_thread, vcpus[2]);
+		run_threaded(vcpus);
+		pthread_join(target, NULL);
+
+		kvm_vm_free(vm);
+
+		on_pending += sync_arg == PSCI_RET_ON_PENDING;
+	}
+
+	return on_pending;
+}
+
+int main(void)
+{
+	int on_pending;
+
+	TEST_REQUIRE(pkvm_supported());
+
+	ksft_print_header();
+	ksft_set_plan(13);
+
+	test_protocol();
+	ksft_test_result_pass("PSCI protocol and CPU_ON boot\n");
+	test_cpu_on_unregistered();
+	ksft_test_result_pass("CPU_ON of a vCPU with no hyp vCPU\n");
+	assert_system_event(guest_system_off, KVM_SYSTEM_EVENT_SHUTDOWN, 0);
+	ksft_test_result_pass("SYSTEM_OFF\n");
+	assert_system_event(guest_system_reset, KVM_SYSTEM_EVENT_RESET, 0);
+	ksft_test_result_pass("SYSTEM_RESET\n");
+	assert_system_event(guest_system_reset2_smc32, KVM_SYSTEM_EVENT_RESET,
+			    KVM_SYSTEM_EVENT_RESET_FLAG_PSCI_RESET2);
+	ksft_test_result_pass("SYSTEM_RESET2 (SMC32)\n");
+	assert_system_event(guest_system_reset2_smc64, KVM_SYSTEM_EVENT_RESET,
+			    KVM_SYSTEM_EVENT_RESET_FLAG_PSCI_RESET2);
+	ksft_test_result_pass("SYSTEM_RESET2 (SMC64)\n");
+	test_forged_runnable();
+	ksft_test_result_pass("forged RUNNABLE rejected at hyp vCPU creation\n");
+	test_second_primary();
+	ksft_test_result_pass("second RUNNABLE vCPU rejected\n");
+	test_run_gate_off();
+	ksft_test_result_pass("forged RUNNABLE and SUSPENDED of an OFF vCPU rejected\n");
+	test_stress();
+	ksft_test_result_pass("CPU_ON/CPU_OFF race: %d boots\n", STRESS_BOOTS);
+	test_alternating_ctx();
+	ksft_test_result_pass("CPU_ON alternating context: %d boots\n", ALT_BOOTS);
+	test_handshake();
+	ksft_test_result_pass("CPU_OFF/CPU_ON handshake: %d cycles\n", HANDSHAKE_CYCLES);
+	on_pending = test_concurrent_cpu_on();
+	ksft_test_result_pass("concurrent CPU_ON: one winner in %d rounds, %d ON_PENDING losers\n",
+			      RACE_ROUNDS, on_pending);
+
+	ksft_finished();
+}
-- 
2.47.3


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

Thread overview: 19+ 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 ` Fuad Tabba [this message]
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 ` [PATCH 13/16] KVM: arm64: selftests: Add pkvm_irq interrupt test Fuad Tabba
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
2026-11-04 19:38   ` Fuad Tabba

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-9-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

all inboxes | Powered by JetHome®