847 lines
22 KiB
C
847 lines
22 KiB
C
/*
|
|
* Copyright (C) 2014 Apple Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <setjmp.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <arch/arm/arm.h>
|
|
#include <arch/arm64/proc_reg.h>
|
|
|
|
#define VERBOSITY FAILURE
|
|
|
|
#define FAILURE 0
|
|
#define INFO 1
|
|
#define SPEW 2
|
|
|
|
static size_t tests_run, tests_passed;
|
|
|
|
enum Reason {
|
|
REASON_OK = 0,
|
|
REASON_EXPECTED_PANIC,
|
|
REASON_UNEXPECTED_PANIC,
|
|
REASON_PARAMS,
|
|
REASON_LEVEL,
|
|
REASON_OOM,
|
|
REASON_PTE_ALIGNMENT,
|
|
REASON_PTE_BASE,
|
|
REASON_PTE_INDEX,
|
|
REASON_PTE_ACCESS,
|
|
REASON_PTE_MISMATCH
|
|
};
|
|
|
|
static void test_unwind(enum Reason reason) __attribute__((noreturn));
|
|
|
|
#define TEST_ASSERT(reason, c) \
|
|
do { \
|
|
if (!(c)) { \
|
|
printf("!!! Test assertion failure: %s\n", \
|
|
#c ); \
|
|
test_unwind(reason); \
|
|
} \
|
|
} while (0)
|
|
|
|
struct State {
|
|
// Environment constants for an individual test.
|
|
const char *test_name;
|
|
const char *expect_panic_func;
|
|
const char *expect_panic_substr;
|
|
size_t level_entries[4];
|
|
void *page_tables_base;
|
|
size_t page_tables_size;
|
|
size_t total_stack_size;
|
|
unsigned page_granule_shift;
|
|
jmp_buf jmp_env;
|
|
|
|
// Updated during test.
|
|
bool arm_flush_tlbs_called;
|
|
|
|
// Callbacks from test.
|
|
void (^mmu_setup_func)();
|
|
void (^get_table_entry_hook)();
|
|
void (^set_table_entry_hook)();
|
|
};
|
|
|
|
static struct State state;
|
|
|
|
unsigned get_page_granule_shift(void) { return state.page_granule_shift; }
|
|
|
|
void mmu_get_memmap(addr_t *page_tables_base,
|
|
size_t *page_tables_size)
|
|
{
|
|
*page_tables_base = (addr_t) state.page_tables_base;
|
|
*page_tables_size = state.page_tables_size;
|
|
}
|
|
|
|
// Access to mmu.c static functions and variables.
|
|
uint64_t *mmu_get_tt_top_level(void);
|
|
unsigned mmu_get_start_level(void);
|
|
uint64_t mmu_get_tcr(void);
|
|
addr_t mmu_tt_alloc(unsigned level);
|
|
size_t mmu_get_level_entries(unsigned level);
|
|
uint64_t mmu_get_table_flags(void);
|
|
uint64_t mmu_get_block_flags(unsigned level, arm_mmu_extended_attr_t attr);
|
|
size_t mmu_map_at_level(unsigned level, addr_t table_base,
|
|
addr_t vaddr, addr_t paddr, size_t size,
|
|
arm_mmu_extended_attr_t attr);
|
|
void mmu_get_tt_alloc_range(addr_t *start, addr_t *next, addr_t *end);
|
|
void mmu_reset_state(void);
|
|
|
|
const char *reason_to_string(int reason)
|
|
{
|
|
switch (reason) {
|
|
case REASON_OK: return "OK";
|
|
case REASON_EXPECTED_PANIC: return "Expected panic() called";
|
|
case REASON_UNEXPECTED_PANIC: return "Unexpected panic() called";
|
|
case REASON_PARAMS: return "Bad parameters detected";
|
|
case REASON_LEVEL: return "Invalid level used";
|
|
case REASON_OOM: return "Test ran out of memory for structures";
|
|
case REASON_PTE_ALIGNMENT: return "Bad page table alignment";
|
|
case REASON_PTE_BASE: return "Page table base does not match allocated";
|
|
case REASON_PTE_INDEX: return "Page table index out of bounds";
|
|
case REASON_PTE_ACCESS: return "Page table unexpected access";
|
|
case REASON_PTE_MISMATCH: return "Page table value mismatch";
|
|
default:
|
|
printf("Bad reason code %d\n", (int) reason);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void mmu_printf(int verbosity, const char *fmt, ...)
|
|
{
|
|
if (VERBOSITY >= verbosity) {
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
vprintf(fmt, va);
|
|
va_end(va);
|
|
}
|
|
}
|
|
|
|
static void test_unwind(enum Reason reason)
|
|
{
|
|
assert(reason != REASON_OK);
|
|
longjmp(state.jmp_env, (int) reason);
|
|
}
|
|
|
|
void _panic(const char *func, const char *fmt, ...)
|
|
{
|
|
char *buf = malloc(4096);
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
vsnprintf(buf, 4096, fmt, va);
|
|
va_end(va);
|
|
|
|
bool expected = state.expect_panic_func && state.expect_panic_substr &&
|
|
strstr(func, state.expect_panic_func) != NULL &&
|
|
strstr(buf, state.expect_panic_substr) != NULL;
|
|
|
|
if (!expected) {
|
|
mmu_printf(FAILURE, "!!! Unexpected panic in function %s: %s\n", func, buf);
|
|
} else {
|
|
mmu_printf(INFO, "Expected panic in function %s: %s\n", func, buf);
|
|
}
|
|
|
|
free(buf);
|
|
|
|
if (expected) {
|
|
test_unwind(REASON_EXPECTED_PANIC);
|
|
} else if (strstr(buf, "Out of table allocation space")) {
|
|
test_unwind(REASON_OOM);
|
|
} else {
|
|
test_unwind(REASON_UNEXPECTED_PANIC);
|
|
}
|
|
}
|
|
|
|
static void expect_panic(const char *func, const char *substr)
|
|
{
|
|
state.expect_panic_func = func;
|
|
state.expect_panic_substr = substr;
|
|
}
|
|
|
|
uint64_t check_valid_index(unsigned level, addr_t table_base, size_t index)
|
|
{
|
|
if (level < mmu_get_start_level() || level > 3) {
|
|
mmu_printf(FAILURE, "L%u invalid level\n", level);
|
|
test_unwind(REASON_LEVEL);
|
|
}
|
|
void *p = NULL;
|
|
|
|
size_t table_size =
|
|
mmu_get_level_entries(level) * sizeof(uint64_t);
|
|
size_t table_entries = mmu_get_level_entries(level);
|
|
uint64_t alloc_start, alloc_next, alloc_end;
|
|
mmu_get_tt_alloc_range(&alloc_start, &alloc_next, &alloc_end);
|
|
if (table_base < alloc_start ||
|
|
table_size > alloc_next ||
|
|
table_base > alloc_next - table_size) {
|
|
mmu_printf(FAILURE, "L%u base 0x%" PRIx64 " size 0x%" PRIx64
|
|
" outside allocated space\n",
|
|
level,
|
|
(uint64_t) table_base,
|
|
(uint64_t) table_size);
|
|
test_unwind(REASON_PTE_BASE);
|
|
}
|
|
if (index > table_entries) {
|
|
mmu_printf(FAILURE, "L%u entry %u >= table_entries %u\n",
|
|
level, (unsigned) index,
|
|
(unsigned) table_entries);
|
|
test_unwind(REASON_PTE_INDEX);
|
|
}
|
|
p = (void *) (table_base + index * 8);
|
|
|
|
uint64_t result;
|
|
memcpy(&result, p, sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
static void get_table_entry_internal(unsigned level, addr_t table_base,
|
|
size_t index)
|
|
{
|
|
mmu_printf(SPEW, "get_table_entry(%u, 0x%" PRIx64 ", %u) ",
|
|
level, (uint64_t) table_base, (unsigned) index);
|
|
uint64_t result = check_valid_index(level, table_base, index);
|
|
mmu_printf(SPEW, "= 0x%016" PRIx64 "\n", result);
|
|
}
|
|
|
|
void get_table_entry_hook(unsigned level, addr_t table_base, size_t index)
|
|
{
|
|
if (state.get_table_entry_hook) {
|
|
state.get_table_entry_hook(level, table_base, index);
|
|
return;
|
|
} else {
|
|
get_table_entry_internal(level, table_base, index);
|
|
}
|
|
}
|
|
|
|
static void set_table_entry_internal(unsigned level, addr_t table_base,
|
|
size_t index, uint64_t value)
|
|
{
|
|
mmu_printf(SPEW, "set_table_entry(%u, 0x%" PRIx64 ", %u, "
|
|
"0x%" PRIx64 ") ",
|
|
level, (uint64_t) table_base, (unsigned) index,
|
|
value);
|
|
uint64_t old = check_valid_index(level, table_base, index);
|
|
mmu_printf(SPEW, " was 0x%016" PRIx64 "\n", old);
|
|
}
|
|
|
|
void set_table_entry_hook(unsigned level, addr_t table_base, size_t index,
|
|
uint64_t value)
|
|
{
|
|
if (state.set_table_entry_hook) {
|
|
state.set_table_entry_hook(level, table_base, index, value);
|
|
return;
|
|
} else {
|
|
set_table_entry_internal(level, table_base, index, value);
|
|
}
|
|
}
|
|
|
|
// Mock HAL.
|
|
|
|
uint64_t arm_read_sctlr(void)
|
|
{
|
|
mmu_printf(INFO, "arm_read_sctlr()\n");
|
|
return 0;
|
|
}
|
|
|
|
void arm_write_mair(uint64_t mair)
|
|
{
|
|
mmu_printf(INFO, "arm_write_mair(0x%016" PRIx64 ")\n", (uint64_t) mair);
|
|
}
|
|
|
|
void arm_write_tcr(uint64_t tcr)
|
|
{
|
|
mmu_printf(INFO, "arm_write_tcr(0x%016" PRIx64 ")\n", (uint64_t) tcr);
|
|
}
|
|
|
|
void arm_write_ttbr0(void *ttbr0)
|
|
{
|
|
mmu_printf(INFO, "arm_write_ttbr0(%p)\n", ttbr0);
|
|
if ((addr_t) ttbr0 != (addr_t) mmu_get_tt_top_level()) {
|
|
mmu_printf(FAILURE, "TTBR0 written with %p expected %p\n",
|
|
ttbr0, mmu_get_tt_top_level());
|
|
test_unwind(REASON_PTE_BASE);
|
|
}
|
|
}
|
|
|
|
void arm_flush_tlbs(void)
|
|
{
|
|
mmu_printf(INFO, "arm_flush_tlbs()\n");
|
|
state.arm_flush_tlbs_called = true;
|
|
}
|
|
|
|
void platform_mmu_setup(bool resume)
|
|
{
|
|
(void) resume;
|
|
uint64_t alloc_start, alloc_next, alloc_end;
|
|
mmu_get_tt_alloc_range(&alloc_start, &alloc_next, &alloc_end);
|
|
mmu_printf(INFO, "platform_mmu_setup(%d)\n", (int) resume);
|
|
mmu_printf(INFO, "tt_top_level = %p\n", mmu_get_tt_top_level());
|
|
mmu_printf(INFO, "page_tables_base = %p\n", state.page_tables_base);
|
|
mmu_printf(INFO, "page_tables_size = 0x%" PRIx64 "\n",
|
|
(uint64_t) state.page_tables_size);
|
|
mmu_printf(INFO, "tt_alloc_start = 0x%" PRIx64 "\n"
|
|
"tt_alloc_next = 0x%" PRIx64 "\n"
|
|
"tt_alloc_end = 0x%" PRIx64 "\n",
|
|
(uint64_t) alloc_start,
|
|
(uint64_t) alloc_next,
|
|
(uint64_t) alloc_end);
|
|
if (state.mmu_setup_func) {
|
|
state.mmu_setup_func();
|
|
}
|
|
}
|
|
|
|
// Test setup functions.
|
|
|
|
static void reset_state(void)
|
|
{
|
|
mmu_reset_state();
|
|
assert(state.page_tables_base == NULL); // Prevent leak.
|
|
memset(&state, 0, sizeof(state));
|
|
}
|
|
|
|
static void mmu_setup_flat(void)
|
|
{
|
|
uint64_t io_base = 0x200000000ULL;
|
|
uint64_t io_size = 256ULL * 1024 * 1024;
|
|
uint64_t sdram_base = 0x800000000ULL;
|
|
uint64_t sdram_len = 1ULL << 30;
|
|
arm_mmu_map_rw(sdram_base, sdram_len);
|
|
arm_mmu_map_device_rw(io_base, io_size);
|
|
}
|
|
|
|
static void mmu_setup_fail_remap_existing(void)
|
|
{
|
|
uint64_t io_base = 0x200000000ULL;
|
|
uint64_t io_size = 256ULL * 1024 * 1024;
|
|
arm_mmu_map_rw(io_base, io_size);
|
|
// Map again
|
|
arm_mmu_map_device_rw(io_base, io_size);
|
|
}
|
|
|
|
static void mmu_setup_fail_breaking_down(void)
|
|
{
|
|
uint64_t io_base = 0x200000000ULL;
|
|
uint64_t io_size = 256ULL * 1024 * 1024;
|
|
arm_mmu_map_rw(io_base, io_size);
|
|
// Map one L3 page again
|
|
arm_mmu_map_rw(io_base, 4096);
|
|
}
|
|
|
|
static void mmu_setup_misaligned(void)
|
|
{
|
|
uint64_t sdram_base = 0x800000000ULL;
|
|
uint64_t sdram_len = 1ULL << 30;
|
|
arm_mmu_map_rw(sdram_base, sdram_len + 512);
|
|
}
|
|
|
|
static void test_delete(void)
|
|
{
|
|
uint64_t alloc_start, alloc_next, alloc_end;
|
|
mmu_get_tt_alloc_range(&alloc_start, &alloc_next, &alloc_end);
|
|
mmu_printf(INFO, "Allocation space used: 0x%" PRIx64 "\n",
|
|
alloc_next - alloc_start);
|
|
mmu_printf(INFO, "Allocation space remaining: 0x%" PRIx64 "\n",
|
|
alloc_end - alloc_next);
|
|
if (state.page_tables_base) {
|
|
free(state.page_tables_base);
|
|
state.page_tables_base = NULL;
|
|
}
|
|
reset_state();
|
|
}
|
|
|
|
static int mmu_test(void)
|
|
{
|
|
mmu_printf(INFO, "Test \"%s\" running\n", state.test_name);
|
|
int ret = posix_memalign(&state.page_tables_base,
|
|
1 << state.page_granule_shift,
|
|
state.page_tables_size);
|
|
assert(ret == 0);
|
|
memset(state.page_tables_base, 0, state.page_tables_size);
|
|
arm_mmu_init(false);
|
|
// Calls back on platform_mmu_setup(resume)
|
|
if (state.arm_flush_tlbs_called) {
|
|
return true;
|
|
} else {
|
|
mmu_printf(FAILURE, "!!! arm_flush_tlbs() was not called\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void do_test(enum Reason expect_reason, int (^test_func)(void))
|
|
{
|
|
assert(state.test_name != NULL);
|
|
mmu_printf(INFO, "----------------------------------------"
|
|
"--------------------------------------\n");
|
|
mmu_printf(INFO, "Test: %s\n", state.test_name);
|
|
|
|
// Increment number of tests run.
|
|
++tests_run;
|
|
|
|
// Set a jump environment for panic/abort exit path.
|
|
int reason_code = setjmp(state.jmp_env);
|
|
if (reason_code != 0) {
|
|
// Returns non-zero if longjmp() invoked to return here.
|
|
if (reason_code == (int) expect_reason) {
|
|
// Failure for an expected reason: exercising error
|
|
// detection.
|
|
mmu_printf(INFO, "Test \"%s\" aborted for expected reason: %s\n",
|
|
state.test_name,
|
|
reason_to_string(expect_reason));
|
|
++tests_passed;
|
|
} else {
|
|
mmu_printf(FAILURE, "!!! Test \"%s\" failed: %s\n",
|
|
state.test_name,
|
|
reason_to_string(reason_code));
|
|
mmu_printf(FAILURE, "!!! Expected outcome: %s\n",
|
|
reason_to_string(expect_reason));
|
|
}
|
|
test_delete();
|
|
reset_state();
|
|
return;
|
|
}
|
|
|
|
if (test_func()) {
|
|
mmu_printf(INFO, "Test \"%s\" completed, passed\n",
|
|
state.test_name);
|
|
++tests_passed;
|
|
} else {
|
|
mmu_printf(FAILURE, "!!! Test \"%s\" failed at completion\n",
|
|
state.test_name);
|
|
mmu_printf(FAILURE, "!!! Expected outcome: %s\n",
|
|
reason_to_string(expect_reason));
|
|
}
|
|
|
|
test_delete();
|
|
reset_state();
|
|
}
|
|
|
|
static void setup_standard_granule(size_t granule)
|
|
{
|
|
switch (granule) {
|
|
case 4096:
|
|
state.page_granule_shift = 12;
|
|
state.page_tables_size = 4096 * 4;
|
|
break;
|
|
|
|
case 16384:
|
|
state.page_granule_shift = 14;
|
|
state.page_tables_size = 16384 * 4;
|
|
break;
|
|
|
|
case 65536:
|
|
state.page_granule_shift = 16;
|
|
state.page_tables_size = 65536 * 4;
|
|
break;
|
|
|
|
default:
|
|
mmu_printf(FAILURE, "Bad granule %u\n", (unsigned) granule);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static void test_granule(size_t bytes)
|
|
{
|
|
state.mmu_setup_func = ^{ mmu_setup_flat(); };
|
|
setup_standard_granule(bytes);
|
|
do_test(REASON_OK, ^{ return mmu_test(); });
|
|
}
|
|
|
|
static void test_granule_4kb(void)
|
|
{
|
|
state.test_name = "4KB granule";
|
|
test_granule(4096);
|
|
}
|
|
|
|
static void test_granule_16kb(void)
|
|
{
|
|
state.test_name = "16KB granule";
|
|
test_granule(16384);
|
|
}
|
|
|
|
static void test_granule_64kb(void)
|
|
{
|
|
state.test_name = "64KB granule";
|
|
test_granule(65536);
|
|
}
|
|
|
|
static void test_fail_oom(void)
|
|
{
|
|
state.test_name = "Detect out-of-memory PTE allocation";
|
|
state.mmu_setup_func = ^{ mmu_setup_flat(); };
|
|
setup_standard_granule(4096);
|
|
// Too small.
|
|
state.page_tables_size = 16384;
|
|
expect_panic("tt_alloc", "Out of table allocation space L2");
|
|
do_test(REASON_EXPECTED_PANIC, ^{ return mmu_test(); });
|
|
}
|
|
|
|
static void test_fail_range_misaligned(void)
|
|
{
|
|
state.test_name = "Detect misaligned mapping request";
|
|
state.mmu_setup_func = ^{ mmu_setup_misaligned(); };
|
|
setup_standard_granule(4096);
|
|
// Expect vaddr, paddr, size to misaligned.
|
|
expect_panic("arm_mmu_map_range", "vaddr | paddr | size");
|
|
do_test(REASON_EXPECTED_PANIC, ^{ return mmu_test(); });
|
|
}
|
|
|
|
static void test_fail_bad_granule(void)
|
|
{
|
|
state.test_name = "Detect bad granule size";
|
|
state.mmu_setup_func = ^{
|
|
// Alter the granule to an illegal value.
|
|
++state.page_granule_shift;
|
|
mmu_setup_flat();
|
|
};
|
|
setup_standard_granule(4096);
|
|
expect_panic("get_level_shift", "Granule 2^13");
|
|
do_test(REASON_EXPECTED_PANIC, ^{ return mmu_test(); });
|
|
}
|
|
|
|
static void test_fail_remap_existing(void)
|
|
{
|
|
state.test_name = "Detect remapping existing page";
|
|
state.mmu_setup_func = ^{ mmu_setup_fail_remap_existing(); };
|
|
setup_standard_granule(4096);
|
|
expect_panic("map_at_level", "Remapping an existing L2");
|
|
do_test(REASON_EXPECTED_PANIC, ^{ return mmu_test(); });
|
|
}
|
|
|
|
static void test_fail_breaking_down(void)
|
|
{
|
|
state.test_name = "Detect breaking down existing mapping";
|
|
state.mmu_setup_func = ^{ mmu_setup_fail_breaking_down(); };
|
|
setup_standard_granule(4096);
|
|
expect_panic("map_at_level", "Breaking down");
|
|
do_test(REASON_EXPECTED_PANIC, ^{ return mmu_test(); });
|
|
}
|
|
|
|
static void test_get_tcr(void)
|
|
{
|
|
state.test_name = "get_tcr()";
|
|
do_test(REASON_OK,
|
|
^{
|
|
state.page_granule_shift = 12;
|
|
mmu_get_tcr();
|
|
state.page_granule_shift = 14;
|
|
mmu_get_tcr();
|
|
state.page_granule_shift = 16;
|
|
mmu_get_tcr();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void test_fail_get_tcr_bad_granule(void)
|
|
{
|
|
state.test_name = "Detect get_tcr() with bad granule";
|
|
expect_panic("get_tcr", "Bad granule");
|
|
state.page_granule_shift = 13;
|
|
do_test(REASON_EXPECTED_PANIC, ^{ mmu_get_tcr(); return false; });
|
|
}
|
|
|
|
static void test_get_block_flags_for_level(unsigned level)
|
|
{
|
|
// [63:59] SBZ
|
|
// [58:55] S/W use SBZ
|
|
// [54:53] XN,PXN = execute never, 0 for RX, 3 for R/RW
|
|
// [52] Contiguous hint, SBZ
|
|
// [51:12] SBZ and address bits, also SBZ
|
|
// [11] nG - non-global, we don't use ASID - SBZ
|
|
// [10] AF - access flag, should set - SBO
|
|
// [9:8] SH - shareability - 0 for device, 2 for normal
|
|
// [7:6] AP - access permission - 0 for RW, 2 for RO
|
|
// [5] NS - non-secure, should set - SBO
|
|
// [4:2] AttrIndx - 0 for device, 1 for normal
|
|
// [1] Walk - L0,L1,L2 0=Block; L3 1=Block
|
|
// [0] V - 0=fault, 1=valid
|
|
const uint64_t sbo = (1 << 10) | (1 << 5) | (1 << 0);
|
|
const uint64_t normal = (2 << 8) | (1 << 2);
|
|
const uint64_t device = (0 << 8) | (0 << 2);
|
|
const uint64_t read_write = 0 << 6;
|
|
const uint64_t read_only = 2 << 6;
|
|
const uint64_t execute = 0ULL << 53;
|
|
const uint64_t no_execute = 3ULL << 53;
|
|
static const struct {
|
|
arm_mmu_extended_attr_t attr;
|
|
uint64_t expect;
|
|
} tests[] = {
|
|
{ kARMMMUDeviceR, sbo | device | read_only | no_execute },
|
|
{ kARMMMUDeviceRX, sbo | device | read_only | execute },
|
|
{ kARMMMUDeviceRW, sbo | device | read_write | no_execute },
|
|
{ kARMMMUNormalR, sbo | normal | read_only | no_execute },
|
|
{ kARMMMUNormalRX, sbo | normal | read_only | execute },
|
|
{ kARMMMUNormalRW, sbo | normal | read_write | no_execute },
|
|
};
|
|
for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) {
|
|
arm_mmu_extended_attr_t attr = tests[i].attr;
|
|
uint64_t expect = tests[i].expect;
|
|
if (level == 3) {
|
|
expect |= 1 << 1; // "Walk" flag
|
|
}
|
|
uint64_t value = mmu_get_block_flags(level, attr);
|
|
if (value != expect) {
|
|
mmu_printf(FAILURE, "get_block_flags level %u test %u enum %u\n",
|
|
level, (unsigned) i, (unsigned) attr);
|
|
mmu_printf(FAILURE, "got 0x%" PRIx64 "\n", value);
|
|
mmu_printf(FAILURE, "expect 0x%" PRIx64 "\n", expect);
|
|
test_unwind(REASON_PTE_MISMATCH);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_get_block_flags(void)
|
|
{
|
|
state.test_name = "get_block_flags()";
|
|
state.page_granule_shift = 12;
|
|
do_test(REASON_OK, ^{
|
|
for (unsigned level = 1; level <= 3; ++level) {
|
|
test_get_block_flags_for_level(level);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void test_get_table_flags(void)
|
|
{
|
|
state.test_name = "get_tte_flags()";
|
|
state.page_granule_shift = 12;
|
|
// [63] NS
|
|
// [62:61] AP
|
|
// [60] XN
|
|
// [59] PXN
|
|
// [58:52] ignored
|
|
// [51:48] zero, sbz
|
|
// [47:12] TableOutputAddress
|
|
// [11:2] ignored
|
|
// [1] type=1, sbo
|
|
// [0] valid=1, sbo
|
|
const uint64_t expect = (1ULL << 63) | (1ULL << 1) | (1ULL << 0);
|
|
do_test(REASON_OK, ^{
|
|
uint64_t value = mmu_get_table_flags();
|
|
if (value != expect) {
|
|
mmu_printf(FAILURE, "get_table_flags() mismatch\n");
|
|
mmu_printf(FAILURE, "Got 0x%" PRIx64 "\n", value);
|
|
mmu_printf(FAILURE, "Expect 0x%" PRIx64 "\n", expect);
|
|
test_unwind(REASON_PTE_MISMATCH);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void test_fail_get_block_flags_bad_attr(void)
|
|
{
|
|
state.test_name = "Detect get_block_flags() bad attribute";
|
|
state.page_granule_shift = 12;
|
|
expect_panic("get_block_flags", "Bad attr");
|
|
do_test(REASON_EXPECTED_PANIC,
|
|
^{ mmu_get_block_flags(3, -1); return false; });
|
|
}
|
|
|
|
static void test_fail_get_block_flags_bad_level(void)
|
|
{
|
|
state.test_name = "Detect get_pte_flags() bad level";
|
|
state.page_granule_shift = 16;
|
|
expect_panic("get_block_flags", "level_permits_blocks");
|
|
// Should not allow a block at L1 with 64KB granule.
|
|
do_test(REASON_EXPECTED_PANIC,
|
|
^{ mmu_get_block_flags(1, kARMMMUNormalRW); return false; });
|
|
}
|
|
|
|
static void test_tt_alloc(unsigned granule_shift)
|
|
{
|
|
state.test_name = "tt_alloc()";
|
|
state.page_granule_shift = granule_shift;
|
|
state.page_tables_size = (1 << granule_shift) * 3;
|
|
state.mmu_setup_func = ^{};
|
|
do_test(REASON_OK,
|
|
^{
|
|
arm_mmu_init(false);
|
|
if (granule_shift == 12)
|
|
mmu_tt_alloc(2);
|
|
mmu_tt_alloc(3);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void test_tt_alloc_bad_level(void)
|
|
{
|
|
state.test_name = "Detect tt_alloc() bad level";
|
|
state.page_granule_shift = 14;
|
|
state.page_tables_size = 16384 * 3;
|
|
state.mmu_setup_func = ^{};
|
|
expect_panic("tt_alloc", "level > ");
|
|
do_test(REASON_EXPECTED_PANIC,
|
|
^{
|
|
arm_mmu_init(false);
|
|
mmu_tt_alloc(1);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
static void test_tt_alloc_bad_alignment(void)
|
|
{
|
|
state.test_name = "Detect tt_alloc() bad alignment";
|
|
state.page_granule_shift = 14;
|
|
state.page_tables_size = 65536 * 3; // Enough space.
|
|
state.mmu_setup_func = ^{};
|
|
expect_panic("tt_alloc", "Bad aligned pte alloc");
|
|
do_test(REASON_EXPECTED_PANIC,
|
|
^{
|
|
arm_mmu_init(false);
|
|
mmu_tt_alloc(3);
|
|
// Allocation is aligned, so only way to trip it up
|
|
// is to artificially tamper with the granule.
|
|
state.page_granule_shift = 16;
|
|
mmu_tt_alloc(3);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
static void test_map_at_top_level_large(void)
|
|
{
|
|
state.test_name = "map_at_level() at top level traverses only L1";
|
|
setup_standard_granule(4096);
|
|
__block uint64_t alloc_start, alloc_next, alloc_end;
|
|
state.mmu_setup_func = ^{
|
|
mmu_get_tt_alloc_range(&alloc_start, &alloc_next, &alloc_end);
|
|
mmu_map_at_level(1,
|
|
(addr_t) mmu_get_tt_top_level(),
|
|
1ULL << 30, 1ULL << 30, 1ULL << 30,
|
|
kARMMMUNormalRW);
|
|
};
|
|
__block size_t calls = 0;
|
|
state.set_table_entry_hook = ^(unsigned level,
|
|
uint64_t base,
|
|
size_t index,
|
|
uint64_t value) {
|
|
(void) value;
|
|
++calls;
|
|
mmu_printf(INFO, "calls:%u %u 0x%" PRIx64 " 0x%" PRIx64
|
|
" 0x%" PRIx64 "\n",
|
|
(unsigned) calls,
|
|
level, base, (uint64_t) index, value);
|
|
if (calls == 1) {
|
|
if (level != 1 ||
|
|
base != (uint64_t) mmu_get_tt_top_level() ||
|
|
index != 1) {
|
|
mmu_printf(FAILURE, "Did not write L0 index 0\n");
|
|
test_unwind(REASON_PTE_ACCESS);
|
|
}
|
|
} else {
|
|
mmu_printf(FAILURE, "Too many calls to get_table_entry()\n");
|
|
test_unwind(REASON_PTE_ACCESS);
|
|
}
|
|
};
|
|
do_test(REASON_OK,
|
|
^{
|
|
if (!mmu_test()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void test_map_at_l3(void)
|
|
{
|
|
state.test_name = "map_at_level() at l3";
|
|
setup_standard_granule(16384);
|
|
__block uint64_t alloc_start = 0, alloc_next = 0, alloc_end = 0;
|
|
__block uint64_t l3_base = 0;
|
|
state.mmu_setup_func = ^{
|
|
mmu_get_tt_alloc_range(&alloc_start, &alloc_next, &alloc_end);
|
|
l3_base = mmu_tt_alloc(3);
|
|
mmu_map_at_level(3, l3_base,
|
|
(1ULL << 30) + 16384 * 3,
|
|
16384 * 7,
|
|
16384,
|
|
kARMMMUDeviceRW);
|
|
};
|
|
__block size_t calls = 0;
|
|
state.set_table_entry_hook = ^(unsigned level,
|
|
uint64_t base,
|
|
size_t index,
|
|
uint64_t value) {
|
|
(void) value;
|
|
++calls;
|
|
mmu_printf(INFO, "calls:%u %u 0x%" PRIx64 " 0x%" PRIx64
|
|
" 0x%" PRIx64 "\n",
|
|
(unsigned) calls,
|
|
level, base, (uint64_t) index, value);
|
|
if (calls == 1) {
|
|
if (level != 3 ||
|
|
base != l3_base ||
|
|
index != 3) {
|
|
mmu_printf(FAILURE, "Did not write L3 index 3\n");
|
|
test_unwind(REASON_PTE_ACCESS);
|
|
}
|
|
} else {
|
|
mmu_printf(FAILURE, "Too many calls to get_table_entry()\n");
|
|
test_unwind(REASON_PTE_ACCESS);
|
|
}
|
|
};
|
|
do_test(REASON_OK,
|
|
^{
|
|
if (!mmu_test()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
int main()
|
|
{
|
|
reset_state();
|
|
|
|
test_granule_4kb();
|
|
test_granule_16kb();
|
|
test_granule_64kb();
|
|
|
|
test_fail_oom();
|
|
test_fail_range_misaligned();
|
|
test_fail_bad_granule();
|
|
|
|
test_fail_remap_existing();
|
|
test_fail_breaking_down();
|
|
|
|
test_get_tcr();
|
|
// test_fail_get_tcr_bad_granule(); !!!FIXME!!! <rdar://problem/19225114> Unexpected panic in function get_virt_address_bits: Bad granule
|
|
|
|
test_get_block_flags();
|
|
test_get_table_flags();
|
|
test_fail_get_block_flags_bad_attr();
|
|
test_fail_get_block_flags_bad_level();
|
|
|
|
test_tt_alloc(12);
|
|
test_tt_alloc(14);
|
|
test_tt_alloc(16);
|
|
test_tt_alloc_bad_level();
|
|
test_tt_alloc_bad_alignment();
|
|
|
|
test_map_at_top_level_large();
|
|
test_map_at_l3();
|
|
|
|
mmu_printf(INFO, "%" PRIu64 "/%" PRIu64 " tests passed\n",
|
|
(uint64_t) tests_passed, (uint64_t) tests_run);
|
|
assert(tests_passed <= tests_run);
|
|
if (tests_passed == tests_run) {
|
|
mmu_printf(INFO, "Pass\n");
|
|
return 0;
|
|
} else {
|
|
mmu_printf(INFO, "!!! Fail\n");
|
|
return 1;
|
|
}
|
|
}
|