From ada18599101f077b3bac96e0cc50854360e92c96 Mon Sep 17 00:00:00 2001 From: Katharina Heidenreich Date: Sat, 4 Jul 2026 11:30:33 +0200 Subject: [PATCH] feat: add page table --- kernel/include/apic/core.h | 7 + kernel/include/memory/pagetable.h | 36 +++ kernel/include/memory/pointer.h | 6 + kernel/linker.ld | 20 +- kernel/src/apic/core.cpp | 0 kernel/src/memory/pagetable.cpp | 349 ++++++++++++++++++++++++ kernel/src/startup/entry.cpp | 25 +- kernel/src/startup/secondaryStartup.cpp | 5 + 8 files changed, 435 insertions(+), 13 deletions(-) create mode 100644 kernel/include/apic/core.h create mode 100644 kernel/include/memory/pagetable.h create mode 100644 kernel/src/apic/core.cpp create mode 100644 kernel/src/memory/pagetable.cpp create mode 100644 kernel/src/startup/secondaryStartup.cpp diff --git a/kernel/include/apic/core.h b/kernel/include/apic/core.h new file mode 100644 index 0000000..a86a3bf --- /dev/null +++ b/kernel/include/apic/core.h @@ -0,0 +1,7 @@ +#pragma once + +#include "memory/pointer.h" + +namespace apic { +void startupCore(paddr_t code, paddr_t stack); +} \ No newline at end of file diff --git a/kernel/include/memory/pagetable.h b/kernel/include/memory/pagetable.h new file mode 100644 index 0000000..02e9bfd --- /dev/null +++ b/kernel/include/memory/pagetable.h @@ -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 get(void* vaddress); + +private: + paddr_t l4; +}; \ No newline at end of file diff --git a/kernel/include/memory/pointer.h b/kernel/include/memory/pointer.h index 9ba35c9..0ae44a8 100644 --- a/kernel/include/memory/pointer.h +++ b/kernel/include/memory/pointer.h @@ -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(address + high_base); } + + template + T* access_uc() { + return reinterpret_cast(address + uncached_base); + } }; inline void* operator new(size_t, void* p) { diff --git a/kernel/linker.ld b/kernel/linker.ld index a511bf6..200651f 100644 --- a/kernel/linker.ld +++ b/kernel/linker.ld @@ -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 diff --git a/kernel/src/apic/core.cpp b/kernel/src/apic/core.cpp new file mode 100644 index 0000000..e69de29 diff --git a/kernel/src/memory/pagetable.cpp b/kernel/src/memory/pagetable.cpp new file mode 100644 index 0000000..90efbd4 --- /dev/null +++ b/kernel/src/memory/pagetable.cpp @@ -0,0 +1,349 @@ +#include "memory/pagetable.h" +#include "memory/allocator.h" +#include "util/function.h" + +template +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 +struct subtable_entry_t { + using next_table_t = entry_table_t; + 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 +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 +union table_or_huge_entry_t { + static constexpr size_t level = NextEntryType::level + 1; + + subtable_entry_t subtable; + huge_frame_entry_t huge; +}; + +using level1_entry_t = frame_entry_t; +using level2_entry_t = table_or_huge_entry_t; +using level3_entry_t = table_or_huge_entry_t; +using level4_entry_t = table_or_huge_entry_t; + +using level1_table_t = entry_table_t; +using level2_table_t = entry_table_t; +using level3_table_t = entry_table_t; +using level4_table_t = entry_table_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 +static paddr_t address_of(subtable_entry_t 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 +static paddr_t address_of(huge_frame_entry_t entry) { + return paddr_t{entry.frame_address << 13}; +} + +template +static size_t table_index(void* vaddress) { + uint64_t address = reinterpret_cast(vaddress); + return (address >> (12 + 9 * (level - 1))) & 0x1FF; +} + +template +static optional walk(level1_table_t* table, void* vaddress, Fn&& fn) { + frame_entry_t& entry = table->entries[table_index(vaddress)]; + if constexpr (requires { fn(entry); }) { + fn(entry); + } + return &entry; +} + +struct ascend_t {}; + +template +static optional walk(entry_table_t>* table, void* vaddress, Fn&& fn) { + using entry_t = table_or_huge_entry_t; + entry_t& entry = table->entries[table_index(vaddress)]; + if constexpr (requires { fn(entry); }) { + fn(entry); + } + optional 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>(), 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 +static bool is_present(table_or_huge_entry_t entry) { + return entry.subtable.present; +} + +template +static bool is_table_empty(entry_table_t* table) { + for (EntryType& entry : table->entries) { + if (is_present(entry)) { + return false; + } + } + return true; +} + +template +static void reclaim_if_empty(table_or_huge_entry_t& entry) { + if (!entry.subtable.present) { + return; + } + if (entry.subtable.huge_page) { + return; + } + entry_table_t* child_table = address_of(entry.subtable).template access>(); + if (!is_table_empty(child_table)) { + return; + } + PhysicalAllocator::getInstance().free({address_of(entry.subtable), 1}); + entry = table_or_huge_entry_t{}; +} + +template +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 +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(); + uint64_t offset_mask = (uint64_t{1} << (12 + 9 * (LeafEntryType::level - 1))) - 1; + info.address = paddr_t{address_of(entry).address + (reinterpret_cast(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 +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 +static void ensure_subtable(table_or_huge_entry_t& entry) { + if (entry.subtable.present) { + return; + } + optional allocation = PhysicalAllocator::getInstance().allocPages(1); + if (!allocation.has_value()) { + return; + } + memset(allocation->ptr.access(), 0, page_size); + subtable_entry_t 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(), vaddress, + overloaded{ + [&](frame_entry_t& entry) { + frame_entry_t leaf{}; + apply_flags(leaf, flags); + leaf.frame_address = paddress.address >> 12; + entry = leaf; + mapped = true; + }, + [&](table_or_huge_entry_t& entry) { + constexpr size_t level = table_or_huge_entry_t::level; + if (level == target_level) { + if (entry.subtable.present && !entry.subtable.huge_page) { + return; + } + huge_frame_entry_t 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(), 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::get(void* vaddress) { + optional result{}; + walk( + l4.access(), 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; +} \ No newline at end of file diff --git a/kernel/src/startup/entry.cpp b/kernel/src/startup/entry.cpp index 03bda63..add74f3 100644 --- a/kernel/src/startup/entry.cpp +++ b/kernel/src/startup/entry.cpp @@ -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 diff --git a/kernel/src/startup/secondaryStartup.cpp b/kernel/src/startup/secondaryStartup.cpp new file mode 100644 index 0000000..4d214d8 --- /dev/null +++ b/kernel/src/startup/secondaryStartup.cpp @@ -0,0 +1,5 @@ +asm(R"( +.section .trampoline_text +.align 0x1000 +.code16 +)"); \ No newline at end of file