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;
+ }
+}