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