Repository navigation
Expand file tree
/
Copy pathvm.cc
More file actions
315 lines (290 loc) · 12 KB
/
Copy pathvm.cc
File metadata and controls
315 lines (290 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#include "core/vm.h"
#include <cxx/string.h>
#include <cxx/units.h>
#include <cxx/utility.h>
#include <libkernel/assert.h>
#include <libkernel/print.h>
#include <libkernel/status.h>
#include "core/constants.h"
#include "core/loader.h"
#include "core/slab.h"
#include "core/thread.h"
#include "platform/arch.h"
#include "platform/mmu.h"
using namespace core::mem;
using platform::mmu::PageDescriptor;
using platform::mmu::TableDescriptor;
namespace core::mem {
Page* pfndb_arr_start;
}
static Page* pfndb_arr_end;
static uint64_t n_freePages;
static uint64_t n_totalPages;
namespace core::mem {
PageList pfndb_freelist{};
PageList InitrdVmo{};
PageList PerfVmo{};
} // namespace core::mem
extern "C" int perf_monitor_start_;
extern "C" int perf_monitor_end_;
//===----------------------------------------------------------------------===//
// Physical memory allocator code + helpers
//===----------------------------------------------------------------------===//
static inline uint64_t page_to_paddr(Page* p) {
// Calculate i such that pfndb_arr[i] = page, this will give us the paddr
auto i = p - pfndb_arr_start;
return i * 4_KiB;
}
void core::mem::init(loader::LoaderData& args) {
// Since the freelist is with loader addresses, we need to re-create
// the freelist so the addresses are now within the kernel aspace.
pfndb_arr_start = reinterpret_cast<Page*>(args.pfndb_range.start + KernelOffset);
pfndb_arr_end = reinterpret_cast<Page*>(args.pfndb_range.end + KernelOffset);
n_freePages = 0;
n_totalPages = 0;
InitrdVmo = PageList{};
for (auto* page = pfndb_arr_start; page != pfndb_arr_end; page++) {
const auto paddr = page_to_paddr(page);
if (paddr >= args.initrd_range.start && paddr < args.initrd_range.end) {
InitrdVmo.push_back(page);
page->flags = 1;
} else if (cxx::bits<0, 0>(page->flags) == 0) {
n_freePages++;
pfndb_freelist.push_back(page);
}
n_totalPages++;
}
// Print some early statistics about the allocator
libkernel::log("PMM initialized with %lld/%lld free pages (%lld allocated)\r\n", n_freePages,
n_totalPages, n_totalPages - n_freePages);
// Now populate the performance monitor VMO
uint64_t perf_start_vaddrs = reinterpret_cast<uint64_t>(&perf_monitor_start_);
uint64_t perf_end_vaddrs = reinterpret_cast<uint64_t>(&perf_monitor_end_);
libkernel::log("Perf start: 0x%llX, Perf end: 0x%llX\r\n", perf_start_vaddrs, perf_end_vaddrs);
for (auto addrs = perf_start_vaddrs; addrs < perf_end_vaddrs; addrs += 4_KiB) {
auto pfn = traverse(addrs).phys_addr;
auto page = phys_to_pfn(pfn * 4_KiB);
PerfVmo.push_back(page);
page->flags = 1;
}
}
Result<uint64_t> core::mem::alloc_page(PageList& out) {
if (pfndb_freelist.empty()) return E::OUT_OF_MEMORY;
auto page = pfndb_freelist.pop_back();
out.push_back(page);
page->flags = 1;
return page_to_paddr(page);
}
//===----------------------------------------------------------------------===//
// Create explicit virtual memory mappings
//===----------------------------------------------------------------------===//
static Result<void> make_next_level_table(TableDescriptor* table, void* next, uint64_t idx,
PageList& out) {
if (table[idx].valid) return E::OK;
auto pa_wrapped = alloc_page(out);
if (!pa_wrapped) return pa_wrapped.status();
table[idx] = UnifyDescriptors(TableDescriptor{
.valid = 1,
.table_phys_addr = pa_wrapped.unwrap() / 4_KiB,
});
platform::arch::flush_tlb_at(reinterpret_cast<uint64_t>(next));
cxx::memset(next, 0, 4_KiB);
return E::OK;
}
Result<void> core::mem::map(uint64_t va, PageDescriptor pa, PageList& out) {
// 1. Calculate all the table indices from the input address
// See Figure D8-3 Arm Architecture Reference Manual (DDI 0487)
auto l3idx = cxx::bits<20, 12>(va);
auto l2idx = cxx::bits<29, 21>(va);
auto l1idx = cxx::bits<38, 30>(va);
auto l0idx = cxx::bits<47, 39>(va);
auto prefix = cxx::bits<63, 48>(va);
// 2. Check VA and grab all the table addresses via recursive mappings.
// Note: Some of these tables (excep L0) may not even exist yet, so be careful.
assert(prefix == 0 || prefix == 0xFFFF, "The given virtual address is non-canonical!");
auto l0table = reinterpret_cast<TableDescriptor*>(idx_to_va(prefix, 511, 511, 511, 511));
auto l1table = reinterpret_cast<TableDescriptor*>(idx_to_va(prefix, 511, 511, 511, l0idx));
auto l2table = reinterpret_cast<TableDescriptor*>(idx_to_va(prefix, 511, 511, l0idx, l1idx));
auto l3table = reinterpret_cast<PageDescriptor*>(idx_to_va(prefix, 511, l0idx, l1idx, l2idx));
// 3. Now go through each level, allocating a table if needed
PageList allocated{};
Result<void> result{E::OK};
result = make_next_level_table(l0table, l1table, l0idx, allocated);
if (!result) return free_pages(cxx::move(allocated)), result.status();
result = make_next_level_table(l1table, l2table, l1idx, allocated);
if (!result) return free_pages(cxx::move(allocated)), result.status();
result = make_next_level_table(l2table, l3table, l2idx, allocated);
if (!result) return free_pages(cxx::move(allocated)), result.status();
// 4. Now we have the L3 table, populate it with the final translation
l3table[l3idx] = pa;
out.append(cxx::move(allocated));
return E::OK;
}
PageDescriptor core::mem::traverse(uint64_t va) {
// 1. Calculate all the table indices from the input address
// See Figure D8-3 Arm Architecture Reference Manual (DDI 0487)
auto l3idx = cxx::bits<20, 12>(va);
auto l2idx = cxx::bits<29, 21>(va);
auto l1idx = cxx::bits<38, 30>(va);
auto l0idx = cxx::bits<47, 39>(va);
auto prefix = cxx::bits<63, 48>(va);
// 2. Check VA and grab all the table addresses via recursive mappings.
// Note: Some of these tables (excep L0) may not even exist yet, so be careful.
assert(prefix == 0 || prefix == 0xFFFF, "The given virtual address is non-canonical!");
auto l3table = reinterpret_cast<PageDescriptor*>(idx_to_va(prefix, 511, l0idx, l1idx, l2idx));
return l3table[l3idx];
}
//===----------------------------------------------------------------------===//
// Region::*
//===----------------------------------------------------------------------===//
PageDescriptor Region::get_page_descriptor(uint64_t phys) {
const uint8_t attr_idx = flags_.is_device ? 0 : 1;
const uint8_t uxn_table = flags_.executable ? 0 : 1;
// FIXME(kevin): Add back permissions, the thing is we can only do
// kernel R/O + user R/O and not kernel R/W + user R/O
uint8_t ap = 0b01;
// if (region->flags_.readable && region->flags_.writable) ap = 0b01;
// if (region->flags_.readable && !region->flags_.writable) ap = 0b11;
return PageDescriptor{
.valid = 1,
.attr_index = attr_idx,
.ap = ap,
.phys_addr = phys / 4_KiB,
.uxn_table = uxn_table,
};
}
void Region::dump() const {
char flags[4]{0};
flags[0] = flags_.readable ? 'R' : '-';
flags[1] = flags_.writable ? 'W' : '-';
flags[2] = flags_.executable ? 'X' : '-';
char const* type = flags_.shared ? "Shared" : flags_.is_device ? "Device" : "Normal";
char const* grows =
flags_.lazy_commit ? (flags_.grows_down ? "Can grow down" : "Can grow") : "Fixed";
libkernel::log(LL::DEBUG, "Region [%s]\r\n", name_);
libkernel::log(LL::DEBUG, " -> Range 0x%lX-0x%lX (0x%lX bytes)\r\n", base_, base_ + size_,
size_);
libkernel::log(LL::DEBUG, " -> Flags (RWX): %s, Type: %s, %s\r\n", flags, type, grows);
}
Result<void> Region::commit(uint64_t address) {
// 1. Check to see if the region is eligible for lazy-commit
if (!(address >= base_ && address < base_ + size_)) return E::INVALID;
if (!flags_.lazy_commit) return E::INVALID;
// 2. Handle device lazy-commit differently than normal lazy-commit.
// a. For device regions, we should check to see if it's a valid DevicePage.
// b. For normal regions, just allocate the page and then map it.
if (flags_.is_device) {
auto page = cxx::align_down(address, 4_KiB);
for (auto paddr : DevicePages) {
if (!(paddr >= page && paddr < page + 4_KiB)) continue;
const auto pd = get_page_descriptor(paddr);
auto res = map(address, pd, aspace_->mmu_pages_);
if (!res) return res.status();
platform::arch::flush_tlb_at(address);
return E::OK;
}
return E::INVALID;
} else {
PageList pages{};
auto phys = core::mem::alloc_page(pages);
if (!phys) return phys.status();
const auto virt = cxx::align_down(address, 4_KiB);
const auto pd = get_page_descriptor(phys.unwrap());
auto res = map(virt, pd, aspace_->mmu_pages_);
if (!res) {
free_pages(cxx::move(pages));
return res.status();
}
pages_->append(cxx::move(pages));
platform::arch::flush_tlb_at(virt);
}
return E::OK;
}
void Region::activate(bool flush) {
// 1. If the region grows down, allocate the last page first
const auto grows_down = flags_.lazy_commit && flags_.grows_down;
auto addr = !grows_down ? base_ : base_ + size_ - 4_KiB;
// 2. Dump the region
dump();
// 3. For each page in the region, allocate it to the `addr` pointer
for (auto page : *pages_) {
const auto pd = get_page_descriptor(page_to_paddr(page));
auto result = map(addr, pd, aspace_->mmu_pages_);
assert(result, "Failed to map page");
addr = grows_down ? addr - 4_KiB : addr + 4_KiB;
}
if (flush) {
platform::arch::flush_tlb();
}
}
//===----------------------------------------------------------------------===//
// AddressSpace::*
//===----------------------------------------------------------------------===//
AddressSpace::AddressSpace(Page* l0table)
: regions_{}, mmu_pages_{}, l0table_{page_to_paddr(l0table)} {
mmu_pages_.push_back(l0table);
}
void AddressSpace::activate() {
assert(&(thread::cur_thread()->aspace) == this);
for (auto region : regions_) region->activate(false);
platform::arch::flush_tlb();
}
Result<Region*> AddressSpace::add_region(char const* name, Range range, Region::Flags flags) {
// 1. Adjust the flags for the region
if (!flags.is_device) flags.shared = 0;
if (flags.writable) flags.readable = 1;
if (!flags.lazy_commit) flags.grows_down = 0;
// 2a. Allocate the region object
const auto base = range.start;
const auto size = range.end - range.start;
Region* region;
{
auto buf = core::slab::alloc<Region>();
if (!buf) return buf.status();
region = new (buf.unwrap()) Region(base, size, flags, name);
}
// 2b. Allocate the page object for the region
PageList* pages;
{
auto buf = core::slab::alloc<PageList>();
if (!buf) return slab::free(region), buf.status();
pages = new (buf.unwrap()) PageList();
}
// 2c. Calculate + allocate the pages needed for the region
const auto n_pages = flags.lazy_commit ? 1 : size / 4_KiB;
for (auto i = 0UL; i < n_pages; i++) {
auto res = alloc_page(*pages);
if (!res) {
free_pages(cxx::move(*pages));
slab::free(pages);
slab::free(region);
return res.status();
}
}
// 3. Populate the region object and register it
region->pages_ = pages;
region->aspace_ = this;
regions_.push_back(region);
return region;
}
Result<Region*> AddressSpace::add_region(char const* name, uint64_t base, PageList* vmo,
Region::Flags flags) {
// 1. Adjust the flags for the region
flags.shared = 1;
flags.lazy_commit = 0;
flags.grows_down = 0;
if (flags.writable) flags.readable = 1;
// 2. Allocate the region object
Region* region;
{
auto buf = core::slab::alloc<Region>();
auto size = vmo->size() * 4_KiB;
if (!buf) return buf.status();
region = new (buf.unwrap()) Region(base, size, flags, name);
}
// 3. Populate the region object and register it
region->pages_ = vmo;
region->aspace_ = this;
regions_.push_back(region);
return region;
}