This ended up being unused, but still probably useful: Refactor out the "find" logic of page_tree::find_or_add (note that this is different than the "find" logic of page_tree::find, as it potentially modifies the tree to add a location to accommodate the page being searched for) into a new page_tree::get_entry method. That was then used to add an add_existing method for inserting pages into the page_tree.
168 lines
5.0 KiB
C++
168 lines
5.0 KiB
C++
#include <string.h>
|
|
|
|
#include <arch/memory.h>
|
|
|
|
#include "assert.h"
|
|
#include "frame_allocator.h"
|
|
#include "page_tree.h"
|
|
|
|
// Page tree levels map the following parts of an offset. Note the xxx part of
|
|
// the offset but represent the bits of the actual sub-page virtual address.
|
|
// (Also note that level 0's entries are physical page addrs, the rest map
|
|
// other page_tree nodes)
|
|
//
|
|
// Level 0: 0000 0000 0003 fxxx 64 pages / 256 KiB
|
|
// Level 1: 0000 0000 00fc 0xxx 4K pages / 16 MiB -- 24-bit addressing
|
|
// Level 2: 0000 0000 3f00 0xxx 256K pages / 1 GiB
|
|
// Level 3: 0000 000f c000 0xxx 16M pages / 64 GiB -- 36-bit addressing
|
|
// Level 4: 0000 03f0 0000 0xxx 1G pages / 4 TiB
|
|
// Level 5: 0000 fc00 0000 0xxx 64G pages / 256 TiB -- 48-bit addressing
|
|
//
|
|
// Not supported until 5-level paging:
|
|
// Level 6: 003f 0000 0000 0xxx 4T pages / 16 PiB -- 54-bit addressing
|
|
// Level 7: 0fc0 0000 0000 0xxx 256T pages / 1 EiB -- 60-bit addressing
|
|
|
|
static_assert(sizeof(page_tree) == 66 * sizeof(uintptr_t));
|
|
|
|
static constexpr unsigned max_level = 5;
|
|
static constexpr unsigned bits_per_level = 6;
|
|
|
|
inline int level_shift(uint8_t level) { return level * bits_per_level + arch::frame_bits; }
|
|
inline uint64_t level_mask(uint8_t level) { return ~0x3full << level_shift(level); }
|
|
inline int index_for(uint64_t off, uint8_t level) { return (off >> level_shift(level)) & 0x3full; }
|
|
|
|
page_tree::page_tree(uint64_t base, uint8_t level) :
|
|
m_base {base & level_mask(level)},
|
|
m_level {level}
|
|
{
|
|
memset(m_entries, 0, sizeof(m_entries));
|
|
}
|
|
|
|
page_tree::~page_tree()
|
|
{
|
|
if (m_level) {
|
|
for (auto &e : m_entries)
|
|
delete e.child;
|
|
} else {
|
|
auto &fa = frame_allocator::get();
|
|
for (auto &e : m_entries) {
|
|
if (e.entry & 1)
|
|
fa.free(e.entry & ~0xfffull, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
page_tree::contains(uint64_t offset, uint8_t &index) const
|
|
{
|
|
return (offset & level_mask(m_level)) == m_base;
|
|
}
|
|
|
|
uintptr_t &
|
|
page_tree::get_entry(page_tree * &root, uint64_t offset)
|
|
{
|
|
page_tree *level0 = nullptr;
|
|
|
|
if (!root) {
|
|
// There's no root yet, just make a level0 and make it
|
|
// the root.
|
|
level0 = new page_tree(offset, 0);
|
|
root = level0;
|
|
} else {
|
|
// Find or insert an existing level0
|
|
page_tree **parent = &root;
|
|
page_tree *node = root;
|
|
uint8_t parent_level = max_level + 1;
|
|
|
|
while (node) {
|
|
uint8_t level = node->m_level;
|
|
uint8_t index = 0;
|
|
if (!node->contains(offset, index)) {
|
|
// We found a valid parent but the slot where this node should
|
|
// go contains another node. Insert an intermediate parent of
|
|
// this node and a new level0 into the parent.
|
|
uint64_t other = node->m_base;
|
|
uint8_t lcl = parent_level;
|
|
while (index_for(offset, lcl) == index_for(other, lcl)) --lcl;
|
|
|
|
page_tree *inter = new page_tree(offset, lcl);
|
|
inter->m_entries[index_for(other, lcl)].child = node;
|
|
*parent = inter;
|
|
|
|
level0 = new page_tree(offset, 0);
|
|
inter->m_entries[index_for(offset, lcl)].child = level0;
|
|
break;
|
|
}
|
|
|
|
if (!level) {
|
|
level0 = node;
|
|
break;
|
|
}
|
|
|
|
parent = &node->m_entries[index].child;
|
|
node = *parent;
|
|
}
|
|
|
|
kassert( node || parent, "Both node and parent were null in page_tree::get_entry");
|
|
|
|
if (!node) {
|
|
// We found a parent with an empty spot where this node should
|
|
// be. Insert a new level0 there.
|
|
level0 = new page_tree(offset, 0);
|
|
*parent = level0;
|
|
}
|
|
}
|
|
|
|
kassert(level0, "Got through page_tree::get_entry without a level0");
|
|
|
|
uint8_t index = index_for(offset, 0);
|
|
return level0->m_entries[index].entry;
|
|
}
|
|
|
|
bool
|
|
page_tree::find(const page_tree *root, uint64_t offset, uintptr_t &page)
|
|
{
|
|
page_tree const *node = root;
|
|
while (node) {
|
|
uint8_t level = node->m_level;
|
|
uint8_t index = 0;
|
|
if (!node->contains(offset, index))
|
|
return false;
|
|
|
|
if (!level) {
|
|
uintptr_t entry = node->m_entries[index].entry;
|
|
page = entry & ~0xfffull;
|
|
return (entry & 1); // bit 0 marks 'present'
|
|
}
|
|
|
|
node = node->m_entries[index].child;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
page_tree::find_or_add(page_tree * &root, uint64_t offset, uintptr_t &page)
|
|
{
|
|
uint64_t &ent = get_entry(root, offset);
|
|
|
|
if (!(ent & 1)) {
|
|
// No entry for this page exists, so make one
|
|
if (!frame_allocator::get().allocate(1, &ent))
|
|
return false;
|
|
ent |= 1;
|
|
}
|
|
|
|
page = ent & ~0xfffull;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
page_tree::add_existing(page_tree * &root, uint64_t offset, uintptr_t page)
|
|
{
|
|
uint64_t &ent = get_entry(root, offset);
|
|
kassert(!(ent & 1), "Replacing existing mapping in page_tree::add_existing");
|
|
|
|
ent = page | 1;
|
|
}
|