kvm: selftests: add basic test for state save and restore
authorPaolo Bonzini <pbonzini@redhat.com>
Thu, 26 Jul 2018 11:19:23 +0000 (13:19 +0200)
committerPaolo Bonzini <pbonzini@redhat.com>
Mon, 6 Aug 2018 15:32:04 +0000 (17:32 +0200)
The test calls KVM_RUN repeatedly, and creates an entirely new VM with the
old memory and vCPU state on every exit to userspace.  The kvm_util API is
expanded with two functions that manage the lifetime of a kvm_vm struct:
the first closes the file descriptors and leaves the memory allocated,
and the second opens the file descriptors and reuses the memory from
the previous incarnation of the kvm_vm struct.

For now the test is very basic, as it does not test for example XSAVE or
vCPU events.  However, it will test nested virtualization state starting
with the next patch.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
tools/testing/selftests/kvm/.gitignore
tools/testing/selftests/kvm/Makefile
tools/testing/selftests/kvm/include/kvm_util.h
tools/testing/selftests/kvm/include/x86.h
tools/testing/selftests/kvm/lib/kvm_util.c
tools/testing/selftests/kvm/lib/kvm_util_internal.h
tools/testing/selftests/kvm/lib/x86.c
tools/testing/selftests/kvm/state_test.c [new file with mode: 0644]

index 8fb7903f6643c6805d70cef7d49e5a62d51fbcc2..4202139d81d92c724bcb18df30b41deac28cb935 100644 (file)
@@ -2,3 +2,4 @@ cr4_cpuid_sync_test
 set_sregs_test
 sync_regs_test
 vmx_tsc_adjust_test
+state_test
index 65bda4fb0ad6da1d5bcc374e637afec1434f7340..dd0e5163f01fb7aeb1381581a7e9a64cb781e314 100644 (file)
@@ -10,6 +10,7 @@ TEST_GEN_PROGS_x86_64 = set_sregs_test
 TEST_GEN_PROGS_x86_64 += sync_regs_test
 TEST_GEN_PROGS_x86_64 += vmx_tsc_adjust_test
 TEST_GEN_PROGS_x86_64 += cr4_cpuid_sync_test
+TEST_GEN_PROGS_x86_64 += state_test
 
 TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M))
 LIBKVM += $(LIBKVM_$(UNAME_M))
index 87e05664c7f9db288974705a9af3fd90f4f9b5bf..d32632f71ab8d3021e6a8a6db2270a286d3a4d67 100644 (file)
@@ -53,6 +53,8 @@ int kvm_check_cap(long cap);
 
 struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm);
 void kvm_vm_free(struct kvm_vm *vmp);
+void kvm_vm_restart(struct kvm_vm *vmp, int perm);
+void kvm_vm_release(struct kvm_vm *vmp);
 
 int kvm_memcmp_hva_gva(void *hva,
        struct kvm_vm *vm, const vm_vaddr_t gva, size_t len);
index 560304e595125e42b40a271cb3e2ced25460dd66..42c3596815b83fd996741a4901365ce1b2600a8a 100644 (file)
@@ -303,6 +303,10 @@ static inline unsigned long get_xmm(int n)
        return 0;
 }
 
+struct kvm_x86_state;
+struct kvm_x86_state *vcpu_save_state(struct kvm_vm *vm, uint32_t vcpuid);
+void vcpu_load_state(struct kvm_vm *vm, uint32_t vcpuid, struct kvm_x86_state *state);
+
 /*
  * Basic CPU control in CR0
  */
index 1634828733639fac64abfadd07e53feb28234545..643309d6de743d0962b55bc4ce6f3c4c82df5c75 100644 (file)
@@ -62,6 +62,18 @@ int kvm_check_cap(long cap)
        return ret;
 }
 
+static void vm_open(struct kvm_vm *vm, int perm)
+{
+       vm->kvm_fd = open(KVM_DEV_PATH, perm);
+       if (vm->kvm_fd < 0)
+               exit(KSFT_SKIP);
+
+       /* Create VM. */
+       vm->fd = ioctl(vm->kvm_fd, KVM_CREATE_VM, NULL);
+       TEST_ASSERT(vm->fd >= 0, "KVM_CREATE_VM ioctl failed, "
+               "rc: %i errno: %i", vm->fd, errno);
+}
+
 /* VM Create
  *
  * Input Args:
@@ -90,16 +102,7 @@ struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm)
        TEST_ASSERT(vm != NULL, "Insufficent Memory");
 
        vm->mode = mode;
-       kvm_fd = open(KVM_DEV_PATH, perm);
-       if (kvm_fd < 0)
-               exit(KSFT_SKIP);
-
-       /* Create VM. */
-       vm->fd = ioctl(kvm_fd, KVM_CREATE_VM, NULL);
-       TEST_ASSERT(vm->fd >= 0, "KVM_CREATE_VM ioctl failed, "
-               "rc: %i errno: %i", vm->fd, errno);
-
-       close(kvm_fd);
+       vm_open(vm, perm);
 
        /* Setup mode specific traits. */
        switch (vm->mode) {
@@ -132,6 +135,39 @@ struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm)
        return vm;
 }
 
