feat: add page table

This commit is contained in:
Katharina 2026-07-04 11:30:33 +02:00
parent 61dfda6a70
commit ada1859910
8 changed files with 435 additions and 13 deletions

View file

@ -0,0 +1,7 @@
#pragma once
#include "memory/pointer.h"
namespace apic {
void startupCore(paddr_t code, paddr_t stack);
}

View file

@ -0,0 +1,36 @@
#pragma once
#include "memory/pointer.h"
#include "util/optional.h"
struct PageTable {
struct flags_t {
enum class granularity_t : uint8_t {
page_4KiB,
page_2MiB,
page_1GiB,
};
bool writeable : 1; // allow writes
bool user_accessible : 1; // allow ring-3 access
bool write_through : 1; // write-through instead of write-back
bool cache_disabled : 1; // uncached (MMIO)
bool execute_disabled : 1; // forbid instruction fetch
bool global : 1; // keep in TLB across CR3 switches
granularity_t granularity : 2; // mapping size
};
struct info_t {
flags_t flags;
paddr_t address;
};
static PageTable fromCurrent();
void activate();
bool map(void* vaddress, paddr_t paddress, flags_t flags);
void unmap(void* vaddress);
optional<info_t> get(void* vaddress);
private:
paddr_t l4;
};

View file

