Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm
Pull second set of KVM updates from Paolo Bonzini: "ARM: - Support for Group0 interrupts in guests - Cache management optimizations for ARMv8.4 systems - Userspace interface for RAS - Fault path optimization - Emulated physical timer fixes - Random cleanups x86: - fixes for L1TF - a new test case - non-support for SGX (inject the right exception in the guest) - fix lockdep false positive" * tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm: (49 commits) KVM: VMX: fixes for vmentry_l1d_flush module parameter kvm: selftest: add dirty logging test kvm: selftest: pass in extra memory when create vm kvm: selftest: include the tools headers kvm: selftest: unify the guest port macros tools: introduce test_and_clear_bit KVM: x86: SVM: Call x86_spec_ctrl_set_guest/host() with interrupts disabled KVM: vmx: Inject #UD for SGX ENCLS instruction in guest KVM: vmx: Add defines for SGX ENCLS exiting x86/kvm/vmx: Fix coding style in vmx_setup_l1d_flush() x86: kvm: avoid unused variable warning KVM: Documentation: rename the capability of KVM_CAP_ARM_SET_SERROR_ESR KVM: arm/arm64: Skip updating PTE entry if no change KVM: arm/arm64: Skip updating PMD entry if no change KVM: arm: Use true and false for boolean values KVM: arm/arm64: vgic: Do not use spin_lock_irqsave/restore with irq disabled KVM: arm/arm64: vgic: Move DEBUG_SPINLOCK_BUG_ON to vgic.h KVM: arm: vgic-v3: Add support for ICC_SGI0R and ICC_ASGI1R accesses KVM: arm64: vgic-v3: Add support for ICC_SGI0R_EL1 and ICC_ASGI1R_EL1 accesses KVM: arm/arm64: vgic-v3: Add core support for Group0 SGIs ...
This commit is contained in:
@@ -11,13 +11,16 @@ 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_x86_64 += dirty_log_test
|
||||
|
||||
TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M))
|
||||
LIBKVM += $(LIBKVM_$(UNAME_M))
|
||||
|
||||
INSTALL_HDR_PATH = $(top_srcdir)/usr
|
||||
LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/
|
||||
CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -I..
|
||||
LINUX_TOOL_INCLUDE = $(top_srcdir)tools/include
|
||||
CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_TOOL_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -I..
|
||||
LDFLAGS += -lpthread
|
||||
|
||||
# After inclusion, $(OUTPUT) is defined and
|
||||
# $(TEST_GEN_PROGS) starts with $(OUTPUT)/
|
||||
|
@@ -23,20 +23,6 @@
|
||||
#define X86_FEATURE_OSXSAVE (1<<27)
|
||||
#define VCPU_ID 1
|
||||
|
||||
enum {
|
||||
GUEST_UPDATE_CR4 = 0x1000,
|
||||
GUEST_FAILED,
|
||||
GUEST_DONE,
|
||||
};
|
||||
|
||||
static void exit_to_hv(uint16_t port)
|
||||
{
|
||||
__asm__ __volatile__("in %[port], %%al"
|
||||
:
|
||||
: [port]"d"(port)
|
||||
: "rax");
|
||||
}
|
||||
|
||||
static inline bool cr4_cpuid_is_sync(void)
|
||||
{
|
||||
int func, subfunc;
|
||||
@@ -64,17 +50,15 @@ static void guest_code(void)
|
||||
set_cr4(cr4);
|
||||
|
||||
/* verify CR4.OSXSAVE == CPUID.OSXSAVE */
|
||||
if (!cr4_cpuid_is_sync())
|
||||
exit_to_hv(GUEST_FAILED);
|
||||
GUEST_ASSERT(cr4_cpuid_is_sync());
|
||||
|
||||
/* notify hypervisor to change CR4 */
|
||||
exit_to_hv(GUEST_UPDATE_CR4);
|
||||
GUEST_SYNC(0);
|
||||
|
||||
/* check again */
|
||||
if (!cr4_cpuid_is_sync())
|
||||
exit_to_hv(GUEST_FAILED);
|
||||
GUEST_ASSERT(cr4_cpuid_is_sync());
|
||||
|
||||
exit_to_hv(GUEST_DONE);
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
@@ -95,7 +79,7 @@ int main(int argc, char *argv[])
|
||||
setbuf(stdout, NULL);
|
||||
|
||||
/* Create VM */
|
||||
vm = vm_create_default(VCPU_ID, guest_code);
|
||||
vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
|
||||
@@ -104,16 +88,16 @@ int main(int argc, char *argv[])
|
||||
|
||||
if (run->exit_reason == KVM_EXIT_IO) {
|
||||
switch (run->io.port) {
|
||||
case GUEST_UPDATE_CR4:
|
||||
case GUEST_PORT_SYNC:
|
||||
/* emulate hypervisor clearing CR4.OSXSAVE */
|
||||
vcpu_sregs_get(vm, VCPU_ID, &sregs);
|
||||
sregs.cr4 &= ~X86_CR4_OSXSAVE;
|
||||
vcpu_sregs_set(vm, VCPU_ID, &sregs);
|
||||
break;
|
||||
case GUEST_FAILED:
|
||||
case GUEST_PORT_ABORT:
|
||||
TEST_ASSERT(false, "Guest CR4 bit (OSXSAVE) unsynchronized with CPUID bit.");
|
||||
break;
|
||||
case GUEST_DONE:
|
||||
case GUEST_PORT_DONE:
|
||||
goto done;
|
||||
default:
|
||||
TEST_ASSERT(false, "Unknown port 0x%x.",
|
||||
|
308
tools/testing/selftests/kvm/dirty_log_test.c
Normal file
308
tools/testing/selftests/kvm/dirty_log_test.c
Normal file
@@ -0,0 +1,308 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* KVM dirty page logging test
|
||||
*
|
||||
* Copyright (C) 2018, Red Hat, Inc.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#include "test_util.h"
|
||||
#include "kvm_util.h"
|
||||
|
||||
#define DEBUG printf
|
||||
|
||||
#define VCPU_ID 1
|
||||
/* The memory slot index to track dirty pages */
|
||||
#define TEST_MEM_SLOT_INDEX 1
|
||||
/*
|
||||
* GPA offset of the testing memory slot. Must be bigger than the
|
||||
* default vm mem slot, which is DEFAULT_GUEST_PHY_PAGES.
|
||||
*/
|
||||
#define TEST_MEM_OFFSET (1ULL << 30) /* 1G */
|
||||
/* Size of the testing memory slot */
|
||||
#define TEST_MEM_PAGES (1ULL << 18) /* 1G for 4K pages */
|
||||
/* How many pages to dirty for each guest loop */
|
||||
#define TEST_PAGES_PER_LOOP 1024
|
||||
/* How many host loops to run (one KVM_GET_DIRTY_LOG for each loop) */
|
||||
#define TEST_HOST_LOOP_N 32
|
||||
/* Interval for each host loop (ms) */
|
||||
#define TEST_HOST_LOOP_INTERVAL 10
|
||||
|
||||
/*
|
||||
* Guest variables. We use these variables to share data between host
|
||||
* and guest. There are two copies of the variables, one in host memory
|
||||
* (which is unused) and one in guest memory. When the host wants to
|
||||
* access these variables, it needs to call addr_gva2hva() to access the
|
||||
* guest copy.
|
||||
*/
|
||||
uint64_t guest_random_array[TEST_PAGES_PER_LOOP];
|
||||
uint64_t guest_iteration;
|
||||
uint64_t guest_page_size;
|
||||
|
||||
/*
|
||||
* Writes to the first byte of a random page within the testing memory
|
||||
* region continuously.
|
||||
*/
|
||||
void guest_code(void)
|
||||
{
|
||||
int i = 0;
|
||||
uint64_t volatile *array = guest_random_array;
|
||||
uint64_t volatile *guest_addr;
|
||||
|
||||
while (true) {
|
||||
for (i = 0; i < TEST_PAGES_PER_LOOP; i++) {
|
||||
/*
|
||||
* Write to the first 8 bytes of a random page
|
||||
* on the testing memory region.
|
||||
*/
|
||||
guest_addr = (uint64_t *)
|
||||
(TEST_MEM_OFFSET +
|
||||
(array[i] % TEST_MEM_PAGES) * guest_page_size);
|
||||
*guest_addr = guest_iteration;
|
||||
}
|
||||
/* Tell the host that we need more random numbers */
|
||||
GUEST_SYNC(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Host variables. These variables should only be used by the host
|
||||
* rather than the guest.
|
||||
*/
|
||||
bool host_quit;
|
||||
|
||||
/* Points to the test VM memory region on which we track dirty logs */
|
||||
void *host_test_mem;
|
||||
|
||||
/* For statistics only */
|
||||
uint64_t host_dirty_count;
|
||||
uint64_t host_clear_count;
|
||||
uint64_t host_track_next_count;
|
||||
|
||||
/*
|
||||
* We use this bitmap to track some pages that should have its dirty
|
||||
* bit set in the _next_ iteration. For example, if we detected the
|
||||
* page value changed to current iteration but at the same time the
|
||||
* page bit is cleared in the latest bitmap, then the system must
|
||||
* report that write in the next get dirty log call.
|
||||
*/
|
||||
unsigned long *host_bmap_track;
|
||||
|
||||
void generate_random_array(uint64_t *guest_array, uint64_t size)
|
||||
{
|
||||
uint64_t i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
guest_array[i] = random();
|
||||
}
|
||||
}
|
||||
|
||||
void *vcpu_worker(void *data)
|
||||
{
|
||||
int ret;
|
||||
uint64_t loops, *guest_array, pages_count = 0;
|
||||
struct kvm_vm *vm = data;
|
||||
struct kvm_run *run;
|
||||
struct guest_args args;
|
||||
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
|
||||
/* Retrieve the guest random array pointer and cache it */
|
||||
guest_array = addr_gva2hva(vm, (vm_vaddr_t)guest_random_array);
|
||||
|
||||
DEBUG("VCPU starts\n");
|
||||
|
||||
generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
|
||||
|
||||
while (!READ_ONCE(host_quit)) {
|
||||
/* Let the guest to dirty these random pages */
|
||||
ret = _vcpu_run(vm, VCPU_ID);
|
||||
guest_args_read(vm, VCPU_ID, &args);
|
||||
if (run->exit_reason == KVM_EXIT_IO &&
|
||||
args.port == GUEST_PORT_SYNC) {
|
||||
pages_count += TEST_PAGES_PER_LOOP;
|
||||
generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
|
||||
} else {
|
||||
TEST_ASSERT(false,
|
||||
"Invalid guest sync status: "
|
||||
"exit_reason=%s\n",
|
||||
exit_reason_str(run->exit_reason));
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG("VCPU exits, dirtied %"PRIu64" pages\n", pages_count);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vm_dirty_log_verify(unsigned long *bmap, uint64_t iteration)
|
||||
{
|
||||
uint64_t page;
|
||||
uint64_t volatile *value_ptr;
|
||||
|
||||
for (page = 0; page < TEST_MEM_PAGES; page++) {
|
||||
value_ptr = host_test_mem + page * getpagesize();
|
||||
|
||||
/* If this is a special page that we were tracking... */
|
||||
if (test_and_clear_bit(page, host_bmap_track)) {
|
||||
host_track_next_count++;
|
||||
TEST_ASSERT(test_bit(page, bmap),
|
||||
"Page %"PRIu64" should have its dirty bit "
|
||||
"set in this iteration but it is missing",
|
||||
page);
|
||||
}
|
||||
|
||||
if (test_bit(page, bmap)) {
|
||||
host_dirty_count++;
|
||||
/*
|
||||
* If the bit is set, the value written onto
|
||||
* the corresponding page should be either the
|
||||
* previous iteration number or the current one.
|
||||
*/
|
||||
TEST_ASSERT(*value_ptr == iteration ||
|
||||
*value_ptr == iteration - 1,
|
||||
"Set page %"PRIu64" value %"PRIu64
|
||||
" incorrect (iteration=%"PRIu64")",
|
||||
page, *value_ptr, iteration);
|
||||
} else {
|
||||
host_clear_count++;
|
||||
/*
|
||||
* If cleared, the value written can be any
|
||||
* value smaller or equals to the iteration
|
||||
* number. Note that the value can be exactly
|
||||
* (iteration-1) if that write can happen
|
||||
* like this:
|
||||
*
|
||||
* (1) increase loop count to "iteration-1"
|
||||
* (2) write to page P happens (with value
|
||||
* "iteration-1")
|
||||
* (3) get dirty log for "iteration-1"; we'll
|
||||
* see that page P bit is set (dirtied),
|
||||
* and not set the bit in host_bmap_track
|
||||
* (4) increase loop count to "iteration"
|
||||
* (which is current iteration)
|
||||
* (5) get dirty log for current iteration,
|
||||
* we'll see that page P is cleared, with
|
||||
* value "iteration-1".
|
||||
*/
|
||||
TEST_ASSERT(*value_ptr <= iteration,
|
||||
"Clear page %"PRIu64" value %"PRIu64
|
||||
" incorrect (iteration=%"PRIu64")",
|
||||
page, *value_ptr, iteration);
|
||||
if (*value_ptr == iteration) {
|
||||
/*
|
||||
* This page is _just_ modified; it
|
||||
* should report its dirtyness in the
|
||||
* next run
|
||||
*/
|
||||
set_bit(page, host_bmap_track);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void help(char *name)
|
||||
{
|
||||
puts("");
|
||||
printf("usage: %s [-i iterations] [-I interval] [-h]\n", name);
|
||||
puts("");
|
||||
printf(" -i: specify iteration counts (default: %"PRIu64")\n",
|
||||
TEST_HOST_LOOP_N);
|
||||
printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n",
|
||||
TEST_HOST_LOOP_INTERVAL);
|
||||
puts("");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
pthread_t vcpu_thread;
|
||||
struct kvm_vm *vm;
|
||||
uint64_t volatile *psize, *iteration;
|
||||
unsigned long *bmap, iterations = TEST_HOST_LOOP_N,
|
||||
interval = TEST_HOST_LOOP_INTERVAL;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "hi:I:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'i':
|
||||
iterations = strtol(optarg, NULL, 10);
|
||||
break;
|
||||
case 'I':
|
||||
interval = strtol(optarg, NULL, 10);
|
||||
break;
|
||||
case 'h':
|
||||
default:
|
||||
help(argv[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT(iterations > 2, "Iteration must be bigger than zero\n");
|
||||
TEST_ASSERT(interval > 0, "Interval must be bigger than zero");
|
||||
|
||||
DEBUG("Test iterations: %"PRIu64", interval: %"PRIu64" (ms)\n",
|
||||
iterations, interval);
|
||||
|
||||
srandom(time(0));
|
||||
|
||||
bmap = bitmap_alloc(TEST_MEM_PAGES);
|
||||
host_bmap_track = bitmap_alloc(TEST_MEM_PAGES);
|
||||
|
||||
vm = vm_create_default(VCPU_ID, TEST_MEM_PAGES, guest_code);
|
||||
|
||||
/* Add an extra memory slot for testing dirty logging */
|
||||
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
|
||||
TEST_MEM_OFFSET,
|
||||
TEST_MEM_SLOT_INDEX,
|
||||
TEST_MEM_PAGES,
|
||||
KVM_MEM_LOG_DIRTY_PAGES);
|
||||
/* Cache the HVA pointer of the region */
|
||||
host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)TEST_MEM_OFFSET);
|
||||
|
||||
/* Do 1:1 mapping for the dirty track memory slot */
|
||||
virt_map(vm, TEST_MEM_OFFSET, TEST_MEM_OFFSET,
|
||||
TEST_MEM_PAGES * getpagesize(), 0);
|
||||
|
||||
vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
|
||||
|
||||
/* Tell the guest about the page size on the system */
|
||||
psize = addr_gva2hva(vm, (vm_vaddr_t)&guest_page_size);
|
||||
*psize = getpagesize();
|
||||
|
||||
/* Start the iterations */
|
||||
iteration = addr_gva2hva(vm, (vm_vaddr_t)&guest_iteration);
|
||||
*iteration = 1;
|
||||
|
||||
/* Start dirtying pages */
|
||||
pthread_create(&vcpu_thread, NULL, vcpu_worker, vm);
|
||||
|
||||
while (*iteration < iterations) {
|
||||
/* Give the vcpu thread some time to dirty some pages */
|
||||
usleep(interval * 1000);
|
||||
kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
|
||||
vm_dirty_log_verify(bmap, *iteration);
|
||||
(*iteration)++;
|
||||
}
|
||||
|
||||
/* Tell the vcpu thread to quit */
|
||||
host_quit = true;
|
||||
pthread_join(vcpu_thread, NULL);
|
||||
|
||||
DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), "
|
||||
"track_next (%"PRIu64")\n", host_dirty_count, host_clear_count,
|
||||
host_track_next_count);
|
||||
|
||||
free(bmap);
|
||||
free(host_bmap_track);
|
||||
kvm_vm_free(vm);
|
||||
|
||||
return 0;
|
||||
}
|
@@ -55,6 +55,7 @@ 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);
|
||||
void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log);
|
||||
|
||||
int kvm_memcmp_hva_gva(void *hva,
|
||||
struct kvm_vm *vm, const vm_vaddr_t gva, size_t len);
|
||||
@@ -80,6 +81,8 @@ void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags);
|
||||
void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot);
|
||||
vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min,
|
||||
uint32_t data_memslot, uint32_t pgd_memslot);
|
||||
void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
|
||||
size_t size, uint32_t pgd_memslot);
|
||||
void *addr_gpa2hva(struct kvm_vm *vm, vm_paddr_t gpa);
|
||||
void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva);
|
||||
vm_paddr_t addr_hva2gpa(struct kvm_vm *vm, void *hva);
|
||||
@@ -127,7 +130,8 @@ kvm_get_supported_cpuid_entry(uint32_t function)
|
||||
return kvm_get_supported_cpuid_index(function, 0);
|
||||
}
|
||||
|
||||
struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code);
|
||||
struct kvm_vm *vm_create_default(uint32_t vcpuid, uint64_t extra_mem_size,
|
||||
void *guest_code);
|
||||
void vm_vcpu_add_default(struct kvm_vm *vm, uint32_t vcpuid, void *guest_code);
|
||||
|
||||
typedef void (*vmx_guest_code_t)(vm_vaddr_t vmxon_vaddr,
|
||||
@@ -144,4 +148,43 @@ allocate_kvm_dirty_log(struct kvm_userspace_memory_region *region);
|
||||
|
||||
int vm_create_device(struct kvm_vm *vm, struct kvm_create_device *cd);
|
||||
|
||||
#define GUEST_PORT_SYNC 0x1000
|
||||
#define GUEST_PORT_ABORT 0x1001
|
||||
#define GUEST_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");
|
||||
}
|
||||
|
||||
/*
|
||||
* Allows to pass three arguments to the host: port is 16bit wide,
|
||||
* arg0 & arg1 are 64bit wide
|
||||
*/
|
||||
#define GUEST_SYNC_ARGS(_port, _arg0, _arg1) \
|
||||
__exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1))
|
||||
|
||||
#define GUEST_ASSERT(_condition) do { \
|
||||
if (!(_condition)) \
|
||||
GUEST_SYNC_ARGS(GUEST_PORT_ABORT, \
|
||||
"Failed guest assert: " \
|
||||
#_condition, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
#define GUEST_SYNC(stage) GUEST_SYNC_ARGS(GUEST_PORT_SYNC, "hello", stage)
|
||||
|
||||
#define GUEST_DONE() GUEST_SYNC_ARGS(GUEST_PORT_DONE, 0, 0)
|
||||
|
||||
struct guest_args {
|
||||
uint64_t arg0;
|
||||
uint64_t arg1;
|
||||
uint16_t port;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id,
|
||||
struct guest_args *args);
|
||||
|
||||
#endif /* SELFTEST_KVM_UTIL_H */
|
||||
|
@@ -28,8 +28,6 @@ int test_seq_read(const char *path, char **bufp, size_t *sizep);
|
||||
void test_assert(bool exp, const char *exp_str,
|
||||
const char *file, unsigned int line, const char *fmt, ...);
|
||||
|
||||
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
|
||||
|
||||
#define TEST_ASSERT(e, fmt, ...) \
|
||||
test_assert((e), #e, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#define KVM_DEV_PATH "/dev/kvm"
|
||||
|
||||
@@ -168,6 +169,16 @@ void kvm_vm_restart(struct kvm_vm *vmp, int perm)
|
||||
}
|
||||
}
|
||||
|
||||
void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log)
|
||||
{
|
||||
struct kvm_dirty_log args = { .dirty_bitmap = log, .slot = slot };
|
||||
int ret;
|
||||
|
||||
ret = ioctl(vm->fd, KVM_GET_DIRTY_LOG, &args);
|
||||
TEST_ASSERT(ret == 0, "%s: KVM_GET_DIRTY_LOG failed: %s",
|
||||
strerror(-ret));
|
||||
}
|
||||
|
||||
/* Userspace Memory Region Find
|
||||
*
|
||||
* Input Args:
|
||||
@@ -923,6 +934,39 @@ vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min,
|
||||
return vaddr_start;
|
||||
}
|
||||
|
||||
/*
|
||||
* Map a range of VM virtual address to the VM's physical address
|
||||
*
|
||||
* Input Args:
|
||||
* vm - Virtual Machine
|
||||
* vaddr - Virtuall address to map
|
||||
* paddr - VM Physical Address
|
||||
* size - The size of the range to map
|
||||
* pgd_memslot - Memory region slot for new virtual translation tables
|
||||
*
|
||||
* Output Args: None
|
||||
*
|
||||
* Return: None
|
||||
*
|
||||
* Within the VM given by vm, creates a virtual translation for the
|
||||
* page range starting at vaddr to the page range starting at paddr.
|
||||
*/
|
||||
void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
|
||||
size_t size, uint32_t pgd_memslot)
|
||||
{
|
||||
size_t page_size = vm->page_size;
|
||||
size_t npages = size / page_size;
|
||||
|
||||
TEST_ASSERT(vaddr + size > vaddr, "Vaddr overflow");
|
||||
TEST_ASSERT(paddr + size > paddr, "Paddr overflow");
|
||||
|
||||
while (npages--) {
|
||||
virt_pg_map(vm, vaddr, paddr, pgd_memslot);
|
||||
vaddr += page_size;
|
||||
paddr += page_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Address VM Physical to Host Virtual
|
||||
*
|
||||
* Input Args:
|
||||
@@ -1536,3 +1580,17 @@ void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva)
|
||||
{
|
||||
return addr_gpa2hva(vm, addr_gva2gpa(vm, gva));
|
||||
}
|
||||
|
||||
void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id,
|
||||
struct guest_args *args)
|
||||
{
|
||||
struct kvm_run *run = vcpu_state(vm, vcpu_id);
|
||||
struct kvm_regs regs;
|
||||
|
||||
memset(®s, 0, sizeof(regs));
|
||||
vcpu_regs_get(vm, vcpu_id, ®s);
|
||||
|
||||
args->port = run->io.port;
|
||||
args->arg0 = regs.rdi;
|
||||
args->arg1 = regs.rsi;
|
||||
}
|
||||
|
@@ -702,6 +702,9 @@ void vcpu_set_cpuid(struct kvm_vm *vm,
|
||||
*
|
||||
* Input Args:
|
||||
* vcpuid - The id of the single VCPU to add to the VM.
|
||||
* extra_mem_pages - The size of extra memories to add (this will
|
||||
* decide how much extra space we will need to
|
||||
* setup the page tables using mem slot 0)
|
||||
* guest_code - The vCPU's entry point
|
||||
*
|
||||
* Output Args: None
|
||||
@@ -709,12 +712,23 @@ void vcpu_set_cpuid(struct kvm_vm *vm,
|
||||
* Return:
|
||||
* Pointer to opaque structure that describes the created VM.
|
||||
*/
|
||||
struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code)
|
||||
struct kvm_vm *vm_create_default(uint32_t vcpuid, uint64_t extra_mem_pages,
|
||||
void *guest_code)
|
||||
{
|
||||
struct kvm_vm *vm;
|
||||
/*
|
||||
* For x86 the maximum page table size for a memory region
|
||||
* will be when only 4K pages are used. In that case the
|
||||
* total extra size for page tables (for extra N pages) will
|
||||
* be: N/512+N/512^2+N/512^3+... which is definitely smaller
|
||||
* than N/512*2.
|
||||
*/
|
||||
uint64_t extra_pg_pages = extra_mem_pages / 512 * 2;
|
||||
|
||||
/* Create VM */
|
||||
vm = vm_create(VM_MODE_FLAT48PG, DEFAULT_GUEST_PHY_PAGES, O_RDWR);
|
||||
vm = vm_create(VM_MODE_FLAT48PG,
|
||||
DEFAULT_GUEST_PHY_PAGES + extra_pg_pages,
|
||||
O_RDWR);
|
||||
|
||||
/* Setup guest code */
|
||||
kvm_vm_elf_load(vm, program_invocation_name, 0, 0);
|
||||
|
@@ -36,7 +36,7 @@ int main(int argc, char *argv[])
|
||||
setbuf(stdout, NULL);
|
||||
|
||||
/* Create VM */
|
||||
vm = vm_create_default(VCPU_ID, NULL);
|
||||
vm = vm_create_default(VCPU_ID, 0, NULL);
|
||||
|
||||
vcpu_sregs_get(vm, VCPU_ID, &sregs);
|
||||
sregs.apic_base = 1 << 10;
|
||||
|
@@ -21,28 +21,6 @@
|
||||
#include "vmx.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;
|
||||
|
||||
@@ -137,7 +115,7 @@ void guest_code(struct vmx_pages *vmx_pages)
|
||||
if (vmx_pages)
|
||||
l1_guest_code(vmx_pages);
|
||||
|
||||
exit_to_l0(PORT_DONE, 0, 0);
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
@@ -154,7 +132,7 @@ int main(int argc, char *argv[])
|
||||
struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(1);
|
||||
|
||||
/* Create VM */
|
||||
vm = vm_create_default(VCPU_ID, guest_code);
|
||||
vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
|
||||
@@ -178,13 +156,13 @@ int main(int argc, char *argv[])
|
||||
memset(®s1, 0, sizeof(regs1));
|
||||
vcpu_regs_get(vm, VCPU_ID, ®s1);
|
||||
switch (run->io.port) {
|
||||
case PORT_ABORT:
|
||||
case GUEST_PORT_ABORT:
|
||||
TEST_ASSERT(false, "%s at %s:%d", (const char *) regs1.rdi,
|
||||
__FILE__, regs1.rsi);
|
||||
/* NOT REACHED */
|
||||
case PORT_SYNC:
|
||||
case GUEST_PORT_SYNC:
|
||||
break;
|
||||
case PORT_DONE:
|
||||
case GUEST_PORT_DONE:
|
||||
goto done;
|
||||
default:
|
||||
TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port);
|
||||
|
@@ -22,28 +22,11 @@
|
||||
#include "x86.h"
|
||||
|
||||
#define VCPU_ID 5
|
||||
#define PORT_HOST_SYNC 0x1000
|
||||
|
||||
static 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, 0);\
|
||||
} while (0)
|
||||
|
||||
void guest_code(void)
|
||||
{
|
||||
for (;;) {
|
||||
exit_to_l0(PORT_HOST_SYNC, "hello", 0);
|
||||
GUEST_SYNC(0);
|
||||
asm volatile ("inc %r11");
|
||||
}
|
||||
}
|
||||
@@ -111,7 +94,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
/* Create VM */
|
||||
vm = vm_create_default(VCPU_ID, guest_code);
|
||||
vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
|
||||
|
@@ -62,27 +62,12 @@ struct kvm_single_msr {
|
||||
/* The virtual machine object. */
|
||||
static struct kvm_vm *vm;
|
||||
|
||||
#define exit_to_l0(_port, _arg) do_exit_to_l0(_port, (unsigned long) (_arg))
|
||||
static void do_exit_to_l0(uint16_t port, unsigned long arg)
|
||||
{
|
||||
__asm__ __volatile__("in %[port], %%al"
|
||||
:
|
||||
: [port]"d"(port), "D"(arg)
|
||||
: "rax");
|
||||
}
|
||||
|
||||
|
||||
#define GUEST_ASSERT(_condition) do { \
|
||||
if (!(_condition)) \
|
||||
exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition); \
|
||||
} while (0)
|
||||
|
||||
static void check_ia32_tsc_adjust(int64_t max)
|
||||
{
|
||||
int64_t adjust;
|
||||
|
||||
adjust = rdmsr(MSR_IA32_TSC_ADJUST);
|
||||
exit_to_l0(PORT_REPORT, adjust);
|
||||
GUEST_SYNC(adjust);
|
||||
GUEST_ASSERT(adjust <= max);
|
||||
}
|
||||
|
||||
@@ -132,7 +117,7 @@ static void l1_guest_code(struct vmx_pages *vmx_pages)
|
||||
|
||||
check_ia32_tsc_adjust(-2 * TSC_ADJUST_VALUE);
|
||||
|
||||
exit_to_l0(PORT_DONE, 0);
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
void report(int64_t val)
|
||||
@@ -152,7 +137,7 @@ int main(int argc, char *argv[])
|
||||
exit(KSFT_SKIP);
|
||||
}
|
||||
|
||||
vm = vm_create_default(VCPU_ID, (void *) l1_guest_code);
|
||||
vm = vm_create_default(VCPU_ID, 0, (void *) l1_guest_code);
|
||||
vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
|
||||
|
||||
/* Allocate VMX pages and shared descriptors (vmx_pages). */
|
||||
@@ -161,26 +146,26 @@ int main(int argc, char *argv[])
|
||||
|
||||
for (;;) {
|
||||
volatile struct kvm_run *run = vcpu_state(vm, VCPU_ID);
|
||||
struct kvm_regs regs;
|
||||
struct guest_args args;
|
||||
|
||||
vcpu_run(vm, VCPU_ID);
|
||||
vcpu_regs_get(vm, VCPU_ID, ®s);
|
||||
guest_args_read(vm, VCPU_ID, &args);
|
||||
TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
|
||||
"Got exit_reason other than KVM_EXIT_IO: %u (%s), rip=%lx\n",
|
||||
"Got exit_reason other than KVM_EXIT_IO: %u (%s)\n",
|
||||
run->exit_reason,
|
||||
exit_reason_str(run->exit_reason), regs.rip);
|
||||
exit_reason_str(run->exit_reason));
|
||||
|
||||
switch (run->io.port) {
|
||||
case PORT_ABORT:
|
||||
TEST_ASSERT(false, "%s", (const char *) regs.rdi);
|
||||
switch (args.port) {
|
||||
case GUEST_PORT_ABORT:
|
||||
TEST_ASSERT(false, "%s", (const char *) args.arg0);
|
||||
/* NOT REACHED */
|
||||
case PORT_REPORT:
|
||||
report(regs.rdi);
|
||||
case GUEST_PORT_SYNC:
|
||||
report(args.arg1);
|
||||
break;
|
||||
case PORT_DONE:
|
||||
case GUEST_PORT_DONE:
|
||||
goto done;
|
||||
default:
|
||||
TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port);
|
||||
TEST_ASSERT(false, "Unknown port 0x%x.", args.port);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user