From 1b964278762dde86b0b737bd9a34fec569339f54 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Apr 2026 10:29:30 +0200 Subject: x86_64: use p1204 project layout --- arch/x86_64/CMakeLists.txt | 50 ++- arch/x86_64/arch/boot/boot.hpp | 56 +++ arch/x86_64/arch/boot/boot32.S | 443 +++++++++++++++++++++ arch/x86_64/arch/boot/entry64.s | 62 +++ arch/x86_64/arch/boot/initialize_runtime.cpp | 22 + arch/x86_64/arch/boot/ld.hpp | 61 +++ arch/x86_64/arch/boot/multiboot.s | 33 ++ arch/x86_64/arch/bus/isa.cpp | 14 + arch/x86_64/arch/bus/isa.hpp | 18 + arch/x86_64/arch/cpu/control_register.hpp | 249 ++++++++++++ arch/x86_64/arch/cpu/global_descriptor_table.hpp | 91 +++++ arch/x86_64/arch/cpu/initialization.cpp | 164 ++++++++ arch/x86_64/arch/cpu/initialization.hpp | 12 + arch/x86_64/arch/cpu/interrupts.S | 112 ++++++ arch/x86_64/arch/cpu/interrupts.cpp | 203 ++++++++++ arch/x86_64/arch/cpu/interrupts.hpp | 116 ++++++ arch/x86_64/arch/cpu/legacy_pic.hpp | 19 + arch/x86_64/arch/cpu/model_specific_register.hpp | 151 +++++++ arch/x86_64/arch/cpu/registers.hpp | 32 ++ arch/x86_64/arch/cpu/segment_descriptor.hpp | 61 +++ arch/x86_64/arch/cpu/segment_selector.hpp | 21 + arch/x86_64/arch/cpu/task_state_segment.hpp | 30 ++ arch/x86_64/arch/debug/qemu_output.cpp | 25 ++ arch/x86_64/arch/debug/qemu_output.hpp | 43 ++ arch/x86_64/arch/device_io/port_io.hpp | 112 ++++++ arch/x86_64/arch/devices/init.cpp | 76 ++++ arch/x86_64/arch/devices/init.hpp | 12 + arch/x86_64/arch/devices/legacy_pit.cpp | 60 +++ arch/x86_64/arch/devices/legacy_pit.hpp | 29 ++ arch/x86_64/arch/devices/local_apic.cpp | 134 +++++++ arch/x86_64/arch/devices/local_apic.hpp | 37 ++ arch/x86_64/arch/memory/higher_half_mapper.cpp | 119 ++++++ arch/x86_64/arch/memory/higher_half_mapper.hpp | 44 ++ arch/x86_64/arch/memory/kernel_mapper.cpp | 117 ++++++ arch/x86_64/arch/memory/kernel_mapper.hpp | 35 ++ arch/x86_64/arch/memory/mmu.cpp | 19 + arch/x86_64/arch/memory/mmu.hpp | 27 ++ arch/x86_64/arch/memory/page_table.cpp | 82 ++++ arch/x86_64/arch/memory/page_table.hpp | 232 +++++++++++ arch/x86_64/arch/memory/page_utilities.hpp | 29 ++ arch/x86_64/arch/memory/region_allocator.cpp | 165 ++++++++ arch/x86_64/arch/memory/region_allocator.hpp | 93 +++++ arch/x86_64/arch/vga/crtc.hpp | 35 ++ arch/x86_64/arch/vga/text.hpp | 10 + arch/x86_64/arch/vga/text/attribute.hpp | 30 ++ arch/x86_64/arch/vga/text/buffer.cpp | 101 +++++ arch/x86_64/arch/vga/text/buffer.hpp | 101 +++++ arch/x86_64/arch/vga/text/color.hpp | 35 ++ arch/x86_64/arch/vga/text/common_attributes.hpp | 37 ++ arch/x86_64/arch/vga/text/device.cpp | 60 +++ arch/x86_64/arch/vga/text/device.hpp | 45 +++ arch/x86_64/arch/vga/text/flags.hpp | 38 ++ arch/x86_64/include/arch/boot/boot.hpp | 56 --- arch/x86_64/include/arch/boot/ld.hpp | 61 --- arch/x86_64/include/arch/bus/isa.hpp | 18 - arch/x86_64/include/arch/cpu/control_register.hpp | 249 ------------ .../include/arch/cpu/global_descriptor_table.hpp | 91 ----- arch/x86_64/include/arch/cpu/initialization.hpp | 12 - arch/x86_64/include/arch/cpu/interrupts.hpp | 116 ------ arch/x86_64/include/arch/cpu/legacy_pic.hpp | 19 - .../include/arch/cpu/model_specific_register.hpp | 151 ------- arch/x86_64/include/arch/cpu/registers.hpp | 32 -- .../x86_64/include/arch/cpu/segment_descriptor.hpp | 61 --- arch/x86_64/include/arch/cpu/segment_selector.hpp | 21 - .../x86_64/include/arch/cpu/task_state_segment.hpp | 30 -- arch/x86_64/include/arch/debug/qemu_output.hpp | 43 -- arch/x86_64/include/arch/device_io/port_io.hpp | 112 ------ arch/x86_64/include/arch/devices/init.hpp | 12 - arch/x86_64/include/arch/devices/legacy_pit.hpp | 29 -- arch/x86_64/include/arch/devices/local_apic.hpp | 37 -- .../include/arch/memory/higher_half_mapper.hpp | 44 -- arch/x86_64/include/arch/memory/kernel_mapper.hpp | 35 -- arch/x86_64/include/arch/memory/mmu.hpp | 27 -- arch/x86_64/include/arch/memory/page_table.hpp | 232 ----------- arch/x86_64/include/arch/memory/page_utilities.hpp | 29 -- .../include/arch/memory/region_allocator.hpp | 93 ----- 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 -- .../pre/include/arch/context_switching/main.hpp | 51 --- .../arch/context_switching/syscall/main.hpp | 91 ----- .../context_switching/syscall/syscall_enable.hpp | 18 - .../context_switching/syscall/syscall_handler.hpp | 18 - arch/x86_64/pre/include/arch/kernel/halt.hpp | 13 - arch/x86_64/pre/include/arch/kernel/main.hpp | 13 - arch/x86_64/pre/include/arch/user/main.hpp | 16 - arch/x86_64/pre/src/context_switching/main.cpp | 66 --- .../pre/src/context_switching/syscall/main.cpp | 35 -- .../context_switching/syscall/syscall_enable.cpp | 32 -- .../context_switching/syscall/syscall_handler.cpp | 121 ------ arch/x86_64/pre/src/kernel/main.cpp | 71 ---- arch/x86_64/pre/src/user/main.cpp | 35 -- arch/x86_64/src/boot/boot32.S | 443 --------------------- arch/x86_64/src/boot/entry64.s | 62 --- arch/x86_64/src/boot/initialize_runtime.cpp | 22 - arch/x86_64/src/boot/multiboot.s | 33 -- arch/x86_64/src/bus/isa.cpp | 14 - arch/x86_64/src/cpu/initialization.cpp | 164 -------- arch/x86_64/src/cpu/interrupt_stubs.S | 112 ------ arch/x86_64/src/cpu/interrupts.cpp | 203 ---------- arch/x86_64/src/debug/qemu_output.cpp | 25 -- arch/x86_64/src/devices/init.cpp | 76 ---- arch/x86_64/src/devices/legacy_pit.cpp | 60 --- arch/x86_64/src/devices/local_apic.cpp | 134 ------- arch/x86_64/src/memory/higher_half_mapper.cpp | 119 ------ arch/x86_64/src/memory/kernel_mapper.cpp | 117 ------ arch/x86_64/src/memory/mmu.cpp | 19 - arch/x86_64/src/memory/page_table.cpp | 82 ---- arch/x86_64/src/memory/region_allocator.cpp | 165 -------- arch/x86_64/src/vga/text/buffer.cpp | 101 ----- arch/x86_64/src/vga/text/device.cpp | 60 --- 116 files changed, 3982 insertions(+), 4552 deletions(-) create mode 100644 arch/x86_64/arch/boot/boot.hpp create mode 100644 arch/x86_64/arch/boot/boot32.S create mode 100644 arch/x86_64/arch/boot/entry64.s create mode 100644 arch/x86_64/arch/boot/initialize_runtime.cpp create mode 100644 arch/x86_64/arch/boot/ld.hpp create mode 100644 arch/x86_64/arch/boot/multiboot.s create mode 100644 arch/x86_64/arch/bus/isa.cpp create mode 100644 arch/x86_64/arch/bus/isa.hpp create mode 100644 arch/x86_64/arch/cpu/control_register.hpp create mode 100644 arch/x86_64/arch/cpu/global_descriptor_table.hpp create mode 100644 arch/x86_64/arch/cpu/initialization.cpp create mode 100644 arch/x86_64/arch/cpu/initialization.hpp create mode 100644 arch/x86_64/arch/cpu/interrupts.S create mode 100644 arch/x86_64/arch/cpu/interrupts.cpp create mode 100644 arch/x86_64/arch/cpu/interrupts.hpp create mode 100644 arch/x86_64/arch/cpu/legacy_pic.hpp create mode 100644 arch/x86_64/arch/cpu/model_specific_register.hpp create mode 100644 arch/x86_64/arch/cpu/registers.hpp create mode 100644 arch/x86_64/arch/cpu/segment_descriptor.hpp create mode 100644 arch/x86_64/arch/cpu/segment_selector.hpp create mode 100644 arch/x86_64/arch/cpu/task_state_segment.hpp create mode 100644 arch/x86_64/arch/debug/qemu_output.cpp create mode 100644 arch/x86_64/arch/debug/qemu_output.hpp create mode 100644 arch/x86_64/arch/device_io/port_io.hpp create mode 100644 arch/x86_64/arch/devices/init.cpp create mode 100644 arch/x86_64/arch/devices/init.hpp create mode 100644 arch/x86_64/arch/devices/legacy_pit.cpp create mode 100644 arch/x86_64/arch/devices/legacy_pit.hpp create mode 100644 arch/x86_64/arch/devices/local_apic.cpp create mode 100644 arch/x86_64/arch/devices/local_apic.hpp create mode 100644 arch/x86_64/arch/memory/higher_half_mapper.cpp create mode 100644 arch/x86_64/arch/memory/higher_half_mapper.hpp create mode 100644 arch/x86_64/arch/memory/kernel_mapper.cpp create mode 100644 arch/x86_64/arch/memory/kernel_mapper.hpp create mode 100644 arch/x86_64/arch/memory/mmu.cpp create mode 100644 arch/x86_64/arch/memory/mmu.hpp create mode 100644 arch/x86_64/arch/memory/page_table.cpp create mode 100644 arch/x86_64/arch/memory/page_table.hpp create mode 100644 arch/x86_64/arch/memory/page_utilities.hpp create mode 100644 arch/x86_64/arch/memory/region_allocator.cpp create mode 100644 arch/x86_64/arch/memory/region_allocator.hpp create mode 100644 arch/x86_64/arch/vga/crtc.hpp create mode 100644 arch/x86_64/arch/vga/text.hpp create mode 100644 arch/x86_64/arch/vga/text/attribute.hpp create mode 100644 arch/x86_64/arch/vga/text/buffer.cpp create mode 100644 arch/x86_64/arch/vga/text/buffer.hpp create mode 100644 arch/x86_64/arch/vga/text/color.hpp create mode 100644 arch/x86_64/arch/vga/text/common_attributes.hpp create mode 100644 arch/x86_64/arch/vga/text/device.cpp create mode 100644 arch/x86_64/arch/vga/text/device.hpp create mode 100644 arch/x86_64/arch/vga/text/flags.hpp delete mode 100644 arch/x86_64/include/arch/boot/boot.hpp delete mode 100644 arch/x86_64/include/arch/boot/ld.hpp delete mode 100644 arch/x86_64/include/arch/bus/isa.hpp delete mode 100644 arch/x86_64/include/arch/cpu/control_register.hpp delete mode 100644 arch/x86_64/include/arch/cpu/global_descriptor_table.hpp delete mode 100644 arch/x86_64/include/arch/cpu/initialization.hpp delete mode 100644 arch/x86_64/include/arch/cpu/interrupts.hpp delete mode 100644 arch/x86_64/include/arch/cpu/legacy_pic.hpp delete mode 100644 arch/x86_64/include/arch/cpu/model_specific_register.hpp delete mode 100644 arch/x86_64/include/arch/cpu/registers.hpp delete mode 100644 arch/x86_64/include/arch/cpu/segment_descriptor.hpp delete mode 100644 arch/x86_64/include/arch/cpu/segment_selector.hpp delete mode 100644 arch/x86_64/include/arch/cpu/task_state_segment.hpp delete mode 100644 arch/x86_64/include/arch/debug/qemu_output.hpp delete mode 100644 arch/x86_64/include/arch/device_io/port_io.hpp delete mode 100644 arch/x86_64/include/arch/devices/init.hpp delete mode 100644 arch/x86_64/include/arch/devices/legacy_pit.hpp delete mode 100644 arch/x86_64/include/arch/devices/local_apic.hpp delete mode 100644 arch/x86_64/include/arch/memory/higher_half_mapper.hpp delete mode 100644 arch/x86_64/include/arch/memory/kernel_mapper.hpp delete mode 100644 arch/x86_64/include/arch/memory/mmu.hpp delete mode 100644 arch/x86_64/include/arch/memory/page_table.hpp delete mode 100644 arch/x86_64/include/arch/memory/page_utilities.hpp delete mode 100644 arch/x86_64/include/arch/memory/region_allocator.hpp delete mode 100644 arch/x86_64/include/arch/vga/crtc.hpp delete mode 100644 arch/x86_64/include/arch/vga/text.hpp delete mode 100644 arch/x86_64/include/arch/vga/text/attribute.hpp delete mode 100644 arch/x86_64/include/arch/vga/text/buffer.hpp delete mode 100644 arch/x86_64/include/arch/vga/text/color.hpp delete mode 100644 arch/x86_64/include/arch/vga/text/common_attributes.hpp delete mode 100644 arch/x86_64/include/arch/vga/text/device.hpp delete mode 100644 arch/x86_64/include/arch/vga/text/flags.hpp delete mode 100644 arch/x86_64/pre/include/arch/context_switching/main.hpp delete mode 100644 arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp delete mode 100644 arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp delete mode 100644 arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp delete mode 100644 arch/x86_64/pre/include/arch/kernel/halt.hpp delete mode 100644 arch/x86_64/pre/include/arch/kernel/main.hpp delete mode 100644 arch/x86_64/pre/include/arch/user/main.hpp delete mode 100644 arch/x86_64/pre/src/context_switching/main.cpp delete mode 100644 arch/x86_64/pre/src/context_switching/syscall/main.cpp delete mode 100644 arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp delete mode 100644 arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp delete mode 100644 arch/x86_64/pre/src/kernel/main.cpp delete mode 100644 arch/x86_64/pre/src/user/main.cpp delete mode 100644 arch/x86_64/src/boot/boot32.S delete mode 100644 arch/x86_64/src/boot/entry64.s delete mode 100644 arch/x86_64/src/boot/initialize_runtime.cpp delete mode 100644 arch/x86_64/src/boot/multiboot.s delete mode 100644 arch/x86_64/src/bus/isa.cpp delete mode 100644 arch/x86_64/src/cpu/initialization.cpp delete mode 100644 arch/x86_64/src/cpu/interrupt_stubs.S delete mode 100644 arch/x86_64/src/cpu/interrupts.cpp delete mode 100644 arch/x86_64/src/debug/qemu_output.cpp delete mode 100644 arch/x86_64/src/devices/init.cpp delete mode 100644 arch/x86_64/src/devices/legacy_pit.cpp delete mode 100644 arch/x86_64/src/devices/local_apic.cpp delete mode 100644 arch/x86_64/src/memory/higher_half_mapper.cpp delete mode 100644 arch/x86_64/src/memory/kernel_mapper.cpp delete mode 100644 arch/x86_64/src/memory/mmu.cpp delete mode 100644 arch/x86_64/src/memory/page_table.cpp delete mode 100644 arch/x86_64/src/memory/region_allocator.cpp delete mode 100644 arch/x86_64/src/vga/text/buffer.cpp delete mode 100644 arch/x86_64/src/vga/text/device.cpp (limited to 'arch') diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt index 9e346ef..5657010 100644 --- a/arch/x86_64/CMakeLists.txt +++ b/arch/x86_64/CMakeLists.txt @@ -5,8 +5,16 @@ add_library("x86_64" OBJECT) add_library("arch::lib" ALIAS "x86_64") +target_include_directories("x86_64" PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + +target_link_libraries("x86_64" PUBLIC + "os::kapi" + "libs::multiboot2" +) + target_sources("x86_64" PRIVATE - # Platform-dependent KAPI implementation "kapi/boot_modules.cpp" "kapi/cio.cpp" "kapi/cpu.cpp" @@ -14,39 +22,41 @@ target_sources("x86_64" PRIVATE "kapi/interrupts.cpp" "kapi/memory.cpp" "kapi/system.cpp" +) +target_sources("x86_64" PRIVATE # CPU Initialization - "src/cpu/initialization.cpp" - "src/cpu/interrupts.cpp" - "src/cpu/interrupt_stubs.S" + "arch/cpu/initialization.cpp" + "arch/cpu/interrupts.cpp" + "arch/cpu/interrupts.S" # Bus Initialization - "src/bus/isa.cpp" + "arch/bus/isa.cpp" # Low-level bootstrap - "src/boot/boot32.S" - "src/boot/entry64.s" - "src/boot/initialize_runtime.cpp" - "src/boot/multiboot.s" + "arch/boot/boot32.S" + "arch/boot/entry64.s" + "arch/boot/initialize_runtime.cpp" + "arch/boot/multiboot.s" # Debug interfaces - "src/debug/qemu_output.cpp" + "arch/debug/qemu_output.cpp" # Devices - "src/devices/init.cpp" - "src/devices/legacy_pit.cpp" - "src/devices/local_apic.cpp" + "arch/devices/init.cpp" + "arch/devices/legacy_pit.cpp" + "arch/devices/local_apic.cpp" # Memory management - "src/memory/kernel_mapper.cpp" - "src/memory/higher_half_mapper.cpp" - "src/memory/mmu.cpp" - "src/memory/page_table.cpp" - "src/memory/region_allocator.cpp" + "arch/memory/kernel_mapper.cpp" + "arch/memory/higher_half_mapper.cpp" + "arch/memory/mmu.cpp" + "arch/memory/page_table.cpp" + "arch/memory/region_allocator.cpp" # VGA text mode - "src/vga/text/buffer.cpp" - "src/vga/text/device.cpp" + "arch/vga/text/buffer.cpp" + "arch/vga/text/device.cpp" ) file(GLOB_RECURSE ARCH_HEADERS diff --git a/arch/x86_64/arch/boot/boot.hpp b/arch/x86_64/arch/boot/boot.hpp new file mode 100644 index 0000000..7df61c4 --- /dev/null +++ b/arch/x86_64/arch/boot/boot.hpp @@ -0,0 +1,56 @@ +#ifndef TEACHOS_X86_64_BOOT_BOOT_HPP +#define TEACHOS_X86_64_BOOT_BOOT_HPP + +#ifdef __ASSEMBLER__ +// clang-format off + +//! The number of huge pages to map during bootstrap. +#define HUGE_PAGES_TO_MAP (16) + +//! The magic value to be set in eax by the multiboot 2 loader. +#define MULTIBOOT2_MAGIC (0x36d76289) + +//! The "A" bit in a GDT entry. +#define GDT_ACCESSED (1 << 40) + +//! The "R/W" bit in a GDT entry +#define GDT_READ_WRITE (1 << 41) + +//! The "E" bit in a GDT entry. +#define GDT_EXECUTABLE (1 << 43) + +//! The "S" bit in a GDT entry. +#define GDT_DESCRIPTOR_TYPE (1 << 44) + +//! The "P" bit in a GDT entry. +#define GDT_PRESENT (1 << 47) + +//! The "L" bit in a GDT entry. +#define GDT_LONG_MODE (1 << 53) + +// clang-format on +#else + +#include // 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/arch/boot/boot32.S b/arch/x86_64/arch/boot/boot32.S new file mode 100644 index 0000000..e6fcd85 --- /dev/null +++ b/arch/x86_64/arch/boot/boot32.S @@ -0,0 +1,443 @@ +#include + +/** + * @brief Uninitialized data for the bootstrapping process. + */ +.section .boot_bss, "aw", @nobits + +page_maps_start: +page_map_level_4: .skip 512 * 8 +page_map_level_3_high: .skip 512 * 8 +page_map_level_3_low: .skip 512 * 8 +page_map_level_2: .skip 512 * 8 +page_maps_end = . +page_maps_size = page_maps_end - page_maps_start + +/** + * @brief Storage for the multiboot2 information pointer. + */ +.global multiboot_information_pointer +multiboot_information_pointer: .skip 8 + +/** + * @brief Storage for the bootstrap stack. + */ +.section .boot_stack, "aw", @nobits +.align 16 + +early_stack_bottom: .skip 1 << 8 +early_stack_top: +early_stack_size = early_stack_top - early_stack_bottom + +/** + * @brief Constants for the bootstrapping process. + */ +.section .boot_rodata, "a", @progbits + +.global global_descriptor_table_data + +/** + * @brief A basic GDT for long mode. + */ +global_descriptor_table: +global_descriptor_table_null = . - global_descriptor_table +.quad 0 +global_descriptor_table_code = . - global_descriptor_table +.quad GDT_READ_WRITE | GDT_EXECUTABLE | GDT_DESCRIPTOR_TYPE | GDT_PRESENT | GDT_LONG_MODE | (1 << 55) +global_descriptor_table_data = . - global_descriptor_table +.quad GDT_READ_WRITE | GDT_DESCRIPTOR_TYPE | GDT_PRESENT | (1 << 54) | (1 << 55) +global_descriptor_table_end: + +message_prefix_panic: .string "Panic: " +message_not_loaded_by_multiboot2: .string "The operating system was not loaded by a Multiboot 2 loader." +message_cpuid_instruction_no_supported: .string "The 'cpuid' instruction is not supported on this platform." +message_long_mode_not_supported: .string "Long mode is not supported by this platform." +message_return_from_kernel_main: .string "Execution returned from kernel main." + +/** + * @brief Initialized data for the bootstrapping process. + */ +.section .boot_data, "aw", @progbits + +/** + * @brief A pointer to the current position within the VGA text buffer. + */ +.global vga_buffer_pointer +vga_buffer_pointer: .quad 0xb8000 + +/** + * @brief Code for the bootstrapping process. + */ +.section .boot_text, "ax", @progbits +.align 16 +.code32 + +.macro pie_base + push %esi + call 0f + 0: + pop %esi +.endm + +.macro function_start + push %ebp + mov %esp, %ebp +.endm + +.macro function_end + leave + ret +.endm + +.macro pie_function_start + function_start + pie_base +.endm + +.macro pie_function_end + pop %esi + function_end +.endm + +/** + * @brief Prepare the environment and start the kernel. + * + * This function performs all necessary checks to ensure the system was loaded + * by the expected loader and supports all features required to run the kernel. + * If successful, it prepares the system by setting up memory virtualization + * and then start the kernel proper. + * + * @param %eax The Multiboot 2 magic marker. + * @param %ebx The Multiboot 2 information pointer. + * @return void This function does not return. + */ +.global _start +_start: + call 0f +0: + pop %esi + + lea (early_stack_top - 0b)(%esi), %ecx + + mov %ecx, %esp + mov %esp, %ebp + + call _assert_loaded_by_multiboot2_loader + call _save_multiboot_information_pointer + + call _assert_cpuid_instruction_is_supported + call _assert_cpu_supports_long_mode + + push $HUGE_PAGES_TO_MAP + call _prepare_page_maps + add $4, %esp + + call _enable_paging + call _enable_sse + call _reload_gdt + + lea (_entry64 - 0b)(%esi), %eax + pushl $global_descriptor_table_code + pushl %eax + lret + +/** + * @brief Halt the system. + * + * This function will instruct the CPU to halt. It will try to keep the CPU + * halted, even if interrupts occur. + * + * @return This function never returns. + */ +_halt: + function_start + +1: + hlt + jmp 1b + + function_end + +/** + * @brief Print a message via the VGA text buffer. + * + * @param ebp+12 The message to print. + * @param ebp+8 The color to print the message in. + */ +_print: + pie_function_start + + push %edi + push %ebx + + mov 8(%ebp), %al + mov 12(%ebp), %edx + mov $0, %ecx + lea (vga_buffer_pointer - 0b)(%esi), %edi + mov (%edi), %edi + +1: + mov (%edx, %ecx), %bl + test %bl, %bl + je 2f + mov %bl, (%edi, %ecx, 2) + mov %al, 1(%edi, %ecx, 2) + inc %ecx + jmp 1b + +2: + shl $1, %ecx + add %ecx, %edi + lea (vga_buffer_pointer - 0b)(%esi), %ecx + mov %edi, (%ecx) + + pop %ebx + pop %edi + + pie_function_end + +/** + * @brief Print a given panic message and then halt the machine as if by calling ::halt() + * + * @param ebp+4 A message to print. + * @return This function does not return. + */ +_panic: + pie_function_start + + lea (message_prefix_panic - 0b)(%esi), %eax + + push %eax + push $0x4f + call _print + + mov 8(%ebp), %eax + mov %eax, 4(%esp) + call _print + add $8, %esp + + call _halt + + pie_function_end + +/** + * Assert that we were loaded by a Multiboot 2 compliant bootloader. + * + * This assertion will panic the system if the magic signature was not found. + * If we were loaded my an appropriate bootloader, this function also saves + * the provided MBI pointer to `multiboot_information_pointer`. + */ +_assert_loaded_by_multiboot2_loader: + pie_function_start + + cmp $MULTIBOOT2_MAGIC, %eax + je 1f + lea (message_not_loaded_by_multiboot2 - 0b)(%esi), %eax + push %eax + call _panic +1: + pie_function_end + +/** + * @brief Store the multiboot 2 information pointer in the global memory. + * + * @return void + */ +_save_multiboot_information_pointer: + pie_function_start + + lea (multiboot_information_pointer - 0b)(%esi), %eax + mov %ebx, (%eax) + + pie_function_end + +/** + * @brief Assert that the CPU supports the CPUID instruction. + * + * The primary way to check for support of the instruction is to flip the ID + * bin in EFLAGS and then check if this changed was accepted. If so, the CPU + * supports the CPUID instruction, otherwise it most-likely doesn't. + */ +_assert_cpuid_instruction_is_supported: + pie_function_start + + pushfl + pop %eax + mov %eax, %ecx + + xor $(1 << 21), %eax /* Flip the ID bit */ + push %eax /* Move the new bitset on the stack for loading */ + popfl /* Load the flags with ID set back into EFLAGS */ + pushfl /* Copy the flags back onto the stack */ + pop %eax /* Load the flags for further checking */ + + push %ecx + popfl + + cmp %ecx, %eax + jne 1f + lea (message_cpuid_instruction_no_supported - 0b)(%esi), %eax + push %eax + call _panic + +1: + pie_function_end + +/** + * @brief Assert that the CPU supports going into long mode. + */ +_assert_cpu_supports_long_mode: + pie_function_start + + mov $0x80000000, %eax + cpuid + cmp $0x80000001, %eax + jb 1f + + mov $0x80000001, %eax + cpuid + test $(1 << 29), %edx + jnz 2f +1: + lea (message_long_mode_not_supported - 0b)(%esi), %eax + push %eax + call _panic +2: + pie_function_end + +/** + * @brief Prepare a basic page map hierarchy + * + * @param ebp+8 The number of huge pages to map + * @return void + */ +_prepare_page_maps: + pie_function_start + + push %edi + + call _clear_page_map_memory + + lea (page_map_level_4 - 0b)(%esi), %edi + lea (page_map_level_3_low - 0b)(%esi), %eax + or $0b11, %eax + mov %eax, (%edi) + + lea (page_map_level_3_high - 0b)(%esi), %eax + or $0b11, %eax + mov %eax, (511 * 8)(%edi) + + lea (page_map_level_3_low - 0b)(%esi), %edi + lea (page_map_level_2 - 0b)(%esi), %eax + or $0b11, %eax + mov %eax, (%edi) + + lea (page_map_level_3_high - 0b)(%esi), %edi + lea (page_map_level_2 - 0b)(%esi), %eax + or $0b11, %eax + mov %eax, (510 * 8)(%edi) + + lea (page_map_level_2 - 0b)(%esi), %edi + mov 8(%ebp), %ecx + +1: + dec %ecx + mov $(1 << 21), %eax + mul %ecx + or $0b10000011, %eax + mov %eax, (%edi, %ecx, 8) + + test %ecx, %ecx + jnz 1b + + pop %edi + + pie_function_end + +/** + * @brief Clear all page map memory by filling it with 0s. + * + * @return void + */ +_clear_page_map_memory: + pie_function_start + + push %edi + + xor %eax, %eax + mov $page_maps_size, %ecx + shr $2, %ecx + lea (page_maps_start - 0b)(%esi), %edi + rep stosl + + pop %edi + + pie_function_end + +/** + * @p Enable memory virtualization via paging. + * + * Note: This routine expects for there to be a valid set of page maps already + * set up for use. + * + * @return void + */ +_enable_paging: + pie_function_start + + lea (page_map_level_4 - 0b)(%esi), %eax + mov %eax, %cr3 + + /* Enable Physical Address Extension */ + mov %cr4, %eax + or $(1 << 5), %eax + mov %eax, %cr4 + + /* Enable long mode support */ + mov $0xC0000080, %ecx + rdmsr + or $(1 << 8), %eax + wrmsr + + /* Enable paging */ + mov %cr0, %eax + or $(1 << 31), %eax + mov %eax, %cr0 + + pie_function_end + +/** + * @brief Enable use of SSE instructions. + */ +_enable_sse: + function_start + + mov %cr0, %eax + and $0xfffffffb, %eax + or $0x00000002, %eax + mov %eax, %cr0 + + mov %cr4, %eax + or $(3 << 9), %eax + mov %eax, %cr4 + + function_end + +/** + * @brief Prepare a new GTD and load make it active. + * + * @return void + */ +_reload_gdt: + pie_function_start + + sub $10, %esp + lea (global_descriptor_table - 0b)(%esi), %eax + movw $(global_descriptor_table_end - global_descriptor_table -1), (%esp) + mov %eax, 2(%esp) + movl $0, 6(%esp) + + lgdt (%esp) + add $10, %esp + + pie_function_end diff --git a/arch/x86_64/arch/boot/entry64.s b/arch/x86_64/arch/boot/entry64.s new file mode 100644 index 0000000..29fb778 --- /dev/null +++ b/arch/x86_64/arch/boot/entry64.s @@ -0,0 +1,62 @@ +.section .stack, "aw", @nobits + +.align 16 +.global stack_top +stack_bottom: .skip 1 << 20 +stack_top: +stack_size = stack_top - stack_bottom + +.section .bss, "aw", @nobits + +//! A structure containing information gathered during the bootstrap process. +//! Expected layout (as described by teachos::boot::information): +//! +//! struct +//! { +//! multiboot2::information_view const * mbi; +//! std::size_t vga_buffer_index; +//! } +.global bootstrap_information +bootstrap_information: .skip 16 + +.section .boot_text, "ax", @progbits +.code64 + +.global _entry64 +_entry64: + mov $global_descriptor_table_data, %rax + mov %rax, %ss + mov %rax, %ds + mov %rax, %es + mov %rax, %fs + mov %rax, %gs + + mov $stack_top, %rsp + mov %rsp, %rbp + + mov multiboot_information_pointer, %rax + or $TEACHOS_VMA, %rax + mov vga_buffer_pointer, %rdx + sub $0xb8000, %rdx + mov %rax, (bootstrap_information) + mov %rdx, (bootstrap_information + 8) + + call invoke_global_constructors + + xor %rax, %rax + mov %rax, %rbp + mov %rax, %rdx + mov %rax, %rsi + + mov $stack_size, %rcx + shr $3, %rcx + lea (stack_bottom), %rdi + rep stosq + + mov %rax, %rdi + + call main + +1: + hlt + jmp 1b diff --git a/arch/x86_64/arch/boot/initialize_runtime.cpp b/arch/x86_64/arch/boot/initialize_runtime.cpp new file mode 100644 index 0000000..b08c13c --- /dev/null +++ b/arch/x86_64/arch/boot/initialize_runtime.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +namespace arch::boot +{ + + extern "C" + { + using global_initializer = auto (*)() -> void; + + extern global_initializer __init_array_start; + extern global_initializer __init_array_end; + + auto invoke_global_constructors() -> void + { + auto initializers = std::span{&__init_array_start, &__init_array_end}; + std::ranges::for_each(initializers, [](auto invokable) { std::invoke(invokable); }); + } + } + +} // namespace arch::boot \ No newline at end of file diff --git a/arch/x86_64/arch/boot/ld.hpp b/arch/x86_64/arch/boot/ld.hpp new file mode 100644 index 0000000..988723d --- /dev/null +++ b/arch/x86_64/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/arch/boot/multiboot.s b/arch/x86_64/arch/boot/multiboot.s new file mode 100644 index 0000000..37d8afe --- /dev/null +++ b/arch/x86_64/arch/boot/multiboot.s @@ -0,0 +1,33 @@ +.section .boot_mbh, "a" +.align 8 + +multiboot_header_start: +.Lmagic: + .long 0xe85250d6 +.Larch: + .long 0 +.Llength: + .long multiboot_header_end - multiboot_header_start +.Lchecksum: + .long 0x100000000 - (0xe85250d6 + 0 + (multiboot_header_end - multiboot_header_start)) +.align 8 +.Lflags_start: + .word 4 + .word 1 + .long .Lflags_end - .Lflags_start + .long 3 +.Lflags_end: +.align 8 +.Linformation_request_start: + .word 1 + .word 0 + .long .Linformation_request_end - .Linformation_request_start + .long 3 +.Linformation_request_end: +.align 8 +.Lend_start: + .word 0 + .word 0 + .long .Lend_end - .Lend_start +.Lend_end: +multiboot_header_end: diff --git a/arch/x86_64/arch/bus/isa.cpp b/arch/x86_64/arch/bus/isa.cpp new file mode 100644 index 0000000..f6cc72d --- /dev/null +++ b/arch/x86_64/arch/bus/isa.cpp @@ -0,0 +1,14 @@ +#include + +#include + +#include + +namespace arch::bus +{ + + isa::isa(std::size_t major) + : kapi::devices::bus{major, 0, "isa"} + {} + +} // namespace arch::bus \ No newline at end of file diff --git a/arch/x86_64/arch/bus/isa.hpp b/arch/x86_64/arch/bus/isa.hpp new file mode 100644 index 0000000..e56f56a --- /dev/null +++ b/arch/x86_64/arch/bus/isa.hpp @@ -0,0 +1,18 @@ +#ifndef TEACHOS_X86_64_BUS_ISA_HPP +#define TEACHOS_X86_64_BUS_ISA_HPP + +#include + +#include + +namespace arch::bus +{ + + struct isa final : public kapi::devices::bus + { + isa(std::size_t major); + }; + +} // namespace arch::bus + +#endif // TEACHOS_X86_64_BUS_ISA_HPP diff --git a/arch/x86_64/arch/cpu/control_register.hpp b/arch/x86_64/arch/cpu/control_register.hpp new file mode 100644 index 0000000..9cedc35 --- /dev/null +++ b/arch/x86_64/arch/cpu/control_register.hpp @@ -0,0 +1,249 @@ +#ifndef TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP +#define TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP + +// IWYU pragma: private, include + +#include + +#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 + { + constexpr auto address_shift = 12uz; + return kapi::memory::physical_address{m_address << address_shift}; + } + + //! 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/arch/cpu/global_descriptor_table.hpp b/arch/x86_64/arch/cpu/global_descriptor_table.hpp new file mode 100644 index 0000000..b17c509 --- /dev/null +++ b/arch/x86_64/arch/cpu/global_descriptor_table.hpp @@ -0,0 +1,91 @@ +#ifndef TEACHOS_X86_64_GLOBAL_DESCRIPTOR_TABLE_HPP +#define TEACHOS_X86_64_GLOBAL_DESCRIPTOR_TABLE_HPP + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace arch::cpu +{ + + template + struct global_descriptor_table; + + struct [[gnu::packed]] global_descriptor_table_pointer + { + template + global_descriptor_table_pointer(global_descriptor_table const & gdt) + : size{GdtSize * sizeof(segment_descriptor) - 1} + , address{kapi::memory::physical_address{std::bit_cast(&gdt)}.raw()} + {} + + auto load() -> void + { + asm volatile("lgdt %0" : : "m"(*this)); + } + + std::uint16_t size{}; + std::uint64_t address{}; + }; + + static_assert(sizeof(global_descriptor_table_pointer) == sizeof(std::uint16_t) + sizeof(std::uint64_t)); + + template + struct global_descriptor_table + { + template... SegmentDescriptors> + constexpr global_descriptor_table(SegmentDescriptors const &... descriptors) + : m_descriptors{} + { + auto descriptor_data = std::array{ + std::pair{std::bit_cast(&descriptors), sizeof(descriptors)} + ... + }; + auto written_size = 0uz; + std::ranges::for_each(descriptor_data, [&written_size, this](auto entry) { + std::ranges::copy(entry.first, entry.first + entry.second, m_descriptors.begin() + written_size); + written_size += entry.second; + }); + } + + auto load(std::size_t code_segment_index, std::size_t data_segment_index) const -> void + { + auto pointer = global_descriptor_table_pointer{*this}; + pointer.load(); + + asm volatile("push %0\n" + "lea 1f(%%rip), %%rax\n" + "push %%rax\n" + "lretq\n" + "1:\n" + "mov %1, %%rax\n" + "mov %%rax, %%ss\n" + "mov %%rax, %%ds\n" + "mov %%rax, %%es\n" + "mov %%rax, %%fs\n" + "mov %%rax, %%gs\n" + : + : "X"(code_segment_index * sizeof(segment_descriptor)), + "X"(data_segment_index * sizeof(segment_descriptor)) + : "rax"); + } + + private: + std::array m_descriptors; + }; + + template... SegmentDescriptors> + global_descriptor_table(SegmentDescriptors const &... descriptors) + -> global_descriptor_table<(sizeof(SegmentDescriptors) + ...)>; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/cpu/initialization.cpp b/arch/x86_64/arch/cpu/initialization.cpp new file mode 100644 index 0000000..1be9c82 --- /dev/null +++ b/arch/x86_64/arch/cpu/initialization.cpp @@ -0,0 +1,164 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace arch::cpu +{ + + namespace + { + constexpr auto gdt_null_descriptor = segment_descriptor{}; + + constexpr auto gdt_kernel_code_descriptor = segment_descriptor{ + .limit_low = 0xffff, + .base_low = 0, + .accessed = false, + .read_write = false, + .direction_or_conforming = false, + .executable = true, + .type = segment_type::code_or_data, + .privilege_level = 0, + .present = true, + .limit_high = 0xf, + .long_mode = true, + .protected_mode = false, + .granularity = segment_granularity::page, + .base_high = 0, + }; + + constexpr auto gdt_kernel_data_descriptor = segment_descriptor{ + .limit_low = 0xffff, + .base_low = 0, + .accessed = false, + .read_write = true, + .direction_or_conforming = false, + .executable = false, + .type = segment_type::code_or_data, + .privilege_level = 0, + .present = true, + .limit_high = 0xf, + .long_mode = false, + .protected_mode = true, + .granularity = segment_granularity::page, + .base_high = 0, + }; + + constexpr auto gdt_user_code_descriptor = segment_descriptor{ + .limit_low = 0xffff, + .base_low = 0, + .accessed = false, + .read_write = false, + .direction_or_conforming = false, + .executable = true, + .type = segment_type::code_or_data, + .privilege_level = 3, + .present = true, + .limit_high = 0xf, + .long_mode = true, + .protected_mode = false, + .granularity = segment_granularity::page, + .base_high = 0, + }; + + constexpr auto gdt_user_data_descriptor = segment_descriptor{ + .limit_low = 0xffff, + .base_low = 0, + .accessed = false, + .read_write = true, + .direction_or_conforming = false, + .executable = false, + .type = segment_type::code_or_data, + .privilege_level = 3, + .present = true, + .limit_high = 0xf, + .long_mode = false, + .protected_mode = false, + .granularity = segment_granularity::page, + .base_high = 0, + }; + + constexpr auto make_tss_descriptor(task_state_segment const * tss_ptr) -> system_segment_descriptor + { + auto const address = std::bit_cast(tss_ptr); + auto const limit = sizeof(task_state_segment) - 1; + + return system_segment_descriptor{ + { + .limit_low = limit & 0xffff, // NOLINT(readability-magic-numbers) + .base_low = address & 0xffffff, // NOLINT(readability-magic-numbers) + .accessed = false, + .read_write = false, + .direction_or_conforming = false, + .executable = false, + .type = segment_type::system, + .privilege_level = 0, + .present = true, + .limit_high = (limit >> 16) & 0xf, // NOLINT(readability-magic-numbers) + .long_mode = false, + .protected_mode = false, + .granularity = segment_granularity::byte, + .base_high = (address >> 24) & 0xff, // NOLINT(readability-magic-numbers) + }, + (address >> 32) & 0xffff'ffff, // NOLINT(readability-magic-numbers) + }; + } + } // namespace + + auto initialize_descriptors() -> void + { + auto static tss = task_state_segment{}; + auto static tss_descriptor = make_tss_descriptor(&tss); + + auto static gdt = global_descriptor_table{ + gdt_null_descriptor, gdt_kernel_code_descriptor, gdt_kernel_data_descriptor, + gdt_user_code_descriptor, gdt_user_data_descriptor, tss_descriptor, + }; + + kstd::println("[x86_64:SYS] Reloading Global Descriptor Table."); + gdt.load(1, 2); + + kstd::println("[x86_64:SYS] Initializing Interrupt Descriptor Table."); + auto static idt = interrupt_descriptor_table{}; + idt.load(); + } + + auto initialize_legacy_interrupts() -> void + { + constexpr auto pic_init_command = std::uint8_t{0x11}; + constexpr auto pic_master_offset = std::uint8_t{0x20}; + constexpr auto pic_slave_offset = std::uint8_t{0x28}; + constexpr auto pic_cascade_address = std::uint8_t{0x04}; + constexpr auto pic_cascade_slave_identity = std::uint8_t{0x02}; + constexpr auto pic_use_8086_mode = std::uint8_t{0x01}; + constexpr auto pic_master_mask = std::uint8_t{0x00}; + constexpr auto pic_slave_mask = std::uint8_t{0x00}; + + pic_master_control_port::write(pic_init_command); + pic_slave_control_port::write(pic_init_command); + + pic_master_data_port::write(pic_master_offset); + pic_slave_data_port::write(pic_slave_offset); + + pic_master_data_port::write(pic_cascade_address); + pic_slave_data_port::write(pic_cascade_slave_identity); + + pic_master_data_port::write(pic_use_8086_mode); + pic_slave_data_port::write(pic_use_8086_mode); + + pic_master_data_port::write(pic_master_mask); + pic_slave_data_port::write(pic_slave_mask); + + pic_master_data_port::write(pic_master_mask); + pic_slave_data_port::write(pic_slave_mask); + } + +} // namespace arch::cpu diff --git a/arch/x86_64/arch/cpu/initialization.hpp b/arch/x86_64/arch/cpu/initialization.hpp new file mode 100644 index 0000000..564c544 --- /dev/null +++ b/arch/x86_64/arch/cpu/initialization.hpp @@ -0,0 +1,12 @@ +#ifndef TEACHOS_ARCH_X86_64_CPU_INITIALIZATION_HPP +#define TEACHOS_ARCH_X86_64_CPU_INITIALIZATION_HPP + +namespace arch::cpu +{ + auto initialize_descriptors() -> void; + + auto initialize_legacy_interrupts() -> void; + +} // namespace arch::cpu + +#endif diff --git a/arch/x86_64/arch/cpu/interrupts.S b/arch/x86_64/arch/cpu/interrupts.S new file mode 100644 index 0000000..e59bdd2 --- /dev/null +++ b/arch/x86_64/arch/cpu/interrupts.S @@ -0,0 +1,112 @@ +.altmacro + +.macro ISR_WITHOUT_ERROR_CODE vector + .global isr\vector + isr\vector: + pushq $0 + pushq $\vector + jmp common_interrupt_handler +.endm + +.macro ISR_WITH_ERROR_CODE vector + .global isr\vector + isr\vector: + pushq $\vector + jmp common_interrupt_handler +.endm + +.macro ISR_TABLE_ENTRY vector + .quad isr\vector +.endm + +.section .rodata +.global isr_stub_table +.align 16 + +isr_stub_table: +.set i, 0 +.rept 256 + ISR_TABLE_ENTRY %i + .set i, i + 1 +.endr + + +.section .text + +common_interrupt_handler: + push %rax + push %rbx + push %rcx + push %rdx + push %rbp + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + push %r12 + push %r13 + push %r14 + push %r15 + + mov %rsp, %rdi + call interrupt_dispatch + + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rbp + pop %rdx + pop %rcx + pop %rbx + pop %rax + + add $16, %rsp + iretq + +ISR_WITHOUT_ERROR_CODE 0 +ISR_WITHOUT_ERROR_CODE 1 +ISR_WITHOUT_ERROR_CODE 2 +ISR_WITHOUT_ERROR_CODE 3 +ISR_WITHOUT_ERROR_CODE 4 +ISR_WITHOUT_ERROR_CODE 5 +ISR_WITHOUT_ERROR_CODE 6 +ISR_WITHOUT_ERROR_CODE 7 +ISR_WITH_ERROR_CODE 8 +ISR_WITHOUT_ERROR_CODE 9 +ISR_WITH_ERROR_CODE 10 +ISR_WITH_ERROR_CODE 11 +ISR_WITH_ERROR_CODE 12 +ISR_WITH_ERROR_CODE 13 +ISR_WITH_ERROR_CODE 14 +ISR_WITHOUT_ERROR_CODE 15 +ISR_WITHOUT_ERROR_CODE 16 +ISR_WITH_ERROR_CODE 17 +ISR_WITHOUT_ERROR_CODE 18 +ISR_WITHOUT_ERROR_CODE 19 +ISR_WITHOUT_ERROR_CODE 20 +ISR_WITH_ERROR_CODE 21 +ISR_WITHOUT_ERROR_CODE 22 +ISR_WITHOUT_ERROR_CODE 23 +ISR_WITHOUT_ERROR_CODE 24 +ISR_WITHOUT_ERROR_CODE 25 +ISR_WITHOUT_ERROR_CODE 26 +ISR_WITHOUT_ERROR_CODE 27 +ISR_WITHOUT_ERROR_CODE 28 +ISR_WITH_ERROR_CODE 29 +ISR_WITH_ERROR_CODE 30 +ISR_WITHOUT_ERROR_CODE 31 + +.set i, 32 +.rept 256 - 32 + ISR_WITHOUT_ERROR_CODE %i + .set i, i + 1 +.endr diff --git a/arch/x86_64/arch/cpu/interrupts.cpp b/arch/x86_64/arch/cpu/interrupts.cpp new file mode 100644 index 0000000..f40422f --- /dev/null +++ b/arch/x86_64/arch/cpu/interrupts.cpp @@ -0,0 +1,203 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace arch::cpu +{ + + namespace + { + enum struct exception + { + divide_error, + debug_exception, + non_maskable_interrupt, + breakpoint, + overflow, + bound_range_exceeded, + invalid_opcode, + device_not_available, + double_fault, + coprocessor_segment_overrun, + invalid_tss, + segment_not_present, + stack_segment_fault, + general_protection_fault, + page_fault, + x87_fpu_floating_point_error = 16, + alignment_check, + machine_check, + simd_floating_point_error, + virtualization_exception, + control_protection_exception, + hypervisor_injection_exception = 28, + vmm_communication_exception, + security_exception, + }; + + constexpr auto number_of_exception_vectors = 32u; + + constexpr auto pic_master_irq_start = 0x20; + constexpr auto pic_master_irq_end = pic_master_irq_start + 8; + constexpr auto pic_slave_irq_start = pic_master_irq_end; + + constexpr auto to_exception_type(exception e) + { + switch (e) + { + case exception::divide_error: + case exception::x87_fpu_floating_point_error: + case exception::simd_floating_point_error: + return kapi::cpu::exception::type::arithmetic_error; + case exception::breakpoint: + return kapi::cpu::exception::type::breakpoint; + case exception::invalid_opcode: + return kapi::cpu::exception::type::illegal_instruction; + case exception::stack_segment_fault: + return kapi::cpu::exception::type::memory_access_fault; + case exception::general_protection_fault: + return kapi::cpu::exception::type::privilege_violation; + case exception::page_fault: + return kapi::cpu::exception::type::page_fault; + case exception::alignment_check: + return kapi::cpu::exception::type::alignment_fault; + default: + return kapi::cpu::exception::type::unknown; + } + } + + constexpr auto has_error_code(exception e) + { + switch (e) + { + case exception::double_fault: + case exception::invalid_tss: + case exception::segment_not_present: + case exception::stack_segment_fault: + case exception::general_protection_fault: + case exception::page_fault: + case exception::alignment_check: + case exception::control_protection_exception: + case exception::security_exception: + return true; + default: + return false; + } + } + + auto dispatch_exception(interrupt_frame * frame) -> bool + { + auto type = to_exception_type(static_cast(frame->interrupt.number)); + auto fault_address = kapi::memory::linear_address{}; + auto instruction_pointer = frame->cpu_saved.rip; + + switch (type) + { + case kapi::cpu::exception::type::page_fault: + { + asm volatile("mov %%cr2, %0" : "=r"(fault_address)); + auto present = (frame->interrupt.error_code & 0x1) != 0; + auto write = (frame->interrupt.error_code & 0x2) != 0; + auto user = (frame->interrupt.error_code & 0x4) != 0; + + return kapi::cpu::dispatch({type, instruction_pointer, fault_address, present, write, user}); + } + default: + return kapi::cpu::dispatch({type, instruction_pointer}); + } + } + + auto acknowledge_pic_interrupt(interrupt_frame * frame) -> void + { + if (frame->interrupt.number >= pic_slave_irq_start) + { + pic_slave_control_port::write(pic_end_of_interrupt); + } + pic_master_control_port::write(pic_end_of_interrupt); + } + } // namespace + + extern "C" + { + extern std::uintptr_t const isr_stub_table[256]; + + auto interrupt_dispatch(interrupt_frame * frame) -> void + { + auto [number, code] = frame->interrupt; + + if (number < number_of_exception_vectors) + { + if (!dispatch_exception(frame)) + { + if (has_error_code(static_cast(number))) + { + kstd::println(kstd::print_sink::stderr, + "[x86_64:CPU] Unhandled exception number {:#04x} received with code {:#04x}", number, code); + } + else + { + kstd::println(kstd::print_sink::stderr, "[x86_64:CPU] Unhandled exception number {:#04x} received", number); + } + } + } + else + { + auto irq_number = number - number_of_exception_vectors; + + if (kapi::interrupts::dispatch(irq_number) == kapi::interrupts::status::unhandled) + { + kstd::println(kstd::print_sink::stderr, "[x86_64:CPU] Unhandled interrupt {:#04x} (IRQ{})", number, + irq_number); + } + + acknowledge_pic_interrupt(frame); + } + } + } + + interrupt_descriptor_table::interrupt_descriptor_table() noexcept + { + for (auto i = 0uz; i < 256; ++i) + { + m_descriptors[i] = gate_descriptor{ + .offset_low = static_cast(isr_stub_table[i] & 0xffff), // NOLINT(readability-magic-numbers) + .m_code_segment = segment_selector{0, false, 1}, + .interrupt_stack_table_selector = 0, + .gate_type = (i < 32 && i != 2) ? gate_type::trap_gate : gate_type::interrupt_gate, + .descriptor_privilege_level = 0, + .present = true, + .offset_middle = + static_cast((isr_stub_table[i] >> 16) & 0xffff), // NOLINT(readability-magic-numbers) + .offset_high = + static_cast((isr_stub_table[i] >> 32) & 0xffff'ffff), // NOLINT(readability-magic-numbers) + }; + } + } + + auto interrupt_descriptor_table::load() const -> void + { + interrupt_descriptor_table_register{.limit = sizeof(m_descriptors) - 1, .base = m_descriptors.data()}.load(); + } + + auto interrupt_descriptor_table_register::load() const -> void + { + asm volatile("lidt %0" : : "m"(*this)); + } + + auto interrupt_descriptor_table_register::read() -> interrupt_descriptor_table_register + { + interrupt_descriptor_table_register idtr{}; + asm volatile("sidt %0" : : "m"(idtr)); + return idtr; + } + +} // namespace arch::cpu \ No newline at end of file diff --git a/arch/x86_64/arch/cpu/interrupts.hpp b/arch/x86_64/arch/cpu/interrupts.hpp new file mode 100644 index 0000000..6162f56 --- /dev/null +++ b/arch/x86_64/arch/cpu/interrupts.hpp @@ -0,0 +1,116 @@ +#ifndef TEACHOS_X86_64_CPU_INTERRUPTS_HPP +#define TEACHOS_X86_64_CPU_INTERRUPTS_HPP + +#include + +#include + +#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. + enum 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); + + //! The stack frame as established by the low-level assembly interrupt stubs. + //! + //! @note The layout of this struct is reverse than what would reasonably be expected. The reason for this reversal is + //! that fact that it represents a stack frame. Stack frames on x86_64 grow towards lower addresses, meaning the first + //! item on the stack is the last item in C++ memory layout order. + struct interrupt_frame + { + struct + { + std::uint64_t r15{}; + std::uint64_t r14{}; + std::uint64_t r13{}; + std::uint64_t r12{}; + std::uint64_t r11{}; + std::uint64_t r10{}; + std::uint64_t r9{}; + std::uint64_t r8{}; + std::uint64_t rdi{}; + std::uint64_t rsi{}; + std::uint64_t rbp{}; + std::uint64_t rdx{}; + std::uint64_t rcx{}; + std::uint64_t rbx{}; + std::uint64_t rax{}; + } handler_saved; + + struct + { + std::uint64_t number{}; + std::uint64_t error_code{}; + } interrupt; + + struct + { + kapi::memory::linear_address rip{}; + std::uint64_t cs{}; + std::uint64_t rflags{}; + kapi::memory::linear_address rsp{}; + std::uint64_t ss{}; + } cpu_saved; + }; + + struct interrupt_descriptor_table + { + interrupt_descriptor_table() noexcept; + + auto load() const -> void; + + private: + std::array m_descriptors{}; + }; + + struct [[gnu::packed]] interrupt_descriptor_table_register + { + std::uint16_t limit; + gate_descriptor const * base; + + auto load() const -> void; + auto static read() -> interrupt_descriptor_table_register; + }; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/cpu/legacy_pic.hpp b/arch/x86_64/arch/cpu/legacy_pic.hpp new file mode 100644 index 0000000..56ca9c4 --- /dev/null +++ b/arch/x86_64/arch/cpu/legacy_pic.hpp @@ -0,0 +1,19 @@ +#ifndef TEACHOS_X86_64_CPU_LEGACY_PIC_HPP +#define TEACHOS_X86_64_CPU_LEGACY_PIC_HPP + +#include + +#include + +namespace arch::cpu +{ + using pic_master_control_port = io::port<0x20, std::uint8_t, io::port_read, io::port_write>; + using pic_master_data_port = io::port<0x21, std::uint8_t, io::port_read, io::port_write>; + using pic_slave_control_port = io::port<0xa0, std::uint8_t, io::port_read, io::port_write>; + using pic_slave_data_port = io::port<0xa1, std::uint8_t, io::port_read, io::port_write>; + + constexpr auto pic_end_of_interrupt = std::uint8_t{0x20}; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/cpu/model_specific_register.hpp b/arch/x86_64/arch/cpu/model_specific_register.hpp new file mode 100644 index 0000000..bd4aff9 --- /dev/null +++ b/arch/x86_64/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 + +#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/arch/cpu/registers.hpp b/arch/x86_64/arch/cpu/registers.hpp new file mode 100644 index 0000000..58633f6 --- /dev/null +++ b/arch/x86_64/arch/cpu/registers.hpp @@ -0,0 +1,32 @@ +#ifndef TEACHOS_X86_64_CPU_REGISTERS_HPP +#define TEACHOS_X86_64_CPU_REGISTERS_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export + +#include + +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/arch/cpu/segment_descriptor.hpp b/arch/x86_64/arch/cpu/segment_descriptor.hpp new file mode 100644 index 0000000..9570670 --- /dev/null +++ b/arch/x86_64/arch/cpu/segment_descriptor.hpp @@ -0,0 +1,61 @@ +#ifndef TEACHOS_X86_64_SEGMENT_DESCRIPTOR_HPP +#define TEACHOS_X86_64_SEGMENT_DESCRIPTOR_HPP + +#include + +namespace arch::cpu +{ + + //! The type of segment described by a segment_descriptor. + enum struct segment_type : std::uint8_t + { + //! A system (TSS or LDT) segment + system = 0, + //! A code or data segment + code_or_data = 1, + }; + + //! The granularity of a segment described by a segment_descriptor + enum struct segment_granularity : std::uint8_t + { + //! The limit of the segment is defined in bytes. + byte = 0, + //! The limit of the segment is defined in pages (4KiB) + page = 1, + }; + + //! An entry in a segment descriptor table + struct segment_descriptor + { + std::uint64_t limit_low : 16; + std::uint64_t base_low : 24; + bool accessed : 1; + bool read_write : 1; + bool direction_or_conforming : 1; + bool executable : 1; + segment_type type : 1; + std::uint64_t privilege_level : 2; + bool present : 1; + std::uint64_t limit_high : 4; + std::uint64_t : 1; + bool long_mode : 1; + bool protected_mode : 1; + segment_granularity granularity : 1; + std::uint64_t base_high : 8; + }; + + static_assert(sizeof(segment_descriptor) == sizeof(std::uint64_t)); + static_assert(alignof(segment_descriptor) == alignof(std::uint64_t)); + + struct system_segment_descriptor : segment_descriptor + { + std::uint64_t base_extended : 32; + std::uint64_t : 32; + }; + + static_assert(sizeof(system_segment_descriptor) == 2 * sizeof(std::uint64_t)); + static_assert(alignof(system_segment_descriptor) == alignof(segment_descriptor)); + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/cpu/segment_selector.hpp b/arch/x86_64/arch/cpu/segment_selector.hpp new file mode 100644 index 0000000..1a78c47 --- /dev/null +++ b/arch/x86_64/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/arch/cpu/task_state_segment.hpp b/arch/x86_64/arch/cpu/task_state_segment.hpp new file mode 100644 index 0000000..57729dd --- /dev/null +++ b/arch/x86_64/arch/cpu/task_state_segment.hpp @@ -0,0 +1,30 @@ +#ifndef TEACHOS_X86_64_TASK_STATE_SEGMENT_HPP +#define TEACHOS_X86_64_TASK_STATE_SEGMENT_HPP + +#include + +namespace arch::cpu +{ + + struct [[gnu::packed]] task_state_segment + { + uint32_t : 32; + uint64_t rsp0 = {}; + uint64_t rsp1 = {}; + uint64_t rsp2 = {}; + uint64_t : 64; + uint64_t ist1 = {}; + uint64_t ist2 = {}; + uint64_t ist3 = {}; + uint64_t ist4 = {}; + uint64_t ist5 = {}; + uint64_t ist6 = {}; + uint64_t ist7 = {}; + uint64_t : 64; + uint16_t : 16; + uint16_t io_map_base_address = {}; + }; + +} // namespace arch::cpu + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/debug/qemu_output.cpp b/arch/x86_64/arch/debug/qemu_output.cpp new file mode 100644 index 0000000..71acede --- /dev/null +++ b/arch/x86_64/arch/debug/qemu_output.cpp @@ -0,0 +1,25 @@ +#include + +#include + +#include +#include + +namespace arch::debug +{ + + qemu_output::qemu_output(output_device & lower) + : m_lower{lower} + , m_present{port::read() == port::address} + {} + + auto qemu_output::write(kapi::cio::output_stream stream, std::string_view text) -> void + { + if (m_present) + { + std::ranges::for_each(text, port::write); + } + m_lower.write(stream, text); + } + +} // namespace arch::debug diff --git a/arch/x86_64/arch/debug/qemu_output.hpp b/arch/x86_64/arch/debug/qemu_output.hpp new file mode 100644 index 0000000..5ddd4be --- /dev/null +++ b/arch/x86_64/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 + +#include + +#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/arch/device_io/port_io.hpp b/arch/x86_64/arch/device_io/port_io.hpp new file mode 100644 index 0000000..4c8d66a --- /dev/null +++ b/arch/x86_64/arch/device_io/port_io.hpp @@ -0,0 +1,112 @@ +#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 %[data], %%al\nout %%al, %%dx"}, + std::string_view{"mov %[port], %%dx\nmov %[data], %%ax\nout %%ax, %%dx"}, + std::string_view{"mov %[port], %%dx\nmov %[data], %%eax\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"}; + }; + + auto inline wait() -> void + { + port<0x80, std::uint8_t, port_write>::write(0); + } + +} // namespace arch::io + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/devices/init.cpp b/arch/x86_64/arch/devices/init.cpp new file mode 100644 index 0000000..c30e8cf --- /dev/null +++ b/arch/x86_64/arch/devices/init.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +namespace arch::devices +{ + + namespace + { + constexpr auto pit_frequency_in_hz = std::uint32_t{100u}; + + auto get_acpi_root_pointer() -> kstd::observer_ptr<::acpi::rsdp const> + { + auto const & mbi = kapi::boot::bootstrap_information.mbi; + auto system_description_pointer = static_cast<::acpi::rsdp const *>(nullptr); + + if (auto const & xsdp = mbi->maybe_acpi_xsdp()) + { + auto data = xsdp->pointer().data(); + + system_description_pointer = reinterpret_cast<::acpi::xsdp const *>(data); + } + else if (auto const & rsdp = mbi->maybe_acpi_rsdp()) + { + auto data = rsdp->pointer().data(); + system_description_pointer = reinterpret_cast<::acpi::rsdp const *>(data); + } + + return kstd::make_observer(system_description_pointer); + } + } // namespace + + auto init_acpi_devices() -> void + { + auto acpi_root_pointer = get_acpi_root_pointer(); + if (acpi_root_pointer && acpi_root_pointer->validate()) + { + if (kapi::acpi::init(*acpi_root_pointer)) + { + kstd::println("[x86_64:DEV] ACPI subsystem initialized."); + } + } + + kapi::cpu::discover_topology(); + } + + auto init_legacy_devices() -> void + { + kstd::println("[x86_64:DEV] Initializing ISA bus..."); + + auto isa_major_number = kapi::devices::allocate_major_number(); + auto isa_bus = kstd::make_unique(isa_major_number); + + auto pit_major_number = kapi::devices::allocate_major_number(); + auto pit = kstd::make_unique(pit_major_number, pit_frequency_in_hz); + isa_bus->add_child(std::move(pit)); + + auto & root_bus = kapi::devices::get_root_bus(); + root_bus.add_child(std::move(isa_bus)); + } + +} // namespace arch::devices diff --git a/arch/x86_64/arch/devices/init.hpp b/arch/x86_64/arch/devices/init.hpp new file mode 100644 index 0000000..c5fbf37 --- /dev/null +++ b/arch/x86_64/arch/devices/init.hpp @@ -0,0 +1,12 @@ +#ifndef TEACHOS_ARCH_X86_64_DEVICES_INIT_HPP +#define TEACHOS_ARCH_X86_64_DEVICES_INIT_HPP + +namespace arch::devices +{ + + auto init_acpi_devices() -> void; + auto init_legacy_devices() -> void; + +} // namespace arch::devices + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/devices/legacy_pit.cpp b/arch/x86_64/arch/devices/legacy_pit.cpp new file mode 100644 index 0000000..d542d47 --- /dev/null +++ b/arch/x86_64/arch/devices/legacy_pit.cpp @@ -0,0 +1,60 @@ +#include + +#include + +#include +#include +#include + +#include +#include + +namespace arch::devices +{ + + namespace + { + using command_port = io::port<0x43, std::uint8_t, io::port_write>; + using channel_0_port = io::port<0x40, std::uint8_t, io::port_write>; + using channel_1_port = io::port<0x41, std::uint8_t, io::port_write>; + using channel_2_port = io::port<0x42, std::uint8_t, io::port_write>; + + constexpr auto base_frequency = 1'193'182u; + constexpr auto square_wave_mode = 0x36; + } // namespace + + legacy_pit::legacy_pit(std::size_t major, std::uint32_t frequency_in_hz) + : kapi::devices::device{major, 0, "legacy_pit"} + , m_irq_number{0} + , m_frequency_in_hz{frequency_in_hz} + {} + + auto legacy_pit::init() -> bool + { + auto divisor = static_cast(base_frequency / m_frequency_in_hz); + + kapi::interrupts::register_handler(m_irq_number, *this); + + command_port::write(square_wave_mode); + io::wait(); + channel_0_port::write(divisor & 0xff); + io::wait(); + channel_0_port::write(divisor >> 8 & 0xff); + io::wait(); + + return true; + } + + auto legacy_pit::handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status + { + if (irq_number != m_irq_number) + { + return kapi::interrupts::status::unhandled; + } + + ++m_ticks; + + return kapi::interrupts::status::handled; + } + +} // namespace arch::devices \ No newline at end of file diff --git a/arch/x86_64/arch/devices/legacy_pit.hpp b/arch/x86_64/arch/devices/legacy_pit.hpp new file mode 100644 index 0000000..356895c --- /dev/null +++ b/arch/x86_64/arch/devices/legacy_pit.hpp @@ -0,0 +1,29 @@ +#ifndef TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP +#define TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP + +#include +#include + +#include +#include + +namespace arch::devices +{ + + struct legacy_pit : kapi::devices::device, kapi::interrupts::handler + { + legacy_pit(std::size_t major, std::uint32_t frequency_in_hz); + + auto init() -> bool override; + + auto handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status override; + + private: + std::uint32_t m_irq_number{}; + std::uint32_t m_frequency_in_hz{}; + std::uint64_t m_ticks{}; + }; + +} // namespace arch::devices + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/devices/local_apic.cpp b/arch/x86_64/arch/devices/local_apic.cpp new file mode 100644 index 0000000..660921b --- /dev/null +++ b/arch/x86_64/arch/devices/local_apic.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +namespace arch::devices +{ + + namespace + { + constexpr auto lapic_enable_bit = 0x100u; + constexpr auto spurious_interrupt_vector = 0xFFu; + + constexpr auto offset_of_version = 0u; + constexpr auto offset_of_max_lvt_entry = 16u; + constexpr auto offset_of_eoi_suppression = 24u; + + } // namespace + + enum struct local_apic::registers : std::ptrdiff_t + { + id = 0x020, + version = 0x030, + task_priority = 0x080, + arbitration_priority = 0x090, + processor_priority = 0x0a0, + eoi = 0x0b0, + remote_read = 0x0c0, + logical_destination = 0x0d0, + destination_format = 0x0e0, + spurious_interrupt_vector = 0x0f0, + in_service_0 = 0x100, + in_service_1 = 0x110, + in_service_2 = 0x120, + in_service_3 = 0x130, + in_service_4 = 0x140, + in_service_5 = 0x150, + in_service_6 = 0x160, + in_service_7 = 0x170, + trigger_mode_0 = 0x180, + trigger_mode_1 = 0x190, + trigger_mode_2 = 0x1a0, + trigger_mode_3 = 0x1b0, + trigger_mode_4 = 0x1c0, + trigger_mode_5 = 0x1d0, + trigger_mode_6 = 0x1e0, + trigger_mode_7 = 0x1f0, + interrupt_request_0 = 0x200, + interrupt_request_1 = 0x210, + interrupt_request_2 = 0x220, + interrupt_request_3 = 0x230, + interrupt_request_4 = 0x240, + interrupt_request_5 = 0x250, + interrupt_request_6 = 0x260, + interrupt_request_7 = 0x270, + error_status = 0x280, + lvt_corrected_machine_check_interrupt = 0x2f0, + interrupt_command_0 = 0x300, + interrupt_command_1 = 0x310, + lvt_timer = 0x320, + lvt_thermal_sensors = 0x330, + lvt_performance_monitoring_counters = 0x340, + lvt_local_interrupt_0 = 0x350, + lvt_local_interrupt_1 = 0x360, + lvt_error = 0x370, + initial_count = 0x380, + current_count = 0x390, + divide_configuration = 0x3e0, + }; + + local_apic::local_apic(std::size_t major, std::size_t minor, std::uint64_t hardware_id, + kapi::memory::physical_address base, bool is_bsp) + : kapi::devices::device{major, minor, "lapic"} + , m_hardware_id{hardware_id} + , m_base{base} + , m_is_bsp{is_bsp} + {} + + auto local_apic::init() -> bool + { + auto static shared_virtual_base = kapi::memory::allocate_mmio_region(1); + auto static is_mapped = false; + + if (!is_mapped) + { + if (!kapi::memory::map_mmio_region(shared_virtual_base, m_base, kapi::memory::page_mapper::flags::writable)) + { + kstd::println("[x86_64:DEV] LAPIC {} MMIO mapping failed!", m_hardware_id); + return false; + } + is_mapped = true; + } + + m_mapped_region = shared_virtual_base; + + if (m_is_bsp) + { + auto raw_version = read_register(registers::version); + m_version = (raw_version >> offset_of_version) & 0xff; + m_highest_lvt_entry_index = (raw_version >> offset_of_max_lvt_entry) & 0xff; + m_supports_eoi_broadcast_suppression = (raw_version >> offset_of_eoi_suppression) & 0x1; + + write_register(registers::spurious_interrupt_vector, lapic_enable_bit | spurious_interrupt_vector); + + kstd::println("[x86_64:DEV] LAPIC initialized. version: {#x} | max_lvt_entry: {} | eoi_suppression: {:s}", + m_version, m_highest_lvt_entry_index, m_supports_eoi_broadcast_suppression); + } + else + { + kstd::println("[x86_64:DEV] LAPIC {} is not on the BSP, deferring initialization.", m_hardware_id); + } + + return true; + } + + auto local_apic::read_register(registers id) const -> std::uint32_t + { + auto reg = static_cast(m_mapped_region.first + std::to_underlying(id)); + return *reg; + } + + auto local_apic::write_register(registers id, std::uint32_t value) -> void + { + auto reg = static_cast(m_mapped_region.first + std::to_underlying(id)); + *reg = value; + } + +} // namespace arch::devices diff --git a/arch/x86_64/arch/devices/local_apic.hpp b/arch/x86_64/arch/devices/local_apic.hpp new file mode 100644 index 0000000..f8f080d --- /dev/null +++ b/arch/x86_64/arch/devices/local_apic.hpp @@ -0,0 +1,37 @@ +#ifndef TEACHOS_ARCH_X86_64_DEVICES_LOCAL_APIC_HPP +#define TEACHOS_ARCH_X86_64_DEVICES_LOCAL_APIC_HPP + +#include +#include + +#include +#include + +namespace arch::devices +{ + + struct local_apic : kapi::devices::device + { + local_apic(std::size_t major, std::size_t minor, std::uint64_t hardware_id, kapi::memory::physical_address base, + bool is_bsp); + + auto init() -> bool override; + + private: + enum struct registers : std::ptrdiff_t; + + [[nodiscard]] auto read_register(registers id) const -> std::uint32_t; + auto write_register(registers id, std::uint32_t value) -> void; + + std::uint64_t m_hardware_id{}; + kapi::memory::physical_address m_base{}; + kapi::memory::mmio_region m_mapped_region{}; + bool m_is_bsp{}; + std::uint8_t m_version{}; + std::uint8_t m_highest_lvt_entry_index{}; + bool m_supports_eoi_broadcast_suppression{}; + }; + +} // namespace arch::devices + +#endif diff --git a/arch/x86_64/arch/memory/higher_half_mapper.cpp b/arch/x86_64/arch/memory/higher_half_mapper.cpp new file mode 100644 index 0000000..75adb3c --- /dev/null +++ b/arch/x86_64/arch/memory/higher_half_mapper.cpp @@ -0,0 +1,119 @@ +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace arch::memory +{ + + higher_half_mapper::higher_half_mapper(page_table * root) + : m_root{root} + {} + + auto higher_half_mapper::map(kapi::memory::page page, kapi::memory::frame frame, flags flags) -> std::byte * + { + auto table = get_or_create_page_table(page); + if (!table) + { + return nullptr; + } + + auto const index = pml_index(1, page); + auto & entry = (*table)[index]; + + if (entry.present()) + { + kapi::system::panic("[x86_64:MEM] Tried to map a page that is already mapped!"); + } + + entry.frame(frame, to_table_flags(flags) | page_table::entry::flags::present); + + return static_cast(page.start_address()); + } + + auto higher_half_mapper::unmap(kapi::memory::page page) -> void + { + if (!try_unmap(page)) + { + kapi::system::panic("[x86_64:MEM] Tried to unmap a page that is not mapped!"); + } + } + + auto higher_half_mapper::try_unmap(kapi::memory::page page) noexcept -> bool + { + auto table_path = std::array, 4>{}; + table_path[0] = std::pair{m_root, pml_index(4, page)}; + + for (auto level = 4uz; level > 1uz; --level) + { + auto [table, index] = table_path[4 - level]; + auto & entry = (*table)[index]; + + if (!entry.present()) + { + return false; + } + + auto next_table = to_higher_half_pointer(entry.frame()->start_address()); + auto next_index = pml_index(4 - level - 1, page); + table_path[4 - level - 1] = std::pair{next_table, next_index}; + } + + std::ranges::for_each(std::views::reverse(table_path), [previous_was_empty = true](auto & step) mutable { + auto [table, index] = step; + auto & entry = (*table)[index]; + auto frame = entry.frame(); + + if (previous_was_empty) + { + entry.clear(); + previous_was_empty = table->empty(); + kapi::memory::get_frame_allocator().release(*frame); + } + }); + + return true; + } + + auto higher_half_mapper::get_or_create_page_table(kapi::memory::page page) noexcept -> page_table * + { + auto table = m_root; + + for (auto level = 4uz; level > 1uz; --level) + { + auto index = pml_index(level, page); + auto & entry = (*table)[index]; + + if (!entry.present()) + { + auto table_frame = kapi::memory::allocate_frame(); + if (!table_frame) + { + return nullptr; + } + + auto new_table = to_higher_half_pointer(table_frame->start_address()); + std::construct_at(new_table); + + auto const flags = page_table::entry::flags::present | page_table::entry::flags::writable | + page_table::entry::flags::user_accessible; + entry.frame(*table_frame, flags); + } + + table = to_higher_half_pointer(entry.frame()->start_address()); + } + + return table; + } + +} // namespace arch::memory \ No newline at end of file diff --git a/arch/x86_64/arch/memory/higher_half_mapper.hpp b/arch/x86_64/arch/memory/higher_half_mapper.hpp new file mode 100644 index 0000000..9b02ee6 --- /dev/null +++ b/arch/x86_64/arch/memory/higher_half_mapper.hpp @@ -0,0 +1,44 @@ +#ifndef TEACHOS_X86_64_HIGHER_HALF_MAPPER_HPP +#define TEACHOS_X86_64_HIGHER_HALF_MAPPER_HPP + +#include + +#include + +#include + +namespace arch::memory +{ + + //! A simple page mapper making use of a Higher Half Direct Map (HHDM) to access and modify page tables. + struct higher_half_mapper : kapi::memory::page_mapper + { + //! Construct a new mapper for a hierarchy rooted in the given PML. + //! + //! @param root The root of the hierarchy to operate on. + explicit higher_half_mapper(page_table * root); + + //! @copydoc kapi::memory::page_mapper::map + auto map(kapi::memory::page page, kapi::memory::frame frame, flags flags) -> std::byte * override; + + //! @copydoc kapi::memory::page_mapper::unmap + auto unmap(kapi::memory::page page) -> void override; + + //! @copydoc kapi::memory::page_mapper::try_unmap + auto try_unmap(kapi::memory::page page) noexcept -> bool override; + + private: + //! Try to retrieve the the PML1 responsible for mapping this page, creating one if necessary. + //! + //! This function will create a page table hierarchy leading to the target PML1 if it doesn't exist. + //! + //! @param page The page to get the PML1 for. + //! @return The PML1 that manages the given page, nullptr it the system runs out of memory. + auto get_or_create_page_table(kapi::memory::page page) noexcept -> page_table *; + + page_table * m_root; + }; + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/memory/kernel_mapper.cpp b/arch/x86_64/arch/memory/kernel_mapper.cpp new file mode 100644 index 0000000..74272a0 --- /dev/null +++ b/arch/x86_64/arch/memory/kernel_mapper.cpp @@ -0,0 +1,117 @@ +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace kstd::units_literals; + +namespace arch::memory +{ + + namespace + { + constexpr auto static ignored_section_prefixes = std::array{ + ".boot_"sv, + }; + + constexpr auto static user_accessible_prefixes = std::array{ + ".user"sv, + ".stl"sv, + }; + + } // namespace + + kernel_mapper::kernel_mapper(multiboot2::information_view const * mbi) + : m_mbi{std::move(mbi)} + , m_kernel_load_base{std::bit_cast(&arch::boot::TEACHOS_VMA)} + {} + + auto kernel_mapper::remap_kernel(kapi::memory::page_mapper & mapper) -> void + { + auto elf_information = m_mbi->maybe_elf_symbols(); + if (!elf_information) + { + kapi::system::panic("[x86_64:MEM] ELF section information is not available."); + } + + auto sections = *elf_information; + auto allocated_sections = + std::views::all(sections) | std::views::filter(&elf::section_header::allocated) | + std::views::filter([&](auto const & section) -> auto { + auto name = sections.name(section); + return !std::ranges::any_of(ignored_section_prefixes, + [&](auto const & prefix) -> auto { return name.starts_with(prefix); }); + }); + + if (allocated_sections.empty()) + { + kapi::system::panic("[x86_64:MEM] No allocated ELF sections were found."); + } + + std::ranges::for_each(allocated_sections, + [&](auto const & section) -> auto { map_section(section, sections.name(section), mapper); }); + } + + auto kernel_mapper::map_section(section_header_type const & section, std::string_view name, + kapi::memory::page_mapper & mapper) -> void + { + auto number_of_pages = + (kstd::units::bytes{section.size} + (kapi::memory::page::size - 1_B)) / kapi::memory::page::size; + auto linear_start_address = kapi::memory::linear_address{section.virtual_load_address}; + auto physical_start_address = kapi::memory::physical_address{section.virtual_load_address & ~m_kernel_load_base}; + + kstd::println("[x86_64:MEM] mapping {}" + "\n {} bytes -> page count: {}" + "\n {} @ {}", + name, section.size, number_of_pages, linear_start_address, physical_start_address); + + auto first_page = kapi::memory::page::containing(linear_start_address); + auto first_frame = kapi::memory::frame::containing(physical_start_address); + + auto page_flags = kapi::memory::page_mapper::flags::empty; + + if (section.writable()) + { + page_flags |= kapi::memory::page_mapper::flags::writable; + } + + if (section.executable()) + { + page_flags |= kapi::memory::page_mapper::flags::executable; + } + + auto is_prefix_of_name = [=](auto prefix) -> bool { + return name.starts_with(prefix); + }; + + if (!std::ranges::any_of(user_accessible_prefixes, is_prefix_of_name)) + { + page_flags |= kapi::memory::page_mapper::flags::supervisor_only; + } + + for (auto i = 0uz; i < number_of_pages; ++i) + { + mapper.map(first_page + i, first_frame + i, page_flags); + } + } + +} // namespace arch::memory \ No newline at end of file diff --git a/arch/x86_64/arch/memory/kernel_mapper.hpp b/arch/x86_64/arch/memory/kernel_mapper.hpp new file mode 100644 index 0000000..adbf688 --- /dev/null +++ b/arch/x86_64/arch/memory/kernel_mapper.hpp @@ -0,0 +1,35 @@ +#ifndef TEACHOS_X86_64_KERNEL_MAPPER_HPP +#define TEACHOS_X86_64_KERNEL_MAPPER_HPP + +#include + +#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/arch/memory/mmu.cpp b/arch/x86_64/arch/memory/mmu.cpp new file mode 100644 index 0000000..2b53fa4 --- /dev/null +++ b/arch/x86_64/arch/memory/mmu.cpp @@ -0,0 +1,19 @@ +#include + +#include + +#include + +namespace arch::memory +{ + auto tlb_flush(kapi::memory::linear_address address) -> void + { + asm volatile("invlpg (%[input])" : /* no output from call */ : [input] "r"(address) : "memory"); + } + + auto tlb_flush_all() -> void + { + auto paging_root = cpu::cr3::read(); + cpu::cr3::write(paging_root); + } +} // namespace arch::memory diff --git a/arch/x86_64/arch/memory/mmu.hpp b/arch/x86_64/arch/memory/mmu.hpp new file mode 100644 index 0000000..64373f4 --- /dev/null +++ b/arch/x86_64/arch/memory/mmu.hpp @@ -0,0 +1,27 @@ +#ifndef TEACHOS_X86_64_MEMORY_MMU_HPP +#define TEACHOS_X86_64_MEMORY_MMU_HPP + +#include + +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/arch/memory/page_table.cpp b/arch/x86_64/arch/memory/page_table.cpp new file mode 100644 index 0000000..2180420 --- /dev/null +++ b/arch/x86_64/arch/memory/page_table.cpp @@ -0,0 +1,82 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace arch::memory +{ + + auto page_table::entry::clear() noexcept -> void + { + m_raw = 0; + } + + auto page_table::entry::present() const noexcept -> bool + { + return (all_flags() & flags::present) != flags::empty; + } + + auto page_table::entry::huge() const noexcept -> bool + { + return (all_flags() & flags::huge_page) != flags::empty; + } + + auto page_table::entry::all_flags() const noexcept -> flags + { + return std::bit_cast(m_raw & ~frame_number_mask); + } + + auto page_table::entry::all_flags(flags flags) noexcept -> void + { + m_raw = (m_raw & ~frame_number_mask) | std::to_underlying(flags); + } + + auto page_table::entry::operator|=(flags rhs) noexcept -> page_table::entry & + { + auto raw_flags = std::to_underlying(rhs) & ~frame_number_mask; + m_raw |= raw_flags; + return *this; + } + + auto page_table::entry::frame() const noexcept -> std::optional + { + if (present()) + { + return kapi::memory::frame::containing(kapi::memory::physical_address{m_raw & frame_number_mask}); + } + return std::nullopt; + } + + auto page_table::entry::frame(kapi::memory::frame frame, flags flags) noexcept -> void + { + m_raw = (frame.start_address().raw() | static_cast(flags)); + }; + + auto page_table::operator[](std::size_t index) -> entry & + { + return m_entries.at(index); + } + + auto page_table::operator[](std::size_t index) const -> entry const & + { + return m_entries.at(index); + } + + auto page_table::clear() noexcept -> void + { + std::ranges::for_each(m_entries, &page_table::entry::clear); + } + + auto page_table::empty() const noexcept -> bool + { + return std::ranges::all_of(m_entries, + [](auto const & entry) -> auto { return entry.all_flags() == entry::flags::empty; }); + } + +} // namespace arch::memory diff --git a/arch/x86_64/arch/memory/page_table.hpp b/arch/x86_64/arch/memory/page_table.hpp new file mode 100644 index 0000000..ce3d3a1 --- /dev/null +++ b/arch/x86_64/arch/memory/page_table.hpp @@ -0,0 +1,232 @@ +#ifndef TEACHOS_X86_64_PAGE_TABLE_HPP +#define TEACHOS_X86_64_PAGE_TABLE_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. In most cases, 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. The only exception to that rule + //! is the use of huge pages. + 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 / kstd::units::bytes{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{}; + }; + +} // 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::write_through)) != table_flags::empty) + { + result |= mapper_flags::uncached; + } + + if ((flags & table_flags::user_accessible) == table_flags::empty) + { + result |= mapper_flags::supervisor_only; + } + + if ((flags & table_flags::global) != table_flags::empty) + { + result |= mapper_flags::global; + } + + 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 | table_flags::write_through); + } + + if ((flags & mapper_flags::supervisor_only) == mapper_flags::empty) + { + result |= table_flags::user_accessible; + } + + if ((flags & mapper_flags::global) != mapper_flags::empty) + { + result |= table_flags::global; + } + + return result; + } + +} // namespace arch::memory + +#endif diff --git a/arch/x86_64/arch/memory/page_utilities.hpp b/arch/x86_64/arch/memory/page_utilities.hpp new file mode 100644 index 0000000..068e824 --- /dev/null +++ b/arch/x86_64/arch/memory/page_utilities.hpp @@ -0,0 +1,29 @@ +#ifndef TEACHOS_X86_64_PAGE_UTILITIES_HPP +#define TEACHOS_X86_64_PAGE_UTILITIES_HPP + +#include + +#include + +namespace arch::memory +{ + + constexpr auto inline pml_index(std::size_t index, kapi::memory::page page) noexcept -> std::size_t + { + constexpr auto bits_per_level = 9; + auto shift_width = (index - 1) * bits_per_level; + constexpr auto index_mask = 0x1ffuz; + return page.number() >> shift_width & index_mask; + } + + template + [[nodiscard]] constexpr auto to_higher_half_pointer(kapi::memory::physical_address address) -> ValueType * + { + using namespace kapi::memory; + auto const higher_half_address = higher_half_direct_map_base + address.raw(); + return static_cast(higher_half_address); + } + +} // namespace arch::memory + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/memory/region_allocator.cpp b/arch/x86_64/arch/memory/region_allocator.cpp new file mode 100644 index 0000000..4086a10 --- /dev/null +++ b/arch/x86_64/arch/memory/region_allocator.cpp @@ -0,0 +1,165 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace arch::memory +{ + namespace + { + constexpr auto last_frame(multiboot2::memory_map::region const & region) + { + return kapi::memory::frame::containing(kapi::memory::physical_address{region.base + region.size_in_B - 1}); + } + + constexpr auto falls_within(kapi::memory::frame const & candidate, kapi::memory::frame const & start, + kapi::memory::frame const & end) + { + return candidate >= start && candidate <= end; + } + } // namespace + + region_allocator::region_allocator(memory_information const & mem_info) + : m_next_frame{} + , m_current_region{} + , m_memory_map{mem_info.memory_map} + , m_kernel_start{kapi::memory::frame::containing(mem_info.image_range.first)} + , m_kernel_end{kapi::memory::frame::containing(mem_info.image_range.second)} + , m_multiboot_start{kapi::memory::frame::containing(mem_info.mbi_range.first)} + , m_multiboot_end{kapi::memory::frame::containing(mem_info.mbi_range.second)} + , m_multiboot_information{mem_info.mbi} + { + choose_next_region(); + } + + auto region_allocator::choose_next_region() -> void + { + m_current_region.reset(); + + auto remaining_regions = + m_memory_map | // + std::views::filter(&multiboot2::memory_map::region::available) | + std::views::filter([this](auto const & region) -> bool { return last_frame(region) >= m_next_frame; }); + + auto lowest_region = + std::ranges::min_element(remaining_regions, [](auto lhs, auto rhs) -> bool { return lhs.base < rhs.base; }); + + if (lowest_region == remaining_regions.end()) + { + return; + } + + m_current_region = *lowest_region; + if (auto start_of_region = kapi::memory::frame::containing(kapi::memory::physical_address{m_current_region->base}); + start_of_region > m_next_frame) + { + m_next_frame = start_of_region; + } + } + + auto region_allocator::find_next_frame() -> std::optional + { + if (!m_current_region || m_next_frame > last_frame(*m_current_region)) + { + choose_next_region(); + if (!m_current_region) + { + return std::nullopt; + } + } + + auto advanced = true; + while (advanced) + { + advanced = false; + + if (falls_within(m_next_frame, m_kernel_start, m_kernel_end)) + { + m_next_frame = m_kernel_end + 1; + advanced = true; + continue; + } + + if (falls_within(m_next_frame, m_multiboot_start, m_multiboot_end)) + { + m_next_frame = m_multiboot_end + 1; + advanced = true; + continue; + } + + if (m_multiboot_information) + { + for (auto const & module : m_multiboot_information->modules()) + { + auto module_start = kapi::memory::frame::containing(kapi::memory::physical_address{module.start_address}); + auto module_end = kapi::memory::frame::containing(kapi::memory::physical_address{module.end_address}); + + if (falls_within(m_next_frame, module_start, module_end)) + { + m_next_frame = module_end + 1; + advanced = true; + break; + } + } + } + } + + if (m_next_frame > last_frame(*m_current_region)) + { + choose_next_region(); + } + + return m_current_region.transform([this](auto) -> auto { return m_next_frame; }); + } + + auto region_allocator::allocate_many(std::size_t count) noexcept + -> std::optional> + { + while (m_current_region) + { + auto result = find_next_frame(); + + if (result) + { + auto region_end = last_frame(*m_current_region); + if ((region_end.number() - result->number()) >= count) + { + m_next_frame = m_next_frame + count; + return std::make_pair(*result, count); + } + else + { + m_next_frame = region_end + 1; + choose_next_region(); + } + } + } + + return std::nullopt; + } + + auto region_allocator::mark_used(kapi::memory::frame frame) -> void + { + if (frame < m_next_frame) + { + m_next_frame = frame; + find_next_frame(); + } + } + + auto region_allocator::release_many(std::pair) -> void {} + + auto region_allocator::next_free_frame() noexcept -> std::optional + { + return find_next_frame(); + } + +} // namespace arch::memory diff --git a/arch/x86_64/arch/memory/region_allocator.hpp b/arch/x86_64/arch/memory/region_allocator.hpp new file mode 100644 index 0000000..5d9da2e --- /dev/null +++ b/arch/x86_64/arch/memory/region_allocator.hpp @@ -0,0 +1,93 @@ +#ifndef TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP +#define TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP + +#include +#include +#include + +#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 that are located in + //! non-reserved (as in available) regions should be allocated for page storage. + multiboot2::memory_map memory_map; + + //! The loader supplied Multiboot2 information structure. + //! + //! This is used to query boot module ranges so these frames can be excluded from early allocations. + multiboot2::information_view const * mbi; + }; + + using region = multiboot2::memory_map::region; + + //! Construct a new allocator using the provided memory information + //! + //! @param information The description of the detected memory regions as well as regions that are already occupied. + explicit region_allocator(memory_information const & information); + + //! @copydoc kapi::memory::frame_allocator::allocate_many + //! + //! @note As long as free frames are available, successive calls to this implementation are guaranteed to yield + //! frames in ascending order. + auto allocate_many(std::size_t count = 1) noexcept + -> std::optional> override; + + //! @copydoc kapi::memory::frame_allocator::mark_used + auto mark_used(kapi::memory::frame frame) -> void override; + + //! @copydoc kapi::memory::frame_allocator::release_many + //! + //! @note This implementation will never actually release any frames. + auto release_many(std::pair frame_set) -> void override; + + auto next_free_frame() noexcept -> std::optional; + + private: + //! Find the next memory area and write it into current_area. + auto choose_next_region() -> void; + auto find_next_frame() -> std::optional; + + kapi::memory::frame m_next_frame; //!< The next available frame. + std::optional m_current_region; //!< The memory region currently used for allocation + multiboot2::memory_map m_memory_map; //!< The boot loader supplied memory map. + kapi::memory::frame m_kernel_start; //!< The start of the kernel image in physical memory. + kapi::memory::frame m_kernel_end; //!< The end of the kernel image in physical memory. + kapi::memory::frame m_multiboot_start; //!< The start of the Multiboot2 information in physical memory. + kapi::memory::frame m_multiboot_end; //!< The end of the Multiboot2 information in physical memory. + multiboot2::information_view const * m_multiboot_information; //!< Source of Multiboot2 module ranges. + }; + +} // namespace arch::memory + +#endif diff --git a/arch/x86_64/arch/vga/crtc.hpp b/arch/x86_64/arch/vga/crtc.hpp new file mode 100644 index 0000000..a8bec93 --- /dev/null +++ b/arch/x86_64/arch/vga/crtc.hpp @@ -0,0 +1,35 @@ +#ifndef TEACHOS_X86_64_VGA_IO_HPP +#define TEACHOS_X86_64_VGA_IO_HPP + +#include + +#include + +namespace arch::vga::crtc +{ + /** + * @brief The address port of the CRT Controller. + */ + using address = io::port<0x3d4, std::byte, io::port_write>; + + /** + * @brief The data port of the CRT Controller. + */ + using data = io::port<0x3d5, std::byte, io::port_read, io::port_write>; + + namespace registers + { + /** + * @brief The address of the Cursor Start register of the CRTC. + */ + [[maybe_unused]] constexpr auto cursor_start = std::byte{0x0a}; + + /** + * @brief The address of the Cursor End register of the CRTC. + */ + [[maybe_unused]] constexpr auto cursor_end = std::byte{0x0b}; + } // namespace registers + +} // namespace arch::vga::crtc + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text.hpp b/arch/x86_64/arch/vga/text.hpp new file mode 100644 index 0000000..2e73dd2 --- /dev/null +++ b/arch/x86_64/arch/vga/text.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_HPP +#define TEACHOS_X86_64_VGA_TEXT_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export + +#endif // TEACHOS_ARCH_X86_64_VIDEO_VGA_TEXT_HPP \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text/attribute.hpp b/arch/x86_64/arch/vga/text/attribute.hpp new file mode 100644 index 0000000..6395aed --- /dev/null +++ b/arch/x86_64/arch/vga/text/attribute.hpp @@ -0,0 +1,30 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_ATTRIBUTE_HPP +#define TEACHOS_X86_64_VGA_TEXT_ATTRIBUTE_HPP + +// IWYU pragma: private, include + +#include +#include + +namespace arch::vga::text +{ + //! The VGA text mode attribute. + //! + //! @note In the text mode of VGA, every code point being presented is followed by an attribute description. This + //! allows for the modification of how the relevant "cell" is presented. + //! + //! @see text::foreground_flag + //! @see text::background_flag + struct attribute + { + color foreground_color : 3; ///< The foreground color of the cell, e.g. the color of the code point. + enum foreground_flag foreground_flag : 1; ///< The foreground color modification flag of the cell. + color background_color : 3; ///< The background color of the cell. + enum background_flag background_flag : 1; ///< The background color modification flag of the cell. + }; + + static_assert(sizeof(attribute) == 1, "The VGA text mode attribute must fit inside a single byte."); + +} // namespace arch::vga::text + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text/buffer.cpp b/arch/x86_64/arch/vga/text/buffer.cpp new file mode 100644 index 0000000..498b9a3 --- /dev/null +++ b/arch/x86_64/arch/vga/text/buffer.cpp @@ -0,0 +1,101 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace arch::vga::text +{ + buffer::buffer(std::size_t width, std::size_t height, cell * start, std::size_t position) + : m_width{width} + , m_height{height} + , m_buffer{start, m_width * m_height} + , m_position{position} + {} + + auto buffer::clear() -> void + { + m_position = 0; + std::ranges::fill(m_buffer, std::pair{'\0', static_cast(0x00)}); + } + + auto buffer::write(std::string_view code_points, attribute attribute) -> void + { + std::ranges::for_each(code_points, [&](auto code_point) -> void { write(code_point, attribute); }); + } + + auto buffer::write(char code_point, attribute attribute) -> void + { + if (m_position + 1 > m_height * m_width) + { + scroll(); + } + + if (!handle_special_code_point(code_point, attribute)) + { + do_write(code_point, attribute); + } + }; + + auto buffer::newline() -> void + { + auto free_glyphs_in_line = m_width - column(); + m_position += free_glyphs_in_line; + } + + auto buffer::scroll(std::size_t nof_lines) -> void + { + auto scroll_count = std::min(nof_lines, m_height); + + auto scroll_start = m_buffer.begin() + (scroll_count * m_width); + std::ranges::move(scroll_start, m_buffer.end(), m_buffer.begin()); + + auto clear_start = m_buffer.begin() + (m_height - scroll_count) * m_width; + std::ranges::fill(clear_start, m_buffer.end(), cell{}); + + m_position = (line() - scroll_count) * m_width; + } + + auto buffer::column() const noexcept -> std::ptrdiff_t + { + return m_position % m_width; + } + + auto buffer::line() const noexcept -> std::ptrdiff_t + { + return m_position / m_width; + } + auto buffer::handle_special_code_point(char code_point, attribute attribute) -> bool + { + switch (code_point) + { + case '\n': + newline(); + return true; + case '\r': + m_position -= column(); + return true; + case '\t': + do_write(" ", attribute); + return true; + default: + return false; + } + } + + auto buffer::do_write(std::string_view code_points, attribute attribute) -> void + { + std::ranges::for_each(code_points, [&](auto code_point) -> void { do_write(code_point, attribute); }); + } + + auto buffer::do_write(char code_point, attribute attribute) -> void + { + m_buffer[m_position++] = std::pair{code_point, std::bit_cast(attribute)}; + } + +} // namespace arch::vga::text diff --git a/arch/x86_64/arch/vga/text/buffer.hpp b/arch/x86_64/arch/vga/text/buffer.hpp new file mode 100644 index 0000000..8eb6645 --- /dev/null +++ b/arch/x86_64/arch/vga/text/buffer.hpp @@ -0,0 +1,101 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_BUFFER_HPP +#define TEACHOS_X86_64_VGA_TEXT_BUFFER_HPP + +// IWYU pragma: private, include + +#include + +#include +#include +#include +#include + +namespace arch::vga::text +{ + //! A VGA text buffer. + //! + //! VGA text mode presents a linear buffer of so-called cells. Each cell consists of a single code point and a + //! rendering attribute. The codepoint determines the character being rendered in a specific cell, while the attribute + //! determines the visual style of that cell. + //! + //! @see text::attribute + struct buffer + { + using cell = std::pair; + + //! Create a new buffer. + //! + //! @param width The width of the buffer + //! @param height The height of the buffer + //! @param start A pointer to the first byte of the buffer. + //! @param position The starting position for the first write to the buffer + buffer(std::size_t width, std::size_t height, cell * start, std::size_t position = 0); + + //! Clear the buffer. + //! + //! Clearing the buffer ensures it is filled with zeroes, effectively erasing all data and resetting the output + //! position to the start of the buffer. + auto clear() -> void; + + //! Write a string of formatted code points to the buffer. + //! + //! @param code_points A string of (8-bit) code points to write to the buffer. + //! @param attribute The formatting to apply to the written sequence of code points. + auto write(std::string_view code_points, attribute attribute) -> void; + + //! Write a single, formatted code point to the buffer. + //! + //! @param code_point A single (8-bit) code point + //! @param attribute The formatting to apply to the code point. + auto write(char code_point, attribute attribute) -> void; + + //! Move the output position to a new line and scroll the buffer if necessary. + auto newline() -> void; + + //! Scroll the buffer contents. + //! + //! @param nof_lines The number of lines to scroll up. + auto scroll(std::size_t nof_lines = 1) -> void; + + private: + //! Get column number of the current cell. + [[nodiscard]] auto column() const noexcept -> std::ptrdiff_t; + + //! Get the line number of the current cell. + [[nodiscard]] auto line() const noexcept -> std::ptrdiff_t; + + //! Process the semantics of special code points, for example newlines and carriage returns. + //! + //! @param code_point The code point to process. + //! @param attribute The attribute to use when writing to the text buffer. + //! @return @p true iff. the code point was handled, @p false otherwise. + auto handle_special_code_point(char code_point, attribute attribute) -> bool; + + //! Perform the actual output to the buffer. + //! + //! @param code_points The code points to output.. + //! @param attribute The attribute to use when writing to the text buffer. + auto do_write(std::string_view code_points, attribute attribute) -> void; + + //! Perform the actual output to the buffer. + //! + //! @param code_point The code point to output. + //! @param attribute The attribute to use when writing to the text buffer. + auto do_write(char code_point, attribute attribute) -> void; + + //! The width, in cells, of the buffer. + std::size_t m_width{}; + + //! The height, in cells, of the buffer. + std::size_t m_height{}; + + //! The text mode data buffer. + std::span m_buffer; + + //! The position of the next cell to be written to. + std::size_t m_position{}; + }; + +} // namespace arch::vga::text + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text/color.hpp b/arch/x86_64/arch/vga/text/color.hpp new file mode 100644 index 0000000..e0ad6df --- /dev/null +++ b/arch/x86_64/arch/vga/text/color.hpp @@ -0,0 +1,35 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_COLOR_HPP +#define TEACHOS_X86_64_VGA_TEXT_COLOR_HPP + +// IWYU pragma: private, include + +#include + +namespace arch::vga::text +{ + //! VGA Text Mode standard colors. + //! + //! Every color may be used as a foreground and/or background color, in any combination. + enum struct color : std::uint8_t + { + //! Equivalent to HTML color \#000000. + black, + //! Equivalent to HTML color \#0000AA. + blue, + //! Equivalent to HTML color \#00AA00. + green, + //! Equivalent to HTML color \#00AAAA. + cyan, + //! Equivalent to HTML color \#AA0000. + red, + //! Equivalent to HTML color \#AA00AA. + purple, + //! Equivalent to HTML color \#AA5500. + brown, + //! Equivalent to HTML color \#AAAAAA. + gray, + }; + +} // namespace arch::vga::text + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text/common_attributes.hpp b/arch/x86_64/arch/vga/text/common_attributes.hpp new file mode 100644 index 0000000..3d8929f --- /dev/null +++ b/arch/x86_64/arch/vga/text/common_attributes.hpp @@ -0,0 +1,37 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_COMMON_ATTRIBUTES_HPP +#define TEACHOS_X86_64_VGA_TEXT_COMMON_ATTRIBUTES_HPP + +// IWYU pragma: private, include + +#include +#include +#include + +namespace arch::vga::text +{ + //! Make the affected cell display with a gray foreground and black background. + [[maybe_unused]] constexpr auto gray_on_black = attribute{.foreground_color = color::gray, + .foreground_flag = foreground_flag::none, + .background_color = color::black, + .background_flag = background_flag::none}; + + //! Make the affected cell display with a green foreground and black background. + [[maybe_unused]] constexpr auto green_on_black = attribute{.foreground_color = color::green, + .foreground_flag = foreground_flag::none, + .background_color = color::black, + .background_flag = background_flag::none}; + + //! Make the affected cell display with a green foreground and black background. + [[maybe_unused]] constexpr auto red_on_black = attribute{.foreground_color = color::red, + .foreground_flag = foreground_flag::none, + .background_color = color::black, + .background_flag = background_flag::none}; + + //! Make the affected cell display with a white (gray + intense) foreground and red background. + [[maybe_unused]] constexpr auto white_on_red = attribute{.foreground_color = color::gray, + .foreground_flag = foreground_flag::intense, + .background_color = color::red, + .background_flag = background_flag::none}; +} // namespace arch::vga::text + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text/device.cpp b/arch/x86_64/arch/vga/text/device.cpp new file mode 100644 index 0000000..8468358 --- /dev/null +++ b/arch/x86_64/arch/vga/text/device.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace arch::vga::text +{ + namespace + { + constexpr auto default_buffer_address = std::uintptr_t{0xb8000}; + constexpr auto default_buffer_width = 80z; + constexpr auto default_buffer_height = 25z; + + constexpr auto bit_cursor_enabled = 5U; + } // namespace + + device::device() + : m_buffer{ + default_buffer_width, default_buffer_height, + std::bit_cast(default_buffer_address + std::bit_cast(&boot::TEACHOS_VMA)), + kapi::boot::bootstrap_information.vga_buffer_index} + { + clear(); + } + + auto device::clear() -> void + { + m_buffer.clear(); + } + + auto device::cursor(bool enabled) -> void + { + auto cursor_disable_byte = std::byte{!enabled} << bit_cursor_enabled; + + crtc::address::write(crtc::registers::cursor_start); + crtc::data::write(crtc::data::read() | cursor_disable_byte); + } + + auto device::write(kapi::cio::output_stream stream, std::string_view text) -> void + { + auto attributes = [&] -> attribute { + switch (stream) + { + case kapi::cio::output_stream::stderr: + return red_on_black; + default: + return green_on_black; + } + }(); + m_buffer.write(text, attributes); + } + +} // namespace arch::vga::text diff --git a/arch/x86_64/arch/vga/text/device.hpp b/arch/x86_64/arch/vga/text/device.hpp new file mode 100644 index 0000000..0a0e017 --- /dev/null +++ b/arch/x86_64/arch/vga/text/device.hpp @@ -0,0 +1,45 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_DEVICE_HPP +#define TEACHOS_X86_64_VGA_TEXT_DEVICE_HPP + +// IWYU pragma: private, include + +#include + +#include + +#include + +namespace arch::vga::text +{ + //! A VGA Text Mode device. + //! + //! VGA text mode presents a linear buffer of so-called cells. Each cell consists of a single code point and a + //! rendering attribute. The codepoint determines the character being rendered in a specific cell, while the attribute + //! determines the visual style of that cell. + //! + //! @see text::attribute + struct device final : kapi::cio::output_device + { + device(); + + //! Clear the screen. + //! + //! Clearing the screen ensures the text mode buffer is filled with zeroes, effectively erasing all displayed data + //! and resetting the output position to the start of the buffer. + auto clear() -> void; + + //! Enable or disable the VGA text mode cursor. + //! + //! @param enabled Whether to enable the cursor. + auto cursor(bool enabled) -> void; + + //! @copydoc kapi::cio::output_device + auto write(kapi::cio::output_stream stream, std::string_view text) -> void override; + + private: + buffer m_buffer; + }; + +} // namespace arch::vga::text + +#endif \ No newline at end of file diff --git a/arch/x86_64/arch/vga/text/flags.hpp b/arch/x86_64/arch/vga/text/flags.hpp new file mode 100644 index 0000000..7a29e33 --- /dev/null +++ b/arch/x86_64/arch/vga/text/flags.hpp @@ -0,0 +1,38 @@ +#ifndef TEACHOS_X86_64_VGA_TEXT_FLAGS_HPP +#define TEACHOS_X86_64_VGA_TEXT_FLAGS_HPP + +// IWYU pragma: private, include + +namespace arch::vga::text +{ + + //! VGA Text Mode standard background modification flags. + enum struct background_flag : bool + { + //! Do not modify the foreground rendering. + none, + //! Render the background as blinking or intense. + //! + //! Whether this flag is interpreted as 'blink' or 'bright' depends on the currently active configuration of the VGA + //! device. + //! + //! @note The VGA standard does not specify the exact effect of this 'intense', however, most devices render such + //! colors brighter. + blink_or_bright, + }; + + //! VGA Text Mode standard foreground modification flags. + enum struct foreground_flag : bool + { + //! Do not modify the foreground rendering. + none, + //! Render the foreground as intense. + //! + //! @note The VGA standard does not specify the exact effect of this 'intense', however, most devices render such + //! colors brighter. + intense, + }; + +} // namespace arch::vga::text + +#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/boot/boot.hpp b/arch/x86_64/include/arch/boot/boot.hpp deleted file mode 100644 index 7df61c4..0000000 --- a/arch/x86_64/include/arch/boot/boot.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef TEACHOS_X86_64_BOOT_BOOT_HPP -#define TEACHOS_X86_64_BOOT_BOOT_HPP - -#ifdef __ASSEMBLER__ -// clang-format off - -//! The number of huge pages to map during bootstrap. -#define HUGE_PAGES_TO_MAP (16) - -//! The magic value to be set in eax by the multiboot 2 loader. -#define MULTIBOOT2_MAGIC (0x36d76289) - -//! The "A" bit in a GDT entry. -#define GDT_ACCESSED (1 << 40) - -//! The "R/W" bit in a GDT entry -#define GDT_READ_WRITE (1 << 41) - -//! The "E" bit in a GDT entry. -#define GDT_EXECUTABLE (1 << 43) - -//! The "S" bit in a GDT entry. -#define GDT_DESCRIPTOR_TYPE (1 << 44) - -//! The "P" bit in a GDT entry. -#define GDT_PRESENT (1 << 47) - -//! The "L" bit in a GDT entry. -#define GDT_LONG_MODE (1 << 53) - -// clang-format on -#else - -#include // 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 deleted file mode 100644 index 988723d..0000000 --- a/arch/x86_64/include/arch/boot/ld.hpp +++ /dev/null @@ -1,61 +0,0 @@ -//! @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/bus/isa.hpp b/arch/x86_64/include/arch/bus/isa.hpp deleted file mode 100644 index e56f56a..0000000 --- a/arch/x86_64/include/arch/bus/isa.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TEACHOS_X86_64_BUS_ISA_HPP -#define TEACHOS_X86_64_BUS_ISA_HPP - -#include - -#include - -namespace arch::bus -{ - - struct isa final : public kapi::devices::bus - { - isa(std::size_t major); - }; - -} // namespace arch::bus - -#endif // TEACHOS_X86_64_BUS_ISA_HPP diff --git a/arch/x86_64/include/arch/cpu/control_register.hpp b/arch/x86_64/include/arch/cpu/control_register.hpp deleted file mode 100644 index 9cedc35..0000000 --- a/arch/x86_64/include/arch/cpu/control_register.hpp +++ /dev/null @@ -1,249 +0,0 @@ -#ifndef TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP -#define TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP - -// IWYU pragma: private, include - -#include - -#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 - { - constexpr auto address_shift = 12uz; - return kapi::memory::physical_address{m_address << address_shift}; - } - - //! 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/global_descriptor_table.hpp b/arch/x86_64/include/arch/cpu/global_descriptor_table.hpp deleted file mode 100644 index b17c509..0000000 --- a/arch/x86_64/include/arch/cpu/global_descriptor_table.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef TEACHOS_X86_64_GLOBAL_DESCRIPTOR_TABLE_HPP -#define TEACHOS_X86_64_GLOBAL_DESCRIPTOR_TABLE_HPP - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace arch::cpu -{ - - template - struct global_descriptor_table; - - struct [[gnu::packed]] global_descriptor_table_pointer - { - template - global_descriptor_table_pointer(global_descriptor_table const & gdt) - : size{GdtSize * sizeof(segment_descriptor) - 1} - , address{kapi::memory::physical_address{std::bit_cast(&gdt)}.raw()} - {} - - auto load() -> void - { - asm volatile("lgdt %0" : : "m"(*this)); - } - - std::uint16_t size{}; - std::uint64_t address{}; - }; - - static_assert(sizeof(global_descriptor_table_pointer) == sizeof(std::uint16_t) + sizeof(std::uint64_t)); - - template - struct global_descriptor_table - { - template... SegmentDescriptors> - constexpr global_descriptor_table(SegmentDescriptors const &... descriptors) - : m_descriptors{} - { - auto descriptor_data = std::array{ - std::pair{std::bit_cast(&descriptors), sizeof(descriptors)} - ... - }; - auto written_size = 0uz; - std::ranges::for_each(descriptor_data, [&written_size, this](auto entry) { - std::ranges::copy(entry.first, entry.first + entry.second, m_descriptors.begin() + written_size); - written_size += entry.second; - }); - } - - auto load(std::size_t code_segment_index, std::size_t data_segment_index) const -> void - { - auto pointer = global_descriptor_table_pointer{*this}; - pointer.load(); - - asm volatile("push %0\n" - "lea 1f(%%rip), %%rax\n" - "push %%rax\n" - "lretq\n" - "1:\n" - "mov %1, %%rax\n" - "mov %%rax, %%ss\n" - "mov %%rax, %%ds\n" - "mov %%rax, %%es\n" - "mov %%rax, %%fs\n" - "mov %%rax, %%gs\n" - : - : "X"(code_segment_index * sizeof(segment_descriptor)), - "X"(data_segment_index * sizeof(segment_descriptor)) - : "rax"); - } - - private: - std::array m_descriptors; - }; - - template... SegmentDescriptors> - global_descriptor_table(SegmentDescriptors const &... descriptors) - -> global_descriptor_table<(sizeof(SegmentDescriptors) + ...)>; - -} // namespace arch::cpu - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/cpu/initialization.hpp b/arch/x86_64/include/arch/cpu/initialization.hpp deleted file mode 100644 index 564c544..0000000 --- a/arch/x86_64/include/arch/cpu/initialization.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_CPU_INITIALIZATION_HPP -#define TEACHOS_ARCH_X86_64_CPU_INITIALIZATION_HPP - -namespace arch::cpu -{ - auto initialize_descriptors() -> void; - - auto initialize_legacy_interrupts() -> void; - -} // namespace arch::cpu - -#endif diff --git a/arch/x86_64/include/arch/cpu/interrupts.hpp b/arch/x86_64/include/arch/cpu/interrupts.hpp deleted file mode 100644 index 6162f56..0000000 --- a/arch/x86_64/include/arch/cpu/interrupts.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef TEACHOS_X86_64_CPU_INTERRUPTS_HPP -#define TEACHOS_X86_64_CPU_INTERRUPTS_HPP - -#include - -#include - -#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. - enum 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); - - //! The stack frame as established by the low-level assembly interrupt stubs. - //! - //! @note The layout of this struct is reverse than what would reasonably be expected. The reason for this reversal is - //! that fact that it represents a stack frame. Stack frames on x86_64 grow towards lower addresses, meaning the first - //! item on the stack is the last item in C++ memory layout order. - struct interrupt_frame - { - struct - { - std::uint64_t r15{}; - std::uint64_t r14{}; - std::uint64_t r13{}; - std::uint64_t r12{}; - std::uint64_t r11{}; - std::uint64_t r10{}; - std::uint64_t r9{}; - std::uint64_t r8{}; - std::uint64_t rdi{}; - std::uint64_t rsi{}; - std::uint64_t rbp{}; - std::uint64_t rdx{}; - std::uint64_t rcx{}; - std::uint64_t rbx{}; - std::uint64_t rax{}; - } handler_saved; - - struct - { - std::uint64_t number{}; - std::uint64_t error_code{}; - } interrupt; - - struct - { - kapi::memory::linear_address rip{}; - std::uint64_t cs{}; - std::uint64_t rflags{}; - kapi::memory::linear_address rsp{}; - std::uint64_t ss{}; - } cpu_saved; - }; - - struct interrupt_descriptor_table - { - interrupt_descriptor_table() noexcept; - - auto load() const -> void; - - private: - std::array m_descriptors{}; - }; - - struct [[gnu::packed]] interrupt_descriptor_table_register - { - std::uint16_t limit; - gate_descriptor const * base; - - auto load() const -> void; - auto static read() -> interrupt_descriptor_table_register; - }; - -} // namespace arch::cpu - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/cpu/legacy_pic.hpp b/arch/x86_64/include/arch/cpu/legacy_pic.hpp deleted file mode 100644 index 56ca9c4..0000000 --- a/arch/x86_64/include/arch/cpu/legacy_pic.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TEACHOS_X86_64_CPU_LEGACY_PIC_HPP -#define TEACHOS_X86_64_CPU_LEGACY_PIC_HPP - -#include - -#include - -namespace arch::cpu -{ - using pic_master_control_port = io::port<0x20, std::uint8_t, io::port_read, io::port_write>; - using pic_master_data_port = io::port<0x21, std::uint8_t, io::port_read, io::port_write>; - using pic_slave_control_port = io::port<0xa0, std::uint8_t, io::port_read, io::port_write>; - using pic_slave_data_port = io::port<0xa1, std::uint8_t, io::port_read, io::port_write>; - - constexpr auto pic_end_of_interrupt = std::uint8_t{0x20}; - -} // 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 deleted file mode 100644 index bd4aff9..0000000 --- a/arch/x86_64/include/arch/cpu/model_specific_register.hpp +++ /dev/null @@ -1,151 +0,0 @@ -#ifndef TEACHOS_X86_64_CPU_MODEL_SPECIFIC_REGISTER_HPP -#define TEACHOS_X86_64_CPU_MODEL_SPECIFIC_REGISTER_HPP - -// IWYU pragma: private, include - -#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 deleted file mode 100644 index 58633f6..0000000 --- a/arch/x86_64/include/arch/cpu/registers.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef TEACHOS_X86_64_CPU_REGISTERS_HPP -#define TEACHOS_X86_64_CPU_REGISTERS_HPP - -#include // IWYU pragma: export -#include // IWYU pragma: export - -#include - -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_descriptor.hpp b/arch/x86_64/include/arch/cpu/segment_descriptor.hpp deleted file mode 100644 index 9570670..0000000 --- a/arch/x86_64/include/arch/cpu/segment_descriptor.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef TEACHOS_X86_64_SEGMENT_DESCRIPTOR_HPP -#define TEACHOS_X86_64_SEGMENT_DESCRIPTOR_HPP - -#include - -namespace arch::cpu -{ - - //! The type of segment described by a segment_descriptor. - enum struct segment_type : std::uint8_t - { - //! A system (TSS or LDT) segment - system = 0, - //! A code or data segment - code_or_data = 1, - }; - - //! The granularity of a segment described by a segment_descriptor - enum struct segment_granularity : std::uint8_t - { - //! The limit of the segment is defined in bytes. - byte = 0, - //! The limit of the segment is defined in pages (4KiB) - page = 1, - }; - - //! An entry in a segment descriptor table - struct segment_descriptor - { - std::uint64_t limit_low : 16; - std::uint64_t base_low : 24; - bool accessed : 1; - bool read_write : 1; - bool direction_or_conforming : 1; - bool executable : 1; - segment_type type : 1; - std::uint64_t privilege_level : 2; - bool present : 1; - std::uint64_t limit_high : 4; - std::uint64_t : 1; - bool long_mode : 1; - bool protected_mode : 1; - segment_granularity granularity : 1; - std::uint64_t base_high : 8; - }; - - static_assert(sizeof(segment_descriptor) == sizeof(std::uint64_t)); - static_assert(alignof(segment_descriptor) == alignof(std::uint64_t)); - - struct system_segment_descriptor : segment_descriptor - { - std::uint64_t base_extended : 32; - std::uint64_t : 32; - }; - - static_assert(sizeof(system_segment_descriptor) == 2 * sizeof(std::uint64_t)); - static_assert(alignof(system_segment_descriptor) == alignof(segment_descriptor)); - -} // 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 deleted file mode 100644 index 1a78c47..0000000 --- a/arch/x86_64/include/arch/cpu/segment_selector.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#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/cpu/task_state_segment.hpp b/arch/x86_64/include/arch/cpu/task_state_segment.hpp deleted file mode 100644 index 57729dd..0000000 --- a/arch/x86_64/include/arch/cpu/task_state_segment.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef TEACHOS_X86_64_TASK_STATE_SEGMENT_HPP -#define TEACHOS_X86_64_TASK_STATE_SEGMENT_HPP - -#include - -namespace arch::cpu -{ - - struct [[gnu::packed]] task_state_segment - { - uint32_t : 32; - uint64_t rsp0 = {}; - uint64_t rsp1 = {}; - uint64_t rsp2 = {}; - uint64_t : 64; - uint64_t ist1 = {}; - uint64_t ist2 = {}; - uint64_t ist3 = {}; - uint64_t ist4 = {}; - uint64_t ist5 = {}; - uint64_t ist6 = {}; - uint64_t ist7 = {}; - uint64_t : 64; - uint16_t : 16; - uint16_t io_map_base_address = {}; - }; - -} // 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 deleted file mode 100644 index 5ddd4be..0000000 --- a/arch/x86_64/include/arch/debug/qemu_output.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef TEACHOS_X86_64_DEBUG_QEMU_OUTPUT_HPP -#define TEACHOS_X86_64_DEBUG_QEMU_OUTPUT_HPP - -#include - -#include - -#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 deleted file mode 100644 index 4c8d66a..0000000 --- a/arch/x86_64/include/arch/device_io/port_io.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#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 %[data], %%al\nout %%al, %%dx"}, - std::string_view{"mov %[port], %%dx\nmov %[data], %%ax\nout %%ax, %%dx"}, - std::string_view{"mov %[port], %%dx\nmov %[data], %%eax\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"}; - }; - - auto inline wait() -> void - { - port<0x80, std::uint8_t, port_write>::write(0); - } - -} // namespace arch::io - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/devices/init.hpp b/arch/x86_64/include/arch/devices/init.hpp deleted file mode 100644 index c5fbf37..0000000 --- a/arch/x86_64/include/arch/devices/init.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_DEVICES_INIT_HPP -#define TEACHOS_ARCH_X86_64_DEVICES_INIT_HPP - -namespace arch::devices -{ - - auto init_acpi_devices() -> void; - auto init_legacy_devices() -> void; - -} // namespace arch::devices - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/devices/legacy_pit.hpp b/arch/x86_64/include/arch/devices/legacy_pit.hpp deleted file mode 100644 index 356895c..0000000 --- a/arch/x86_64/include/arch/devices/legacy_pit.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP -#define TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP - -#include -#include - -#include -#include - -namespace arch::devices -{ - - struct legacy_pit : kapi::devices::device, kapi::interrupts::handler - { - legacy_pit(std::size_t major, std::uint32_t frequency_in_hz); - - auto init() -> bool override; - - auto handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status override; - - private: - std::uint32_t m_irq_number{}; - std::uint32_t m_frequency_in_hz{}; - std::uint64_t m_ticks{}; - }; - -} // namespace arch::devices - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/devices/local_apic.hpp b/arch/x86_64/include/arch/devices/local_apic.hpp deleted file mode 100644 index f8f080d..0000000 --- a/arch/x86_64/include/arch/devices/local_apic.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_DEVICES_LOCAL_APIC_HPP -#define TEACHOS_ARCH_X86_64_DEVICES_LOCAL_APIC_HPP - -#include -#include - -#include -#include - -namespace arch::devices -{ - - struct local_apic : kapi::devices::device - { - local_apic(std::size_t major, std::size_t minor, std::uint64_t hardware_id, kapi::memory::physical_address base, - bool is_bsp); - - auto init() -> bool override; - - private: - enum struct registers : std::ptrdiff_t; - - [[nodiscard]] auto read_register(registers id) const -> std::uint32_t; - auto write_register(registers id, std::uint32_t value) -> void; - - std::uint64_t m_hardware_id{}; - kapi::memory::physical_address m_base{}; - kapi::memory::mmio_region m_mapped_region{}; - bool m_is_bsp{}; - std::uint8_t m_version{}; - std::uint8_t m_highest_lvt_entry_index{}; - bool m_supports_eoi_broadcast_suppression{}; - }; - -} // namespace arch::devices - -#endif diff --git a/arch/x86_64/include/arch/memory/higher_half_mapper.hpp b/arch/x86_64/include/arch/memory/higher_half_mapper.hpp deleted file mode 100644 index 9b02ee6..0000000 --- a/arch/x86_64/include/arch/memory/higher_half_mapper.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef TEACHOS_X86_64_HIGHER_HALF_MAPPER_HPP -#define TEACHOS_X86_64_HIGHER_HALF_MAPPER_HPP - -#include - -#include - -#include - -namespace arch::memory -{ - - //! A simple page mapper making use of a Higher Half Direct Map (HHDM) to access and modify page tables. - struct higher_half_mapper : kapi::memory::page_mapper - { - //! Construct a new mapper for a hierarchy rooted in the given PML. - //! - //! @param root The root of the hierarchy to operate on. - explicit higher_half_mapper(page_table * root); - - //! @copydoc kapi::memory::page_mapper::map - auto map(kapi::memory::page page, kapi::memory::frame frame, flags flags) -> std::byte * override; - - //! @copydoc kapi::memory::page_mapper::unmap - auto unmap(kapi::memory::page page) -> void override; - - //! @copydoc kapi::memory::page_mapper::try_unmap - auto try_unmap(kapi::memory::page page) noexcept -> bool override; - - private: - //! Try to retrieve the the PML1 responsible for mapping this page, creating one if necessary. - //! - //! This function will create a page table hierarchy leading to the target PML1 if it doesn't exist. - //! - //! @param page The page to get the PML1 for. - //! @return The PML1 that manages the given page, nullptr it the system runs out of memory. - auto get_or_create_page_table(kapi::memory::page page) noexcept -> page_table *; - - page_table * m_root; - }; - -} // 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 deleted file mode 100644 index adbf688..0000000 --- a/arch/x86_64/include/arch/memory/kernel_mapper.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef TEACHOS_X86_64_KERNEL_MAPPER_HPP -#define TEACHOS_X86_64_KERNEL_MAPPER_HPP - -#include - -#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 deleted file mode 100644 index 64373f4..0000000 --- a/arch/x86_64/include/arch/memory/mmu.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef TEACHOS_X86_64_MEMORY_MMU_HPP -#define TEACHOS_X86_64_MEMORY_MMU_HPP - -#include - -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 deleted file mode 100644 index ce3d3a1..0000000 --- a/arch/x86_64/include/arch/memory/page_table.hpp +++ /dev/null @@ -1,232 +0,0 @@ -#ifndef TEACHOS_X86_64_PAGE_TABLE_HPP -#define TEACHOS_X86_64_PAGE_TABLE_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. In most cases, 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. The only exception to that rule - //! is the use of huge pages. - 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 / kstd::units::bytes{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{}; - }; - -} // 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::write_through)) != table_flags::empty) - { - result |= mapper_flags::uncached; - } - - if ((flags & table_flags::user_accessible) == table_flags::empty) - { - result |= mapper_flags::supervisor_only; - } - - if ((flags & table_flags::global) != table_flags::empty) - { - result |= mapper_flags::global; - } - - 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 | table_flags::write_through); - } - - if ((flags & mapper_flags::supervisor_only) == mapper_flags::empty) - { - result |= table_flags::user_accessible; - } - - if ((flags & mapper_flags::global) != mapper_flags::empty) - { - result |= table_flags::global; - } - - return result; - } - -} // namespace arch::memory - -#endif diff --git a/arch/x86_64/include/arch/memory/page_utilities.hpp b/arch/x86_64/include/arch/memory/page_utilities.hpp deleted file mode 100644 index 068e824..0000000 --- a/arch/x86_64/include/arch/memory/page_utilities.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TEACHOS_X86_64_PAGE_UTILITIES_HPP -#define TEACHOS_X86_64_PAGE_UTILITIES_HPP - -#include - -#include - -namespace arch::memory -{ - - constexpr auto inline pml_index(std::size_t index, kapi::memory::page page) noexcept -> std::size_t - { - constexpr auto bits_per_level = 9; - auto shift_width = (index - 1) * bits_per_level; - constexpr auto index_mask = 0x1ffuz; - return page.number() >> shift_width & index_mask; - } - - template - [[nodiscard]] constexpr auto to_higher_half_pointer(kapi::memory::physical_address address) -> ValueType * - { - using namespace kapi::memory; - auto const higher_half_address = higher_half_direct_map_base + address.raw(); - return static_cast(higher_half_address); - } - -} // 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 deleted file mode 100644 index 5d9da2e..0000000 --- a/arch/x86_64/include/arch/memory/region_allocator.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP -#define TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP - -#include -#include -#include - -#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 that are located in - //! non-reserved (as in available) regions should be allocated for page storage. - multiboot2::memory_map memory_map; - - //! The loader supplied Multiboot2 information structure. - //! - //! This is used to query boot module ranges so these frames can be excluded from early allocations. - multiboot2::information_view const * mbi; - }; - - using region = multiboot2::memory_map::region; - - //! Construct a new allocator using the provided memory information - //! - //! @param information The description of the detected memory regions as well as regions that are already occupied. - explicit region_allocator(memory_information const & information); - - //! @copydoc kapi::memory::frame_allocator::allocate_many - //! - //! @note As long as free frames are available, successive calls to this implementation are guaranteed to yield - //! frames in ascending order. - auto allocate_many(std::size_t count = 1) noexcept - -> std::optional> override; - - //! @copydoc kapi::memory::frame_allocator::mark_used - auto mark_used(kapi::memory::frame frame) -> void override; - - //! @copydoc kapi::memory::frame_allocator::release_many - //! - //! @note This implementation will never actually release any frames. - auto release_many(std::pair frame_set) -> void override; - - auto next_free_frame() noexcept -> std::optional; - - private: - //! Find the next memory area and write it into current_area. - auto choose_next_region() -> void; - auto find_next_frame() -> std::optional; - - kapi::memory::frame m_next_frame; //!< The next available frame. - std::optional m_current_region; //!< The memory region currently used for allocation - multiboot2::memory_map m_memory_map; //!< The boot loader supplied memory map. - kapi::memory::frame m_kernel_start; //!< The start of the kernel image in physical memory. - kapi::memory::frame m_kernel_end; //!< The end of the kernel image in physical memory. - kapi::memory::frame m_multiboot_start; //!< The start of the Multiboot2 information in physical memory. - kapi::memory::frame m_multiboot_end; //!< The end of the Multiboot2 information in physical memory. - multiboot2::information_view const * m_multiboot_information; //!< Source of Multiboot2 module ranges. - }; - -} // namespace arch::memory - -#endif diff --git a/arch/x86_64/include/arch/vga/crtc.hpp b/arch/x86_64/include/arch/vga/crtc.hpp deleted file mode 100644 index a8bec93..0000000 --- a/arch/x86_64/include/arch/vga/crtc.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_IO_HPP -#define TEACHOS_X86_64_VGA_IO_HPP - -#include - -#include - -namespace arch::vga::crtc -{ - /** - * @brief The address port of the CRT Controller. - */ - using address = io::port<0x3d4, std::byte, io::port_write>; - - /** - * @brief The data port of the CRT Controller. - */ - using data = io::port<0x3d5, std::byte, io::port_read, io::port_write>; - - namespace registers - { - /** - * @brief The address of the Cursor Start register of the CRTC. - */ - [[maybe_unused]] constexpr auto cursor_start = std::byte{0x0a}; - - /** - * @brief The address of the Cursor End register of the CRTC. - */ - [[maybe_unused]] constexpr auto cursor_end = std::byte{0x0b}; - } // namespace registers - -} // namespace arch::vga::crtc - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text.hpp b/arch/x86_64/include/arch/vga/text.hpp deleted file mode 100644 index 2e73dd2..0000000 --- a/arch/x86_64/include/arch/vga/text.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_HPP -#define TEACHOS_X86_64_VGA_TEXT_HPP - -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export -#include // IWYU pragma: export - -#endif // TEACHOS_ARCH_X86_64_VIDEO_VGA_TEXT_HPP \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text/attribute.hpp b/arch/x86_64/include/arch/vga/text/attribute.hpp deleted file mode 100644 index 6395aed..0000000 --- a/arch/x86_64/include/arch/vga/text/attribute.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_ATTRIBUTE_HPP -#define TEACHOS_X86_64_VGA_TEXT_ATTRIBUTE_HPP - -// IWYU pragma: private, include - -#include -#include - -namespace arch::vga::text -{ - //! The VGA text mode attribute. - //! - //! @note In the text mode of VGA, every code point being presented is followed by an attribute description. This - //! allows for the modification of how the relevant "cell" is presented. - //! - //! @see text::foreground_flag - //! @see text::background_flag - struct attribute - { - color foreground_color : 3; ///< The foreground color of the cell, e.g. the color of the code point. - enum foreground_flag foreground_flag : 1; ///< The foreground color modification flag of the cell. - color background_color : 3; ///< The background color of the cell. - enum background_flag background_flag : 1; ///< The background color modification flag of the cell. - }; - - static_assert(sizeof(attribute) == 1, "The VGA text mode attribute must fit inside a single byte."); - -} // namespace arch::vga::text - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text/buffer.hpp b/arch/x86_64/include/arch/vga/text/buffer.hpp deleted file mode 100644 index 8eb6645..0000000 --- a/arch/x86_64/include/arch/vga/text/buffer.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_BUFFER_HPP -#define TEACHOS_X86_64_VGA_TEXT_BUFFER_HPP - -// IWYU pragma: private, include - -#include - -#include -#include -#include -#include - -namespace arch::vga::text -{ - //! A VGA text buffer. - //! - //! VGA text mode presents a linear buffer of so-called cells. Each cell consists of a single code point and a - //! rendering attribute. The codepoint determines the character being rendered in a specific cell, while the attribute - //! determines the visual style of that cell. - //! - //! @see text::attribute - struct buffer - { - using cell = std::pair; - - //! Create a new buffer. - //! - //! @param width The width of the buffer - //! @param height The height of the buffer - //! @param start A pointer to the first byte of the buffer. - //! @param position The starting position for the first write to the buffer - buffer(std::size_t width, std::size_t height, cell * start, std::size_t position = 0); - - //! Clear the buffer. - //! - //! Clearing the buffer ensures it is filled with zeroes, effectively erasing all data and resetting the output - //! position to the start of the buffer. - auto clear() -> void; - - //! Write a string of formatted code points to the buffer. - //! - //! @param code_points A string of (8-bit) code points to write to the buffer. - //! @param attribute The formatting to apply to the written sequence of code points. - auto write(std::string_view code_points, attribute attribute) -> void; - - //! Write a single, formatted code point to the buffer. - //! - //! @param code_point A single (8-bit) code point - //! @param attribute The formatting to apply to the code point. - auto write(char code_point, attribute attribute) -> void; - - //! Move the output position to a new line and scroll the buffer if necessary. - auto newline() -> void; - - //! Scroll the buffer contents. - //! - //! @param nof_lines The number of lines to scroll up. - auto scroll(std::size_t nof_lines = 1) -> void; - - private: - //! Get column number of the current cell. - [[nodiscard]] auto column() const noexcept -> std::ptrdiff_t; - - //! Get the line number of the current cell. - [[nodiscard]] auto line() const noexcept -> std::ptrdiff_t; - - //! Process the semantics of special code points, for example newlines and carriage returns. - //! - //! @param code_point The code point to process. - //! @param attribute The attribute to use when writing to the text buffer. - //! @return @p true iff. the code point was handled, @p false otherwise. - auto handle_special_code_point(char code_point, attribute attribute) -> bool; - - //! Perform the actual output to the buffer. - //! - //! @param code_points The code points to output.. - //! @param attribute The attribute to use when writing to the text buffer. - auto do_write(std::string_view code_points, attribute attribute) -> void; - - //! Perform the actual output to the buffer. - //! - //! @param code_point The code point to output. - //! @param attribute The attribute to use when writing to the text buffer. - auto do_write(char code_point, attribute attribute) -> void; - - //! The width, in cells, of the buffer. - std::size_t m_width{}; - - //! The height, in cells, of the buffer. - std::size_t m_height{}; - - //! The text mode data buffer. - std::span m_buffer; - - //! The position of the next cell to be written to. - std::size_t m_position{}; - }; - -} // namespace arch::vga::text - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text/color.hpp b/arch/x86_64/include/arch/vga/text/color.hpp deleted file mode 100644 index e0ad6df..0000000 --- a/arch/x86_64/include/arch/vga/text/color.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_COLOR_HPP -#define TEACHOS_X86_64_VGA_TEXT_COLOR_HPP - -// IWYU pragma: private, include - -#include - -namespace arch::vga::text -{ - //! VGA Text Mode standard colors. - //! - //! Every color may be used as a foreground and/or background color, in any combination. - enum struct color : std::uint8_t - { - //! Equivalent to HTML color \#000000. - black, - //! Equivalent to HTML color \#0000AA. - blue, - //! Equivalent to HTML color \#00AA00. - green, - //! Equivalent to HTML color \#00AAAA. - cyan, - //! Equivalent to HTML color \#AA0000. - red, - //! Equivalent to HTML color \#AA00AA. - purple, - //! Equivalent to HTML color \#AA5500. - brown, - //! Equivalent to HTML color \#AAAAAA. - gray, - }; - -} // namespace arch::vga::text - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text/common_attributes.hpp b/arch/x86_64/include/arch/vga/text/common_attributes.hpp deleted file mode 100644 index 3d8929f..0000000 --- a/arch/x86_64/include/arch/vga/text/common_attributes.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_COMMON_ATTRIBUTES_HPP -#define TEACHOS_X86_64_VGA_TEXT_COMMON_ATTRIBUTES_HPP - -// IWYU pragma: private, include - -#include -#include -#include - -namespace arch::vga::text -{ - //! Make the affected cell display with a gray foreground and black background. - [[maybe_unused]] constexpr auto gray_on_black = attribute{.foreground_color = color::gray, - .foreground_flag = foreground_flag::none, - .background_color = color::black, - .background_flag = background_flag::none}; - - //! Make the affected cell display with a green foreground and black background. - [[maybe_unused]] constexpr auto green_on_black = attribute{.foreground_color = color::green, - .foreground_flag = foreground_flag::none, - .background_color = color::black, - .background_flag = background_flag::none}; - - //! Make the affected cell display with a green foreground and black background. - [[maybe_unused]] constexpr auto red_on_black = attribute{.foreground_color = color::red, - .foreground_flag = foreground_flag::none, - .background_color = color::black, - .background_flag = background_flag::none}; - - //! Make the affected cell display with a white (gray + intense) foreground and red background. - [[maybe_unused]] constexpr auto white_on_red = attribute{.foreground_color = color::gray, - .foreground_flag = foreground_flag::intense, - .background_color = color::red, - .background_flag = background_flag::none}; -} // namespace arch::vga::text - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text/device.hpp b/arch/x86_64/include/arch/vga/text/device.hpp deleted file mode 100644 index 0a0e017..0000000 --- a/arch/x86_64/include/arch/vga/text/device.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_DEVICE_HPP -#define TEACHOS_X86_64_VGA_TEXT_DEVICE_HPP - -// IWYU pragma: private, include - -#include - -#include - -#include - -namespace arch::vga::text -{ - //! A VGA Text Mode device. - //! - //! VGA text mode presents a linear buffer of so-called cells. Each cell consists of a single code point and a - //! rendering attribute. The codepoint determines the character being rendered in a specific cell, while the attribute - //! determines the visual style of that cell. - //! - //! @see text::attribute - struct device final : kapi::cio::output_device - { - device(); - - //! Clear the screen. - //! - //! Clearing the screen ensures the text mode buffer is filled with zeroes, effectively erasing all displayed data - //! and resetting the output position to the start of the buffer. - auto clear() -> void; - - //! Enable or disable the VGA text mode cursor. - //! - //! @param enabled Whether to enable the cursor. - auto cursor(bool enabled) -> void; - - //! @copydoc kapi::cio::output_device - auto write(kapi::cio::output_stream stream, std::string_view text) -> void override; - - private: - buffer m_buffer; - }; - -} // namespace arch::vga::text - -#endif \ No newline at end of file diff --git a/arch/x86_64/include/arch/vga/text/flags.hpp b/arch/x86_64/include/arch/vga/text/flags.hpp deleted file mode 100644 index 7a29e33..0000000 --- a/arch/x86_64/include/arch/vga/text/flags.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef TEACHOS_X86_64_VGA_TEXT_FLAGS_HPP -#define TEACHOS_X86_64_VGA_TEXT_FLAGS_HPP - -// IWYU pragma: private, include - -namespace arch::vga::text -{ - - //! VGA Text Mode standard background modification flags. - enum struct background_flag : bool - { - //! Do not modify the foreground rendering. - none, - //! Render the background as blinking or intense. - //! - //! Whether this flag is interpreted as 'blink' or 'bright' depends on the currently active configuration of the VGA - //! device. - //! - //! @note The VGA standard does not specify the exact effect of this 'intense', however, most devices render such - //! colors brighter. - blink_or_bright, - }; - - //! VGA Text Mode standard foreground modification flags. - enum struct foreground_flag : bool - { - //! Do not modify the foreground rendering. - none, - //! Render the foreground as intense. - //! - //! @note The VGA standard does not specify the exact effect of this 'intense', however, most devices render such - //! colors brighter. - intense, - }; - -} // namespace arch::vga::text - -#endif \ No newline at end of file diff --git a/arch/x86_64/pre/include/arch/context_switching/main.hpp b/arch/x86_64/pre/include/arch/context_switching/main.hpp deleted file mode 100644 index 07e00e8..0000000 --- a/arch/x86_64/pre/include/arch/context_switching/main.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_MAIN_HPP -#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_MAIN_HPP - -#include -#include - -namespace teachos::arch::context_switching -{ - /** - * @brief Contains the references to the tables required for context switching - */ - struct descriptor_tables - { - segment_descriptor_table::global_descriptor_table & gdt; ///< Reference to the global descriptor table. - interrupt_descriptor_table::interrupt_descriptor_table & idt; ///< Reference to the interrupt descriptor table. - }; - - /** - * @brief Creates the Interrupt Descriptor Table and Global Descriptor Table as a static variable the first time this - * method is called and update IDTR and GDTR registers values. - * - * @note Subsequent calls after the first one, will simply return the previously created tables, but not update the - * registers again. - * - * @return References to the statically created Interrupt Descriptor and Global Descriptor Table. - */ - auto initialize_descriptor_tables() -> descriptor_tables; - - /** - * @brief Switches from the current Kernel Mode (Level 0) to User Mode (Level 3). Will simply use predefined Segment - * Selectors for the User Data and User Code Segment, which are Index 3 and 4 in the GDT respectively. - */ - auto switch_to_user_mode() -> void; - - /** - * @brief Switches from the current Code and Data Segment to the given Code and Data Segment. - * - * @note This method will additionally call initialize_descriptor_tables, to ensure the GDTR and IDTR have been setup - * correctly before attempting to switch the context. This switch is achieved using a far return, which will once - * executed call the given void function. - * - * @param data_segment Data Segment that the SS, DS; ES, FS and GS register will be set too. - * @param code_segment Code Segment that the CS register will be set too. - * @param return_function Function that will be called once the switch has been achieved. - */ - auto switch_context(interrupt_descriptor_table::segment_selector data_segment, - interrupt_descriptor_table::segment_selector code_segment, void (*return_function)()) -> void; - -} // namespace teachos::arch::context_switching - -#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_MAIN_HPP diff --git a/arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp b/arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp deleted file mode 100644 index f507c61..0000000 --- a/arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_MAIN_HPP -#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_MAIN_HPP - -#include - -namespace teachos::arch::context_switching::syscall -{ - /** - * @brief Possible syscall implementation that should actually be called. - * - * @note Attempts to reflect the Linux interface partially. See https://filippo.io/linux-syscall-table/ for more - * information. - */ - enum class type : uint64_t - { - WRITE = 1U, ///< Loads the arg_0 parameter as an address pointing to a const char array, which will then be printed - ///< onto the VGA buffer screen. - EXPAND_HEAP = 2U, /// Expands the User Heap by additonally mapping 100 KiB of virtual page memory. Ignores the - /// parameters and uses them as out parameters instead, where arg_0 is the start of the newly - /// mapped heap area and arg_1 is the size of the entire area. Can be less than 100 KiB if less - /// space remains. - ASSERT = 3U, /// Loads the arg_0 parameter as a boolean which needs to be true or it will print the message in - /// arg_1 parameter onto the VGA buffer screen, keep it as a nullptr if it shouldn't print anything - /// and then it halts the further execution of the application. - }; - - /** - * @brief Possible error codes that can be returned by the different syscall methods called depending on the type - * enum. - */ - enum class error : uint8_t - { - OK = 0U, ///< No error occured in syscall. - OUT_OF_MEMORY = 1U, ///< Expanding heap failed because we have run out of mappable virtual address space. - }; - - /** - * @brief Allows to convert the error enum type into a boolean directly. Where any code besides 0 being no error, will - * return true. The remaining errors return true. - * - * @param e Error code that was returned by the syscall. - * @return Return true if there was no error and false otherwise. - */ - constexpr bool operator!(error e) - { - return e == error::OK; - } - - /** - * @brief Maximum amount of arguments that can be passed to a syscall. Default value is 0 and arguments are only ever - * used depending on the actual enum type and if the method requires thoose parameters. - */ - struct arguments - { - uint64_t arg_0{}; ///< First optional paramter to the syscall representing the RDI register. - uint64_t arg_1{}; ///< Second optional paramter to the syscall representing the RSI register. - uint64_t arg_2{}; ///< Third optional paramter to the syscall representing the RDX register. - uint64_t arg_3{}; ///< Fourth optional paramter to the syscall representing the R10 register. - uint64_t arg_4{}; ///< Fifth optional paramter to the syscall representing the R8 register. - uint64_t arg_5{}; ///< Sixth optional paramter to the syscall representing the R9 register. - }; - - /** - * @brief Response of a systemcall always containin an error code, signaling if the syscall even succeeded or not. - * Additionally it may contain up to 6 return values in the values struct. - */ - struct response - { - error error_code; ///< Error code returned by the syscall. If it failed all the values will be 0. - arguments values = {}; ///< Optional return values of the syscall implementation. - }; - - /** - * @brief Calls the method associated with the given syscall number and passes the given optional arguments to it, - * over the RDI, RSI, RDX, R10, R8 and R9 register. - * - * @param syscall_number Syscall method that should be called. See enum values in type for possible implemented - * methods. - * @param args Optional arguments passable to the different syscall methods, called depending on the syscall_number. - * Not passing the required parameters to the method, will result in passing 0 instead, which might make the fail or - * not function correctly. - * @return The syscall implementation always returns a bool-convertable error code converting to true if the syscall - * failed or false if it didn't. Additionally it might pase additional values besides the error code, they will be set - * in the arguments struct. So the value can be read and used for further processing. - */ - [[gnu::section(".user_text")]] - auto syscall(type syscall_number, arguments args = {}) -> response; - -} // namespace teachos::arch::context_switching::syscall - -#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_MAIN_HPP diff --git a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp b/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp deleted file mode 100644 index 8cb468a..0000000 --- a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_ENABLE_HPP -#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_ENABLE_HPP - -namespace teachos::arch::context_switching::syscall -{ - /** - * @brief Enables and sets up internal CPU registers to allow for syscall to function correctly and switch context - * temporarily back to the kernel level. - * - * @note Configures the Model Specific Register required by syscall (LSTAR, FMASK, STAR) with the correct values so - * that the syscall_handler is called and sets the System Call Extension bit on the EFER flags to allow for syscall - * to be used. - */ - auto enable_syscall() -> void; - -} // namespace teachos::arch::context_switching::syscall - -#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_ENABLE_HPP diff --git a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp b/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp deleted file mode 100644 index 2e7bcd1..0000000 --- a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_HANDLER_HPP -#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_HANDLER_HPP - -#include - -namespace teachos::arch::context_switching::syscall -{ - /** - * @brief Handler for SYSCALL instruction. Calls a specific implementation based - * on the register RAX. - * - * @return Returns with LEAVE, SYSRETQ - */ - auto syscall_handler() -> void; - -} // namespace teachos::arch::context_switching::syscall - -#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_HANDLER_HPP diff --git a/arch/x86_64/pre/include/arch/kernel/halt.hpp b/arch/x86_64/pre/include/arch/kernel/halt.hpp deleted file mode 100644 index 377acc0..0000000 --- a/arch/x86_64/pre/include/arch/kernel/halt.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_KERNEL_HALT_HPP -#define TEACHOS_ARCH_X86_64_KERNEL_HALT_HPP - -namespace teachos::arch::kernel -{ - /** - * @brief Halts the kernel execution, meaning any code after a call to this will not run anymore. - */ - extern "C" [[noreturn]] auto halt() -> void; - -} // namespace teachos::arch::kernel - -#endif // TEACHOS_ARCH_X86_64_KERNEL_HALT_HPP diff --git a/arch/x86_64/pre/include/arch/kernel/main.hpp b/arch/x86_64/pre/include/arch/kernel/main.hpp deleted file mode 100644 index a13e5f4..0000000 --- a/arch/x86_64/pre/include/arch/kernel/main.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_KERNEL_MAIN_HPP -#define TEACHOS_ARCH_X86_64_KERNEL_MAIN_HPP - -namespace teachos::arch::kernel -{ - /** - * @brief Initalizes the kernel system. - */ - auto main() -> void; - -} // namespace teachos::arch::kernel - -#endif // TEACHOS_ARCH_X86_64_KERNEL_MAIN_HPP diff --git a/arch/x86_64/pre/include/arch/user/main.hpp b/arch/x86_64/pre/include/arch/user/main.hpp deleted file mode 100644 index c168a1f..0000000 --- a/arch/x86_64/pre/include/arch/user/main.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef TEACHOS_ARCH_X86_64_USER_MAIN_HPP -#define TEACHOS_ARCH_X86_64_USER_MAIN_HPP - -namespace teachos::arch::user -{ - /** - * @brief User Main method. If this method finishes there is no code left to run and the whole OS will shut down. - * Additionally this main method is executed at Ring 3 and accessing CPU Registers or Kernel level functionality can - * only be done over syscalls and not directly anymore. - */ - [[gnu::section(".user_text")]] - auto main() -> void; - -} // namespace teachos::arch::user - -#endif // TEACHOS_ARCH_X86_64_USER_MAIN_HPP diff --git a/arch/x86_64/pre/src/context_switching/main.cpp b/arch/x86_64/pre/src/context_switching/main.cpp deleted file mode 100644 index 0961499..0000000 --- a/arch/x86_64/pre/src/context_switching/main.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace teachos::arch::context_switching -{ - namespace - { - constexpr interrupt_descriptor_table::segment_selector KERNEL_CODE_SEGMENT_SELECTOR{ - 1U, interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_KERNEL}; - constexpr kernel::cpu::far_pointer KERNEL_CODE_POINTER{&kernel::cpu::reload_data_segment_registers, - KERNEL_CODE_SEGMENT_SELECTOR}; - constexpr context_switching::interrupt_descriptor_table::segment_selector USER_CODE_SEGMENT_SELECTOR{ - 3U, context_switching::interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_USER}; - constexpr context_switching::interrupt_descriptor_table::segment_selector USER_DATA_SEGMENT_SELECTOR{ - 4U, context_switching::interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_USER}; - - auto reload_gdtr() -> void - { - kernel::cpu::call(KERNEL_CODE_POINTER); - } - } // namespace - - auto initialize_descriptor_tables() -> descriptor_tables - { - bool static initalized = false; - - if (!initalized) - { - kernel::cpu::clear_interrupt_flag(); - - segment_descriptor_table::update_gdtr(); - interrupt_descriptor_table::update_interrupt_descriptor_table_register(); - - reload_gdtr(); - segment_descriptor_table::update_tss_register(); - - kernel::cpu::set_interrupt_flag(); - initalized = true; - } - - descriptor_tables tables = {segment_descriptor_table::get_or_create_gdt(), - interrupt_descriptor_table::get_or_create_interrupt_descriptor_table()}; - return tables; - } - - auto switch_to_user_mode() -> void - { - syscall::enable_syscall(); - switch_context(USER_DATA_SEGMENT_SELECTOR, USER_CODE_SEGMENT_SELECTOR, user::main); - } - - auto switch_context(interrupt_descriptor_table::segment_selector data_segment, - interrupt_descriptor_table::segment_selector code_segment, void (*return_function)()) -> void - { - (void)initialize_descriptor_tables(); - kernel::cpu::set_data_segment_registers(data_segment); - kernel::cpu::set_code_segment_register(data_segment, code_segment, reinterpret_cast(return_function)); - } -} // namespace teachos::arch::context_switching diff --git a/arch/x86_64/pre/src/context_switching/syscall/main.cpp b/arch/x86_64/pre/src/context_switching/syscall/main.cpp deleted file mode 100644 index 10bd087..0000000 --- a/arch/x86_64/pre/src/context_switching/syscall/main.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include - -namespace teachos::arch::context_switching::syscall -{ - auto syscall(type syscall_number, arguments args) -> response - { - asm volatile("mov %[input], %%rax" - : /* no output from call */ - : [input] "m"(syscall_number) - : "memory"); - - asm volatile("mov %[input], %%rdi " : /* no output from call */ : [input] "m"(args.arg_0) : "memory"); - asm volatile("mov %[input], %%rsi" : /* no output from call */ : [input] "m"(args.arg_1) : "memory"); - asm volatile("mov %[input], %%rdx" : /* no output from call */ : [input] "m"(args.arg_2) : "memory"); - asm volatile("mov %[input], %%r10" : /* no output from call */ : [input] "m"(args.arg_3) : "memory"); - asm volatile("mov %[input], %%r8" : /* no output from call */ : [input] "m"(args.arg_4) : "memory"); - asm volatile("mov %[input], %%r9" : /* no output from call */ : [input] "m"(args.arg_5) : "memory"); - - asm volatile("syscall"); - - arguments values{}; - asm volatile("mov %%rdi, %[output]" : [output] "=m"(values.arg_0)); - asm volatile("mov %%rsi, %[output]" : [output] "=m"(values.arg_1)); - asm volatile("mov %%rdx, %[output]" : [output] "=m"(values.arg_2)); - asm volatile("mov %%r10, %[output]" : [output] "=m"(values.arg_3)); - asm volatile("mov %%r8, %[output]" : [output] "=m"(values.arg_4)); - asm volatile("mov %%r9, %[output]" : [output] "=m"(values.arg_5)); - - error error_code{}; - asm volatile("mov %%al, %[output]" : [output] "=m"(error_code)); - - return {error_code, values}; - } - -} // namespace teachos::arch::context_switching::syscall diff --git a/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp b/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp deleted file mode 100644 index f9f070a..0000000 --- a/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include - -#include -#include -#include - -namespace teachos::arch::context_switching::syscall -{ - namespace - { - constexpr interrupt_descriptor_table::segment_selector KERNEL_CODE_SEGMENT_SELECTOR{ - 1U, interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_KERNEL}; - - constexpr auto IA32_STAR_ADDRESS = 0xC000'0081; - constexpr auto IA32_LSTAR_ADDRESS = 0xC000'0082; - constexpr auto IA32_FMASK_ADDRESS = 0xC000'0084; - - } // namespace - - auto enable_syscall() -> void - { - uint64_t const syscall_function = reinterpret_cast(syscall_handler); - kernel::cpu::write_msr(IA32_LSTAR_ADDRESS, syscall_function); - kernel::cpu::write_msr(IA32_FMASK_ADDRESS, 0U); - - uint64_t const kernel_cs = KERNEL_CODE_SEGMENT_SELECTOR; - uint64_t const star_value = (kernel_cs << 32) | (kernel_cs << 48); - kernel::cpu::write_msr(IA32_STAR_ADDRESS, star_value); - - kernel::cpu::set_efer_bit(kernel::cpu::efer_flags::SCE); - } -} // namespace teachos::arch::context_switching::syscall diff --git a/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp b/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp deleted file mode 100644 index 430d65c..0000000 --- a/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -namespace teachos::arch::context_switching::syscall -{ - - namespace - { - auto write_to_vga_buffer(uint64_t buffer) -> response - { - video::vga::text::write(reinterpret_cast(buffer), - video::vga::text::common_attributes::green_on_black); - video::vga::text::newline(); - return {error::OK}; - } - - auto expand_user_heap() -> response - { - auto static current_heap_end = memory::heap::USER_HEAP_START; - uint64_t const heap_start = current_heap_end; - memory::remap_heap(heap_start, memory::heap::USER_HEAP_SIZE, memory::paging::entry::USER_ACCESSIBLE); - current_heap_end += memory::heap::USER_HEAP_SIZE; - return { - error::OK, - {heap_start, memory::heap::USER_HEAP_SIZE} - }; - } - } // namespace - - auto syscall_handler() -> void - { - // Saving state of rcx and r11 because it is required by sysretq to function. - // Calls to other functions potentially overwrite these registers, because of - // callee saved calling convention. - uint64_t return_instruction_pointer, rflags = {}; - asm volatile("mov %%rcx, %[output]" : [output] "=m"(return_instruction_pointer)); - asm volatile("mov %%r11, %[output]" : [output] "=m"(rflags)); - - uint64_t syscall_number, arg_0, arg_1, arg_2, arg_3, arg_4, arg_5 = {}; - asm volatile("mov %%rdi, %[output]" : [output] "=m"(arg_0)); - asm volatile("mov %%rsi, %[output]" : [output] "=m"(arg_1)); - asm volatile("mov %%rdx, %[output]" : [output] "=m"(arg_2)); - asm volatile("mov %%r10, %[output]" : [output] "=m"(arg_3)); - asm volatile("mov %%r8, %[output]" : [output] "=m"(arg_4)); - asm volatile("mov %%r9, %[output]" : [output] "=m"(arg_5)); - - // RAX is read last, because paired with our type enum, we can use it to check - // if the register has been written by the compiled code between executing the syscall - // and now. - asm volatile("mov %%rax, %[output]" : [output] "=m"(syscall_number)); - - response result; - switch (static_cast(syscall_number)) - { - case type::WRITE: - result = write_to_vga_buffer(arg_0); - break; - case type::EXPAND_HEAP: - result = expand_user_heap(); - break; - case type::ASSERT: - teachos::arch::exception_handling::assert(arg_0, reinterpret_cast(arg_1)); - break; - default: - teachos::arch::exception_handling::panic("[Syscall Handler] Invalid syscall number"); - break; - } - - asm volatile("mov %[input], %%rax" - : /* no output from call */ - : [input] "m"(result.error_code) - : "memory"); - - asm volatile("mov %[input], %%rdi" - : /* no output from call */ - : [input] "m"(result.values.arg_0) - : "memory"); - asm volatile("mov %[input], %%rsi" - : /* no output from call */ - : [input] "m"(result.values.arg_1) - : "memory"); - asm volatile("mov %[input], %%rdx" - : /* no output from call */ - : [input] "m"(result.values.arg_2) - : "memory"); - asm volatile("mov %[input], %%r10" - : /* no output from call */ - : [input] "m"(result.values.arg_3) - : "memory"); - asm volatile("mov %[input], %%r8" - : /* no output from call */ - : [input] "m"(result.values.arg_4) - : "memory"); - asm volatile("mov %[input], %%r9" - : /* no output from call */ - : [input] "m"(result.values.arg_5) - : "memory"); - - asm volatile("mov %[input], %%rcx" - : /* no output from call */ - : [input] "m"(return_instruction_pointer) - : "memory"); - asm volatile("mov %[input], %%r11" - : /* no output from call */ - : [input] "m"(rflags) - : "memory"); - - // Additionally call leave, because x86 allocates stack space for the internal variables. If we do not clean up this - // newly created stack frame the syscall instruction that landed in this syscall_handler, will never return to the - // method that originally called it, because the RIP has not been restored from the previous stack frame. - asm volatile("leave\n" - "sysretq"); - } - -} // namespace teachos::arch::context_switching::syscall diff --git a/arch/x86_64/pre/src/kernel/main.cpp b/arch/x86_64/pre/src/kernel/main.cpp deleted file mode 100644 index 2658678..0000000 --- a/arch/x86_64/pre/src/kernel/main.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace teachos::arch::kernel -{ - auto stack_overflow_test(int count) -> int - { - int test[5000] = {}; - if (test[0] == 0xFFFF) - { - return count; - } - count = stack_overflow_test(count); - return count++; - } - - auto heap_test() -> void - { - auto test2 = new memory::multiboot::memory_information{}; - auto test3 = new memory::multiboot::memory_information{}; - auto test4 = *test2; - auto test5 = *test3; - test4.kernel_end = 5000; - test5.kernel_end = 3000; - auto test6 = test4.kernel_end; - auto test7 = test5.kernel_end; - auto test8 = memory::multiboot::read_multiboot2(); - if (test6 && test7 && test8.kernel_end) - { - video::vga::text::write("Heap test successful", video::vga::text::common_attributes::green_on_black); - } - test2->kernel_end = 2000; - test2->kernel_start = 1000; - test2->multiboot_start = 2000; - delete test2; - delete test3; - - auto test9 = new int(50); - delete test9; - } - - auto main() -> void - { - video::vga::text::clear(); - video::vga::text::cursor(false); - video::vga::text::write("TeachOS is starting up...", video::vga::text::common_attributes::green_on_black); - video::vga::text::newline(); - - memory::initialize_memory_management(); - // stack_overflow_test(0); - - memory::heap::global_heap_allocator::register_heap_allocator(memory::heap::heap_allocator_type::LINKED_LIST); - // heap_test(); - - auto address = memory::heap::global_heap_allocator::kmalloc(8U); - (void)address; - - context_switching::switch_to_user_mode(); - } -} // namespace teachos::arch::kernel diff --git a/arch/x86_64/pre/src/user/main.cpp b/arch/x86_64/pre/src/user/main.cpp deleted file mode 100644 index 10a17db..0000000 --- a/arch/x86_64/pre/src/user/main.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include - -#include -#include - -#include -#include -#include -#include - -namespace teachos::arch::user -{ - auto main() -> void - { - constexpr char syscall_message[] = "Successfully entered user mode and wrote to VGA buffer via syscall!"; - context_switching::syscall::syscall(context_switching::syscall::type::WRITE, - {reinterpret_cast(&syscall_message)}); - - // Test C++ standard library - std::array, 4> array_test = {std::atomic{5}, std::atomic{10}, - std::atomic{15}, std::atomic{20}}; - std::ranges::for_each(array_test, [](auto & item) { - auto value = item.load(); - uint8_t max_value = std::max(value, uint8_t{10}); - item.exchange(max_value + 2); - }); - - auto address = new uint64_t{10U}; - (void)address; - - for (;;) - { - } - } -} // namespace teachos::arch::user diff --git a/arch/x86_64/src/boot/boot32.S b/arch/x86_64/src/boot/boot32.S deleted file mode 100644 index e6fcd85..0000000 --- a/arch/x86_64/src/boot/boot32.S +++ /dev/null @@ -1,443 +0,0 @@ -#include - -/** - * @brief Uninitialized data for the bootstrapping process. - */ -.section .boot_bss, "aw", @nobits - -page_maps_start: -page_map_level_4: .skip 512 * 8 -page_map_level_3_high: .skip 512 * 8 -page_map_level_3_low: .skip 512 * 8 -page_map_level_2: .skip 512 * 8 -page_maps_end = . -page_maps_size = page_maps_end - page_maps_start - -/** - * @brief Storage for the multiboot2 information pointer. - */ -.global multiboot_information_pointer -multiboot_information_pointer: .skip 8 - -/** - * @brief Storage for the bootstrap stack. - */ -.section .boot_stack, "aw", @nobits -.align 16 - -early_stack_bottom: .skip 1 << 8 -early_stack_top: -early_stack_size = early_stack_top - early_stack_bottom - -/** - * @brief Constants for the bootstrapping process. - */ -.section .boot_rodata, "a", @progbits - -.global global_descriptor_table_data - -/** - * @brief A basic GDT for long mode. - */ -global_descriptor_table: -global_descriptor_table_null = . - global_descriptor_table -.quad 0 -global_descriptor_table_code = . - global_descriptor_table -.quad GDT_READ_WRITE | GDT_EXECUTABLE | GDT_DESCRIPTOR_TYPE | GDT_PRESENT | GDT_LONG_MODE | (1 << 55) -global_descriptor_table_data = . - global_descriptor_table -.quad GDT_READ_WRITE | GDT_DESCRIPTOR_TYPE | GDT_PRESENT | (1 << 54) | (1 << 55) -global_descriptor_table_end: - -message_prefix_panic: .string "Panic: " -message_not_loaded_by_multiboot2: .string "The operating system was not loaded by a Multiboot 2 loader." -message_cpuid_instruction_no_supported: .string "The 'cpuid' instruction is not supported on this platform." -message_long_mode_not_supported: .string "Long mode is not supported by this platform." -message_return_from_kernel_main: .string "Execution returned from kernel main." - -/** - * @brief Initialized data for the bootstrapping process. - */ -.section .boot_data, "aw", @progbits - -/** - * @brief A pointer to the current position within the VGA text buffer. - */ -.global vga_buffer_pointer -vga_buffer_pointer: .quad 0xb8000 - -/** - * @brief Code for the bootstrapping process. - */ -.section .boot_text, "ax", @progbits -.align 16 -.code32 - -.macro pie_base - push %esi - call 0f - 0: - pop %esi -.endm - -.macro function_start - push %ebp - mov %esp, %ebp -.endm - -.macro function_end - leave - ret -.endm - -.macro pie_function_start - function_start - pie_base -.endm - -.macro pie_function_end - pop %esi - function_end -.endm - -/** - * @brief Prepare the environment and start the kernel. - * - * This function performs all necessary checks to ensure the system was loaded - * by the expected loader and supports all features required to run the kernel. - * If successful, it prepares the system by setting up memory virtualization - * and then start the kernel proper. - * - * @param %eax The Multiboot 2 magic marker. - * @param %ebx The Multiboot 2 information pointer. - * @return void This function does not return. - */ -.global _start -_start: - call 0f -0: - pop %esi - - lea (early_stack_top - 0b)(%esi), %ecx - - mov %ecx, %esp - mov %esp, %ebp - - call _assert_loaded_by_multiboot2_loader - call _save_multiboot_information_pointer - - call _assert_cpuid_instruction_is_supported - call _assert_cpu_supports_long_mode - - push $HUGE_PAGES_TO_MAP - call _prepare_page_maps - add $4, %esp - - call _enable_paging - call _enable_sse - call _reload_gdt - - lea (_entry64 - 0b)(%esi), %eax - pushl $global_descriptor_table_code - pushl %eax - lret - -/** - * @brief Halt the system. - * - * This function will instruct the CPU to halt. It will try to keep the CPU - * halted, even if interrupts occur. - * - * @return This function never returns. - */ -_halt: - function_start - -1: - hlt - jmp 1b - - function_end - -/** - * @brief Print a message via the VGA text buffer. - * - * @param ebp+12 The message to print. - * @param ebp+8 The color to print the message in. - */ -_print: - pie_function_start - - push %edi - push %ebx - - mov 8(%ebp), %al - mov 12(%ebp), %edx - mov $0, %ecx - lea (vga_buffer_pointer - 0b)(%esi), %edi - mov (%edi), %edi - -1: - mov (%edx, %ecx), %bl - test %bl, %bl - je 2f - mov %bl, (%edi, %ecx, 2) - mov %al, 1(%edi, %ecx, 2) - inc %ecx - jmp 1b - -2: - shl $1, %ecx - add %ecx, %edi - lea (vga_buffer_pointer - 0b)(%esi), %ecx - mov %edi, (%ecx) - - pop %ebx - pop %edi - - pie_function_end - -/** - * @brief Print a given panic message and then halt the machine as if by calling ::halt() - * - * @param ebp+4 A message to print. - * @return This function does not return. - */ -_panic: - pie_function_start - - lea (message_prefix_panic - 0b)(%esi), %eax - - push %eax - push $0x4f - call _print - - mov 8(%ebp), %eax - mov %eax, 4(%esp) - call _print - add $8, %esp - - call _halt - - pie_function_end - -/** - * Assert that we were loaded by a Multiboot 2 compliant bootloader. - * - * This assertion will panic the system if the magic signature was not found. - * If we were loaded my an appropriate bootloader, this function also saves - * the provided MBI pointer to `multiboot_information_pointer`. - */ -_assert_loaded_by_multiboot2_loader: - pie_function_start - - cmp $MULTIBOOT2_MAGIC, %eax - je 1f - lea (message_not_loaded_by_multiboot2 - 0b)(%esi), %eax - push %eax - call _panic -1: - pie_function_end - -/** - * @brief Store the multiboot 2 information pointer in the global memory. - * - * @return void - */ -_save_multiboot_information_pointer: - pie_function_start - - lea (multiboot_information_pointer - 0b)(%esi), %eax - mov %ebx, (%eax) - - pie_function_end - -/** - * @brief Assert that the CPU supports the CPUID instruction. - * - * The primary way to check for support of the instruction is to flip the ID - * bin in EFLAGS and then check if this changed was accepted. If so, the CPU - * supports the CPUID instruction, otherwise it most-likely doesn't. - */ -_assert_cpuid_instruction_is_supported: - pie_function_start - - pushfl - pop %eax - mov %eax, %ecx - - xor $(1 << 21), %eax /* Flip the ID bit */ - push %eax /* Move the new bitset on the stack for loading */ - popfl /* Load the flags with ID set back into EFLAGS */ - pushfl /* Copy the flags back onto the stack */ - pop %eax /* Load the flags for further checking */ - - push %ecx - popfl - - cmp %ecx, %eax - jne 1f - lea (message_cpuid_instruction_no_supported - 0b)(%esi), %eax - push %eax - call _panic - -1: - pie_function_end - -/** - * @brief Assert that the CPU supports going into long mode. - */ -_assert_cpu_supports_long_mode: - pie_function_start - - mov $0x80000000, %eax - cpuid - cmp $0x80000001, %eax - jb 1f - - mov $0x80000001, %eax - cpuid - test $(1 << 29), %edx - jnz 2f -1: - lea (message_long_mode_not_supported - 0b)(%esi), %eax - push %eax - call _panic -2: - pie_function_end - -/** - * @brief Prepare a basic page map hierarchy - * - * @param ebp+8 The number of huge pages to map - * @return void - */ -_prepare_page_maps: - pie_function_start - - push %edi - - call _clear_page_map_memory - - lea (page_map_level_4 - 0b)(%esi), %edi - lea (page_map_level_3_low - 0b)(%esi), %eax - or $0b11, %eax - mov %eax, (%edi) - - lea (page_map_level_3_high - 0b)(%esi), %eax - or $0b11, %eax - mov %eax, (511 * 8)(%edi) - - lea (page_map_level_3_low - 0b)(%esi), %edi - lea (page_map_level_2 - 0b)(%esi), %eax - or $0b11, %eax - mov %eax, (%edi) - - lea (page_map_level_3_high - 0b)(%esi), %edi - lea (page_map_level_2 - 0b)(%esi), %eax - or $0b11, %eax - mov %eax, (510 * 8)(%edi) - - lea (page_map_level_2 - 0b)(%esi), %edi - mov 8(%ebp), %ecx - -1: - dec %ecx - mov $(1 << 21), %eax - mul %ecx - or $0b10000011, %eax - mov %eax, (%edi, %ecx, 8) - - test %ecx, %ecx - jnz 1b - - pop %edi - - pie_function_end - -/** - * @brief Clear all page map memory by filling it with 0s. - * - * @return void - */ -_clear_page_map_memory: - pie_function_start - - push %edi - - xor %eax, %eax - mov $page_maps_size, %ecx - shr $2, %ecx - lea (page_maps_start - 0b)(%esi), %edi - rep stosl - - pop %edi - - pie_function_end - -/** - * @p Enable memory virtualization via paging. - * - * Note: This routine expects for there to be a valid set of page maps already - * set up for use. - * - * @return void - */ -_enable_paging: - pie_function_start - - lea (page_map_level_4 - 0b)(%esi), %eax - mov %eax, %cr3 - - /* Enable Physical Address Extension */ - mov %cr4, %eax - or $(1 << 5), %eax - mov %eax, %cr4 - - /* Enable long mode support */ - mov $0xC0000080, %ecx - rdmsr - or $(1 << 8), %eax - wrmsr - - /* Enable paging */ - mov %cr0, %eax - or $(1 << 31), %eax - mov %eax, %cr0 - - pie_function_end - -/** - * @brief Enable use of SSE instructions. - */ -_enable_sse: - function_start - - mov %cr0, %eax - and $0xfffffffb, %eax - or $0x00000002, %eax - mov %eax, %cr0 - - mov %cr4, %eax - or $(3 << 9), %eax - mov %eax, %cr4 - - function_end - -/** - * @brief Prepare a new GTD and load make it active. - * - * @return void - */ -_reload_gdt: - pie_function_start - - sub $10, %esp - lea (global_descriptor_table - 0b)(%esi), %eax - movw $(global_descriptor_table_end - global_descriptor_table -1), (%esp) - mov %eax, 2(%esp) - movl $0, 6(%esp) - - lgdt (%esp) - add $10, %esp - - pie_function_end diff --git a/arch/x86_64/src/boot/entry64.s b/arch/x86_64/src/boot/entry64.s deleted file mode 100644 index 29fb778..0000000 --- a/arch/x86_64/src/boot/entry64.s +++ /dev/null @@ -1,62 +0,0 @@ -.section .stack, "aw", @nobits - -.align 16 -.global stack_top -stack_bottom: .skip 1 << 20 -stack_top: -stack_size = stack_top - stack_bottom - -.section .bss, "aw", @nobits - -//! A structure containing information gathered during the bootstrap process. -//! Expected layout (as described by teachos::boot::information): -//! -//! struct -//! { -//! multiboot2::information_view const * mbi; -//! std::size_t vga_buffer_index; -//! } -.global bootstrap_information -bootstrap_information: .skip 16 - -.section .boot_text, "ax", @progbits -.code64 - -.global _entry64 -_entry64: - mov $global_descriptor_table_data, %rax - mov %rax, %ss - mov %rax, %ds - mov %rax, %es - mov %rax, %fs - mov %rax, %gs - - mov $stack_top, %rsp - mov %rsp, %rbp - - mov multiboot_information_pointer, %rax - or $TEACHOS_VMA, %rax - mov vga_buffer_pointer, %rdx - sub $0xb8000, %rdx - mov %rax, (bootstrap_information) - mov %rdx, (bootstrap_information + 8) - - call invoke_global_constructors - - xor %rax, %rax - mov %rax, %rbp - mov %rax, %rdx - mov %rax, %rsi - - mov $stack_size, %rcx - shr $3, %rcx - lea (stack_bottom), %rdi - rep stosq - - mov %rax, %rdi - - call main - -1: - hlt - jmp 1b diff --git a/arch/x86_64/src/boot/initialize_runtime.cpp b/arch/x86_64/src/boot/initialize_runtime.cpp deleted file mode 100644 index b08c13c..0000000 --- a/arch/x86_64/src/boot/initialize_runtime.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include -#include - -namespace arch::boot -{ - - extern "C" - { - using global_initializer = auto (*)() -> void; - - extern global_initializer __init_array_start; - extern global_initializer __init_array_end; - - auto invoke_global_constructors() -> void - { - auto initializers = std::span{&__init_array_start, &__init_array_end}; - std::ranges::for_each(initializers, [](auto invokable) { std::invoke(invokable); }); - } - } - -} // namespace arch::boot \ No newline at end of file diff --git a/arch/x86_64/src/boot/multiboot.s b/arch/x86_64/src/boot/multiboot.s deleted file mode 100644 index 37d8afe..0000000 --- a/arch/x86_64/src/boot/multiboot.s +++ /dev/null @@ -1,33 +0,0 @@ -.section .boot_mbh, "a" -.align 8 - -multiboot_header_start: -.Lmagic: - .long 0xe85250d6 -.Larch: - .long 0 -.Llength: - .long multiboot_header_end - multiboot_header_start -.Lchecksum: - .long 0x100000000 - (0xe85250d6 + 0 + (multiboot_header_end - multiboot_header_start)) -.align 8 -.Lflags_start: - .word 4 - .word 1 - .long .Lflags_end - .Lflags_start - .long 3 -.Lflags_end: -.align 8 -.Linformation_request_start: - .word 1 - .word 0 - .long .Linformation_request_end - .Linformation_request_start - .long 3 -.Linformation_request_end: -.align 8 -.Lend_start: - .word 0 - .word 0 - .long .Lend_end - .Lend_start -.Lend_end: -multiboot_header_end: diff --git a/arch/x86_64/src/bus/isa.cpp b/arch/x86_64/src/bus/isa.cpp deleted file mode 100644 index f6cc72d..0000000 --- a/arch/x86_64/src/bus/isa.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include - -#include - -#include - -namespace arch::bus -{ - - isa::isa(std::size_t major) - : kapi::devices::bus{major, 0, "isa"} - {} - -} // namespace arch::bus \ No newline at end of file diff --git a/arch/x86_64/src/cpu/initialization.cpp b/arch/x86_64/src/cpu/initialization.cpp deleted file mode 100644 index 1be9c82..0000000 --- a/arch/x86_64/src/cpu/initialization.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace arch::cpu -{ - - namespace - { - constexpr auto gdt_null_descriptor = segment_descriptor{}; - - constexpr auto gdt_kernel_code_descriptor = segment_descriptor{ - .limit_low = 0xffff, - .base_low = 0, - .accessed = false, - .read_write = false, - .direction_or_conforming = false, - .executable = true, - .type = segment_type::code_or_data, - .privilege_level = 0, - .present = true, - .limit_high = 0xf, - .long_mode = true, - .protected_mode = false, - .granularity = segment_granularity::page, - .base_high = 0, - }; - - constexpr auto gdt_kernel_data_descriptor = segment_descriptor{ - .limit_low = 0xffff, - .base_low = 0, - .accessed = false, - .read_write = true, - .direction_or_conforming = false, - .executable = false, - .type = segment_type::code_or_data, - .privilege_level = 0, - .present = true, - .limit_high = 0xf, - .long_mode = false, - .protected_mode = true, - .granularity = segment_granularity::page, - .base_high = 0, - }; - - constexpr auto gdt_user_code_descriptor = segment_descriptor{ - .limit_low = 0xffff, - .base_low = 0, - .accessed = false, - .read_write = false, - .direction_or_conforming = false, - .executable = true, - .type = segment_type::code_or_data, - .privilege_level = 3, - .present = true, - .limit_high = 0xf, - .long_mode = true, - .protected_mode = false, - .granularity = segment_granularity::page, - .base_high = 0, - }; - - constexpr auto gdt_user_data_descriptor = segment_descriptor{ - .limit_low = 0xffff, - .base_low = 0, - .accessed = false, - .read_write = true, - .direction_or_conforming = false, - .executable = false, - .type = segment_type::code_or_data, - .privilege_level = 3, - .present = true, - .limit_high = 0xf, - .long_mode = false, - .protected_mode = false, - .granularity = segment_granularity::page, - .base_high = 0, - }; - - constexpr auto make_tss_descriptor(task_state_segment const * tss_ptr) -> system_segment_descriptor - { - auto const address = std::bit_cast(tss_ptr); - auto const limit = sizeof(task_state_segment) - 1; - - return system_segment_descriptor{ - { - .limit_low = limit & 0xffff, // NOLINT(readability-magic-numbers) - .base_low = address & 0xffffff, // NOLINT(readability-magic-numbers) - .accessed = false, - .read_write = false, - .direction_or_conforming = false, - .executable = false, - .type = segment_type::system, - .privilege_level = 0, - .present = true, - .limit_high = (limit >> 16) & 0xf, // NOLINT(readability-magic-numbers) - .long_mode = false, - .protected_mode = false, - .granularity = segment_granularity::byte, - .base_high = (address >> 24) & 0xff, // NOLINT(readability-magic-numbers) - }, - (address >> 32) & 0xffff'ffff, // NOLINT(readability-magic-numbers) - }; - } - } // namespace - - auto initialize_descriptors() -> void - { - auto static tss = task_state_segment{}; - auto static tss_descriptor = make_tss_descriptor(&tss); - - auto static gdt = global_descriptor_table{ - gdt_null_descriptor, gdt_kernel_code_descriptor, gdt_kernel_data_descriptor, - gdt_user_code_descriptor, gdt_user_data_descriptor, tss_descriptor, - }; - - kstd::println("[x86_64:SYS] Reloading Global Descriptor Table."); - gdt.load(1, 2); - - kstd::println("[x86_64:SYS] Initializing Interrupt Descriptor Table."); - auto static idt = interrupt_descriptor_table{}; - idt.load(); - } - - auto initialize_legacy_interrupts() -> void - { - constexpr auto pic_init_command = std::uint8_t{0x11}; - constexpr auto pic_master_offset = std::uint8_t{0x20}; - constexpr auto pic_slave_offset = std::uint8_t{0x28}; - constexpr auto pic_cascade_address = std::uint8_t{0x04}; - constexpr auto pic_cascade_slave_identity = std::uint8_t{0x02}; - constexpr auto pic_use_8086_mode = std::uint8_t{0x01}; - constexpr auto pic_master_mask = std::uint8_t{0x00}; - constexpr auto pic_slave_mask = std::uint8_t{0x00}; - - pic_master_control_port::write(pic_init_command); - pic_slave_control_port::write(pic_init_command); - - pic_master_data_port::write(pic_master_offset); - pic_slave_data_port::write(pic_slave_offset); - - pic_master_data_port::write(pic_cascade_address); - pic_slave_data_port::write(pic_cascade_slave_identity); - - pic_master_data_port::write(pic_use_8086_mode); - pic_slave_data_port::write(pic_use_8086_mode); - - pic_master_data_port::write(pic_master_mask); - pic_slave_data_port::write(pic_slave_mask); - - pic_master_data_port::write(pic_master_mask); - pic_slave_data_port::write(pic_slave_mask); - } - -} // namespace arch::cpu diff --git a/arch/x86_64/src/cpu/interrupt_stubs.S b/arch/x86_64/src/cpu/interrupt_stubs.S deleted file mode 100644 index e59bdd2..0000000 --- a/arch/x86_64/src/cpu/interrupt_stubs.S +++ /dev/null @@ -1,112 +0,0 @@ -.altmacro - -.macro ISR_WITHOUT_ERROR_CODE vector - .global isr\vector - isr\vector: - pushq $0 - pushq $\vector - jmp common_interrupt_handler -.endm - -.macro ISR_WITH_ERROR_CODE vector - .global isr\vector - isr\vector: - pushq $\vector - jmp common_interrupt_handler -.endm - -.macro ISR_TABLE_ENTRY vector - .quad isr\vector -.endm - -.section .rodata -.global isr_stub_table -.align 16 - -isr_stub_table: -.set i, 0 -.rept 256 - ISR_TABLE_ENTRY %i - .set i, i + 1 -.endr - - -.section .text - -common_interrupt_handler: - push %rax - push %rbx - push %rcx - push %rdx - push %rbp - push %rsi - push %rdi - push %r8 - push %r9 - push %r10 - push %r11 - push %r12 - push %r13 - push %r14 - push %r15 - - mov %rsp, %rdi - call interrupt_dispatch - - pop %r15 - pop %r14 - pop %r13 - pop %r12 - pop %r11 - pop %r10 - pop %r9 - pop %r8 - pop %rdi - pop %rsi - pop %rbp - pop %rdx - pop %rcx - pop %rbx - pop %rax - - add $16, %rsp - iretq - -ISR_WITHOUT_ERROR_CODE 0 -ISR_WITHOUT_ERROR_CODE 1 -ISR_WITHOUT_ERROR_CODE 2 -ISR_WITHOUT_ERROR_CODE 3 -ISR_WITHOUT_ERROR_CODE 4 -ISR_WITHOUT_ERROR_CODE 5 -ISR_WITHOUT_ERROR_CODE 6 -ISR_WITHOUT_ERROR_CODE 7 -ISR_WITH_ERROR_CODE 8 -ISR_WITHOUT_ERROR_CODE 9 -ISR_WITH_ERROR_CODE 10 -ISR_WITH_ERROR_CODE 11 -ISR_WITH_ERROR_CODE 12 -ISR_WITH_ERROR_CODE 13 -ISR_WITH_ERROR_CODE 14 -ISR_WITHOUT_ERROR_CODE 15 -ISR_WITHOUT_ERROR_CODE 16 -ISR_WITH_ERROR_CODE 17 -ISR_WITHOUT_ERROR_CODE 18 -ISR_WITHOUT_ERROR_CODE 19 -ISR_WITHOUT_ERROR_CODE 20 -ISR_WITH_ERROR_CODE 21 -ISR_WITHOUT_ERROR_CODE 22 -ISR_WITHOUT_ERROR_CODE 23 -ISR_WITHOUT_ERROR_CODE 24 -ISR_WITHOUT_ERROR_CODE 25 -ISR_WITHOUT_ERROR_CODE 26 -ISR_WITHOUT_ERROR_CODE 27 -ISR_WITHOUT_ERROR_CODE 28 -ISR_WITH_ERROR_CODE 29 -ISR_WITH_ERROR_CODE 30 -ISR_WITHOUT_ERROR_CODE 31 - -.set i, 32 -.rept 256 - 32 - ISR_WITHOUT_ERROR_CODE %i - .set i, i + 1 -.endr diff --git a/arch/x86_64/src/cpu/interrupts.cpp b/arch/x86_64/src/cpu/interrupts.cpp deleted file mode 100644 index f40422f..0000000 --- a/arch/x86_64/src/cpu/interrupts.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include - -#include -#include - -#include -#include -#include - -#include - -#include - -namespace arch::cpu -{ - - namespace - { - enum struct exception - { - divide_error, - debug_exception, - non_maskable_interrupt, - breakpoint, - overflow, - bound_range_exceeded, - invalid_opcode, - device_not_available, - double_fault, - coprocessor_segment_overrun, - invalid_tss, - segment_not_present, - stack_segment_fault, - general_protection_fault, - page_fault, - x87_fpu_floating_point_error = 16, - alignment_check, - machine_check, - simd_floating_point_error, - virtualization_exception, - control_protection_exception, - hypervisor_injection_exception = 28, - vmm_communication_exception, - security_exception, - }; - - constexpr auto number_of_exception_vectors = 32u; - - constexpr auto pic_master_irq_start = 0x20; - constexpr auto pic_master_irq_end = pic_master_irq_start + 8; - constexpr auto pic_slave_irq_start = pic_master_irq_end; - - constexpr auto to_exception_type(exception e) - { - switch (e) - { - case exception::divide_error: - case exception::x87_fpu_floating_point_error: - case exception::simd_floating_point_error: - return kapi::cpu::exception::type::arithmetic_error; - case exception::breakpoint: - return kapi::cpu::exception::type::breakpoint; - case exception::invalid_opcode: - return kapi::cpu::exception::type::illegal_instruction; - case exception::stack_segment_fault: - return kapi::cpu::exception::type::memory_access_fault; - case exception::general_protection_fault: - return kapi::cpu::exception::type::privilege_violation; - case exception::page_fault: - return kapi::cpu::exception::type::page_fault; - case exception::alignment_check: - return kapi::cpu::exception::type::alignment_fault; - default: - return kapi::cpu::exception::type::unknown; - } - } - - constexpr auto has_error_code(exception e) - { - switch (e) - { - case exception::double_fault: - case exception::invalid_tss: - case exception::segment_not_present: - case exception::stack_segment_fault: - case exception::general_protection_fault: - case exception::page_fault: - case exception::alignment_check: - case exception::control_protection_exception: - case exception::security_exception: - return true; - default: - return false; - } - } - - auto dispatch_exception(interrupt_frame * frame) -> bool - { - auto type = to_exception_type(static_cast(frame->interrupt.number)); - auto fault_address = kapi::memory::linear_address{}; - auto instruction_pointer = frame->cpu_saved.rip; - - switch (type) - { - case kapi::cpu::exception::type::page_fault: - { - asm volatile("mov %%cr2, %0" : "=r"(fault_address)); - auto present = (frame->interrupt.error_code & 0x1) != 0; - auto write = (frame->interrupt.error_code & 0x2) != 0; - auto user = (frame->interrupt.error_code & 0x4) != 0; - - return kapi::cpu::dispatch({type, instruction_pointer, fault_address, present, write, user}); - } - default: - return kapi::cpu::dispatch({type, instruction_pointer}); - } - } - - auto acknowledge_pic_interrupt(interrupt_frame * frame) -> void - { - if (frame->interrupt.number >= pic_slave_irq_start) - { - pic_slave_control_port::write(pic_end_of_interrupt); - } - pic_master_control_port::write(pic_end_of_interrupt); - } - } // namespace - - extern "C" - { - extern std::uintptr_t const isr_stub_table[256]; - - auto interrupt_dispatch(interrupt_frame * frame) -> void - { - auto [number, code] = frame->interrupt; - - if (number < number_of_exception_vectors) - { - if (!dispatch_exception(frame)) - { - if (has_error_code(static_cast(number))) - { - kstd::println(kstd::print_sink::stderr, - "[x86_64:CPU] Unhandled exception number {:#04x} received with code {:#04x}", number, code); - } - else - { - kstd::println(kstd::print_sink::stderr, "[x86_64:CPU] Unhandled exception number {:#04x} received", number); - } - } - } - else - { - auto irq_number = number - number_of_exception_vectors; - - if (kapi::interrupts::dispatch(irq_number) == kapi::interrupts::status::unhandled) - { - kstd::println(kstd::print_sink::stderr, "[x86_64:CPU] Unhandled interrupt {:#04x} (IRQ{})", number, - irq_number); - } - - acknowledge_pic_interrupt(frame); - } - } - } - - interrupt_descriptor_table::interrupt_descriptor_table() noexcept - { - for (auto i = 0uz; i < 256; ++i) - { - m_descriptors[i] = gate_descriptor{ - .offset_low = static_cast(isr_stub_table[i] & 0xffff), // NOLINT(readability-magic-numbers) - .m_code_segment = segment_selector{0, false, 1}, - .interrupt_stack_table_selector = 0, - .gate_type = (i < 32 && i != 2) ? gate_type::trap_gate : gate_type::interrupt_gate, - .descriptor_privilege_level = 0, - .present = true, - .offset_middle = - static_cast((isr_stub_table[i] >> 16) & 0xffff), // NOLINT(readability-magic-numbers) - .offset_high = - static_cast((isr_stub_table[i] >> 32) & 0xffff'ffff), // NOLINT(readability-magic-numbers) - }; - } - } - - auto interrupt_descriptor_table::load() const -> void - { - interrupt_descriptor_table_register{.limit = sizeof(m_descriptors) - 1, .base = m_descriptors.data()}.load(); - } - - auto interrupt_descriptor_table_register::load() const -> void - { - asm volatile("lidt %0" : : "m"(*this)); - } - - auto interrupt_descriptor_table_register::read() -> interrupt_descriptor_table_register - { - interrupt_descriptor_table_register idtr{}; - asm volatile("sidt %0" : : "m"(idtr)); - return idtr; - } - -} // namespace arch::cpu \ No newline at end of file diff --git a/arch/x86_64/src/debug/qemu_output.cpp b/arch/x86_64/src/debug/qemu_output.cpp deleted file mode 100644 index 71acede..0000000 --- a/arch/x86_64/src/debug/qemu_output.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -#include - -#include -#include - -namespace arch::debug -{ - - qemu_output::qemu_output(output_device & lower) - : m_lower{lower} - , m_present{port::read() == port::address} - {} - - auto qemu_output::write(kapi::cio::output_stream stream, std::string_view text) -> void - { - if (m_present) - { - std::ranges::for_each(text, port::write); - } - m_lower.write(stream, text); - } - -} // namespace arch::debug diff --git a/arch/x86_64/src/devices/init.cpp b/arch/x86_64/src/devices/init.cpp deleted file mode 100644 index c30e8cf..0000000 --- a/arch/x86_64/src/devices/init.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -#include -#include - -namespace arch::devices -{ - - namespace - { - constexpr auto pit_frequency_in_hz = std::uint32_t{100u}; - - auto get_acpi_root_pointer() -> kstd::observer_ptr<::acpi::rsdp const> - { - auto const & mbi = kapi::boot::bootstrap_information.mbi; - auto system_description_pointer = static_cast<::acpi::rsdp const *>(nullptr); - - if (auto const & xsdp = mbi->maybe_acpi_xsdp()) - { - auto data = xsdp->pointer().data(); - - system_description_pointer = reinterpret_cast<::acpi::xsdp const *>(data); - } - else if (auto const & rsdp = mbi->maybe_acpi_rsdp()) - { - auto data = rsdp->pointer().data(); - system_description_pointer = reinterpret_cast<::acpi::rsdp const *>(data); - } - - return kstd::make_observer(system_description_pointer); - } - } // namespace - - auto init_acpi_devices() -> void - { - auto acpi_root_pointer = get_acpi_root_pointer(); - if (acpi_root_pointer && acpi_root_pointer->validate()) - { - if (kapi::acpi::init(*acpi_root_pointer)) - { - kstd::println("[x86_64:DEV] ACPI subsystem initialized."); - } - } - - kapi::cpu::discover_topology(); - } - - auto init_legacy_devices() -> void - { - kstd::println("[x86_64:DEV] Initializing ISA bus..."); - - auto isa_major_number = kapi::devices::allocate_major_number(); - auto isa_bus = kstd::make_unique(isa_major_number); - - auto pit_major_number = kapi::devices::allocate_major_number(); - auto pit = kstd::make_unique(pit_major_number, pit_frequency_in_hz); - isa_bus->add_child(std::move(pit)); - - auto & root_bus = kapi::devices::get_root_bus(); - root_bus.add_child(std::move(isa_bus)); - } - -} // namespace arch::devices diff --git a/arch/x86_64/src/devices/legacy_pit.cpp b/arch/x86_64/src/devices/legacy_pit.cpp deleted file mode 100644 index d542d47..0000000 --- a/arch/x86_64/src/devices/legacy_pit.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include - -#include -#include -#include - -#include -#include - -namespace arch::devices -{ - - namespace - { - using command_port = io::port<0x43, std::uint8_t, io::port_write>; - using channel_0_port = io::port<0x40, std::uint8_t, io::port_write>; - using channel_1_port = io::port<0x41, std::uint8_t, io::port_write>; - using channel_2_port = io::port<0x42, std::uint8_t, io::port_write>; - - constexpr auto base_frequency = 1'193'182u; - constexpr auto square_wave_mode = 0x36; - } // namespace - - legacy_pit::legacy_pit(std::size_t major, std::uint32_t frequency_in_hz) - : kapi::devices::device{major, 0, "legacy_pit"} - , m_irq_number{0} - , m_frequency_in_hz{frequency_in_hz} - {} - - auto legacy_pit::init() -> bool - { - auto divisor = static_cast(base_frequency / m_frequency_in_hz); - - kapi::interrupts::register_handler(m_irq_number, *this); - - command_port::write(square_wave_mode); - io::wait(); - channel_0_port::write(divisor & 0xff); - io::wait(); - channel_0_port::write(divisor >> 8 & 0xff); - io::wait(); - - return true; - } - - auto legacy_pit::handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status - { - if (irq_number != m_irq_number) - { - return kapi::interrupts::status::unhandled; - } - - ++m_ticks; - - return kapi::interrupts::status::handled; - } - -} // namespace arch::devices \ No newline at end of file diff --git a/arch/x86_64/src/devices/local_apic.cpp b/arch/x86_64/src/devices/local_apic.cpp deleted file mode 100644 index 660921b..0000000 --- a/arch/x86_64/src/devices/local_apic.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include - -namespace arch::devices -{ - - namespace - { - constexpr auto lapic_enable_bit = 0x100u; - constexpr auto spurious_interrupt_vector = 0xFFu; - - constexpr auto offset_of_version = 0u; - constexpr auto offset_of_max_lvt_entry = 16u; - constexpr auto offset_of_eoi_suppression = 24u; - - } // namespace - - enum struct local_apic::registers : std::ptrdiff_t - { - id = 0x020, - version = 0x030, - task_priority = 0x080, - arbitration_priority = 0x090, - processor_priority = 0x0a0, - eoi = 0x0b0, - remote_read = 0x0c0, - logical_destination = 0x0d0, - destination_format = 0x0e0, - spurious_interrupt_vector = 0x0f0, - in_service_0 = 0x100, - in_service_1 = 0x110, - in_service_2 = 0x120, - in_service_3 = 0x130, - in_service_4 = 0x140, - in_service_5 = 0x150, - in_service_6 = 0x160, - in_service_7 = 0x170, - trigger_mode_0 = 0x180, - trigger_mode_1 = 0x190, - trigger_mode_2 = 0x1a0, - trigger_mode_3 = 0x1b0, - trigger_mode_4 = 0x1c0, - trigger_mode_5 = 0x1d0, - trigger_mode_6 = 0x1e0, - trigger_mode_7 = 0x1f0, - interrupt_request_0 = 0x200, - interrupt_request_1 = 0x210, - interrupt_request_2 = 0x220, - interrupt_request_3 = 0x230, - interrupt_request_4 = 0x240, - interrupt_request_5 = 0x250, - interrupt_request_6 = 0x260, - interrupt_request_7 = 0x270, - error_status = 0x280, - lvt_corrected_machine_check_interrupt = 0x2f0, - interrupt_command_0 = 0x300, - interrupt_command_1 = 0x310, - lvt_timer = 0x320, - lvt_thermal_sensors = 0x330, - lvt_performance_monitoring_counters = 0x340, - lvt_local_interrupt_0 = 0x350, - lvt_local_interrupt_1 = 0x360, - lvt_error = 0x370, - initial_count = 0x380, - current_count = 0x390, - divide_configuration = 0x3e0, - }; - - local_apic::local_apic(std::size_t major, std::size_t minor, std::uint64_t hardware_id, - kapi::memory::physical_address base, bool is_bsp) - : kapi::devices::device{major, minor, "lapic"} - , m_hardware_id{hardware_id} - , m_base{base} - , m_is_bsp{is_bsp} - {} - - auto local_apic::init() -> bool - { - auto static shared_virtual_base = kapi::memory::allocate_mmio_region(1); - auto static is_mapped = false; - - if (!is_mapped) - { - if (!kapi::memory::map_mmio_region(shared_virtual_base, m_base, kapi::memory::page_mapper::flags::writable)) - { - kstd::println("[x86_64:DEV] LAPIC {} MMIO mapping failed!", m_hardware_id); - return false; - } - is_mapped = true; - } - - m_mapped_region = shared_virtual_base; - - if (m_is_bsp) - { - auto raw_version = read_register(registers::version); - m_version = (raw_version >> offset_of_version) & 0xff; - m_highest_lvt_entry_index = (raw_version >> offset_of_max_lvt_entry) & 0xff; - m_supports_eoi_broadcast_suppression = (raw_version >> offset_of_eoi_suppression) & 0x1; - - write_register(registers::spurious_interrupt_vector, lapic_enable_bit | spurious_interrupt_vector); - - kstd::println("[x86_64:DEV] LAPIC initialized. version: {#x} | max_lvt_entry: {} | eoi_suppression: {:s}", - m_version, m_highest_lvt_entry_index, m_supports_eoi_broadcast_suppression); - } - else - { - kstd::println("[x86_64:DEV] LAPIC {} is not on the BSP, deferring initialization.", m_hardware_id); - } - - return true; - } - - auto local_apic::read_register(registers id) const -> std::uint32_t - { - auto reg = static_cast(m_mapped_region.first + std::to_underlying(id)); - return *reg; - } - - auto local_apic::write_register(registers id, std::uint32_t value) -> void - { - auto reg = static_cast(m_mapped_region.first + std::to_underlying(id)); - *reg = value; - } - -} // namespace arch::devices diff --git a/arch/x86_64/src/memory/higher_half_mapper.cpp b/arch/x86_64/src/memory/higher_half_mapper.cpp deleted file mode 100644 index 75adb3c..0000000 --- a/arch/x86_64/src/memory/higher_half_mapper.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace arch::memory -{ - - higher_half_mapper::higher_half_mapper(page_table * root) - : m_root{root} - {} - - auto higher_half_mapper::map(kapi::memory::page page, kapi::memory::frame frame, flags flags) -> std::byte * - { - auto table = get_or_create_page_table(page); - if (!table) - { - return nullptr; - } - - auto const index = pml_index(1, page); - auto & entry = (*table)[index]; - - if (entry.present()) - { - kapi::system::panic("[x86_64:MEM] Tried to map a page that is already mapped!"); - } - - entry.frame(frame, to_table_flags(flags) | page_table::entry::flags::present); - - return static_cast(page.start_address()); - } - - auto higher_half_mapper::unmap(kapi::memory::page page) -> void - { - if (!try_unmap(page)) - { - kapi::system::panic("[x86_64:MEM] Tried to unmap a page that is not mapped!"); - } - } - - auto higher_half_mapper::try_unmap(kapi::memory::page page) noexcept -> bool - { - auto table_path = std::array, 4>{}; - table_path[0] = std::pair{m_root, pml_index(4, page)}; - - for (auto level = 4uz; level > 1uz; --level) - { - auto [table, index] = table_path[4 - level]; - auto & entry = (*table)[index]; - - if (!entry.present()) - { - return false; - } - - auto next_table = to_higher_half_pointer(entry.frame()->start_address()); - auto next_index = pml_index(4 - level - 1, page); - table_path[4 - level - 1] = std::pair{next_table, next_index}; - } - - std::ranges::for_each(std::views::reverse(table_path), [previous_was_empty = true](auto & step) mutable { - auto [table, index] = step; - auto & entry = (*table)[index]; - auto frame = entry.frame(); - - if (previous_was_empty) - { - entry.clear(); - previous_was_empty = table->empty(); - kapi::memory::get_frame_allocator().release(*frame); - } - }); - - return true; - } - - auto higher_half_mapper::get_or_create_page_table(kapi::memory::page page) noexcept -> page_table * - { - auto table = m_root; - - for (auto level = 4uz; level > 1uz; --level) - { - auto index = pml_index(level, page); - auto & entry = (*table)[index]; - - if (!entry.present()) - { - auto table_frame = kapi::memory::allocate_frame(); - if (!table_frame) - { - return nullptr; - } - - auto new_table = to_higher_half_pointer(table_frame->start_address()); - std::construct_at(new_table); - - auto const flags = page_table::entry::flags::present | page_table::entry::flags::writable | - page_table::entry::flags::user_accessible; - entry.frame(*table_frame, flags); - } - - table = to_higher_half_pointer(entry.frame()->start_address()); - } - - return table; - } - -} // namespace arch::memory \ No newline at end of file diff --git a/arch/x86_64/src/memory/kernel_mapper.cpp b/arch/x86_64/src/memory/kernel_mapper.cpp deleted file mode 100644 index 74272a0..0000000 --- a/arch/x86_64/src/memory/kernel_mapper.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_view_literals; -using namespace kstd::units_literals; - -namespace arch::memory -{ - - namespace - { - constexpr auto static ignored_section_prefixes = std::array{ - ".boot_"sv, - }; - - constexpr auto static user_accessible_prefixes = std::array{ - ".user"sv, - ".stl"sv, - }; - - } // namespace - - kernel_mapper::kernel_mapper(multiboot2::information_view const * mbi) - : m_mbi{std::move(mbi)} - , m_kernel_load_base{std::bit_cast(&arch::boot::TEACHOS_VMA)} - {} - - auto kernel_mapper::remap_kernel(kapi::memory::page_mapper & mapper) -> void - { - auto elf_information = m_mbi->maybe_elf_symbols(); - if (!elf_information) - { - kapi::system::panic("[x86_64:MEM] ELF section information is not available."); - } - - auto sections = *elf_information; - auto allocated_sections = - std::views::all(sections) | std::views::filter(&elf::section_header::allocated) | - std::views::filter([&](auto const & section) -> auto { - auto name = sections.name(section); - return !std::ranges::any_of(ignored_section_prefixes, - [&](auto const & prefix) -> auto { return name.starts_with(prefix); }); - }); - - if (allocated_sections.empty()) - { - kapi::system::panic("[x86_64:MEM] No allocated ELF sections were found."); - } - - std::ranges::for_each(allocated_sections, - [&](auto const & section) -> auto { map_section(section, sections.name(section), mapper); }); - } - - auto kernel_mapper::map_section(section_header_type const & section, std::string_view name, - kapi::memory::page_mapper & mapper) -> void - { - auto number_of_pages = - (kstd::units::bytes{section.size} + (kapi::memory::page::size - 1_B)) / kapi::memory::page::size; - auto linear_start_address = kapi::memory::linear_address{section.virtual_load_address}; - auto physical_start_address = kapi::memory::physical_address{section.virtual_load_address & ~m_kernel_load_base}; - - kstd::println("[x86_64:MEM] mapping {}" - "\n {} bytes -> page count: {}" - "\n {} @ {}", - name, section.size, number_of_pages, linear_start_address, physical_start_address); - - auto first_page = kapi::memory::page::containing(linear_start_address); - auto first_frame = kapi::memory::frame::containing(physical_start_address); - - auto page_flags = kapi::memory::page_mapper::flags::empty; - - if (section.writable()) - { - page_flags |= kapi::memory::page_mapper::flags::writable; - } - - if (section.executable()) - { - page_flags |= kapi::memory::page_mapper::flags::executable; - } - - auto is_prefix_of_name = [=](auto prefix) -> bool { - return name.starts_with(prefix); - }; - - if (!std::ranges::any_of(user_accessible_prefixes, is_prefix_of_name)) - { - page_flags |= kapi::memory::page_mapper::flags::supervisor_only; - } - - for (auto i = 0uz; i < number_of_pages; ++i) - { - mapper.map(first_page + i, first_frame + i, page_flags); - } - } - -} // namespace arch::memory \ No newline at end of file diff --git a/arch/x86_64/src/memory/mmu.cpp b/arch/x86_64/src/memory/mmu.cpp deleted file mode 100644 index 2b53fa4..0000000 --- a/arch/x86_64/src/memory/mmu.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include - -#include - -namespace arch::memory -{ - auto tlb_flush(kapi::memory::linear_address address) -> void - { - asm volatile("invlpg (%[input])" : /* no output from call */ : [input] "r"(address) : "memory"); - } - - auto tlb_flush_all() -> void - { - auto paging_root = cpu::cr3::read(); - cpu::cr3::write(paging_root); - } -} // namespace arch::memory diff --git a/arch/x86_64/src/memory/page_table.cpp b/arch/x86_64/src/memory/page_table.cpp deleted file mode 100644 index 2180420..0000000 --- a/arch/x86_64/src/memory/page_table.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace arch::memory -{ - - auto page_table::entry::clear() noexcept -> void - { - m_raw = 0; - } - - auto page_table::entry::present() const noexcept -> bool - { - return (all_flags() & flags::present) != flags::empty; - } - - auto page_table::entry::huge() const noexcept -> bool - { - return (all_flags() & flags::huge_page) != flags::empty; - } - - auto page_table::entry::all_flags() const noexcept -> flags - { - return std::bit_cast(m_raw & ~frame_number_mask); - } - - auto page_table::entry::all_flags(flags flags) noexcept -> void - { - m_raw = (m_raw & ~frame_number_mask) | std::to_underlying(flags); - } - - auto page_table::entry::operator|=(flags rhs) noexcept -> page_table::entry & - { - auto raw_flags = std::to_underlying(rhs) & ~frame_number_mask; - m_raw |= raw_flags; - return *this; - } - - auto page_table::entry::frame() const noexcept -> std::optional - { - if (present()) - { - return kapi::memory::frame::containing(kapi::memory::physical_address{m_raw & frame_number_mask}); - } - return std::nullopt; - } - - auto page_table::entry::frame(kapi::memory::frame frame, flags flags) noexcept -> void - { - m_raw = (frame.start_address().raw() | static_cast(flags)); - }; - - auto page_table::operator[](std::size_t index) -> entry & - { - return m_entries.at(index); - } - - auto page_table::operator[](std::size_t index) const -> entry const & - { - return m_entries.at(index); - } - - auto page_table::clear() noexcept -> void - { - std::ranges::for_each(m_entries, &page_table::entry::clear); - } - - auto page_table::empty() const noexcept -> bool - { - return std::ranges::all_of(m_entries, - [](auto const & entry) -> auto { return entry.all_flags() == entry::flags::empty; }); - } - -} // namespace arch::memory diff --git a/arch/x86_64/src/memory/region_allocator.cpp b/arch/x86_64/src/memory/region_allocator.cpp deleted file mode 100644 index 4086a10..0000000 --- a/arch/x86_64/src/memory/region_allocator.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace arch::memory -{ - namespace - { - constexpr auto last_frame(multiboot2::memory_map::region const & region) - { - return kapi::memory::frame::containing(kapi::memory::physical_address{region.base + region.size_in_B - 1}); - } - - constexpr auto falls_within(kapi::memory::frame const & candidate, kapi::memory::frame const & start, - kapi::memory::frame const & end) - { - return candidate >= start && candidate <= end; - } - } // namespace - - region_allocator::region_allocator(memory_information const & mem_info) - : m_next_frame{} - , m_current_region{} - , m_memory_map{mem_info.memory_map} - , m_kernel_start{kapi::memory::frame::containing(mem_info.image_range.first)} - , m_kernel_end{kapi::memory::frame::containing(mem_info.image_range.second)} - , m_multiboot_start{kapi::memory::frame::containing(mem_info.mbi_range.first)} - , m_multiboot_end{kapi::memory::frame::containing(mem_info.mbi_range.second)} - , m_multiboot_information{mem_info.mbi} - { - choose_next_region(); - } - - auto region_allocator::choose_next_region() -> void - { - m_current_region.reset(); - - auto remaining_regions = - m_memory_map | // - std::views::filter(&multiboot2::memory_map::region::available) | - std::views::filter([this](auto const & region) -> bool { return last_frame(region) >= m_next_frame; }); - - auto lowest_region = - std::ranges::min_element(remaining_regions, [](auto lhs, auto rhs) -> bool { return lhs.base < rhs.base; }); - - if (lowest_region == remaining_regions.end()) - { - return; - } - - m_current_region = *lowest_region; - if (auto start_of_region = kapi::memory::frame::containing(kapi::memory::physical_address{m_current_region->base}); - start_of_region > m_next_frame) - { - m_next_frame = start_of_region; - } - } - - auto region_allocator::find_next_frame() -> std::optional - { - if (!m_current_region || m_next_frame > last_frame(*m_current_region)) - { - choose_next_region(); - if (!m_current_region) - { - return std::nullopt; - } - } - - auto advanced = true; - while (advanced) - { - advanced = false; - - if (falls_within(m_next_frame, m_kernel_start, m_kernel_end)) - { - m_next_frame = m_kernel_end + 1; - advanced = true; - continue; - } - - if (falls_within(m_next_frame, m_multiboot_start, m_multiboot_end)) - { - m_next_frame = m_multiboot_end + 1; - advanced = true; - continue; - } - - if (m_multiboot_information) - { - for (auto const & module : m_multiboot_information->modules()) - { - auto module_start = kapi::memory::frame::containing(kapi::memory::physical_address{module.start_address}); - auto module_end = kapi::memory::frame::containing(kapi::memory::physical_address{module.end_address}); - - if (falls_within(m_next_frame, module_start, module_end)) - { - m_next_frame = module_end + 1; - advanced = true; - break; - } - } - } - } - - if (m_next_frame > last_frame(*m_current_region)) - { - choose_next_region(); - } - - return m_current_region.transform([this](auto) -> auto { return m_next_frame; }); - } - - auto region_allocator::allocate_many(std::size_t count) noexcept - -> std::optional> - { - while (m_current_region) - { - auto result = find_next_frame(); - - if (result) - { - auto region_end = last_frame(*m_current_region); - if ((region_end.number() - result->number()) >= count) - { - m_next_frame = m_next_frame + count; - return std::make_pair(*result, count); - } - else - { - m_next_frame = region_end + 1; - choose_next_region(); - } - } - } - - return std::nullopt; - } - - auto region_allocator::mark_used(kapi::memory::frame frame) -> void - { - if (frame < m_next_frame) - { - m_next_frame = frame; - find_next_frame(); - } - } - - auto region_allocator::release_many(std::pair) -> void {} - - auto region_allocator::next_free_frame() noexcept -> std::optional - { - return find_next_frame(); - } - -} // namespace arch::memory diff --git a/arch/x86_64/src/vga/text/buffer.cpp b/arch/x86_64/src/vga/text/buffer.cpp deleted file mode 100644 index 498b9a3..0000000 --- a/arch/x86_64/src/vga/text/buffer.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace arch::vga::text -{ - buffer::buffer(std::size_t width, std::size_t height, cell * start, std::size_t position) - : m_width{width} - , m_height{height} - , m_buffer{start, m_width * m_height} - , m_position{position} - {} - - auto buffer::clear() -> void - { - m_position = 0; - std::ranges::fill(m_buffer, std::pair{'\0', static_cast(0x00)}); - } - - auto buffer::write(std::string_view code_points, attribute attribute) -> void - { - std::ranges::for_each(code_points, [&](auto code_point) -> void { write(code_point, attribute); }); - } - - auto buffer::write(char code_point, attribute attribute) -> void - { - if (m_position + 1 > m_height * m_width) - { - scroll(); - } - - if (!handle_special_code_point(code_point, attribute)) - { - do_write(code_point, attribute); - } - }; - - auto buffer::newline() -> void - { - auto free_glyphs_in_line = m_width - column(); - m_position += free_glyphs_in_line; - } - - auto buffer::scroll(std::size_t nof_lines) -> void - { - auto scroll_count = std::min(nof_lines, m_height); - - auto scroll_start = m_buffer.begin() + (scroll_count * m_width); - std::ranges::move(scroll_start, m_buffer.end(), m_buffer.begin()); - - auto clear_start = m_buffer.begin() + (m_height - scroll_count) * m_width; - std::ranges::fill(clear_start, m_buffer.end(), cell{}); - - m_position = (line() - scroll_count) * m_width; - } - - auto buffer::column() const noexcept -> std::ptrdiff_t - { - return m_position % m_width; - } - - auto buffer::line() const noexcept -> std::ptrdiff_t - { - return m_position / m_width; - } - auto buffer::handle_special_code_point(char code_point, attribute attribute) -> bool - { - switch (code_point) - { - case '\n': - newline(); - return true; - case '\r': - m_position -= column(); - return true; - case '\t': - do_write(" ", attribute); - return true; - default: - return false; - } - } - - auto buffer::do_write(std::string_view code_points, attribute attribute) -> void - { - std::ranges::for_each(code_points, [&](auto code_point) -> void { do_write(code_point, attribute); }); - } - - auto buffer::do_write(char code_point, attribute attribute) -> void - { - m_buffer[m_position++] = std::pair{code_point, std::bit_cast(attribute)}; - } - -} // namespace arch::vga::text diff --git a/arch/x86_64/src/vga/text/device.cpp b/arch/x86_64/src/vga/text/device.cpp deleted file mode 100644 index 8468358..0000000 --- a/arch/x86_64/src/vga/text/device.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -namespace arch::vga::text -{ - namespace - { - constexpr auto default_buffer_address = std::uintptr_t{0xb8000}; - constexpr auto default_buffer_width = 80z; - constexpr auto default_buffer_height = 25z; - - constexpr auto bit_cursor_enabled = 5U; - } // namespace - - device::device() - : m_buffer{ - default_buffer_width, default_buffer_height, - std::bit_cast(default_buffer_address + std::bit_cast(&boot::TEACHOS_VMA)), - kapi::boot::bootstrap_information.vga_buffer_index} - { - clear(); - } - - auto device::clear() -> void - { - m_buffer.clear(); - } - - auto device::cursor(bool enabled) -> void - { - auto cursor_disable_byte = std::byte{!enabled} << bit_cursor_enabled; - - crtc::address::write(crtc::registers::cursor_start); - crtc::data::write(crtc::data::read() | cursor_disable_byte); - } - - auto device::write(kapi::cio::output_stream stream, std::string_view text) -> void - { - auto attributes = [&] -> attribute { - switch (stream) - { - case kapi::cio::output_stream::stderr: - return red_on_black; - default: - return green_on_black; - } - }(); - m_buffer.write(text, attributes); - } - -} // namespace arch::vga::text -- cgit v1.2.3