@ -3,6 +3,7 @@
#include "util/number.h"
constexpr uint64_t high_base = 0xFFFF800000000000;
constexpr uint64_t uncached_base = high_base + (uint64_t{1} << 38);
#define read_symbol(symbol) \
[]() { \
@ -18,6 +19,11 @@ struct paddr_t {
T* access() {
return reinterpret_cast<T*>(address + high_base);
}
template<typename T>
T* access_uc() {
return reinterpret_cast<T*>(address + uncached_base);
}
};
inline void* operator new(size_t, void* p) {

View file

@ -33,13 +33,13 @@ SECTIONS
} > START_MEMORY_PV
.text : AT(ADDR(.text) - high_base) {
__text_start__ = .;
*(EXCLUDE_FILE(*trampoline*) .text .text.*)
*(EXCLUDE_FILE(*trampoline*) .iplt)
*(.text .text.*)
*(.iplt)
__text_end__ = .;
} > HIGH_KERNEL_V
.rodata : AT(ADDR(.rodata) - high_base) {
__rodata_start__ = .;
*(EXCLUDE_FILE(*trampoline*) .rodata .rodata.*)
*(.rodata .rodata.*)
. = ALIGN(8);
__reserved_ranges_start__ = .;
KEEP(*(.reserved_ranges))
@ -52,37 +52,37 @@ SECTIONS
} > HIGH_KERNEL_V
.data : AT(ADDR(.data) - high_base) {
__data_start__ = .;
*(EXCLUDE_FILE(*trampoline*) .data .data.*)
*(EXCLUDE_FILE(*trampoline*) .got .got.plt .igot.plt .data.rel.ro .data.rel.ro.*)
*(.data .data.*)
*(.got .got.plt .igot.plt .data.rel.ro .data.rel.ro.*)
__data_end__ = .;
} > HIGH_KERNEL_V
.bss (NOLOAD) : AT(ADDR(.bss) - high_base) {
__bss_start__ = .;
*(EXCLUDE_FILE(*trampoline*) .bss .bss.*)
*(.bss .bss.*)
__bss_end__ = .;
} > HIGH_KERNEL_V
.trampoline_text : {
__trampoline_text_start__ = .;
*trampoline*(.text)
*(.trampoline_text)
__trampoline_text_end__ = .;
} > TRAMPOLINE_PV
.trampoline_data : {
__trampoline_data_start__ = .;
*trampoline*(.data)
*(.trampoline_data)
__trampoline_data_end__ = .;
} > TRAMPOLINE_PV
.trampoline_rodata : {
__trampoline_rodata_start__ = .;
*trampoline*(.rodata)
*(.trampoline_rodata)
__trampoline_rodata_end__ = .;
} > TRAMPOLINE_PV
.trampoline_bss (NOLOAD) : {
__trampoline_bss_start__ = .;
*trampoline*(.bss)
*(.trampoline_bss)
__trampoline_bss_end__ = .;
} > TRAMPOLINE_PV

0
kernel/src/apic/core.cpp Normal file
View file

View file

@ -0,0 +1,349 @@
#include "memory/pagetable.h"
#include "memory/allocator.h"
#include "util/function.h"
template<typename EntryType>
struct entry_table_t {
EntryType entries[512];
};
struct frame_entry_t {
static constexpr size_t level = 1;
uint64_t present : 1;
uint64_t writeable : 1;
uint64_t user_accessible : 1;
uint64_t write_through : 1;
uint64_t cache_disabled : 1;
uint64_t accessed : 1;
uint64_t dirty : 1;
uint64_t page_attribute_table : 1;
uint64_t global : 1;
uint64_t available_low : 3;
uint64_t frame_address : 40;
uint64_t available_high : 11;
uint64_t execute_disabled : 1;
};
template<typename NextEntryType>
struct subtable_entry_t {
using next_table_t = entry_table_t<NextEntryType>;
static constexpr size_t level = NextEntryType::level + 1;
uint64_t present : 1;
uint64_t writeable : 1;
uint64_t user_accessible : 1;
uint64_t write_through : 1;
uint64_t cache_disabled : 1;
uint64_t accessed : 1;
uint64_t ignored_dirty : 1;
uint64_t huge_page : 1;
uint64_t ignored_global : 1;
uint64_t available_low : 3;
uint64_t table_address : 40;
uint64_t available_high : 11;
uint64_t execute_disabled : 1;
};
template<size_t level_>
struct huge_frame_entry_t {
static constexpr size_t level = level_;
uint64_t present : 1;
uint64_t writeable : 1;
uint64_t user_accessible : 1;
uint64_t write_through : 1;
uint64_t cache_disabled : 1;
uint64_t accessed : 1;
uint64_t dirty : 1;
uint64_t huge_page : 1;
uint64_t global : 1;
uint64_t available_low : 3;
uint64_t page_attribute_table : 1;
uint64_t frame_address : 39;
uint64_t available_high : 11;
uint64_t execute_disabled : 1;
};
using frame_2MiB_entry_t = huge_frame_entry_t<2>;
using frame_1GiB_entry_t = huge_frame_entry_t<3>;
template<typename NextEntryType>
union table_or_huge_entry_t {
static constexpr size_t level = NextEntryType::level + 1;
subtable_entry_t<NextEntryType> subtable;
huge_frame_entry_t<NextEntryType::level + 1> huge;
};
using level1_entry_t = frame_entry_t;
using level2_entry_t = table_or_huge_entry_t<level1_entry_t>;
using level3_entry_t = table_or_huge_entry_t<level2_entry_t>;
using level4_entry_t = table_or_huge_entry_t<level3_entry_t>;
using level1_table_t = entry_table_t<level1_entry_t>;
using level2_table_t = entry_table_t<level2_entry_t>;
using level3_table_t = entry_table_t<level3_entry_t>;
using level4_table_t = entry_table_t<level4_entry_t>;
static_assert(sizeof(frame_2MiB_entry_t) == sizeof(uint64_t));
static_assert(sizeof(frame_1GiB_entry_t) == sizeof(uint64_t));
static_assert(sizeof(level1_entry_t) == sizeof(uint64_t));
static_assert(sizeof(level2_entry_t) == sizeof(uint64_t));
static_assert(sizeof(level3_entry_t) == sizeof(uint64_t));
static_assert(sizeof(level4_entry_t) == sizeof(uint64_t));
static_assert(sizeof(level1_table_t) == page_size);
static_assert(sizeof(level2_table_t) == page_size);
static_assert(sizeof(level3_table_t) == page_size);
static_assert(sizeof(level4_table_t) == page_size);
template<typename NextEntryType>
static paddr_t address_of(subtable_entry_t<NextEntryType> entry) {
return paddr_t{entry.table_address << 12};
}
static paddr_t address_of(frame_entry_t entry) {
return paddr_t{entry.frame_address << 12};
}
template<size_t level_>
static paddr_t address_of(huge_frame_entry_t<level_> entry) {
return paddr_t{entry.frame_address << 13};
}
template<size_t level>
static size_t table_index(void* vaddress) {
uint64_t address = reinterpret_cast<uint64_t>(vaddress);
return (address >> (12 + 9 * (level - 1))) & 0x1FF;
}
template<typename Fn>
static optional<frame_entry_t*> walk(level1_table_t* table, void* vaddress, Fn&& fn) {
frame_entry_t& entry = table->entries[table_index<frame_entry_t::level>(vaddress)];
if constexpr (requires { fn(entry); }) {
fn(entry);
}
return &entry;
}
struct ascend_t {};
template<typename NextEntryType, typename Fn>
static optional<frame_entry_t*> walk(entry_table_t<table_or_huge_entry_t<NextEntryType>>* table, void* vaddress, Fn&& fn) {
using entry_t = table_or_huge_entry_t<NextEntryType>;
entry_t& entry = table->entries[table_index<entry_t::level>(vaddress)];
if constexpr (requires { fn(entry); }) {
fn(entry);
}
optional<frame_entry_t*> leaf{};
if (entry.subtable.present && entry.subtable.huge_page) {
if constexpr (requires { fn(entry.huge); }) {
fn(entry.huge);
}
} else if (entry.subtable.present) {
leaf = walk(address_of(entry.subtable).template access<entry_table_t<NextEntryType>>(), vaddress, fn);
}
if constexpr (requires { fn(entry, ascend_t{}); }) {
fn(entry, ascend_t{});
}
return leaf;
}
static bool is_present(frame_entry_t entry) {
return entry.present;
}
template<typename NextEntryType>
static bool is_present(table_or_huge_entry_t<NextEntryType> entry) {
return entry.subtable.present;
}
template<typename EntryType>
static bool is_table_empty(entry_table_t<EntryType>* table) {
for (EntryType& entry : table->entries) {
if (is_present(entry)) {
return false;
}
}
return true;
}
template<typename NextEntryType>
static void reclaim_if_empty(table_or_huge_entry_t<NextEntryType>& entry) {
if (!entry.subtable.present) {
return;
}
if (entry.subtable.huge_page) {
return;
}
entry_table_t<NextEntryType>* child_table = address_of(entry.subtable).template access<entry_table_t<NextEntryType>>();
if (!is_table_empty(child_table)) {
return;
}
PhysicalAllocator::getInstance().free({address_of(entry.subtable), 1});
entry = table_or_huge_entry_t<NextEntryType>{};
}
template<typename LeafEntryType>
static PageTable::flags_t::granularity_t granularity_of() {
using granularity_t = PageTable::flags_t::granularity_t;
if constexpr (LeafEntryType::level == 1) {
return granularity_t::page_4KiB;
} else if constexpr (LeafEntryType::level == 2) {
return granularity_t::page_2MiB;
} else {
return granularity_t::page_1GiB;
}
}
template<typename LeafEntryType>
static PageTable::info_t info_of(LeafEntryType entry, void* vaddress) {
PageTable::info_t info{};
info.flags.writeable = entry.writeable;
info.flags.user_accessible = entry.user_accessible;
info.flags.write_through = entry.write_through;
info.flags.cache_disabled = entry.cache_disabled;
info.flags.execute_disabled = entry.execute_disabled;
info.flags.global = entry.global;
info.flags.granularity = granularity_of<LeafEntryType>();
uint64_t offset_mask = (uint64_t{1} << (12 + 9 * (LeafEntryType::level - 1))) - 1;
info.address = paddr_t{address_of(entry).address + (reinterpret_cast<uint64_t>(vaddress) & offset_mask)};
return info;
}
static paddr_t current_l4() {
uint64_t cr3;
asm volatile("mov %%cr3, %0" : "=r"(cr3));
return paddr_t{cr3 & ~uint64_t{0xFFF}};
}
static bool is_active(paddr_t l4) {
return current_l4().address == l4.address;
}
template<typename LeafEntryType>
static void apply_flags(LeafEntryType& entry, PageTable::flags_t flags) {
entry.present = 1;
entry.writeable = flags.writeable;
entry.user_accessible = flags.user_accessible;
entry.write_through = flags.write_through;
entry.cache_disabled = flags.cache_disabled;
entry.execute_disabled = flags.execute_disabled;
entry.global = flags.global;
}
static size_t target_level_of(PageTable::flags_t::granularity_t granularity) {
if (granularity == PageTable::flags_t::granularity_t::page_1GiB) {
return 3;
}
if (granularity == PageTable::flags_t::granularity_t::page_2MiB) {
return 2;
}
return 1;
}
template<typename NextEntryType>
static void ensure_subtable(table_or_huge_entry_t<NextEntryType>& entry) {
if (entry.subtable.present) {
return;
}
optional<PhysicalAllocator::allocation_t> allocation = PhysicalAllocator::getInstance().allocPages(1);
if (!allocation.has_value()) {
return;
}
memset(allocation->ptr.access<uint8_t>(), 0, page_size);
subtable_entry_t<NextEntryType> subtable{};
subtable.present = 1;
subtable.writeable = 1;
subtable.user_accessible = 1;
subtable.table_address = allocation->ptr.address >> 12;
entry.subtable = subtable;
}
void PageTable::activate() {
asm volatile("mov %0, %%cr3" : : "r"(l4.address) : "memory");
}
PageTable PageTable::fromCurrent() {
PageTable table;
table.l4 = current_l4();
return table;
}
bool PageTable::map(void* vaddress, paddr_t paddress, flags_t flags) {
size_t target_level = target_level_of(flags.granularity);
bool mapped = false;
walk(
l4.access<level4_table_t>(), vaddress,
overloaded{
[&](frame_entry_t& entry) {
frame_entry_t leaf{};
apply_flags(leaf, flags);
leaf.frame_address = paddress.address >> 12;
entry = leaf;
mapped = true;
},
[&]<typename NextEntryType>(table_or_huge_entry_t<NextEntryType>& entry) {
constexpr size_t level = table_or_huge_entry_t<NextEntryType>::level;
if (level == target_level) {
if (entry.subtable.present && !entry.subtable.huge_page) {
return;
}
huge_frame_entry_t<level> leaf{};
apply_flags(leaf, flags);
leaf.huge_page = 1;
leaf.frame_address = paddress.address >> 13;
entry.huge = leaf;
mapped = true;
} else {
ensure_subtable(entry);
}
}
}
);
if (mapped && is_active(l4)) {
asm volatile("invlpg (%0)" : : "r"(vaddress) : "memory");
}
return mapped;
}
void PageTable::unmap(void* vaddress) {
walk(
l4.access<level4_table_t>(), vaddress,
overloaded{
[](frame_entry_t& entry) {
entry = frame_entry_t{};
},
[](frame_2MiB_entry_t& entry) {
entry = frame_2MiB_entry_t{};
},
[](frame_1GiB_entry_t& entry) {
entry = frame_1GiB_entry_t{};
},
[](auto& entry, ascend_t) {
reclaim_if_empty(entry);
}
}
);
if (is_active(l4)) {
asm volatile("invlpg (%0)" : : "r"(vaddress) : "memory");
}
}
optional<PageTable::info_t> PageTable::get(void* vaddress) {
optional<info_t> result{};
walk(
l4.access<level4_table_t>(), vaddress,
overloaded{
[&](frame_entry_t& entry) {
if (entry.present) {
result = info_of(entry, vaddress);
}
},
[&](frame_2MiB_entry_t& entry) {
result = info_of(entry, vaddress);
},
[&](frame_1GiB_entry_t& entry) {
result = info_of(entry, vaddress);
}
}
);
return result;
}

View file

@ -48,6 +48,11 @@ __x64_not_supported:
call __print
jmp __stop32
__nx_not_supported:
mov $__nx_not_supported_string, %esi
call __print
jmp __stop32
__check_if_x64:
pushfl
pop %eax
@ -70,6 +75,8 @@ __check_if_x64:
cpuid
test $(1 << 29), %edx
jz __x64_not_supported
test $(1 << 20), %edx
jz __nx_not_supported
mov $__x64_supported_string, %esi
call __print
ret
@ -85,7 +92,7 @@ __go_to_x64:
mov $0xC0000080, %ecx
rdmsr
or $(0x1 << 8), %eax
or $((0x1 << 8) | (0x1 << 11)), %eax
wrmsr
mov %cr0, %eax
@ -135,6 +142,8 @@ __hello_string:
.string "Starting PrOSess! 32bit start!\n"
__x64_not_supported_string:
.string "x64 is not supported!\n"
__nx_not_supported_string:
.string "CPU lacks the NX (no-execute) feature, refusing to boot!\n"
__x64_supported_string:
.string "x64 is supported!\n"
__multiboot_info:
@ -227,18 +236,28 @@ __page_level_4:
.align 0x1000
__page_level_3_low_1gb_identity_map:
.set gib_index, 0
.rept 512 # identity-map all 512 GiB the PDPT spans, one 1 GiB huge page each
.rept 256 # identity-map the first 256 GiB, cached, one 1 GiB huge page each
.quad (1 << 0) | (1 << 1) | (1 << 7) | (gib_index << 30)
.set gib_index, gib_index + 1
.endr
.set gib_index, 0
.rept 256 # alias the same 256 GiB uncached (PWT | PCD) for MMIO
.quad (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 7) | (gib_index << 30)
.set gib_index, gib_index + 1
.endr
.align 0x1000
__page_level_3_kernel_1gb_identity_map:
.set gib_index, 0
.rept 512 # identity-map all 512 GiB the PDPT spans, one 1 GiB huge page each
.rept 256 # identity-map the first 256 GiB, cached, one 1 GiB huge page each
.quad (1 << 0) | (1 << 1) | (1 << 7) | (gib_index << 30)
.set gib_index, gib_index + 1
.endr
.set gib_index, 0
.rept 256 # alias the same 256 GiB uncached (PWT | PCD) for MMIO
.quad (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 7) | (gib_index << 30)
.set gib_index, gib_index + 1
.endr
)");
// startup gdt

View file

@ -0,0 +1,5 @@
asm(R"(
.section .trampoline_text
.align 0x1000
.code16
)");