Skip to content

Commit

Permalink
kvm: selftests: add basic test for state save and restore
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Paolo Bonzini committed Aug 6, 2018
1 parent 0a505fe commit fa3899a
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 22 deletions.
1 change: 1 addition & 0 deletions tools/testing/selftests/kvm/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ cr4_cpuid_sync_test
set_sregs_test
sync_regs_test
vmx_tsc_adjust_test
state_test
1 change: 1 addition & 0 deletions tools/testing/selftests/kvm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions tools/testing/selftests/kvm/include/kvm_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions tools/testing/selftests/kvm/include/x86.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
88 changes: 66 additions & 22 deletions tools/testing/selftests/kvm/lib/kvm_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tools/testing/selftests/kvm/lib/kvm_util_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct vcpu {

struct kvm_vm {
int mode;
int kvm_fd;
int fd;
unsigned int page_size;
unsigned int page_shift;
Expand All @@ -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;
Expand Down
116 changes: 116 additions & 0 deletions tools/testing/selftests/kvm/lib/x86.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading

0 comments on commit fa3899a

Please sign in to comment.