[WiP] Add pgtable-test
Add a pgtable test harness, forked from Fuad's kvmtest-arm64 code.
TODO: explain
diff --git a/kvmtest/pgtable-test/dirty_logging.c b/kvmtest/pgtable-test/dirty_logging.c
new file mode 100644
index 0000000..712fe2d
--- /dev/null
+++ b/kvmtest/pgtable-test/dirty_logging.c
@@ -0,0 +1,121 @@
+/*
+ * XXX - Credit, licence, copyright
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/kvm.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "helpers.h"
+
+#define GUEST_PHYS_ADDR (1UL << 30)
+#define GUEST_MMIO_ADDR (1UL << 38)
+#define GUEST_MEM_SIZE (PUD_SIZE*2)
+
+/* x0[0] = x1; */
+void guest_code()
+{
+ asm(
+ "1: str x1, [x0] \n"
+ " brk #0 \n"
+ ::);
+
+}
+#define GUEST_NR_INST 2
+
+static void guest_write_memory(int vcpufd, struct kvm_run *run, uint64_t addr)
+{
+ set_one_reg(vcpufd, REG_X(0), addr);
+ set_one_reg(vcpufd, REG_X(1), 1);
+ set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR);
+ assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG);
+}
+
+struct kvm_userspace_memory_region guest_prepare_memory(int vmfd, int vcpufd)
+{
+ uint8_t *mem_code;
+
+ mem_code = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (!mem_code)
+ err(-ENOMEM, "allocating guest memory");
+ memcpy(mem_code, guest_code, GUEST_NR_INST * ARM64_INST_SIZE);
+
+ /* Map code memory to the second page frame. */
+ struct kvm_userspace_memory_region region = {
+ .slot = 0,
+ .guest_phys_addr = GUEST_PHYS_ADDR,
+ .memory_size = GUEST_MEM_SIZE,
+ .userspace_addr = (uint64_t)mem_code,
+ };
+ KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion);
+
+ vm_add_mmio_page(vmfd, 1, GUEST_MMIO_ADDR);
+ set_one_reg(vcpufd, REG_X(3), GUEST_MMIO_ADDR);
+
+ return region;
+}
+
+#define RAND_PAGE() ((rand() % (GUEST_MEM_SIZE / PAGE_SIZE - 1)) + 1)
+#define NR_DIRTY_PAGES 500
+int main(void)
+{
+ struct kvm_userspace_memory_region region;
+ uint8_t *dirty_bm, *oracle_bm;
+ struct kvm_run *run = NULL;
+ struct kvm_dirty_log dlog;
+ uint64_t addr, offset;
+ int kvm, vmfd, vcpufd;
+ int i;
+
+ srand(time(NULL));
+
+ /* Prepare the guest */
+ kvm = get_kvm();
+ vmfd = create_vm(kvm);
+ vcpufd = create_vcpu(kvm, vmfd, &run, true);
+ region = guest_prepare_memory(vmfd, vcpufd);
+
+ /* We need two bitmap, one for dirty logging, and one for the oracle */
+ dirty_bm = calloc(1, BITMAP_SIZE(GUEST_MEM_SIZE));
+ oracle_bm = calloc(1, BITMAP_SIZE(GUEST_MEM_SIZE));
+ if (!dirty_bm || !oracle_bm)
+ return -ENOMEM;
+
+ /* Optional - poke random pages in memory, with logging disabled */
+ for (i = 0; i < NR_DIRTY_PAGES ; i++) {
+ offset = RAND_PAGE() * PAGE_SIZE;
+ addr = offset + GUEST_PHYS_ADDR;
+ guest_write_memory(vcpufd, run, addr);
+ }
+
+ /* Enable dirty logging, and try again */
+ region.flags = KVM_MEM_LOG_DIRTY_PAGES;
+ KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion);
+
+ for (i = 0; i < NR_DIRTY_PAGES ; i++) {
+ offset = RAND_PAGE() * PAGE_SIZE;
+ addr = offset + GUEST_PHYS_ADDR;
+ guest_write_memory(vcpufd, run, addr);
+ oracle_bm[OFFSET_IDX(offset)] |= OFFSET_BIT(offset);
+ }
+
+ /* Dump the dirty log bitmap, and compare to the oracle */
+ dlog.slot = region.slot;
+ dlog.dirty_bitmap = dirty_bm;
+ KVM_IOCTL(vmfd, KVM_GET_DIRTY_LOG, &dlog);
+
+ assert(bitmap_equal(dirty_bm, oracle_bm, BITMAP_SIZE(GUEST_MEM_SIZE)));
+ printf("Passing.\n");
+}
diff --git a/kvmtest/pgtable-test/helpers.c b/kvmtest/pgtable-test/helpers.c
new file mode 100644
index 0000000..886e61f
--- /dev/null
+++ b/kvmtest/pgtable-test/helpers.c
@@ -0,0 +1,120 @@
+/*
+ * XXX - Credit, licence, copyright
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/kvm.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "helpers.h"
+
+int get_kvm(void)
+{
+ int kvm, ret;
+
+ kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
+ if (kvm < 0)
+ err(kvm, "/dev/kvm");
+
+ /* Ensure this is the stable version of the KVM API (defined as 12) */
+ ret = KVM_IOCTL(kvm, KVM_GET_API_VERSION, NULL);
+ if (ret != 12)
+ errx(-EINVAL, "KVM_GET_API_VERSION %d, expected 12", ret);
+
+ return kvm;
+}
+
+int create_vm(int kvm)
+{
+ return KVM_IOCTL(kvm, KVM_CREATE_VM, (unsigned long)0);
+}
+
+int create_vcpu(int kvm, int vmfd, struct kvm_run **run, bool enable_debug)
+{
+ struct kvm_guest_debug debug = {
+ .control = KVM_GUESTDBG_ENABLE,
+ };
+ struct kvm_vcpu_init vcpu_init;
+ size_t mmap_size;
+ int vcpufd;
+
+ /* Create one CPU to run in the VM. */
+ vcpufd = KVM_IOCTL(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
+
+ /* Map the shared kvm_run structure and following data. */
+ mmap_size = KVM_IOCTL(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
+ if (mmap_size < sizeof(*run))
+ err(-ENOMEM, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
+ *run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
+ if (!*run)
+ err(-ENOMEM, "mmap vcpu");
+
+ /* Query KVM for preferred CPU target type that can be emulated. */
+ KVM_IOCTL(vmfd, KVM_ARM_PREFERRED_TARGET, &vcpu_init);
+ KVM_IOCTL(vcpufd, KVM_ARM_VCPU_INIT, &vcpu_init);
+
+ if (enable_debug)
+ KVM_IOCTL(vcpufd, KVM_SET_GUEST_DEBUG, &debug);
+
+ return vcpufd;
+}
+
+void vm_add_mmio_page(int vmfd, int slot, uint64_t addr)
+{
+ struct kvm_userspace_memory_region region = {
+ .flags = KVM_MEM_READONLY,
+ .slot = slot,
+ .guest_phys_addr = addr,
+ .userspace_addr = 0ULL,
+ .memory_size = PAGE_SIZE,
+ };
+
+ KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion);
+}
+
+void set_one_reg(int vcpufd, uint64_t reg_id, uint64_t val)
+{
+ uint64_t reg_data;
+ struct kvm_one_reg reg;
+
+ reg.addr = (__u64) ®_data;
+ reg_data = val;
+ reg.id = reg_id;
+ KVM_IOCTL(vcpufd, KVM_SET_ONE_REG, ®);
+}
+
+int vcpu_run(int vcpufd, struct kvm_run *run)
+{
+ for (;;) {
+ KVM_IOCTL(vcpufd, KVM_RUN, NULL);
+ switch (run->exit_reason) {
+ case KVM_EXIT_DEBUG:
+ return KVM_EXIT_DEBUG;
+ case KVM_EXIT_MMIO:
+ {
+ uint64_t payload = *(uint64_t*)(run->mmio.data); /* sorry */
+ printf("KVM_EXIT_MMIO: addr = 0x%llx, len = %u, is_write = %u, data = 0x%08lx\n",
+ run->mmio.phys_addr, run->mmio.len, run->mmio.is_write,
+ payload);
+ break;
+ }
+ case KVM_EXIT_FAIL_ENTRY:
+ errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx",
+ (unsigned long long)run->fail_entry.hardware_entry_failure_reason);
+ case KVM_EXIT_INTERNAL_ERROR:
+ errx(1, "KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x",
+ run->internal.suberror);
+ default:
+ errx(1, "exit_reason = 0x%x", run->exit_reason);
+ }
+ }
+}
diff --git a/kvmtest/pgtable-test/helpers.h b/kvmtest/pgtable-test/helpers.h
new file mode 100644
index 0000000..bb322b0
--- /dev/null
+++ b/kvmtest/pgtable-test/helpers.h
@@ -0,0 +1,54 @@
+#ifndef __PGTABLE_TEST_H
+#define __PGTABLE_TEST_H
+
+#include <linux/kvm.h>
+
+#define PTRS_PER_PTE 512UL
+#define PAGE_SIZE 0x1000UL
+#define PMD_SIZE (PTRS_PER_PTE * PAGE_SIZE)
+#define PUD_SIZE (PTRS_PER_PTE * PMD_SIZE)
+
+#define ARM64_INST_SIZE 4
+
+/* Register ids */
+#define REG_X(number) (0x6030000000100000ULL + (number) * 2UL)
+#define REG_PC 0x6030000000100040ULL
+
+#define KVM_IOCTL(fd, ioctl_id, ...) \
+({ \
+ int ret = ioctl(fd, ioctl_id, __VA_ARGS__); \
+ if (ret < 0) \
+ err(ret, #ioctl_id ); \
+ ret; \
+})
+
+/* XXX */
+typedef enum {
+ false = 0,
+ true = 1,
+} bool;
+
+int get_kvm(void);
+int create_vm(int kvm);
+int create_vcpu(int kvm, int vmfd, struct kvm_run **run, bool debug);
+void vm_add_mmio_page(int vmfd, int slot, uint64_t addr);
+void set_one_reg(int vcpufd, uint64_t reg_id, uint64_t val);
+int vcpu_run(int vcpufd, struct kvm_run *run);
+
+/* XXX - Move the bitmap stuff elsewhere */
+#define ALIGN(x, y) (((x)+(y)-1) & ~((y)-1))
+
+#define BITMAP_SIZE(mem) (ALIGN((mem) / PAGE_SIZE, 64) / 8)
+#define OFFSET_IDX(offset) (offset / PAGE_SIZE / 8)
+#define OFFSET_BIT(offset) (1 << ((offset / PAGE_SIZE) % 8))
+
+static inline bool bitmap_equal(uint8_t *bm_a, uint8_t *bm_b, uint64_t len)
+{
+ for (uint64_t i = 0; i < len; i++) {
+ if (bm_a[i] != bm_b[i])
+ return false;
+ }
+
+ return true;
+}
+#endif /* __PGTABLE_TEST_H */