Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: Sean Christopherson <seanjc@google.com>
To: Sean Christopherson <seanjc@google.com>,
	Paolo Bonzini <pbonzini@redhat.com>
Cc: kvm@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Naveen N Rao <naveen@kernel.org>
Subject: [PATCH v2 5/5] *** DO NOT MERGE *** KVM: selftests: Add hacky test to verify x2APIC MSR interception
Date: Wed,  6 May 2026 11:47:46 -0700	[thread overview]
Message-ID: <20260506184746.2719880-6-seanjc@google.com> (raw)
In-Reply-To: <20260506184746.2719880-1-seanjc@google.com>

Not-signed-off-by: Sean Christopherson <seanjc@google.com>
---
 .../testing/selftests/kvm/include/x86/apic.h  |  84 ++++++-
 .../selftests/kvm/x86/fix_hypercall_test.c    |   2 +-
 .../selftests/kvm/x86/xapic_ipi_test.c        |   4 +-
 .../selftests/kvm/x86/xapic_state_test.c      | 217 ++++++++++++++++++
 4 files changed, 296 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/x86/apic.h b/tools/testing/selftests/kvm/include/x86/apic.h
index 31887bdc3d6c..3c3e362f2b98 100644
--- a/tools/testing/selftests/kvm/include/x86/apic.h
+++ b/tools/testing/selftests/kvm/include/x86/apic.h
@@ -23,21 +23,64 @@
 
 #define APIC_BASE_MSR	0x800
 #define X2APIC_ENABLE	(1UL << 10)
+
+#define APIC_DELIVERY_MODE_FIXED	0
+#define APIC_DELIVERY_MODE_LOWESTPRIO	1
+#define APIC_DELIVERY_MODE_SMI		2
+#define APIC_DELIVERY_MODE_NMI		4
+#define APIC_DELIVERY_MODE_INIT		5
+#define APIC_DELIVERY_MODE_EXTINT	7
+
 #define	APIC_ID		0x20
+
 #define	APIC_LVR	0x30
-#define		GET_APIC_ID_FIELD(x)	(((x) >> 24) & 0xFF)
+#define		APIC_LVR_MASK		0xFF00FF
+#define		APIC_LVR_DIRECTED_EOI	(1 << 24)
+#define		GET_APIC_VERSION(x)	((x) & 0xFFu)
+#define		GET_APIC_MAXLVT(x)	(((x) >> 16) & 0xFFu)
+#ifdef CONFIG_X86_32
+#  define	APIC_INTEGRATED(x)	((x) & 0xF0u)
+#else
+#  define	APIC_INTEGRATED(x)	(1)
+#endif
+#define		APIC_XAPIC(x)		((x) >= 0x14)
+#define		APIC_EXT_SPACE(x)	((x) & 0x80000000)
 #define	APIC_TASKPRI	0x80
+#define		APIC_TPRI_MASK		0xFFu
+#define	APIC_ARBPRI	0x90
+#define		APIC_ARBPRI_MASK	0xFFu
 #define	APIC_PROCPRI	0xA0
-#define	GET_APIC_PRI(x) (((x) & GENMASK(7, 4)) >> 4)
-#define	SET_APIC_PRI(x, y) (((x) & ~GENMASK(7, 4)) | (y << 4))
+#define		GET_APIC_PRI(x) (((x) & GENMASK(7, 4)) >> 4)
+#define		SET_APIC_PRI(x, y) (((x) & ~GENMASK(7, 4)) | (y << 4))
 #define	APIC_EOI	0xB0
+#define		APIC_EOI_ACK		0x0 /* Docs say 0 for future compat. */
+#define	APIC_RRR	0xC0
+#define	APIC_LDR	0xD0
+#define		APIC_LDR_MASK		(0xFFu << 24)
+#define		GET_APIC_LOGICAL_ID(x)	(((x) >> 24) & 0xFFu)
+#define		SET_APIC_LOGICAL_ID(x)	(((x) << 24))
+#define		APIC_ALL_CPUS		0xFFu
+#define	APIC_DFR	0xE0
+#define		APIC_DFR_CLUSTER		0x0FFFFFFFul
+#define		APIC_DFR_FLAT			0xFFFFFFFFul
 #define	APIC_SPIV	0xF0
