From: David Woodhouse 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 --- 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 #include @@ -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