From nobody Mon Sep 28 04:49:02 2026 Received: from mail-pg1-f181.google.com (mail-pg1-f181.google.com [209.85.215.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5127D3B994A for ; Wed, 26 Aug 2026 10:16:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787739415; cv=none; b=Gdcf4zxdKHcYr5KMfAoueTO0Soq7sdtALidW6KqAzRKfU72PmdIVHjfsjaDEHcgtu6tE7vjzv8Xl/xHtEs6vJ15ULkE1COxCWcaNxEyx6SUi4v9v0nzFNuZsNeH5yQmTuNdbwW2aOa2wM81d/ayfjfrP8LpgqX6ZK+gDij8B5c0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787739415; c=relaxed/simple; bh=59vTpxxWEv1HtJfPmZuk8ygZ5wZi/hyyb/b5qluJ0jc=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=D3fs90wHGYg53FDVTgILhiSs5nMGx/65C+UNzTCZJYFIes58XnvlYG9MY4m+NvJVzH+R2wXgxiu8ZcZd9GB3INW/Wn1G7TaoGKWk4mIAGxmLmTYDQ0Lcv/9qteQLX5clcZtM5uF3utbG35WbBWLuPiMCWWNAG0kYcBPJopAoH/8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=T6pnBiX2; arc=none smtp.client-ip=209.85.215.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="T6pnBiX2" Received: by mail-pg1-f181.google.com with SMTP id 41be03b00d2f7-cc1c8d4a959so269900a12.3 for ; Wed, 26 Aug 2026 03:16:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787739411; x=1788344211; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=CktsvTeO7UFrmo4k4+hz7ck5sXhdCkmKXB1FCIhVdHE=; b=T6pnBiX2IFwNb055FqJdoNpF0Y9uAyKkwupTejeNQQda0g7nTV8x7G/RRM4Lo7dCIH O2rkROcqHHzWH/CoTrnfaWnMV+QdM/KoCSqkyXk6c8Y14CImQgeq1QGJiOeR91M2oWQU RtXgJViVUk/VaL0VPuTGE6LjcbdIA0mTw3UKChIuZ1Eq10lOULwzVs4QK9b0ykuUosBc DlsWEG0ju5794b2+adub3jjbE23/Rlmk1cynNny+Z8/8T2M1acQr+c7ZVgxcv0uExvkH L5CvdBxfHlrKQdHjkmCW/ayllG/2SidF3BP4WiO1KfAdiHrftLBLbVXoVR5CFzM0YMx/ ASRg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787739411; x=1788344211; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=CktsvTeO7UFrmo4k4+hz7ck5sXhdCkmKXB1FCIhVdHE=; b=SOQHRwp6KnlwvWh2M56eEZiwbdUO0QW4PoJkE9pzE02J3iopmpP3ETP/RoJtFhrRLH KxFgej2/Rt2yOJa4aRfiss04zWd18DCLX2UQNCsvjggGmY63lptoUDn/yX3bBU30hZWw 5KoXYvbjActH60MWEbS//sjAwCtKTb1hgEX/NisgNmIXCXUNSq5SkFTvpGEOFjM3g3xf PXXeH4hJzQoZFbsGWd45GNAF6ejuSm6mrCBGxxKsuKttYCKrsBn+Uw2BlgE6OPz6CPhJ S/S6RNte4jlILXTDtz977yaAKaaqahan43w5RcDBdEsE2XzUQMwvcdH5iUU74fPEkD29 Kr/g== X-Forwarded-Encrypted: i=1; AHgh+RqcJXuh9oKX4c/3HqSc+FI1d/frCmFSbjS9utoKvi4ffSAVW3WVbIDCgItA7kAlE5GC03TeNN+Cye65p2s=@vger.kernel.org X-Gm-Message-State: AFuF++l+m21SfYQGH9qY9FTo2RsPV0CJB7DJtiW1/uLGAb7ofttSzHlt FQ1mN0tbASBlz5axmsd3Bnn54RcM6St8IfAmwqSJuoMO+OII4suaSeEB X-Gm-Gg: AR+sD10VguN6dE21ywDwKxx2Yxk1tYjtJpF3qgoj0aVvUNE13hom/6uSPp8N6Mhm8MP hIeTkpIZ+hEFlHvU+MmIlv0eP4LvVPh7AQzfRPfu6Dpmd5GSEuKnx+5qbgS9ms6jlyp2qETo1W4 yldR4sxLfr2eb96hJWenyqB6hJbELZANB3bia4PNJcLEwVZbXR3mKNFEUA0N8Ow5Tc3G3ORwdOC u5xNyZmr6qWW8WJJ2wiohrXb17y6Qm/2ARp24wEmbI8fBdo24/DQR/XqW7pOosPkWufZLXMzakx BFy9SLIyr4X6Lb+a+xGVZe5QWBZ91J1+rIr1MUiGz4Y3Sk7NaOyBwcLz45dl4RihRg/D3raTpQi U3+i6+5XhDD5+Aiqb8yeAN3mMHcnwlz0IZc3KurYnw1nL8o8vNwsytJ8mYFT2soqr5GcFCHYSwr P6xQAmd4wmXHH0e+MJBcbsJvqf7mevY1G/RXeN08GXDM9nG9vuItRBNZ2UNTOstPJui/hTD8Yph UpQEduYbVU= X-Received: by 2002:a17:90b:1fc3:b0:38e:485c:ebd3 with SMTP id 98e67ed59e1d1-3966d48f861mr12753264a91.15.1787739410488; Wed, 26 Aug 2026 03:16:50 -0700 (PDT) Received: from volcano9f8e-host.amd.com ([165.204.217.251]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-141a8eefd98sm12974385c88.3.2026.08.26.03.16.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Aug 2026 03:16:49 -0700 (PDT) From: Hemanth Selam 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] KVM: selftests: Test the PV_UNHALT feature, not just its CPUID bit Date: Wed, 26 Aug 2026 15:46:27 +0530 Message-ID: <20260826101627.2873660-1-hemanth.selam@gmail.com> X-Mailer: git-send-email 2.43.7 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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. 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 --- Built and run on x86_64 (AMD). Untested on Intel, though the kick is handled in common code and delivered through the generic LAPIC path. - On kvm-x86/next, built warning-free and passed 10 of 10 runs, and 20 of 20 runs on a mainline build. - Also run inside a VM booted on a kernel built from kvm-x86/next, so against the KVM this targets rather than the host's: 6 of 6 runs passed, which also exercises the halt/kick ordering under much slower timing. - Whole x86 selftest suite with this applied: 61 passed, 24 skipped, and set_sregs_test failed with "KVM allowed invalid efer bit (0x100)". That one fails identically with this patch reverted, i.e. it is the host kernel, not this change. The test was checked against three deliberate breakages, to make sure it can only pass when the kick really works: - drop the KVM_HC_KICK_CPU call from the kicking vCPU: the halted vCPU is never resumed and the test times out, i.e. nothing else wakes it; - clear PV_UNHALT from the kicking vCPU's CPUID while enforcement is on: the hypercall returns -KVM_ENOSYS and the test fails with 0xfffffffffffffc18 !=3D 0x0 (kvm_hypercall(KVM_HC_KICK_CPU, ...) !=3D = 0) - remove the halt from the halting vCPU: the bounded wait trips and the test fails with "vCPU never halted" rather than hanging. The explicit vcpu_set_cpuid_feature() is redundant today, since KVM advertises PV_UNHALT by default while HLT-exiting is enabled - verified by asserting on it - but it keeps the test honest if that ever changes. tools/testing/selftests/kvm/x86/kvm_pv_test.c | 93 ++++++++++++++++++- 1 file changed, 92 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..d14d55d3a9ba 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 #include +#include #include =20 +#include "apic.h" #include "test_util.h" #include "kvm_util.h" #include "processor.h" @@ -193,7 +195,95 @@ 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"); =20 - /* FIXME: actually test KVM_FEATURE_PV_UNHALT feature */ + kvm_vm_free(vm); +} + +static void pv_unhalt_halter_guest_code(void) +{ + /* + * 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) +{ + GUEST_ASSERT_EQ(kvm_hypercall(KVM_HC_KICK_CPU, halter_apic_id, 0, 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 =3D get_ucall(vcpu, &uc); + if (cmd =3D=3D 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 u32 vcpu_get_apic_id(struct kvm_vcpu *vcpu) +{ + struct kvm_lapic_state lapic; + + vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic); + return GET_APIC_ID_FIELD(*(u32 *)&lapic.regs[APIC_ID]); +} + +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"); + + vm =3D vm_create_with_one_vcpu(&halter, pv_unhalt_halter_guest_code); + kicker =3D vm_vcpu_add(vm, 1, pv_unhalt_kicker_guest_code); + + /* + * 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 =3D pthread_create(&thread, NULL, pv_unhalt_halter_thread, halter); + TEST_ASSERT(!r, "pthread_create halter failed, errno=3D%d", errno); + + /* 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 =3D pthread_join(thread, NULL); + TEST_ASSERT(!r, "pthread_join halter failed, errno=3D%d", errno); =20 kvm_vm_free(vm); } @@ -215,4 +305,5 @@ int main(void) kvm_vm_free(vm); =20 test_pv_unhalt(); + test_pv_unhalt_kick(); } --=20 2.43.7