Commit 18178ff8 authored by Vitaly Kuznetsov's avatar Vitaly Kuznetsov Committed by Paolo Bonzini
Browse files

KVM: selftests: add Enlightened VMCS test



Modify test library and add eVMCS test. This includes nVMX save/restore
testing.

Signed-off-by: default avatarVitaly Kuznetsov <vkuznets@redhat.com>
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent c939989d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/sync_regs_test
TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test
TEST_GEN_PROGS_x86_64 += x86_64/cr4_cpuid_sync_test
TEST_GEN_PROGS_x86_64 += x86_64/state_test
TEST_GEN_PROGS_x86_64 += x86_64/evmcs_test
TEST_GEN_PROGS_x86_64 += dirty_log_test

TEST_GEN_PROGS_aarch64 += dirty_log_test
+1098 −0

File added.

Preview size limit exceeded, changes collapsed.

+28 −0
Original line number Diff line number Diff line
@@ -339,6 +339,8 @@ struct vmx_msr_entry {
	uint64_t value;
} __attribute__ ((aligned(16)));

#include "evmcs.h"

static inline int vmxon(uint64_t phys)
{
	uint8_t ret;
@@ -372,6 +374,9 @@ static inline int vmptrld(uint64_t vmcs_pa)
{
	uint8_t ret;

	if (enable_evmcs)
		return -1;

	__asm__ __volatile__ ("vmptrld %[pa]; setna %[ret]"
		: [ret]"=rm"(ret)
		: [pa]"m"(vmcs_pa)
@@ -385,6 +390,9 @@ static inline int vmptrst(uint64_t *value)
	uint64_t tmp;
	uint8_t ret;

	if (enable_evmcs)
		return evmcs_vmptrst(value);

	__asm__ __volatile__("vmptrst %[value]; setna %[ret]"
		: [value]"=m"(tmp), [ret]"=rm"(ret)
		: : "cc", "memory");
@@ -411,6 +419,9 @@ static inline int vmlaunch(void)
{
	int ret;

	if (enable_evmcs)
		return evmcs_vmlaunch();

	__asm__ __volatile__("push %%rbp;"
			     "push %%rcx;"
			     "push %%rdx;"
@@ -443,6 +454,9 @@ static inline int vmresume(void)
{
	int ret;

	if (enable_evmcs)
		return evmcs_vmresume();

	__asm__ __volatile__("push %%rbp;"
			     "push %%rcx;"
			     "push %%rdx;"
@@ -482,6 +496,9 @@ static inline int vmread(uint64_t encoding, uint64_t *value)
	uint64_t tmp;
	uint8_t ret;

	if (enable_evmcs)
		return evmcs_vmread(encoding, value);

	__asm__ __volatile__("vmread %[encoding], %[value]; setna %[ret]"
		: [value]"=rm"(tmp), [ret]"=rm"(ret)
		: [encoding]"r"(encoding)
@@ -506,6 +523,9 @@ static inline int vmwrite(uint64_t encoding, uint64_t value)
{
	uint8_t ret;

	if (enable_evmcs)
		return evmcs_vmwrite(encoding, value);

	__asm__ __volatile__ ("vmwrite %[value], %[encoding]; setna %[ret]"
		: [ret]"=rm"(ret)
		: [value]"rm"(value), [encoding]"r"(encoding)
@@ -543,6 +563,14 @@ struct vmx_pages {
	void *vmwrite_hva;
	uint64_t vmwrite_gpa;
	void *vmwrite;

	void *vp_assist_hva;
	uint64_t vp_assist_gpa;
	void *vp_assist;

	void *enlightened_vmcs_hva;
	uint64_t enlightened_vmcs_gpa;
	void *enlightened_vmcs;
};

struct vmx_pages *vcpu_alloc_vmx(struct kvm_vm *vm, vm_vaddr_t *p_vmx_gva);
+36 −12
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@
#include "processor.h"
#include "vmx.h"

bool enable_evmcs;

/* Allocate memory regions for nested VMX tests.
 *
 * Input Args:
@@ -62,6 +64,20 @@ vcpu_alloc_vmx(struct kvm_vm *vm, vm_vaddr_t *p_vmx_gva)
	vmx->vmwrite_gpa = addr_gva2gpa(vm, (uintptr_t)vmx->vmwrite);
	memset(vmx->vmwrite_hva, 0, getpagesize());

	/* Setup of a region of guest memory for the VP Assist page. */
	vmx->vp_assist = (void *)vm_vaddr_alloc(vm, getpagesize(),
						0x10000, 0, 0);
	vmx->vp_assist_hva = addr_gva2hva(vm, (uintptr_t)vmx->vp_assist);
	vmx->vp_assist_gpa = addr_gva2gpa(vm, (uintptr_t)vmx->vp_assist);

	/* Setup of a region of guest memory for the enlightened VMCS. */
	vmx->enlightened_vmcs = (void *)vm_vaddr_alloc(vm, getpagesize(),
						       0x10000, 0, 0);
	vmx->enlightened_vmcs_hva =
		addr_gva2hva(vm, (uintptr_t)vmx->enlightened_vmcs);
	vmx->enlightened_vmcs_gpa =
		addr_gva2gpa(vm, (uintptr_t)vmx->enlightened_vmcs);

	*p_vmx_gva = vmx_gva;
	return vmx;
}
@@ -112,6 +128,7 @@ bool prepare_for_vmx_operation(struct vmx_pages *vmx)

bool load_vmcs(struct vmx_pages *vmx)
{
	if (!enable_evmcs) {
		/* Load a VMCS. */
		*(uint32_t *)(vmx->vmcs) = vmcs_revision();
		if (vmclear(vmx->vmcs_gpa))
@@ -121,9 +138,16 @@ bool load_vmcs(struct vmx_pages *vmx)
			return false;

		/* Setup shadow VMCS, do not load it yet. */
	*(uint32_t *)(vmx->shadow_vmcs) = vmcs_revision() | 0x80000000ul;
		*(uint32_t *)(vmx->shadow_vmcs) =
			vmcs_revision() | 0x80000000ul;
		if (vmclear(vmx->shadow_vmcs_gpa))
			return false;
	} else {
		if (evmcs_vmptrld(vmx->enlightened_vmcs_gpa,
				  vmx->enlightened_vmcs))
			return false;
		current_evmcs->revision_id = vmcs_revision();
	}

	return true;
}
+160 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2018, Red Hat, Inc.
 *
 * Tests for Enlightened VMCS, 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 "vmx.h"

#define VCPU_ID		5

static bool have_nested_state;

void l2_guest_code(void)
{
	GUEST_SYNC(6);

	GUEST_SYNC(7);

	/* Done, exit to L1 and never come back.  */
	vmcall();
}

void l1_guest_code(struct vmx_pages *vmx_pages)
{
#define L2_GUEST_STACK_SIZE 64
	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];

	enable_vp_assist(vmx_pages->vp_assist_gpa, vmx_pages->vp_assist);

	GUEST_ASSERT(vmx_pages->vmcs_gpa);
	GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
	GUEST_SYNC(3);
	GUEST_ASSERT(load_vmcs(vmx_pages));
	GUEST_ASSERT(vmptrstz() == vmx_pages->enlightened_vmcs_gpa);

	GUEST_SYNC(4);
	GUEST_ASSERT(vmptrstz() == vmx_pages->enlightened_vmcs_gpa);

	prepare_vmcs(vmx_pages, l2_guest_code,
		     &l2_guest_stack[L2_GUEST_STACK_SIZE]);

	GUEST_SYNC(5);
	GUEST_ASSERT(vmptrstz() == vmx_pages->enlightened_vmcs_gpa);
	GUEST_ASSERT(!vmlaunch());
	GUEST_ASSERT(vmptrstz() == vmx_pages->enlightened_vmcs_gpa);
	GUEST_SYNC(8);
	GUEST_ASSERT(!vmresume());
	GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
	GUEST_SYNC(9);
}

void guest_code(struct vmx_pages *vmx_pages)
{
	GUEST_SYNC(1);
	GUEST_SYNC(2);

	if (vmx_pages)
		l1_guest_code(vmx_pages);

	GUEST_DONE();
}

int main(int argc, char *argv[])
{
	struct vmx_pages *vmx_pages = NULL;
	vm_vaddr_t vmx_pages_gva = 0;

	struct kvm_regs regs1, regs2;
	struct kvm_vm *vm;
	struct kvm_run *run;
	struct kvm_x86_state *state;
	struct ucall uc;
	int stage;
	uint16_t evmcs_ver;
	struct kvm_enable_cap enable_evmcs_cap = {
		.cap = KVM_CAP_HYPERV_ENLIGHTENED_VMCS,
		 .args[0] = (unsigned long)&evmcs_ver
	};

	struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(1);

	/* Create VM */
	vm = vm_create_default(VCPU_ID, 0, guest_code);

	vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());

	if (!kvm_check_cap(KVM_CAP_NESTED_STATE) ||
	    !kvm_check_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS)) {
		printf("capabilities not available, skipping test\n");
		exit(KSFT_SKIP);
	}

	vcpu_ioctl(vm, VCPU_ID, KVM_ENABLE_CAP, &enable_evmcs_cap);

	run = vcpu_state(vm, VCPU_ID);

	vcpu_regs_get(vm, VCPU_ID, &regs1);

	vmx_pages = vcpu_alloc_vmx(vm, &vmx_pages_gva);
	vcpu_args_set(vm, VCPU_ID, 1, vmx_pages_gva);

	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 (get_ucall(vm, VCPU_ID, &uc)) {
		case UCALL_ABORT:
			TEST_ASSERT(false, "%s at %s:%d", (const char *)uc.args[0],
				    __FILE__, uc.args[1]);
			/* NOT REACHED */
		case UCALL_SYNC:
			break;
		case UCALL_DONE:
			goto done;
		default:
			TEST_ASSERT(false, "Unknown ucall 0x%x.", uc.cmd);
		}

		/* UCALL_SYNC is handled here.  */
		TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
			    uc.args[1] == stage, "Unexpected register values vmexit #%lx, got %lx",
			    stage, (ulong)uc.args[1]);

		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);
}