blob: 236e763ec5e69c7e4b43ae9745eb7daa58421e07 [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 Filhofdf4afc2018-07-19 15:45:21 +010017static struct mm_ptable ptable;
18
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010019/**
20 * Calculates the size of the address space represented by a page table entry at
21 * the given level.
22 */
23static inline size_t mm_entry_size(int level)
24{
25 return 1ull << (PAGE_BITS + level * PAGE_LEVEL_BITS);
26}
27
28/**
29 * For a given virtual address, calculates the maximum (plus one) address that
30 * can be represented by the same table at the given level.
31 */
32static inline vaddr_t mm_level_end(vaddr_t va, int level)
33{
34 size_t offset = PAGE_BITS + (level + 1) * PAGE_LEVEL_BITS;
35 return ((va >> offset) + 1) << offset;
36}
37
38/**
39 * For a given virtual address, calculates the index at which its entry is
40 * stored in a table at the given level.
41 */
42static inline size_t mm_index(vaddr_t va, int level)
43{
44 vaddr_t v = va >> (PAGE_BITS + level * PAGE_LEVEL_BITS);
45 return v & ((1ull << PAGE_LEVEL_BITS) - 1);
46}
47
48/**
49 * Populates the provided page table entry with a reference to another table if
50 * needed, that is, if it does not yet point to another table.
51 *
52 * Returns a pointer to the table the entry now points to.
53 */
54static pte_t *mm_populate_table_pte(pte_t *pte, int level, bool sync_alloc)
55{
56 pte_t *ntable;
57 pte_t v = *pte;
58 pte_t new_pte;
59 size_t i;
60 size_t inc;
61
62 /* Just return pointer to table if it's already populated. */
Andrew Scull7364a8e2018-07-19 15:39:29 +010063 if (arch_mm_pte_is_table(v)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010064 return arch_mm_pte_to_table(v);
Andrew Scull7364a8e2018-07-19 15:39:29 +010065 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010066
67 /* Allocate a new table. */
68 ntable = (sync_alloc ? halloc_aligned : halloc_aligned_nosync)(
Andrew Scull4f170f52018-07-19 12:58:20 +010069 PAGE_SIZE, PAGE_SIZE);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010070 if (!ntable) {
71 dlog("Failed to allocate memory for page table\n");
72 return NULL;
73 }
74
75 /* Determine template for new pte and its increment. */
76 if (!arch_mm_pte_is_block(v)) {
77 inc = 0;
78 new_pte = arch_mm_absent_pte();
79 } else {
80 inc = mm_entry_size(level - 1);
Andrew Scull7364a8e2018-07-19 15:39:29 +010081 if (level == 1) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010082 new_pte = arch_mm_block_to_page_pte(v);
Andrew Scull7364a8e2018-07-19 15:39:29 +010083 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010084 new_pte = v;
Andrew Scull7364a8e2018-07-19 15:39:29 +010085 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010086 }
87
88 /* Initialise entries in the new table. */
89 for (i = 0; i < PAGE_SIZE / sizeof(paddr_t); i++) {
90 ntable[i] = new_pte;
91 new_pte += inc;
92 }
93
94 /*
95 * Ensure initialisation is visible before updating the actual pte, then
96 * update it.
97 */
98 atomic_thread_fence(memory_order_release);
99 *pte = arch_mm_pa_to_table_pte((paddr_t)ntable);
100
101 return ntable;
102}
103
104/**
105 * Frees all page-table-related memory associated with the given pte at the
106 * given level.
107 */
108static void mm_free_page_pte(pte_t pte, int level, bool sync)
109{
Andrew Scull020ae692018-07-19 16:20:14 +0100110 (void)pte;
111 (void)level;
112 (void)sync;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100113 /* TODO: Implement.
114 if (!arch_mm_pte_is_present(pte) || level < 1)
115 return;
116 */
117}
118
119/**
120 * Updates the page table at the given level to map the given virtual address
121 * range to a physical range using the provided (architecture-specific)
122 * attributes.
123 *
124 * This function calls itself recursively if it needs to update additional
125 * levels, but the recursion is bound by the maximum number of levels in a page
126 * table.
127 */
128static bool mm_map_level(vaddr_t va, vaddr_t va_end, paddr_t pa, uint64_t attrs,
129 pte_t *table, int level, int flags)
130{
131 size_t i = mm_index(va, level);
132 vaddr_t va_level_end = mm_level_end(va, level);
133 size_t entry_size = mm_entry_size(level);
134 bool commit = flags & MAP_FLAG_COMMIT;
135 bool sync = flags & MAP_FLAG_SYNC;
136
137 /* Cap va_end so that we don't go over the current level max. */
Andrew Scull7364a8e2018-07-19 15:39:29 +0100138 if (va_end > va_level_end) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100139 va_end = va_level_end;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100140 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100141
142 /* Fill each entry in the table. */
143 while (va < va_end) {
144 if (level == 0) {
Andrew Scull7364a8e2018-07-19 15:39:29 +0100145 if (commit) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100146 table[i] = arch_mm_pa_to_page_pte(pa, attrs);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100147 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100148 } else if ((va_end - va) >= entry_size &&
149 arch_mm_is_block_allowed(level) &&
150 (va & (entry_size - 1)) == 0) {
151 if (commit) {
152 pte_t pte = table[i];
153 table[i] = arch_mm_pa_to_block_pte(pa, attrs);
154 /* TODO: Add barrier. How do we ensure this
155 * isn't in use by another CPU? Send IPI? */
156 mm_free_page_pte(pte, level, sync);
157 }
158 } else {
Andrew Scull4f170f52018-07-19 12:58:20 +0100159 pte_t *nt =
160 mm_populate_table_pte(table + i, level, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100161 if (!nt) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100162 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100163 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100164
Andrew Scull4f170f52018-07-19 12:58:20 +0100165 if (!mm_map_level(va, va_end, pa, attrs, nt, level - 1,
Andrew Scull7364a8e2018-07-19 15:39:29 +0100166 flags)) {
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 }
170
171 va = (va + entry_size) & ~(entry_size - 1);
172 pa = (pa + entry_size) & ~(entry_size - 1);
173 i++;
174 }
175
176 return true;
177}
178
179/**
180 * Invalidates the TLB for the given virtual address range.
181 */
182static void mm_invalidate_tlb(vaddr_t begin, vaddr_t end, bool stage1)
183{
Andrew Scull7364a8e2018-07-19 15:39:29 +0100184 if (stage1) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100185 arch_mm_invalidate_stage1_range(begin, end);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100186 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100187 arch_mm_invalidate_stage2_range(begin, end);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100188 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100189}
190
191/**
192 * Updates the given table such that the given virtual address range is mapped
193 * to the given physical address range in the architecture-agnostic mode
194 * provided.
195 */
196bool mm_ptable_map(struct mm_ptable *t, vaddr_t begin, vaddr_t end,
197 paddr_t paddr, int mode)
198{
199 uint64_t attrs = arch_mm_mode_to_attrs(mode);
200 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
201 int level = arch_mm_max_level(&t->arch);
202
203 begin = arch_mm_clear_va(begin);
204 end = arch_mm_clear_va(end + PAGE_SIZE - 1);
205 paddr = arch_mm_clear_pa(paddr);
206
207 /*
208 * Do it in two steps to prevent leaving the table in a halfway updated
209 * state. In such a two-step implementation, the table may be left with
210 * extra internal tables, but no different mapping on failure.
211 */
Andrew Scull7364a8e2018-07-19 15:39:29 +0100212 if (!mm_map_level(begin, end, paddr, attrs, t->table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100213 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100214 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100215
216 mm_map_level(begin, end, paddr, attrs, t->table, level,
217 flags | MAP_FLAG_COMMIT);
218
219 /* Invalidate the tlb. */
220 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
221
222 return true;
223}
224
225/**
226 * Updates the given table such that the given virtual address range is not
227 * mapped to any physical address.
228 */
229bool mm_ptable_unmap(struct mm_ptable *t, vaddr_t begin, vaddr_t end, int mode)
230{
231 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
232 int level = arch_mm_max_level(&t->arch);
233
234 begin = arch_mm_clear_va(begin);
235 end = arch_mm_clear_va(end + PAGE_SIZE - 1);
236
237 /* Also do updates in two steps, similarly to mm_ptable_map. */
Andrew Scull7364a8e2018-07-19 15:39:29 +0100238 if (!mm_map_level(begin, end, begin, 0, t->table, level, flags)) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100239 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100240 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100241
242 mm_map_level(begin, end, begin, 0, t->table, level,
243 flags | MAP_FLAG_COMMIT);
244
245 /* Invalidate the tlb. */
246 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
247
248 return true;
249}
250
251/**
252 * Updates the given table such that a single virtual address page is mapped
253 * to a single physical address page in the provided architecture-agnostic mode.
254 */
255bool mm_ptable_map_page(struct mm_ptable *t, vaddr_t va, paddr_t pa, int mode)
256{
257 size_t i;
258 uint64_t attrs = arch_mm_mode_to_attrs(mode);
259 pte_t *table = t->table;
260 bool sync = !(mode & MM_MODE_NOSYNC);
261
262 va = arch_mm_clear_va(va);
263 pa = arch_mm_clear_pa(pa);
264
265 for (i = arch_mm_max_level(&t->arch); i > 0; i--) {
266 table = mm_populate_table_pte(table + mm_index(va, i), i, sync);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100267 if (!table) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100268 return false;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100269 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100270 }
271
272 i = mm_index(va, 0);
273 table[i] = arch_mm_pa_to_page_pte(pa, attrs);
274 return true;
275}
276
277/**
278 * Writes the given table to the debug log, calling itself recursively to
279 * write sub-tables.
280 */
281static void mm_dump_table_recursive(pte_t *table, int level, int max_level)
282{
283 uint64_t i;
284 for (i = 0; i < PAGE_SIZE / sizeof(pte_t); i++) {
Andrew Scull7364a8e2018-07-19 15:39:29 +0100285 if (!arch_mm_pte_is_present(table[i])) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100286 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100287 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100288
289 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i, table[i]);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100290 if (!level) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100291 continue;
Andrew Scull7364a8e2018-07-19 15:39:29 +0100292 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100293
294 if (arch_mm_pte_is_table(table[i])) {
295 mm_dump_table_recursive(arch_mm_pte_to_table(table[i]),
296 level - 1, max_level);
297 }
298 }
299}
300
301/**
302 * Write the given table to the debug log.
303 */
304void mm_ptable_dump(struct mm_ptable *t)
305{
306 int max_level = arch_mm_max_level(&t->arch);
307 mm_dump_table_recursive(t->table, max_level, max_level);
308}
309
310/**
311 * Defragments the given page table by converting page table references to
312 * blocks whenever possible.
313 */
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100314void mm_ptable_defrag(struct mm_ptable *t, int mode)
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100315{
316 /* TODO: Implement. */
Andrew Scull020ae692018-07-19 16:20:14 +0100317 (void)t;
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100318 (void)mode;
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100319}
320
321/**
322 * Initialises the given page table.
323 */
324bool mm_ptable_init(struct mm_ptable *t, int mode)
325{
326 size_t i;
327 pte_t *table;
328
Andrew Scull7364a8e2018-07-19 15:39:29 +0100329 if (mode & MM_MODE_NOSYNC) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100330 table = halloc_aligned_nosync(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100331 } else {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100332 table = halloc_aligned(PAGE_SIZE, PAGE_SIZE);
Andrew Scull7364a8e2018-07-19 15:39:29 +0100333 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100334
Andrew Scull7364a8e2018-07-19 15:39:29 +0100335 if (!table) {
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 Scull7364a8e2018-07-19 15:39:29 +0100339 for (i = 0; i < PAGE_SIZE / sizeof(pte_t); i++) {
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100340 table[i] = arch_mm_absent_pte();
Andrew Scull7364a8e2018-07-19 15:39:29 +0100341 }
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100342
343 t->table = table;
344 arch_mm_ptable_init(&t->arch);
345
346 return true;
347}
Wedson Almeida Filhofdf4afc2018-07-19 15:45:21 +0100348
349/**
350 * Updates the hypervisor page table such that the given virtual address range
351 * is mapped to the given physical address range in the architecture-agnostic
352 * mode provided.
353 */
354bool mm_map(vaddr_t begin, vaddr_t end, paddr_t paddr, int mode)
355{
356 return mm_ptable_map(&ptable, begin, end, paddr, mode | MM_MODE_STAGE1);
357}
358
359/**
360 * Updates the hypervisor table such that the given virtual address range is not
361 * mapped to any physical address.
362 */
363bool mm_unmap(vaddr_t begin, vaddr_t end, int mode)
364{
365 return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
366}
367
368/**
369 * Initialises memory management for the hypervisor itself.
370 */
371bool mm_init(void)
372{
373 extern char text_begin[];
374 extern char text_end[];
375 extern char rodata_begin[];
376 extern char rodata_end[];
377 extern char data_begin[];
378 extern char data_end[];
379
380 dlog("text: 0x%x - 0x%x\n", text_begin, text_end);
381 dlog("rodata: 0x%x - 0x%x\n", rodata_begin, rodata_end);
382 dlog("data: 0x%x - 0x%x\n", data_begin, data_end);
383
384 if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
385 dlog("Unable to allocate memory for page table.\n");
386 return false;
387 }
388
389 /* Map page for uart. */
390 /* TODO: We may not want to map this. */
391 mm_ptable_map_page(&ptable, PL011_BASE, PL011_BASE,
392 MM_MODE_R | MM_MODE_W | MM_MODE_D | MM_MODE_NOSYNC |
393 MM_MODE_STAGE1);
394
395 /* Map each section. */
396 mm_map((vaddr_t)text_begin, (vaddr_t)text_end, (paddr_t)text_begin,
397 MM_MODE_X | MM_MODE_NOSYNC);
398
399 mm_map((vaddr_t)rodata_begin, (vaddr_t)rodata_end,
400 (paddr_t)rodata_begin, MM_MODE_R | MM_MODE_NOSYNC);
401
402 mm_map((vaddr_t)data_begin, (vaddr_t)data_end, (paddr_t)data_begin,
403 MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
404
405 arch_mm_init((paddr_t)ptable.table);
406
407 return true;
408}
409
410/**
411 * Defragments the hypervisor page table.
412 */
413void mm_defrag(void)
414{
415 mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
416}