From nobody Wed May  7 22:59:33 2025
Received: from us-smtp-delivery-124.mimecast.com
 (us-smtp-delivery-124.mimecast.com [170.10.133.124])
	(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 EFB1A2248A8
	for <linux-kernel@vger.kernel.org>; Tue,  1 Apr 2025 16:12:32 +0000 (UTC)
Authentication-Results: smtp.subspace.kernel.org;
 arc=none smtp.client-ip=170.10.133.124
ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;
	t=1743523954; cv=none;
 b=KAFb0MRXYV6bV2Kh63OPUrwHEbwU3VWpreT8k8jxTV1o4YxuUViEE9i9LL2jB+Tt4h0lVcv4owi550XWBd94uuHTPz616NHcKCEKiqRMYYs/TX3Oy5tAb5bIsWFNsUFx4/SflkdFY0XmBNb6qlTuV3WwVfjUNyt7OrIYXdcQ0wU=
ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org;
	s=arc-20240116; t=1743523954; c=relaxed/simple;
	bh=qiOLED8BkRze4KF+KMmrHA+MJ7cag8ndXy7QMTI4Sw0=;
	h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:
	 MIME-Version;
 b=afEhdOCC2dvo0TCxk5xyc4NVAP0mzw5HhgqprbKPJZCEqqKq2lcZEOW7wzxtt9SzrvWNA7ZiyQ5n76fkMEwGtYv0NTPutUa73zCqx3GVh1lzPMzm2RxpTPVCV3LpwBYym4ydMywrsCGd7Lb3q3DXx2P9mr+NZgvSCcgtutMtamA=
ARC-Authentication-Results: i=1; smtp.subspace.kernel.org;
 dmarc=pass (p=quarantine dis=none) header.from=redhat.com;
 spf=pass smtp.mailfrom=redhat.com;
 dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com
 header.b=e8ltetUC; arc=none smtp.client-ip=170.10.133.124
Authentication-Results: smtp.subspace.kernel.org;
 dmarc=pass (p=quarantine dis=none) header.from=redhat.com
Authentication-Results: smtp.subspace.kernel.org;
 spf=pass smtp.mailfrom=redhat.com
Authentication-Results: smtp.subspace.kernel.org;
	dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com
 header.b="e8ltetUC"
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;
	s=mimecast20190719; t=1743523951;
	h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
	 to:to:cc:cc:mime-version:mime-version:
	 content-transfer-encoding:content-transfer-encoding:
	 in-reply-to:in-reply-to:references:references;
	bh=tPLDTEU28EVlLVRImtt+kppWrpwzBYCScgMp8fqLlvI=;
	b=e8ltetUCCmMGBy/1mpEaWFftDwNG9z4/kkdfB+8Cv+M8SLU7KESbJFUHmCS6cfg3P4h94Y
	In4/2KWLQOOCKu2U/p1HRrJqoudu3nduG/jWcU6WGvZj+XLRqzbk5JBk1f6kD3os456DXM
	LNosIUg9Lp/u8iP5bBfydTso8VShbS8=
Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com
 [209.85.221.69]) by relay.mimecast.com with ESMTP with STARTTLS
 (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id
 us-mta-668-wT1Mo0l-Nki21hqXOOh8OQ-1; Tue, 01 Apr 2025 12:12:30 -0400
X-MC-Unique: wT1Mo0l-Nki21hqXOOh8OQ-1
X-Mimecast-MFC-AGG-ID: wT1Mo0l-Nki21hqXOOh8OQ_1743523949
Received: by mail-wr1-f69.google.com with SMTP id
 ffacd0b85a97d-3913aaf1e32so3298653f8f.0
        for <linux-kernel@vger.kernel.org>;
 Tue, 01 Apr 2025 09:12:30 -0700 (PDT)
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1743523949; x=1744128749;
        h=content-transfer-encoding:mime-version:references:in-reply-to
         :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
         :subject:date:message-id:reply-to;
        bh=tPLDTEU28EVlLVRImtt+kppWrpwzBYCScgMp8fqLlvI=;
        b=nnc5wqt1s5BMbOHxVkFuky6vO5hIz2KTp8M551rWWbsKPeJ6WI+vmSFy3Gy1dcS6Yy
         3LijgFkUld0/g5hqoEytCKJrJneMnqpOMkMw77zqhbMXw2X3j0kM9n40GWwa2bFybwZS
         cnq08FBPLYXZIQFJU50TeIA75PQG9JyC1J+IuFTjlmZ5ar616/rC6f0FCtVasyiElSij
         shK7Ba2hxvNpKZravXV/TmgsLUx1IUdUgXpb3vrhfVfW2sYa4gklKIa040DP+JrH/Ypw
         vFOtAQVXhwWZKDy2O/JXPGPkocBi5Qo5R9q+IrKT0vLmq2qa5kqHiDA7ASzmUSsw85yi
         3vRg==
X-Gm-Message-State: AOJu0Yzl1ZzwemtH6bBPorHce3gQMvL8UMrzNHkU9/+M0wAFePaHfCm3
	s/rWCfVVzWgNhHHmYSmk93VffC1jSWcKOXq18gB1/gSbWhCL33Gch1pTwV9NY5G+4rp44Qdv7op
	Lcd6JZPNxx2WM5XAsKspm2/YxVDK2nZ9OiW+LdZ5Ch03hjBVJfcW2BaQORyW0VI5++Vo2Gagum9
	UNk9Kk1ssvGGkDQGcVquPUaDdUBtBk2dnOJ59J0+myzfV3xA==
X-Gm-Gg: ASbGncv8BHzArJsqxDmo++CNWsq7CZYcKevnkbGwFdw+pONmSpfS95Ta5t8BOB/6oyp
	RScBMrCDrmT0DzXNyJz4raa4H0L6a/dWsePoxbYKDzZIApE5hH6vWF9KYCRFj8kqxUBsq/R1TCm
	+UHUDeVLwmojphc/sNqEROJrmDdyUsajlhBZVUVhOjdvr2nLOf0qeIFWSjNmFZ10fMpFDqAsFfr
	hgcKWyb9JIvNE2RtCQU4WtnL8ThFoCwMZ5K6LPuIPEtpjj4IqrL/gZxOORP4uN5It2U4qb+dx9G
	JlJWNmVTlHXyHgBfrLEYDg==
X-Received: by 2002:a05:6000:2913:b0:391:122c:8b2 with SMTP id
 ffacd0b85a97d-39c120e1566mr11925050f8f.31.1743523948584;
        Tue, 01 Apr 2025 09:12:28 -0700 (PDT)
X-Google-Smtp-Source: 
 AGHT+IEEasAFSdvxOC3yWEZX6iq1LZ/fATrSFrxZYBcxLlTIQzYSIpNJNzx61Lq79fmq0JTbS0oYjw==
X-Received: by 2002:a05:6000:2913:b0:391:122c:8b2 with SMTP id
 ffacd0b85a97d-39c120e1566mr11924994f8f.31.1743523948129;
        Tue, 01 Apr 2025 09:12:28 -0700 (PDT)
Received: from [192.168.10.48] ([176.206.111.201])
        by smtp.gmail.com with ESMTPSA id
 5b1f17b1804b1-43ea8d2bc7fsm13944985e9.0.2025.04.01.09.12.26
        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
        Tue, 01 Apr 2025 09:12:26 -0700 (PDT)
From: Paolo Bonzini <pbonzini@redhat.com>
To: linux-kernel@vger.kernel.org,
	kvm@vger.kernel.org
Cc: roy.hopkins@suse.com,
	seanjc@google.com,
	thomas.lendacky@amd.com,
	ashish.kalra@amd.com,
	michael.roth@amd.com,
	jroedel@suse.de,
	nsaenz@amazon.com,
	anelkz@amazon.de,
	James.Bottomley@HansenPartnership.com
Subject: [PATCH 29/29] selftests: kvm: add x86-specific plane test
Date: Tue,  1 Apr 2025 18:11:06 +0200
Message-ID: <20250401161106.790710-30-pbonzini@redhat.com>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <20250401161106.790710-1-pbonzini@redhat.com>
References: <20250401161106.790710-1-pbonzini@redhat.com>
Precedence: bulk
X-Mailing-List: linux-kernel@vger.kernel.org
List-Id: <linux-kernel.vger.kernel.org>
List-Subscribe: <mailto:linux-kernel+subscribe@vger.kernel.org>
List-Unsubscribe: <mailto:linux-kernel+unsubscribe@vger.kernel.org>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

Add a new test for x86-specific behavior such as vCPU state sharing
and interrupts.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/include/x86/processor.h     |   1 +
 .../testing/selftests/kvm/lib/x86/processor.c |  15 +
 tools/testing/selftests/kvm/x86/plane_test.c  | 270 ++++++++++++++++++
 4 files changed, 287 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/plane_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selft=
ests/kvm/Makefile.kvm
index b1d0b410cc03..9d94db9d750f 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -82,6 +82,7 @@ TEST_GEN_PROGS_x86 +=3D x86/kvm_pv_test
 TEST_GEN_PROGS_x86 +=3D x86/monitor_mwait_test
 TEST_GEN_PROGS_x86 +=3D x86/nested_emulation_test
 TEST_GEN_PROGS_x86 +=3D x86/nested_exceptions_test
+TEST_GEN_PROGS_x86 +=3D x86/plane_test
 TEST_GEN_PROGS_x86 +=3D x86/platform_info_test
 TEST_GEN_PROGS_x86 +=3D x86/pmu_counters_test
 TEST_GEN_PROGS_x86 +=3D x86/pmu_event_filter_test
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/te=
sting/selftests/kvm/include/x86/processor.h
index 32ab6ca7ec32..cf2095f3a7d5 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -1106,6 +1106,7 @@ static inline void vcpu_clear_cpuid_feature(struct kv=
m_vcpu *vcpu,
=20
 uint64_t vcpu_get_msr(struct kvm_vcpu *vcpu, uint64_t msr_index);
 int _vcpu_set_msr(struct kvm_vcpu *vcpu, uint64_t msr_index, uint64_t msr_=
value);
+int _plane_vcpu_set_msr(struct kvm_plane_vcpu *plane_vcpu, uint64_t msr_in=
dex, uint64_t msr_value);
=20
 /*
  * Assert on an MSR access(es) and pretty print the MSR name when possible.
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testin=
g/selftests/kvm/lib/x86/processor.c
index bd5a802fa7a5..b4431ca7fbca 100644
--- a/tools/testing/selftests/kvm/lib/x86/processor.c
+++ b/tools/testing/selftests/kvm/lib/x86/processor.c
@@ -917,6 +917,21 @@ uint64_t vcpu_get_msr(struct kvm_vcpu *vcpu, uint64_t =
msr_index)
 	return buffer.entry.data;
 }
=20
+int _plane_vcpu_set_msr(struct kvm_plane_vcpu *plane_vcpu, uint64_t msr_in=
dex, uint64_t msr_value)
+{
+	struct {
+		struct kvm_msrs header;
+		struct kvm_msr_entry entry;
+	} buffer =3D {};
+
+	memset(&buffer, 0, sizeof(buffer));
+	buffer.header.nmsrs =3D 1;
+	buffer.entry.index =3D msr_index;
+	buffer.entry.data =3D msr_value;
+
+	return __plane_vcpu_ioctl(plane_vcpu, KVM_SET_MSRS, &buffer.header);
+}
+
 int _vcpu_set_msr(struct kvm_vcpu *vcpu, uint64_t msr_index, uint64_t msr_=
value)
 {
 	struct {
diff --git a/tools/testing/selftests/kvm/x86/plane_test.c b/tools/testing/s=
elftests/kvm/x86/plane_test.c
new file mode 100644
index 000000000000..0fdd8a066723
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/plane_test.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Red Hat, Inc.
+ *
+ * Test for x86-specific VM plane functionality
+ */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "test_util.h"
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "apic.h"
+#include "asm/kvm.h"
+#include "linux/kvm.h"
+
+static void test_plane_regs(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct kvm_plane *plane;
+	struct kvm_plane_vcpu *plane_vcpu;
+
+	struct kvm_regs regs0, regs1;
+
+	vm =3D vm_create_barebones();
+	vcpu =3D __vm_vcpu_add(vm, 0);
+	plane =3D vm_plane_add(vm, 1);
+	plane_vcpu =3D __vm_plane_vcpu_add(vcpu, plane);
+
+	vcpu_ioctl(vcpu, KVM_GET_REGS, &regs0);
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_REGS, &regs1);
+	regs0.rax =3D 0x12345678;
+	regs1.rax =3D 0x87654321;
+
+	vcpu_ioctl(vcpu, KVM_SET_REGS, &regs0);
+	plane_vcpu_ioctl(plane_vcpu, KVM_SET_REGS, &regs1);
+
+	vcpu_ioctl(vcpu, KVM_GET_REGS, &regs0);
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_REGS, &regs1);
+	TEST_ASSERT_EQ(regs0.rax, 0x12345678);
+	TEST_ASSERT_EQ(regs1.rax, 0x87654321);
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("get/set regs for planes\n");
+}
+
+/* Offset of XMM0 in the legacy XSAVE area.  */
+#define XSTATE_BV_OFFSET	(0x200/4)
+#define XMM_OFFSET		(0xa0/4)
+#define PKRU_OFFSET		(0xa80/4)
+
+static void test_plane_fpu_nonshared(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct kvm_plane *plane;
+	struct kvm_plane_vcpu *plane_vcpu;
+
+	struct kvm_xsave xsave0, xsave1;
+
+	vm =3D vm_create_barebones();
+	TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_FPU), false);
+
+	vcpu =3D __vm_vcpu_add(vm, 0);
+	vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
+	vcpu_set_cpuid(vcpu);
+
+	plane =3D vm_plane_add(vm, 1);
+	plane_vcpu =3D __vm_plane_vcpu_add(vcpu, plane);
+
+	vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
+	xsave0.region[XSTATE_BV_OFFSET] |=3D XFEATURE_MASK_FP | XFEATURE_MASK_SSE;
+	xsave0.region[XMM_OFFSET] =3D 0x12345678;
+	vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
+
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave1);
+	xsave1.region[XSTATE_BV_OFFSET] |=3D XFEATURE_MASK_FP | XFEATURE_MASK_SSE;
+	xsave1.region[XMM_OFFSET] =3D 0x87654321;
+	plane_vcpu_ioctl(plane_vcpu, KVM_SET_XSAVE, &xsave1);
+
+	memset(&xsave0, 0, sizeof(xsave0));
+	vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
+	TEST_ASSERT_EQ(xsave0.region[XMM_OFFSET], 0x12345678);
+
+	memset(&xsave1, 0, sizeof(xsave0));
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave1);
+	TEST_ASSERT_EQ(xsave1.region[XMM_OFFSET], 0x87654321);
+
+	ksft_test_result_pass("get/set FPU not shared across planes\n");
+}
+
+static void test_plane_fpu_shared(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct kvm_plane *plane;
+	struct kvm_plane_vcpu *plane_vcpu;
+
+	struct kvm_xsave xsave0, xsave1;
+
+	vm =3D vm_create_barebones();
+	vm_enable_cap(vm, KVM_CAP_PLANES_FPU, 1ul);
+	TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_FPU), true);
+
+	vcpu =3D __vm_vcpu_add(vm, 0);
+	vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
+	vcpu_set_cpuid(vcpu);
+
+	plane =3D vm_plane_add(vm, 1);
+	plane_vcpu =3D __vm_plane_vcpu_add(vcpu, plane);
+
+	vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
+
+	xsave0.region[XSTATE_BV_OFFSET] |=3D XFEATURE_MASK_FP | XFEATURE_MASK_SSE;
+	xsave0.region[XMM_OFFSET] =3D 0x12345678;
+	vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave1);
+	TEST_ASSERT_EQ(xsave1.region[XMM_OFFSET], 0x12345678);
+
+	xsave1.region[XSTATE_BV_OFFSET] |=3D XFEATURE_MASK_FP | XFEATURE_MASK_SSE;
+	xsave1.region[XMM_OFFSET] =3D 0x87654321;
+	plane_vcpu_ioctl(plane_vcpu, KVM_SET_XSAVE, &xsave1);
+	vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
+	TEST_ASSERT_EQ(xsave0.region[XMM_OFFSET], 0x87654321);
+
+	ksft_test_result_pass("get/set FPU shared across planes\n");
+
+	if (!this_cpu_has(X86_FEATURE_PKU)) {
+		ksft_test_result_skip("get/set PKRU with shared FPU\n");
+		goto exit;
+	}
+
+	xsave0.region[XSTATE_BV_OFFSET] =3D XFEATURE_MASK_PKRU;
+	xsave0.region[PKRU_OFFSET] =3D 0xffffffff;
+	vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave0);
+
+	xsave0.region[XSTATE_BV_OFFSET] =3D XFEATURE_MASK_PKRU;
+	xsave0.region[PKRU_OFFSET] =3D 0xaaaaaaaa;
+	vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave1);
+	assert(xsave1.region[PKRU_OFFSET] =3D=3D 0xffffffff);
+
+	xsave1.region[XSTATE_BV_OFFSET] =3D XFEATURE_MASK_PKRU;
+	xsave1.region[PKRU_OFFSET] =3D 0x55555555;
+	plane_vcpu_ioctl(plane_vcpu, KVM_SET_XSAVE, &xsave1);
+	vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
+	assert(xsave0.region[PKRU_OFFSET] =3D=3D 0xaaaaaaaa);
+
+	ksft_test_result_pass("get/set PKRU with shared FPU\n");
+
+exit:
+	kvm_vm_free(vm);
+}
+
+#define APIC_SPIV		0xF0
+#define APIC_IRR		0x200
+
+#define MYVEC			192
+
+#define MAKE_MSI(cpu, vector) ((struct kvm_msi){		\
+	.address_lo =3D APIC_DEFAULT_GPA + (((cpu) & 0xff) << 8),	\
+	.address_hi =3D (cpu) & ~0xff,				\
+	.data =3D (vector),					\
+})
+
+static bool has_irr(struct kvm_lapic_state *apic, int vector)
+{
+	int word =3D vector >> 5;
+	int bit_in_word =3D vector & 31;
+	int bit =3D (APIC_IRR + word * 16) * CHAR_BIT + (bit_in_word & 31);
+
+	return apic->regs[bit >> 3] & (1 << (bit & 7));
+}
+
+static void do_enable_lapic(struct kvm_lapic_state *apic)
+{
+	/* set bit 8 */
+	apic->regs[APIC_SPIV + 1] |=3D 1;
+}
+
+static void test_plane_msi(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct kvm_plane *plane;
+	struct kvm_plane_vcpu *plane_vcpu;
+	int r;
+
+	struct kvm_msi msi =3D MAKE_MSI(0, MYVEC);
+	struct kvm_lapic_state lapic0, lapic1;
+
+	vm =3D __vm_create(VM_SHAPE_DEFAULT, 1, 0);
+
+	vcpu =3D __vm_vcpu_add(vm, 0);
+	vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
+	vcpu_set_cpuid(vcpu);
+
+	plane =3D vm_plane_add(vm, 1);
+	plane_vcpu =3D __vm_plane_vcpu_add(vcpu, plane);
+
+	vcpu_set_msr(vcpu, MSR_IA32_APICBASE,
+		     APIC_DEFAULT_GPA | MSR_IA32_APICBASE_ENABLE | X2APIC_ENABLE);
+	vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic0);
+	do_enable_lapic(&lapic0);
+	vcpu_ioctl(vcpu, KVM_SET_LAPIC, &lapic0);
+
+	_plane_vcpu_set_msr(plane_vcpu, MSR_IA32_APICBASE,
+			    APIC_DEFAULT_GPA | MSR_IA32_APICBASE_ENABLE | X2APIC_ENABLE);
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_LAPIC, &lapic1);
+	do_enable_lapic(&lapic1);
+	plane_vcpu_ioctl(plane_vcpu, KVM_SET_LAPIC, &lapic1);
+
+	r =3D __plane_ioctl(plane, KVM_SIGNAL_MSI, &msi);
+	TEST_ASSERT(r =3D=3D 1,
+		   "Delivering interrupt to plane 1. ret: %d, errno: %d", r, errno);
+
+	vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic0);
+	TEST_ASSERT(!has_irr(&lapic0, MYVEC), "Vector clear in plane 0");
+	plane_vcpu_ioctl(plane_vcpu, KVM_GET_LAPIC, &lapic1);
+	TEST_ASSERT(has_irr(&lapic1, MYVEC), "Vector set in plane 1");
+
+	/* req_exit_planes always has priority */
+	vcpu->run->req_exit_planes =3D (1 << 1);
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_PLANE_EVENT);
+	TEST_ASSERT_EQ(vcpu->run->plane_event.cause, KVM_PLANE_EVENT_INTERRUPT);
+	TEST_ASSERT_EQ(vcpu->run->plane_event.pending_event_planes, (1 << 1));
+	TEST_ASSERT_EQ(vcpu->run->plane_event.target, (1 << 1));
+
+	r =3D __vm_ioctl(vm, KVM_SIGNAL_MSI, &msi);
+	TEST_ASSERT(r =3D=3D 1,
+		   "Delivering interrupt to plane 0. ret: %d, errno: %d", r, errno);
+	vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic0);
+	TEST_ASSERT(has_irr(&lapic0, MYVEC), "Vector set in plane 0");
+
+	/* req_exit_planes ignores current plane; current plane is cleared */
+	vcpu->run->plane =3D 1;
+	vcpu->run->req_exit_planes =3D (1 << 0) | (1 << 1);
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_PLANE_EVENT);
+	TEST_ASSERT_EQ(vcpu->run->plane_event.cause, KVM_PLANE_EVENT_INTERRUPT);
+	TEST_ASSERT_EQ(vcpu->run->plane_event.pending_event_planes, (1 << 0));
+	TEST_ASSERT_EQ(vcpu->run->plane_event.target, (1 << 0));
+
+	kvm_vm_free(vm);
+	ksft_test_result_pass("signal MSI for planes\n");
+}
+
+int main(int argc, char *argv[])
+{
+	int cap_planes =3D kvm_check_cap(KVM_CAP_PLANES);
+	TEST_REQUIRE(cap_planes && cap_planes > 1);
+
+	ksft_print_header();
+	ksft_set_plan(5);
+
+	pr_info("# KVM_CAP_PLANES: %d\n", cap_planes);
+
+	test_plane_regs();
+	test_plane_fpu_nonshared();
+	test_plane_fpu_shared();
+	test_plane_msi();
+
+	ksft_finished();
+}
--=20
2.49.0