From nobody Mon Feb 9 18:46:27 2026 Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.10]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C0B713EDAD6; Tue, 3 Feb 2026 18:17:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=198.175.65.10 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770142676; cv=none; b=pQXMqyWijEKDgMRbnG+RjfKOz4ZGN77PioSYUbpIiojo7gX915AQQoFGqaC+WQkUqnDq0srw5iwdE3yed62OC0Mjicxghkj2VWCzhLTLTlWMEa6e3S0a02shPfEXxTtToxdQAhNtduZ726adKHWcP+HOJek2HEFC7hmEjmWKJiU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770142676; c=relaxed/simple; bh=RLyF8Vlox6NJKFpvnb58liGFm595VlLftIT9aGmUkY0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gi4OHtvpUBLIAIJc9OHiOS+43acKJGTkqzJPOyZA9ZeoK3Nys0mYISmCEAl88Q+1hy9YjmDPmytptyZt/YZC/rgZrcjfeEfS0V9Lyj/nY4Q5lwGMwhzuliZefC0gy1q62pjUru69nAO2yeVBALL34aJ6dmG36W8wG/YFP3NRyno= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=YeaYW1OD; arc=none smtp.client-ip=198.175.65.10 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="YeaYW1OD" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1770142675; x=1801678675; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=RLyF8Vlox6NJKFpvnb58liGFm595VlLftIT9aGmUkY0=; b=YeaYW1ODlIpwcw+r8aAfp8IfvlMFET02TWgNCeOP5FaT0iuQrHB3Zdu1 SjIi5kG6+exzWsALDR71EUwyRshvozqfXrH1fz8bdg+weMLJRfM1kb+Yl 5Bcu+ZqoMMgCB2bRckeO97jDMwcVkHRmb2SOS7Qmwklgi8gTie0RC5r0j lojGcC6yPPSlAslrgE6a9hUQnxRwsyfTv6/6UssefyZTiOBQdUUlW8sXc o1cUOb5i1bra7Kgl8pxuJA99katgD1cSEHoQxKZ8aQcBHz+aSRw6378Ts zDDEhXSZbeNQg4RT9BvHqZVhdoTckNvk9PhEgIGBIx6OzSH8ONhoM8wFB w==; X-CSE-ConnectionGUID: ePzrL22WRbeHRyihP3anUQ== X-CSE-MsgGUID: SoRaf16YQH2eU5mYkQSlMw== X-IronPort-AV: E=McAfee;i="6800,10657,11691"; a="88745882" X-IronPort-AV: E=Sophos;i="6.21,271,1763452800"; d="scan'208";a="88745882" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa102.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Feb 2026 10:17:49 -0800 X-CSE-ConnectionGUID: 6Jbr6sP5TQ2RZ7JcbIonSw== X-CSE-MsgGUID: 8wMkKnPDTfupzzfAzZCQ1Q== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.21,271,1763452800"; d="scan'208";a="209605555" Received: from khuang2-desk.gar.corp.intel.com (HELO localhost) ([10.124.221.188]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Feb 2026 10:17:48 -0800 From: isaku.yamahata@intel.com To: kvm@vger.kernel.org Cc: isaku.yamahata@intel.com, isaku.yamahata@gmail.com, Paolo Bonzini , Sean Christopherson , linux-kernel@vger.kernel.org Subject: [PATCH 30/32] KVM: selftests: Test cases for L1 APIC timer virtualization Date: Tue, 3 Feb 2026 10:17:13 -0800 Message-ID: <23a90397b5ece88a8297d4010d5f53acd17335ff.1770116051.git.isaku.yamahata@intel.com> X-Mailer: git-send-email 2.45.2 In-Reply-To: References: 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" From: Isaku Yamahata Test nVMX APIC timer virtualization for L1 to see how KVM in L0 works. It exercises KVM TSC deadline conversion between L0 and L1. Signed-off-by: Isaku Yamahata --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/include/x86/processor.h | 6 + .../kvm/x86/vmx_apic_timer_virt_test.c | 317 ++++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/vmx_apic_timer_virt_tes= t.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selft= ests/kvm/Makefile.kvm index df126774f028..aec47a608b87 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -140,6 +140,7 @@ TEST_GEN_PROGS_x86 +=3D x86/triple_fault_event_test TEST_GEN_PROGS_x86 +=3D x86/recalc_apic_map_test TEST_GEN_PROGS_x86 +=3D x86/aperfmperf_test TEST_GEN_PROGS_x86 +=3D x86/timer_latency +TEST_GEN_PROGS_x86 +=3D x86/vmx_apic_timer_virt_test TEST_GEN_PROGS_x86 +=3D x86/vmx_apic_timer_virt_vmcs_test TEST_GEN_PROGS_x86 +=3D access_tracking_perf_test TEST_GEN_PROGS_x86 +=3D coalesced_io_test diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/te= sting/selftests/kvm/include/x86/processor.h index 57d62a425109..b6c33bc34ed6 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -1430,6 +1430,12 @@ static inline void cli(void) asm volatile ("cli"); } =20 +static inline void serialize(void) +{ + /* serialize instruction. binuutils >=3D 2.35 */ + kvm_asm_safe(".byte 0x0f, 0x01, 0xe8"); +} + void __vm_xsave_require_permission(uint64_t xfeature, const char *name); =20 #define vm_xsave_require_permission(xfeature) \ diff --git a/tools/testing/selftests/kvm/x86/vmx_apic_timer_virt_test.c b/t= ools/testing/selftests/kvm/x86/vmx_apic_timer_virt_test.c new file mode 100644 index 000000000000..ea465e9825d8 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/vmx_apic_timer_virt_test.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025, Intel Corporation. + * + * Test timer expiration conversion and exercise various LVTT mode. + */ + +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "vmx.h" + +#include +#include +#include + +#include + +static uint64_t host_tsc_khz; +static uint64_t max_guest_tsc_khz; + +/* Any value [32, 255] for timer vector is okay. */ +#define TIMER_VECTOR 0xec + +static atomic_int timer_interrupted; + +static void guest_timer_interrupt_handler(struct ex_regs *regs) +{ + atomic_fetch_add(&timer_interrupted, 1); + x2apic_write_reg(APIC_EOI, 0); +} + +static void reap_interrupt(void) +{ + GUEST_ASSERT(!wrmsr_safe(MSR_IA32_TSC_DEADLINE, 0)); + sti_nop_cli(); +} + +static void deadline_write_test(bool do_interrupt, bool mask, + uint64_t deadlines[], size_t nr_deadlines) +{ + int i; + + for (i =3D 0; i < nr_deadlines; i++) { + uint64_t deadline =3D deadlines[i]; + uint64_t val; + + reap_interrupt(); + + atomic_store(&timer_interrupted, 0); + sti(); + GUEST_ASSERT(!wrmsr_safe(MSR_IA32_TSC_DEADLINE, deadline)); + /* serialize to wait for timer interrupt to fire. */ + serialize(); + cli(); + + GUEST_ASSERT(!rdmsr_safe(MSR_IA32_TSC_DEADLINE, &val)); + + if (do_interrupt) { + GUEST_ASSERT(val =3D=3D 0); + if (mask || deadline =3D=3D 0) + GUEST_ASSERT(!atomic_load(&timer_interrupted)); + else + GUEST_ASSERT(atomic_load(&timer_interrupted) =3D=3D 1); + } else { + GUEST_ASSERT(val =3D=3D deadline); + GUEST_ASSERT(!atomic_load(&timer_interrupted)); + } + } +} + +static void deadline_write_hlt_test(uint64_t deadlines[], size_t nr_deadli= nes) +{ + int i; + + for (i =3D 0; i < nr_deadlines; i++) { + uint64_t deadline =3D deadlines[i]; + uint64_t val; + + reap_interrupt(); + + GUEST_ASSERT(deadline); + + atomic_store(&timer_interrupted, 0); + GUEST_ASSERT(!wrmsr_safe(MSR_IA32_TSC_DEADLINE, deadline)); + + GUEST_ASSERT(!rdmsr_safe(MSR_IA32_TSC_DEADLINE, &val)); + GUEST_ASSERT(val =3D=3D deadline || val =3D=3D 0); + GUEST_ASSERT(!atomic_load(&timer_interrupted)); + + asm volatile ("sti; hlt; nop; cli" + /* L1 exit handler doesn't preserve GP registers. */ + : : : "cc", "memory", + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", + "r15"); + + GUEST_ASSERT(!rdmsr_safe(MSR_IA32_TSC_DEADLINE, &val)); + GUEST_ASSERT(val =3D=3D 0); + GUEST_ASSERT(atomic_load(&timer_interrupted) =3D=3D 1); + } +} + +static void deadline_no_int_test(void) +{ + uint64_t tsc =3D rdtsc(); + uint64_t deadlines[] =3D { + 0ull, + /* big values > tsc. */ + max(~0ull - tsc, ~0ull / 2 + tsc / 2), + ~0ull - 1, + ~0ull - 2, + ~0ull, + }; + + deadline_write_test(false, false, deadlines, ARRAY_SIZE(deadlines)); +} + +static void __deadline_int_test(bool do_interrupt, bool mask) +{ + uint64_t tsc =3D rdtsc(); + uint64_t deadlines[] =3D { + 0ull, + 1ull, + 2ull, + /* 1 msec past. tsc /2 is to avoid underflow. */ + min(tsc - guest_tsc_khz, tsc / 2 + 1), + tsc, + }; + + deadline_write_test(do_interrupt, mask, deadlines, ARRAY_SIZE(deadlines)); +} + +static void deadline_int_test(void) +{ + __deadline_int_test(true, false); +} + +static void deadline_int_mask_test(void) +{ + __deadline_int_test(true, true); +} + +static void deadline_hlt_test(void) +{ + uint64_t tsc =3D rdtsc(); + /* 1 msec future. */ + uint64_t future =3D tsc + guest_tsc_khz; + uint64_t deadlines[] =3D { + 1ull, + 2ull, + /* pick a positive value between [0, tsc]. */ + tsc > guest_tsc_khz ? tsc - guest_tsc_khz : tsc / 2 + 1, + tsc, + /* If overflow, pick near future value > tsc. */ + future > tsc ? future : ~0ull / 2 + tsc / 2, + }; + + deadline_write_hlt_test(deadlines, ARRAY_SIZE(deadlines)); +} + +static void guest_code(void) +{ + x2apic_enable(); + + x2apic_write_reg(APIC_LVTT, APIC_LVT_TIMER_TSCDEADLINE | TIMER_VECTOR); + deadline_no_int_test(); + deadline_int_test(); + deadline_hlt_test(); + + x2apic_write_reg(APIC_LVTT, APIC_LVT_TIMER_TSCDEADLINE | + APIC_LVT_MASKED | TIMER_VECTOR); + deadline_no_int_test(); + deadline_int_mask_test(); + + GUEST_DONE(); +} + +static void run_vcpu(struct kvm_vcpu *vcpu) +{ + bool done =3D false; + + while (!done) { + struct ucall uc; + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + /* NOT REACHED */ + case UCALL_SYNC: + break; + case UCALL_PRINTF: + pr_info("%s", uc.buffer); + break; + case UCALL_DONE: + done =3D true; + break; + default: + TEST_FAIL("Unknown ucall %lu", uc.cmd); + } + } +} + +static int test_tsc_deadline(bool tsc_offset, uint64_t guest_tsc_khz__) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + + vm =3D vm_create_with_one_vcpu(&vcpu, guest_code); + + if (guest_tsc_khz__) { + int ret; + + ret =3D __vcpu_ioctl(vcpu, KVM_SET_TSC_KHZ, (void *)guest_tsc_khz__); + if (ret) { + kvm_vm_free(vm); + return ret; + } + + guest_tsc_khz =3D guest_tsc_khz__; + } + + if (tsc_offset) { + uint64_t offset; + + __TEST_REQUIRE(!__vcpu_has_device_attr(vcpu, KVM_VCPU_TSC_CTRL, + KVM_VCPU_TSC_OFFSET), + "KVM_VCPU_TSC_OFFSET not supported; skipping test"); + + /* + * Make the conversion guest deadline virt(L1) =3D> phy (l0) + * can overflow/underflow. + */ + offset =3D -rdtsc(); + vcpu_device_attr_set(vcpu, KVM_VCPU_TSC_CTRL, + KVM_VCPU_TSC_OFFSET, &offset); + } + + vcpu_set_cpuid_feature(vcpu, X86_FEATURE_TSC_DEADLINE_TIMER); + vm_install_exception_handler(vm, TIMER_VECTOR, + guest_timer_interrupt_handler); + + sync_global_to_guest(vm, host_tsc_khz); + sync_global_to_guest(vm, guest_tsc_khz); + run_vcpu(vcpu); + + kvm_vm_free(vm); + + return 0; +} + +static void test(void) +{ + uint64_t guest_tsc_khz__; + int r; + + test_tsc_deadline(false, 0); + test_tsc_deadline(true, 0); + + for (guest_tsc_khz__ =3D host_tsc_khz; guest_tsc_khz__ > 0; + guest_tsc_khz__ >>=3D 1) { + r =3D test_tsc_deadline(false, guest_tsc_khz__); + if (r) + break; + + test_tsc_deadline(true, guest_tsc_khz__); + } + + for (guest_tsc_khz__ =3D host_tsc_khz; guest_tsc_khz__ < max_guest_tsc_kh= z; + guest_tsc_khz__ <<=3D 1) { + r =3D test_tsc_deadline(false, guest_tsc_khz__); + if (r) + break; + + test_tsc_deadline(true, guest_tsc_khz__); + } + + test_tsc_deadline(false, max_guest_tsc_khz); + test_tsc_deadline(true, max_guest_tsc_khz); +} + +int main(int argc, char *argv[]) +{ + uint32_t eax_denominator, ebx_numerator, ecx_hz, edx; + union vmx_ctrl_msr ctls; + uint64_t ctls3; + + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_X2APIC)); + TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL)); + + cpuid(0x15, &eax_denominator, &ebx_numerator, &ecx_hz, &edx); + TEST_REQUIRE(ebx_numerator > 0); + TEST_REQUIRE(eax_denominator > 0); + + if (ecx_hz > 0) + host_tsc_khz =3D ecx_hz * ebx_numerator / eax_denominator / 1000; + else { + uint32_t eax_base_mhz, ebx, ecx; + + cpuid(0x16, &eax_base_mhz, &ebx, &ecx, &edx); + host_tsc_khz =3D eax_base_mhz * 1000 * ebx_numerator / + eax_denominator; + } + TEST_REQUIRE(host_tsc_khz > 0); + + /* See arch/x86/kvm/{x86.c, vmx/vmx.c}. There is no way for userspace to = retrieve it. */ +#define KVM_VMX_TSC_MULTIPLIER_MAX 0xffffffffffffffffULL + max_guest_tsc_khz =3D min((uint64_t)0x7fffffffULL, + mul_u64_u32_shr(KVM_VMX_TSC_MULTIPLIER_MAX, host_tsc_khz, 48)); + + test(); + + return 0; +} --=20 2.45.2