blob: 5785d64269404f8cf0bc49c66c8d8ff155a53101 [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/**
Andrew Scull80871322018-08-06 12:04:09 +0100385 * Updates the given table such that a single physical address page is mapped
386 * into the address space with the corresponding address page in the provided
387 * architecture-agnostic mode.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100388 */
Andrew Scull80871322018-08-06 12:04:09 +0100389static bool mm_ptable_identity_map_page(struct mm_ptable *t, paddr_t pa,
390 int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100391{
392 size_t i;
393 uint64_t attrs = arch_mm_mode_to_attrs(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100394 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100395 bool sync = !(mode & MM_MODE_NOSYNC);
Andrew Scull80871322018-08-06 12:04:09 +0100396 ptable_addr_t addr;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100397
Andrew Scull80871322018-08-06 12:04:09 +0100398 pa = arch_mm_clear_pa(pa);
399 addr = pa_addr(pa);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100400
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100401 for (i = arch_mm_max_level(mode); i > 0; i--) {
Andrew Walbran6324fc92018-10-03 11:46:43 +0100402 pte_t *pte = &table[mm_index(addr, i)];
403 if (arch_mm_pte_is_block(*pte, i) &&
404 arch_mm_pte_attrs(*pte) == attrs) {
405 /* If the page is within a block that is already mapped
406 * with the appropriate attributes, no need to do
407 * anything more. */
408 return true;
409 }
410 table = mm_populate_table_pte(pte, i, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100411 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100412 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100413 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100414 }
415
Andrew Scull80871322018-08-06 12:04:09 +0100416 i = mm_index(addr, 0);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100417 table[i] = arch_mm_block_pte(0, pa, attrs);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100418 return true;
419}
420
421/**
422 * Writes the given table to the debug log, calling itself recursively to
423 * write sub-tables.
424 */
425static void mm_dump_table_recursive(pte_t *table, int level, int max_level)
426{
427 uint64_t i;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100428 for (i = 0; i < NUM_ENTRIES; i++) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100429 if (!arch_mm_pte_is_present(table[i], level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100430 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100431 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100432
433 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i, table[i]);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100434
Andrew Scull78d6fd92018-09-06 15:08:36 +0100435 if (arch_mm_pte_is_table(table[i], level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100436 mm_dump_table_recursive(
437 ptr_from_va(va_from_pa(
Andrew Scull78d6fd92018-09-06 15:08:36 +0100438 arch_mm_table_from_pte(table[i]))),
Andrew Scull80871322018-08-06 12:04:09 +0100439 level - 1, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100440 }
441 }
442}
443
444/**
445 * Write the given table to the debug log.
446 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100447void mm_ptable_dump(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100448{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100449 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100450 int max_level = arch_mm_max_level(mode);
Andrew Scull265ada92018-07-30 15:19:01 +0100451 mm_dump_table_recursive(table, max_level, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100452}
453
454/**
Andrew Walbran2400ed22018-09-27 14:45:58 +0100455 * Given that `entry` is a subtable but its entries are all absent, return the
456 * absent entry with which it can be replaced. Note that `entry` will no longer
457 * be valid after calling this function as the subtable will have been freed.
458 */
459static pte_t mm_table_pte_to_absent(pte_t entry, int level)
460{
461 pte_t *subtable = ptr_from_pa(arch_mm_table_from_pte(entry));
462 /*
463 * Free the subtable. This is safe to do directly (rather than
464 * using mm_free_page_pte) because we know by this point that it
465 * doesn't have any subtables of its own.
466 */
467 hfree(subtable);
468 /* Replace subtable with a single absent entry. */
469 return arch_mm_absent_pte(level);
470}
471
472/**
473 * Given that `entry` is a subtable and its entries are all identical, return
474 * the single block entry with which it can be replaced if possible. Note that
475 * `entry` will no longer be valid after calling this function as the subtable
476 * may have been freed.
477 */
478static pte_t mm_table_pte_to_block(pte_t entry, int level)
479{
480 pte_t *subtable;
481 uint64_t block_attrs;
482 uint64_t table_attrs;
483 uint64_t combined_attrs;
484 paddr_t block_address;
485
486 if (!arch_mm_is_block_allowed(level)) {
487 return entry;
488 }
489
490 subtable = ptr_from_pa(arch_mm_table_from_pte(entry));
491 /*
492 * Replace subtable with a single block, with equivalent
493 * attributes.
494 */
495 block_attrs = arch_mm_pte_attrs(subtable[0]);
496 table_attrs = arch_mm_pte_attrs(entry);
497 combined_attrs =
498 arch_mm_combine_table_entry_attrs(table_attrs, block_attrs);
499 block_address = arch_mm_block_from_pte(subtable[0]);
500 /* Free the subtable. */
501 hfree(subtable);
502 /*
503 * We can assume that the block is aligned properly
504 * because all virtual addresses are aligned by
505 * definition, and we have a 1-1 mapping from virtual to
506 * physical addresses.
507 */
508 return arch_mm_block_pte(level, block_address, combined_attrs);
509}
510
511/**
512 * Defragment the given ptable entry by recursively replacing any tables with
513 * block or absent entries where possible.
514 */
515static pte_t mm_ptable_defrag_entry(pte_t entry, int level)
516{
517 pte_t *table;
518 uint64_t i;
519 uint64_t attrs;
520 bool identical_blocks_so_far = true;
521 bool all_absent_so_far = true;
522
523 if (!arch_mm_pte_is_table(entry, level)) {
524 return entry;
525 }
526
527 table = ptr_from_pa(arch_mm_table_from_pte(entry));
528
529 /*
530 * Check if all entries are blocks with the same flags or are all
531 * absent.
532 */
533 attrs = arch_mm_pte_attrs(table[0]);
534 for (i = 0; i < NUM_ENTRIES; ++i) {
535 /*
536 * First try to defrag the entry, in case it is a subtable.
537 */
538 table[i] = mm_ptable_defrag_entry(table[i], level - 1);
539
540 if (arch_mm_pte_is_present(table[i], level - 1)) {
541 all_absent_so_far = false;
542 }
543
544 /*
545 * If the entry is a block, check that the flags are the same as
546 * what we have so far.
547 */
548 if (!arch_mm_pte_is_block(table[i], level - 1) ||
549 arch_mm_pte_attrs(table[i]) != attrs) {
550 identical_blocks_so_far = false;
551 }
552 }
553 if (identical_blocks_so_far) {
554 return mm_table_pte_to_block(entry, level);
555 }
556 if (all_absent_so_far) {
557 return mm_table_pte_to_absent(entry, level);
558 }
559 return entry;
560}
561
562/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100563 * Defragments the given page table by converting page table references to
564 * blocks whenever possible.
565 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100566void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100567{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100568 pte_t *table = ptr_from_pa(t->table);
569 int level = arch_mm_max_level(mode);
570 uint64_t i;
571
572 /*
573 * Loop through each entry in the table. If it points to another table,
574 * check if that table can be replaced by a block or an absent entry.
575 */
576 for (i = 0; i < NUM_ENTRIES; ++i) {
577 table[i] = mm_ptable_defrag_entry(table[i], level);
578 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100579}
580
581/**
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100582 * Unmaps the hypervisor pages from the given page table.
583 */
584bool mm_ptable_unmap_hypervisor(struct mm_ptable *t, int mode)
585{
586 /* TODO: If we add pages dynamically, they must be included here too. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100587 return mm_ptable_unmap(t, layout_text_begin(), layout_text_end(),
588 mode) &&
589 mm_ptable_unmap(t, layout_rodata_begin(), layout_rodata_end(),
590 mode) &&
591 mm_ptable_unmap(t, layout_data_begin(), layout_data_end(), mode);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100592}
593
594/**
Andrew Scull80871322018-08-06 12:04:09 +0100595 * Determines if the given address is mapped in the given page table by
596 * recursively traversing all levels of the page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100597 */
Andrew Scull80871322018-08-06 12:04:09 +0100598static bool mm_is_mapped_recursive(const pte_t *table, ptable_addr_t addr,
599 int level)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100600{
601 pte_t pte;
Andrew Scull80871322018-08-06 12:04:09 +0100602 ptable_addr_t va_level_end = mm_level_end(addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100603
604 /* It isn't mapped if it doesn't fit in the table. */
Andrew Scull80871322018-08-06 12:04:09 +0100605 if (addr >= va_level_end) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100606 return false;
607 }
608
609 pte = table[mm_index(addr, level)];
610
Andrew Scull78d6fd92018-09-06 15:08:36 +0100611 if (arch_mm_pte_is_block(pte, level)) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100612 return true;
613 }
614
Andrew Scull78d6fd92018-09-06 15:08:36 +0100615 if (arch_mm_pte_is_table(pte, level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100616 return mm_is_mapped_recursive(
Andrew Walbran2400ed22018-09-27 14:45:58 +0100617 ptr_from_pa(arch_mm_table_from_pte(pte)), addr,
618 level - 1);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100619 }
620
Andrew Scull78d6fd92018-09-06 15:08:36 +0100621 /* The entry is not present. */
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100622 return false;
623}
624
625/**
Andrew Scull80871322018-08-06 12:04:09 +0100626 * Determines if the given address is mapped in the given page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100627 */
Andrew Scull80871322018-08-06 12:04:09 +0100628static bool mm_ptable_is_mapped(struct mm_ptable *t, ptable_addr_t addr,
629 int mode)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100630{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100631 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100632 int level = arch_mm_max_level(mode);
633
Andrew Scull80871322018-08-06 12:04:09 +0100634 addr = mm_round_down_to_page(addr);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100635
Andrew Scull265ada92018-07-30 15:19:01 +0100636 return mm_is_mapped_recursive(table, addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100637}
638
639/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100640 * Initialises the given page table.
641 */
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100642bool mm_ptable_init(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100643{
644 size_t i;
645 pte_t *table;
646
Andrew Scull7364a8e2018-07-19 15:39:29 +0100647 if (mode & MM_MODE_NOSYNC) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100648 table = halloc_aligned_nosync(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100649 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100650 table = halloc_aligned(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100651 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100652
Andrew Scull7364a8e2018-07-19 15:39:29 +0100653 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100654 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100655 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100656
Andrew Walbran2400ed22018-09-27 14:45:58 +0100657 for (i = 0; i < NUM_ENTRIES; i++) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100658 table[i] = arch_mm_absent_pte(arch_mm_max_level(mode));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100659 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100660
Andrew Scull265ada92018-07-30 15:19:01 +0100661 /* TODO: halloc could return a virtual or physical address if mm not
662 * enabled? */
663 t->table = pa_init((uintpaddr_t)table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100664
665 return true;
666}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100667
668/**
Andrew Scull80871322018-08-06 12:04:09 +0100669 * Updates a VM's page table such that the given physical address range is
670 * mapped in the address space at the corresponding address range in the
Andrew Scullfe636b12018-07-30 14:15:54 +0100671 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100672 */
Andrew Scull80871322018-08-06 12:04:09 +0100673bool mm_vm_identity_map(struct mm_ptable *t, paddr_t begin, paddr_t end,
674 int mode, ipaddr_t *ipa)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100675{
Andrew Scull80871322018-08-06 12:04:09 +0100676 bool success =
677 mm_ptable_identity_map(t, begin, end, mode & ~MM_MODE_STAGE1);
678
679 if (success && ipa != NULL) {
680 *ipa = ipa_from_pa(begin);
681 }
682
683 return success;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100684}
685
686/**
Andrew Scull80871322018-08-06 12:04:09 +0100687 * Updates a VM's page table such that the given physical address page is
688 * mapped in the address space at the corresponding address page in the
689 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100690 */
Andrew Scull80871322018-08-06 12:04:09 +0100691bool mm_vm_identity_map_page(struct mm_ptable *t, paddr_t begin, int mode,
692 ipaddr_t *ipa)
693{
694 bool success =
695 mm_ptable_identity_map_page(t, begin, mode & ~MM_MODE_STAGE1);
696
697 if (success && ipa != NULL) {
698 *ipa = ipa_from_pa(begin);
699 }
700
701 return success;
702}
703
704/**
705 * Updates the VM's table such that the given physical address range is not
706 * mapped in the address space.
707 */
708bool mm_vm_unmap(struct mm_ptable *t, paddr_t begin, paddr_t end, int mode)
709{
710 return mm_ptable_unmap(t, begin, end, mode & ~MM_MODE_STAGE1);
711}
712
713/**
714 * Checks whether the given intermediate physical addess is mapped in the given
715 * page table of a VM.
716 */
717bool mm_vm_is_mapped(struct mm_ptable *t, ipaddr_t ipa, int mode)
718{
719 return mm_ptable_is_mapped(t, ipa_addr(ipa), mode & ~MM_MODE_STAGE1);
720}
721
722/**
723 * Translates an intermediate physical address to a physical address. Addresses
724 * are currently identity mapped so this is a simple type convertion. Returns
725 * true if the address was mapped in the table and the address was converted.
726 */
727bool mm_vm_translate(struct mm_ptable *t, ipaddr_t ipa, paddr_t *pa)
728{
729 bool mapped = mm_vm_is_mapped(t, ipa, 0);
730
731 if (mapped) {
732 *pa = pa_init(ipa_addr(ipa));
733 }
734
735 return mapped;
736}
737
738/**
739 * Updates the hypervisor page table such that the given physical address range
740 * is mapped into the address space at the corresponding address range in the
741 * architecture-agnostic mode provided.
742 */
743void *mm_identity_map(paddr_t begin, paddr_t end, int mode)
744{
745 if (mm_ptable_identity_map(&ptable, begin, end,
746 mode | MM_MODE_STAGE1)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100747 return ptr_from_pa(begin);
Andrew Scull80871322018-08-06 12:04:09 +0100748 }
749
750 return NULL;
751}
752
753/**
754 * Updates the hypervisor table such that the given physical address range is
755 * not mapped in the address space.
756 */
757bool mm_unmap(paddr_t begin, paddr_t end, int mode)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100758{
759 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
760}
761
762/**
763 * Initialises memory management for the hypervisor itself.
764 */
765bool mm_init(void)
766{
Andrew Scull5991ec92018-10-08 14:55:02 +0100767 dlog("text: 0x%x - 0x%x\n", pa_addr(layout_text_begin()),
768 pa_addr(layout_text_end()));
769 dlog("rodata: 0x%x - 0x%x\n", pa_addr(layout_rodata_begin()),
770 pa_addr(layout_rodata_end()));
771 dlog("data: 0x%x - 0x%x\n", pa_addr(layout_data_begin()),
772 pa_addr(layout_data_end()));
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100773
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100774 if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100775 dlog("Unable to allocate memory for page table.\n");
776 return false;
777 }
778
779 /* Map page for uart. */
780 /* TODO: We may not want to map this. */
Andrew Scull80871322018-08-06 12:04:09 +0100781 mm_ptable_identity_map_page(&ptable, pa_init(PL011_BASE),
Andrew Scullfe636b12018-07-30 14:15:54 +0100782 MM_MODE_R | MM_MODE_W | MM_MODE_D |
783 MM_MODE_NOSYNC | MM_MODE_STAGE1);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100784
785 /* Map each section. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100786 mm_identity_map(layout_text_begin(), layout_text_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100787 MM_MODE_X | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100788
Andrew Scull5991ec92018-10-08 14:55:02 +0100789 mm_identity_map(layout_rodata_begin(), layout_rodata_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100790 MM_MODE_R | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100791
Andrew Scull5991ec92018-10-08 14:55:02 +0100792 mm_identity_map(layout_data_begin(), layout_data_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100793 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100794
Andrew Scull265ada92018-07-30 15:19:01 +0100795 return arch_mm_init(ptable.table, true);
Wedson Almeida Filho03e767a2018-07-30 15:32:03 +0100796}
797
798bool mm_cpu_init(void)
799{
Andrew Scull265ada92018-07-30 15:19:01 +0100800 return arch_mm_init(ptable.table, false);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100801}
802
803/**
804 * Defragments the hypervisor page table.
805 */
806void mm_defrag(void)
807{
808 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
809}