[boot] Restructure boot paging and program loading

Restructuring paging into an object that carries its page cache with it
and makes for simpler code. Program loading is also changed to not copy
the pages loaded from the file into new pages - we can impose a new
constraint that anything loaded by boot have a simple, page-aligned
layout so that we can just map the existing pages into the right
addresses. Also included are some linker script changes to help
accommodate this.
This commit is contained in:
Justin C. Miller
2023-02-05 22:02:41 -08:00
parent aba45b9b67
commit ab31825ab3
16 changed files with 406 additions and 301 deletions

View File

@@ -4,7 +4,6 @@
#include <util/pointers.h>
#include "allocator.h"
#include "console.h"
#include "error.h"
#include "loader.h"
#include "memory.h"
@@ -20,7 +19,7 @@ using memory::page_size;
// Flags: 0 0 0 1 0 0 0 0 0 0 0 1 = 0x0101
// IGN | | | | | | | | +- Present
// | | | | | | | +--- Writeable
// | | | | | | +----- Usermode access (supervisor only)
// | | | | | | +----- Usermode access (Supervisor only)
// | | | | | +------- PWT (determining memory type for page)
// | | | | +---------- PCD (determining memory type for page)
// | | | +------------ Accessed flag (not accessed yet)
@@ -33,7 +32,7 @@ constexpr uint64_t page_flags = 0x101;
// Flags: 0 0 0 0 1 1 0 0 0 1 0 1 1 = 0x018b
// | IGN | | | | | | | | +- Present
// | | | | | | | | +--- Writeable
// | | | | | | | +----- Supervisor only
// | | | | | | | +----- Usermode access (Supervisor only)
// | | | | | | +------- PWT (determining memory type for page)
// | | | | | +---------- PCD (determining memory type for page)
// | | | | +------------ Accessed flag (not accessed yet)
@@ -57,17 +56,22 @@ constexpr uint64_t huge_page_flags = 0x18b;
constexpr uint64_t table_flags = 0x003;
inline void *
pop_pages(util::counted<void> &pages, size_t count)
/// Struct to allow easy accessing of a memory page being used as a page table.
struct page_table
{
if (count > pages.count)
error::raise(uefi::status::out_of_resources, L"Page table cache empty", 0x7ab1e5);
uint64_t entries[512];
void *next = pages.pointer;
pages.pointer = util::offset_pointer(pages.pointer, count*page_size);
pages.count -= count;
return next;
}
inline page_table * get(int i, uint16_t *flags = nullptr) const {
uint64_t entry = entries[i];
if ((entry & 1) == 0) return nullptr;
if (flags) *flags = entry & 0xfff;
return reinterpret_cast<page_table *>(entry & ~0xfffull);
}
inline void set(int i, void *p, uint16_t flags) {
entries[i] = reinterpret_cast<uint64_t>(p) | (flags & 0xfff);
}
};
/// Iterator over page table entries.
template <unsigned D = 4>
@@ -75,25 +79,23 @@ class page_entry_iterator
{
public:
/// Constructor.
/// \arg virt Virtual address this iterator is starting at
/// \arg pml4 Root of the page tables to iterate
/// \arg pages Cache of usable table pages
/// \arg virt Virtual address this iterator is starting at
/// \arg pgr The pager, used for its page table cache
page_entry_iterator(
uintptr_t virt,
page_table *pml4,
util::counted<void> &pages) :
m_pages(pages)
pager &pgr) :
m_pager(pgr)
{
m_table[0] = pml4;
m_table[0] = pgr.m_pml4;
for (unsigned i = 0; i < D; ++i) {
m_index[i] = static_cast<uint16_t>((virt >> (12 + 9*(3-i))) & 0x1ff);
ensure_table(i);
}
}
uintptr_t vaddress() const {
uintptr_t vaddress(unsigned level = D) const {
uintptr_t address = 0;
for (unsigned i = 0; i < D; ++i)
for (unsigned i = 0; i < level; ++i)
address |= static_cast<uintptr_t>(m_index[i]) << (12 + 9*(3-i));
if (address & (1ull<<47)) // canonicalize the address
address |= (0xffffull<<48);
@@ -114,6 +116,7 @@ public:
}
uint64_t & operator*() { return entry(D-1); }
uint64_t operator[](int i) { return i < D ? m_index[i] : 0; }
private:
inline uint64_t & entry(unsigned level) { return m_table[level]->entries[m_index[level]]; }
@@ -129,7 +132,7 @@ private:
uint64_t & parent_ent = entry(level - 1);
if (!(parent_ent & 1)) {
page_table *table = reinterpret_cast<page_table*>(pop_pages(m_pages, 1));
page_table *table = reinterpret_cast<page_table*>(m_pager.pop_pages(1));
parent_ent = (reinterpret_cast<uintptr_t>(table) & ~0xfffull) | table_flags;
m_table[level] = table;
} else {
@@ -137,44 +140,62 @@ private:
}
}
util::counted<void> &m_pages;
pager &m_pager;
page_table *m_table[D];
uint16_t m_index[D];
};
static void
add_offset_mappings(page_table *pml4, util::counted<void> &pages)
pager::pager(uefi::boot_services *bs) :
m_bs {bs}
{
uintptr_t phys = 0;
uintptr_t virt = bootproto::mem::linear_offset; // Start of offset-mapped area
size_t page_count = 64 * 1024; // 64 TiB of 1 GiB pages
constexpr size_t GiB = 0x40000000ull;
status_line status(L"Allocating initial page tables");
page_entry_iterator<2> iterator{virt, pml4, pages};
// include 1 extra for kernel PML4
static constexpr size_t tables_needed = pd_tables + extra_tables + 1;
while (true) {
*iterator = phys | huge_page_flags;
if (--page_count == 0)
break;
void *addr = g_alloc.allocate_pages(tables_needed, alloc_type::page_table, true);
m_bs->set_mem(addr, tables_needed * page_size, 0);
m_table_pages = { .pointer = addr, .count = tables_needed };
iterator.increment();
phys += GiB;
}
}
static void
add_kernel_pds(page_table *pml4, util::counted<void> &pages)
{
constexpr unsigned start = arch::kernel_root_index;
constexpr unsigned end = arch::table_entries;
for (unsigned i = start; i < end; ++i)
pml4->set(i, pop_pages(pages, 1), table_flags);
create_kernel_tables();
}
void
add_current_mappings(page_table *new_pml4)
pager::map_pages(
uintptr_t phys, uintptr_t virt, size_t count,
bool write_flag, bool exe_flag)
{
if (!count)
return;
page_entry_iterator<4> iterator {virt, *this};
uint64_t flags = page_flags;
if (!exe_flag) flags |= (1ull << 63); // set NX bit
if (write_flag) flags |= 2;
while (true) {
uint64_t entry = phys | flags;
*iterator = entry;
if (--count == 0)
break;
iterator.increment();
phys += page_size;
}
}
void
pager::update_kernel_args(bootproto::args *args)
{
status_line status {L"Updating kernel args"};
args->pml4 = reinterpret_cast<void*>(m_pml4);
args->page_tables = m_table_pages;
}
void
pager::add_current_mappings()
{
// Get the pointer to the current PML4
page_table *old_pml4 = 0;
@@ -185,98 +206,49 @@ add_current_mappings(page_table *new_pml4)
for (int i = 0; i < halfway; ++i) {
uint64_t entry = old_pml4->entries[i];
if (entry & 1)
new_pml4->entries[i] = entry;
m_pml4->entries[i] = entry;
}
}
void
allocate_tables(bootproto::args *args)
page_table *
pager::pop_pages(size_t count)
{
status_line status(L"Allocating initial page tables");
if (count > m_table_pages.count)
error::raise(uefi::status::out_of_resources, L"Page table cache empty", 0x7ab1e5);
static constexpr size_t pd_tables = 256; // number of pages for kernelspace PDs
static constexpr size_t extra_tables = 64; // number of extra pages
// number of pages for kernelspace PDs + PML4
static constexpr size_t kernel_tables = pd_tables + 1;
static constexpr size_t tables_needed = kernel_tables + extra_tables;
void *addr = g_alloc.allocate_pages(tables_needed, alloc_type::page_table, true);
page_table *pml4 = reinterpret_cast<page_table*>(addr);
args->pml4 = pml4;
args->page_tables = { .pointer = pml4 + 1, .count = tables_needed - 1 };
console::print(L" First page (pml4) at: 0x%lx\r\n", pml4);
add_kernel_pds(pml4, args->page_tables);
add_offset_mappings(pml4, args->page_tables);
//console::print(L" Set up initial mappings, %d spare tables.\r\n", args->table_count);
}
template <typename E>
constexpr bool has_flag(E set, E flag) {
return
(static_cast<uint64_t>(set) & static_cast<uint64_t>(flag)) ==
static_cast<uint64_t>(flag);
page_table *next = reinterpret_cast<page_table*>(m_table_pages.pointer);
m_table_pages.pointer = util::offset_pointer(m_table_pages.pointer, count*page_size);
m_table_pages.count -= count;
return next;
}
void
map_pages(
bootproto::args *args,
uintptr_t phys, uintptr_t virt,
size_t count, bool write_flag, bool exe_flag)
pager::create_kernel_tables()
{
if (!count)
return;
m_pml4 = pop_pages(1);
page_table *pds = pop_pages(pd_tables);
paging::page_table *pml4 =
reinterpret_cast<paging::page_table*>(args->pml4);
// Add PDs for all of high memory into the PML4
constexpr unsigned start = arch::kernel_root_index;
constexpr unsigned end = arch::table_entries;
for (unsigned i = start; i < end; ++i)
m_pml4->set(i, &pds[i - start], table_flags);
page_entry_iterator<4> iterator{virt, pml4, args->page_tables};
uint64_t flags = page_flags;
if (!exe_flag)
flags |= (1ull << 63); // set NX bit
if (write_flag)
flags |= 2;
// Add the linear offset-mapped area
uintptr_t phys = 0;
uintptr_t virt_base = bootproto::mem::linear_offset;
size_t page_count = 64 * 1024; // 64 TiB of 1 GiB pages
constexpr size_t GiB = 0x40000000ull;
page_entry_iterator<2> iterator {virt_base, *this};
while (true) {
*iterator = phys | flags;
if (--count == 0)
*iterator = phys | huge_page_flags;
if (--page_count == 0)
break;
iterator.increment();
phys += page_size;
phys += GiB;
}
}
void
map_section(
bootproto::args *args,
const bootproto::program_section &section)
{
using bootproto::section_flags;
map_pages(
args,
section.phys_addr,
section.virt_addr,
memory::bytes_to_pages(section.size),
has_flag(section.type, section_flags::write),
has_flag(section.type, section_flags::execute));
}
void
map_program(
bootproto::args *args,
bootproto::program &program)
{
for (auto &section : program.sections)
paging::map_section(args, section);
}
} // namespace paging
} // namespace boot