blob: 14bbac70158b0f2d46f478d2ab9b4ad21ec26fe9 [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 Filhofed69022018-07-11 15:39:12 +010054
Andrew Scull4f170f52018-07-19 12:58:20 +010055/* clang-format on */
56
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +010057static struct mm_ptable ptable;
58
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010059/**
Andrew Scull4e5f8142018-10-12 14:37:19 +010060 * Get the page table from the physical address.
Andrew Walbran2400ed22018-09-27 14:45:58 +010061 */
Andrew Scull4e5f8142018-10-12 14:37:19 +010062static struct mm_page_table *mm_page_table_from_pa(paddr_t pa)
Andrew Walbran2400ed22018-09-27 14:45:58 +010063{
64 return ptr_from_va(va_from_pa(pa));
65}
66
67/**
Andrew Scull80871322018-08-06 12:04:09 +010068 * Rounds an address down to a page boundary.
69 */
70static ptable_addr_t mm_round_down_to_page(ptable_addr_t addr)
71{
72 return addr & ~((ptable_addr_t)(PAGE_SIZE - 1));
73}
74
75/**
76 * Rounds an address up to a page boundary.
77 */
78static ptable_addr_t mm_round_up_to_page(ptable_addr_t addr)
79{
80 return mm_round_down_to_page(addr + PAGE_SIZE - 1);
81}
82
83/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010084 * Calculates the size of the address space represented by a page table entry at
85 * the given level.
86 */
Andrew Sculle9827712018-10-19 14:54:20 +010087static size_t mm_entry_size(uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010088{
Andrew Scull78d6fd92018-09-06 15:08:36 +010089 return UINT64_C(1) << (PAGE_BITS + level * PAGE_LEVEL_BITS);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010090}
91
92/**
Andrew Scull80871322018-08-06 12:04:09 +010093 * For a given address, calculates the maximum (plus one) address that can be
94 * represented by the same table at the given level.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010095 */
Andrew Sculle9827712018-10-19 14:54:20 +010096static ptable_addr_t mm_level_end(ptable_addr_t addr, uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010097{
98 size_t offset = PAGE_BITS + (level + 1) * PAGE_LEVEL_BITS;
Andrew Scull80871322018-08-06 12:04:09 +010099 return ((addr >> offset) + 1) << offset;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100100}
101
102/**
Andrew Scull80871322018-08-06 12:04:09 +0100103 * For a given address, calculates the index at which its entry is stored in a
104 * table at the given level.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100105 */
Andrew Sculle9827712018-10-19 14:54:20 +0100106static size_t mm_index(ptable_addr_t addr, uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100107{
Andrew Scull80871322018-08-06 12:04:09 +0100108 ptable_addr_t v = addr >> (PAGE_BITS + level * PAGE_LEVEL_BITS);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100109 return v & ((UINT64_C(1) << PAGE_LEVEL_BITS) - 1);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100110}
111
112/**
Andrew Scull4e5f8142018-10-12 14:37:19 +0100113 * Allocate a new page table.
114 */
Andrew Scullf2f948e2018-10-22 18:39:28 +0100115static struct mm_page_table *mm_alloc_page_table(bool nosync)
Andrew Scull4e5f8142018-10-12 14:37:19 +0100116{
Andrew Scullf2f948e2018-10-22 18:39:28 +0100117 if (nosync) {
Andrew Scull4e5f8142018-10-12 14:37:19 +0100118 return halloc_aligned_nosync(sizeof(struct mm_page_table),
119 alignof(struct mm_page_table));
120 }
121
122 return halloc_aligned(sizeof(struct mm_page_table),
123 alignof(struct mm_page_table));
124}
125
126/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100127 * Populates the provided page table entry with a reference to another table if
128 * needed, that is, if it does not yet point to another table.
129 *
130 * Returns a pointer to the table the entry now points to.
131 */
Andrew Sculle9827712018-10-19 14:54:20 +0100132static struct mm_page_table *mm_populate_table_pte(pte_t *pte, uint8_t level,
Andrew Scullf2f948e2018-10-22 18:39:28 +0100133 bool nosync)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100134{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100135 struct mm_page_table *ntable;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100136 pte_t v = *pte;
137 pte_t new_pte;
138 size_t i;
139 size_t inc;
Andrew Sculle9827712018-10-19 14:54:20 +0100140 uint8_t level_below = level - 1;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100141
142 /* Just return pointer to table if it's already populated. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100143 if (arch_mm_pte_is_table(v, level)) {
Andrew Scull4e5f8142018-10-12 14:37:19 +0100144 return mm_page_table_from_pa(arch_mm_table_from_pte(v));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100145 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100146
147 /* Allocate a new table. */
Andrew Scullf2f948e2018-10-22 18:39:28 +0100148 ntable = mm_alloc_page_table(nosync);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100149 if (ntable == NULL) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100150 dlog("Failed to allocate memory for page table\n");
151 return NULL;
152 }
153
154 /* Determine template for new pte and its increment. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100155 if (arch_mm_pte_is_block(v, level)) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100156 inc = mm_entry_size(level_below);
157 new_pte = arch_mm_block_pte(level_below,
158 arch_mm_block_from_pte(v),
159 arch_mm_pte_attrs(v));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100160 } else {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100161 inc = 0;
Andrew Walbran1b99f9d2018-10-03 17:54:40 +0100162 new_pte = arch_mm_absent_pte(level_below);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100163 }
164
165 /* Initialise entries in the new table. */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100166 for (i = 0; i < MM_PTE_PER_PAGE; i++) {
167 ntable->entries[i] = new_pte;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100168 new_pte += inc;
169 }
170
171 /*
172 * Ensure initialisation is visible before updating the actual pte, then
173 * update it.
174 */
175 atomic_thread_fence(memory_order_release);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100176 *pte = arch_mm_table_pte(level, pa_init((uintpaddr_t)ntable));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100177
178 return ntable;
179}
180
181/**
182 * Frees all page-table-related memory associated with the given pte at the
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100183 * given level, including any subtables recursively.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100184 */
Andrew Sculle9827712018-10-19 14:54:20 +0100185static void mm_free_page_pte(pte_t pte, uint8_t level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100186{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100187 struct mm_page_table *table;
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100188 uint64_t i;
189
190 if (!arch_mm_pte_is_table(pte, level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100191 return;
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100192 }
193
Andrew Scull4e5f8142018-10-12 14:37:19 +0100194 table = mm_page_table_from_pa(arch_mm_table_from_pte(pte));
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100195 /* Recursively free any subtables. */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100196 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
197 mm_free_page_pte(table->entries[i], level - 1);
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100198 }
199
200 /* Free the table itself. */
201 hfree(table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100202}
203
204/**
Andrew Walbran6324fc92018-10-03 11:46:43 +0100205 * Returns whether all entries in this table are absent.
206 */
Andrew Sculle9827712018-10-19 14:54:20 +0100207static bool mm_ptable_is_empty(struct mm_page_table *table, uint8_t level)
Andrew Walbran6324fc92018-10-03 11:46:43 +0100208{
209 uint64_t i;
210
Andrew Scull4e5f8142018-10-12 14:37:19 +0100211 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
212 if (arch_mm_pte_is_present(table->entries[i], level)) {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100213 return false;
214 }
215 }
216 return true;
217}
218
219/**
Andrew Scull80871322018-08-06 12:04:09 +0100220 * Updates the page table at the given level to map the given address range to a
Andrew Walbran6324fc92018-10-03 11:46:43 +0100221 * physical range using the provided (architecture-specific) attributes. Or if
222 * MAP_FLAG_UNMAP is set, unmap the given range instead.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100223 *
224 * This function calls itself recursively if it needs to update additional
225 * levels, but the recursion is bound by the maximum number of levels in a page
226 * table.
227 */
Andrew Scull80871322018-08-06 12:04:09 +0100228static bool mm_map_level(ptable_addr_t begin, ptable_addr_t end, paddr_t pa,
Andrew Sculle9827712018-10-19 14:54:20 +0100229 uint64_t attrs, struct mm_page_table *table,
230 uint8_t level, int flags)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100231{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100232 pte_t *pte = &table->entries[mm_index(begin, level)];
Andrew Scull80871322018-08-06 12:04:09 +0100233 ptable_addr_t level_end = mm_level_end(begin, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100234 size_t entry_size = mm_entry_size(level);
235 bool commit = flags & MAP_FLAG_COMMIT;
Andrew Scullf2f948e2018-10-22 18:39:28 +0100236 bool nosync = flags & MAP_FLAG_NOSYNC;
Andrew Walbran6324fc92018-10-03 11:46:43 +0100237 bool unmap = flags & MAP_FLAG_UNMAP;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100238
Andrew Scull265ada92018-07-30 15:19:01 +0100239 /* Cap end so that we don't go over the current level max. */
240 if (end > level_end) {
241 end = level_end;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100242 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100243
244 /* Fill each entry in the table. */
Andrew Scull265ada92018-07-30 15:19:01 +0100245 while (begin < end) {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100246 if (unmap ? !arch_mm_pte_is_present(*pte, level)
247 : arch_mm_pte_is_block(*pte, level) &&
248 arch_mm_pte_attrs(*pte) == attrs) {
249 /*
250 * If the entry is already mapped with the right
251 * attributes, or already absent in the case of
252 * unmapping, no need to do anything; carry on to the
253 * next entry.
254 */
255 } else if ((end - begin) >= entry_size &&
256 (unmap || arch_mm_is_block_allowed(level)) &&
257 (begin & (entry_size - 1)) == 0) {
258 /*
259 * If the entire entry is within the region we want to
260 * map, map/unmap the whole entry.
261 */
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100262 if (commit) {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100263 pte_t v = *pte;
Andrew Walbran6324fc92018-10-03 11:46:43 +0100264 *pte = unmap ? arch_mm_absent_pte(level)
265 : arch_mm_block_pte(level, pa,
266 attrs);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100267 /* TODO: Add barrier. How do we ensure this
268 * isn't in use by another CPU? Send IPI? */
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100269 mm_free_page_pte(v, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100270 }
271 } else {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100272 /*
273 * If the entry is already a subtable get it; otherwise
274 * replace it with an equivalent subtable and get that.
275 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100276 struct mm_page_table *nt =
Andrew Scullf2f948e2018-10-22 18:39:28 +0100277 mm_populate_table_pte(pte, level, nosync);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100278 if (nt == NULL) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100279 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100280 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100281
Andrew Walbran6324fc92018-10-03 11:46:43 +0100282 /*
283 * Recurse to map/unmap the appropriate entries within
284 * the subtable.
285 */
Andrew Scull80871322018-08-06 12:04:09 +0100286 if (!mm_map_level(begin, end, pa, attrs, nt, level - 1,
287 flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100288 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100289 }
Andrew Walbran6324fc92018-10-03 11:46:43 +0100290
291 /*
292 * If the subtable is now empty, replace it with an
293 * absent entry at this level.
294 */
295 if (commit && unmap &&
296 mm_ptable_is_empty(nt, level - 1)) {
297 pte_t v = *pte;
298 *pte = arch_mm_absent_pte(level);
299 /* TODO: Add barrier. How do we ensure this
300 * isn't in use by another CPU? Send IPI? */
301 mm_free_page_pte(v, level);
302 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100303 }
304
Andrew Scull265ada92018-07-30 15:19:01 +0100305 begin = (begin + entry_size) & ~(entry_size - 1);
306 pa = pa_init((pa_addr(pa) + entry_size) & ~(entry_size - 1));
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100307 pte++;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100308 }
309
310 return true;
311}
312
313/**
Andrew Scull80871322018-08-06 12:04:09 +0100314 * Invalidates the TLB for the given address range.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100315 */
Andrew Scull80871322018-08-06 12:04:09 +0100316static void mm_invalidate_tlb(ptable_addr_t begin, ptable_addr_t end,
317 bool stage1)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100318{
Andrew Scull7364a8e2018-07-19 15:39:29 +0100319 if (stage1) {
Andrew Scull80871322018-08-06 12:04:09 +0100320 arch_mm_invalidate_stage1_range(va_init(begin), va_init(end));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100321 } else {
Andrew Scull80871322018-08-06 12:04:09 +0100322 arch_mm_invalidate_stage2_range(ipa_init(begin), ipa_init(end));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100323 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100324}
325
326/**
Andrew Scull80871322018-08-06 12:04:09 +0100327 * Updates the given table such that the given physical address range is mapped
328 * into the address space with the corresponding address range in the
329 * architecture-agnostic mode provided.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100330 */
Andrew Scull80871322018-08-06 12:04:09 +0100331static bool mm_ptable_identity_map(struct mm_ptable *t, paddr_t pa_begin,
332 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100333{
334 uint64_t attrs = arch_mm_mode_to_attrs(mode);
Andrew Scullf2f948e2018-10-22 18:39:28 +0100335 int flags = (mode & MM_MODE_NOSYNC) ? MAP_FLAG_NOSYNC : 0;
Andrew Sculle9827712018-10-19 14:54:20 +0100336 uint8_t level = arch_mm_max_level(mode);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100337 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100338 ptable_addr_t begin;
339 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100340
Andrew Scull80871322018-08-06 12:04:09 +0100341 pa_begin = arch_mm_clear_pa(pa_begin);
342 begin = pa_addr(pa_begin);
343 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100344
345 /*
346 * Do it in two steps to prevent leaving the table in a halfway updated
347 * state. In such a two-step implementation, the table may be left with
348 * extra internal tables, but no different mapping on failure.
349 */
Andrew Scull80871322018-08-06 12:04:09 +0100350 if (!mm_map_level(begin, end, pa_begin, attrs, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100351 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100352 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100353
Andrew Scull80871322018-08-06 12:04:09 +0100354 mm_map_level(begin, end, pa_begin, attrs, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100355 flags | MAP_FLAG_COMMIT);
356
357 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100358 if (!(mode & MM_MODE_NOINVALIDATE)) {
359 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
360 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100361
362 return true;
363}
364
365/**
Andrew Scull80871322018-08-06 12:04:09 +0100366 * Updates the given table such that the given physical address range is not
367 * mapped into the address space.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100368 */
Andrew Scull80871322018-08-06 12:04:09 +0100369static bool mm_ptable_unmap(struct mm_ptable *t, paddr_t pa_begin,
370 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100371{
Andrew Scullf2f948e2018-10-22 18:39:28 +0100372 int flags = ((mode & MM_MODE_NOSYNC) ? MAP_FLAG_NOSYNC : 0) |
373 MAP_FLAG_UNMAP;
Andrew Sculle9827712018-10-19 14:54:20 +0100374 uint8_t level = arch_mm_max_level(mode);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100375 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100376 ptable_addr_t begin;
377 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100378
Andrew Scull80871322018-08-06 12:04:09 +0100379 pa_begin = arch_mm_clear_pa(pa_begin);
380 begin = pa_addr(pa_begin);
381 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100382
Andrew Scullfe636b12018-07-30 14:15:54 +0100383 /* Also do updates in two steps, similarly to mm_ptable_identity_map. */
Andrew Scull80871322018-08-06 12:04:09 +0100384 if (!mm_map_level(begin, end, pa_begin, 0, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100385 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100386 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100387
Andrew Scull80871322018-08-06 12:04:09 +0100388 mm_map_level(begin, end, pa_begin, 0, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100389 flags | MAP_FLAG_COMMIT);
390
391 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100392 if (!(mode & MM_MODE_NOINVALIDATE)) {
393 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
394 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100395
396 return true;
397}
398
399/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100400 * Writes the given table to the debug log, calling itself recursively to
401 * write sub-tables.
402 */
Andrew Sculle9827712018-10-19 14:54:20 +0100403static void mm_dump_table_recursive(struct mm_page_table *table, uint8_t level,
Andrew Scull4e5f8142018-10-12 14:37:19 +0100404 int max_level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100405{
406 uint64_t i;
Andrew Scull4e5f8142018-10-12 14:37:19 +0100407 for (i = 0; i < MM_PTE_PER_PAGE; i++) {
408 if (!arch_mm_pte_is_present(table->entries[i], level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100409 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100410 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100411
Andrew Scull4e5f8142018-10-12 14:37:19 +0100412 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i,
413 table->entries[i]);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100414
Andrew Scull4e5f8142018-10-12 14:37:19 +0100415 if (arch_mm_pte_is_table(table->entries[i], level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100416 mm_dump_table_recursive(
Andrew Scull4e5f8142018-10-12 14:37:19 +0100417 mm_page_table_from_pa(arch_mm_table_from_pte(
418 table->entries[i])),
Andrew Scull80871322018-08-06 12:04:09 +0100419 level - 1, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100420 }
421 }
422}
423
424/**
425 * Write the given table to the debug log.
426 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100427void mm_ptable_dump(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100428{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100429 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100430 int max_level = arch_mm_max_level(mode);
Andrew Scull265ada92018-07-30 15:19:01 +0100431 mm_dump_table_recursive(table, max_level, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100432}
433
434/**
Andrew Walbran2400ed22018-09-27 14:45:58 +0100435 * Given that `entry` is a subtable but its entries are all absent, return the
436 * absent entry with which it can be replaced. Note that `entry` will no longer
437 * be valid after calling this function as the subtable will have been freed.
438 */
Andrew Sculle9827712018-10-19 14:54:20 +0100439static pte_t mm_table_pte_to_absent(pte_t entry, uint8_t level)
Andrew Walbran2400ed22018-09-27 14:45:58 +0100440{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100441 struct mm_page_table *table =
442 mm_page_table_from_pa(arch_mm_table_from_pte(entry));
Andrew Walbran2400ed22018-09-27 14:45:58 +0100443 /*
444 * Free the subtable. This is safe to do directly (rather than
445 * using mm_free_page_pte) because we know by this point that it
446 * doesn't have any subtables of its own.
447 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100448 hfree(table);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100449 /* Replace subtable with a single absent entry. */
450 return arch_mm_absent_pte(level);
451}
452
453/**
454 * Given that `entry` is a subtable and its entries are all identical, return
455 * the single block entry with which it can be replaced if possible. Note that
456 * `entry` will no longer be valid after calling this function as the subtable
457 * may have been freed.
458 */
Andrew Sculle9827712018-10-19 14:54:20 +0100459static pte_t mm_table_pte_to_block(pte_t entry, uint8_t level)
Andrew Walbran2400ed22018-09-27 14:45:58 +0100460{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100461 struct mm_page_table *table;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100462 uint64_t block_attrs;
463 uint64_t table_attrs;
464 uint64_t combined_attrs;
465 paddr_t block_address;
466
467 if (!arch_mm_is_block_allowed(level)) {
468 return entry;
469 }
470
Andrew Scull4e5f8142018-10-12 14:37:19 +0100471 table = mm_page_table_from_pa(arch_mm_table_from_pte(entry));
Andrew Walbran2400ed22018-09-27 14:45:58 +0100472 /*
473 * Replace subtable with a single block, with equivalent
474 * attributes.
475 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100476 block_attrs = arch_mm_pte_attrs(table->entries[0]);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100477 table_attrs = arch_mm_pte_attrs(entry);
478 combined_attrs =
479 arch_mm_combine_table_entry_attrs(table_attrs, block_attrs);
Andrew Scull4e5f8142018-10-12 14:37:19 +0100480 block_address = arch_mm_block_from_pte(table->entries[0]);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100481 /* Free the subtable. */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100482 hfree(table);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100483 /*
484 * We can assume that the block is aligned properly
485 * because all virtual addresses are aligned by
486 * definition, and we have a 1-1 mapping from virtual to
487 * physical addresses.
488 */
489 return arch_mm_block_pte(level, block_address, combined_attrs);
490}
491
492/**
493 * Defragment the given ptable entry by recursively replacing any tables with
494 * block or absent entries where possible.
495 */
Andrew Sculle9827712018-10-19 14:54:20 +0100496static pte_t mm_ptable_defrag_entry(pte_t entry, uint8_t level)
Andrew Walbran2400ed22018-09-27 14:45:58 +0100497{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100498 struct mm_page_table *table;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100499 uint64_t i;
500 uint64_t attrs;
501 bool identical_blocks_so_far = true;
502 bool all_absent_so_far = true;
503
504 if (!arch_mm_pte_is_table(entry, level)) {
505 return entry;
506 }
507
Andrew Scull4e5f8142018-10-12 14:37:19 +0100508 table = mm_page_table_from_pa(arch_mm_table_from_pte(entry));
Andrew Walbran2400ed22018-09-27 14:45:58 +0100509
510 /*
511 * Check if all entries are blocks with the same flags or are all
512 * absent.
513 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100514 attrs = arch_mm_pte_attrs(table->entries[0]);
515 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100516 /*
517 * First try to defrag the entry, in case it is a subtable.
518 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100519 table->entries[i] =
520 mm_ptable_defrag_entry(table->entries[i], level - 1);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100521
Andrew Scull4e5f8142018-10-12 14:37:19 +0100522 if (arch_mm_pte_is_present(table->entries[i], level - 1)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100523 all_absent_so_far = false;
524 }
525
526 /*
527 * If the entry is a block, check that the flags are the same as
528 * what we have so far.
529 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100530 if (!arch_mm_pte_is_block(table->entries[i], level - 1) ||
531 arch_mm_pte_attrs(table->entries[i]) != attrs) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100532 identical_blocks_so_far = false;
533 }
534 }
535 if (identical_blocks_so_far) {
536 return mm_table_pte_to_block(entry, level);
537 }
538 if (all_absent_so_far) {
539 return mm_table_pte_to_absent(entry, level);
540 }
541 return entry;
542}
543
544/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100545 * Defragments the given page table by converting page table references to
546 * blocks whenever possible.
547 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100548void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100549{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100550 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Sculle9827712018-10-19 14:54:20 +0100551 uint8_t level = arch_mm_max_level(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100552 uint64_t i;
553
554 /*
555 * Loop through each entry in the table. If it points to another table,
556 * check if that table can be replaced by a block or an absent entry.
557 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100558 for (i = 0; i < MM_PTE_PER_PAGE; ++i) {
559 table->entries[i] =
560 mm_ptable_defrag_entry(table->entries[i], level);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100561 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100562}
563
564/**
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100565 * Unmaps the hypervisor pages from the given page table.
566 */
567bool mm_ptable_unmap_hypervisor(struct mm_ptable *t, int mode)
568{
569 /* TODO: If we add pages dynamically, they must be included here too. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100570 return mm_ptable_unmap(t, layout_text_begin(), layout_text_end(),
571 mode) &&
572 mm_ptable_unmap(t, layout_rodata_begin(), layout_rodata_end(),
573 mode) &&
574 mm_ptable_unmap(t, layout_data_begin(), layout_data_end(), mode);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100575}
576
577/**
Andrew Scull80871322018-08-06 12:04:09 +0100578 * Determines if the given address is mapped in the given page table by
579 * recursively traversing all levels of the page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100580 */
Andrew Scull4e5f8142018-10-12 14:37:19 +0100581static bool mm_is_mapped_recursive(struct mm_page_table *table,
Andrew Sculle9827712018-10-19 14:54:20 +0100582 ptable_addr_t addr, uint8_t level)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100583{
584 pte_t pte;
Andrew Scull80871322018-08-06 12:04:09 +0100585 ptable_addr_t va_level_end = mm_level_end(addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100586
587 /* It isn't mapped if it doesn't fit in the table. */
Andrew Scull80871322018-08-06 12:04:09 +0100588 if (addr >= va_level_end) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100589 return false;
590 }
591
Andrew Scull4e5f8142018-10-12 14:37:19 +0100592 pte = table->entries[mm_index(addr, level)];
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100593
Andrew Scull78d6fd92018-09-06 15:08:36 +0100594 if (arch_mm_pte_is_block(pte, level)) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100595 return true;
596 }
597
Andrew Scull78d6fd92018-09-06 15:08:36 +0100598 if (arch_mm_pte_is_table(pte, level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100599 return mm_is_mapped_recursive(
Andrew Scull4e5f8142018-10-12 14:37:19 +0100600 mm_page_table_from_pa(arch_mm_table_from_pte(pte)),
601 addr, level - 1);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100602 }
603
Andrew Scull78d6fd92018-09-06 15:08:36 +0100604 /* The entry is not present. */
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100605 return false;
606}
607
608/**
Andrew Scull80871322018-08-06 12:04:09 +0100609 * Determines if the given address is mapped in the given page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100610 */
Andrew Scull80871322018-08-06 12:04:09 +0100611static bool mm_ptable_is_mapped(struct mm_ptable *t, ptable_addr_t addr,
612 int mode)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100613{
Andrew Scull4e5f8142018-10-12 14:37:19 +0100614 struct mm_page_table *table = mm_page_table_from_pa(t->table);
Andrew Sculle9827712018-10-19 14:54:20 +0100615 uint8_t level = arch_mm_max_level(mode);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100616
Andrew Scull80871322018-08-06 12:04:09 +0100617 addr = mm_round_down_to_page(addr);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100618
Andrew Scull265ada92018-07-30 15:19:01 +0100619 return mm_is_mapped_recursive(table, addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100620}
621
622/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100623 * Initialises the given page table.
624 */
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100625bool mm_ptable_init(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100626{
627 size_t i;
Andrew Scull4e5f8142018-10-12 14:37:19 +0100628 struct mm_page_table *table;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100629
Andrew Scull4e5f8142018-10-12 14:37:19 +0100630 table = mm_alloc_page_table(mode & MM_MODE_NOSYNC);
631 if (table == NULL) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100632 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100633 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100634
Andrew Scull4e5f8142018-10-12 14:37:19 +0100635 for (i = 0; i < MM_PTE_PER_PAGE; i++) {
636 table->entries[i] = arch_mm_absent_pte(arch_mm_max_level(mode));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100637 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100638
Andrew Scull265ada92018-07-30 15:19:01 +0100639 /* TODO: halloc could return a virtual or physical address if mm not
640 * enabled? */
641 t->table = pa_init((uintpaddr_t)table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100642
643 return true;
644}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100645
646/**
Andrew Scull80871322018-08-06 12:04:09 +0100647 * Updates a VM's page table such that the given physical address range is
648 * mapped in the address space at the corresponding address range in the
Andrew Scullfe636b12018-07-30 14:15:54 +0100649 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100650 */
Andrew Scull80871322018-08-06 12:04:09 +0100651bool mm_vm_identity_map(struct mm_ptable *t, paddr_t begin, paddr_t end,
652 int mode, ipaddr_t *ipa)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100653{
Andrew Scull80871322018-08-06 12:04:09 +0100654 bool success =
655 mm_ptable_identity_map(t, begin, end, mode & ~MM_MODE_STAGE1);
656
657 if (success && ipa != NULL) {
658 *ipa = ipa_from_pa(begin);
659 }
660
661 return success;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100662}
663
664/**
Andrew Scull80871322018-08-06 12:04:09 +0100665 * Updates the VM's table such that the given physical address range is not
666 * mapped in the address space.
667 */
668bool mm_vm_unmap(struct mm_ptable *t, paddr_t begin, paddr_t end, int mode)
669{
670 return mm_ptable_unmap(t, begin, end, mode & ~MM_MODE_STAGE1);
671}
672
673/**
674 * Checks whether the given intermediate physical addess is mapped in the given
675 * page table of a VM.
676 */
677bool mm_vm_is_mapped(struct mm_ptable *t, ipaddr_t ipa, int mode)
678{
679 return mm_ptable_is_mapped(t, ipa_addr(ipa), mode & ~MM_MODE_STAGE1);
680}
681
682/**
683 * Translates an intermediate physical address to a physical address. Addresses
684 * are currently identity mapped so this is a simple type convertion. Returns
685 * true if the address was mapped in the table and the address was converted.
686 */
687bool mm_vm_translate(struct mm_ptable *t, ipaddr_t ipa, paddr_t *pa)
688{
689 bool mapped = mm_vm_is_mapped(t, ipa, 0);
690
691 if (mapped) {
692 *pa = pa_init(ipa_addr(ipa));
693 }
694
695 return mapped;
696}
697
698/**
699 * Updates the hypervisor page table such that the given physical address range
700 * is mapped into the address space at the corresponding address range in the
701 * architecture-agnostic mode provided.
702 */
703void *mm_identity_map(paddr_t begin, paddr_t end, int mode)
704{
705 if (mm_ptable_identity_map(&ptable, begin, end,
706 mode | MM_MODE_STAGE1)) {
Andrew Scull4e5f8142018-10-12 14:37:19 +0100707 return ptr_from_va(va_from_pa(begin));
Andrew Scull80871322018-08-06 12:04:09 +0100708 }
709
710 return NULL;
711}
712
713/**
714 * Updates the hypervisor table such that the given physical address range is
715 * not mapped in the address space.
716 */
717bool mm_unmap(paddr_t begin, paddr_t end, int mode)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100718{
719 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
720}
721
722/**
723 * Initialises memory management for the hypervisor itself.
724 */
725bool mm_init(void)
726{
Andrew Scull5991ec92018-10-08 14:55:02 +0100727 dlog("text: 0x%x - 0x%x\n", pa_addr(layout_text_begin()),
728 pa_addr(layout_text_end()));
729 dlog("rodata: 0x%x - 0x%x\n", pa_addr(layout_rodata_begin()),
730 pa_addr(layout_rodata_end()));
731 dlog("data: 0x%x - 0x%x\n", pa_addr(layout_data_begin()),
732 pa_addr(layout_data_end()));
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100733
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100734 if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100735 dlog("Unable to allocate memory for page table.\n");
736 return false;
737 }
738
739 /* Map page for uart. */
740 /* TODO: We may not want to map this. */
Andrew Scull24e032f2018-10-15 17:18:12 +0100741 mm_ptable_identity_map(&ptable, pa_init(PL011_BASE),
742 pa_add(pa_init(PL011_BASE), PAGE_SIZE),
743 MM_MODE_R | MM_MODE_W | MM_MODE_D |
744 MM_MODE_NOSYNC | MM_MODE_STAGE1);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100745
746 /* Map each section. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100747 mm_identity_map(layout_text_begin(), layout_text_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100748 MM_MODE_X | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100749
Andrew Scull5991ec92018-10-08 14:55:02 +0100750 mm_identity_map(layout_rodata_begin(), layout_rodata_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100751 MM_MODE_R | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100752
Andrew Scull5991ec92018-10-08 14:55:02 +0100753 mm_identity_map(layout_data_begin(), layout_data_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100754 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100755
Andrew Scull265ada92018-07-30 15:19:01 +0100756 return arch_mm_init(ptable.table, true);
Wedson Almeida Filho03e767a2018-07-30 15:32:03 +0100757}
758
759bool mm_cpu_init(void)
760{
Andrew Scull265ada92018-07-30 15:19:01 +0100761 return arch_mm_init(ptable.table, false);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100762}
763
764/**
765 * Defragments the hypervisor page table.
766 */
767void mm_defrag(void)
768{
769 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
770}