pgtable-test: Add coalesce_collapse test

Add a test that creates a guest backed by huge tlb pages, make sure to
fault in a block range to create the block mapping, then enable logging,
write fault in the block range to collapse the mapping. Then disable
logging, and fault in another part of the block range, to hit
coallescing.
diff --git a/kvmtest/pgtable-test/coalesce_collapse.c b/kvmtest/pgtable-test/coalesce_collapse.c
new file mode 100644
index 0000000..0e07d8c
--- /dev/null
+++ b/kvmtest/pgtable-test/coalesce_collapse.c
@@ -0,0 +1,131 @@
+/*
+ * 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 <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	(PMD_SIZE*4)
+
+/*
+ * x0[0] = x1;
+ * x1 = x0[0];
+ */
+void guest_code()
+{
+	asm(
+	    "	str x1, [x0]		\n"
+	    "	brk #0			\n"
+	    "	ldr x1, [x0]		\n"
+	    "	brk #0			\n"
+	    ::);
+
+}
+#define GUEST_NR_INST	4
+
+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);
+}
+
+static void guest_read_memory(int vcpufd, struct kvm_run *run, uint64_t addr)
+{
+	set_one_reg(vcpufd, REG_X(0), addr);
+	set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR + 2 * ARM64_INST_SIZE);
+	assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG);
+}
+
+struct kvm_userspace_memory_region guest_prepare_memory(int vmfd, int vcpufd)
+{
+	uint8_t *mem_code;
+
+	/* Note: this uses MAP_HUGETLB */
+	mem_code = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE,
+			MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
+	if (mem_code == MAP_FAILED)
+		err(-ENOMEM, "Are there PMD hugetlb pages available on the system?");
+	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;
+}
+
+int main(void)
+{
+	struct kvm_userspace_memory_region region;
+	struct kvm_run *run = NULL;
+	struct kvm_dirty_log dlog;
+	uint64_t addr, offset;
+	int kvm, vmfd, vcpufd;
+	uint8_t *dirty_bm;
+	int i;
+
+	/* Prepare the guest */
+	kvm = get_kvm();
+	vmfd = create_vm(kvm);
+	vcpufd = create_vcpu(kvm, vmfd, &run, true);
+
+	/* Create a memslot backed by hugetlb */
+	region = guest_prepare_memory(vmfd, vcpufd);
+
+	offset = PMD_SIZE;
+	addr = GUEST_PHYS_ADDR + offset;
+
+	/* Read fault to get the huge mapping created */
+	guest_read_memory(vcpufd, run, addr);
+
+	/* Enable dirty logging */
+	region.flags = KVM_MEM_LOG_DIRTY_PAGES;
+	KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
+
+	/* Write at the same address now, which should trigger the collapse case */
+	guest_write_memory(vcpufd, run, addr);
+
+	/* Dump the dirty log bitmap */
+	dirty_bm = calloc(1, BITMAP_SIZE(GUEST_MEM_SIZE));
+	if (!dirty_bm)
+		return -ENOMEM;
+	dlog.slot = region.slot;
+	dlog.dirty_bitmap = dirty_bm;
+	KVM_IOCTL(vmfd, KVM_GET_DIRTY_LOG, &dlog);
+
+	assert(dirty_bm[OFFSET_IDX(offset)] & OFFSET_BIT(offset));
+
+	/* Disable dirty logging */
+	region.flags = 0;
+	KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
+
+	/* Write to another address in the pmd range to trigger coallescing */
+	guest_write_memory(vcpufd, run, addr);
+
+	printf("Passing.\n");
+}