Stopping map and unmap from adding subtables unnecessarily.

Also added tests for both map and unmap.

Change-Id: I9398e890d1e0e5ba0fe544b8192df8f866df684d
diff --git a/src/mm.c b/src/mm.c
index 3538f45..5785d64 100644
--- a/src/mm.c
+++ b/src/mm.c
@@ -48,6 +48,7 @@
 
 #define MAP_FLAG_SYNC   0x01
 #define MAP_FLAG_COMMIT 0x02
+#define MAP_FLAG_UNMAP  0x04
 
 /* clang-format on */
 
@@ -188,8 +189,24 @@
 }
 
 /**
+ * Returns whether all entries in this table are absent.
+ */
+static bool mm_ptable_is_empty(pte_t *table, int level)
+{
+	uint64_t i;
+
+	for (i = 0; i < NUM_ENTRIES; ++i) {
+		if (arch_mm_pte_is_present(table[i], level)) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/**
  * Updates the page table at the given level to map the given address range to a
- * physical range using the provided (architecture-specific) attributes.
+ * physical range using the provided (architecture-specific) attributes. Or if
+ * MAP_FLAG_UNMAP is set, unmap the given range instead.
  *
  * This function calls itself recursively if it needs to update additional
  * levels, but the recursion is bound by the maximum number of levels in a page
@@ -203,6 +220,7 @@
 	size_t entry_size = mm_entry_size(level);
 	bool commit = flags & MAP_FLAG_COMMIT;
 	bool sync = flags & MAP_FLAG_SYNC;
+	bool unmap = flags & MAP_FLAG_UNMAP;
 
 	/* Cap end so that we don't go over the current level max. */
 	if (end > level_end) {
@@ -211,26 +229,62 @@
 
 	/* Fill each entry in the table. */
 	while (begin < end) {
-		if ((end - begin) >= entry_size &&
-		    arch_mm_is_block_allowed(level) &&
-		    (begin & (entry_size - 1)) == 0) {
+		if (unmap ? !arch_mm_pte_is_present(*pte, level)
+			  : arch_mm_pte_is_block(*pte, level) &&
+				    arch_mm_pte_attrs(*pte) == attrs) {
+			/*
+			 * If the entry is already mapped with the right
+			 * attributes, or already absent in the case of
+			 * unmapping, no need to do anything; carry on to the
+			 * next entry.
+			 */
+		} else if ((end - begin) >= entry_size &&
+			   (unmap || arch_mm_is_block_allowed(level)) &&
+			   (begin & (entry_size - 1)) == 0) {
+			/*
+			 * If the entire entry is within the region we want to
+			 * map, map/unmap the whole entry.
+			 */
 			if (commit) {
 				pte_t v = *pte;
-				*pte = arch_mm_block_pte(level, pa, attrs);
+				*pte = unmap ? arch_mm_absent_pte(level)
+					     : arch_mm_block_pte(level, pa,
+								 attrs);
 				/* TODO: Add barrier. How do we ensure this
 				 * isn't in use by another CPU? Send IPI? */
 				mm_free_page_pte(v, level);
 			}
 		} else {
+			/*
+			 * If the entry is already a subtable get it; otherwise
+			 * replace it with an equivalent subtable and get that.
+			 */
 			pte_t *nt = mm_populate_table_pte(pte, level, sync);
 			if (!nt) {
 				return false;
 			}
 
+			/*
+			 * Recurse to map/unmap the appropriate entries within
+			 * the subtable.
+			 */
 			if (!mm_map_level(begin, end, pa, attrs, nt, level - 1,
 					  flags)) {
 				return false;
 			}
+
+			/*
+			 * If the subtable is now empty, replace it with an
+			 * absent entry at this level.
+			 */
+			if (commit && unmap &&
+			    mm_ptable_is_empty(nt, level - 1)) {
+				pte_t v = *pte;
+				*pte = arch_mm_absent_pte(level);
+				/* TODO: Add barrier. How do we ensure this
+				 * isn't in use by another CPU? Send IPI? */
+				mm_free_page_pte(v, level);
+			}
 		}
 
 		begin = (begin + entry_size) & ~(entry_size - 1);
@@ -300,7 +354,8 @@
 static bool mm_ptable_unmap(struct mm_ptable *t, paddr_t pa_begin,
 			    paddr_t pa_end, int mode)
 {
-	int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
+	int flags =
+		((mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC) | MAP_FLAG_UNMAP;
 	int level = arch_mm_max_level(mode);
 	pte_t *table = ptr_from_pa(t->table);
 	ptable_addr_t begin;
@@ -344,8 +399,15 @@
 	addr = pa_addr(pa);
 
 	for (i = arch_mm_max_level(mode); i > 0; i--) {
-		table = mm_populate_table_pte(&table[mm_index(addr, i)], i,
-					      sync);
+		pte_t *pte = &table[mm_index(addr, i)];
+		if (arch_mm_pte_is_block(*pte, i) &&
+		    arch_mm_pte_attrs(*pte) == attrs) {
+			/* If the page is within a block that is already mapped
+			 * with the appropriate attributes, no need to do
+			 * anything more. */
+			return true;
+		}
+		table = mm_populate_table_pte(pte, i, sync);
 		if (!table) {
 			return false;
 		}
diff --git a/src/mm_test.cc b/src/mm_test.cc
index 05324f7..d0c976d 100644
--- a/src/mm_test.cc
+++ b/src/mm_test.cc
@@ -162,3 +162,185 @@
 			<< "i=" << i;
 	}
 }
+
+/** If nothing is mapped, unmapping the hypervisor should have no effect. */
+TEST(mm, ptable_unmap_hypervisor_not_mapped)
+{
+	auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
+	halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
+
+	pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	init_absent(table);
+
+	struct mm_ptable ptable;
+	ptable.table = pa_init((uintpaddr_t)table);
+
+	EXPECT_TRUE(mm_ptable_unmap_hypervisor(&ptable, 0));
+
+	for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+}
+
+/**
+ * Unmapping everything should result in an empty page table with no subtables.
+ */
+TEST(mm, vm_unmap)
+{
+	auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
+	halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
+
+	pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	pte_t *subtable_a = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	pte_t *subtable_aa = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	init_absent(table);
+	init_absent(subtable_a);
+	init_absent(subtable_aa);
+
+	subtable_aa[0] = arch_mm_block_pte(TOP_LEVEL - 2, pa_init(0), 0);
+	subtable_a[0] = arch_mm_table_pte(TOP_LEVEL - 1,
+					  pa_init((uintpaddr_t)subtable_aa));
+	table[0] =
+		arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_a));
+
+	struct mm_ptable ptable;
+	ptable.table = pa_init((uintpaddr_t)table);
+
+	EXPECT_TRUE(mm_vm_unmap(&ptable, pa_init(0), pa_init(1), 0));
+
+	for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+}
+
+/**
+ * Mapping a range should result in just the corresponding pages being mapped.
+ */
+TEST(mm, vm_identity_map)
+{
+	auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
+	halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
+
+	/* Start with an empty page table. */
+	pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	init_absent(table);
+	struct mm_ptable ptable;
+	ptable.table = pa_init((uintpaddr_t)table);
+
+	/* Try mapping the first page. */
+	ipaddr_t ipa = ipa_init(-1);
+	EXPECT_TRUE(mm_vm_identity_map(&ptable, pa_init(0), pa_init(PAGE_SIZE),
+				       0, &ipa));
+	EXPECT_THAT(ipa_addr(ipa), Eq(0));
+
+	/* Check that the first page is mapped, and nothing else. */
+	for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+	ASSERT_TRUE(arch_mm_pte_is_table(table[0], TOP_LEVEL));
+	pte_t *subtable_a = (pte_t *)ptr_from_va(
+		va_from_pa(arch_mm_table_from_pte(table[0])));
+	for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(subtable_a[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+	ASSERT_TRUE(arch_mm_pte_is_table(subtable_a[0], TOP_LEVEL - 1));
+	pte_t *subtable_aa = (pte_t *)ptr_from_va(
+		va_from_pa(arch_mm_table_from_pte(subtable_a[0])));
+	for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(subtable_aa[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+	EXPECT_TRUE(arch_mm_pte_is_block(subtable_aa[0], TOP_LEVEL - 2));
+	EXPECT_THAT(pa_addr(arch_mm_block_from_pte(subtable_aa[0])), Eq(0));
+}
+
+/** Mapping a range that is already mapped should be a no-op. */
+TEST(mm, vm_identity_map_already_mapped)
+{
+	auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
+	halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
+
+	/* Start with a full page table mapping everything. */
+	pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	init_blocks(table, TOP_LEVEL, pa_init(0), 0);
+	struct mm_ptable ptable;
+	ptable.table = pa_init((uintpaddr_t)table);
+
+	/* Try mapping the first page. */
+	ipaddr_t ipa = ipa_init(-1);
+	EXPECT_TRUE(mm_vm_identity_map(&ptable, pa_init(0), pa_init(PAGE_SIZE),
+				       0, &ipa));
+	EXPECT_THAT(ipa_addr(ipa), Eq(0));
+
+	/*
+	 * The table should still be full of blocks, with no subtables or
+	 * anything else.
+	 */
+	for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
+		EXPECT_TRUE(arch_mm_pte_is_block(table[i], TOP_LEVEL))
+			<< "i=" << i;
+	}
+}
+
+/** Mapping a single page should result in just that page being mapped. */
+TEST(mm, vm_identity_map_page)
+{
+	auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
+	halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
+
+	/* Start with an empty page table. */
+	pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	init_absent(table);
+	struct mm_ptable ptable;
+	ptable.table = pa_init((uintpaddr_t)table);
+
+	/* Try mapping the first page. */
+	ipaddr_t ipa = ipa_init(-1);
+	EXPECT_TRUE(mm_vm_identity_map_page(&ptable, pa_init(0), 0, &ipa));
+	EXPECT_THAT(ipa_addr(ipa), Eq(0));
+
+	/* Check that the first page is mapped, and nothing else. */
+	for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+	ASSERT_TRUE(arch_mm_pte_is_table(table[0], TOP_LEVEL));
+	pte_t *subtable_a = (pte_t *)ptr_from_va(
+		va_from_pa(arch_mm_table_from_pte(table[0])));
+	for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(subtable_a[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+	ASSERT_TRUE(arch_mm_pte_is_table(subtable_a[0], TOP_LEVEL - 1));
+	pte_t *subtable_aa = (pte_t *)ptr_from_va(
+		va_from_pa(arch_mm_table_from_pte(subtable_a[0])));
+	for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
+		EXPECT_THAT(subtable_aa[i], Eq(ABSENT_ENTRY)) << "i=" << i;
+	}
+	EXPECT_TRUE(arch_mm_pte_is_block(subtable_aa[0], TOP_LEVEL - 2));
+	EXPECT_THAT(pa_addr(arch_mm_block_from_pte(subtable_aa[0])), Eq(0));
+}
+
+/** Mapping a page that is already mapped should be a no-op. */
+TEST(mm, vm_identity_map_page_already_mapped)
+{
+	auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
+	halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
+
+	/* Start with a full page table mapping everything. */
+	pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
+	init_blocks(table, TOP_LEVEL, pa_init(0), 0);
+	struct mm_ptable ptable;
+	ptable.table = pa_init((uintpaddr_t)table);
+
+	/* Try mapping the first page. */
+	ipaddr_t ipa = ipa_init(-1);
+	EXPECT_TRUE(mm_vm_identity_map_page(&ptable, pa_init(0), 0, &ipa));
+	EXPECT_THAT(ipa_addr(ipa), Eq(0));
+
+	/*
+	 * The table should still be full of blocks, with no subtables or
+	 * anything else.
+	 */
+	for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
+		EXPECT_TRUE(arch_mm_pte_is_block(table[i], TOP_LEVEL))
+			<< "i=" << i;
+	}
+}