Skip to content

Commit e6eebea

Browse files
author
Nicolas Pitre
committed
tests: arm64: exercize MMU page table allocation and recycling
This code was never formally tested before... and without the preceding commit it obviously didn't work either. Signed-off-by: Nicolas Pitre <[email protected]>
1 parent a4ebc21 commit e6eebea

File tree

5 files changed

+208
-0
lines changed

5 files changed

+208
-0
lines changed

arch/arm64/core/mmu.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,35 @@ static inline bool is_table_unused(uint64_t *table)
102102
return (table_usage(table, 0) & XLAT_PTE_COUNT_MASK) == 0;
103103
}
104104

105+
#ifdef CONFIG_TEST
106+
/* Hooks to let test code peek at table states */
107+
108+
int arm64_mmu_nb_free_tables(void)
109+
{
110+
int count = 0;
111+
112+
for (int i = 0; i < CONFIG_MAX_XLAT_TABLES; i++) {
113+
if (xlat_use_count[i] == 0) {
114+
count++;
115+
}
116+
}
117+
118+
return count;
119+
}
120+
121+
int arm64_mmu_tables_total_usage(void)
122+
{
123+
int count = 0;
124+
125+
for (int i = 0; i < CONFIG_MAX_XLAT_TABLES; i++) {
126+
count += xlat_use_count[i];
127+
}
128+
129+
return count;
130+
}
131+
132+
#endif /* CONFIG_TEST */
133+
105134
static inline bool is_free_desc(uint64_t desc)
106135
{
107136
return (desc & PTE_DESC_TYPE_MASK) == PTE_INVALID_DESC;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(arm64_mmu)
7+
8+
target_include_directories(app PRIVATE
9+
${ZEPHYR_BASE}/kernel/include
10+
${ZEPHYR_BASE}/arch/${ARCH}/include
11+
)
12+
13+
target_sources(app PRIVATE src/main.c)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CONFIG_ASSERT=y
2+
CONFIG_LOG=y
3+
CONFIG_LOG_MODE_MINIMAL=y
4+
CONFIG_ZTEST=y
5+
CONFIG_ARM_MMU=y
6+
CONFIG_MMU_PAGE_SIZE=0x1000
7+
CONFIG_ARM64_VA_BITS_36=y
8+
CONFIG_ARM64_PA_BITS_36=y
9+
CONFIG_MAX_XLAT_TABLES=8
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright (c) 2024 BayLibre, SAS
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/ztest.h>
9+
#include <kernel_arch_interface.h>
10+
11+
/*
12+
* Virtual and physical addresses used to exercize MMU page table recycling.
13+
* Those are completely arbitrary addresses away from any existing addresses
14+
* (no worry, the test will fail otherwise). Those addresses don't have
15+
* to be valid as we won't attempt any access to the mapped memory.
16+
*/
17+
#define TEST_VIRT_ADDR 0x456560000
18+
#define TEST_PHYS_ADDR 0x123230000
19+
20+
/* special test hooks in arch/arm64/core/mmu.c for test purpose */
21+
extern int arm64_mmu_nb_free_tables(void);
22+
extern int arm64_mmu_tables_total_usage(void);
23+
24+
/* initial states to compare against */
25+
static int initial_nb_free_tables;
26+
static int initial_tables_usage;
27+
28+
static void *arm64_mmu_test_init(void)
29+
{
30+
/* get initial states */
31+
initial_nb_free_tables = arm64_mmu_nb_free_tables();
32+
initial_tables_usage = arm64_mmu_tables_total_usage();
33+
34+
TC_PRINT(" Total page tables: %d\n", CONFIG_MAX_XLAT_TABLES);
35+
TC_PRINT(" Initial free tables: %d\n", initial_nb_free_tables);
36+
TC_PRINT(" Initial total table usage: %#x\n", initial_tables_usage);
37+
38+
zassert_true(initial_nb_free_tables > 1,
39+
"initial_nb_free_tables = %d", initial_nb_free_tables);
40+
zassert_true(initial_tables_usage > 1,
41+
"initial_tables_usage = %d", initial_tables_usage);
42+
43+
return NULL;
44+
}
45+
46+
static int mem_map_test(uintptr_t virt_addr, uintptr_t phys_addr, size_t size)
47+
{
48+
/*
49+
* This is not defined to return any error but the implementation
50+
* will call k_panic() if an error occurs.
51+
*/
52+
arch_mem_map((void *)virt_addr, phys_addr, size, K_MEM_ARM_NORMAL_NC);
53+
54+
int mapped_nb_free_tables = arm64_mmu_nb_free_tables();
55+
int mapped_tables_usage = arm64_mmu_tables_total_usage();
56+
57+
TC_PRINT(" After arch_mem_map:\n");
58+
TC_PRINT(" current free tables: %d\n", mapped_nb_free_tables);
59+
TC_PRINT(" current total table usage: %#x\n", mapped_tables_usage);
60+
61+
zassert_true(mapped_nb_free_tables < initial_nb_free_tables,
62+
"%d vs %d", mapped_nb_free_tables, initial_nb_free_tables);
63+
zassert_true(mapped_tables_usage > initial_tables_usage,
64+
"%#x vs %#x", mapped_tables_usage > initial_tables_usage);
65+
66+
arch_mem_unmap((void *)virt_addr, size);
67+
68+
int unmapped_nb_free_tables = arm64_mmu_nb_free_tables();
69+
int unmapped_tables_usage = arm64_mmu_tables_total_usage();
70+
71+
TC_PRINT(" After arch_mem_unmap:\n");
72+
TC_PRINT(" current free tables: %d\n", unmapped_nb_free_tables);
73+
TC_PRINT(" current total table usage: %#x\n", unmapped_tables_usage);
74+
75+
zassert_true(unmapped_nb_free_tables == initial_nb_free_tables,
76+
"%d vs %d", unmapped_nb_free_tables, initial_nb_free_tables);
77+
zassert_true(unmapped_tables_usage == initial_tables_usage,
78+
"%#x vs %#x", unmapped_tables_usage > initial_tables_usage);
79+
80+
int tables_used = unmapped_nb_free_tables - mapped_nb_free_tables;
81+
return tables_used;
82+
}
83+
84+
ZTEST(arm64_mmu, test_arm64_mmu_01_single_page)
85+
{
86+
/*
87+
* Let's map a single page to start with. This will allocate
88+
* multiple tables to reach the deepest level.
89+
*/
90+
uintptr_t virt = TEST_VIRT_ADDR;
91+
uintptr_t phys = TEST_PHYS_ADDR;
92+
size_t size = CONFIG_MMU_PAGE_SIZE;
93+
94+
int tables_used = mem_map_test(virt, phys, size);
95+
96+
zassert_true(tables_used == 2, "used %d tables", tables_used);
97+
}
98+
99+
ZTEST(arm64_mmu, test_arm64_mmu_02_single_block)
100+
{
101+
/*
102+
* Same thing as above, except that we expect a block mapping
103+
* this time. Both addresses and the size must be properly aligned.
104+
* Table allocation won't go as deep as for a page.
105+
*/
106+
int table_entries = CONFIG_MMU_PAGE_SIZE / sizeof(uint64_t);
107+
size_t block_size = table_entries * CONFIG_MMU_PAGE_SIZE;
108+
uintptr_t virt = TEST_VIRT_ADDR & ~(block_size - 1);
109+
uintptr_t phys = TEST_PHYS_ADDR & ~(block_size - 1);
110+
111+
int tables_used = mem_map_test(virt, phys, block_size);
112+
113+
zassert_true(tables_used == 1, "used %d tables", tables_used);
114+
}
115+
116+
ZTEST(arm64_mmu, test_arm64_mmu_03_block_and_page)
117+
{
118+
/*
119+
* Same thing as above, except that we expect a block mapping
120+
* followed by a page mapping to exercize range splitting.
121+
* To achieve that we simply increase the size by one page and keep
122+
* starting addresses aligned to a block.
123+
*/
124+
int table_entries = CONFIG_MMU_PAGE_SIZE / sizeof(uint64_t);
125+
size_t block_size = table_entries * CONFIG_MMU_PAGE_SIZE;
126+
uintptr_t virt = TEST_VIRT_ADDR & ~(block_size - 1);
127+
uintptr_t phys = TEST_PHYS_ADDR & ~(block_size - 1);
128+
size_t size = block_size + CONFIG_MMU_PAGE_SIZE;
129+
130+
int tables_used = mem_map_test(virt, phys, size);
131+
132+
zassert_true(tables_used == 2, "used %d tables", tables_used);
133+
}
134+
135+
ZTEST(arm64_mmu, test_arm64_mmu_04_page_and_block)
136+
{
137+
/*
138+
* Same thing as above, except that we expect a page mapping
139+
* followed by a block mapping to exercize range splitting.
140+
* To achieve that we increase the size by one page and decrease
141+
* starting addresses by one page below block alignment.
142+
*/
143+
int table_entries = CONFIG_MMU_PAGE_SIZE / sizeof(uint64_t);
144+
size_t block_size = table_entries * CONFIG_MMU_PAGE_SIZE;
145+
uintptr_t virt = (TEST_VIRT_ADDR & ~(block_size - 1)) - CONFIG_MMU_PAGE_SIZE;
146+
uintptr_t phys = (TEST_PHYS_ADDR & ~(block_size - 1)) - CONFIG_MMU_PAGE_SIZE;
147+
size_t size = block_size + CONFIG_MMU_PAGE_SIZE;
148+
149+
int tables_used = mem_map_test(virt, phys, size);
150+
151+
zassert_true(tables_used == 2, "used %d tables", tables_used);
152+
}
153+
154+
ZTEST_SUITE(arm64_mmu, NULL, arm64_mmu_test_init, NULL, NULL, NULL);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tests:
2+
arch.arm64.mmu:
3+
platform_allow: qemu_cortex_a53

0 commit comments

Comments
 (0)