blob: 3538f4563b33bb424d0bf700457ff561fc313bff [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
51
Andrew Scull4f170f52018-07-19 12:58:20 +010052/* clang-format on */
53
Andrew Walbran2400ed22018-09-27 14:45:58 +010054#define NUM_ENTRIES (PAGE_SIZE / sizeof(pte_t))
55
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +010056static struct mm_ptable ptable;
57
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010058/**
Andrew Walbran2400ed22018-09-27 14:45:58 +010059 * Casts a physical address to a pointer. This assumes that it is mapped (to the
60 * same address), so should only be used within the mm code.
61 */
62static inline void *ptr_from_pa(paddr_t pa)
63{
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 */
87static inline size_t mm_entry_size(int level)
88{
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 Scull80871322018-08-06 12:04:09 +010096static inline ptable_addr_t mm_level_end(ptable_addr_t addr, int 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 Scull80871322018-08-06 12:04:09 +0100106static inline size_t mm_index(ptable_addr_t addr, int 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/**
113 * Populates the provided page table entry with a reference to another table if
114 * needed, that is, if it does not yet point to another table.
115 *
116 * Returns a pointer to the table the entry now points to.
117 */
118static pte_t *mm_populate_table_pte(pte_t *pte, int level, bool sync_alloc)
119{
120 pte_t *ntable;
121 pte_t v = *pte;
122 pte_t new_pte;
123 size_t i;
124 size_t inc;
Andrew Walbran1b99f9d2018-10-03 17:54:40 +0100125 int level_below = level - 1;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100126
127 /* Just return pointer to table if it's already populated. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100128 if (arch_mm_pte_is_table(v, level)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100129 return ptr_from_pa(arch_mm_table_from_pte(v));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100130 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100131
132 /* Allocate a new table. */
133 ntable = (sync_alloc ? halloc_aligned : halloc_aligned_nosync)(
Andrew Scull4f170f52018-07-19 12:58:20 +0100134 PAGE_SIZE, PAGE_SIZE);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100135 if (!ntable) {
136 dlog("Failed to allocate memory for page table\n");
137 return NULL;
138 }
139
140 /* Determine template for new pte and its increment. */
Andrew Scull78d6fd92018-09-06 15:08:36 +0100141 if (arch_mm_pte_is_block(v, level)) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100142 inc = mm_entry_size(level_below);
143 new_pte = arch_mm_block_pte(level_below,
144 arch_mm_block_from_pte(v),
145 arch_mm_pte_attrs(v));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100146 } else {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100147 inc = 0;
Andrew Walbran1b99f9d2018-10-03 17:54:40 +0100148 new_pte = arch_mm_absent_pte(level_below);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100149 }
150
151 /* Initialise entries in the new table. */
Andrew Walbran2400ed22018-09-27 14:45:58 +0100152 for (i = 0; i < NUM_ENTRIES; i++) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100153 ntable[i] = new_pte;
154 new_pte += inc;
155 }
156
157 /*
158 * Ensure initialisation is visible before updating the actual pte, then
159 * update it.
160 */
161 atomic_thread_fence(memory_order_release);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100162 *pte = arch_mm_table_pte(level, pa_init((uintpaddr_t)ntable));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100163
164 return ntable;
165}
166
167/**
168 * Frees all page-table-related memory associated with the given pte at the
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100169 * given level, including any subtables recursively.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100170 */
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100171static void mm_free_page_pte(pte_t pte, int level)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100172{
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100173 pte_t *table;
174 uint64_t i;
175
176 if (!arch_mm_pte_is_table(pte, level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100177 return;
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100178 }
179
Andrew Walbran2400ed22018-09-27 14:45:58 +0100180 table = ptr_from_pa(arch_mm_table_from_pte(pte));
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100181 /* Recursively free any subtables. */
Andrew Walbran2400ed22018-09-27 14:45:58 +0100182 for (i = 0; i < NUM_ENTRIES; ++i) {
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100183 mm_free_page_pte(table[i], level - 1);
184 }
185
186 /* Free the table itself. */
187 hfree(table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100188}
189
190/**
Andrew Scull80871322018-08-06 12:04:09 +0100191 * Updates the page table at the given level to map the given address range to a
192 * physical range using the provided (architecture-specific) attributes.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100193 *
194 * This function calls itself recursively if it needs to update additional
195 * levels, but the recursion is bound by the maximum number of levels in a page
196 * table.
197 */
Andrew Scull80871322018-08-06 12:04:09 +0100198static bool mm_map_level(ptable_addr_t begin, ptable_addr_t end, paddr_t pa,
Andrew Scull265ada92018-07-30 15:19:01 +0100199 uint64_t attrs, pte_t *table, int level, int flags)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100200{
Andrew Scullf3d45592018-09-20 14:30:22 +0100201 pte_t *pte = &table[mm_index(begin, level)];
Andrew Scull80871322018-08-06 12:04:09 +0100202 ptable_addr_t level_end = mm_level_end(begin, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100203 size_t entry_size = mm_entry_size(level);
204 bool commit = flags & MAP_FLAG_COMMIT;
205 bool sync = flags & MAP_FLAG_SYNC;
206
Andrew Scull265ada92018-07-30 15:19:01 +0100207 /* Cap end so that we don't go over the current level max. */
208 if (end > level_end) {
209 end = level_end;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100210 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100211
212 /* Fill each entry in the table. */
Andrew Scull265ada92018-07-30 15:19:01 +0100213 while (begin < end) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100214 if ((end - begin) >= entry_size &&
215 arch_mm_is_block_allowed(level) &&
216 (begin & (entry_size - 1)) == 0) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100217 if (commit) {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100218 pte_t v = *pte;
Andrew Scull78d6fd92018-09-06 15:08:36 +0100219 *pte = arch_mm_block_pte(level, pa, attrs);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100220 /* TODO: Add barrier. How do we ensure this
221 * isn't in use by another CPU? Send IPI? */
Andrew Walbran5bf935c2018-09-28 14:21:54 +0100222 mm_free_page_pte(v, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100223 }
224 } else {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100225 pte_t *nt = mm_populate_table_pte(pte, level, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100226 if (!nt) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100227 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100228 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100229
Andrew Scull80871322018-08-06 12:04:09 +0100230 if (!mm_map_level(begin, end, pa, attrs, nt, level - 1,
231 flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100232 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100233 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100234 }
235
Andrew Scull265ada92018-07-30 15:19:01 +0100236 begin = (begin + entry_size) & ~(entry_size - 1);
237 pa = pa_init((pa_addr(pa) + entry_size) & ~(entry_size - 1));
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100238 pte++;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100239 }
240
241 return true;
242}
243
244/**
Andrew Scull80871322018-08-06 12:04:09 +0100245 * Invalidates the TLB for the given address range.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100246 */
Andrew Scull80871322018-08-06 12:04:09 +0100247static void mm_invalidate_tlb(ptable_addr_t begin, ptable_addr_t end,
248 bool stage1)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100249{
Andrew Scull7364a8e2018-07-19 15:39:29 +0100250 if (stage1) {
Andrew Scull80871322018-08-06 12:04:09 +0100251 arch_mm_invalidate_stage1_range(va_init(begin), va_init(end));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100252 } else {
Andrew Scull80871322018-08-06 12:04:09 +0100253 arch_mm_invalidate_stage2_range(ipa_init(begin), ipa_init(end));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100254 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100255}
256
257/**
Andrew Scull80871322018-08-06 12:04:09 +0100258 * Updates the given table such that the given physical address range is mapped
259 * into the address space with the corresponding address range in the
260 * architecture-agnostic mode provided.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100261 */
Andrew Scull80871322018-08-06 12:04:09 +0100262static bool mm_ptable_identity_map(struct mm_ptable *t, paddr_t pa_begin,
263 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100264{
265 uint64_t attrs = arch_mm_mode_to_attrs(mode);
266 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100267 int level = arch_mm_max_level(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100268 pte_t *table = ptr_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100269 ptable_addr_t begin;
270 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100271
Andrew Scull80871322018-08-06 12:04:09 +0100272 pa_begin = arch_mm_clear_pa(pa_begin);
273 begin = pa_addr(pa_begin);
274 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100275
276 /*
277 * Do it in two steps to prevent leaving the table in a halfway updated
278 * state. In such a two-step implementation, the table may be left with
279 * extra internal tables, but no different mapping on failure.
280 */
Andrew Scull80871322018-08-06 12:04:09 +0100281 if (!mm_map_level(begin, end, pa_begin, attrs, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100282 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100283 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100284
Andrew Scull80871322018-08-06 12:04:09 +0100285 mm_map_level(begin, end, pa_begin, attrs, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100286 flags | MAP_FLAG_COMMIT);
287
288 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100289 if (!(mode & MM_MODE_NOINVALIDATE)) {
290 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
291 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100292
293 return true;
294}
295
296/**
Andrew Scull80871322018-08-06 12:04:09 +0100297 * Updates the given table such that the given physical address range is not
298 * mapped into the address space.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100299 */
Andrew Scull80871322018-08-06 12:04:09 +0100300static bool mm_ptable_unmap(struct mm_ptable *t, paddr_t pa_begin,
301 paddr_t pa_end, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100302{
303 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100304 int level = arch_mm_max_level(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100305 pte_t *table = ptr_from_pa(t->table);
Andrew Scull80871322018-08-06 12:04:09 +0100306 ptable_addr_t begin;
307 ptable_addr_t end;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100308
Andrew Scull80871322018-08-06 12:04:09 +0100309 pa_begin = arch_mm_clear_pa(pa_begin);
310 begin = pa_addr(pa_begin);
311 end = mm_round_up_to_page(pa_addr(pa_end));
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100312
Andrew Scullfe636b12018-07-30 14:15:54 +0100313 /* Also do updates in two steps, similarly to mm_ptable_identity_map. */
Andrew Scull80871322018-08-06 12:04:09 +0100314 if (!mm_map_level(begin, end, pa_begin, 0, table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100315 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100316 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100317
Andrew Scull80871322018-08-06 12:04:09 +0100318 mm_map_level(begin, end, pa_begin, 0, table, level,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100319 flags | MAP_FLAG_COMMIT);
320
321 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100322 if (!(mode & MM_MODE_NOINVALIDATE)) {
323 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
324 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100325
326 return true;
327}
328
329/**
Andrew Scull80871322018-08-06 12:04:09 +0100330 * Updates the given table such that a single physical address page is mapped
331 * into the address space with the corresponding address page in the provided
332 * architecture-agnostic mode.
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100333 */
Andrew Scull80871322018-08-06 12:04:09 +0100334static bool mm_ptable_identity_map_page(struct mm_ptable *t, paddr_t pa,
335 int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100336{
337 size_t i;
338 uint64_t attrs = arch_mm_mode_to_attrs(mode);
Andrew Walbran2400ed22018-09-27 14:45:58 +0100339 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100340 bool sync = !(mode & MM_MODE_NOSYNC);
Andrew Scull80871322018-08-06 12:04:09 +0100341 ptable_addr_t addr;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100342
Andrew Scull80871322018-08-06 12:04:09 +0100343 pa = arch_mm_clear_pa(pa);
344 addr = pa_addr(pa);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100345
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100346 for (i = arch_mm_max_level(mode); i > 0; i--) {
Andrew Scullf3d45592018-09-20 14:30:22 +0100347 table = mm_populate_table_pte(&table[mm_index(addr, i)], i,
Andrew Scull80871322018-08-06 12:04:09 +0100348 sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100349 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100350 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100351 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100352 }
353
Andrew Scull80871322018-08-06 12:04:09 +0100354 i = mm_index(addr, 0);
Andrew Scull78d6fd92018-09-06 15:08:36 +0100355 table[i] = arch_mm_block_pte(0, pa, attrs);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100356 return true;
357}
358
359/**
360 * Writes the given table to the debug log, calling itself recursively to
361 * write sub-tables.
362 */
363static void mm_dump_table_recursive(pte_t *table, int level, int max_level)
364{
365 uint64_t i;
Andrew Walbran2400ed22018-09-27 14:45:58 +0100366 for (i = 0; i < NUM_ENTRIES; i++) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100367 if (!arch_mm_pte_is_present(table[i], level)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100368 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100369 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100370
371 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i, table[i]);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100372
Andrew Scull78d6fd92018-09-06 15:08:36 +0100373 if (arch_mm_pte_is_table(table[i], level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100374 mm_dump_table_recursive(
375 ptr_from_va(va_from_pa(
Andrew Scull78d6fd92018-09-06 15:08:36 +0100376 arch_mm_table_from_pte(table[i]))),
Andrew Scull80871322018-08-06 12:04:09 +0100377 level - 1, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100378 }
379 }
380}
381
382/**
383 * Write the given table to the debug log.
384 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100385void mm_ptable_dump(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100386{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100387 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100388 int max_level = arch_mm_max_level(mode);
Andrew Scull265ada92018-07-30 15:19:01 +0100389 mm_dump_table_recursive(table, max_level, max_level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100390}
391
392/**
Andrew Walbran2400ed22018-09-27 14:45:58 +0100393 * Given that `entry` is a subtable but its entries are all absent, return the
394 * absent entry with which it can be replaced. Note that `entry` will no longer
395 * be valid after calling this function as the subtable will have been freed.
396 */
397static pte_t mm_table_pte_to_absent(pte_t entry, int level)
398{
399 pte_t *subtable = ptr_from_pa(arch_mm_table_from_pte(entry));
400 /*
401 * Free the subtable. This is safe to do directly (rather than
402 * using mm_free_page_pte) because we know by this point that it
403 * doesn't have any subtables of its own.
404 */
405 hfree(subtable);
406 /* Replace subtable with a single absent entry. */
407 return arch_mm_absent_pte(level);
408}
409
410/**
411 * Given that `entry` is a subtable and its entries are all identical, return
412 * the single block entry with which it can be replaced if possible. Note that
413 * `entry` will no longer be valid after calling this function as the subtable
414 * may have been freed.
415 */
416static pte_t mm_table_pte_to_block(pte_t entry, int level)
417{
418 pte_t *subtable;
419 uint64_t block_attrs;
420 uint64_t table_attrs;
421 uint64_t combined_attrs;
422 paddr_t block_address;
423
424 if (!arch_mm_is_block_allowed(level)) {
425 return entry;
426 }
427
428 subtable = ptr_from_pa(arch_mm_table_from_pte(entry));
429 /*
430 * Replace subtable with a single block, with equivalent
431 * attributes.
432 */
433 block_attrs = arch_mm_pte_attrs(subtable[0]);
434 table_attrs = arch_mm_pte_attrs(entry);
435 combined_attrs =
436 arch_mm_combine_table_entry_attrs(table_attrs, block_attrs);
437 block_address = arch_mm_block_from_pte(subtable[0]);
438 /* Free the subtable. */
439 hfree(subtable);
440 /*
441 * We can assume that the block is aligned properly
442 * because all virtual addresses are aligned by
443 * definition, and we have a 1-1 mapping from virtual to
444 * physical addresses.
445 */
446 return arch_mm_block_pte(level, block_address, combined_attrs);
447}
448
449/**
450 * Defragment the given ptable entry by recursively replacing any tables with
451 * block or absent entries where possible.
452 */
453static pte_t mm_ptable_defrag_entry(pte_t entry, int level)
454{
455 pte_t *table;
456 uint64_t i;
457 uint64_t attrs;
458 bool identical_blocks_so_far = true;
459 bool all_absent_so_far = true;
460
461 if (!arch_mm_pte_is_table(entry, level)) {
462 return entry;
463 }
464
465 table = ptr_from_pa(arch_mm_table_from_pte(entry));
466
467 /*
468 * Check if all entries are blocks with the same flags or are all
469 * absent.
470 */
471 attrs = arch_mm_pte_attrs(table[0]);
472 for (i = 0; i < NUM_ENTRIES; ++i) {
473 /*
474 * First try to defrag the entry, in case it is a subtable.
475 */
476 table[i] = mm_ptable_defrag_entry(table[i], level - 1);
477
478 if (arch_mm_pte_is_present(table[i], level - 1)) {
479 all_absent_so_far = false;
480 }
481
482 /*
483 * If the entry is a block, check that the flags are the same as
484 * what we have so far.
485 */
486 if (!arch_mm_pte_is_block(table[i], level - 1) ||
487 arch_mm_pte_attrs(table[i]) != attrs) {
488 identical_blocks_so_far = false;
489 }
490 }
491 if (identical_blocks_so_far) {
492 return mm_table_pte_to_block(entry, level);
493 }
494 if (all_absent_so_far) {
495 return mm_table_pte_to_absent(entry, level);
496 }
497 return entry;
498}
499
500/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100501 * Defragments the given page table by converting page table references to
502 * blocks whenever possible.
503 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100504void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100505{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100506 pte_t *table = ptr_from_pa(t->table);
507 int level = arch_mm_max_level(mode);
508 uint64_t i;
509
510 /*
511 * Loop through each entry in the table. If it points to another table,
512 * check if that table can be replaced by a block or an absent entry.
513 */
514 for (i = 0; i < NUM_ENTRIES; ++i) {
515 table[i] = mm_ptable_defrag_entry(table[i], level);
516 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100517}
518
519/**
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100520 * Unmaps the hypervisor pages from the given page table.
521 */
522bool mm_ptable_unmap_hypervisor(struct mm_ptable *t, int mode)
523{
524 /* TODO: If we add pages dynamically, they must be included here too. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100525 return mm_ptable_unmap(t, layout_text_begin(), layout_text_end(),
526 mode) &&
527 mm_ptable_unmap(t, layout_rodata_begin(), layout_rodata_end(),
528 mode) &&
529 mm_ptable_unmap(t, layout_data_begin(), layout_data_end(), mode);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100530}
531
532/**
Andrew Scull80871322018-08-06 12:04:09 +0100533 * Determines if the given address is mapped in the given page table by
534 * recursively traversing all levels of the page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100535 */
Andrew Scull80871322018-08-06 12:04:09 +0100536static bool mm_is_mapped_recursive(const pte_t *table, ptable_addr_t addr,
537 int level)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100538{
539 pte_t pte;
Andrew Scull80871322018-08-06 12:04:09 +0100540 ptable_addr_t va_level_end = mm_level_end(addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100541
542 /* It isn't mapped if it doesn't fit in the table. */
Andrew Scull80871322018-08-06 12:04:09 +0100543 if (addr >= va_level_end) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100544 return false;
545 }
546
547 pte = table[mm_index(addr, level)];
548
Andrew Scull78d6fd92018-09-06 15:08:36 +0100549 if (arch_mm_pte_is_block(pte, level)) {
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100550 return true;
551 }
552
Andrew Scull78d6fd92018-09-06 15:08:36 +0100553 if (arch_mm_pte_is_table(pte, level)) {
Andrew Scull80871322018-08-06 12:04:09 +0100554 return mm_is_mapped_recursive(
Andrew Walbran2400ed22018-09-27 14:45:58 +0100555 ptr_from_pa(arch_mm_table_from_pte(pte)), addr,
556 level - 1);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100557 }
558
Andrew Scull78d6fd92018-09-06 15:08:36 +0100559 /* The entry is not present. */
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100560 return false;
561}
562
563/**
Andrew Scull80871322018-08-06 12:04:09 +0100564 * Determines if the given address is mapped in the given page table.
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100565 */
Andrew Scull80871322018-08-06 12:04:09 +0100566static bool mm_ptable_is_mapped(struct mm_ptable *t, ptable_addr_t addr,
567 int mode)
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100568{
Andrew Walbran2400ed22018-09-27 14:45:58 +0100569 pte_t *table = ptr_from_pa(t->table);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100570 int level = arch_mm_max_level(mode);
571
Andrew Scull80871322018-08-06 12:04:09 +0100572 addr = mm_round_down_to_page(addr);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100573
Andrew Scull265ada92018-07-30 15:19:01 +0100574 return mm_is_mapped_recursive(table, addr, level);
Wedson Almeida Filho2f94ec12018-07-26 16:00:48 +0100575}
576
577/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100578 * Initialises the given page table.
579 */
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100580bool mm_ptable_init(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100581{
582 size_t i;
583 pte_t *table;
584
Andrew Scull7364a8e2018-07-19 15:39:29 +0100585 if (mode & MM_MODE_NOSYNC) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100586 table = halloc_aligned_nosync(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100587 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100588 table = halloc_aligned(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100589 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100590
Andrew Scull7364a8e2018-07-19 15:39:29 +0100591 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100592 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100593 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100594
Andrew Walbran2400ed22018-09-27 14:45:58 +0100595 for (i = 0; i < NUM_ENTRIES; i++) {
Andrew Scull78d6fd92018-09-06 15:08:36 +0100596 table[i] = arch_mm_absent_pte(arch_mm_max_level(mode));
Andrew Scull7364a8e2018-07-19 15:39:29 +0100597 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100598
Andrew Scull265ada92018-07-30 15:19:01 +0100599 /* TODO: halloc could return a virtual or physical address if mm not
600 * enabled? */
601 t->table = pa_init((uintpaddr_t)table);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100602
603 return true;
604}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100605
606/**
Andrew Scull80871322018-08-06 12:04:09 +0100607 * Updates a VM's page table such that the given physical address range is
608 * mapped in the address space at the corresponding address range in the
Andrew Scullfe636b12018-07-30 14:15:54 +0100609 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100610 */
Andrew Scull80871322018-08-06 12:04:09 +0100611bool mm_vm_identity_map(struct mm_ptable *t, paddr_t begin, paddr_t end,
612 int mode, ipaddr_t *ipa)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100613{
Andrew Scull80871322018-08-06 12:04:09 +0100614 bool success =
615 mm_ptable_identity_map(t, begin, end, mode & ~MM_MODE_STAGE1);
616
617 if (success && ipa != NULL) {
618 *ipa = ipa_from_pa(begin);
619 }
620
621 return success;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100622}
623
624/**
Andrew Scull80871322018-08-06 12:04:09 +0100625 * Updates a VM's page table such that the given physical address page is
626 * mapped in the address space at the corresponding address page in the
627 * architecture-agnostic mode provided.
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100628 */
Andrew Scull80871322018-08-06 12:04:09 +0100629bool mm_vm_identity_map_page(struct mm_ptable *t, paddr_t begin, int mode,
630 ipaddr_t *ipa)
631{
632 bool success =
633 mm_ptable_identity_map_page(t, begin, mode & ~MM_MODE_STAGE1);
634
635 if (success && ipa != NULL) {
636 *ipa = ipa_from_pa(begin);
637 }
638
639 return success;
640}
641
642/**
643 * Updates the VM's table such that the given physical address range is not
644 * mapped in the address space.
645 */
646bool mm_vm_unmap(struct mm_ptable *t, paddr_t begin, paddr_t end, int mode)
647{
648 return mm_ptable_unmap(t, begin, end, mode & ~MM_MODE_STAGE1);
649}
650
651/**
652 * Checks whether the given intermediate physical addess is mapped in the given
653 * page table of a VM.
654 */
655bool mm_vm_is_mapped(struct mm_ptable *t, ipaddr_t ipa, int mode)
656{
657 return mm_ptable_is_mapped(t, ipa_addr(ipa), mode & ~MM_MODE_STAGE1);
658}
659
660/**
661 * Translates an intermediate physical address to a physical address. Addresses
662 * are currently identity mapped so this is a simple type convertion. Returns
663 * true if the address was mapped in the table and the address was converted.
664 */
665bool mm_vm_translate(struct mm_ptable *t, ipaddr_t ipa, paddr_t *pa)
666{
667 bool mapped = mm_vm_is_mapped(t, ipa, 0);
668
669 if (mapped) {
670 *pa = pa_init(ipa_addr(ipa));
671 }
672
673 return mapped;
674}
675
676/**
677 * Updates the hypervisor page table such that the given physical address range
678 * is mapped into the address space at the corresponding address range in the
679 * architecture-agnostic mode provided.
680 */
681void *mm_identity_map(paddr_t begin, paddr_t end, int mode)
682{
683 if (mm_ptable_identity_map(&ptable, begin, end,
684 mode | MM_MODE_STAGE1)) {
Andrew Walbran2400ed22018-09-27 14:45:58 +0100685 return ptr_from_pa(begin);
Andrew Scull80871322018-08-06 12:04:09 +0100686 }
687
688 return NULL;
689}
690
691/**
692 * Updates the hypervisor table such that the given physical address range is
693 * not mapped in the address space.
694 */
695bool mm_unmap(paddr_t begin, paddr_t end, int mode)
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100696{
697 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
698}
699
700/**
701 * Initialises memory management for the hypervisor itself.
702 */
703bool mm_init(void)
704{
Andrew Scull5991ec92018-10-08 14:55:02 +0100705 dlog("text: 0x%x - 0x%x\n", pa_addr(layout_text_begin()),
706 pa_addr(layout_text_end()));
707 dlog("rodata: 0x%x - 0x%x\n", pa_addr(layout_rodata_begin()),
708 pa_addr(layout_rodata_end()));
709 dlog("data: 0x%x - 0x%x\n", pa_addr(layout_data_begin()),
710 pa_addr(layout_data_end()));
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100711
Andrew Scull8c3a63a2018-09-20 13:38:34 +0100712 if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100713 dlog("Unable to allocate memory for page table.\n");
714 return false;
715 }
716
717 /* Map page for uart. */
718 /* TODO: We may not want to map this. */
Andrew Scull80871322018-08-06 12:04:09 +0100719 mm_ptable_identity_map_page(&ptable, pa_init(PL011_BASE),
Andrew Scullfe636b12018-07-30 14:15:54 +0100720 MM_MODE_R | MM_MODE_W | MM_MODE_D |
721 MM_MODE_NOSYNC | MM_MODE_STAGE1);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100722
723 /* Map each section. */
Andrew Scull5991ec92018-10-08 14:55:02 +0100724 mm_identity_map(layout_text_begin(), layout_text_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100725 MM_MODE_X | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100726
Andrew Scull5991ec92018-10-08 14:55:02 +0100727 mm_identity_map(layout_rodata_begin(), layout_rodata_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100728 MM_MODE_R | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100729
Andrew Scull5991ec92018-10-08 14:55:02 +0100730 mm_identity_map(layout_data_begin(), layout_data_end(),
Andrew Scullfe636b12018-07-30 14:15:54 +0100731 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100732
Andrew Scull265ada92018-07-30 15:19:01 +0100733 return arch_mm_init(ptable.table, true);
Wedson Almeida Filho03e767a2018-07-30 15:32:03 +0100734}
735
736bool mm_cpu_init(void)
737{
Andrew Scull265ada92018-07-30 15:19:01 +0100738 return arch_mm_init(ptable.table, false);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100739}
740
741/**
742 * Defragments the hypervisor page table.
743 */
744void mm_defrag(void)
745{
746 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
747}