+#define		APIC_SPIV_DIRECTED_EOI		(1 << 12)
 #define		APIC_SPIV_FOCUS_DISABLED	(1 << 9)
 #define		APIC_SPIV_APIC_ENABLED		(1 << 8)
 #define	APIC_ISR	0x100
-#define APIC_IRR	0x200
+#define	APIC_ISR_NR     0x8     /* Number of 32 bit ISR registers. */
+#define	APIC_TMR	0x180
+#define	APIC_IRR	0x200
+#define	APIC_ESR	0x280
+#define		APIC_ESR_SEND_CS	0x00001
+#define		APIC_ESR_RECV_CS	0x00002
+#define		APIC_ESR_SEND_ACC	0x00004
+#define		APIC_ESR_RECV_ACC	0x00008
+#define		APIC_ESR_SENDILL	0x00020
+#define		APIC_ESR_RECVILL	0x00040
+#define		APIC_ESR_ILLREGA	0x00080
+#define 	APIC_LVTCMCI	0x2f0
 #define	APIC_ICR	0x300
-#define	APIC_LVTCMCI	0x2f0
 #define		APIC_DEST_SELF		0x40000
 #define		APIC_DEST_ALLINC	0x80000
 #define		APIC_DEST_ALLBUT	0xC0000
@@ -61,16 +104,41 @@
 #define		APIC_DM_EXTINT		0x00700
 #define		APIC_VECTOR_MASK	0x000FF
 #define	APIC_ICR2	0x310
-#define		SET_APIC_DEST_FIELD(x)	((x) << 24)
-#define APIC_LVTT	0x320
+#define		GET_XAPIC_DEST_FIELD(x)	(((x) >> 24) & 0xFF)
+#define		SET_XAPIC_DEST_FIELD(x)	((x) << 24)
+#define	APIC_LVTT	0x320
+#define	APIC_LVTTHMR	0x330
+#define	APIC_LVTPC	0x340
+#define	APIC_LVT0	0x350
 #define		APIC_LVT_TIMER_ONESHOT		(0 << 17)
 #define		APIC_LVT_TIMER_PERIODIC		(1 << 17)
 #define		APIC_LVT_TIMER_TSCDEADLINE	(2 << 17)
 #define		APIC_LVT_MASKED			(1 << 16)
+#define		APIC_LVT_LEVEL_TRIGGER		(1 << 15)
+#define		APIC_LVT_REMOTE_IRR		(1 << 14)
+#define		APIC_INPUT_POLARITY		(1 << 13)
+#define		APIC_SEND_PENDING		(1 << 12)
+#define		APIC_MODE_MASK			0x700
+#define		GET_APIC_DELIVERY_MODE(x)	(((x) >> 8) & 0x7)
+#define		SET_APIC_DELIVERY_MODE(x, y)	(((x) & ~0x700) | ((y) << 8))
+#define			APIC_MODE_FIXED		0x0
+#define			APIC_MODE_NMI		0x4
+#define			APIC_MODE_EXTINT	0x7
+#define	APIC_LVT1	0x360
+#define	APIC_LVTERR	0x370
 #define	APIC_TMICT	0x380
 #define	APIC_TMCCT	0x390
 #define	APIC_TDCR	0x3E0
-#define	APIC_SELF_IPI	0x3F0
+#define APIC_SELF_IPI	0x3F0
+#define		APIC_TDR_DIV_TMBASE	(1 << 2)
+#define		APIC_TDR_DIV_1		0xB
+#define		APIC_TDR_DIV_2		0x0
+#define		APIC_TDR_DIV_4		0x1
+#define		APIC_TDR_DIV_8		0x2
+#define		APIC_TDR_DIV_16		0x3
+#define		APIC_TDR_DIV_32		0x8
+#define		APIC_TDR_DIV_64		0x9
+#define		APIC_TDR_DIV_128	0xA
 
 #define APIC_VECTOR_TO_BIT_NUMBER(v) ((unsigned int)(v) % 32)
 #define APIC_VECTOR_TO_REG_OFFSET(v) ((unsigned int)(v) / 32 * 0x10)
