blob: afdbf50d6d9647b292db7792bfd6e2eee5caca54 [file] [log] [blame]
Wedson Almeida Filhofed69022018-07-11 15:39:12 +01001#include "mm.h"
2
3#include <stdatomic.h>
4#include <stdint.h>
5
6#include "alloc.h"
7#include "dlog.h"
8
Andrew Scull4f170f52018-07-19 12:58:20 +01009/* Keep macro alignment */
10/* clang-format off */
11
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010012#define MAP_FLAG_SYNC 0x01
13#define MAP_FLAG_COMMIT 0x02
14
Andrew Scull4f170f52018-07-19 12:58:20 +010015/* clang-format on */
16
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +010017extern char text_begin[];
18extern char text_end[];
19extern char rodata_begin[];
20extern char rodata_end[];
21extern char data_begin[];
22extern char data_end[];
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +010023static struct mm_ptable ptable;
24
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010025/**
26 * Calculates the size of the address space represented by a page table entry at
27 * the given level.
28 */
29static inline size_t mm_entry_size(int level)
30{
31 return 1ull << (PAGE_BITS + level * PAGE_LEVEL_BITS);
32}
33
34/**
35 * For a given virtual address, calculates the maximum (plus one) address that
36 * can be represented by the same table at the given level.
37 */
38static inline vaddr_t mm_level_end(vaddr_t va, int level)
39{
40 size_t offset = PAGE_BITS + (level + 1) * PAGE_LEVEL_BITS;
41 return ((va >> offset) + 1) << offset;
42}
43
44/**
45 * For a given virtual address, calculates the index at which its entry is
46 * stored in a table at the given level.
47 */
48static inline size_t mm_index(vaddr_t va, int level)
49{
50 vaddr_t v = va >> (PAGE_BITS + level * PAGE_LEVEL_BITS);
51 return v & ((1ull << PAGE_LEVEL_BITS) - 1);
52}
53
54/**
55 * Populates the provided page table entry with a reference to another table if
56 * needed, that is, if it does not yet point to another table.
57 *
58 * Returns a pointer to the table the entry now points to.
59 */
60static pte_t *mm_populate_table_pte(pte_t *pte, int level, bool sync_alloc)
61{
62 pte_t *ntable;
63 pte_t v = *pte;
64 pte_t new_pte;
65 size_t i;
66 size_t inc;
67
68 /* Just return pointer to table if it's already populated. */
Andrew Scull7364a8e2018-07-19 15:39:29 +010069 if (arch_mm_pte_is_table(v)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010070 return arch_mm_pte_to_table(v);
Andrew Scull7364a8e2018-07-19 15:39:29 +010071 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010072
73 /* Allocate a new table. */
74 ntable = (sync_alloc ? halloc_aligned : halloc_aligned_nosync)(
Andrew Scull4f170f52018-07-19 12:58:20 +010075 PAGE_SIZE, PAGE_SIZE);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010076 if (!ntable) {
77 dlog("Failed to allocate memory for page table\n");
78 return NULL;
79 }
80
81 /* Determine template for new pte and its increment. */
82 if (!arch_mm_pte_is_block(v)) {
83 inc = 0;
84 new_pte = arch_mm_absent_pte();
85 } else {
86 inc = mm_entry_size(level - 1);
Andrew Scull7364a8e2018-07-19 15:39:29 +010087 if (level == 1) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010088 new_pte = arch_mm_block_to_page_pte(v);
Andrew Scull7364a8e2018-07-19 15:39:29 +010089 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010090 new_pte = v;
Andrew Scull7364a8e2018-07-19 15:39:29 +010091 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010092 }
93
94 /* Initialise entries in the new table. */
95 for (i = 0; i < PAGE_SIZE / sizeof(paddr_t); i++) {
96 ntable[i] = new_pte;
97 new_pte += inc;
98 }
99
100 /*
101 * Ensure initialisation is visible before updating the actual pte, then
102 * update it.
103 */
104 atomic_thread_fence(memory_order_release);
105 *pte = arch_mm_pa_to_table_pte((paddr_t)ntable);
106
107 return ntable;
108}
109
110/**
111 * Frees all page-table-related memory associated with the given pte at the
112 * given level.
113 */
114static void mm_free_page_pte(pte_t pte, int level, bool sync)
115{
Andrew Scull020ae692018-07-19 16:20:14 +0100116 (void)pte;
117 (void)level;
118 (void)sync;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100119 /* TODO: Implement.
120 if (!arch_mm_pte_is_present(pte) || level < 1)
121 return;
122 */
123}
124
125/**
126 * Updates the page table at the given level to map the given virtual address
127 * range to a physical range using the provided (architecture-specific)
128 * attributes.
129 *
130 * This function calls itself recursively if it needs to update additional
131 * levels, but the recursion is bound by the maximum number of levels in a page
132 * table.
133 */
134static bool mm_map_level(vaddr_t va, vaddr_t va_end, paddr_t pa, uint64_t attrs,
135 pte_t *table, int level, int flags)
136{
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100137 pte_t *pte = table + mm_index(va, level);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100138 vaddr_t va_level_end = mm_level_end(va, level);
139 size_t entry_size = mm_entry_size(level);
140 bool commit = flags & MAP_FLAG_COMMIT;
141 bool sync = flags & MAP_FLAG_SYNC;
142
143 /* Cap va_end so that we don't go over the current level max. */
Andrew Scull7364a8e2018-07-19 15:39:29 +0100144 if (va_end > va_level_end) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100145 va_end = va_level_end;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100146 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100147
148 /* Fill each entry in the table. */
149 while (va < va_end) {
150 if (level == 0) {
Andrew Scull7364a8e2018-07-19 15:39:29 +0100151 if (commit) {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100152 *pte = arch_mm_pa_to_page_pte(pa, attrs);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100153 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100154 } else if ((va_end - va) >= entry_size &&
155 arch_mm_is_block_allowed(level) &&
156 (va & (entry_size - 1)) == 0) {
157 if (commit) {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100158 pte_t v = *pte;
159 *pte = arch_mm_pa_to_block_pte(pa, attrs);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100160 /* TODO: Add barrier. How do we ensure this
161 * isn't in use by another CPU? Send IPI? */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100162 mm_free_page_pte(v, level, sync);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100163 }
164 } else {
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100165 pte_t *nt = mm_populate_table_pte(pte, level, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100166 if (!nt) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100167 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100168 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100169
Andrew Scull4f170f52018-07-19 12:58:20 +0100170 if (!mm_map_level(va, va_end, pa, attrs, nt, level - 1,
Andrew Scull7364a8e2018-07-19 15:39:29 +0100171 flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100172 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100173 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100174 }
175
176 va = (va + entry_size) & ~(entry_size - 1);
177 pa = (pa + entry_size) & ~(entry_size - 1);
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100178 pte++;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100179 }
180
181 return true;
182}
183
184/**
185 * Invalidates the TLB for the given virtual address range.
186 */
187static void mm_invalidate_tlb(vaddr_t begin, vaddr_t end, bool stage1)
188{
Andrew Scull7364a8e2018-07-19 15:39:29 +0100189 if (stage1) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100190 arch_mm_invalidate_stage1_range(begin, end);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100191 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100192 arch_mm_invalidate_stage2_range(begin, end);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100193 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100194}
195
196/**
197 * Updates the given table such that the given virtual address range is mapped
198 * to the given physical address range in the architecture-agnostic mode
199 * provided.
200 */
201bool mm_ptable_map(struct mm_ptable *t, vaddr_t begin, vaddr_t end,
202 paddr_t paddr, int mode)
203{
204 uint64_t attrs = arch_mm_mode_to_attrs(mode);
205 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100206 int level = arch_mm_max_level(mode);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100207
208 begin = arch_mm_clear_va(begin);
209 end = arch_mm_clear_va(end + PAGE_SIZE - 1);
210 paddr = arch_mm_clear_pa(paddr);
211
212 /*
213 * Do it in two steps to prevent leaving the table in a halfway updated
214 * state. In such a two-step implementation, the table may be left with
215 * extra internal tables, but no different mapping on failure.
216 */
Andrew Scull7364a8e2018-07-19 15:39:29 +0100217 if (!mm_map_level(begin, end, paddr, attrs, t->table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100218 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100219 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100220
221 mm_map_level(begin, end, paddr, attrs, t->table, level,
222 flags | MAP_FLAG_COMMIT);
223
224 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100225 if (!(mode & MM_MODE_NOINVALIDATE)) {
226 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
227 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100228
229 return true;
230}
231
232/**
233 * Updates the given table such that the given virtual address range is not
234 * mapped to any physical address.
235 */
236bool mm_ptable_unmap(struct mm_ptable *t, vaddr_t begin, vaddr_t end, int mode)
237{
238 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100239 int level = arch_mm_max_level(mode);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100240
241 begin = arch_mm_clear_va(begin);
242 end = arch_mm_clear_va(end + PAGE_SIZE - 1);
243
244 /* Also do updates in two steps, similarly to mm_ptable_map. */
Andrew Scull7364a8e2018-07-19 15:39:29 +0100245 if (!mm_map_level(begin, end, begin, 0, t->table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100246 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100247 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100248
249 mm_map_level(begin, end, begin, 0, t->table, level,
250 flags | MAP_FLAG_COMMIT);
251
252 /* Invalidate the tlb. */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100253 if (!(mode & MM_MODE_NOINVALIDATE)) {
254 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
255 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100256
257 return true;
258}
259
260/**
261 * Updates the given table such that a single virtual address page is mapped
262 * to a single physical address page in the provided architecture-agnostic mode.
263 */
264bool mm_ptable_map_page(struct mm_ptable *t, vaddr_t va, paddr_t pa, int mode)
265{
266 size_t i;
267 uint64_t attrs = arch_mm_mode_to_attrs(mode);
268 pte_t *table = t->table;
269 bool sync = !(mode & MM_MODE_NOSYNC);
270
271 va = arch_mm_clear_va(va);
272 pa = arch_mm_clear_pa(pa);
273
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100274 for (i = arch_mm_max_level(mode); i > 0; i--) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100275 table = mm_populate_table_pte(table + mm_index(va, i), i, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100276 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100277 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100278 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100279 }
280
281 i = mm_index(va, 0);
282 table[i] = arch_mm_pa_to_page_pte(pa, attrs);
283 return true;
284}
285
286/**
287 * Writes the given table to the debug log, calling itself recursively to
288 * write sub-tables.
289 */
290static void mm_dump_table_recursive(pte_t *table, int level, int max_level)
291{
292 uint64_t i;
293 for (i = 0; i < PAGE_SIZE / sizeof(pte_t); i++) {
Andrew Scull7364a8e2018-07-19 15:39:29 +0100294 if (!arch_mm_pte_is_present(table[i])) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100295 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100296 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100297
298 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i, table[i]);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100299 if (!level) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100300 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100301 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100302
303 if (arch_mm_pte_is_table(table[i])) {
304 mm_dump_table_recursive(arch_mm_pte_to_table(table[i]),
305 level - 1, max_level);
306 }
307 }
308}
309
310/**
311 * Write the given table to the debug log.
312 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100313void mm_ptable_dump(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100314{
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100315 int max_level = arch_mm_max_level(mode);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100316 mm_dump_table_recursive(t->table, max_level, max_level);
317}
318
319/**
320 * Defragments the given page table by converting page table references to
321 * blocks whenever possible.
322 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100323void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100324{
325 /* TODO: Implement. */
Andrew Scull020ae692018-07-19 16:20:14 +0100326 (void)t;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100327 (void)mode;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100328}
329
330/**
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100331 * Unmaps the hypervisor pages from the given page table.
332 */
333bool mm_ptable_unmap_hypervisor(struct mm_ptable *t, int mode)
334{
335 /* TODO: If we add pages dynamically, they must be included here too. */
336 return mm_ptable_unmap(t, (vaddr_t)text_begin, (vaddr_t)text_end,
337 mode) &&
338 mm_ptable_unmap(t, (vaddr_t)rodata_begin, (vaddr_t)rodata_end,
339 mode) &&
340 mm_ptable_unmap(t, (vaddr_t)data_begin, (vaddr_t)data_end, mode);
341}
342
343/**
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100344 * Initialises the given page table.
345 */
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100346bool mm_ptable_init(struct mm_ptable *t, uint32_t id, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100347{
348 size_t i;
349 pte_t *table;
350
Andrew Scull7364a8e2018-07-19 15:39:29 +0100351 if (mode & MM_MODE_NOSYNC) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100352 table = halloc_aligned_nosync(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100353 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100354 table = halloc_aligned(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100355 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100356
Andrew Scull7364a8e2018-07-19 15:39:29 +0100357 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100358 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100359 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100360
Andrew Scull7364a8e2018-07-19 15:39:29 +0100361 for (i = 0; i < PAGE_SIZE / sizeof(pte_t); i++) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100362 table[i] = arch_mm_absent_pte();
Andrew Scull7364a8e2018-07-19 15:39:29 +0100363 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100364
365 t->table = table;
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100366 t->id = id;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100367
368 return true;
369}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100370
371/**
372 * Updates the hypervisor page table such that the given virtual address range
373 * is mapped to the given physical address range in the architecture-agnostic
374 * mode provided.
375 */
376bool mm_map(vaddr_t begin, vaddr_t end, paddr_t paddr, int mode)
377{
378 return mm_ptable_map(&ptable, begin, end, paddr, mode | MM_MODE_STAGE1);
379}
380
381/**
382 * Updates the hypervisor table such that the given virtual address range is not
383 * mapped to any physical address.
384 */
385bool mm_unmap(vaddr_t begin, vaddr_t end, int mode)
386{
387 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
388}
389
390/**
391 * Initialises memory management for the hypervisor itself.
392 */
393bool mm_init(void)
394{
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100395 dlog("text: 0x%x - 0x%x\n", text_begin, text_end);
396 dlog("rodata: 0x%x - 0x%x\n", rodata_begin, rodata_end);
397 dlog("data: 0x%x - 0x%x\n", data_begin, data_end);
398
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100399 if (!mm_ptable_init(&ptable, 0, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100400 dlog("Unable to allocate memory for page table.\n");
401 return false;
402 }
403
404 /* Map page for uart. */
405 /* TODO: We may not want to map this. */
406 mm_ptable_map_page(&ptable, PL011_BASE, PL011_BASE,
407 MM_MODE_R | MM_MODE_W | MM_MODE_D | MM_MODE_NOSYNC |
408 MM_MODE_STAGE1);
409
410 /* Map each section. */
411 mm_map((vaddr_t)text_begin, (vaddr_t)text_end, (paddr_t)text_begin,
412 MM_MODE_X | MM_MODE_NOSYNC);
413
414 mm_map((vaddr_t)rodata_begin, (vaddr_t)rodata_end,
415 (paddr_t)rodata_begin, MM_MODE_R | MM_MODE_NOSYNC);
416
417 mm_map((vaddr_t)data_begin, (vaddr_t)data_end, (paddr_t)data_begin,
418 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
419
Wedson Almeida Filho84a30a02018-07-23 20:05:05 +0100420 return arch_mm_init((paddr_t)ptable.table);
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100421}
422
423/**
424 * Defragments the hypervisor page table.
425 */
426void mm_defrag(void)
427{
428 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
429}