+/* VM Restart
+ *
+ * Input Args:
+ *   vm - VM that has been released before
+ *   perm - permission
+ *
+ * Output Args: None
+ *
+ * Reopens the file descriptors associated to the VM and reinstates the
+ * global state, such as the irqchip and the memory regions that are mapped
+ * into the guest.
+ */
+void kvm_vm_restart(struct kvm_vm *vmp, int perm)
+{
+       struct userspace_mem_region *region;
+
+       vm_open(vmp, perm);
+       if (vmp->has_irqchip)
+               vm_create_irqchip(vmp);
+
+       for (region = vmp->userspace_mem_region_head; region;
+               region = region->next) {
+               int ret = ioctl(vmp->fd, KVM_SET_USER_MEMORY_REGION, &region->region);
+               TEST_ASSERT(ret == 0, "KVM_SET_USER_MEMORY_REGION IOCTL failed,\n"
+                           "  rc: %i errno: %i\n"
+                           "  slot: %u flags: 0x%x\n"
+                           "  guest_phys_addr: 0x%lx size: 0x%lx",
+                           ret, errno, region->region.slot, region->region.flags,
+                           region->region.guest_phys_addr,
+                           region->region.memory_size);
+       }
+}
+
 /* Userspace Memory Region Find
  *
  * Input Args:
@@ -256,6 +292,23 @@ static void vm_vcpu_rm(struct kvm_vm *vm, uint32_t vcpuid)
        free(vcpu);
 }
 
+void kvm_vm_release(struct kvm_vm *vmp)
+{
+       int ret;
+
+       /* Free VCPUs. */
+       while (vmp->vcpu_head)
+               vm_vcpu_rm(vmp, vmp->vcpu_head->id);
+
+       /* Close file descriptor for the VM. */
+       ret = close(vmp->fd);
+       TEST_ASSERT(ret == 0, "Close of vm fd failed,\n"
+               "  vmp->fd: %i rc: %i errno: %i", vmp->fd, ret, errno);
+
+       close(vmp->kvm_fd);
+       TEST_ASSERT(ret == 0, "Close of /dev/kvm fd failed,\n"
+               "  vmp->kvm_fd: %i rc: %i errno: %i", vmp->kvm_fd, ret, errno);
+}
 
 /* Destroys and frees the VM pointed to by vmp.
  */
@@ -286,22 +339,11 @@ void kvm_vm_free(struct kvm_vm *vmp)
                free(region);
        }
 
-       /* Free VCPUs. */
-       while (vmp->vcpu_head)
-               vm_vcpu_rm(vmp, vmp->vcpu_head->id);
-
        /* Free sparsebit arrays. */
        sparsebit_free(&vmp->vpages_valid);
        sparsebit_free(&vmp->vpages_mapped);
 
-       /* Close file descriptor for the VM. */
-       ret = close(vmp->fd);
-       TEST_ASSERT(ret == 0, "Close of vm fd failed,\n"
-               "  vmp->fd: %i rc: %i errno: %i", vmp->fd, ret, errno);
-
-       close(vmp->kvm_fd);
-       TEST_ASSERT(ret == 0, "Close of /dev/kvm fd failed,\n"
-               "  vmp->kvm_fd: %i rc: %i errno: %i", vmp->kvm_fd, ret, errno);
+       kvm_vm_release(vmp);
 
        /* Free the structure describing the VM. */
        free(vmp);
@@ -965,6 +1007,8 @@ void vm_create_irqchip(struct kvm_vm *vm)
        ret = ioctl(vm->fd, KVM_CREATE_IRQCHIP, 0);
        TEST_ASSERT(ret == 0, "KVM_CREATE_IRQCHIP IOCTL failed, "
                "rc: %i errno: %i", ret, errno);
