From 7d6f0ed063790042a808f4bf07c50d308b3f2de4 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 16 Jan 2026 13:36:38 +0100 Subject: chore: restructure namespaces --- arch/x86_64/CMakeLists.txt | 10 +- arch/x86_64/include/arch/boot/boot.hpp | 70 +++++ arch/x86_64/include/arch/boot/ld.hpp | 61 ++++ arch/x86_64/include/arch/cpu/control_register.hpp | 248 +++++++++++++++ arch/x86_64/include/arch/cpu/interrupts.hpp | 61 ++++ .../include/arch/cpu/model_specific_register.hpp | 151 +++++++++ arch/x86_64/include/arch/cpu/registers.hpp | 32 ++ arch/x86_64/include/arch/cpu/segment_selector.hpp | 21 ++ arch/x86_64/include/arch/debug/qemu_output.hpp | 43 +++ arch/x86_64/include/arch/device_io/port_io.hpp | 107 +++++++ .../include/arch/memory/buffered_allocator.hpp | 138 +++++++++ arch/x86_64/include/arch/memory/kernel_mapper.hpp | 34 ++ arch/x86_64/include/arch/memory/mmu.hpp | 27 ++ arch/x86_64/include/arch/memory/page_table.hpp | 342 +++++++++++++++++++++ arch/x86_64/include/arch/memory/page_utilities.hpp | 22 ++ arch/x86_64/include/arch/memory/paging_root.hpp | 27 ++ .../include/arch/memory/recursive_page_mapper.hpp | 25 ++ .../include/arch/memory/region_allocator.hpp | 84 +++++ arch/x86_64/include/arch/memory/scoped_mapping.hpp | 68 ++++ arch/x86_64/include/arch/vga/crtc.hpp | 35 +++ arch/x86_64/include/arch/vga/text.hpp | 10 + arch/x86_64/include/arch/vga/text/attribute.hpp | 30 ++ arch/x86_64/include/arch/vga/text/buffer.hpp | 101 ++++++ arch/x86_64/include/arch/vga/text/color.hpp | 35 +++ .../include/arch/vga/text/common_attributes.hpp | 37 +++ arch/x86_64/include/arch/vga/text/device.hpp | 45 +++ arch/x86_64/include/arch/vga/text/flags.hpp | 38 +++ arch/x86_64/include/x86_64/boot/boot.hpp | 70 ----- arch/x86_64/include/x86_64/boot/ld.hpp | 61 ---- .../x86_64/include/x86_64/cpu/control_register.hpp | 248 --------------- arch/x86_64/include/x86_64/cpu/interrupts.hpp | 61 ---- .../include/x86_64/cpu/model_specific_register.hpp | 151 --------- arch/x86_64/include/x86_64/cpu/registers.hpp | 32 -- .../x86_64/include/x86_64/cpu/segment_selector.hpp | 20 -- arch/x86_64/include/x86_64/debug/qemu_output.hpp | 43 --- arch/x86_64/include/x86_64/device_io/port_io.hpp | 107 ------- .../include/x86_64/memory/buffered_allocator.hpp | 137 --------- .../x86_64/include/x86_64/memory/kernel_mapper.hpp | 33 -- arch/x86_64/include/x86_64/memory/mmu.hpp | 27 -- arch/x86_64/include/x86_64/memory/page_table.hpp | 341 -------------------- .../include/x86_64/memory/page_utilities.hpp | 22 -- arch/x86_64/include/x86_64/memory/paging_root.hpp | 27 -- .../x86_64/memory/recursive_page_mapper.hpp | 25 -- .../include/x86_64/memory/region_allocator.hpp | 83 ----- .../include/x86_64/memory/scoped_mapping.hpp | 66 ---- arch/x86_64/include/x86_64/vga/crtc.hpp | 37 --- arch/x86_64/include/x86_64/vga/text.hpp | 10 - arch/x86_64/include/x86_64/vga/text/attribute.hpp | 30 -- arch/x86_64/include/x86_64/vga/text/buffer.hpp | 101 ------ arch/x86_64/include/x86_64/vga/text/color.hpp | 35 --- .../include/x86_64/vga/text/common_attributes.hpp | 37 --- arch/x86_64/include/x86_64/vga/text/device.hpp | 45 --- arch/x86_64/include/x86_64/vga/text/flags.hpp | 38 --- arch/x86_64/kapi/cio.cpp | 20 ++ arch/x86_64/kapi/cpu.cpp | 12 + arch/x86_64/kapi/memory.cpp | 194 ++++++++++++ arch/x86_64/src/boot/boot32.S | 2 +- arch/x86_64/src/boot/initialize_runtime.cpp | 23 +- arch/x86_64/src/debug/qemu_output.cpp | 8 +- arch/x86_64/src/kapi/cio.cpp | 20 -- arch/x86_64/src/kapi/cpu.cpp | 12 - arch/x86_64/src/kapi/memory.cpp | 192 ------------ arch/x86_64/src/memory/kernel_mapper.cpp | 36 +-- arch/x86_64/src/memory/mmu.cpp | 12 +- arch/x86_64/src/memory/page_table.cpp | 12 +- arch/x86_64/src/memory/paging_root.cpp | 6 +- arch/x86_64/src/memory/recursive_page_mapper.cpp | 30 +- arch/x86_64/src/memory/region_allocator.cpp | 30 +- arch/x86_64/src/memory/scoped_mapping.cpp | 22 +- arch/x86_64/src/vga/text/buffer.cpp | 8 +- arch/x86_64/src/vga/text/device.cpp | 24 +- kapi/include/kapi/boot.hpp | 4 +- kapi/include/kapi/cio.hpp | 4 +- kapi/include/kapi/cio/output_device.hpp | 4 +- kapi/include/kapi/cpu.hpp | 4 +- kapi/include/kapi/memory.hpp | 4 +- kapi/include/kapi/memory/address.hpp | 12 +- kapi/include/kapi/memory/chunk.hpp | 4 +- kapi/include/kapi/memory/frame.hpp | 4 +- kapi/include/kapi/memory/frame_allocator.hpp | 4 +- kapi/include/kapi/memory/page.hpp | 4 +- kapi/include/kapi/memory/page_mapper.hpp | 6 +- kapi/include/kapi/system.hpp | 4 +- kernel/CMakeLists.txt | 15 +- kernel/kapi/cio.cpp | 36 +++ kernel/kapi/memory.cpp | 94 ++++++ kernel/kapi/system.cpp | 21 ++ kernel/kstd/os.cpp | 16 + kernel/kstd/print.cpp | 145 +++++++++ kernel/src/kapi/cio.cpp | 36 --- kernel/src/kapi/memory.cpp | 94 ------ kernel/src/kapi/system.cpp | 21 -- kernel/src/kstd/os.cpp | 16 - kernel/src/kstd/print.cpp | 145 --------- kernel/src/main.cpp | 6 +- 95 files changed, 2585 insertions(+), 2570 deletions(-) create mode 100644 arch/x86_64/include/arch/boot/boot.hpp create mode 100644 arch/x86_64/include/arch/boot/ld.hpp create mode 100644 arch/x86_64/include/arch/cpu/control_register.hpp create mode 100644 arch/x86_64/include/arch/cpu/interrupts.hpp create mode 100644 arch/x86_64/include/arch/cpu/model_specific_register.hpp create mode 100644 arch/x86_64/include/arch/cpu/registers.hpp create mode 100644 arch/x86_64/include/arch/cpu/segment_selector.hpp create mode 100644 arch/x86_64/include/arch/debug/qemu_output.hpp create mode 100644 arch/x86_64/include/arch/device_io/port_io.hpp create mode 100644 arch/x86_64/include/arch/memory/buffered_allocator.hpp create mode 100644 arch/x86_64/include/arch/memory/kernel_mapper.hpp create mode 100644 arch/x86_64/include/arch/memory/mmu.hpp create mode 100644 arch/x86_64/include/arch/memory/page_table.hpp create mode 100644 arch/x86_64/include/arch/memory/page_utilities.hpp create mode 100644 arch/x86_64/include/arch/memory/paging_root.hpp create mode 100644 arch/x86_64/include/arch/memory/recursive_page_mapper.hpp create mode 100644 arch/x86_64/include/arch/memory/region_allocator.hpp create mode 100644 arch/x86_64/include/arch/memory/scoped_mapping.hpp create mode 100644 arch/x86_64/include/arch/vga/crtc.hpp create mode 100644 arch/x86_64/include/arch/vga/text.hpp create mode 100644 arch/x86_64/include/arch/vga/text/attribute.hpp create mode 100644 arch/x86_64/include/arch/vga/text/buffer.hpp create mode 100644 arch/x86_64/include/arch/vga/text/color.hpp create mode 100644 arch/x86_64/include/arch/vga/text/common_attributes.hpp create mode 100644 arch/x86_64/include/arch/vga/text/device.hpp create mode 100644 arch/x86_64/include/arch/vga/text/flags.hpp delete mode 100644 arch/x86_64/include/x86_64/boot/boot.hpp delete mode 100644 arch/x86_64/include/x86_64/boot/ld.hpp delete mode 100644 arch/x86_64/include/x86_64/cpu/control_register.hpp delete mode 100644 arch/x86_64/include/x86_64/cpu/interrupts.hpp delete mode 100644 arch/x86_64/include/x86_64/cpu/model_specific_register.hpp delete mode 100644 arch/x86_64/include/x86_64/cpu/registers.hpp delete mode 100644 arch/x86_64/include/x86_64/cpu/segment_selector.hpp delete mode 100644 arch/x86_64/include/x86_64/debug/qemu_output.hpp delete mode 100644 arch/x86_64/include/x86_64/device_io/port_io.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/buffered_allocator.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/kernel_mapper.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/mmu.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/page_table.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/page_utilities.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/paging_root.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/recursive_page_mapper.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/region_allocator.hpp delete mode 100644 arch/x86_64/include/x86_64/memory/scoped_mapping.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/crtc.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text/attribute.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text/buffer.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text/color.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text/common_attributes.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text/device.hpp delete mode 100644 arch/x86_64/include/x86_64/vga/text/flags.hpp create mode 100644 arch/x86_64/kapi/cio.cpp create mode 100644 arch/x86_64/kapi/cpu.cpp create mode 100644 arch/x86_64/kapi/memory.cpp delete mode 100644 arch/x86_64/src/kapi/cio.cpp delete mode 100644 arch/x86_64/src/kapi/cpu.cpp delete mode 100644 arch/x86_64/src/kapi/memory.cpp create mode 100644 kernel/kapi/cio.cpp create mode 100644 kernel/kapi/memory.cpp create mode 100644 kernel/kapi/system.cpp create mode 100644 kernel/kstd/os.cpp create mode 100644 kernel/kstd/print.cpp delete mode 100644 kernel/src/kapi/cio.cpp delete mode 100644 kernel/src/kapi/memory.cpp delete mode 100644 kernel/src/kapi/system.cpp delete mode 100644 kernel/src/kstd/os.cpp delete mode 100644 kernel/src/kstd/print.cpp diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt index 519fb93..413b5aa 100644 --- a/arch/x86_64/CMakeLists.txt +++ b/arch/x86_64/CMakeLists.txt @@ -11,6 +11,11 @@ target_link_libraries("x86_64" PUBLIC ) target_sources("x86_64" PRIVATE + # Platform-dependent KAPI implementation + "kapi/cio.cpp" + "kapi/cpu.cpp" + "kapi/memory.cpp" + # Low-level bootstrap "src/boot/boot32.S" "src/boot/entry64.s" @@ -20,11 +25,6 @@ target_sources("x86_64" PRIVATE # Debug interfaces "src/debug/qemu_output.cpp" - # api::kapi implementation - "src/kapi/cio.cpp" - "src/kapi/cpu.cpp" - "src/kapi/memory.cpp" - # Memory management "src/memory/kernel_mapper.cpp" "src/memory/mmu.cpp" diff --git a/arch/x86_64/include/arch/boot/boot.hpp b/arch/x86_64/include/arch/boot/boot.hpp new file mode 100644 index 0000000..71e8a70 --- /dev/null +++ b/arch/x86_64/include/arch/boot/boot.hpp @@ -0,0 +1,70 @@ +#ifndef TEACHOS_X86_64_BOOT_BOOT_H +#define TEACHOS_X86_64_BOOT_BOOT_H + +#ifdef __ASSEMBLER__ +/* clang-format off */ +/** + * @brief The number of huge pages to map during bootstrap. + */ +#define HUGE_PAGES_TO_MAP (16) + +/** + * @brief The magic value to be set in eax by the multiboot 2 loader. + */ +#define MULTIBOOT2_MAGIC (0x36d76289) + +/** + * @brief The "A" bit in a GDT entry. + */ +#define GDT_ACCESSED (1 << 40) + +/** + * @brief The "R/W" bit in a GDT entry + */ +#define GDT_READ_WRITE (1 << 41) + +/** + * @brief The "E" bit in a GDT entry. + */ +#define GDT_EXECUTABLE (1 << 43) + +/** + * @brief The "S" bit in a GDT entry. + */ +#define GDT_DESCRIPTOR_TYPE (1 << 44) + +/** + * @brief The "P" bit in a GDT entry. + */ +#define GDT_PRESENT (1 << 47) + +/** + * @brief The "L" bit in a GDT entry. + */ +#define GDT_LONG_MODE (1 << 53) +/* clang-format on */ +#else + +#include "kapi/boot.hpp" // IWYU pragma: export + +#include + +#include + +namespace kapi::boot +{ + + struct information + { + //! A pointer to the loader provided Multiboot2 Information structure. + multiboot2::information_view const * mbi; + + //! The index of the next character to be written in the VGA text buffer after handoff. + std::size_t vga_buffer_index; + }; + +} // namespace kapi::boot + +#endif + +#endif diff --git a/arch/x86_64/include/arch/boot/ld.hpp b/arch/x86_64/include/arch/boot/ld.hpp new file mode 100644 index 0000000..988723d --- /dev/null +++ b/arch/x86_64/include/arch/boot/ld.hpp @@ -0,0 +1,61 @@ +//! @file +//! The interface to linker script defined symbols. +//! +//! This header provides declarations for symbols that are defined in the linker script itself. The symbols declared +//! here provide important information, for example the start and end of the kernel image in virtual and physical +//! memory. +//! +//! Any variables defined in this file must not be read themselves, but rather their address shall be taken, yielding a +//! pointer to the memory location the represent. +//! +//! @note The symbols declared in this header are declared using C-language linkage in order to suppress name mangling. +//! +//! @see arch/x86_64/scripts/kernel.ld + +#ifndef TEACHOS_X86_64_BOOT_LD_HPP +#define TEACHOS_X86_64_BOOT_LD_HPP + +#include + +namespace arch::boot +{ + + extern "C" + { + //! The beginning of the kernel image in physical memory + //! + //! This symbol marks the start of the kernel image in physical memory. + //! + //! @see _end_physical + extern std::byte _start_physical; + + //! The first byte after the loaded kernel image. + //! + //! This symbol marks the end of the kernel image in physical memory. + //! + //! @see _start_physical + extern std::byte _end_physical; + + //! The first byte of the loaded kernel image in the virtual address space. + //! + //! This symbol and marks the start of the kernel image in virtual memory. + //! + //! @see _end_virtual + extern std::byte _start_virtual; + + //! The first byte after the loaded kernel image in the virtual address space. + //! + //! This symbol marks the end of the kernel image in virtual memory. + //! + //! @see _start_virtual + extern std::byte _end_virtual; + + //! The first byte of the kernel's virtual address space. + //! + //! This symbol marks beginning of the kernel virtual address space. + extern std::byte TEACHOS_VMA; + } + +} // namespace arch::boot + +#endif diff --git a/arch/x86_64/include/arch/cpu/control_register.hpp b/arch/x86_64/include/arch/cpu/control_register.hpp new file mode 100644 index 0000000..681dc5f --- /dev/null +++ b/arch/x86_64/include/arch/cpu/control_register.hpp @@ -0,0 +1,248 @@ +#ifndef TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP +#define TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP + +// IWYU pragma: private, include "arch/cpu/registers.hpp" + +#include "kapi/memory.hpp" + +#include + +#include +#include +#include +#include + +namespace arch::cpu +{ + namespace impl + { + //! The assembler templates used to access (r/w) CR0; + constexpr auto static cr0_asm = std::pair{"mov %%cr0, %0", "mov %0, %%cr0"}; + + //! The assembler templates used to access (r/w) CR2; + constexpr auto static cr2_asm = std::pair{"mov %%cr2, %0", "mov %0, %%cr2"}; + + //! The assembler templates used to access (r/w) CR3; + constexpr auto static cr3_asm = std::pair{"mov %%cr3, %0", "mov %0, %%cr3"}; + } // namespace impl + + //! The flags that can be set on CR0 configuration register. + enum struct cr0_flags : uint64_t + { + //! Enable protected mode. + protection_enable = 1uz << 0, + //! Enable wait-monitoring of the coprocessor after task switching. + monitor_coprocessor = 1uz << 1, + //! Emulate floating point coprocessor. + emulation = 1uz << 2, + //! Marks that a task switch has occurred. + task_switched = 1uz << 3, + //! Marks Intel 387 DX math coprocessor as available + extension_type = 1uz << 4, + //! Numeric error handling mode. + numeric_error = 1uz << 5, + //! Disable writing to read-only marked memory. + write_protect = 1uz << 16, + //! Enable Ring-3 alignment checks + alignment_check = 1uz << 18, + //! Disable write through + not_write_through = 1uz << 29, + //! Disable caching of memory accesses + cache_disable = 1uz << 30, + //! Enable paging + paging = 1uz << 31 + }; + + enum struct cr3_flags : std::uint64_t + { + page_level_write_through = 1uz << 0, + page_level_cache_disable = 1uz << 1, + }; +} // namespace arch::cpu + +namespace kstd::ext +{ + template<> + struct is_bitfield_enum : std::true_type + { + }; + + template<> + struct is_bitfield_enum : std::true_type + { + }; +} // namespace kstd::ext + +namespace arch::cpu +{ + //! A mixin for flag-oriented control registers. + //! + //! This mixin provides additional functionality for flag-oriented, or partially flag-oriented, control registers. A + //! control register is flag-oriented, if it comprises a bitfield and zero or more additional non-bitfield parts. + //! + //! @tparam Derived The class deriving from this mixin. + //! @tparam ValueType The value type of the class deriving from this mixin. + template + struct control_register_with_flags + { + private: + constexpr control_register_with_flags() noexcept = default; + friend Derived; + }; + + //! @copydoc control_register_with_flags + //! + //! @note This specialization provides the implementation for the case in which the value type of the control register + //! is an enum. + template + struct control_register_with_flags>> + { + //! The type of the flags used by this control register + using flags = ValueType; + + //! Set one or more flags in this control register. + //! + //! @warning This function is to be considered **UNSAFE**. Setting flags in a control register may lead to + //! unexpected CPU behavior if the prerequisites imposed by the CPU specification are not fulfilled. This function + //! will perform no additional checks, and may, by extension, crash the system. + //! + //! @param value One or a combination of flags to be set in the control register. + auto static set(ValueType value) -> void + { + auto current = Derived::read(); + current |= value; + Derived::write(current); + } + + //! Clear one or more flags in this control register. + //! + //! @warning This function is to be considered **UNSAFE**. Clearing flags in a control register may lead to + //! unexpected CPU behavior if the prerequisites imposed by the CPU specification are not fulfilled. This function + //! will perform no additional checks, and may, by extension, crash the system. + //! + //! @param value One or a combination of flags to be cleared in the control register. + auto static clear(ValueType value) -> void + { + auto current = Derived::read(); + current &= ~value; + Derived::write(current); + } + }; + + //! A CPU control register. + //! + //! CPU control registers are used to configure builtin features of the CPU, for example memory protection and FPU + //! error reporting. Writing to a control register is inherently dangerous, since a misconfiguration can leave the CPU + //! in an invalid/undefined state. + template + struct control_register : control_register_with_flags, ValueType> + { + //! Read the current value of the control register. + //! + //! @return The currently set value of the control register. + [[nodiscard]] auto static read() -> ValueType + { + auto value = ValueType{}; + asm volatile((AssemblerTemplates->first) : "=r"(value)); + return value; + } + + //! Write a new value to the control register. + //! + //! @warning This function should be considered **UNSAFE**. Writing values to a control register may lead to + //! unexpected CPU behavior if the prerequisites imposed by the CPU specification are not fulfilled. This function + //! will perform no additional checks, and may, by extension, crash the system. + //! + //! @param value The new value to write to the control register. + auto static write(ValueType value) -> void + { + asm volatile((AssemblerTemplates->second) : : "r"(value)); + } + }; + + //! The value type of the CR3 control register. + //! + //! The CR3 control register holds the root configuration of the virtual memory protection mechanism. It contains the + //! page aligned physical address of the root page map, as well as the root paging configuration flags. + struct cr3_value + { + //! Contstruct a 0-value CR3 value. + constexpr cr3_value() = default; + + //! Construct a CR3 value using the given root page map address and flags. + //! + //! @param address The physical address of the root page map + //! @param flags The root configuration flags of the paging system. + constexpr cr3_value(kapi::memory::physical_address address, cr3_flags flags = static_cast(0)) + : m_flags{static_cast(flags)} + , m_address{static_cast(address.raw())} + {} + + //! Extract the physical address of the root page map from this value. + //! + //! @return The physical address of the root page map. + [[nodiscard]] constexpr auto address() const -> kapi::memory::physical_address + { + return kapi::memory::physical_address{m_address}; + } + + //! Encode the frame aligned physical address of the root page map into this value. + //! + //! @param frame The frame containing a PML4. + constexpr auto frame(kapi::memory::frame frame) -> void + { + m_address = static_cast(frame.number()); + } + + //! Extract the root paging configuration flags from this value. + //! + //! @return The root paging configuration flags. + [[nodiscard]] constexpr auto flags() const -> cr3_flags + { + return static_cast(m_flags); + } + + //! Encode the root paging configuration flags into this value. + //! + //! @param flags The root paging configuration flags. + constexpr auto flags(cr3_flags flags) -> void + { + m_flags = static_cast(flags); + } + + //! Add the given flags to the current set of encoded root configuration flags of this value. + //! + //! @param flags The root configuration flags to add. + //! @return A reference to this value. + constexpr auto operator|=(cr3_flags flags) -> cr3_value & + { + m_flags |= static_cast(flags); + return *this; + } + + //! Mask the root configuration flags of this value. + //! + //! @param mask The mask to apply to the root configuration flags. + //! @return A reference to this value. + constexpr auto operator&=(cr3_flags mask) -> cr3_value & + { + m_flags &= static_cast(mask); + return *this; + } + + private: + //! Reserved bits. + std::uint64_t : 3; + //! The root paging configuration flags. + std::uint64_t m_flags : 2 {}; + //! Reserved bits. + std::uint64_t : 7; + //! The page aligned physical address of the root page map. + std::uint64_t m_address : 52 {}; + }; + + static_assert(sizeof(cr3_value) == sizeof(std::uint64_t)); + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/cpu/interrupts.hpp b/arch/x86_64/include/arch/cpu/interrupts.hpp new file mode 100644 index 0000000..92c5824 --- /dev/null +++ b/arch/x86_64/include/arch/cpu/interrupts.hpp @@ -0,0 +1,61 @@ +#ifndef TEACHOS_X86_64_CPU_INTERRUPTS_HPP +#define TEACHOS_X86_64_CPU_INTERRUPTS_HPP + +#include "arch/cpu/segment_selector.hpp" + +#include +#include +#include + +namespace arch::cpu +{ + + //! The types of supported gates. + enum struct gate_type : std::uint8_t + { + //! A gate entered through the @p INT instruction. + interrupt_gate = 0b1110, + //! A gate entered via an exception. + trap_gate = 0b1111, + }; + + struct alignas(std::uint64_t) gate_descriptor + { + //! The lowest 16 bits of the address of this gate's handler function. + std::uint16_t offset_low : 16; + //! The code segment used when handling the respective interrupt. + segment_selector m_code_segment; + //! The index into the Interrupt Stack Table naming the stack to use. + std::uint8_t interrupt_stack_table_selector : 3; + //! Reserved + std::uint8_t : 5; + //! The type of this gate. + gate_type gate_type : 4; + //! Reserved + std::uint8_t : 1; + //! The privilege level required to enter through this gate. + std::uint8_t descriptor_privilege_level : 2; + //! Whether this gate is present or not. + std::uint8_t present : 1; + //! The middle 16 bits of the address of this gate's handler function. + std::uint16_t offset_middle : 16; + //! The highest 32 bits of the address of this gate's handler function. + std::uint32_t offset_high : 32; + //! Reserved + std::uint32_t : 32; + }; + + static_assert(sizeof(gate_descriptor) == 2 * sizeof(std::uint64_t)); + static_assert(std::is_aggregate_v); + + struct interrupt_descriptor_table + { + interrupt_descriptor_table(); + + private: + std::array m_descriptors{}; + }; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/cpu/model_specific_register.hpp b/arch/x86_64/include/arch/cpu/model_specific_register.hpp new file mode 100644 index 0000000..8539a24 --- /dev/null +++ b/arch/x86_64/include/arch/cpu/model_specific_register.hpp @@ -0,0 +1,151 @@ +#ifndef TEACHOS_X86_64_CPU_MODEL_SPECIFIC_REGISTER_HPP +#define TEACHOS_X86_64_CPU_MODEL_SPECIFIC_REGISTER_HPP + +// IWYU pragma: private, include "x86_64/cpu/registers.hpp" + +#include + +#include +#include +#include + +namespace arch::cpu +{ + + //! The flags of the IA32_EFER (Extended Features Enable Register) MSR. + enum struct ia32_efer_flags : std::uint64_t + { + //! Enable the syscall and sysret instructions. + syscall_enable = 1uz << 0, + //! Enable IA-32e mode operation. + ia32e_mode_enable = 1uz << 8, + //! Indicates IA-32e mode is active (read-only) + ia32e_mode_active = 1uz << 10, + //! Enable the use of the NX page table bit. + execute_disable_bit_enable = 1uz << 11, + }; + +} // namespace arch::cpu + +namespace kstd::ext +{ + + template<> + struct is_bitfield_enum : std::true_type + { + }; + +} // namespace kstd::ext + +namespace arch::cpu +{ + //! The MSR number for the IA32_EFER MSR + constexpr auto ia32_efer_number = 0xC000'0080u; + + //! A mixin for flag-oriented model specific registers. + //! + //! This mixin provides additional functionality for a flag-oriented model specific register. A models specific + //! register is flag-oriented, if it comprises a single field of bitfield. + //! + //! @tparam Derived The class deriving from this mixin. + //! @tparam ValueType The value type of the class deriving from this mixin. + template + struct model_specific_register_with_flags + { + private: + constexpr model_specific_register_with_flags() noexcept = default; + friend Derived; + }; + + //! @copydoc model_specific_register_with_flags + //! + //! @note This specialization provides the implementation for the case in which the value type of the model specific + //! register is a bitfield enum. + template + struct model_specific_register_with_flags>> + { + //! The of the flags used by this model specific register. + using flags = ValueType; + + //! Set one or more flags in this model specific register. + //! + //! @warning This function is to be considered **UNSAFE**. Setting flags in a model specific register may lead to + //! unexpected CPU behavior if the prerequisites imposed by the CPU specification are not fulfilled. This function + //! will perform no additional checks, and may, by extension, crash the system. + //! + //! @param flag One or a combination of flags to be set in the model specific register. + auto static set(flags flag) -> void + { + auto current = Derived::read(); + current |= flag; + Derived::write(current); + } + + //! Clear one or more flags in this model specific register. + //! + //! @warning This function is to be considered **UNSAFE**. Clearing flags in a model specific register may lead to + //! unexpected CPU behavior if the prerequisites imposed by the CPU specification are not fulfilled. This function + //! will perform no additional checks, and may, by extension, crash the system. + //! + //! @param flag One or a combination of flags to be cleared in the model specific register. + auto static clear(flags flag) -> void + { + auto current = Derived::read(); + current &= ~flag; + Derived::write(current); + } + + //! Test one or more flags in this model specific register + //! + //! @param flag One or a combination of flags to test for. + auto test(flags flag) -> flags + { + return Derived::read() & flag; + } + }; + + //! A model specific register (MSR) + //! + //! Model specific register are used to configure CPU features that a not necessarily present on all CPUs generations. + //! In the past, some MSRs have been defined to be architectural, meaning all CPUs of a given architecture (x86-64 in + //! this case) support them. Writing to a MSR is inherently dangerous, since a misconfiguration cal leave the CPU in + //! an invalid/undefined state. + //! + //! @tparam Number The register number of this MSR + //! @tparam ValueType The value type of this MSR + template + struct model_specific_register + : model_specific_register_with_flags, ValueType> + { + //! A raw MSR value, comprising two halfs. + //! + //! MSRs have been 64-bit in size even in the 32-bit intel architecture, and are thus written in two halfs. + struct raw_value + { + std::uint32_t low_half; //!< The lower half of the register value + std::uint32_t high_half; //!< The upper half of the register value + }; + + //! Read the current value of this MSR. + //! + //! @return The current value of this MSR. + auto static read() -> ValueType + { + auto raw = raw_value{}; + asm volatile("rdmsr" : "=a"(raw.low_half), "=d"(raw.high_half) : "c"(Number)); + return static_cast(std::bit_cast(raw)); + } + + //! Write a new value to this MSR. + //! + //! @param value The new value for this MSR. + auto static write(ValueType value) -> void + { + auto raw = std::bit_cast(static_cast(value)); + asm volatile("wrmsr" : : "a"(raw.low_half), "d"(raw.high_half), "c"(Number)); + } + }; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/cpu/registers.hpp b/arch/x86_64/include/arch/cpu/registers.hpp new file mode 100644 index 0000000..d7def10 --- /dev/null +++ b/arch/x86_64/include/arch/cpu/registers.hpp @@ -0,0 +1,32 @@ +#ifndef TEACHOS_X86_64_CPU_REGISTERS_HPP +#define TEACHOS_X86_64_CPU_REGISTERS_HPP + +#include "kapi/memory.hpp" + +#include "arch/cpu/control_register.hpp" // IWYU pragma: export +#include "arch/cpu/model_specific_register.hpp" // IWYU pragma: export + +namespace arch::cpu +{ + + //! Configuration Register 0. + //! + //! This configuration register holds various control flags to configure the configure the basic operation of the CPU. + using cr0 = control_register; + + //! Configuration Register 2. + //! + //! This configuration register holds the memory address the access to which has triggered the most recent page fault. + using cr2 = control_register; + + //! Configuration Register 3. + //! + //! This register holds the configuration of the virtual memory protection configuration. + using cr3 = control_register; + + //! The I32_EFER (Extended Feature Enable Register) MSR + using i32_efer = model_specific_register; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/cpu/segment_selector.hpp b/arch/x86_64/include/arch/cpu/segment_selector.hpp new file mode 100644 index 0000000..1a78c47 --- /dev/null +++ b/arch/x86_64/include/arch/cpu/segment_selector.hpp @@ -0,0 +1,21 @@ +#ifndef TEACHOS_X86_64_SEGMENT_SELECTOR_HPP +#define TEACHOS_X86_64_SEGMENT_SELECTOR_HPP + +#include + +namespace arch::cpu +{ + + struct segment_selector + { + std::uint16_t request_privilege_level : 2; + bool use_local_descriptor_table : 1; + std::uint16_t table_index : 13; + }; + + static_assert(sizeof(segment_selector) == sizeof(std::uint16_t)); + static_assert(alignof(segment_selector) == alignof(std::uint16_t)); + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/debug/qemu_output.hpp b/arch/x86_64/include/arch/debug/qemu_output.hpp new file mode 100644 index 0000000..e72eb39 --- /dev/null +++ b/arch/x86_64/include/arch/debug/qemu_output.hpp @@ -0,0 +1,43 @@ +#ifndef TEACHOS_X86_64_DEBUG_QEMU_OUTPUT_HPP +#define TEACHOS_X86_64_DEBUG_QEMU_OUTPUT_HPP + +#include "kapi/cio.hpp" + +#include "arch/device_io/port_io.hpp" + +#include + +namespace arch::debug +{ + + //! A QEMU debug console output device. + //! + //! This device implements output to the port 0xE9 debug console present in QEMU in Bochs. It is designed to wrap a + //! different output device (e.g. a VGA text output device) and forwards the written data accordingly. + //! + //! @note Support for the device has to be enabled when the emulator is started. The device will try to detect if the + //! port is available. If the port is detected, any output to the device will be written to port before being + //! forwarded to the lower device. + struct qemu_output : kapi::cio::output_device + { + //! The port to write to. + using port = io::port<0xE9, unsigned char, io::port_write, io::port_read>; + + //! Construct a new debug device wrapper for the given output device. + //! + //! @param lower The device to forward the output to. + explicit qemu_output(output_device & lower); + + //! @copydoc kapi::cio::output_device + auto write(kapi::cio::output_stream stream, std::string_view text) -> void override; + + private: + //! The device to forward the output to. + output_device & m_lower; + //! Whether the device has been detected. + bool m_present; + }; + +} // namespace arch::debug + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/device_io/port_io.hpp b/arch/x86_64/include/arch/device_io/port_io.hpp new file mode 100644 index 0000000..65e58e3 --- /dev/null +++ b/arch/x86_64/include/arch/device_io/port_io.hpp @@ -0,0 +1,107 @@ +#ifndef TEACHOS_X86_64_IO_PORT_IO_HPP +#define TEACHOS_X86_64_IO_PORT_IO_HPP + +#include +#include +#include +#include +#include +#include + +namespace arch::io +{ + + //! The requirements imposed on a type usable for port I/O. + template + concept port_io_type = requires { + requires sizeof(ValueType) == 1 || sizeof(ValueType) == 2 || sizeof(ValueType) == 4; + requires std::default_initializable; + std::bit_cast( + std::conditional_t>{}); + }; + + template + struct port_read + { + //! Read from the I/O port. + //! + //! @return The data read from the I/O port. + auto static read() noexcept + { + auto data = typename Derived::value_type{}; + asm volatile((code[Derived::size / 2]) + : [data] "=m"(data) + : [port] "i"(Derived::address) + : "dx", (Derived::data_register)); + return data; + } + + private: + constexpr port_read() noexcept = default; + friend Derived; + + //! The assembly templates used for reading from an I/O port. + constexpr auto static code = std::array{ + std::string_view{"mov %[port], %%dx\nin %%dx, %%al\nmov %%al, %[data]"}, + std::string_view{"mov %[port], %%dx\nin %%dx, %%ax\nmov %%ax, %[data]"}, + std::string_view{"mov %[port], %%dx\nin %%dx, %%eax\nmov %%eax, %[data]"}, + }; + }; + + template + struct port_write + { + //! Write data to the I/O port. + //! + //! @param data The data to write to the I/O port. + auto static write(std::same_as auto data) noexcept -> void + { + asm volatile((code[Derived::size / 2]) + : + : [port] "i"(Derived::address), [data] "im"(data) + : "dx", (Derived::data_register)); + } + + private: + constexpr port_write() noexcept = default; + friend Derived; + + //! The assembly templates used for writing to an I/O port. + constexpr auto static code = std::array{ + std::string_view{"mov %[port], %%dx\nmov %%dx, %[data]\nout %%al, %%dx"}, + std::string_view{"mov %[port], %%dx\nmov %%dx, %[data]\nout %%ax, %%dx"}, + std::string_view{"mov %[port], %%dx\nmov %%dx, %[data]\nout %%eax, %%dx"}, + }; + }; + + //! An I/O port of a given size at a given address. + //! + //! Port I/O leverages a separate address space to communicate with devices via the memory bus, allowing for byte + //! to double-word sized transfers. + //! + //! @tparam Address The address (port number) of the I/O port. + //! @tparam Size The size (in bytes) of the I/O port. + //! @tparam Features The features (readable, writeable) + template typename... Features> + requires(sizeof...(Features) > 0) + struct port : Features>... + { + //! The type of the data of this port. + using value_type = ValueType; + + //! The address of this I/O port. + constexpr auto static address = Address; + + //! The size of this I/O port. + constexpr auto static size = sizeof(value_type); + + //! The register clobbered by the I/O operation. + constexpr auto static data_register = size == 1 ? std::string_view{"al"} + : size == 2 ? std::string_view{"ax"} + : std::string_view{"eax"}; + }; + +} // namespace arch::io + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/buffered_allocator.hpp b/arch/x86_64/include/arch/memory/buffered_allocator.hpp new file mode 100644 index 0000000..87ebbf6 --- /dev/null +++ b/arch/x86_64/include/arch/memory/buffered_allocator.hpp @@ -0,0 +1,138 @@ +#ifndef TEACHOS_X86_64_BUFFERED_ALLOCATOR_HPP +#define TEACHOS_X86_64_BUFFERED_ALLOCATOR_HPP + +#include "kapi/memory.hpp" +#include "kapi/system.hpp" + +#include +#include +#include +#include +#include + +namespace arch::memory +{ + + template + struct buffered_allocator : kapi::memory::frame_allocator + { + explicit buffered_allocator(frame_allocator * underlying) + : m_underlying{underlying} + , m_free{BufferSize} + { + auto from_underlying = m_underlying->allocate_many(BufferSize); + if (!from_underlying) + { + kapi::system::panic("[x86_64:MEM] Not enough frames available from underlying allocator."); + } + auto [first_frame, count] = *from_underlying; + std::ranges::generate_n(m_pool.begin(), count, [first_frame]() mutable { return first_frame++; }); + } + + buffered_allocator(buffered_allocator const &) = delete; + buffered_allocator(buffered_allocator && other) noexcept = delete; + + ~buffered_allocator() override + { + std::ranges::for_each(m_pool, [this](auto const & maybe_frame) { + if (maybe_frame) + { + m_underlying->release_many({*maybe_frame, 1}); + } + }); + } + + auto operator=(buffered_allocator const &) = delete; + auto operator=(buffered_allocator &&) = delete; + + auto allocate_many(std::size_t count = 1) noexcept + -> std::optional> override + { + if (count > m_free) + { + return m_underlying->allocate_many(count); + } + + auto start_of_set = std::ranges::find_if(m_pool, [](auto const & candidate) { return candidate.has_value(); }); + if (start_of_set == m_pool.end()) + { + return m_underlying->allocate_many(count); + } + + if (std::distance(start_of_set, m_pool.end()) < static_cast(count)) + { + return m_underlying->allocate_many(count); + } + + auto number_of_consecutive_frames = 1uz; + for (auto current = std::next(start_of_set); current != m_pool.end() && number_of_consecutive_frames < count; + ++current) + { + if (*current && *start_of_set && **current == (**start_of_set) + 1) + { + ++number_of_consecutive_frames; + continue; + } + number_of_consecutive_frames = 0; + start_of_set = current; + } + + if (std::distance(start_of_set, m_pool.end()) >= static_cast(count)) + { + auto result = std::make_pair(**start_of_set, count); + m_free -= count; + std::ranges::for_each(start_of_set, start_of_set + count, [](auto & frame) { frame.reset(); }); + sort_pool(); + return result; + } + return m_underlying->allocate_many(count); + } + + auto release_many(std::pair frame_set) -> void override + { + if (m_free == BufferSize) + { + return m_underlying->release_many(frame_set); + } + + auto [first_frame, count] = frame_set; + auto start_of_set = std::ranges::find_if(m_pool, [](auto const & candidate) { return !candidate.has_value(); }); + + for (auto current = start_of_set; current != m_pool.end() && count; ++current) + { + *current = first_frame++; + ++m_free; + --count; + } + sort_pool(); + + if (count) + { + m_underlying->release_many({first_frame, count}); + } + } + + private: + auto sort_pool() -> void + { + std::ranges::sort(m_pool, [](auto lhs, auto rhs) -> bool { + if (lhs && rhs) + { + return *lhs < *rhs; + } + if (!lhs) + { + return false; + } + return true; + }); + } + + frame_allocator * m_underlying; + std::size_t m_free; + std::array, BufferSize> m_pool{}; + }; + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/kernel_mapper.hpp b/arch/x86_64/include/arch/memory/kernel_mapper.hpp new file mode 100644 index 0000000..4329f1b --- /dev/null +++ b/arch/x86_64/include/arch/memory/kernel_mapper.hpp @@ -0,0 +1,34 @@ +#ifndef TEACHOS_X86_64_KERNEL_MAPPER_HPP +#define TEACHOS_X86_64_KERNEL_MAPPER_HPP + +#include "kapi/memory.hpp" + +#include +#include +#include + +#include +#include + +namespace arch::memory +{ + + struct kernel_mapper + { + using section_header_type = elf::section_header; + + explicit kernel_mapper(multiboot2::information_view const * mbi); + + auto remap_kernel(kapi::memory::page_mapper & mapper) -> void; + + private: + auto map_section(section_header_type const & section, std::string_view name, kapi::memory::page_mapper & mapper) + -> void; + + multiboot2::information_view const * m_mbi; + std::uintptr_t m_kernel_load_base; + }; + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/mmu.hpp b/arch/x86_64/include/arch/memory/mmu.hpp new file mode 100644 index 0000000..2d64184 --- /dev/null +++ b/arch/x86_64/include/arch/memory/mmu.hpp @@ -0,0 +1,27 @@ +#ifndef TEACHOS_X86_64_MEMORY_MMU_HPP +#define TEACHOS_X86_64_MEMORY_MMU_HPP + +#include "kapi/memory/address.hpp" + +namespace arch::memory +{ + /** + * @brief Invalidates any translation lookaside buffer (TLB) entry for the page table the given address is cotained + * in. See https://www.felixcloutier.com/x86/invlpg for more information on the used x86 instruction. + * + * @param address Memory address, which will be used to determine the contained page and flush the TLB entry for + * that page. + */ + auto tlb_flush(kapi::memory::linear_address address) -> void; + + /** + * @brief Invalidates the translation lookaside buffer (TLB) entry for all page tables. + * + * @note Simply reassigns the CR3 register the value of the CR3 register, causing a flush of the TLB buffer, because + * the system has to assume that the location of the level 4 page table moved. + */ + auto tlb_flush_all() -> void; + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/page_table.hpp b/arch/x86_64/include/arch/memory/page_table.hpp new file mode 100644 index 0000000..a82d9e0 --- /dev/null +++ b/arch/x86_64/include/arch/memory/page_table.hpp @@ -0,0 +1,342 @@ +#ifndef TEACHOS_X86_64_PAGE_TABLE_HPP +#define TEACHOS_X86_64_PAGE_TABLE_HPP + +#include "kapi/memory.hpp" + +#include "arch/memory/page_utilities.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace arch::memory +{ + + //! A table containing page mapping entries. + //! + //! Page tables exist in a multi-level hierarchy and are used to map pages (virtual memory) onto frames (physical + //! memory). Conceptually, pages represent the data found in a virtual address space, while frames represent their + //! storage. Only a level 1 page table maps an actual page onto a frame. All other page tables on higher levels do not + //! map payload pages, but rather their subordinate page tables. + struct page_table + { + //! An entry in a page table. + //! + //! A page table entry is a combination of a frame number and a set of flags that determine the properties and + //! access rights to a mapped page. Entries at a higher level in the page hierarchy do not map a page directly, + //! unless that page is marked as huge on the relevant level, but rather map the next lower page table. + struct entry + { + //! Flags marking the state and configuration of an entry. + //! + //! An entry in a page table may have any combination of these flags active at the same time. The final flags of a + //! page are determined as the strictest combination (logical AND) of all flags along the hierarchy. + //! + //! @note This is a bitfield enum as defined by kstd::ext::bitfield_enum. + enum struct flags : std::uint64_t + { + empty = 0, + present = 1uz << 0, //!< The page is mapped. + writable = 1uz << 1, //!< The page is writable. + user_accessible = 1uz << 2, //!< The page is accessible in user mode. + write_through = 1uz << 3, //!< Any writes to the page must immediately hit memory. + disable_cache = 1uz << 4, //!< Any writes to the page must never be cached. + accessed = 1uz << 5, //!< The page was accessed. + dirty = 1uz << 6, //!< The page was written to. + huge_page = 1uz << 7, //!< The page is huge. + global = 1uz << 8, //!< The TLB entry for this page must not be flushed on context switches. + no_execute = 1uz << 63, //!< The data in this page must not be executed. + }; + + //! Construct an empty entry. + entry() = default; + + //! Clear this entry, ensuring all information is set to zero. + //! + //! This effectively marks the page represented by this entry as not present. In addition, it also removes any + //! information about the frame referenced by this entry. + auto clear() noexcept -> void; + + //! Check if the page represented by this entry is present. + //! + //! @note This function does not attempt to walk the page table hierarchy, but only performs a local check. This + //! means, that if the page is not mapped at a lower level, this will not be detected. + //! + //! @return @p true iff. the page is present at this level, @p false otherwise. + [[nodiscard]] auto present() const noexcept -> bool; + + //! Check if the page represented by this entry is a huge page. + //! + //! @note The effective size of the page depends on the level of the page table containing this entry. + //! + //! @return @p true iff. the page is marked as being huge, @p false otherwise. + [[nodiscard]] auto huge() const noexcept -> bool; + + //! Get all flags present in this entry. + //! + //! @return The flags that are currently set on this entry. + [[nodiscard]] auto all_flags() const noexcept -> flags; + + //! Set all flags of this entry. + //! + //! @param flags The flags to apply to this entry. + auto all_flags(flags flags) noexcept -> void; + + //! Add the given flags to the flags of this entry. + //! + //! @param rhs The flags to add to this entry's flags. + //! @return A reference to this entry. + auto operator|=(flags rhs) noexcept -> entry &; + + //! Get the frame number associated with this entry, if the referenced page is present. + //! + //! @return an engaged std::optional iff. this entry maps a page, std::nullopt otherwise. + [[nodiscard]] auto frame() const noexcept -> std::optional; + + //! Map this entry. + //! + //! @param frame The frame to map in this entry. + //! @param flags The flags to apply to this entry. + auto frame(kapi::memory::frame frame, flags flags) noexcept -> void; + + private: + //! A mask to retrieve, or exclude, the frame number from the raw entry. + //! + //! Page table entries in x86_64 are a compacted combination of the relevant flags and the frame number. This mask + //! represents the bits that make up the frame number in an entry. + constexpr auto static frame_number_mask{0x000f'ffff'ffff'f000uz}; + + //! The raw entry bytes. + //! + //! @see entry::frame_number_mask + std::uint64_t m_raw{}; + }; + + //! The maximum number of entries in this table. + constexpr auto static entry_count{kapi::memory::page::size / sizeof(entry)}; + + //! Get the entry at the given index. + //! + //! @warning This function will panic if the entry index is out of bounds. + //! + //! @param index The index of the desired entry. + //! @return A reference to the entry at the given index. + [[nodiscard]] auto operator[](std::size_t index) -> entry &; + + //! @copydoc page_table::operator[] + [[nodiscard]] auto operator[](std::size_t index) const -> entry const &; + + //! Clear the entire page table. + //! + //! This function effectively marks the page table as not mapping any pages. + auto clear() noexcept -> void; + + //! Check if the page table is empty. + //! + //! @return @p true iff. this page table has no entries marked present, @p false otherwise. + [[nodiscard]] auto empty() const noexcept -> bool; + + private: + std::array m_entries{}; + }; + + //! A recursively mapped page table. + //! + //! A page table in which at least one entry maps the same table. Recursive page tables allow for easy access to other + //! tables within the page mapping hierarchy, without having to map them prior to access, through careful construction + //! of linear addresses that pass through the same index multiple times. + template + requires(Level > 0uz && Level < 5uz) + struct recursive_page_table : page_table + { + constexpr auto static next_level = Level - 1uz; + constexpr auto static recursive_index = 0776uz; + + //! Get the next lower lever table. + //! + //! @param self The object type of this object. + //! @param index The index corresponding to the desired page map. + //! @return An engaged std::optional holding a pointer to the next lower page table iff. the next lower page table + //! at the desired index is present, std::nullopt otherwise. + [[nodiscard]] auto next(this auto && self, std::size_t index) noexcept + requires(next_level > 1) + { + return self.next_address(index).transform([](auto address) -> auto { + auto table_pointer = std::bit_cast *>(address); + return &std::forward_like(*table_pointer); + }); + } + + //! @copydoc recursive_page_table::next + //! + //! @note This overload returns a non-hierarchical, or leaf, page table + [[nodiscard]] auto next(this auto && self, std::size_t index) noexcept + requires(next_level == 1) + { + return self.next_address(index).transform([](auto address) -> auto { + auto table_pointer = std::bit_cast(address); + return &std::forward_like(*table_pointer); + }); + } + + [[nodiscard]] auto translate(kapi::memory::linear_address address) const + -> std::optional + requires(Level == 4) + { + auto offset = address.raw() % kapi::memory::page::size; + return translate(kapi::memory::page::containing(address)).transform([offset](auto frame) -> auto { + return kapi::memory::physical_address{frame.start_address().raw() + offset}; + }); + } + + [[nodiscard]] auto translate(kapi::memory::page page) const -> std::optional + requires(Level == 4) + { + auto pml3 = next(pml_index<4>(page)); + + if (!pml3) + { + return std::nullopt; + } + + auto handle_huge_page = [&] -> std::optional { + auto pml3_entry = pml3.transform([&](auto pml3) -> auto { return (*pml3)[pml_index<3>(page)]; }); + if (!pml3_entry) + { + return std::nullopt; + } + else if (pml3_entry->huge()) + { + auto pml3_entry_frame = *pml3_entry->frame(); + return kapi::memory::frame{pml3_entry_frame.number() + pml_index<2>(page) * entry_count + pml_index<1>(page)}; + } + + auto pml2 = (*pml3)->next(pml_index<3>(page)); + auto pml2_entry = pml2.transform([&](auto pml2) -> auto { return (*pml2)[pml_index<2>(page)]; }); + if (!pml2_entry) + { + return std::nullopt; + } + else if (pml2_entry->huge()) + { + auto pml2_entry_frame = *pml2_entry->frame(); + return kapi::memory::frame{pml2_entry_frame.number() + pml_index<1>(page)}; + } + + return std::nullopt; + }; + + return pml3.and_then([&](auto pml3) -> auto { return pml3->next(pml_index<3>(page)); }) + .and_then([&](auto pml2) -> auto { return pml2->next(pml_index<2>(page)); }) + .and_then([&](auto pml1) -> auto { return (*pml1)[pml_index<1>(page)].frame(); }) + .or_else(handle_huge_page); + } + + private: + //! The number of address bits used to represent the page index per level. + constexpr auto static level_bits = 9; + //! The highest address bit. + constexpr auto static high_bit = 48; + //! The number of bits representing the offset into a page. + constexpr auto static offset_bits = 12; + + //! Calculate the recursive address of the next lower page table. + //! + //! @param index The index of the desired page table. + //! @return An engaged std::optional holding the address of the new lower page table iff. the next lower page table + //! at the desired index is present, std::nullopt otherwise. + [[nodiscard]] auto next_address(std::size_t index) const noexcept -> std::optional + { + if (auto entry = (*this)[index]; entry.present() && !entry.huge()) + { + auto this_address = std::bit_cast(this); + auto next_address = (this_address << level_bits) | 1uz << high_bit | (index << offset_bits); + return next_address; + } + + return std::nullopt; + } + }; + +} // namespace arch::memory + +namespace kstd::ext +{ + template<> + struct is_bitfield_enum : std::true_type + { + }; +} // namespace kstd::ext + +namespace arch::memory +{ + + constexpr auto to_mapper_flags(page_table::entry::flags flags) -> kapi::memory::page_mapper::flags + { + using table_flags = page_table::entry::flags; + using mapper_flags = kapi::memory::page_mapper::flags; + + auto result = mapper_flags{}; + + if ((flags & table_flags::no_execute) == table_flags::empty) + { + result |= mapper_flags::executable; + } + + if ((flags & table_flags::writable) != table_flags::empty) + { + result |= mapper_flags::writable; + } + + if ((flags & table_flags::disable_cache) != table_flags::empty) + { + result |= mapper_flags::uncached; + } + + if ((flags & table_flags::user_accessible) == table_flags::empty) + { + result |= mapper_flags::supervisor_only; + } + + return result; + } + + constexpr auto to_table_flags(kapi::memory::page_mapper::flags flags) -> page_table::entry::flags + { + using table_flags = page_table::entry::flags; + using mapper_flags = kapi::memory::page_mapper::flags; + + auto result = table_flags{}; + + if ((flags & mapper_flags::executable) == mapper_flags::empty) + { + result |= table_flags::no_execute; + } + + if ((flags & mapper_flags::writable) != mapper_flags::empty) + { + result |= table_flags::writable; + } + + if ((flags & mapper_flags::uncached) != mapper_flags::empty) + { + result |= table_flags::disable_cache; + } + + if ((flags & mapper_flags::supervisor_only) != mapper_flags::empty) + { + result |= table_flags::user_accessible; + } + + return result; + } + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/page_utilities.hpp b/arch/x86_64/include/arch/memory/page_utilities.hpp new file mode 100644 index 0000000..8c25af3 --- /dev/null +++ b/arch/x86_64/include/arch/memory/page_utilities.hpp @@ -0,0 +1,22 @@ +#ifndef TEACHOS_X86_64_PAGE_UTILITIES_HPP +#define TEACHOS_X86_64_PAGE_UTILITIES_HPP + +#include "kapi/memory.hpp" + +#include + +namespace arch::memory +{ + + template + requires(Level > 0uz && Level < 5uz) + constexpr auto pml_index(kapi::memory::page page) noexcept -> std::size_t + { + constexpr auto shift_width = (Level - 1) * 9; + constexpr auto index_mask = 0x1ffuz; + return page.number() >> shift_width & index_mask; + } + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/paging_root.hpp b/arch/x86_64/include/arch/memory/paging_root.hpp new file mode 100644 index 0000000..febbb11 --- /dev/null +++ b/arch/x86_64/include/arch/memory/paging_root.hpp @@ -0,0 +1,27 @@ +#ifndef TEACHOS_X86_64_PAGING_ROOT_HPP +#define TEACHOS_X86_64_PAGING_ROOT_HPP + +#include "arch/memory/page_table.hpp" + +namespace arch::memory +{ + + //! The active, recursively mapped, root map (e.g. PML4) + struct paging_root : recursive_page_table<4> + { + auto static get() -> paging_root *; + + paging_root(paging_root const &) = delete; + paging_root(paging_root &&) = delete; + auto operator=(paging_root const &) -> paging_root & = delete; + auto operator=(paging_root &&) -> paging_root & = delete; + + ~paging_root() = delete; + + private: + paging_root() = default; + }; + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/recursive_page_mapper.hpp b/arch/x86_64/include/arch/memory/recursive_page_mapper.hpp new file mode 100644 index 0000000..e278a6d --- /dev/null +++ b/arch/x86_64/include/arch/memory/recursive_page_mapper.hpp @@ -0,0 +1,25 @@ +#ifndef TEACHOS_X86_64_RECURSIVE_PAGE_MAPPER_HPP +#define TEACHOS_X86_64_RECURSIVE_PAGE_MAPPER_HPP + +#include "kapi/memory.hpp" + +#include + +namespace arch::memory +{ + + struct recursive_page_mapper : kapi::memory::page_mapper + { + explicit recursive_page_mapper(kapi::memory::frame_allocator & allocator); + + auto map(kapi::memory::page page, kapi::memory::frame frame, flags flags) -> std::byte * override; + auto unmap(kapi::memory::page page) -> void override; + auto try_unmap(kapi::memory::page page) noexcept -> bool override; + + private: + kapi::memory::frame_allocator * m_allocator; + }; + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/memory/region_allocator.hpp b/arch/x86_64/include/arch/memory/region_allocator.hpp new file mode 100644 index 0000000..f391293 --- /dev/null +++ b/arch/x86_64/include/arch/memory/region_allocator.hpp @@ -0,0 +1,84 @@ +#ifndef TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP +#define TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP + +#include "kapi/memory/address.hpp" +#include "kapi/memory/frame.hpp" +#include "kapi/memory/frame_allocator.hpp" + +#include + +#include +#include +#include + +namespace arch::memory +{ + //! A simple, memory-region based frame allocator. + //! + //! This frame allocator linearly allocates frames that are in available memory regions. It automatically skips any + //! frames occupied by the kernel image or any bootloader provided data. + //! + //! @note This allocator will never release frames. + struct region_allocator final : kapi::memory::frame_allocator + { + struct memory_information + { + //! The memory range occupied by the loaded kernel image. + //! + //! This includes all sections that are marked as occupying space in the kernel executable. The internal structure + //! of this area is more described in a more fine-grained manner by the ELF symbol information provided in the + //! Multiboot2 information by the loader. + std::pair image_range; + + //! The memory range occupied by the loader supplied Multiboot2 information structure. + //! + //! In general, this information is allocated somewhere in the range of the loaded image, but the loader protocol + //! does not guarantee this. It is thus imperative to be able to handle the cases where the loader chooses to + //! allocate the information structure outside of the image range. + std::pair mbi_range; + + //! The loader supplied map of memory regions. + //! + //! These include available, unavailable, and reclaimable regions. In general, only frames tha