blob: 8bfd6f491f3c9e511ed014e348d01626cf071f06 [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 Filhofed69022018-07-11 15:39:12 +010017/**
18 * Calculates the size of the address space represented by a page table entry at
19 * the given level.
20 */
21static inline size_t mm_entry_size(int level)
22{
23 return 1ull << (PAGE_BITS + level * PAGE_LEVEL_BITS);
24}
25
26/**
27 * For a given virtual address, calculates the maximum (plus one) address that
28 * can be represented by the same table at the given level.
29 */
30static inline vaddr_t mm_level_end(vaddr_t va, int level)
31{
32 size_t offset = PAGE_BITS + (level + 1) * PAGE_LEVEL_BITS;
33 return ((va >> offset) + 1) << offset;
34}
35
36/**
37 * For a given virtual address, calculates the index at which its entry is
38 * stored in a table at the given level.
39 */
40static inline size_t mm_index(vaddr_t va, int level)
41{
42 vaddr_t v = va >> (PAGE_BITS + level * PAGE_LEVEL_BITS);
43 return v & ((1ull << PAGE_LEVEL_BITS) - 1);
44}
45
46/**
47 * Populates the provided page table entry with a reference to another table if
48 * needed, that is, if it does not yet point to another table.
49 *
50 * Returns a pointer to the table the entry now points to.
51 */
52static pte_t *mm_populate_table_pte(pte_t *pte, int level, bool sync_alloc)
53{
54 pte_t *ntable;
55 pte_t v = *pte;
56 pte_t new_pte;
57 size_t i;
58 size_t inc;
59
60 /* Just return pointer to table if it's already populated. */
61 if (arch_mm_pte_is_table(v))
62 return arch_mm_pte_to_table(v);
63
64 /* Allocate a new table. */
65 ntable = (sync_alloc ? halloc_aligned : halloc_aligned_nosync)(
Andrew Scull4f170f52018-07-19 12:58:20 +010066 PAGE_SIZE, PAGE_SIZE);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +010067 if (!ntable) {
68 dlog("Failed to allocate memory for page table\n");
69 return NULL;
70 }
71
72 /* Determine template for new pte and its increment. */
73 if (!arch_mm_pte_is_block(v)) {
74 inc = 0;
75 new_pte = arch_mm_absent_pte();
76 } else {
77 inc = mm_entry_size(level - 1);
78 if (level == 1)
79 new_pte = arch_mm_block_to_page_pte(v);
80 else
81 new_pte = v;
82 }
83
84 /* Initialise entries in the new table. */
85 for (i = 0; i < PAGE_SIZE / sizeof(paddr_t); i++) {
86 ntable[i] = new_pte;
87 new_pte += inc;
88 }
89
90 /*
91 * Ensure initialisation is visible before updating the actual pte, then
92 * update it.
93 */
94 atomic_thread_fence(memory_order_release);
95 *pte = arch_mm_pa_to_table_pte((paddr_t)ntable);
96
97 return ntable;
98}
99
100/**
101 * Frees all page-table-related memory associated with the given pte at the
102 * given level.
103 */
104static void mm_free_page_pte(pte_t pte, int level, bool sync)
105{
106 /* TODO: Implement.
107 if (!arch_mm_pte_is_present(pte) || level < 1)
108 return;
109 */
110}
111
112/**
113 * Updates the page table at the given level to map the given virtual address
114 * range to a physical range using the provided (architecture-specific)
115 * attributes.
116 *
117 * This function calls itself recursively if it needs to update additional
118 * levels, but the recursion is bound by the maximum number of levels in a page
119 * table.
120 */
121static bool mm_map_level(vaddr_t va, vaddr_t va_end, paddr_t pa, uint64_t attrs,
122 pte_t *table, int level, int flags)
123{
124 size_t i = mm_index(va, level);
125 vaddr_t va_level_end = mm_level_end(va, level);
126 size_t entry_size = mm_entry_size(level);
127 bool commit = flags & MAP_FLAG_COMMIT;
128 bool sync = flags & MAP_FLAG_SYNC;
129
130 /* Cap va_end so that we don't go over the current level max. */
131 if (va_end > va_level_end)
132 va_end = va_level_end;
133
134 /* Fill each entry in the table. */
135 while (va < va_end) {
136 if (level == 0) {
137 if (commit)
138 table[i] = arch_mm_pa_to_page_pte(pa, attrs);
139 } else if ((va_end - va) >= entry_size &&
140 arch_mm_is_block_allowed(level) &&
141 (va & (entry_size - 1)) == 0) {
142 if (commit) {
143 pte_t pte = table[i];
144 table[i] = arch_mm_pa_to_block_pte(pa, attrs);
145 /* TODO: Add barrier. How do we ensure this
146 * isn't in use by another CPU? Send IPI? */
147 mm_free_page_pte(pte, level, sync);
148 }
149 } else {
Andrew Scull4f170f52018-07-19 12:58:20 +0100150 pte_t *nt =
151 mm_populate_table_pte(table + i, level, sync);
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100152 if (!nt)
153 return false;
154
Andrew Scull4f170f52018-07-19 12:58:20 +0100155 if (!mm_map_level(va, va_end, pa, attrs, nt, level - 1,
Wedson Almeida Filhofed69022018-07-11 15:39:12 +0100156 flags))
157 return false;
158 }
159
160 va = (va + entry_size) & ~(entry_size - 1);
161 pa = (pa + entry_size) & ~(entry_size - 1);
162 i++;
163 }
164
165 return true;
166}
167
168/**
169 * Invalidates the TLB for the given virtual address range.
170 */
171static void mm_invalidate_tlb(vaddr_t begin, vaddr_t end, bool stage1)
172{
173 if (stage1)
174 arch_mm_invalidate_stage1_range(begin, end);
175 else
176 arch_mm_invalidate_stage2_range(begin, end);
177}
178
179/**
180 * Updates the given table such that the given virtual address range is mapped
181 * to the given physical address range in the architecture-agnostic mode
182 * provided.
183 */
184bool mm_ptable_map(struct mm_ptable *t, vaddr_t begin, vaddr_t end,
185 paddr_t paddr, int mode)
186{
187 uint64_t attrs = arch_mm_mode_to_attrs(mode);
188 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
189 int level = arch_mm_max_level(&t->arch);
190
191 begin = arch_mm_clear_va(begin);
192 end = arch_mm_clear_va(end + PAGE_SIZE - 1);
193 paddr = arch_mm_clear_pa(paddr);
194
195 /*
196 * Do it in two steps to prevent leaving the table in a halfway updated
197 * state. In such a two-step implementation, the table may be left with
198 * extra internal tables, but no different mapping on failure.
199 */
200 if (!mm_map_level(begin, end, paddr, attrs, t->table, level, flags))
201 return false;
202
203 mm_map_level(begin, end, paddr, attrs, t->table, level,
204 flags | MAP_FLAG_COMMIT);
205
206 /* Invalidate the tlb. */
207 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
208
209 return true;
210}
211
212/**
213 * Updates the given table such that the given virtual address range is not
214 * mapped to any physical address.
215 */
216bool mm_ptable_unmap(struct mm_ptable *t, vaddr_t begin, vaddr_t end, int mode)
217{
218 int flags = (mode & MM_MODE_NOSYNC) ? 0 : MAP_FLAG_SYNC;
219 int level = arch_mm_max_level(&t->arch);
220
221 begin = arch_mm_clear_va(begin);
222 end = arch_mm_clear_va(end + PAGE_SIZE - 1);
223
224 /* Also do updates in two steps, similarly to mm_ptable_map. */
225 if (!mm_map_level(begin, end, begin, 0, t->table, level, flags))
226 return false;
227
228 mm_map_level(begin, end, begin, 0, t->table, level,
229 flags | MAP_FLAG_COMMIT);
230
231 /* Invalidate the tlb. */
232 mm_invalidate_tlb(begin, end, (mode & MM_MODE_STAGE1) != 0);
233
234 return true;
235}
236
237/**
238 * Updates the given table such that a single virtual address page is mapped
239 * to a single physical address page in the provided architecture-agnostic mode.
240 */
241bool mm_ptable_map_page(struct mm_ptable *t, vaddr_t va, paddr_t pa, int mode)
242{
243 size_t i;
244 uint64_t attrs = arch_mm_mode_to_attrs(mode);
245 pte_t *table = t->table;
246 bool sync = !(mode & MM_MODE_NOSYNC);
247
248 va = arch_mm_clear_va(va);
249 pa = arch_mm_clear_pa(pa);
250
251 for (i = arch_mm_max_level(&t->arch); i > 0; i--) {
252 table = mm_populate_table_pte(table + mm_index(va, i), i, sync);
253 if (!table)
254 return false;
255 }
256
257 i = mm_index(va, 0);
258 table[i] = arch_mm_pa_to_page_pte(pa, attrs);
259 return true;
260}
261
262/**
263 * Writes the given table to the debug log, calling itself recursively to
264 * write sub-tables.
265 */
266static void mm_dump_table_recursive(pte_t *table, int level, int max_level)
267{
268 uint64_t i;
269 for (i = 0; i < PAGE_SIZE / sizeof(pte_t); i++) {
270 if (!arch_mm_pte_is_present(table[i]))
271 continue;
272
273 dlog("%*s%x: %x\n", 4 * (max_level - level), "", i, table[i]);
274 if (!level)
275 continue;
276
277 if (arch_mm_pte_is_table(table[i])) {
278 mm_dump_table_recursive(arch_mm_pte_to_table(table[i]),
279 level - 1, max_level);
280 }
281 }
282}
283
284/**
285 * Write the given table to the debug log.
286 */
287void mm_ptable_dump(struct mm_ptable *t)
288{
289 int max_level = arch_mm_max_level(&t->arch);
290 mm_dump_table_recursive(t->table, max_level, max_level);
291}
292
293/**
294 * Defragments the given page table by converting page table references to
295 * blocks whenever possible.
296 */
297void mm_ptable_defrag(struct mm_ptable *t)
298{
299 /* TODO: Implement. */
300}
301
302/**
303 * Initialises the given page table.
304 */
305bool mm_ptable_init(struct mm_ptable *t, int mode)
306{
307 size_t i;
308 pte_t *table;
309
310 if (mode & MM_MODE_NOSYNC)
311 table = halloc_aligned_nosync(PAGE_SIZE, PAGE_SIZE);
312 else
313 table = halloc_aligned(PAGE_SIZE, PAGE_SIZE);
314
315 if (!table)
316 return false;
317
318 for (i = 0; i < PAGE_SIZE / sizeof(pte_t); i++)
319 table[i] = arch_mm_absent_pte();
320
321 t->table = table;
322 arch_mm_ptable_init(&t->arch);
323
324 return true;
325}