The Pedigree Project 0.1
arm64/VirtualAddressSpace.cc
1#include "VirtualAddressSpace.h"
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/panic.h"
4#include "pedigree/kernel/processor/Processor.h"
5
6#include <string.h>
7
8#include "PhysicalMemoryManager.h"
9
10extern "C" char arm64_boot_high_l0, arm64_boot_low_l0;
11
12namespace {
13constexpr uint64_t Valid = 1ULL;
14constexpr uint64_t Table = 2ULL;
15constexpr uint64_t AddressMask = 0x0000fffffffff000ULL;
16constexpr uint64_t Shareable = 3ULL << 8;
17constexpr uint64_t AccessFlag = 1ULL << 10;
18constexpr uint64_t NotGlobal = 1ULL << 11;
19constexpr uint64_t PrivilegedExecuteNever = 1ULL << 53;
20constexpr uint64_t UserExecuteNever = 1ULL << 54;
21constexpr uint64_t SoftwareNoAccess = 1ULL << 3;
22constexpr uint64_t SoftwareSwapped = 1ULL << 4;
23constexpr uint64_t SoftwareCopyOnWrite = 1ULL << 55;
24constexpr uint64_t SoftwareShared = 1ULL << 56;
25constexpr uint64_t SoftwareBorrowed = 1ULL << 57;
26constexpr uint64_t SoftwareWriteProtected = 1ULL << 58;
27
28uint64_t* tableAt(physical_uintptr_t physical) {
29 return reinterpret_cast<uint64_t*>(ARM64_DIRECT_MAP_BASE + physical);
30}
31
32size_t tableIndex(uintptr_t address, size_t level) {
33 return (address >> (39 - level * 9)) & 511;
34}
35} // namespace
36
37Arm64VirtualAddressSpace Arm64VirtualAddressSpace::m_KernelSpace(true);
38
40 return Arm64VirtualAddressSpace::m_KernelSpace;
41}
42
46
47Arm64VirtualAddressSpace::Arm64VirtualAddressSpace(bool kernel)
49 reinterpret_cast<void*>(kernel ? KERNEL_VIRTUAL_HEAP : USERSPACE_VIRTUAL_HEAP)),
50 m_Root(kernel ? reinterpret_cast<uintptr_t>(&arm64_boot_high_l0)
51 : Arm64PhysicalMemoryManager::instance().allocatePage()),
52 m_StackTop(kernel ? KERNEL_VIRTUAL_STACK : USERSPACE_VIRTUAL_STACK) {
53 if (!kernel) {
54 memset(tableAt(m_Root), 0, PAGE_SIZE);
55 }
56}
57
58Arm64VirtualAddressSpace::~Arm64VirtualAddressSpace() {
59 if (this != &m_KernelSpace && m_Root) {
60 freeTable(tableAt(m_Root), 0, true);
61 Arm64PhysicalMemoryManager::instance().freePage(m_Root);
62 }
63}
64
66 const uintptr_t value = reinterpret_cast<uintptr_t>(address);
67 return value < 0x0000800000000000ULL || value >= ARM64_DIRECT_MAP_BASE;
68}
69
70uint64_t* Arm64VirtualAddressSpace::findExistingEntry(uintptr_t address, size_t* depth) const {
71 physical_uintptr_t root = m_Root;
72 if (address >= ARM64_DIRECT_MAP_BASE) {
73 root = m_KernelSpace.m_Root;
74 } else if (this == &m_KernelSpace) {
75 root = reinterpret_cast<uintptr_t>(&arm64_boot_low_l0);
76 }
77 uint64_t* table = tableAt(root);
78 for (size_t level = 0; level < 4; ++level) {
79 uint64_t* entry = &table[tableIndex(address, level)];
80 if (level == 3 || !(*entry & Valid) || !(*entry & Table)) {
81 if (depth) {
82 *depth = level;
83 }
84 return entry;
85 }
86 table = tableAt(*entry & AddressMask);
87 }
88 return nullptr;
89}
90
91uint64_t* Arm64VirtualAddressSpace::findEntry(uintptr_t address, bool create, size_t* newTables) {
92 physical_uintptr_t root = m_Root;
93 if (address >= ARM64_DIRECT_MAP_BASE) {
94 root = m_KernelSpace.m_Root;
95 } else if (this == &m_KernelSpace) {
96 root = reinterpret_cast<uintptr_t>(&arm64_boot_low_l0);
97 }
98 uint64_t* table = tableAt(root);
99 for (size_t level = 0; level < 3; ++level) {
100 uint64_t* entry = &table[tableIndex(address, level)];
101 if (!(*entry & Valid) || !(*entry & Table)) {
102 if (!create) {
103 return nullptr;
104 }
105 physical_uintptr_t child = Arm64PhysicalMemoryManager::instance().tryAllocatePage();
106 if (!child) {
107 return nullptr;
108 }
109 uint64_t* childTable = tableAt(child);
110 if (*entry & Valid) {
111 // Split a bootstrap identity block before inserting a 4 KiB mapping.
112 const size_t shift = 39 - level * 9;
113 const uint64_t subSize = 1ULL << (shift - 9);
114 const uint64_t base = *entry & ~(subSize * 512 - 1);
115 const uint64_t attributes = *entry & ~AddressMask;
116 for (size_t i = 0; i < 512; ++i) {
117 childTable[i] = (base + i * subSize) | attributes | Valid;
118 }
119 } else {
120 memset(childTable, 0, PAGE_SIZE);
121 }
122 *entry = child | Valid | Table;
123 if (newTables) {
124 ++*newTables;
125 }
126 }
127 table = tableAt(*entry & AddressMask);
128 }
129 return &table[tableIndex(address, 3)];
130}
131
132uint64_t Arm64VirtualAddressSpace::pageDescriptor(physical_uintptr_t physical, size_t flags) {
133 const bool kernel = flags & KernelMode;
134 uint64_t entry = (physical & AddressMask) | Valid | Table | Shareable | AccessFlag;
135 if (!(flags & CacheDisable)) {
136 entry |= 1ULL << 2;
137 }
138 if (!kernel) {
139 entry |= 1ULL << 6;
140 // TTBR0 changes flush all entries, including runtime pages kept global.
141 if (!(flags & RuntimeMapping)) {
142 entry |= NotGlobal;
143 }
144 }
145 if (!(flags & Write) || (flags & (CopyOnWrite | WriteProtected))) {
146 entry |= 1ULL << 7;
147 }
148 if (kernel || !(flags & Execute)) {
149 entry |= UserExecuteNever;
150 }
151 if (!kernel || !(flags & Execute)) {
152 entry |= PrivilegedExecuteNever;
153 }
154 if (flags & NoAccess) {
155 entry = (entry & ~Valid) | SoftwareNoAccess;
156 }
157 if (flags & CopyOnWrite) {
158 entry |= SoftwareCopyOnWrite;
159 }
160 if (flags & Shared) {
161 entry |= SoftwareShared;
162 }
163 if (flags & Borrowed) {
164 entry |= SoftwareBorrowed;
165 }
166 if (flags & WriteProtected) {
167 entry |= SoftwareWriteProtected;
168 }
169 if (flags & Swapped) {
170 entry = (entry & ~Valid) | SoftwareSwapped;
171 }
172 return entry;
173}
174
175size_t Arm64VirtualAddressSpace::descriptorFlags(uint64_t entry) {
176 size_t flags = 0;
177 if (!(entry & (1ULL << 6))) {
178 flags |= KernelMode;
179 }
180 if (!(entry & (1ULL << 7))) {
181 flags |= Write;
182 }
183 if ((entry & (1ULL << 6)) ? !(entry & UserExecuteNever) : !(entry & PrivilegedExecuteNever)) {
184 flags |= Execute;
185 }
186 if (!(entry & (1ULL << 2))) {
187 flags |= CacheDisable;
188 }
189 if (entry & SoftwareNoAccess) {
190 flags |= NoAccess;
191 }
192 if (entry & SoftwareCopyOnWrite) {
193 flags |= CopyOnWrite;
194 }
195 if (entry & SoftwareShared) {
196 flags |= Shared;
197 }
198 if (entry & SoftwareBorrowed) {
199 flags |= Borrowed;
200 }
201 if (entry & SoftwareWriteProtected) {
202 flags |= WriteProtected;
203 }
204 if (entry & SoftwareSwapped) {
205 flags |= Swapped;
206 }
207 if ((entry & (1ULL << 6)) && !(entry & NotGlobal)) {
208 flags |= RuntimeMapping;
209 }
210 return flags;
211}
212
214 const uintptr_t value = reinterpret_cast<uintptr_t>(address);
215 Arm64VirtualAddressSpace& owner = value >= ARM64_DIRECT_MAP_BASE ? m_KernelSpace : *this;
216 LockGuard<Spinlock> guard(owner.m_Lock);
217 uint64_t* entry = owner.findExistingEntry(value);
218 return entry && *entry;
219}
220
221bool Arm64VirtualAddressSpace::map(physical_uintptr_t physical, void* address, size_t flags) {
222 const uintptr_t value = reinterpret_cast<uintptr_t>(address);
223 if (!isAddressValid(address) || (value & (PAGE_SIZE - 1)) || (physical & (PAGE_SIZE - 1))) {
224 return false;
225 }
226 Arm64VirtualAddressSpace& owner = value >= ARM64_DIRECT_MAP_BASE ? m_KernelSpace : *this;
227 LockGuard<Spinlock> guard(owner.m_Lock);
228 uint64_t* old = owner.findExistingEntry(value);
229 if (old && *old && (*old & Valid) && value >= ARM64_DIRECT_MAP_BASE) {
230 return false;
231 }
232 uint64_t* entry = owner.findEntry(value, true);
233 if (!entry || (*entry && !(descriptorFlags(*entry) & KernelMode))) {
234 return false;
235 }
236 *entry = pageDescriptor(physical, flags);
237 Processor::invalidate(address);
238 return true;
239}
240
241bool Arm64VirtualAddressSpace::tryMapUserPage(physical_uintptr_t physical, void* address,
242 size_t flags, size_t* committedTablePages) {
243 if (flags & KernelMode || reinterpret_cast<uintptr_t>(address) >= ARM64_DIRECT_MAP_BASE) {
244 return false;
245 }
246 if (committedTablePages) {
247 *committedTablePages = 0;
248 }
249 return map(physical, address, flags);
250}
251
252bool Arm64VirtualAddressSpace::getMapping(void* address, physical_uintptr_t& physical,
253 size_t& flags) {
254 const uintptr_t value = reinterpret_cast<uintptr_t>(address);
255 Arm64VirtualAddressSpace& owner = value >= ARM64_DIRECT_MAP_BASE ? m_KernelSpace : *this;
256 LockGuard<Spinlock> guard(owner.m_Lock);
257 size_t level = 0;
258 uint64_t* entry = owner.findExistingEntry(value, &level);
259 if (!entry || !*entry || (level != 3 && !(*entry & Valid))) {
260 return false;
261 }
262 const uint64_t mask = (1ULL << (39 - level * 9)) - 1;
263 physical = (*entry & AddressMask & ~mask) | (reinterpret_cast<uintptr_t>(address) & mask);
264 flags = descriptorFlags(*entry);
265 return true;
266}
267
268bool Arm64VirtualAddressSpace::trySetFlags(void* address, size_t flags) {
269 const uintptr_t value = reinterpret_cast<uintptr_t>(address);
270 Arm64VirtualAddressSpace& owner = value >= ARM64_DIRECT_MAP_BASE ? m_KernelSpace : *this;
271 LockGuard<Spinlock> guard(owner.m_Lock);
272 uint64_t* entry = owner.findEntry(value, false);
273 if (!entry || !*entry) {
274 return false;
275 }
276 *entry = pageDescriptor(*entry & AddressMask, flags);
277 Processor::invalidate(address);
278 return true;
279}
280
281void Arm64VirtualAddressSpace::setFlags(void* address, size_t flags) {
282 if (!trySetFlags(address, flags)) {
283 panic("ARM64: setFlags on absent mapping");
284 }
285}
286
287bool Arm64VirtualAddressSpace::detachMapping(void* address, physical_uintptr_t& physical,
288 size_t& flags, size_t requiredFlags) {
289 const uintptr_t value = reinterpret_cast<uintptr_t>(address);
290 Arm64VirtualAddressSpace& owner = value >= ARM64_DIRECT_MAP_BASE ? m_KernelSpace : *this;
291 LockGuard<Spinlock> guard(owner.m_Lock);
292 uint64_t* entry = owner.findEntry(value, false);
293 if (!entry || !*entry) {
294 return false;
295 }
296 flags = descriptorFlags(*entry);
297 if ((flags & requiredFlags) != requiredFlags) {
298 return false;
299 }
300 physical = *entry & AddressMask;
301 *entry = 0;
302 Processor::invalidate(address);
303 return true;
304}
305
306bool Arm64VirtualAddressSpace::tryDetachUserPage(void* address, physical_uintptr_t expected) {
307 physical_uintptr_t physical = 0;
308 size_t flags = 0;
309 if (!getMapping(address, physical, flags) || physical != expected || (flags & KernelMode)) {
310 return false;
311 }
312 return detachMapping(address, physical, flags);
313}
314
316 physical_uintptr_t physical = 0;
317 size_t flags = 0;
318 if (!detachMapping(address, physical, flags)) {
319 panic("ARM64: unmap on absent mapping");
320 }
321}
322
323bool Arm64VirtualAddressSpace::handleCopyOnWriteFault(void* address, bool userMode) {
324 physical_uintptr_t old = 0;
325 size_t flags = 0;
326 if (!getMapping(address, old, flags) || !(flags & CopyOnWrite) ||
327 (flags & (NoAccess | WriteProtected | Swapped)) || (userMode && (flags & KernelMode))) {
328 return false;
329 }
330 physical_uintptr_t fresh = Arm64PhysicalMemoryManager::instance().tryAllocatePage();
331 if (!fresh) {
332 return false;
333 }
334 memcpy(reinterpret_cast<void*>(ARM64_DIRECT_MAP_BASE + fresh),
335 reinterpret_cast<const void*>(ARM64_DIRECT_MAP_BASE + old), PAGE_SIZE);
336 physical_uintptr_t detached = 0;
337 size_t detachedFlags = 0;
338 if (!detachMapping(address, detached, detachedFlags) || detached != old ||
339 !map(fresh, address, (flags | Write) & ~(CopyOnWrite | Borrowed | Shared))) {
340 Arm64PhysicalMemoryManager::instance().freePage(fresh);
341 return false;
342 }
343 Arm64PhysicalMemoryManager::instance().freePage(old);
344 return true;
345}
346
348 return allocateStack(this == &m_KernelSpace ? KERNEL_STACK_SIZE : USERSPACE_VIRTUAL_STACK_SIZE);
349}
350
352 if (!bytes) {
353 return allocateStack();
354 }
355 bytes = (bytes + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
356 uintptr_t top;
357 {
358 LockGuard<Spinlock> guard(m_Lock);
359 if (m_StackTop < bytes + PAGE_SIZE) {
360 return nullptr;
361 }
362 top = m_StackTop;
363 m_StackTop -= bytes + PAGE_SIZE;
364 }
365 size_t flags = Write | (this == &m_KernelSpace ? KernelMode : 0);
366 for (size_t offset = PAGE_SIZE; offset <= bytes; offset += PAGE_SIZE) {
367 physical_uintptr_t physical = Arm64PhysicalMemoryManager::instance().tryAllocatePage();
368 if (!physical || !map(physical, reinterpret_cast<void*>(top - offset), flags)) {
369 if (physical) {
370 Arm64PhysicalMemoryManager::instance().freePage(physical);
371 }
372 for (size_t mapped = PAGE_SIZE; mapped < offset; mapped += PAGE_SIZE) {
373 void* page = reinterpret_cast<void*>(top - mapped);
374 physical_uintptr_t old = 0;
375 size_t oldFlags = 0;
376 if (detachMapping(page, old, oldFlags)) {
377 Arm64PhysicalMemoryManager::instance().freePage(old);
378 }
379 }
380 return nullptr;
381 }
382 }
383 return new Stack(reinterpret_cast<void*>(top), bytes);
384}
385
387 if (!stack) {
388 return;
389 }
390 const uintptr_t base = reinterpret_cast<uintptr_t>(stack->getBase());
391 for (size_t offset = 0; offset < stack->getSize(); offset += PAGE_SIZE) {
392 void* page = reinterpret_cast<void*>(base + offset);
393 physical_uintptr_t physical = 0;
394 size_t flags = 0;
395 if (detachMapping(page, physical, flags)) {
396 Arm64PhysicalMemoryManager::instance().freePage(physical);
397 }
398 }
399 delete stack;
400}
401
402bool Arm64VirtualAddressSpace::cloneTable(uint64_t* destination, const uint64_t* source,
403 size_t level, bool copyOnWrite) {
404 for (size_t i = 0; i < 512; ++i) {
405 const uint64_t entry = source[i];
406 if (!entry) {
407 continue;
408 }
409 if (level < 3 && (entry & Valid) && (entry & Table)) {
410 physical_uintptr_t child = Arm64PhysicalMemoryManager::instance().tryAllocatePage();
411 if (!child) {
412 return false;
413 }
414 memset(tableAt(child), 0, PAGE_SIZE);
415 destination[i] = child | Valid | Table;
416 if (!cloneTable(tableAt(child), tableAt(entry & AddressMask), level + 1, copyOnWrite)) {
417 return false;
418 }
419 } else if (level == 3 && (entry & (1ULL << 6)) && (entry & Valid)) {
420 const physical_uintptr_t old = entry & AddressMask;
421 if (copyOnWrite && !(entry & SoftwareShared)) {
422 physical_uintptr_t fresh = Arm64PhysicalMemoryManager::instance().tryAllocatePage();
423 if (!fresh) {
424 return false;
425 }
426 memcpy(reinterpret_cast<void*>(ARM64_DIRECT_MAP_BASE + fresh),
427 reinterpret_cast<const void*>(ARM64_DIRECT_MAP_BASE + old), PAGE_SIZE);
428 destination[i] = ((entry & ~AddressMask) & ~SoftwareBorrowed) | fresh;
429 } else {
430 if (!(entry & SoftwareBorrowed)) {
431 Arm64PhysicalMemoryManager::instance().pin(old);
432 }
433 destination[i] = entry;
434 }
435 } else {
436 destination[i] = entry;
437 }
438 }
439 return true;
440}
441
442void Arm64VirtualAddressSpace::freeTable(uint64_t* table, size_t level, bool freeLeaves) {
443 for (size_t i = 0; i < 512; ++i) {
444 uint64_t entry = table[i];
445 if (!entry) {
446 continue;
447 }
448 if (level < 3 && (entry & Valid) && (entry & Table)) {
449 freeTable(tableAt(entry & AddressMask), level + 1, freeLeaves);
450 Arm64PhysicalMemoryManager::instance().freePage(entry & AddressMask);
451 } else if (level == 3 && freeLeaves && (entry & (1ULL << 6)) && (entry & Valid) &&
452 !(entry & (SoftwareBorrowed | SoftwareShared))) {
453 Arm64PhysicalMemoryManager::instance().freePage(entry & AddressMask);
454 }
455 table[i] = 0;
456 }
457}
458
460 auto* clone = new Arm64VirtualAddressSpace(false);
461 if (!clone) {
462 return nullptr;
463 }
464 LockGuard<Spinlock> guard(m_Lock);
465 if (!cloneTable(tableAt(clone->m_Root), tableAt(m_Root), 0, copyOnWrite)) {
466 delete clone;
467 return nullptr;
468 }
471 clone->m_StackTop = m_StackTop;
472 return clone;
473}
474
476 if (this == &m_KernelSpace) {
477 return;
478 }
479 LockGuard<Spinlock> guard(m_Lock);
480 uint64_t* root = tableAt(m_Root);
481 freeTable(root, 0, true);
482 m_Heap = reinterpret_cast<void*>(USERSPACE_VIRTUAL_HEAP);
484 m_StackTop = USERSPACE_VIRTUAL_STACK;
485 Processor::invalidate(nullptr);
486}
487
489 uintptr_t value = reinterpret_cast<uintptr_t>(address);
490 return value >= KERNEL_VIRTUAL_HEAP && value < KERNEL_VIRTUAL_HEAP_END;
491}
492
494 uintptr_t value = reinterpret_cast<uintptr_t>(address);
495 return value >= reinterpret_cast<uintptr_t>(m_Heap) &&
496 value < reinterpret_cast<uintptr_t>(m_HeapEnd);
497}
498
500 return reinterpret_cast<void*>(this == &m_KernelSpace ? KERNEL_VIRTUAL_HEAP_END
501 : USERSPACE_DYNAMIC_END);
502}
void pin(physical_uintptr_t page) override
void freePage(physical_uintptr_t page) override
bool memIsInHeap(void *address) override
bool isMapped(void *address) override
void freeStack(Stack *stack) override
void unmap(void *address) override
void setFlags(void *address, size_t flags) override
bool getMapping(void *address, physical_uintptr_t &physical, size_t &flags) override
bool handleCopyOnWriteFault(void *address, bool userMode) override
VirtualAddressSpace * clone(bool copyOnWrite=true) override
bool trySetFlags(void *address, size_t flags) override
bool detachMapping(void *address, physical_uintptr_t &physical, size_t &flags, size_t requiredFlags=0) override
bool map(physical_uintptr_t physical, void *address, size_t flags) override
bool isAddressValid(void *address) override
bool memIsInKernelHeap(void *address) override
static void invalidate(void *pAddress)
static VirtualAddressSpace * create()
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117