blob: d0c976d6d81f16daa39b453cdad09f7d9572e12a [file] [log] [blame]
Andrew Walbran9fa106c2018-09-28 14:19:29 +01001/*
2 * Copyright 2018 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17extern "C" {
18#include "hf/mm.h"
19
20#include "hf/arch/mm.h"
21
22#include "hf/alloc.h"
23}
24
25#include <memory>
26
27#include <gmock/gmock.h>
28
29using ::testing::Eq;
30
31constexpr size_t TEST_HEAP_SIZE = PAGE_SIZE * 10;
32constexpr size_t ENTRY_COUNT = PAGE_SIZE / sizeof(pte_t);
33const static int TOP_LEVEL = arch_mm_max_level(0);
34const static pte_t ABSENT_ENTRY = arch_mm_absent_pte(TOP_LEVEL);
35
36/**
37 * Calculates the size of the address space represented by a page table entry at
38 * the given level.
39 */
40static size_t mm_entry_size(int level)
41{
42 return UINT64_C(1) << (PAGE_BITS + level * PAGE_LEVEL_BITS);
43}
44
45/**
46 * Fill a ptable with absent entries.
47 */
48static void init_absent(pte_t *table)
49{
50 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
51 table[i] = ABSENT_ENTRY;
52 }
53}
54
55/**
56 * Fill a ptable with block entries.
57 */
58static void init_blocks(pte_t *table, int level, paddr_t start_address,
59 uint64_t attrs)
60{
61 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
62 table[i] = arch_mm_block_pte(
63 level, pa_add(start_address, i * mm_entry_size(level)),
64 attrs);
65 }
66}
67
68/**
69 * Defragging an entirely empty table should have no effect.
70 */
71TEST(mm, ptable_defrag_empty)
72{
73 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
74 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
75
76 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
77 init_absent(table);
78 struct mm_ptable ptable;
79 ptable.table = pa_init((uintpaddr_t)table);
80
81 mm_ptable_defrag(&ptable, 0);
82
83 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
84 EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
85 }
86}
87
88/**
89 * Defragging a table with some empty subtables (even nested) should result in
90 * an empty table.
91 */
92TEST(mm, ptable_defrag_empty_subtables)
93{
94 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
95 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
96
97 pte_t *subtable_a = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
98 pte_t *subtable_aa = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
99 pte_t *subtable_b = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
100 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
101 init_absent(subtable_a);
102 init_absent(subtable_aa);
103 init_absent(subtable_b);
104 init_absent(table);
105
106 subtable_a[3] = arch_mm_table_pte(TOP_LEVEL - 1,
107 pa_init((uintpaddr_t)subtable_aa));
108 table[0] =
109 arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_a));
110 table[5] =
111 arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_b));
112
113 struct mm_ptable ptable;
114 ptable.table = pa_init((uintpaddr_t)table);
115
116 mm_ptable_defrag(&ptable, 0);
117
118 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
119 EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
120 }
121}
122
123/**
124 * Any subtable with all blocks with the same attributes should be replaced
125 * with a single block.
126 */
127TEST(mm, ptable_defrag_block_subtables)
128{
129 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
130 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
131
132 pte_t *subtable_a = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
133 pte_t *subtable_aa = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
134 pte_t *subtable_b = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
135 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
136 init_blocks(subtable_a, TOP_LEVEL - 1, pa_init(0), 0);
137 init_blocks(subtable_aa, TOP_LEVEL - 2,
138 pa_init(3 * mm_entry_size(TOP_LEVEL - 1)), 0);
139 init_blocks(subtable_b, TOP_LEVEL - 1,
140 pa_init(5 * mm_entry_size(TOP_LEVEL)), 0);
141 init_blocks(table, TOP_LEVEL, pa_init(0), 0);
142
143 subtable_a[3] = arch_mm_table_pte(TOP_LEVEL - 1,
144 pa_init((uintpaddr_t)subtable_aa));
145 table[0] =
146 arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_a));
147 table[5] =
148 arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_b));
149
150 struct mm_ptable ptable;
151 ptable.table = pa_init((uintpaddr_t)table);
152
153 mm_ptable_defrag(&ptable, 0);
154
155 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
156 EXPECT_TRUE(arch_mm_pte_is_present(table[i], TOP_LEVEL))
157 << "i=" << i;
158 EXPECT_TRUE(arch_mm_pte_is_block(table[i], TOP_LEVEL))
159 << "i=" << i;
160 EXPECT_THAT(pa_addr(arch_mm_block_from_pte(table[i])),
161 Eq(i * mm_entry_size(TOP_LEVEL)))
162 << "i=" << i;
163 }
164}
Andrew Walbran6324fc92018-10-03 11:46:43 +0100165
166/** If nothing is mapped, unmapping the hypervisor should have no effect. */
167TEST(mm, ptable_unmap_hypervisor_not_mapped)
168{
169 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
170 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
171
172 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
173 init_absent(table);
174
175 struct mm_ptable ptable;
176 ptable.table = pa_init((uintpaddr_t)table);
177
178 EXPECT_TRUE(mm_ptable_unmap_hypervisor(&ptable, 0));
179
180 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
181 EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
182 }
183}
184
185/**
186 * Unmapping everything should result in an empty page table with no subtables.
187 */
188TEST(mm, vm_unmap)
189{
190 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
191 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
192
193 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
194 pte_t *subtable_a = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
195 pte_t *subtable_aa = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
196 init_absent(table);
197 init_absent(subtable_a);
198 init_absent(subtable_aa);
199
200 subtable_aa[0] = arch_mm_block_pte(TOP_LEVEL - 2, pa_init(0), 0);
201 subtable_a[0] = arch_mm_table_pte(TOP_LEVEL - 1,
202 pa_init((uintpaddr_t)subtable_aa));
203 table[0] =
204 arch_mm_table_pte(TOP_LEVEL, pa_init((uintpaddr_t)subtable_a));
205
206 struct mm_ptable ptable;
207 ptable.table = pa_init((uintpaddr_t)table);
208
209 EXPECT_TRUE(mm_vm_unmap(&ptable, pa_init(0), pa_init(1), 0));
210
211 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
212 EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
213 }
214}
215
216/**
217 * Mapping a range should result in just the corresponding pages being mapped.
218 */
219TEST(mm, vm_identity_map)
220{
221 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
222 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
223
224 /* Start with an empty page table. */
225 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
226 init_absent(table);
227 struct mm_ptable ptable;
228 ptable.table = pa_init((uintpaddr_t)table);
229
230 /* Try mapping the first page. */
231 ipaddr_t ipa = ipa_init(-1);
232 EXPECT_TRUE(mm_vm_identity_map(&ptable, pa_init(0), pa_init(PAGE_SIZE),
233 0, &ipa));
234 EXPECT_THAT(ipa_addr(ipa), Eq(0));
235
236 /* Check that the first page is mapped, and nothing else. */
237 for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
238 EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
239 }
240 ASSERT_TRUE(arch_mm_pte_is_table(table[0], TOP_LEVEL));
241 pte_t *subtable_a = (pte_t *)ptr_from_va(
242 va_from_pa(arch_mm_table_from_pte(table[0])));
243 for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
244 EXPECT_THAT(subtable_a[i], Eq(ABSENT_ENTRY)) << "i=" << i;
245 }
246 ASSERT_TRUE(arch_mm_pte_is_table(subtable_a[0], TOP_LEVEL - 1));
247 pte_t *subtable_aa = (pte_t *)ptr_from_va(
248 va_from_pa(arch_mm_table_from_pte(subtable_a[0])));
249 for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
250 EXPECT_THAT(subtable_aa[i], Eq(ABSENT_ENTRY)) << "i=" << i;
251 }
252 EXPECT_TRUE(arch_mm_pte_is_block(subtable_aa[0], TOP_LEVEL - 2));
253 EXPECT_THAT(pa_addr(arch_mm_block_from_pte(subtable_aa[0])), Eq(0));
254}
255
256/** Mapping a range that is already mapped should be a no-op. */
257TEST(mm, vm_identity_map_already_mapped)
258{
259 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
260 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
261
262 /* Start with a full page table mapping everything. */
263 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
264 init_blocks(table, TOP_LEVEL, pa_init(0), 0);
265 struct mm_ptable ptable;
266 ptable.table = pa_init((uintpaddr_t)table);
267
268 /* Try mapping the first page. */
269 ipaddr_t ipa = ipa_init(-1);
270 EXPECT_TRUE(mm_vm_identity_map(&ptable, pa_init(0), pa_init(PAGE_SIZE),
271 0, &ipa));
272 EXPECT_THAT(ipa_addr(ipa), Eq(0));
273
274 /*
275 * The table should still be full of blocks, with no subtables or
276 * anything else.
277 */
278 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
279 EXPECT_TRUE(arch_mm_pte_is_block(table[i], TOP_LEVEL))
280 << "i=" << i;
281 }
282}
283
284/** Mapping a single page should result in just that page being mapped. */
285TEST(mm, vm_identity_map_page)
286{
287 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
288 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
289
290 /* Start with an empty page table. */
291 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
292 init_absent(table);
293 struct mm_ptable ptable;
294 ptable.table = pa_init((uintpaddr_t)table);
295
296 /* Try mapping the first page. */
297 ipaddr_t ipa = ipa_init(-1);
298 EXPECT_TRUE(mm_vm_identity_map_page(&ptable, pa_init(0), 0, &ipa));
299 EXPECT_THAT(ipa_addr(ipa), Eq(0));
300
301 /* Check that the first page is mapped, and nothing else. */
302 for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
303 EXPECT_THAT(table[i], Eq(ABSENT_ENTRY)) << "i=" << i;
304 }
305 ASSERT_TRUE(arch_mm_pte_is_table(table[0], TOP_LEVEL));
306 pte_t *subtable_a = (pte_t *)ptr_from_va(
307 va_from_pa(arch_mm_table_from_pte(table[0])));
308 for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
309 EXPECT_THAT(subtable_a[i], Eq(ABSENT_ENTRY)) << "i=" << i;
310 }
311 ASSERT_TRUE(arch_mm_pte_is_table(subtable_a[0], TOP_LEVEL - 1));
312 pte_t *subtable_aa = (pte_t *)ptr_from_va(
313 va_from_pa(arch_mm_table_from_pte(subtable_a[0])));
314 for (uint64_t i = 1; i < ENTRY_COUNT; ++i) {
315 EXPECT_THAT(subtable_aa[i], Eq(ABSENT_ENTRY)) << "i=" << i;
316 }
317 EXPECT_TRUE(arch_mm_pte_is_block(subtable_aa[0], TOP_LEVEL - 2));
318 EXPECT_THAT(pa_addr(arch_mm_block_from_pte(subtable_aa[0])), Eq(0));
319}
320
321/** Mapping a page that is already mapped should be a no-op. */
322TEST(mm, vm_identity_map_page_already_mapped)
323{
324 auto test_heap = std::make_unique<uint8_t[]>(TEST_HEAP_SIZE);
325 halloc_init((size_t)test_heap.get(), TEST_HEAP_SIZE);
326
327 /* Start with a full page table mapping everything. */
328 pte_t *table = (pte_t *)halloc_aligned(PAGE_SIZE, PAGE_SIZE);
329 init_blocks(table, TOP_LEVEL, pa_init(0), 0);
330 struct mm_ptable ptable;
331 ptable.table = pa_init((uintpaddr_t)table);
332
333 /* Try mapping the first page. */
334 ipaddr_t ipa = ipa_init(-1);
335 EXPECT_TRUE(mm_vm_identity_map_page(&ptable, pa_init(0), 0, &ipa));
336 EXPECT_THAT(ipa_addr(ipa), Eq(0));
337
338 /*
339 * The table should still be full of blocks, with no subtables or
340 * anything else.
341 */
342 for (uint64_t i = 0; i < ENTRY_COUNT; ++i) {
343 EXPECT_TRUE(arch_mm_pte_is_block(table[i], TOP_LEVEL))
344 << "i=" << i;
345 }
346}