blob: d0c976d6d81f16daa39b453cdad09f7d9572e12a [file] [log] [blame]
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
extern "C" {
#include "hf/mm.h"
#include "hf/arch/mm.h"
#include "hf/alloc.h"
}
#include <memory>
#include <gmock/gmock.h>
using ::testing::Eq;
constexpr size_t TEST_HEAP_SIZE = PAGE_SIZE * 10;
constexpr size_t ENTRY_COUNT = PAGE_SIZE / sizeof(pte_t);
const static int TOP_LEVEL = arch_mm_max_level(0);
const static pte_t ABSENT_ENTRY = arch_mm_absent_pte(TOP_LEVEL);
/**
* Calculates the size of the address space represented by a page table entry at
* the given level.
*/
static size_t mm_entry_size(int level)
{
return UINT64_C(1) << (PAGE_BITS + level * PAGE_LEVEL_BITS);
}
/**
* Fill a ptable with absent entries.
*/
static void init_absent(pte_t *table)
{
for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
table[i] = ABSENT_ENTRY;
}
}
/**
* Fill a ptable with block entries.
*/
static void init_blocks(pte_t *table, int level, paddr_t start_address,
uint64_t attrs)
{
for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
table[i] = arch_mm_block_pte(
level, pa_add(start_address, i * mm_entry_size(level)),
attrs);
}
}
/**
* Defragging an entirely empty table should have no effect.
*/
TEST(mm, ptable_defrag_empty)
{
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);
mm_ptable_defrag(&ptable, 0);
for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
}
}
/**
* Defragging a table with some empty subtables (even nested) should result in
* an empty table.
*/
TEST(mm, ptable_defrag_empty_subtables)
{
auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
halloc_init((size_t)test_heap.get(), TEST_HEAP_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);
pte_t *subtable_b = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
init_absent(subtable_a);
init_absent(subtable_aa);
init_absent(subtable_b);
init_absent(table);
subtable_a[3] = 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));
table[5] =
arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_b));
struct mm_ptable ptable;
ptable.table = pa_init((uintpaddr_t)table);
mm_ptable_defrag(&ptable, 0);
for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
}
}
/**
* Any subtable with all blocks with the same attributes should be replaced
* with a single block.
*/
TEST(mm, ptable_defrag_block_subtables)
{
auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
halloc_init((size_t)test_heap.get(), TEST_HEAP_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);
pte_t *subtable_b = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
init_blocks(subtable_a, TOP_LEVEL - 1, pa_init(0), 0);
init_blocks(subtable_aa, TOP_LEVEL - 2,
pa_init(3 * mm_entry_size(TOP_LEVEL - 1)), 0);
init_blocks(subtable_b, TOP_LEVEL - 1,
pa_init(5 * mm_entry_size(TOP_LEVEL)), 0);
init_blocks(table, TOP_LEVEL, pa_init(0), 0);
subtable_a[3] = 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));
table[5] =
arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_b));
struct mm_ptable ptable;
ptable.table = pa_init((uintpaddr_t)table);
mm_ptable_defrag(&ptable, 0);
for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
EXPECT_TRUE(arch_mm_pte_is_present(table[i], TOP_LEVEL))
<< "i=" << i;
EXPECT_TRUE(arch_mm_pte_is_block(table[i], TOP_LEVEL))
<< "i=" << i;
EXPECT_THAT(pa_addr(arch_mm_block_from_pte(table[i])),
Eq(i * mm_entry_size(TOP_LEVEL)))
<< "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;
}
}