From: Hemanth Selam <hemanth.selam@gmail.com>
To: seanjc@google.com, pbonzini@redhat.com, shuah@kernel.org
Cc: kvm@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH v2 2/2] KVM: selftests: Test the PV_UNHALT feature, not just its CPUID bit
Date: Wed, 26 Aug 2026 17:29:15 +0530 [thread overview]
Message-ID: <20260826115915.2882221-3-hemanth.selam@gmail.com> (raw)
In-Reply-To: <20260826115915.2882221-1-hemanth.selam@gmail.com>
test_pv_unhalt() only checks that KVM clears KVM_FEATURE_PV_UNHALT from
guest CPUID when HLT-exiting is disabled. The feature itself has never
been exercised, hence the FIXME.
Add a two vCPU test for it. The first vCPU halts with interrupts
disabled, so nothing except the KVM_HC_KICK_CPU issued by the second can
resume it: KVM delivers the kick as APIC_DM_REMRD, which sets pv_unhalted
and makes the vCPU runnable without injecting an interrupt. Reaching the
instruction after HLT is therefore proof that the kick arrived.
Give the halting vCPU a non-zero APIC ID, so that a kick aimed at the
wrong vCPU fails the test instead of hitting the halter by chance, and
enable its APIC as a guest using PV spinlocks would, since KVM only routes
the kick once the vCPU's APIC is in the map.
Wait for the halter's halt_exits to tick before kicking so that the kick
lands on a vCPU that has actually halted, and bound the wait so a vCPU
that never halts fails the test instead of hanging it.
The kicking vCPU runs with KVM_CAP_ENFORCE_PV_FEATURE_CPUID enabled and
PV_UNHALT advertised, so that KVM services the hypercall because the
feature is exposed rather than because enforcement is off.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: Hemanth Selam <hemanth.selam@gmail.com>
---
tools/testing/selftests/kvm/x86/kvm_pv_test.c | 97 ++++++++++++++++++-
1 file changed, 96 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kvm/x86/kvm_pv_test.c b/tools/testing/selftests/kvm/x86/kvm_pv_test.c
index 8ed5fa635021..120d08ac25ce 100644
--- a/tools/testing/selftests/kvm/x86/kvm_pv_test.c
+++ b/tools/testing/selftests/kvm/x86/kvm_pv_test.c
@@ -6,8 +6,10 @@
*/
#include <asm/kvm_para.h>
#include <linux/kvm_para.h>
+#include <pthread.h>
#include <stdint.h>
+#include "apic.h"
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
@@ -193,7 +195,99 @@ static void test_pv_unhalt(void)
TEST_ASSERT(!vcpu_cpuid_has(vcpu, X86_FEATURE_KVM_PV_UNHALT),
"PV_UNHALT set in guest CPUID when HLT-exiting is disabled");
- /* FIXME: actually test KVM_FEATURE_PV_UNHALT feature */
+ kvm_vm_free(vm);
+}
+
+static void pv_unhalt_halter_guest_code(void)
+{
+ /*
+ * Enable the local APIC, as a guest that uses PV spinlocks would. KVM
+ * only routes the kick to this vCPU once its APIC is in the map.
+ */
+ xapic_enable();
+
+ /*
+ * Interrupts are disabled, so nothing except the KVM_HC_KICK_CPU from
+ * the other vCPU can end the halt, i.e. reaching GUEST_DONE() proves
+ * the kick was delivered.
+ */
+ asm volatile("cli; hlt");
+
+ GUEST_DONE();
+}
+
+static void pv_unhalt_kicker_guest_code(u32 halter_apic_id)
+{
+ /* KVM takes flags in a0 and the APIC ID to kick in a1. */
+ GUEST_ASSERT_EQ(kvm_hypercall(KVM_HC_KICK_CPU, 0, halter_apic_id, 0, 0), 0);
+ GUEST_DONE();
+}
+
+static void run_guest_to_done(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ u64 cmd;
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ cmd = get_ucall(vcpu, &uc);
+ if (cmd == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ TEST_ASSERT_EQ(cmd, UCALL_DONE);
+}
+
+static void *pv_unhalt_halter_thread(void *vcpu)
+{
+ run_guest_to_done(vcpu);
+ return NULL;
+}
+
+static void test_pv_unhalt_kick(void)
+{
+ struct kvm_vcpu *halter, *kicker;
+ struct timespec start;
+ struct kvm_vm *vm;
+ pthread_t thread;
+ int r;
+
+ pr_info("testing KVM_HC_KICK_CPU\n");
+
+ /*
+ * Give the halter a non-zero APIC ID, so that a kick sent to the wrong
+ * vCPU fails the test instead of hitting the halter by chance.
+ */
+ vm = vm_create_with_one_vcpu(&kicker, pv_unhalt_kicker_guest_code);
+ halter = vm_vcpu_add(vm, 1, pv_unhalt_halter_guest_code);
+ virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
+
+ /*
+ * Enforce the PV CPUID so that KVM services the hypercall because
+ * PV_UNHALT is advertised to the kicker, and not because enforcement
+ * is off. KVM advertises PV_UNHALT by default while HLT-exiting is
+ * enabled; set it explicitly so that the test keeps testing the
+ * feature if that ever changes.
+ */
+ vcpu_enable_cap(kicker, KVM_CAP_ENFORCE_PV_FEATURE_CPUID, 1);
+ vcpu_set_cpuid_feature(kicker, X86_FEATURE_KVM_PV_UNHALT);
+ vcpu_args_set(kicker, 1, vcpu_get_apic_id(halter));
+
+ r = pthread_create(&thread, NULL, pv_unhalt_halter_thread, halter);
+ TEST_ASSERT(!r, "pthread_create halter failed, error=%d", r);
+
+ /* Kick only once the halter has taken its HLT exit. */
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ while (!vcpu_get_stat(halter, halt_exits)) {
+ TEST_ASSERT(timespec_elapsed(start).tv_sec < 10,
+ "vCPU never halted");
+ usleep(100);
+ }
+
+ run_guest_to_done(kicker);
+
+ /* Nothing except the kick can get the halter to GUEST_DONE(). */
+ r = pthread_join(thread, NULL);
+ TEST_ASSERT(!r, "pthread_join halter failed, error=%d", r);
kvm_vm_free(vm);
}
@@ -215,4 +309,5 @@ int main(void)
kvm_vm_free(vm);
test_pv_unhalt();
+ test_pv_unhalt_kick();
}
--
2.43.7
next prev parent reply other threads:[~2026-08-26 11:59 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-26 11:59 [PATCH v2 0/2] KVM: selftests: Actually test PV_UNHALT Hemanth Selam
2026-08-26 11:59 ` [PATCH v2 1/2] KVM: selftests: Add a helper to read a vCPU's APIC ID Hemanth Selam
2026-08-26 12:11 ` sashiko-bot
2026-08-26 11:59 ` Hemanth Selam [this message]
2026-08-27 4:48 ` Hemanth Selam
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=20260826115915.2882221-3-hemanth.selam@gmail.com \
--to=hemanth.selam@gmail.com \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=pbonzini@redhat.com \
--cc=seanjc@google.com \
--cc=shuah@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.