diff --git a/tools/testing/selftests/kvm/x86/fix_hypercall_test.c b/tools/testing/selftests/kvm/x86/fix_hypercall_test.c
index 753a0e730ea8..ad61da99ee4c 100644
--- a/tools/testing/selftests/kvm/x86/fix_hypercall_test.c
+++ b/tools/testing/selftests/kvm/x86/fix_hypercall_test.c
@@ -63,7 +63,7 @@ static void guest_main(void)
 
 	memcpy(hypercall_insn, other_hypercall_insn, HYPERCALL_INSN_SIZE);
 
-	ret = do_sched_yield(GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID)));
+	ret = do_sched_yield(GET_XAPIC_DEST_FIELD(xapic_read_reg(APIC_ID)));
 
 	/*
 	 * If the quirk is disabled, verify that guest_ud_handler() "returned"
diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
index 39ce9a9369f5..75b87f850abc 100644
--- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
+++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c
@@ -91,7 +91,7 @@ static void halter_guest_code(struct test_data_page *data)
 	verify_apic_base_addr();
 	xapic_enable();
 
-	data->halter_apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
+	data->halter_apic_id = GET_XAPIC_DEST_FIELD(xapic_read_reg(APIC_ID));
 	data->halter_lvr = xapic_read_reg(APIC_LVR);
 
 	/*
@@ -147,7 +147,7 @@ static void sender_guest_code(struct test_data_page *data)
 	 * set data->halter_apic_id.
 	 */
 	icr_val = (APIC_DEST_PHYSICAL | APIC_DM_FIXED | IPI_VECTOR);
-	icr2_val = SET_APIC_DEST_FIELD(data->halter_apic_id);
+	icr2_val = SET_XAPIC_DEST_FIELD(data->halter_apic_id);
 	data->icr = icr_val;
 	data->icr2 = icr2_val;
 
diff --git a/tools/testing/selftests/kvm/x86/xapic_state_test.c b/tools/testing/selftests/kvm/x86/xapic_state_test.c
index 637bb90c1d93..3c7c6a5485e4 100644
--- a/tools/testing/selftests/kvm/x86/xapic_state_test.c
+++ b/tools/testing/selftests/kvm/x86/xapic_state_test.c
@@ -222,6 +222,221 @@ static void test_x2apic_id(void)
 	kvm_vm_free(vm);
 }
 
