[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, &region);
+
+	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, &region);
+
+	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, &region);
+}
+
+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) &reg_data;
+	reg_data = val;
+	reg.id = reg_id;
+	KVM_IOCTL(vcpufd, KVM_SET_ONE_REG, &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 */