From: David Woodhouse Assert that a new plane-1 vCPU starts with KVM_PLANE_RUN_STATE_INACTIVE and plane 0's with no flags, that every valid combination of KVM_PLANE_RUN_STATE_USER_HOLD, KVM_PLANE_RUN_STATE_SUSPENDED and KVM_PLANE_RUN_STATE_INACTIVE reads back as written (so a saved state restores), and that clearing them reads back as runnable. INACTIVE on plane 0, INACTIVE together with SUSPENDED and the lowest undefined bit are refused with EINVAL, and a refused SET leaves an existing hold in place. On a VM that did not enable more than one plane both ioctls fail with EINVAL. With plane 0 held and nothing runnable, KVM_RUN still honours immediate_exit with EINTR, and otherwise exits with KVM_EXIT_PLANE_EVENT and cause KVM_PLANE_EVENT_HELD, with kvm_run's if_flag and apic_base filled in as on any other exit. That exit leaves registers marked in kvm_dirty_regs unconsumed and unrefreshed, and once plane 1 is released they land in plane 1, not plane 0. If plane 0 holds a pending I/O completion, here from a real-mode OUT, a held KVM_RUN fails with EBUSY, with or without immediate_exit, leaves the KVM_EXIT_IO in kvm_run and plane 0's RIP on the OUT, and the OUT retires once plane 0 is released. Signed-off-by: David Woodhouse --- tools/testing/selftests/kvm/x86/planes_test.c | 198 +++++++++++++++++- 1 file changed, 196 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/planes_test.c b/tools/testing/selftests/kvm/x86/planes_test.c index f7db6cb6807d..968f1b099b8e 100644 --- a/tools/testing/selftests/kvm/x86/planes_test.c +++ b/tools/testing/selftests/kvm/x86/planes_test.c @@ -8,6 +8,10 @@ #include #include "kvm_util.h" +#include "processor.h" + +/* Where the run-state test puts plane 0's one instruction. */ +#define CODE_GPA 0x1000 /* * Whether this host lets a VM have more than one plane: the opt-in also @@ -61,12 +65,22 @@ static void test_enable_cap_bounds(void) static void test_enable_cap_too_late(void) { struct kvm_vm *vm = vm_create_barebones_irqchip(true); - - __vm_vcpu_add(vm, 0); + struct kvm_vcpu *vcpu = __vm_vcpu_add(vm, 0); TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_PLANES, 2), -1); TEST_ASSERT_EQ(errno, EEXIST); + /* Without more than one user-managed plane there is no run state. */ + { + u64 flags; + + TEST_ASSERT_EQ(__vcpu_ioctl(vcpu, KVM_GET_PLANE_RUN_STATE, &flags), -1); + TEST_ASSERT_EQ(errno, EINVAL); + flags = 0; + TEST_ASSERT_EQ(__vcpu_ioctl(vcpu, KVM_SET_PLANE_RUN_STATE, &flags), -1); + TEST_ASSERT_EQ(errno, EINVAL); + } + kvm_vm_free(vm); } @@ -107,6 +121,186 @@ static void test_plane_lifecycle(void) vcpu0 = __vm_vcpu_add(vm, 0); vcpu1 = __vm_plane_vcpu_add(vcpu0, plane); + /* + * A new plane-1 vCPU is inactive, and plane 0's has no flags. + * Every valid combination reads back as written, so a saved state + * restores; unknown bits, INACTIVE on plane 0 and INACTIVE together + * with SUSPENDED are refused. + */ + { + static const u64 states[] = { + KVM_PLANE_RUN_STATE_USER_HOLD, + KVM_PLANE_RUN_STATE_SUSPENDED, + KVM_PLANE_RUN_STATE_USER_HOLD | + KVM_PLANE_RUN_STATE_SUSPENDED, + KVM_PLANE_RUN_STATE_INACTIVE, + KVM_PLANE_RUN_STATE_USER_HOLD | + KVM_PLANE_RUN_STATE_INACTIVE, + 0, + }; + u64 flags = ~0ULL; + int i; + + plane_vcpu_ioctl(vcpu1, KVM_GET_PLANE_RUN_STATE, &flags); + TEST_ASSERT_EQ(flags, KVM_PLANE_RUN_STATE_INACTIVE); + flags = ~0ULL; + vcpu_ioctl(vcpu0, KVM_GET_PLANE_RUN_STATE, &flags); + TEST_ASSERT_EQ(flags, 0); + + flags = KVM_PLANE_RUN_STATE_INACTIVE; + TEST_ASSERT_EQ(__vcpu_ioctl(vcpu0, KVM_SET_PLANE_RUN_STATE, + &flags), -1); + TEST_ASSERT_EQ(errno, EINVAL); + + for (i = 0; i < ARRAY_SIZE(states); i++) { + flags = states[i]; + plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, &flags); + flags = ~0ULL; + plane_vcpu_ioctl(vcpu1, KVM_GET_PLANE_RUN_STATE, &flags); + TEST_ASSERT_EQ(flags, states[i]); + } + + /* A refused SET leaves the hold in place. */ + flags = KVM_PLANE_RUN_STATE_USER_HOLD; + plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, &flags); + + flags = KVM_PLANE_RUN_STATE_INACTIVE | KVM_PLANE_RUN_STATE_SUSPENDED; + TEST_ASSERT_EQ(__plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, + &flags), -1); + TEST_ASSERT_EQ(errno, EINVAL); + + /* The lowest bit that no flag defines. */ + flags = ~(u64)(KVM_PLANE_RUN_STATE_USER_HOLD | + KVM_PLANE_RUN_STATE_SUSPENDED | + KVM_PLANE_RUN_STATE_INACTIVE); + flags &= -flags; + TEST_ASSERT_EQ(__plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, + &flags), -1); + TEST_ASSERT_EQ(errno, EINVAL); + flags = ~0ULL; + plane_vcpu_ioctl(vcpu1, KVM_GET_PLANE_RUN_STATE, &flags); + TEST_ASSERT_EQ(flags, KVM_PLANE_RUN_STATE_USER_HOLD); + } + + /* + * Plane 1 is held from above. With plane 0 held too, nothing is + * runnable, and KVM_RUN hands that back to userspace instead of + * running the held plane 0. + */ + { + u64 flags = KVM_PLANE_RUN_STATE_USER_HOLD; + struct kvm_regs regs; + + vcpu_ioctl(vcpu0, KVM_SET_PLANE_RUN_STATE, &flags); + + /* immediate_exit yields EINTR, as on any KVM_RUN. */ + vcpu0->run->immediate_exit = 1; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), -1); + TEST_ASSERT_EQ(errno, EINTR); + vcpu0->run->immediate_exit = 0; + + /* The exit fills in kvm_run like any other: poison two fields. */ + vcpu0->run->if_flag = 0xff; + vcpu0->run->apic_base = 0; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), 0); + TEST_ASSERT_EQ(vcpu0->run->exit_reason, KVM_EXIT_PLANE_EVENT); + TEST_ASSERT_EQ(vcpu0->run->plane_event.cause, KVM_PLANE_EVENT_HELD); + TEST_ASSERT_EQ(vcpu0->run->plane_event.plane, 0); + TEST_ASSERT_EQ(vcpu0->run->if_flag, 0); + TEST_ASSERT_EQ(vcpu0->run->apic_base, + vcpu_get_msr(vcpu0, MSR_IA32_APICBASE)); + + /* + * Registers userspace marks dirty are for the plane that runs + * next: the held exit neither consumes nor overwrites them. + */ + plane_vcpu_ioctl(vcpu1, KVM_GET_REGS, &vcpu0->run->s.regs.regs); + vcpu0->run->s.regs.regs.rdx = 0x5ca1ab1e; /* arbitrary marker */ + vcpu0->run->kvm_valid_regs = KVM_SYNC_X86_REGS; + vcpu0->run->kvm_dirty_regs = KVM_SYNC_X86_REGS; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), 0); + TEST_ASSERT_EQ(vcpu0->run->exit_reason, KVM_EXIT_PLANE_EVENT); + TEST_ASSERT_EQ(vcpu0->run->kvm_dirty_regs, KVM_SYNC_X86_REGS); + TEST_ASSERT_EQ(vcpu0->run->s.regs.regs.rdx, 0x5ca1ab1e); + + /* + * Released, plane 1 is next and takes them; immediate_exit + * stops it before entry, after the registers are applied. + */ + flags = 0; + plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, &flags); + vcpu0->run->immediate_exit = 1; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), -1); + TEST_ASSERT_EQ(errno, EINTR); + vcpu0->run->immediate_exit = 0; + TEST_ASSERT_EQ(vcpu0->run->kvm_dirty_regs, 0); + vcpu0->run->kvm_valid_regs = 0; + + plane_vcpu_ioctl(vcpu1, KVM_GET_REGS, ®s); + TEST_ASSERT_EQ(regs.rdx, 0x5ca1ab1e); + vcpu_ioctl(vcpu0, KVM_GET_REGS, ®s); + TEST_ASSERT(regs.rdx != 0x5ca1ab1e, + "plane 0 took the registers dirtied while it was held"); + + vcpu_ioctl(vcpu0, KVM_SET_PLANE_RUN_STATE, &flags); + } + + /* + * A completion plane 0 still owes its last exit stays pending while + * plane 0 is held: KVM_RUN fails with EBUSY and leaves the exit in + * kvm_run, and the completion runs once plane 0 runs again. The + * exit is a real-mode OUT at CODE_GPA, which userspace handles. + */ + { + static const u8 out_insn[] = { 0xe6, 0x10 }; /* out %al, $0x10 */ + u64 flags = KVM_PLANE_RUN_STATE_USER_HOLD; + struct kvm_sregs sregs; + struct kvm_regs regs; + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, CODE_GPA, + 1, 1, 0); + memcpy(addr_gpa2hva(vm, CODE_GPA), out_insn, sizeof(out_insn)); + + vcpu_sregs_get(vcpu0, &sregs); + sregs.cs.selector = 0; + sregs.cs.base = 0; + vcpu_sregs_set(vcpu0, &sregs); + vcpu_regs_get(vcpu0, ®s); + regs.rip = CODE_GPA; + vcpu_regs_set(vcpu0, ®s); + + /* Plane 1 stays held, so only plane 0 can run. */ + plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, &flags); + TEST_ASSERT_EQ(__vcpu_run(vcpu0), 0); + TEST_ASSERT_EQ(vcpu0->run->exit_reason, KVM_EXIT_IO); + TEST_ASSERT_EQ(vcpu0->run->io.direction, KVM_EXIT_IO_OUT); + TEST_ASSERT_EQ(vcpu0->run->io.port, 0x10); + + vcpu_ioctl(vcpu0, KVM_SET_PLANE_RUN_STATE, &flags); + vcpu0->run->immediate_exit = 1; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), -1); + TEST_ASSERT_EQ(errno, EBUSY); + vcpu0->run->immediate_exit = 0; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), -1); + TEST_ASSERT_EQ(errno, EBUSY); + TEST_ASSERT_EQ(vcpu0->run->exit_reason, KVM_EXIT_IO); + TEST_ASSERT_EQ(vcpu0->run->io.port, 0x10); + vcpu_regs_get(vcpu0, ®s); + TEST_ASSERT_EQ(regs.rip, CODE_GPA); + + /* Released, plane 0 retires the OUT before immediate_exit. */ + flags = 0; + vcpu_ioctl(vcpu0, KVM_SET_PLANE_RUN_STATE, &flags); + vcpu0->run->immediate_exit = 1; + TEST_ASSERT_EQ(__vcpu_run(vcpu0), -1); + TEST_ASSERT_EQ(errno, EINTR); + vcpu0->run->immediate_exit = 0; + vcpu_regs_get(vcpu0, ®s); + TEST_ASSERT_EQ(regs.rip, CODE_GPA + sizeof(out_insn)); + + plane_vcpu_ioctl(vcpu1, KVM_SET_PLANE_RUN_STATE, &flags); + } + /* Register state is per plane: write plane 1, plane 0 unaffected. */ plane_vcpu_ioctl(vcpu1, KVM_GET_REGS, ®s1); regs1.rbx = 0x5157ca11; /* arbitrary marker */ -- 2.50.1