+
+       vm->has_irqchip = true;
 }
 
 /* VM VCPU State
index cbb40288890afc40e3e8aa315e9a755514f40b3b..542ed606b338c7884f3c9e4bd58abc8b0e6248bf 100644 (file)
@@ -43,6 +43,7 @@ struct vcpu {
 
 struct kvm_vm {
        int mode;
+       int kvm_fd;
        int fd;
        unsigned int page_size;
        unsigned int page_shift;
@@ -52,6 +53,7 @@ struct kvm_vm {
        struct sparsebit *vpages_valid;
        struct sparsebit *vpages_mapped;
 
+       bool has_irqchip;
        bool pgd_created;
        vm_paddr_t pgd;
        vm_vaddr_t gdt;
index 024e95f1b47068db4e393efa9f8709c883301e7e..2013efb6099139458bd6c714c2d82287d740998a 100644 (file)
@@ -727,3 +727,119 @@ struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code)
 
        return vm;
 }
+
+struct kvm_x86_state {
+       struct kvm_vcpu_events events;
+       struct kvm_mp_state mp_state;
+       struct kvm_regs regs;
+       struct kvm_xsave xsave;
+       struct kvm_xcrs xcrs;
+       struct kvm_sregs sregs;
+       struct kvm_debugregs debugregs;
+       struct kvm_msrs msrs;
+};
+
+static int kvm_get_num_msrs(struct kvm_vm *vm)
+{
+       struct kvm_msr_list nmsrs;
+       int r;
+
+       nmsrs.nmsrs = 0;
+       r = ioctl(vm->kvm_fd, KVM_GET_MSR_INDEX_LIST, &nmsrs);
+       TEST_ASSERT(r == -1 && errno == E2BIG, "Unexpected result from KVM_GET_MSR_INDEX_LIST probe, r: %i",
+               r);
+
+       return nmsrs.nmsrs;
+}
+
+struct kvm_x86_state *vcpu_save_state(struct kvm_vm *vm, uint32_t vcpuid)
+{
+       struct vcpu *vcpu = vcpu_find(vm, vcpuid);
+       struct kvm_msr_list *list;
+       struct kvm_x86_state *state;
+       int nmsrs, r, i;
+
+       nmsrs = kvm_get_num_msrs(vm);
+       list = malloc(sizeof(*list) + nmsrs * sizeof(list->indices[0]));
+       list->nmsrs = nmsrs;
+       r = ioctl(vm->kvm_fd, KVM_GET_MSR_INDEX_LIST, list);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_MSR_INDEX_LIST, r: %i",
+                r);
+
+       state = malloc(sizeof(*state) + nmsrs * sizeof(state->msrs.entries[0]));
+       r = ioctl(vcpu->fd, KVM_GET_VCPU_EVENTS, &state->events);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_VCPU_EVENTS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_GET_MP_STATE, &state->mp_state);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_MP_STATE, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_GET_REGS, &state->regs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_REGS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_GET_XSAVE, &state->xsave);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_XSAVE, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_GET_XCRS, &state->xcrs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_XCRS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_GET_SREGS, &state->sregs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_SREGS, r: %i",
+                r);
+
+       state->msrs.nmsrs = nmsrs;
+       for (i = 0; i < nmsrs; i++)
+               state->msrs.entries[i].index = list->indices[i];
+       r = ioctl(vcpu->fd, KVM_GET_MSRS, &state->msrs);
+        TEST_ASSERT(r == nmsrs, "Unexpected result from KVM_GET_MSRS, r: %i (failed at %x)",
+                r, r == nmsrs ? -1 : list->indices[r]);
+
+       r = ioctl(vcpu->fd, KVM_GET_DEBUGREGS, &state->debugregs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_GET_DEBUGREGS, r: %i",
+                r);
+
+       free(list);
+       return state;
+}
+
+void vcpu_load_state(struct kvm_vm *vm, uint32_t vcpuid, struct kvm_x86_state *state)
+{
+       struct vcpu *vcpu = vcpu_find(vm, vcpuid);
+       int r;
+
+       r = ioctl(vcpu->fd, KVM_SET_XSAVE, &state->xsave);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_XSAVE, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_SET_XCRS, &state->xcrs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_XCRS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_SET_SREGS, &state->sregs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_SREGS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_SET_MSRS, &state->msrs);
+        TEST_ASSERT(r == state->msrs.nmsrs, "Unexpected result from KVM_SET_MSRS, r: %i (failed at %x)",
+                r, r == state->msrs.nmsrs ? -1 : state->msrs.entries[r].index);
+
+       r = ioctl(vcpu->fd, KVM_SET_VCPU_EVENTS, &state->events);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_VCPU_EVENTS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_SET_MP_STATE, &state->mp_state);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_MP_STATE, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_SET_DEBUGREGS, &state->debugregs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_DEBUGREGS, r: %i",
+                r);
+
+       r = ioctl(vcpu->fd, KVM_SET_REGS, &state->regs);
+        TEST_ASSERT(r == 0, "Unexpected result from KVM_SET_REGS, r: %i",
+                r);
+}
diff --git a/tools/testing/selftests/kvm/state_test.c b/tools/testing/selftests/kvm/state_test.c
new file mode 100644 (file)
index 0000000..e47b27b
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * KVM_GET/SET_* tests
+ *
+ * Copyright (C) 2018, Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ *
+ * Tests for vCPU state save/restore, including nested guest state.
+ */
+#define _GNU_SOURCE /* for program_invocation_short_name */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "test_util.h"
+
+#include "kvm_util.h"
+#include "x86.h"
+
+#define VCPU_ID                5
+#define PORT_SYNC      0x1000
+#define PORT_ABORT     0x1001
+#define PORT_DONE      0x1002
+
+static inline void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1)
+{
+       __asm__ __volatile__("in %[port], %%al"
+                            :
+                            : [port]"d"(port), "D"(arg0), "S"(arg1)
+                            : "rax");
+}
+
+#define exit_to_l0(_port, _arg0, _arg1) \
+       __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1))
+
+#define GUEST_ASSERT(_condition) do { \
+       if (!(_condition)) \
+               exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition, __LINE__);\
+} while (0)
+
+#define GUEST_SYNC(stage) \
+       exit_to_l0(PORT_SYNC, "hello", stage);
+
+static bool have_nested_state;
+
+void guest_code(void)
+{
+       GUEST_SYNC(1);
+       GUEST_SYNC(2);
+
+       exit_to_l0(PORT_DONE, 0, 0);
+}
+
+int main(int argc, char *argv[])
+{
+       struct kvm_regs regs1, regs2;
+       struct kvm_vm *vm;
+       struct kvm_run *run;
+       struct kvm_x86_state *state;
+       int stage;
+
+       struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(1);
+
+       /* Create VM */
+       vm = vm_create_default(VCPU_ID, guest_code);
+       vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
+       run = vcpu_state(vm, VCPU_ID);
+
+       vcpu_regs_get(vm, VCPU_ID, &regs1);
+       for (stage = 1;; stage++) {
+               _vcpu_run(vm, VCPU_ID);
+               TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
+                           "Unexpected exit reason: %u (%s),\n",
+                           run->exit_reason,
+                           exit_reason_str(run->exit_reason));
+
+               memset(&regs1, 0, sizeof(regs1));
+               vcpu_regs_get(vm, VCPU_ID, &regs1);
+               switch (run->io.port) {
+               case PORT_ABORT:
+                       TEST_ASSERT(false, "%s at %s:%d", (const char *) regs1.rdi,
+                                   __FILE__, regs1.rsi);
+                       /* NOT REACHED */
+               case PORT_SYNC:
+                       break;
+               case PORT_DONE:
+                       goto done;
+               default:
+                       TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port);
+               }
+
+               /* PORT_SYNC is handled here.  */
+               TEST_ASSERT(!strcmp((const char *)regs1.rdi, "hello") &&
+                           regs1.rsi == stage, "Unexpected register values vmexit #%lx, got %lx",
+                           stage, (ulong) regs1.rsi);
+
+               state = vcpu_save_state(vm, VCPU_ID);
+               kvm_vm_release(vm);
+
+               /* Restore state in a new VM.  */
+               kvm_vm_restart(vm, O_RDWR);
+               vm_vcpu_add(vm, VCPU_ID, 0, 0);
+               vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
+               vcpu_load_state(vm, VCPU_ID, state);
+               run = vcpu_state(vm, VCPU_ID);
+               free(state);
+
+               memset(&regs2, 0, sizeof(regs2));
+               vcpu_regs_get(vm, VCPU_ID, &regs2);
+               TEST_ASSERT(!memcmp(&regs1, &regs2, sizeof(regs2)),
+                           "Unexpected register values after vcpu_load_state; rdi: %lx rsi: %lx",
+                           (ulong) regs2.rdi, (ulong) regs2.rsi);
+       }
+
+done:
+       kvm_vm_free(vm);
+}