blob: 166086383408860496e379d5536361505087df7a [file] [log] [blame]
Andrew Scull18834872018-10-12 11:48:09 +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
Andrew Scull18c78fc2018-08-20 12:57:41 +010017#include "hf/mm.h"
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010018
Andrew Scull80871322018-08-06 12:04:09 +010019#include <assert.h>
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010020#include <stdatomic.h>
21#include <stdint.h>
22
Andrew Scull18c78fc2018-08-20 12:57:41 +010023#include "hf/alloc.h"
24#include "hf/dlog.h"
Andrew Scull5991ec92018-10-08 14:55:02 +010025#include "hf/layout.h"
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010026
Andrew Walbran2400ed22018-09-27 14:45:58 +010027/**
28 * This file has functions for managing the level 1 and 2 page tables used by
29 * Hafnium. There is a level 1 mapping used by Hafnium itself to access memory,
30 * and then a level 2 mapping per VM. The design assumes that all page tables
31 * contain only 1-1 mappings, aligned on the block boundaries.
32 */
33
Andrew Scull80871322018-08-06 12:04:09 +010034/* The type of addresses stored in the page table. */
35typedef uintvaddr_t ptable_addr_t;
36
Wedson Almeida Filhob2c159e2018-10-25 13:27:47 +010037/*
38 * For stage 2, the input is an intermediate physical addresses rather than a
39 * virtual address so:
40 */
Andrew Scull80871322018-08-06 12:04:09 +010041static_assert(
42 sizeof(ptable_addr_t) == sizeof(uintpaddr_t),
43 "Currently, the same code manages the stage 1 and stage 2 page tables "
44 "which only works if the virtual and intermediate physical addresses "
45 "are the same size. It looks like that assumption might not be holding "
46 "so we need to check that everything is going to be ok.");
47
Andrew Scull4f170f52018-07-19 12:58:20 +010048/* Keep macro alignment */
49/* clang-format off */
50
Andrew Scullf2f948e2018-10-22 18:39:28 +010051#define MAP_FLAG_NOSYNC 0x01
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010052#define MAP_FLAG_COMMIT 0x02
Andrew Walbran6324fc92018-10-03 11:46:43 +010053#define MAP_FLAG_UNMAP 0x04
Wedson Almeida Filho7c913232018-11-23 18:20:29 +000054#define MAP_FLAG_NOBBM 0x08
55#define MAP_FLAG_STAGE1 0x10
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010056
Andrew Scull4f170f52018-07-19 12:58:20 +010057/* clang-format on */
58
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +010059static struct mm_ptable ptable;
60
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010061/**
Andrew Scull4e5f8142018-10-12 14:37:19 +010062 * Get the page table from the physical address.
Andrew Walbran2400ed22018-09-27 14:45:58 +010063 */
Andrew Scull4e5f8142018-10-12 14:37:19 +010064static struct mm_page_table *mm_page_table_from_pa(paddr_t pa)
Andrew Walbran2400ed22018-09-27 14:45:58 +010065{
66 return ptr_from_va(va_from_pa(pa));
67}
68
69/**
Andrew Scull80871322018-08-06 12:04:09 +010070 * Rounds an address down to a page boundary.
71 */
72static ptable_addr_t mm_round_down_to_page(ptable_addr_t addr)
73{
74 return addr & ~((ptable_addr_t)(PAGE_SIZE - 1));
75}
76
77/**
78 * Rounds an address up to a page boundary.
79 */
80static ptable_addr_t mm_round_up_to_page(ptable_addr_t addr)
81{
82 return mm_round_down_to_page(addr + PAGE_SIZE - 1);
83}
84
85/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010086 * Calculates the size of the address space represented by a page table entry at
87 * the given level.
88 */
Andrew Sculle9827712018-10-19 14:54:20 +010089static size_t mm_entry_size(uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010090{
Andrew Scull78d6fd92018-09-06 15:08:36 +010091 return UINT64_C(1) << (PAGE_BITS + level * PAGE_LEVEL_BITS);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010092}
93
94/**
Andrew Scull80871322018-08-06 12:04:09 +010095 * For a given address, calculates the maximum (plus one) address that can be
96 * represented by the same table at the given level.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010097 */
Andrew Sculle9827712018-10-19 14:54:20 +010098static ptable_addr_t mm_level_end(ptable_addr_t addr, uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010099{
100 size_t offset = PAGE_BITS + (level + 1) * PAGE_LEVEL_BITS;
Andrew Scull80871322018-08-06 12:04:09 +0100101 return ((addr >> offset) + 1) << offset;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100102}
103
104/**
Andrew Scull80871322018-08-06 12:04:09 +0100105 * For a given address, calculates the index at which its entry is stored in a
106 * table at the given level.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100107 */
Andrew Sculle9827712018-10-19 14:54:20 +0100108static size_t mm_index(ptable_addr_t addr, uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100109{
Andrew Scull80871322018-08-06 12:04:09 +0100110 ptable_addr_t v = addr >> (PAGE_BITS + level * PAGE_LEVEL_BITS);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100111 return v & ((UINT64_C(1) << PAGE_LEVEL_BITS) - 1);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100112}
113
114/**
Andrew Scull4e5f8142018-10-12 14:37:19 +0100115 * Allocate a new page table.
116 */
Andrew Scullf2f948e2018-10-22 18:39:28 +0100117static struct mm_page_table *mm_alloc_page_table(bool nosync)
Andrew Scull4e5f8142018-10-12 14:37:19 +0100118{
Andrew Scullf2f948e2018-10-22 18:39:28 +0100119 if (nosync) {
Andrew Scull4e5f8142018-10-12 14:37:19 +0100120 return halloc_aligned_nosync(sizeof(struct mm_page_table),
121 alignof(struct mm_page_table));
122 }
123
124 return halloc_aligned(sizeof(struct mm_page_table),
125 alignof(struct mm_page_table));
126}
127
128/**
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000129 * Invalidates the TLB for the given address range.
130 */
131static void mm_invalidate_tlb(ptable_addr_t begin, ptable_addr_t end,
132 bool stage1)
133{
134 if (stage1) {
135 arch_mm_invalidate_stage1_range(va_init(begin), va_init(end));
136 } else {
137 arch_mm_invalidate_stage2_range(ipa_init(begin), ipa_init(end));
138 }
139}
140
141/**
142 * Frees all page-table-related memory associated with the given pte at the
143 * given level, including any subtables recursively.
144 */
145static void mm_free_page_pte(pte_t pte, uint8_t level)
146{
147 struct mm_page_table *table;
148 uint64_t i;
149
150 if (!arch_mm_pte_is_table(pte, level)) {
151 return;
152 }
153
154 /* Recursively free any subtables. */
155 table = mm_page_table_from_pa(arch_mm_table_from_pte(pte));
156 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
157 mm_free_page_pte(table->entries[i], level - 1);
158 }
159
160 /* Free the table itself. */
161 hfree(table);
162}
163
164/**
165 * Replaces a page table entry with the given value. If both old and new values
166 * are present, it performs a break-before-make sequence where it first writes
167 * an absent value to the PTE, flushes the TLB, then writes the actual new
168 * value. This is to prevent cases where CPUs have different 'present' values in
169 * their TLBs, which may result in issues for example in cache coherency.
170 */
171static void mm_replace_entry(ptable_addr_t begin, pte_t *pte, pte_t new_pte,
172 uint8_t level, int flags)
173{
174 pte_t v = *pte;
175
176 /*
177 * We need to do the break-before-make sequence if both values are
178 * present, and if it hasn't been inhibited by the NOBBM flag.
179 */
180 if (!(flags & MAP_FLAG_NOBBM) && arch_mm_pte_is_present(v, level) &&
181 arch_mm_pte_is_present(new_pte, level)) {
182 *pte = arch_mm_absent_pte(level);
183 mm_invalidate_tlb(begin, begin + mm_entry_size(level),
184 flags & MAP_FLAG_STAGE1);
185 }
186
187 /* Assign the new pte. */
188 *pte = new_pte;
189
190 /* Free pages that aren't in use anymore. */
191 mm_free_page_pte(v, level);
192}
193
194/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100195 * Populates the provided page table entry with a reference to another table if
196 * needed, that is, if it does not yet point to another table.
197 *
198 * Returns a pointer to the table the entry now points to.
199 */
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000200static struct mm_page_table *mm_populate_table_pte(ptable_addr_t begin,
201 pte_t *pte, uint8_t level,
202 int flags)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100203{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100204 struct mm_page_table *ntable;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100205 pte_t v = *pte;
206 pte_t new_pte;
207 size_t i;
208 size_t inc;
Andrew Sculle9827712018-10-19 14:54:20 +0100209 uint8_t level_below = level - 1;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100210
211 /* Just return pointer to table if it's already populated. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100212 if (arch_mm_pte_is_table(v, level)) {
Andrew Scull4e5f8142018-10-12 14:37:19 +0100213 return mm_page_table_from_pa(arch_mm_table_from_pte(v));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100214 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100215
216 /* Allocate a new table. */
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000217 ntable = mm_alloc_page_table(flags & MAP_FLAG_NOSYNC);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100218 if (ntable == NULL) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100219 dlog("Failed to allocate memory for page table\n");
220 return NULL;
221 }
222
223 /* Determine template for new pte and its increment. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100224 if (arch_mm_pte_is_block(v, level)) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100225 inc = mm_entry_size(level_below);
226 new_pte = arch_mm_block_pte(level_below,
227 arch_mm_block_from_pte(v),
228 arch_mm_pte_attrs(v));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100229 } else {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100230 inc = 0;
Andrew Walbran1b99f9d2018-10-03 17:54:40 +0100231 new_pte = arch_mm_absent_pte(level_below);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100232 }
233
234 /* Initialise entries in the new table. */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100235 for (i = 0; i < MM_PTE_PER_PAGE; i++) {
236 ntable->entries[i] = new_pte;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100237 new_pte += inc;
238 }
239
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000240 /* Ensure initialisation is visible before updating the pte. */
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100241 atomic_thread_fence(memory_order_release);
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000242
243 /* Replace the pte entry, doing a break-before-make if needed. */
244 mm_replace_entry(begin, pte,
245 arch_mm_table_pte(level, pa_init((uintpaddr_t)ntable)),
246 level, flags);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100247
248 return ntable;
249}
250
251/**
Andrew Walbran6324fc92018-10-03 11:46:43 +0100252 * Returns whether all entries in this table are absent.
253 */
Andrew Sculle9827712018-10-19 14:54:20 +0100254static bool mm_ptable_is_empty(struct mm_page_table *table, uint8_t level)
Andrew Walbran6324fc92018-10-03 11:46:43 +0100255{
256 uint64_t i;
257
Andrew Scull4e5f8142018-10-12 14:37:19 +0100258 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
259 if (arch_mm_pte_is_present(table->entries[i], level)) {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100260 return false;
261 }
262 }
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000263
Andrew Walbran6324fc92018-10-03 11:46:43 +0100264 return true;
265}
266
267/**
Andrew Scull80871322018-08-06 12:04:09 +0100268 * Updates the page table at the given level to map the given address range to a
Andrew Walbran6324fc92018-10-03 11:46:43 +0100269 * physical range using the provided (architecture-specific) attributes. Or if
270 * MAP_FLAG_UNMAP is set, unmap the given range instead.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100271 *
272 * This function calls itself recursively if it needs to update additional
273 * levels, but the recursion is bound by the maximum number of levels in a page
274 * table.
275 */
Andrew Scull80871322018-08-06 12:04:09 +0100276static bool mm_map_level(ptable_addr_t begin, ptable_addr_t end, paddr_t pa,
Andrew Sculle9827712018-10-19 14:54:20 +0100277 uint64_t attrs, struct mm_page_table *table,
278 uint8_t level, int flags)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100279{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100280 pte_t *pte = &table->entries[mm_index(begin, level)];
Andrew Scull80871322018-08-06 12:04:09 +0100281 ptable_addr_t level_end = mm_level_end(begin, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100282 size_t entry_size = mm_entry_size(level);
283 bool commit = flags & MAP_FLAG_COMMIT;
Andrew Walbran6324fc92018-10-03 11:46:43 +0100284 bool unmap = flags & MAP_FLAG_UNMAP;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100285
Andrew Scull265ada92018-07-30 15:19:01 +0100286 /* Cap end so that we don't go over the current level max. */
287 if (end > level_end) {
288 end = level_end;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100289 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100290
291 /* Fill each entry in the table. */
Andrew Scull265ada92018-07-30 15:19:01 +0100292 while (begin < end) {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100293 if (unmap ? !arch_mm_pte_is_present(*pte, level)
294 : arch_mm_pte_is_block(*pte, level) &&
295 arch_mm_pte_attrs(*pte) == attrs) {
296 /*
297 * If the entry is already mapped with the right
298 * attributes, or already absent in the case of
299 * unmapping, no need to do anything; carry on to the
300 * next entry.
301 */
302 } else if ((end - begin) >= entry_size &&
303 (unmap || arch_mm_is_block_allowed(level)) &&
304 (begin & (entry_size - 1)) == 0) {
305 /*
306 * If the entire entry is within the region we want to
307 * map, map/unmap the whole entry.
308 */
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100309 if (commit) {
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000310 pte_t new_pte =
311 unmap ? arch_mm_absent_pte(level)
312 : arch_mm_block_pte(level, pa,
313 attrs);
314 mm_replace_entry(begin, pte, new_pte, level,
315 flags);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100316 }
317 } else {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100318 /*
319 * If the entry is already a subtable get it; otherwise
320 * replace it with an equivalent subtable and get that.
321 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100322 struct mm_page_table *nt =
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000323 mm_populate_table_pte(begin, pte, level, flags);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100324 if (nt == NULL) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100325 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100326 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100327
Andrew Walbran6324fc92018-10-03 11:46:43 +0100328 /*
329 * Recurse to map/unmap the appropriate entries within
330 * the subtable.
331 */
Andrew Scull80871322018-08-06 12:04:09 +0100332 if (!mm_map_level(begin, end, pa, attrs, nt, level - 1,
333 flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100334 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100335 }
Andrew Walbran6324fc92018-10-03 11:46:43 +0100336
337 /*
338 * If the subtable is now empty, replace it with an
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000339 * absent entry at this level. We never need to do
340 * break-before-makes here because we are assigning
341 * an absent value.
Andrew Walbran6324fc92018-10-03 11:46:43 +0100342 */
343 if (commit && unmap &&
344 mm_ptable_is_empty(nt, level - 1)) {
345 pte_t v = *pte;
346 *pte = arch_mm_absent_pte(level);
Andrew Walbran6324fc92018-10-03 11:46:43 +0100347 mm_free_page_pte(v, level);
348 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100349 }
350
Andrew Scull265ada92018-07-30 15:19:01 +0100351 begin = (begin + entry_size) & ~(entry_size - 1);
352 pa = pa_init((pa_addr(pa) + entry_size) & ~(entry_size - 1));
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100353 pte++;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100354 }
355
356 return true;
357}
358
359/**
Andrew Scull80871322018-08-06 12:04:09 +0100360 * Updates the given table such that the given physical address range is mapped
Andrew Sculla6da8342018-11-01 12:29:49 +0000361 * or not mapped into the address space with the architecture-agnostic mode
362 * provided.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100363 */
Andrew Sculla6da8342018-11-01 12:29:49 +0000364static bool mm_ptable_identity_update(struct mm_ptable *t, paddr_t pa_begin,
365 paddr_t pa_end, int mode, bool unmap)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100366{
Andrew Sculla6da8342018-11-01 12:29:49 +0000367 uint64_t attrs = unmap ? 0 : arch_mm_mode_to_attrs(mode);
368 int flags = (mode & MM_MODE_NOSYNC ? MAP_FLAG_NOSYNC : 0) |
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000369 (mode & MM_MODE_NOINVALIDATE ? MAP_FLAG_NOBBM : 0) |
370 (mode & MM_MODE_STAGE1 ? MAP_FLAG_STAGE1 : 0) |
Andrew Sculla6da8342018-11-01 12:29:49 +0000371 (unmap ? MAP_FLAG_UNMAP : 0);
Andrew Sculle9827712018-10-19 14:54:20 +0100372 uint8_t level = arch_mm_max_level(mode);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100373 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100374 ptable_addr_t begin;
375 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100376
Andrew Scull80871322018-08-06 12:04:09 +0100377 pa_begin = arch_mm_clear_pa(pa_begin);
378 begin = pa_addr(pa_begin);
379 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100380
381 /*
382 * Do it in two steps to prevent leaving the table in a halfway updated
383 * state. In such a two-step implementation, the table may be left with
384 * extra internal tables, but no different mapping on failure.
385 */
Andrew Scull80871322018-08-06 12:04:09 +0100386 if (!mm_map_level(begin, end, pa_begin, attrs, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100387 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100388 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100389
Andrew Scull80871322018-08-06 12:04:09 +0100390 mm_map_level(begin, end, pa_begin, attrs, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100391 flags | MAP_FLAG_COMMIT);
392
393 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100394 if (!(mode & MM_MODE_NOINVALIDATE)) {
395 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
396 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100397
398 return true;
399}
400
401/**
Andrew Sculla6da8342018-11-01 12:29:49 +0000402 * Updates the given table such that the given physical address range is mapped
403 * into the address space with the architecture-agnostic mode provided.
404 */
405static bool mm_ptable_identity_map(struct mm_ptable *t, paddr_t pa_begin,
406 paddr_t pa_end, int mode)
407{
408 return mm_ptable_identity_update(t, pa_begin, pa_end, mode, false);
409}
410
411/**
Andrew Scull80871322018-08-06 12:04:09 +0100412 * Updates the given table such that the given physical address range is not
413 * mapped into the address space.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100414 */
Andrew Scull80871322018-08-06 12:04:09 +0100415static bool mm_ptable_unmap(struct mm_ptable *t, paddr_t pa_begin,
416 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100417{
Andrew Sculla6da8342018-11-01 12:29:49 +0000418 return mm_ptable_identity_update(t, pa_begin, pa_end, mode, true);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100419}
420
421/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100422 * Writes the given table to the debug log, calling itself recursively to
423 * write sub-tables.
424 */
Andrew Sculle9827712018-10-19 14:54:20 +0100425static void mm_dump_table_recursive(struct mm_page_table *table, uint8_t level,
Andrew Scull4e5f8142018-10-12 14:37:19 +0100426 int max_level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100427{
428 uint64_t i;
Andrew Scull4e5f8142018-10-12 14:37:19 +0100429 for (i = 0; i < MM_PTE_PER_PAGE; i++) {
430 if (!arch_mm_pte_is_present(table->entries[i], level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100431 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100432 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100433
Andrew Scull4e5f8142018-10-12 14:37:19 +0100434 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i,
435 table->entries[i]);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100436
Andrew Scull4e5f8142018-10-12 14:37:19 +0100437 if (arch_mm_pte_is_table(table->entries[i], level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100438 mm_dump_table_recursive(
Andrew Scull4e5f8142018-10-12 14:37:19 +0100439 mm_page_table_from_pa(arch_mm_table_from_pte(
440 table->entries[i])),
Andrew Scull80871322018-08-06 12:04:09 +0100441 level - 1, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100442 }
443 }
444}
445
446/**
447 * Write the given table to the debug log.
448 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100449void mm_ptable_dump(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100450{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100451 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100452 int max_level = arch_mm_max_level(mode);
Andrew Scull265ada92018-07-30 15:19:01 +0100453 mm_dump_table_recursive(table, max_level, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100454}
455
456/**
Andrew Walbran2400ed22018-09-27 14:45:58 +0100457 * Given that `entry` is a subtable but its entries are all absent, return the
458 * absent entry with which it can be replaced. Note that `entry` will no longer
459 * be valid after calling this function as the subtable will have been freed.
460 */
Andrew Sculle9827712018-10-19 14:54:20 +0100461static pte_t mm_table_pte_to_absent(pte_t entry, uint8_t level)
Andrew Walbran2400ed22018-09-27 14:45:58 +0100462{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100463 struct mm_page_table *table =
464 mm_page_table_from_pa(arch_mm_table_from_pte(entry));
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000465
Andrew Walbran2400ed22018-09-27 14:45:58 +0100466 /*
467 * Free the subtable. This is safe to do directly (rather than
468 * using mm_free_page_pte) because we know by this point that it
469 * doesn't have any subtables of its own.
470 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100471 hfree(table);
Wedson Almeida Filho7c913232018-11-23 18:20:29 +0000472
Andrew Walbran2400ed22018-09-27 14:45:58 +0100473 /* Replace subtable with a single absent entry. */
474 return arch_mm_absent_pte(level);
475}
476
477/**
478 * Given that `entry` is a subtable and its entries are all identical, return
479 * the single block entry with which it can be replaced if possible. Note that
480 * `entry` will no longer be valid after calling this function as the subtable
481 * may have been freed.
482 */
Andrew Sculle9827712018-10-19 14:54:20 +0100483static pte_t mm_table_pte_to_block(pte_t entry, uint8_t level)
Andrew Walbran2400ed22018-09-27 14:45:58 +0100484{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100485 struct mm_page_table *table;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100486 uint64_t block_attrs;
487 uint64_t table_attrs;
488 uint64_t combined_attrs;
489 paddr_t block_address;
490
491 if (!arch_mm_is_block_allowed(level)) {
492 return entry;
493 }
494
Andrew Scull4e5f8142018-10-12 14:37:19 +0100495 table = mm_page_table_from_pa(arch_mm_table_from_pte(entry));
Andrew Walbran2400ed22018-09-27 14:45:58 +0100496 /*
497 * Replace subtable with a single block, with equivalent
498 * attributes.
499 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100500 block_attrs = arch_mm_pte_attrs(table->entries[0]);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100501 table_attrs = arch_mm_pte_attrs(entry);
502 combined_attrs =
503 arch_mm_combine_table_entry_attrs(table_attrs, block_attrs);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100504 block_address = arch_mm_block_from_pte(table->entries[0]);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100505 /* Free the subtable. */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100506 hfree(table);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100507 /*
508 * We can assume that the block is aligned properly
509 * because all virtual addresses are aligned by
510 * definition, and we have a 1-1 mapping from virtual to
511 * physical addresses.
512 */
513 return arch_mm_block_pte(level, block_address, combined_attrs);
514}
515
516/**
517 * Defragment the given ptable entry by recursively replacing any tables with
518 * block or absent entries where possible.
519 */
Andrew Sculle9827712018-10-19 14:54:20 +0100520static pte_t mm_ptable_defrag_entry(pte_t entry, uint8_t level)
Andrew Walbran2400ed22018-09-27 14:45:58 +0100521{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100522 struct mm_page_table *table;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100523 uint64_t i;
524 uint64_t attrs;
525 bool identical_blocks_so_far = true;
526 bool all_absent_so_far = true;
527
528 if (!arch_mm_pte_is_table(entry, level)) {
529 return entry;
530 }
531
Andrew Scull4e5f8142018-10-12 14:37:19 +0100532 table = mm_page_table_from_pa(arch_mm_table_from_pte(entry));
Andrew Walbran2400ed22018-09-27 14:45:58 +0100533
534 /*
535 * Check if all entries are blocks with the same flags or are all
536 * absent.
537 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100538 attrs = arch_mm_pte_attrs(table->entries[0]);
539 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100540 /*
541 * First try to defrag the entry, in case it is a subtable.
542 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100543 table->entries[i] =
544 mm_ptable_defrag_entry(table->entries[i], level - 1);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100545
Andrew Scull4e5f8142018-10-12 14:37:19 +0100546 if (arch_mm_pte_is_present(table->entries[i], level - 1)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100547 all_absent_so_far = false;
548 }
549
550 /*
551 * If the entry is a block, check that the flags are the same as
552 * what we have so far.
553 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100554 if (!arch_mm_pte_is_block(table->entries[i], level - 1) ||
555 arch_mm_pte_attrs(table->entries[i]) != attrs) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100556 identical_blocks_so_far = false;
557 }
558 }
559 if (identical_blocks_so_far) {
560 return mm_table_pte_to_block(entry, level);
561 }
562 if (all_absent_so_far) {
563 return mm_table_pte_to_absent(entry, level);
564 }
565 return entry;
566}
567
568/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100569 * Defragments the given page table by converting page table references to
570 * blocks whenever possible.
571 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100572void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100573{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100574 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Sculle9827712018-10-19 14:54:20 +0100575 uint8_t level = arch_mm_max_level(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100576 uint64_t i;
577
578 /*
579 * Loop through each entry in the table. If it points to another table,
580 * check if that table can be replaced by a block or an absent entry.
581 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100582 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
583 table->entries[i] =
584 mm_ptable_defrag_entry(table->entries[i], level);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100585 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100586}
587
588/**
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100589 * Unmaps the hypervisor pages from the given page table.
590 */
591bool mm_ptable_unmap_hypervisor(struct mm_ptable *t, int mode)
592{
593 /* TODO: If we add pages dynamically, they must be included here too. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100594 return mm_ptable_unmap(t, layout_text_begin(), layout_text_end(),
595 mode) &&
596 mm_ptable_unmap(t, layout_rodata_begin(), layout_rodata_end(),
597 mode) &&
598 mm_ptable_unmap(t, layout_data_begin(), layout_data_end(), mode);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100599}
600
601/**
Andrew Scull80871322018-08-06 12:04:09 +0100602 * Determines if the given address is mapped in the given page table by
603 * recursively traversing all levels of the page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100604 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100605static bool mm_is_mapped_recursive(struct mm_page_table *table,
Andrew Sculle9827712018-10-19 14:54:20 +0100606 ptable_addr_t addr, uint8_t level)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100607{
608 pte_t pte;
Andrew Scull80871322018-08-06 12:04:09 +0100609 ptable_addr_t va_level_end = mm_level_end(addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100610
611 /* It isn't mapped if it doesn't fit in the table. */
Andrew Scull80871322018-08-06 12:04:09 +0100612 if (addr >= va_level_end) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100613 return false;
614 }
615
Andrew Scull4e5f8142018-10-12 14:37:19 +0100616 pte = table->entries[mm_index(addr, level)];
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100617
Andrew Scull78d6fd92018-09-06 15:08:36 +0100618 if (arch_mm_pte_is_block(pte, level)) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100619 return true;
620 }
621
Andrew Scull78d6fd92018-09-06 15:08:36 +0100622 if (arch_mm_pte_is_table(pte, level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100623 return mm_is_mapped_recursive(
Andrew Scull4e5f8142018-10-12 14:37:19 +0100624 mm_page_table_from_pa(arch_mm_table_from_pte(pte)),
625 addr, level - 1);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100626 }
627
Andrew Scull78d6fd92018-09-06 15:08:36 +0100628 /* The entry is not present. */
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100629 return false;
630}
631
632/**
Andrew Scull80871322018-08-06 12:04:09 +0100633 * Determines if the given address is mapped in the given page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100634 */
Andrew Scull80871322018-08-06 12:04:09 +0100635static bool mm_ptable_is_mapped(struct mm_ptable *t, ptable_addr_t addr,
636 int mode)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100637{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100638 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Sculle9827712018-10-19 14:54:20 +0100639 uint8_t level = arch_mm_max_level(mode);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100640
Andrew Scull80871322018-08-06 12:04:09 +0100641 addr = mm_round_down_to_page(addr);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100642
Andrew Scull265ada92018-07-30 15:19:01 +0100643 return mm_is_mapped_recursive(table, addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100644}
645
646/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100647 * Initialises the given page table.
648 */
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100649bool mm_ptable_init(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100650{
651 size_t i;
Andrew Scull4e5f8142018-10-12 14:37:19 +0100652 struct mm_page_table *table;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100653
Andrew Scull4e5f8142018-10-12 14:37:19 +0100654 table = mm_alloc_page_table(mode & MM_MODE_NOSYNC);
655 if (table == NULL) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100656 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100657 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100658
Andrew Scull4e5f8142018-10-12 14:37:19 +0100659 for (i = 0; i < MM_PTE_PER_PAGE; i++) {
660 table->entries[i] = arch_mm_absent_pte(arch_mm_max_level(mode));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100661 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100662
Andrew Scull265ada92018-07-30 15:19:01 +0100663 /* TODO: halloc could return a virtual or physical address if mm not
664 * enabled? */
665 t->table = pa_init((uintpaddr_t)table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100666
667 return true;
668}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100669
670/**
Andrew Scull80871322018-08-06 12:04:09 +0100671 * Updates a VM's page table such that the given physical address range is
672 * mapped in the address space at the corresponding address range in the
Andrew Scullfe636b12018-07-30 14:15:54 +0100673 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100674 */
Andrew Scull80871322018-08-06 12:04:09 +0100675bool mm_vm_identity_map(struct mm_ptable *t, paddr_t begin, paddr_t end,
676 int mode, ipaddr_t *ipa)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100677{
Andrew Scull80871322018-08-06 12:04:09 +0100678 bool success =
679 mm_ptable_identity_map(t, begin, end, mode & ~MM_MODE_STAGE1);
680
681 if (success && ipa != NULL) {
682 *ipa = ipa_from_pa(begin);
683 }
684
685 return success;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100686}
687
688/**
Andrew Scull80871322018-08-06 12:04:09 +0100689 * Updates the VM's table such that the given physical address range is not
690 * mapped in the address space.
691 */
692bool mm_vm_unmap(struct mm_ptable *t, paddr_t begin, paddr_t end, int mode)
693{
694 return mm_ptable_unmap(t, begin, end, mode & ~MM_MODE_STAGE1);
695}
696
697/**
698 * Checks whether the given intermediate physical addess is mapped in the given
699 * page table of a VM.
700 */
701bool mm_vm_is_mapped(struct mm_ptable *t, ipaddr_t ipa, int mode)
702{
703 return mm_ptable_is_mapped(t, ipa_addr(ipa), mode & ~MM_MODE_STAGE1);
704}
705
706/**
707 * Translates an intermediate physical address to a physical address. Addresses
708 * are currently identity mapped so this is a simple type convertion. Returns
709 * true if the address was mapped in the table and the address was converted.
710 */
711bool mm_vm_translate(struct mm_ptable *t, ipaddr_t ipa, paddr_t *pa)
712{
713 bool mapped = mm_vm_is_mapped(t, ipa, 0);
714
715 if (mapped) {
716 *pa = pa_init(ipa_addr(ipa));
717 }
718
719 return mapped;
720}
721
722/**
723 * Updates the hypervisor page table such that the given physical address range
724 * is mapped into the address space at the corresponding address range in the
725 * architecture-agnostic mode provided.
726 */
727void *mm_identity_map(paddr_t begin, paddr_t end, int mode)
728{
729 if (mm_ptable_identity_map(&ptable, begin, end,
730 mode | MM_MODE_STAGE1)) {
Andrew Scull4e5f8142018-10-12 14:37:19 +0100731 return ptr_from_va(va_from_pa(begin));
Andrew Scull80871322018-08-06 12:04:09 +0100732 }
733
734 return NULL;
735}
736
737/**
738 * Updates the hypervisor table such that the given physical address range is
739 * not mapped in the address space.
740 */
741bool mm_unmap(paddr_t begin, paddr_t end, int mode)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100742{
743 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
744}
745
746/**
747 * Initialises memory management for the hypervisor itself.
748 */
749bool mm_init(void)
750{
Andrew Scullcb0a7412018-11-06 17:28:14 +0000751 dlog_nosync("text: 0x%x - 0x%x\n", pa_addr(layout_text_begin()),
752 pa_addr(layout_text_end()));
753 dlog_nosync("rodata: 0x%x - 0x%x\n", pa_addr(layout_rodata_begin()),
754 pa_addr(layout_rodata_end()));
755 dlog_nosync("data: 0x%x - 0x%x\n", pa_addr(layout_data_begin()),
756 pa_addr(layout_data_end()));
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100757
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100758 if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
Andrew Scullcb0a7412018-11-06 17:28:14 +0000759 dlog_nosync("Unable to allocate memory for page table.\n");
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100760 return false;
761 }
762
763 /* Map page for uart. */
764 /* TODO: We may not want to map this. */
Andrew Scull24e032f2018-10-15 17:18:12 +0100765 mm_ptable_identity_map(&ptable, pa_init(PL011_BASE),
766 pa_add(pa_init(PL011_BASE), PAGE_SIZE),
767 MM_MODE_R | MM_MODE_W | MM_MODE_D |
768 MM_MODE_NOSYNC | MM_MODE_STAGE1);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100769
770 /* Map each section. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100771 mm_identity_map(layout_text_begin(), layout_text_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100772 MM_MODE_X | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100773
Andrew Scull5991ec92018-10-08 14:55:02 +0100774 mm_identity_map(layout_rodata_begin(), layout_rodata_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100775 MM_MODE_R | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100776
Andrew Scull5991ec92018-10-08 14:55:02 +0100777 mm_identity_map(layout_data_begin(), layout_data_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100778 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100779
Andrew Scull265ada92018-07-30 15:19:01 +0100780 return arch_mm_init(ptable.table, true);
Wedson Almeida Filho03e767a2018-07-30 15:32:03 +0100781}
782
783bool mm_cpu_init(void)
784{
Andrew Scull265ada92018-07-30 15:19:01 +0100785 return arch_mm_init(ptable.table, false);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100786}
787
788/**
789 * Defragments the hypervisor page table.
790 */
791void mm_defrag(void)
792{
793 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
794}