[RFC PATCH 13/13] KVM: selftests: Cover plane state sharing
From: David Woodhouse
Date: Fri Nov 13 2026 - 16:00:54 EST
From: David Woodhouse <dwmw@xxxxxxxxxxxx>
Check the KVM_CAP_PLANES share mask: KVM_CAP_PLANES_SHARE reports
XCR0|DR6 on the system fd, 0 on a VM fd before the opt-in and the
enabled bits after it; an unknown bit and a non-zero args[2] fail with
EINVAL, and a second opt-in with another mask fails with EEXIST.
Then, with no sharing and with XCR0 and DR6 shared, write XCR0 and DR6
through plane 0 before creating a plane-1 twin, and check that the twin
inherits them only when shared; write them through plane 1 and check
plane 0; and have plane 1 write DR6 with MOV (real mode, plane 0 held)
and check that plane 0 sees the guest's value only when DR6 is shared.
Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
---
tools/testing/selftests/kvm/x86/planes_test.c | 159 +++++++++++++++++-
1 file changed, 157 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/x86/planes_test.c b/tools/testing/selftests/kvm/x86/planes_test.c
index dd3c251f0b7a..02242cc7d576 100644
--- a/tools/testing/selftests/kvm/x86/planes_test.c
+++ b/tools/testing/selftests/kvm/x86/planes_test.c
@@ -3,8 +3,8 @@
* Test the KVM_CAP_PLANES opt-in and the plane lifecycle uAPI: plane
* creation bounds, plane vCPU creation, the independence of
* per-plane vCPU register state, the Hyper-V VP index of a plane
- * vCPU created after KVM_SET_CPUID, and KVM_SET_CPUID2 once a twin
- * has run.
+ * vCPU created after KVM_SET_CPUID, KVM_SET_CPUID2 once a twin has
+ * run, and the KVM_CAP_PLANES share mask.
*/
#include <errno.h>
#include <string.h>
@@ -434,6 +434,154 @@ static void test_cpuid_after_twin_ran(void)
kvm_vm_free(vm);
}
+static int __enable_planes_share(struct kvm_vm *vm, u64 count, u64 share, u64 arg2)
+{
+ struct kvm_enable_cap cap = {
+ .cap = KVM_CAP_PLANES,
+ .args = { count, share, arg2 },
+ };
+
+ return __vm_ioctl(vm, KVM_ENABLE_CAP, &cap);
+}
+
+/*
+ * KVM_CAP_PLANES_SHARE reports the supported share bits on the system fd
+ * and the enabled ones on a VM fd. Unknown bits and a non-zero args[2]
+ * are refused, and like the count the mask is set once.
+ */
+static void test_share_opt_in(void)
+{
+ u64 defined = KVM_PLANE_SHARE_XCR0 | KVM_PLANE_SHARE_DR6;
+ u64 unknown = ~defined & -~defined;
+ struct kvm_vm *vm;
+
+ TEST_ASSERT_EQ(kvm_check_cap(KVM_CAP_PLANES_SHARE), defined);
+
+ vm = vm_create_barebones_irqchip(true);
+ TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_SHARE), 0);
+ TEST_ASSERT_EQ(__enable_planes_share(vm, 2, unknown, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ TEST_ASSERT_EQ(__enable_planes_share(vm, 2, 0, 1), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES), 1);
+
+ TEST_ASSERT_EQ(__enable_planes_share(vm, 2, KVM_PLANE_SHARE_DR6, 0), 0);
+ TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_SHARE), KVM_PLANE_SHARE_DR6);
+ TEST_ASSERT_EQ(__enable_planes_share(vm, 2, KVM_PLANE_SHARE_XCR0, 0), -1);
+ TEST_ASSERT_EQ(errno, EEXIST);
+ TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_SHARE), KVM_PLANE_SHARE_DR6);
+ kvm_vm_free(vm);
+}
+
+static u64 get_xcr0(int fd)
+{
+ struct kvm_xcrs xcrs = {};
+
+ TEST_ASSERT_EQ(ioctl(fd, KVM_GET_XCRS, &xcrs), 0);
+ TEST_ASSERT_EQ(xcrs.nr_xcrs, 1);
+ return xcrs.xcrs[0].value;
+}
+
+static void set_xcr0(int fd, u64 xcr0)
+{
+ struct kvm_xcrs xcrs = { .nr_xcrs = 1, .xcrs[0] = { .xcr = 0, .value = xcr0 } };
+
+ TEST_ASSERT_EQ(ioctl(fd, KVM_SET_XCRS, &xcrs), 0);
+}
+
+static u64 get_dr6(int fd)
+{
+ struct kvm_debugregs dbg;
+
+ TEST_ASSERT_EQ(ioctl(fd, KVM_GET_DEBUGREGS, &dbg), 0);
+ return dbg.dr6;
+}
+
+static void set_dr6(int fd, u64 dr6)
+{
+ struct kvm_debugregs dbg;
+
+ TEST_ASSERT_EQ(ioctl(fd, KVM_GET_DEBUGREGS, &dbg), 0);
+ dbg.dr6 = dr6;
+ TEST_ASSERT_EQ(ioctl(fd, KVM_SET_DEBUGREGS, &dbg), 0);
+}
+
+/*
+ * With @share, a write through one plane is seen through the other, and a
+ * plane vCPU created later starts with plane 0's value; without it each
+ * plane keeps its own. Checked for XCR0 and DR6 by ioctl, and for DR6 by
+ * a guest MOV in plane 1 (real mode, at CODE_GPA, with plane 0 held).
+ */
+static void test_share_state(u64 share)
+{
+ static const u8 mov_dr6[] = {
+ 0x66, 0xb8, 0xf4, 0x0f, 0xff, 0xff, /* mov $0xffff0ff4, %eax */
+ 0x0f, 0x23, 0xf0, /* mov %eax, %dr6 */
+ 0xe6, 0x10, /* out %al, $0x10 */
+ };
+ bool xcr0_shared = share & KVM_PLANE_SHARE_XCR0;
+ bool dr6_shared = share & KVM_PLANE_SHARE_DR6;
+ u64 hold = KVM_PLANE_RUN_STATE_USER_HOLD, none = 0;
+ struct kvm_plane_vcpu *vcpu1;
+ struct kvm_plane *plane;
+ struct kvm_sregs sregs;
+ struct kvm_vcpu *vcpu0;
+ struct kvm_regs regs;
+ struct kvm_vm *vm;
+
+ vm = vm_create_barebones_irqchip(true);
+ TEST_ASSERT_EQ(__enable_planes_share(vm, 2, share, 0), 0);
+ plane = vm_plane_add(vm, 1);
+ vcpu0 = __vm_vcpu_add(vm, 0);
+ vcpu_init_cpuid(vcpu0, kvm_get_supported_cpuid());
+
+ /* Plane 0 first, then a plane-1 twin that inherits or not. */
+ set_xcr0(vcpu0->fd, XFEATURE_MASK_FP | XFEATURE_MASK_SSE);
+ set_dr6(vcpu0->fd, 0xffff0ff1);
+ vcpu1 = __vm_plane_vcpu_add(vcpu0, plane);
+ TEST_ASSERT_EQ(get_xcr0(vcpu1->fd), xcr0_shared ?
+ XFEATURE_MASK_FP | XFEATURE_MASK_SSE : XFEATURE_MASK_FP);
+ TEST_ASSERT_EQ(get_dr6(vcpu1->fd), dr6_shared ? 0xffff0ff1 : 0xffff0ff0);
+
+ /* A write through plane 1. */
+ set_xcr0(vcpu1->fd, XFEATURE_MASK_FP);
+ set_dr6(vcpu1->fd, 0xffff0ff2);
+ TEST_ASSERT_EQ(get_xcr0(vcpu0->fd), xcr0_shared ?
+ XFEATURE_MASK_FP : XFEATURE_MASK_FP | XFEATURE_MASK_SSE);
+ TEST_ASSERT_EQ(get_dr6(vcpu0->fd), dr6_shared ? 0xffff0ff2 : 0xffff0ff1);
+
+ /* A guest write of DR6 in plane 1. */
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, CODE_GPA, 1, 1, 0);
+ memcpy(addr_gpa2hva(vm, CODE_GPA), mov_dr6, sizeof(mov_dr6));
+ plane_vcpu_ioctl(vcpu1, KVM_GET_SREGS, &sregs);
+ sregs.cs.selector = 0;
+ sregs.cs.base = 0;
+ plane_vcpu_ioctl(vcpu1, KVM_SET_SREGS, &sregs);
+ plane_vcpu_ioctl(vcpu1, KVM_GET_REGS, ®s);
+ regs.rip = CODE_GPA;
+ plane_vcpu_ioctl(vcpu1, KVM_SET_REGS, ®s);
+
+ vcpu_ioctl(vcpu0, KVM_SET_PLANE_RUN_STATE, &hold);
+ plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, &none);
+ TEST_ASSERT_EQ(__vcpu_run(vcpu0), 0);
+ TEST_ASSERT_EQ(vcpu0->run->exit_reason, KVM_EXIT_IO);
+ TEST_ASSERT_EQ(vcpu0->run->io.port, 0x10);
+ vcpu0->run->immediate_exit = 1;
+ TEST_ASSERT_EQ(__vcpu_run(vcpu0), -1);
+ TEST_ASSERT_EQ(errno, EINTR);
+ vcpu0->run->immediate_exit = 0;
+
+ TEST_ASSERT(get_dr6(vcpu1->fd) & BIT(2),
+ "plane 1 DR6 0x%lx lacks the B2 bit the guest wrote",
+ (unsigned long)get_dr6(vcpu1->fd));
+ if (dr6_shared)
+ TEST_ASSERT_EQ(get_dr6(vcpu0->fd), get_dr6(vcpu1->fd));
+ else
+ TEST_ASSERT_EQ(get_dr6(vcpu0->fd), 0xffff0ff1);
+
+ kvm_vm_free(vm);
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_has_cap(KVM_CAP_PLANES));
@@ -445,6 +593,13 @@ int main(int argc, char *argv[])
test_plane_lifecycle();
test_late_twin_vp_index();
test_cpuid_after_twin_ran();
+ test_share_opt_in();
+ if (kvm_cpu_has(X86_FEATURE_XSAVE)) {
+ test_share_state(0);
+ test_share_state(KVM_PLANE_SHARE_XCR0 | KVM_PLANE_SHARE_DR6);
+ } else {
+ print_skip("XSAVE not supported, plane state sharing");
+ }
return 0;
}
--
2.50.1