+#define X2APIC_MSR(r) (0x800 + ((r) >> 4))
+
+static bool is_x2apic_mode = true;
+
+static bool is_ro_only_reg(int reg)
+{
+	switch (reg) {
+	case APIC_ID:
+	case APIC_LVR:
+	case APIC_PROCPRI:
+	case APIC_LDR:
+	case APIC_ARBPRI:
+	case APIC_ISR:
+	case APIC_TMR:
+	case APIC_IRR:
+	case APIC_TMCCT:
+		return true;
+	}
+	return false;
+}
+
+static bool is_xapic_only_reg(int reg)
+{
+	return reg == APIC_ARBPRI || reg == APIC_DFR || reg == APIC_ICR2;
+}
+
+static bool is_accelerated_reg(int reg, bool write)
+{
+	if (!write)
+		return reg != APIC_TMCCT;
+
+	switch (reg) {
+	case APIC_TASKPRI:
+	case APIC_EOI:
+	case APIC_SELF_IPI:
+	case APIC_ICR:
+		return true;
+	default:
+		break;
+	}
+	return false;
+}
+
+static void x2apic_msr_guest_code(void)
+{
+	const u32 xapic_regs[] = {
+		APIC_ID,
+		APIC_LVR,
+		APIC_TASKPRI,
+		APIC_PROCPRI,
+		APIC_LDR,
+		APIC_SPIV,
+		APIC_ISR,
+		APIC_TMR,
+		APIC_IRR,
+		APIC_ESR,
+		APIC_LVTT,
+		APIC_LVTTHMR,
+		APIC_LVTPC,
+		APIC_LVT0,
+		APIC_LVT1,
+		APIC_LVTERR,
+		APIC_TMICT,
+		APIC_TMCCT,
+		APIC_TDCR,
+
+		// APIC_EOI,
+		// APIC_SELF_IPI,
+		// APIC_ICR,
+
+		APIC_ARBPRI,
+		APIC_DFR,
+		APIC_ICR2,
+	};
+	int i, j;
+	u64 val;
+	u32 msr;
+	u8 vec;
+
+	cli();
+
+	if (is_x2apic_mode)
+		x2apic_enable();
+
+	GUEST_SYNC(0xbeef);
+
+	for (i = 0; i < ARRAY_SIZE(xapic_regs); i++) {
+		int nr_regs;
+		u8 rd, wr;
+
+		if (!is_x2apic_mode || is_xapic_only_reg(xapic_regs[i])) {
+			rd = wr = GP_VECTOR;
+		} else {
+			rd = 0;
+			wr = is_ro_only_reg(xapic_regs[i]) ? GP_VECTOR : 0;
+		}
+
+		if (xapic_regs[i] == APIC_IRR ||
+		    xapic_regs[i] == APIC_ISR ||
+		    xapic_regs[i] == APIC_TMR)
+			nr_regs = APIC_ISR_NR;
+		else
+			nr_regs = 1;
+
+		for (j = 0; j < nr_regs; j++) {
+			msr = X2APIC_MSR(xapic_regs[i] + j * 0x10);
+
+			vec = rdmsr_safe(msr, &val);
+			__GUEST_ASSERT(vec == rd,
+				       "Wanted %s on RDMSR(%x), got %s",
+				       ex_str(rd), msr, ex_str(vec));
+			GUEST_SYNC3(xapic_regs[i], false, vec);
+
+			vec = wrmsr_safe(msr, 0);
+			__GUEST_ASSERT(vec == wr,
+				       "Wanted %s on WRMSR(%x), got %s",
+				       ex_str(wr), msr, ex_str(vec));
+
+			GUEST_SYNC3(xapic_regs[i], true, vec);
+		}
+	}
+	GUEST_DONE();
+}
+
+static void test_x2apic_msr_intercepts(void)
+{
+	u64 last_guest, last_io, last_msr, guest_exits, io_exits, msr_exits;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+
+	vm = vm_create_with_one_vcpu(&vcpu, x2apic_msr_guest_code);
+
+	TEST_ASSERT_EQ(vcpu_get_stat(vcpu, guest_induced_exits), 0);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+	TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_SYNC);
+
+	for ( ;; ) {
+		last_guest = vcpu_get_stat(vcpu, guest_induced_exits);
+		last_io = vcpu_get_stat(vcpu, io_exits);
+		last_msr = vcpu_get_stat(vcpu, msr_exits);
+
+		vcpu_run(vcpu);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+		guest_exits = vcpu_get_stat(vcpu, guest_induced_exits);
+		io_exits = vcpu_get_stat(vcpu, io_exits);
+		msr_exits = vcpu_get_stat(vcpu, msr_exits);
+
+		TEST_ASSERT_EQ(io_exits, last_io + 1);
+
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_SYNC: {
+			int vector = uc.args[2];
+			bool write = uc.args[1];
+			u32 reg = uc.args[0];
+
+			printf("reg = %x, write = %u, fault = %u\n", reg, write, vector);
+			if (vector || !is_accelerated_reg(reg, write)) {
+				TEST_ASSERT_EQ(msr_exits, last_msr + 1);
+				TEST_ASSERT_EQ(guest_exits - last_guest,
+					       io_exits - last_io + msr_exits - last_msr);
+			} else {
+				TEST_ASSERT_EQ(msr_exits, last_msr);
+				TEST_ASSERT_EQ(guest_exits - last_guest,
+					       io_exits - last_io);
+			}
+			// printf("On msr = %x\n", msr);
+			break;
+		}
+		case UCALL_DONE:
+			goto test_xapic;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+		default:
+			TEST_FAIL("Unknown ucall %lu", uc.cmd);
+		}
+	}
+
+test_xapic:
+	kvm_vm_free(vm);
+
+	vm = vm_create_with_one_vcpu(&vcpu, x2apic_msr_guest_code);
+	vcpu_clear_cpuid_feature(vcpu, X86_FEATURE_X2APIC);
+	is_x2apic_mode = false;
+	sync_global_to_guest(vm, is_x2apic_mode);
+
+	for ( ;; ) {
+		vcpu_run(vcpu);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+		// vcpu_get_stat(vcpu, io_exits);
+		// vcpu_get_stat(vcpu, msr_exits);
+		// vcpu_get_stat(vcpu, guest_induced_exits);
+
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_SYNC:
+			// u32 msr = uc.args[1];
+
+			// printf("On msr = %x\n", msr);
+			break;
+		case UCALL_DONE:
+			goto done;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+		default:
+			TEST_FAIL("Unknown ucall %lu", uc.cmd);
+		}
+	}
+done:
+}
+
 int main(int argc, char *argv[])
 {
 	struct xapic_vcpu x = {
@@ -230,6 +445,8 @@ int main(int argc, char *argv[])
 	};
 	struct kvm_vm *vm;
 
+	test_x2apic_msr_intercepts();
+
 	vm = vm_create_with_one_vcpu(&x.vcpu, x2apic_guest_code);
 	test_icr(&x);
 	kvm_vm_free(vm);
-- 
2.54.0.545.g6539524ca2-goog


  parent reply	other threads:[~2026-05-06 18:47 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-06 18:47 [PATCH v2 0/5] KVM: SVM: Fix x2AVIC MSR interception issues Sean Christopherson
2026-05-06 18:47 ` [PATCH v2 1/5] KVM: SVM: Disable x2AVIC RDMSR interception for MSRs KVM actually supports Sean Christopherson
2026-05-07 13:56   ` Naveen N Rao
2026-05-07 14:27     ` Sean Christopherson
2026-05-08 16:35       ` Naveen N Rao
2026-05-06 18:47 ` [PATCH v2 2/5] KVM: SVM: Always intercept RDMSR for TMCCT (current APIC timer count) Sean Christopherson
2026-05-07 14:19   ` Naveen N Rao
2026-05-07 15:44     ` Sean Christopherson
2026-05-07 18:26       ` Sean Christopherson
2026-05-08 16:41         ` Naveen N Rao
2026-05-08 16:56           ` Sean Christopherson
2026-05-06 18:47 ` [PATCH v2 3/5] KVM: SVM: Only disable x2AVIC WRMSR interception for MSRs that are accelerated Sean Christopherson
2026-05-08 16:59   ` Naveen N Rao
2026-05-13  6:29   ` Naveen N Rao
2026-05-13 13:33     ` Sean Christopherson
2026-05-06 18:47 ` [PATCH v2 4/5] *** DO NOT MERGE *** KVM: x86: Hack in a stat to track guest-induced exits (for testing) Sean Christopherson
2026-05-08 17:14   ` Naveen N Rao
2026-05-08 17:49     ` Sean Christopherson
2026-05-09  5:08       ` Naveen N Rao
2026-05-06 18:47 ` Sean Christopherson [this message]
2026-05-09  5:10 ` [PATCH v2 0/5] KVM: SVM: Fix x2AVIC MSR interception issues Naveen N Rao

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=20260506184746.2719880-6-seanjc@google.com \
    --to=seanjc@google.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=naveen@kernel.org \
    --cc=pbonzini@redhat.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