blob: 210fa8e72dec1b063bafc2a097759b57397f6ba5 [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
37/* For stage 2, the input is an intermediate physical addresses rather than a
38 * virtual address so: */
39static_assert(
40 sizeof(ptable_addr_t) == sizeof(uintpaddr_t),
41 "Currently, the same code manages the stage 1 and stage 2 page tables "
42 "which only works if the virtual and intermediate physical addresses "
43 "are the same size. It looks like that assumption might not be holding "
44 "so we need to check that everything is going to be ok.");
45
Andrew Scull4f170f52018-07-19 12:58:20 +010046/* Keep macro alignment */
47/* clang-format off */
48
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010049#define MAP_FLAG_SYNC 0x01
50#define MAP_FLAG_COMMIT 0x02
Andrew Walbran6324fc92018-10-03 11:46:43 +010051#define MAP_FLAG_UNMAP 0x04
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010052
Andrew Scull4f170f52018-07-19 12:58:20 +010053/* clang-format on */
54
Andrew Walbran2400ed22018-09-27 14:45:58 +010055#define NUM_ENTRIES (PAGE_SIZE / sizeof(pte_t))
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 Walbran2400ed22018-09-27 14:45:58 +010060 * Casts a physical address to a pointer. This assumes that it is mapped (to the
61 * same address), so should only be used within the mm code.
62 */
63static inline void *ptr_from_pa(paddr_t pa)
64{
65 return ptr_from_va(va_from_pa(pa));
66}
67
68/**
Andrew Scull80871322018-08-06 12:04:09 +010069 * Rounds an address down to a page boundary.
70 */
71static ptable_addr_t mm_round_down_to_page(ptable_addr_t addr)
72{
73 return addr & ~((ptable_addr_t)(PAGE_SIZE - 1));
74}
75
76/**
77 * Rounds an address up to a page boundary.
78 */
79static ptable_addr_t mm_round_up_to_page(ptable_addr_t addr)
80{
81 return mm_round_down_to_page(addr + PAGE_SIZE - 1);
82}
83
84/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010085 * Calculates the size of the address space represented by a page table entry at
86 * the given level.
87 */
88static inline size_t mm_entry_size(int level)
89{
Andrew Scull78d6fd92018-09-06 15:08:36 +010090 return UINT64_C(1) << (PAGE_BITS + level * PAGE_LEVEL_BITS);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010091}
92
93/**
Andrew Scull80871322018-08-06 12:04:09 +010094 * For a given address, calculates the maximum (plus one) address that can be
95 * represented by the same table at the given level.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010096 */
Andrew Scull80871322018-08-06 12:04:09 +010097static inline ptable_addr_t mm_level_end(ptable_addr_t addr, int level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010098{
99 size_t offset = PAGE_BITS + (level + 1) * PAGE_LEVEL_BITS;
Andrew Scull80871322018-08-06 12:04:09 +0100100 return ((addr >> offset) + 1) << offset;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100101}
102
103/**
Andrew Scull80871322018-08-06 12:04:09 +0100104 * For a given address, calculates the index at which its entry is stored in a
105 * table at the given level.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100106 */
Andrew Scull80871322018-08-06 12:04:09 +0100107static inline size_t mm_index(ptable_addr_t addr, int level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100108{
Andrew Scull80871322018-08-06 12:04:09 +0100109 ptable_addr_t v = addr >> (PAGE_BITS + level * PAGE_LEVEL_BITS);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100110 return v & ((UINT64_C(1) << PAGE_LEVEL_BITS) - 1);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100111}
112
113/**
114 * Populates the provided page table entry with a reference to another table if
115 * needed, that is, if it does not yet point to another table.
116 *
117 * Returns a pointer to the table the entry now points to.
118 */
119static pte_t *mm_populate_table_pte(pte_t *pte, int level, bool sync_alloc)
120{
121 pte_t *ntable;
122 pte_t v = *pte;
123 pte_t new_pte;
124 size_t i;
125 size_t inc;
Andrew Walbran1b99f9d2018-10-03 17:54:40 +0100126 int level_below = level - 1;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100127
128 /* Just return pointer to table if it's already populated. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100129 if (arch_mm_pte_is_table(v, level)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100130 return ptr_from_pa(arch_mm_table_from_pte(v));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100131 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100132
133 /* Allocate a new table. */
134 ntable = (sync_alloc ? halloc_aligned : halloc_aligned_nosync)(
Andrew Scull4f170f52018-07-19 12:58:20 +0100135 PAGE_SIZE, PAGE_SIZE);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100136 if (!ntable) {
137 dlog("Failed to allocate memory for page table\n");
138 return NULL;
139 }
140
141 /* Determine template for new pte and its increment. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100142 if (arch_mm_pte_is_block(v, level)) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100143 inc = mm_entry_size(level_below);
144 new_pte = arch_mm_block_pte(level_below,
145 arch_mm_block_from_pte(v),
146 arch_mm_pte_attrs(v));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100147 } else {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100148 inc = 0;
Andrew Walbran1b99f9d2018-10-03 17:54:40 +0100149 new_pte = arch_mm_absent_pte(level_below);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100150 }
151
152 /* Initialise entries in the new table. */
Andrew Walbran2400ed22018-09-27 14:45:58 +0100153 for (i = 0; i < NUM_ENTRIES; i++) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100154 ntable[i] = new_pte;
155 new_pte += inc;
156 }
157
158 /*
159 * Ensure initialisation is visible before updating the actual pte, then
160 * update it.
161 */
162 atomic_thread_fence(memory_order_release);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100163 *pte = arch_mm_table_pte(level, pa_init((uintpaddr_t)ntable));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100164
165 return ntable;
166}
167
168/**
169 * Frees all page-table-related memory associated with the given pte at the
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100170 * given level, including any subtables recursively.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100171 */
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100172static void mm_free_page_pte(pte_t pte, int level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100173{
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100174 pte_t *table;
175 uint64_t i;
176
177 if (!arch_mm_pte_is_table(pte, level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100178 return;
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100179 }
180
Andrew Walbran2400ed22018-09-27 14:45:58 +0100181 table = ptr_from_pa(arch_mm_table_from_pte(pte));
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100182 /* Recursively free any subtables. */
Andrew Walbran2400ed22018-09-27 14:45:58 +0100183 for (i = 0; i < NUM_ENTRIES; ++i) {
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100184 mm_free_page_pte(table[i], level - 1);
185 }
186
187 /* Free the table itself. */
188 hfree(table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100189}
190
191/**
Andrew Walbran6324fc92018-10-03 11:46:43 +0100192 * Returns whether all entries in this table are absent.
193 */
194static bool mm_ptable_is_empty(pte_t *table, int level)
195{
196 uint64_t i;
197
198 for (i = 0; i < NUM_ENTRIES; ++i) {
199 if (arch_mm_pte_is_present(table[i], level)) {
200 return false;
201 }
202 }
203 return true;
204}
205
206/**
Andrew Scull80871322018-08-06 12:04:09 +0100207 * Updates the page table at the given level to map the given address range to a
Andrew Walbran6324fc92018-10-03 11:46:43 +0100208 * physical range using the provided (architecture-specific) attributes. Or if
209 * MAP_FLAG_UNMAP is set, unmap the given range instead.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100210 *
211 * This function calls itself recursively if it needs to update additional
212 * levels, but the recursion is bound by the maximum number of levels in a page
213 * table.
214 */
Andrew Scull80871322018-08-06 12:04:09 +0100215static bool mm_map_level(ptable_addr_t begin, ptable_addr_t end, paddr_t pa,
Andrew Scull265ada92018-07-30 15:19:01 +0100216 uint64_t attrs, pte_t *table, int level, int flags)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100217{
Andrew Scullf3d45592018-09-20 14:30:22 +0100218 pte_t *pte = &table[mm_index(begin, level)];
Andrew Scull80871322018-08-06 12:04:09 +0100219 ptable_addr_t level_end = mm_level_end(begin, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100220 size_t entry_size = mm_entry_size(level);
221 bool commit = flags & MAP_FLAG_COMMIT;
222 bool sync = flags & MAP_FLAG_SYNC;
Andrew Walbran6324fc92018-10-03 11:46:43 +0100223 bool unmap = flags & MAP_FLAG_UNMAP;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100224
Andrew Scull265ada92018-07-30 15:19:01 +0100225 /* Cap end so that we don't go over the current level max. */
226 if (end > level_end) {
227 end = level_end;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100228 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100229
230 /* Fill each entry in the table. */
Andrew Scull265ada92018-07-30 15:19:01 +0100231 while (begin < end) {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100232 if (unmap ? !arch_mm_pte_is_present(*pte, level)
233 : arch_mm_pte_is_block(*pte, level) &&
234 arch_mm_pte_attrs(*pte) == attrs) {
235 /*
236 * If the entry is already mapped with the right
237 * attributes, or already absent in the case of
238 * unmapping, no need to do anything; carry on to the
239 * next entry.
240 */
241 } else if ((end - begin) >= entry_size &&
242 (unmap || arch_mm_is_block_allowed(level)) &&
243 (begin & (entry_size - 1)) == 0) {
244 /*
245 * If the entire entry is within the region we want to
246 * map, map/unmap the whole entry.
247 */
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100248 if (commit) {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100249 pte_t v = *pte;
Andrew Walbran6324fc92018-10-03 11:46:43 +0100250 *pte = unmap ? arch_mm_absent_pte(level)
251 : arch_mm_block_pte(level, pa,
252 attrs);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100253 /* TODO: Add barrier. How do we ensure this
254 * isn't in use by another CPU? Send IPI? */
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100255 mm_free_page_pte(v, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100256 }
257 } else {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100258 /*
259 * If the entry is already a subtable get it; otherwise
260 * replace it with an equivalent subtable and get that.
261 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100262 pte_t *nt = mm_populate_table_pte(pte, level, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100263 if (!nt) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100264 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100265 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100266
Andrew Walbran6324fc92018-10-03 11:46:43 +0100267 /*
268 * Recurse to map/unmap the appropriate entries within
269 * the subtable.
270 */
Andrew Scull80871322018-08-06 12:04:09 +0100271 if (!mm_map_level(begin, end, pa, attrs, nt, level - 1,
272 flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100273 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100274 }
Andrew Walbran6324fc92018-10-03 11:46:43 +0100275
276 /*
277 * If the subtable is now empty, replace it with an
278 * absent entry at this level.
279 */
280 if (commit && unmap &&
281 mm_ptable_is_empty(nt, level - 1)) {
282 pte_t v = *pte;
283 *pte = arch_mm_absent_pte(level);
284 /* TODO: Add barrier. How do we ensure this
285 * isn't in use by another CPU? Send IPI? */
286 mm_free_page_pte(v, level);
287 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100288 }
289
Andrew Scull265ada92018-07-30 15:19:01 +0100290 begin = (begin + entry_size) & ~(entry_size - 1);
291 pa = pa_init((pa_addr(pa) + entry_size) & ~(entry_size - 1));
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100292 pte++;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100293 }
294
295 return true;
296}
297
298/**
Andrew Scull80871322018-08-06 12:04:09 +0100299 * Invalidates the TLB for the given address range.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100300 */
Andrew Scull80871322018-08-06 12:04:09 +0100301static void mm_invalidate_tlb(ptable_addr_t begin, ptable_addr_t end,
302 bool stage1)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100303{
Andrew Scull7364a8e2018-07-19 15:39:29 +0100304 if (stage1) {
Andrew Scull80871322018-08-06 12:04:09 +0100305 arch_mm_invalidate_stage1_range(va_init(begin), va_init(end));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100306 } else {
Andrew Scull80871322018-08-06 12:04:09 +0100307 arch_mm_invalidate_stage2_range(ipa_init(begin), ipa_init(end));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100308 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100309}
310
311/**
Andrew Scull80871322018-08-06 12:04:09 +0100312 * Updates the given table such that the given physical address range is mapped
313 * into the address space with the corresponding address range in the
314 * architecture-agnostic mode provided.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100315 */
Andrew Scull80871322018-08-06 12:04:09 +0100316static bool mm_ptable_identity_map(struct mm_ptable *t, paddr_t pa_begin,
317 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100318{
319 uint64_t attrs = arch_mm_mode_to_attrs(mode);
320 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100321 int level = arch_mm_max_level(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100322 pte_t *table = ptr_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100323 ptable_addr_t begin;
324 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100325
Andrew Scull80871322018-08-06 12:04:09 +0100326 pa_begin = arch_mm_clear_pa(pa_begin);
327 begin = pa_addr(pa_begin);
328 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100329
330 /*
331 * Do it in two steps to prevent leaving the table in a halfway updated
332 * state. In such a two-step implementation, the table may be left with
333 * extra internal tables, but no different mapping on failure.
334 */
Andrew Scull80871322018-08-06 12:04:09 +0100335 if (!mm_map_level(begin, end, pa_begin, attrs, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100336 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100337 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100338
Andrew Scull80871322018-08-06 12:04:09 +0100339 mm_map_level(begin, end, pa_begin, attrs, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100340 flags | MAP_FLAG_COMMIT);
341
342 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100343 if (!(mode & MM_MODE_NOINVALIDATE)) {
344 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
345 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100346
347 return true;
348}
349
350/**
Andrew Scull80871322018-08-06 12:04:09 +0100351 * Updates the given table such that the given physical address range is not
352 * mapped into the address space.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100353 */
Andrew Scull80871322018-08-06 12:04:09 +0100354static bool mm_ptable_unmap(struct mm_ptable *t, paddr_t pa_begin,
355 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100356{
Andrew Walbran6324fc92018-10-03 11:46:43 +0100357 int flags =
358 ((mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC) | MAP_FLAG_UNMAP;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100359 int level = arch_mm_max_level(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100360 pte_t *table = ptr_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100361 ptable_addr_t begin;
362 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100363
Andrew Scull80871322018-08-06 12:04:09 +0100364 pa_begin = arch_mm_clear_pa(pa_begin);
365 begin = pa_addr(pa_begin);
366 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100367
Andrew Scullfe636b12018-07-30 14:15:54 +0100368 /* Also do updates in two steps, similarly to mm_ptable_identity_map. */
Andrew Scull80871322018-08-06 12:04:09 +0100369 if (!mm_map_level(begin, end, pa_begin, 0, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100370 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100371 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100372
Andrew Scull80871322018-08-06 12:04:09 +0100373 mm_map_level(begin, end, pa_begin, 0, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100374 flags | MAP_FLAG_COMMIT);
375
376 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100377 if (!(mode & MM_MODE_NOINVALIDATE)) {
378 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
379 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100380
381 return true;
382}
383
384/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100385 * Writes the given table to the debug log, calling itself recursively to
386 * write sub-tables.
387 */
388static void mm_dump_table_recursive(pte_t *table, int level, int max_level)
389{
390 uint64_t i;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100391 for (i = 0; i < NUM_ENTRIES; i++) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100392 if (!arch_mm_pte_is_present(table[i], level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100393 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100394 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100395
396 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i, table[i]);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100397
Andrew Scull78d6fd92018-09-06 15:08:36 +0100398 if (arch_mm_pte_is_table(table[i], level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100399 mm_dump_table_recursive(
400 ptr_from_va(va_from_pa(
Andrew Scull78d6fd92018-09-06 15:08:36 +0100401 arch_mm_table_from_pte(table[i]))),
Andrew Scull80871322018-08-06 12:04:09 +0100402 level - 1, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100403 }
404 }
405}
406
407/**
408 * Write the given table to the debug log.
409 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100410void mm_ptable_dump(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100411{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100412 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100413 int max_level = arch_mm_max_level(mode);
Andrew Scull265ada92018-07-30 15:19:01 +0100414 mm_dump_table_recursive(table, max_level, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100415}
416
417/**
Andrew Walbran2400ed22018-09-27 14:45:58 +0100418 * Given that `entry` is a subtable but its entries are all absent, return the
419 * absent entry with which it can be replaced. Note that `entry` will no longer
420 * be valid after calling this function as the subtable will have been freed.
421 */
422static pte_t mm_table_pte_to_absent(pte_t entry, int level)
423{
424 pte_t *subtable = ptr_from_pa(arch_mm_table_from_pte(entry));
425 /*
426 * Free the subtable. This is safe to do directly (rather than
427 * using mm_free_page_pte) because we know by this point that it
428 * doesn't have any subtables of its own.
429 */
430 hfree(subtable);
431 /* Replace subtable with a single absent entry. */
432 return arch_mm_absent_pte(level);
433}
434
435/**
436 * Given that `entry` is a subtable and its entries are all identical, return
437 * the single block entry with which it can be replaced if possible. Note that
438 * `entry` will no longer be valid after calling this function as the subtable
439 * may have been freed.
440 */
441static pte_t mm_table_pte_to_block(pte_t entry, int level)
442{
443 pte_t *subtable;
444 uint64_t block_attrs;
445 uint64_t table_attrs;
446 uint64_t combined_attrs;
447 paddr_t block_address;
448
449 if (!arch_mm_is_block_allowed(level)) {
450 return entry;
451 }
452
453 subtable = ptr_from_pa(arch_mm_table_from_pte(entry));
454 /*
455 * Replace subtable with a single block, with equivalent
456 * attributes.
457 */
458 block_attrs = arch_mm_pte_attrs(subtable[0]);
459 table_attrs = arch_mm_pte_attrs(entry);
460 combined_attrs =
461 arch_mm_combine_table_entry_attrs(table_attrs, block_attrs);
462 block_address = arch_mm_block_from_pte(subtable[0]);
463 /* Free the subtable. */
464 hfree(subtable);
465 /*
466 * We can assume that the block is aligned properly
467 * because all virtual addresses are aligned by
468 * definition, and we have a 1-1 mapping from virtual to
469 * physical addresses.
470 */
471 return arch_mm_block_pte(level, block_address, combined_attrs);
472}
473
474/**
475 * Defragment the given ptable entry by recursively replacing any tables with
476 * block or absent entries where possible.
477 */
478static pte_t mm_ptable_defrag_entry(pte_t entry, int level)
479{
480 pte_t *table;
481 uint64_t i;
482 uint64_t attrs;
483 bool identical_blocks_so_far = true;
484 bool all_absent_so_far = true;
485
486 if (!arch_mm_pte_is_table(entry, level)) {
487 return entry;
488 }
489
490 table = ptr_from_pa(arch_mm_table_from_pte(entry));
491
492 /*
493 * Check if all entries are blocks with the same flags or are all
494 * absent.
495 */
496 attrs = arch_mm_pte_attrs(table[0]);
497 for (i = 0; i < NUM_ENTRIES; ++i) {
498 /*
499 * First try to defrag the entry, in case it is a subtable.
500 */
501 table[i] = mm_ptable_defrag_entry(table[i], level - 1);
502
503 if (arch_mm_pte_is_present(table[i], level - 1)) {
504 all_absent_so_far = false;
505 }
506
507 /*
508 * If the entry is a block, check that the flags are the same as
509 * what we have so far.
510 */
511 if (!arch_mm_pte_is_block(table[i], level - 1) ||
512 arch_mm_pte_attrs(table[i]) != attrs) {
513 identical_blocks_so_far = false;
514 }
515 }
516 if (identical_blocks_so_far) {
517 return mm_table_pte_to_block(entry, level);
518 }
519 if (all_absent_so_far) {
520 return mm_table_pte_to_absent(entry, level);
521 }
522 return entry;
523}
524
525/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100526 * Defragments the given page table by converting page table references to
527 * blocks whenever possible.
528 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100529void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100530{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100531 pte_t *table = ptr_from_pa(t->table);
532 int level = arch_mm_max_level(mode);
533 uint64_t i;
534
535 /*
536 * Loop through each entry in the table. If it points to another table,
537 * check if that table can be replaced by a block or an absent entry.
538 */
539 for (i = 0; i < NUM_ENTRIES; ++i) {
540 table[i] = mm_ptable_defrag_entry(table[i], level);
541 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100542}
543
544/**
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100545 * Unmaps the hypervisor pages from the given page table.
546 */
547bool mm_ptable_unmap_hypervisor(struct mm_ptable *t, int mode)
548{
549 /* TODO: If we add pages dynamically, they must be included here too. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100550 return mm_ptable_unmap(t, layout_text_begin(), layout_text_end(),
551 mode) &&
552 mm_ptable_unmap(t, layout_rodata_begin(), layout_rodata_end(),
553 mode) &&
554 mm_ptable_unmap(t, layout_data_begin(), layout_data_end(), mode);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100555}
556
557/**
Andrew Scull80871322018-08-06 12:04:09 +0100558 * Determines if the given address is mapped in the given page table by
559 * recursively traversing all levels of the page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100560 */
Andrew Scull80871322018-08-06 12:04:09 +0100561static bool mm_is_mapped_recursive(const pte_t *table, ptable_addr_t addr,
562 int level)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100563{
564 pte_t pte;
Andrew Scull80871322018-08-06 12:04:09 +0100565 ptable_addr_t va_level_end = mm_level_end(addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100566
567 /* It isn't mapped if it doesn't fit in the table. */
Andrew Scull80871322018-08-06 12:04:09 +0100568 if (addr >= va_level_end) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100569 return false;
570 }
571
572 pte = table[mm_index(addr, level)];
573
Andrew Scull78d6fd92018-09-06 15:08:36 +0100574 if (arch_mm_pte_is_block(pte, level)) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100575 return true;
576 }
577
Andrew Scull78d6fd92018-09-06 15:08:36 +0100578 if (arch_mm_pte_is_table(pte, level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100579 return mm_is_mapped_recursive(
Andrew Walbran2400ed22018-09-27 14:45:58 +0100580 ptr_from_pa(arch_mm_table_from_pte(pte)), addr,
581 level - 1);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100582 }
583
Andrew Scull78d6fd92018-09-06 15:08:36 +0100584 /* The entry is not present. */
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100585 return false;
586}
587
588/**
Andrew Scull80871322018-08-06 12:04:09 +0100589 * Determines if the given address is mapped in the given page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100590 */
Andrew Scull80871322018-08-06 12:04:09 +0100591static bool mm_ptable_is_mapped(struct mm_ptable *t, ptable_addr_t addr,
592 int mode)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100593{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100594 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100595 int level = arch_mm_max_level(mode);
596
Andrew Scull80871322018-08-06 12:04:09 +0100597 addr = mm_round_down_to_page(addr);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100598
Andrew Scull265ada92018-07-30 15:19:01 +0100599 return mm_is_mapped_recursive(table, addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100600}
601
602/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100603 * Initialises the given page table.
604 */
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100605bool mm_ptable_init(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100606{
607 size_t i;
608 pte_t *table;
609
Andrew Scull7364a8e2018-07-19 15:39:29 +0100610 if (mode & MM_MODE_NOSYNC) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100611 table = halloc_aligned_nosync(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100612 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100613 table = halloc_aligned(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100614 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100615
Andrew Scull7364a8e2018-07-19 15:39:29 +0100616 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100617 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100618 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100619
Andrew Walbran2400ed22018-09-27 14:45:58 +0100620 for (i = 0; i < NUM_ENTRIES; i++) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100621 table[i] = arch_mm_absent_pte(arch_mm_max_level(mode));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100622 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100623
Andrew Scull265ada92018-07-30 15:19:01 +0100624 /* TODO: halloc could return a virtual or physical address if mm not
625 * enabled? */
626 t->table = pa_init((uintpaddr_t)table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100627
628 return true;
629}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100630
631/**
Andrew Scull80871322018-08-06 12:04:09 +0100632 * Updates a VM's page table such that the given physical address range is
633 * mapped in the address space at the corresponding address range in the
Andrew Scullfe636b12018-07-30 14:15:54 +0100634 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100635 */
Andrew Scull80871322018-08-06 12:04:09 +0100636bool mm_vm_identity_map(struct mm_ptable *t, paddr_t begin, paddr_t end,
637 int mode, ipaddr_t *ipa)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100638{
Andrew Scull80871322018-08-06 12:04:09 +0100639 bool success =
640 mm_ptable_identity_map(t, begin, end, mode & ~MM_MODE_STAGE1);
641
642 if (success && ipa != NULL) {
643 *ipa = ipa_from_pa(begin);
644 }
645
646 return success;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100647}
648
649/**
Andrew Scull80871322018-08-06 12:04:09 +0100650 * Updates the VM's table such that the given physical address range is not
651 * mapped in the address space.
652 */
653bool mm_vm_unmap(struct mm_ptable *t, paddr_t begin, paddr_t end, int mode)
654{
655 return mm_ptable_unmap(t, begin, end, mode & ~MM_MODE_STAGE1);
656}
657
658/**
659 * Checks whether the given intermediate physical addess is mapped in the given
660 * page table of a VM.
661 */
662bool mm_vm_is_mapped(struct mm_ptable *t, ipaddr_t ipa, int mode)
663{
664 return mm_ptable_is_mapped(t, ipa_addr(ipa), mode & ~MM_MODE_STAGE1);
665}
666
667/**
668 * Translates an intermediate physical address to a physical address. Addresses
669 * are currently identity mapped so this is a simple type convertion. Returns
670 * true if the address was mapped in the table and the address was converted.
671 */
672bool mm_vm_translate(struct mm_ptable *t, ipaddr_t ipa, paddr_t *pa)
673{
674 bool mapped = mm_vm_is_mapped(t, ipa, 0);
675
676 if (mapped) {
677 *pa = pa_init(ipa_addr(ipa));
678 }
679
680 return mapped;
681}
682
683/**
684 * Updates the hypervisor page table such that the given physical address range
685 * is mapped into the address space at the corresponding address range in the
686 * architecture-agnostic mode provided.
687 */
688void *mm_identity_map(paddr_t begin, paddr_t end, int mode)
689{
690 if (mm_ptable_identity_map(&ptable, begin, end,
691 mode | MM_MODE_STAGE1)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100692 return ptr_from_pa(begin);
Andrew Scull80871322018-08-06 12:04:09 +0100693 }
694
695 return NULL;
696}
697
698/**
699 * Updates the hypervisor table such that the given physical address range is
700 * not mapped in the address space.
701 */
702bool mm_unmap(paddr_t begin, paddr_t end, int mode)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100703{
704 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
705}
706
707/**
708 * Initialises memory management for the hypervisor itself.
709 */
710bool mm_init(void)
711{
Andrew Scull5991ec92018-10-08 14:55:02 +0100712 dlog("text: 0x%x - 0x%x\n", pa_addr(layout_text_begin()),
713 pa_addr(layout_text_end()));
714 dlog("rodata: 0x%x - 0x%x\n", pa_addr(layout_rodata_begin()),
715 pa_addr(layout_rodata_end()));
716 dlog("data: 0x%x - 0x%x\n", pa_addr(layout_data_begin()),
717 pa_addr(layout_data_end()));
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100718
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100719 if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100720 dlog("Unable to allocate memory for page table.\n");
721 return false;
722 }
723
724 /* Map page for uart. */
725 /* TODO: We may not want to map this. */
Andrew Scull24e032f2018-10-15 17:18:12 +0100726 mm_ptable_identity_map(&ptable, pa_init(PL011_BASE),
727 pa_add(pa_init(PL011_BASE), PAGE_SIZE),
728 MM_MODE_R | MM_MODE_W | MM_MODE_D |
729 MM_MODE_NOSYNC | MM_MODE_STAGE1);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100730
731 /* Map each section. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100732 mm_identity_map(layout_text_begin(), layout_text_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100733 MM_MODE_X | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100734
Andrew Scull5991ec92018-10-08 14:55:02 +0100735 mm_identity_map(layout_rodata_begin(), layout_rodata_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100736 MM_MODE_R | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100737
Andrew Scull5991ec92018-10-08 14:55:02 +0100738 mm_identity_map(layout_data_begin(), layout_data_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100739 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100740
Andrew Scull265ada92018-07-30 15:19:01 +0100741 return arch_mm_init(ptable.table, true);
Wedson Almeida Filho03e767a2018-07-30 15:32:03 +0100742}
743
744bool mm_cpu_init(void)
745{
Andrew Scull265ada92018-07-30 15:19:01 +0100746 return arch_mm_init(ptable.table, false);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100747}
748
749/**
750 * Defragments the hypervisor page table.
751 */
752void mm_defrag(void)
753{
754 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
755}