set_sregs_test
 sync_regs_test
 vmx_tsc_adjust_test
+state_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))
 
 
 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);
 
        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
  */
 
        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:
        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) {
        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, ®ion->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:
        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.
  */
                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);
        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
 
 
 struct kvm_vm {
        int mode;
+       int kvm_fd;
        int fd;
        unsigned int page_size;
        unsigned int page_shift;
        struct sparsebit *vpages_valid;
        struct sparsebit *vpages_mapped;
 
+       bool has_irqchip;
        bool pgd_created;
        vm_paddr_t pgd;
        vm_vaddr_t gdt;
 
 
        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);
+}
 
--- /dev/null
+/*
+ * 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, ®s1);
+       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(®s1, 0, sizeof(regs1));
+               vcpu_regs_get(vm, VCPU_ID, ®s1);
+               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(®s2, 0, sizeof(regs2));
+               vcpu_regs_get(vm, VCPU_ID, ®s2);
+               TEST_ASSERT(!memcmp(®s1, ®s2, sizeof(regs2)),
+                           "Unexpected register values after vcpu_load_state; rdi: %lx rsi: %lx",
+                           (ulong) regs2.rdi, (ulong) regs2.rsi);
+       }
+
+done:
+       kvm_vm_free(vm);
+}