diff options
Diffstat (limited to 'arch/x86_64/pre/src')
35 files changed, 1619 insertions, 0 deletions
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp new file mode 100644 index 0000000..28f289c --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp @@ -0,0 +1,24 @@ +#include "arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp" + +namespace teachos::arch::context_switching::interrupt_descriptor_table +{ + gate_descriptor::gate_descriptor(uint128_t flags) + : _offset_1(flags) + , _selector(flags >> 19U, flags >> 16U) + , _ist(flags >> 32U) + , _flags(flags >> 40U) + , _offset_2(flags >> 48U) + { + // Nothing to do. + } + + gate_descriptor::gate_descriptor(segment_selector selector, ist_offset ist, idt_flags flags, uint64_t offset) + : _offset_1(offset) + , _selector(selector) + , _ist(ist) + , _flags(flags) + , _offset_2(offset >> 16U) + { + // Nothing to do. + } +} // namespace teachos::arch::context_switching::interrupt_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp new file mode 100644 index 0000000..f3b9d5e --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp @@ -0,0 +1,20 @@ +#include "arch/context_switching/interrupt_descriptor_table/idt_flags.hpp" + +namespace teachos::arch::context_switching::interrupt_descriptor_table +{ + idt_flags::idt_flags(uint8_t flags) + : _flags(flags) + { + // Nothing to do. + } + + auto idt_flags::contains_flags(std::bitset<8U> other) const -> bool + { + return (std::bitset<8U>{_flags} & other) == other; + } + + auto idt_flags::operator|=(std::bitset<8U> other) -> void + { + _flags |= other.to_ulong(); + } +} // namespace teachos::arch::context_switching::interrupt_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp new file mode 100644 index 0000000..8640385 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp @@ -0,0 +1,53 @@ +#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp" + +#include "arch/exception_handling/assert.hpp" +#include "arch/interrupt_handling/generic_interrupt_handler.hpp" +#include "arch/kernel/cpu/idtr.hpp" + +namespace teachos::arch::context_switching::interrupt_descriptor_table +{ + namespace + { + /// @brief Amount of currently reserved interrupt indicies. + /// See https://wiki.osdev.org/Interrupt_Descriptor_Table#IDT_items for more information. + constexpr uint16_t RESERVED_INTERRUPT_COUNT = 256U; + + auto create_interrupt_descriptor_table() -> interrupt_descriptor_table + { + interrupt_descriptor_table interrupt_descriptor_table{RESERVED_INTERRUPT_COUNT}; + + uint64_t offset = reinterpret_cast<uint64_t>(interrupt_handling::generic_interrupt_handler); + segment_selector selector{1U, segment_selector::REQUEST_LEVEL_KERNEL}; + ist_offset ist{0U}; + idt_flags flags{idt_flags::DESCRIPTOR_LEVEL_KERNEL | idt_flags::INTERRUPT_GATE | idt_flags::PRESENT}; + + for (std::size_t i = 0; i < interrupt_descriptor_table.size(); i++) + { + interrupt_descriptor_table.at(i) = {selector, ist, flags, offset}; + } + + return interrupt_descriptor_table; + } + } // namespace + + auto get_or_create_interrupt_descriptor_table() -> interrupt_descriptor_table & + { + // Interrupt Descriptor Table needs to be kept alive + interrupt_descriptor_table static idt = create_interrupt_descriptor_table(); + return idt; + } + + auto update_interrupt_descriptor_table_register() -> void + { + decltype(auto) idt = get_or_create_interrupt_descriptor_table(); + + interrupt_descriptor_table_pointer idt_pointer{static_cast<uint16_t>((idt.size() * sizeof(gate_descriptor)) - 1), + idt.data()}; + kernel::cpu::load_interrupt_descriptor_table(idt_pointer); + + auto const stored_gdt_pointer = kernel::cpu::store_interrupt_descriptor_table(); + arch::exception_handling::assert( + idt_pointer == stored_gdt_pointer, + "[Interrupt Descriptor Table] Loaded IDTR value is not the same as the stored value."); + } +} // namespace teachos::arch::context_switching::interrupt_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp new file mode 100644 index 0000000..7bcbae6 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp @@ -0,0 +1,13 @@ +#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp" + +namespace teachos::arch::context_switching::interrupt_descriptor_table +{ + interrupt_descriptor_table_pointer::interrupt_descriptor_table_pointer(uint16_t table_length, + gate_descriptor * address) + : table_length(table_length) + , address(address) + { + // Nothing to do. + } + +} // namespace teachos::arch::context_switching::interrupt_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp new file mode 100644 index 0000000..a70e75d --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp @@ -0,0 +1,10 @@ +#include "arch/context_switching/interrupt_descriptor_table/ist_offset.hpp" + +namespace teachos::arch::context_switching::interrupt_descriptor_table +{ + ist_offset::ist_offset(uint8_t index) + : _ist(index) + { + // Nothing to do. + } +} // namespace teachos::arch::context_switching::interrupt_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp new file mode 100644 index 0000000..25ba859 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp @@ -0,0 +1,24 @@ +#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp" + +namespace teachos::arch::context_switching::interrupt_descriptor_table +{ + auto segment_selector::contains_flags(std::bitset<3U> other) const -> bool + { + return (std::bitset<3U>{_flags} & other) == other; + } + + auto segment_selector::get_index() const -> uint16_t + { + return _index; + } + + auto segment_selector::operator|=(std::bitset<3U> other) -> void + { + _flags |= other.to_ulong(); + } + + segment_selector::operator uint16_t() const + { + return *reinterpret_cast<uint16_t const *>(this); + } +} // namespace teachos::arch::context_switching::interrupt_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/main.cpp b/arch/x86_64/pre/src/context_switching/main.cpp new file mode 100644 index 0000000..3eb6dae --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/main.cpp @@ -0,0 +1,66 @@ +#include "arch/context_switching/main.hpp" + +#include "arch/boot/pointers.hpp" +#include "arch/context_switching/syscall/syscall_enable.hpp" +#include "arch/kernel/cpu/call.hpp" +#include "arch/kernel/cpu/if.hpp" +#include "arch/kernel/cpu/segment_register.hpp" +#include "arch/kernel/cpu/tr.hpp" +#include "arch/user/main.hpp" + +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<uint64_t>(return_function)); + } +} // namespace teachos::arch::context_switching diff --git a/arch/x86_64/pre/src/context_switching/segment_descriptor_table/access_byte.cpp b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/access_byte.cpp new file mode 100644 index 0000000..fcc72cf --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/access_byte.cpp @@ -0,0 +1,20 @@ +#include "arch/context_switching/segment_descriptor_table/access_byte.hpp" + +namespace teachos::arch::context_switching::segment_descriptor_table +{ + access_byte::access_byte(uint8_t flags) + : _flags(flags) + { + // Nothing to do. + } + + auto access_byte::contains_flags(std::bitset<8U> other) const -> bool + { + return (std::bitset<8U>{_flags} & other) == other; + } + + auto access_byte::operator|=(std::bitset<8U> other) -> void + { + _flags |= other.to_ulong(); + } +} // namespace teachos::arch::context_switching::segment_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/segment_descriptor_table/gdt_flags.cpp b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/gdt_flags.cpp new file mode 100644 index 0000000..ad1366a --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/gdt_flags.cpp @@ -0,0 +1,26 @@ +#include "arch/context_switching/segment_descriptor_table/gdt_flags.hpp" + +namespace teachos::arch::context_switching::segment_descriptor_table +{ + gdt_flags::gdt_flags(uint8_t flags, std::bitset<20U> limit) + : _limit_2(limit.to_ulong() >> 16U) + , _flags(flags) + { + // Nothing to do. + } + + auto gdt_flags::contains_flags(std::bitset<4U> other) const -> bool + { + return (std::bitset<4U>{_flags} & other) == other; + } + + auto gdt_flags::get_limit() const -> std::bitset<4U> + { + return std::bitset<4U>{_limit_2}; + } + + auto gdt_flags::operator|=(std::bitset<4U> other) -> void + { + _flags |= other.to_ulong(); + } +} // namespace teachos::arch::context_switching::segment_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/segment_descriptor_table/global_descriptor_table.cpp b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/global_descriptor_table.cpp new file mode 100644 index 0000000..1c4729f --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/global_descriptor_table.cpp @@ -0,0 +1,109 @@ +#include "arch/context_switching/segment_descriptor_table/global_descriptor_table.hpp" + +#include "arch/context_switching/segment_descriptor_table/segment_descriptor_extension.hpp" +#include "arch/exception_handling/assert.hpp" +#include "arch/kernel/cpu/gdtr.hpp" +#include "arch/kernel/cpu/tr.hpp" + +namespace teachos::arch::context_switching::segment_descriptor_table +{ + namespace + { + auto create_segment_descriptor(segment_descriptor_type segment_descriptor_type, access_byte access_level) + -> segment_descriptor_base + { + constexpr uint64_t BASE = 0x0; + constexpr std::bitset<20U> LIMIT{0xFFFFF}; + gdt_flags flags{gdt_flags::GRANULARITY, LIMIT}; + + access_level |= access_byte::PRESENT | access_byte::CODE_OR_DATA_SEGMENT; + if (segment_descriptor_type == segment_descriptor_type::CODE_SEGMENT) + { + flags |= gdt_flags::LONG_MODE; + access_level |= access_byte::CODE_SEGMENT | access_byte::READABLE; + } + else if (segment_descriptor_type == segment_descriptor_type::DATA_SEGMENT) + { + access_level |= access_byte::WRITABLE; + } + + segment_descriptor_base const segment_descriptor_base{access_level, flags, BASE, LIMIT}; + return segment_descriptor_base; + } + + auto create_tss_descriptor(task_state_segment * tss) -> segment_descriptor_extension + { + constexpr uint64_t TSS_LIMIT = sizeof(task_state_segment) - 1; + access_byte const tss_access_byte{access_byte::PRESENT | access_byte::DESCRIPTOR_LEVEL_KERNEL | + access_byte::TASK_STATE_SEGMENT_AVAILABLE}; + gdt_flags const tss_gdt_flags{0U, TSS_LIMIT}; + segment_descriptor_extension const tss_descriptor{tss_access_byte, tss_gdt_flags, reinterpret_cast<uint64_t>(tss), + TSS_LIMIT}; + return tss_descriptor; + } + + auto create_gdt() -> global_descriptor_table + { + segment_descriptor_base const null_segment{0}; + segment_descriptor_base const kernel_code_segment = + create_segment_descriptor(segment_descriptor_type::CODE_SEGMENT, access_byte::DESCRIPTOR_LEVEL_KERNEL); + segment_descriptor_base const kernel_data_segment = + create_segment_descriptor(segment_descriptor_type::DATA_SEGMENT, access_byte::DESCRIPTOR_LEVEL_KERNEL); + segment_descriptor_base const user_code_segment = + create_segment_descriptor(segment_descriptor_type::CODE_SEGMENT, access_byte::DESCRIPTOR_LEVEL_USER); + segment_descriptor_base const user_data_segment = + create_segment_descriptor(segment_descriptor_type::DATA_SEGMENT, access_byte::DESCRIPTOR_LEVEL_USER); + + // Task State Segment needs to be kept alive + auto static tss = new task_state_segment(); + segment_descriptor_extension const tss_descriptor = create_tss_descriptor(tss); + + global_descriptor_table global_descriptor_table{null_segment, + kernel_code_segment, + kernel_data_segment, + user_code_segment, + user_data_segment, + tss_descriptor.get_first_gdt_entry(), + tss_descriptor.get_second_gdt_entry()}; + return global_descriptor_table; + } + } // namespace + + auto get_or_create_gdt() -> global_descriptor_table & + { + // Global Descriptor Table needs to be kept alive + global_descriptor_table static gdt = create_gdt(); + return gdt; + } + + auto update_gdtr() -> void + { + decltype(auto) gdt = get_or_create_gdt(); + + // Calculate the size of the gdt in bytes - 1. This subtraction occurs because the maximum value of Size is 65535, + // while the GDT can be up to 65536 bytes in length (8192 entries). Further, no GDT can have a size of 0 bytes. + uint16_t gdt_size = static_cast<uint16_t>((gdt.size() * sizeof(segment_descriptor_base)) - 1); + global_descriptor_table_pointer gdt_pointer{gdt_size, gdt.data()}; + kernel::cpu::load_global_descriptor_table(gdt_pointer); + + auto const stored_gdt_pointer = kernel::cpu::store_global_descriptor_table(); + arch::exception_handling::assert( + gdt_pointer == stored_gdt_pointer, + "[Global Descriptor Table] Loaded GDTR value is not the same as the stored value."); + } + + auto update_tss_register() -> void + { + decltype(auto) gdt = get_or_create_gdt(); + + // Load task state segment descriptor from the last element in the global descriptor table, done by calculating + // offset in bytes to the start of the segment descriptor (5 * 8) = 40 + uint16_t tss_selector = (gdt.size() * sizeof(segment_descriptor_base)) - sizeof(segment_descriptor_extension); + kernel::cpu::load_task_register(tss_selector); + + auto const stored_task_register = kernel::cpu::store_task_register(); + arch::exception_handling::assert(tss_selector == stored_task_register, + "[Global Descriptor Table] Loaded TR value is not the same as the stored value."); + } + +} // namespace teachos::arch::context_switching::segment_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/segment_descriptor_table/global_descriptor_table_pointer.cpp b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/global_descriptor_table_pointer.cpp new file mode 100644 index 0000000..79088b8 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/global_descriptor_table_pointer.cpp @@ -0,0 +1,11 @@ +#include "arch/context_switching/segment_descriptor_table/global_descriptor_table_pointer.hpp" + +namespace teachos::arch::context_switching::segment_descriptor_table +{ + global_descriptor_table_pointer::global_descriptor_table_pointer(uint16_t table_length, uint64_t * address) + : table_length(table_length) + , address(address) + { + // Nothing to do. + } +} // namespace teachos::arch::context_switching::segment_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/segment_descriptor_table/segment_descriptor_base.cpp b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/segment_descriptor_base.cpp new file mode 100644 index 0000000..c3a03fc --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/segment_descriptor_base.cpp @@ -0,0 +1,41 @@ +#include "arch/context_switching/segment_descriptor_table/segment_descriptor_base.hpp" + +namespace teachos::arch::context_switching::segment_descriptor_table +{ + segment_descriptor_base::segment_descriptor_base(uint64_t flags) + : _limit_1(flags) + , _base_1(flags >> 16U) + , _access(flags >> 40U) + , _flag(flags >> 52U, flags >> 48U) + , _base_2(flags >> 56U) + { + // Nothing to do. + } + + segment_descriptor_base::segment_descriptor_base(access_byte access_byte, gdt_flags flags, uint32_t base, + std::bitset<20U> limit) + : _limit_1(limit.to_ulong()) + , _base_1(base) + , _access(access_byte) + , _flag(flags) + , _base_2(base >> 24U) + { + // Nothing to do + } + + auto segment_descriptor_base::get_segment_type() const -> segment_descriptor_type + { + if (!_access.contains_flags(access_byte::CODE_OR_DATA_SEGMENT)) + { + return segment_descriptor_type::SYSTEM_SEGMENT; + } + return _access.contains_flags(access_byte::CODE_SEGMENT) ? segment_descriptor_type::CODE_SEGMENT + : segment_descriptor_type::DATA_SEGMENT; + } + + segment_descriptor_base::operator uint64_t() const + { + return *reinterpret_cast<uint64_t const *>(this); + } + +} // namespace teachos::arch::context_switching::segment_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/segment_descriptor_table/segment_descriptor_extension.cpp b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/segment_descriptor_extension.cpp new file mode 100644 index 0000000..5ea0d8a --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/segment_descriptor_table/segment_descriptor_extension.cpp @@ -0,0 +1,30 @@ +#include "arch/context_switching/segment_descriptor_table/segment_descriptor_extension.hpp" + +namespace teachos::arch::context_switching::segment_descriptor_table +{ + segment_descriptor_extension::segment_descriptor_extension(uint128_t flags) + : _base(flags) + , _base_3(flags >> 64U) + { + // Nothing to do. + } + + segment_descriptor_extension::segment_descriptor_extension(access_byte access_byte, gdt_flags flags, uint64_t base, + std::bitset<20U> limit) + : _base(access_byte, flags, base, limit) + , _base_3(base >> 32U) + { + // Nothing to do + } + + auto segment_descriptor_extension::get_first_gdt_entry() const -> segment_descriptor_base + { + return _base; + } + + auto segment_descriptor_extension::get_second_gdt_entry() const -> uint64_t + { + return _base_3; + } + +} // namespace teachos::arch::context_switching::segment_descriptor_table diff --git a/arch/x86_64/pre/src/context_switching/syscall/main.cpp b/arch/x86_64/pre/src/context_switching/syscall/main.cpp new file mode 100644 index 0000000..b4ab468 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/syscall/main.cpp @@ -0,0 +1,35 @@ +#include "arch/context_switching/syscall/main.hpp" + +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 new file mode 100644 index 0000000..dbb3ed9 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp @@ -0,0 +1,32 @@ +#include "arch/context_switching/syscall/syscall_enable.hpp" + +#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp" +#include "arch/context_switching/syscall/syscall_handler.hpp" +#include "arch/kernel/cpu/msr.hpp" + +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<uint64_t>(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 new file mode 100644 index 0000000..c120f77 --- /dev/null +++ b/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp @@ -0,0 +1,121 @@ +#include "arch/context_switching/syscall/syscall_handler.hpp" + +#include "arch/context_switching/syscall/main.hpp" +#include "arch/exception_handling/assert.hpp" +#include "arch/exception_handling/panic.hpp" +#include "arch/memory/heap/global_heap_allocator.hpp" +#include "arch/memory/main.hpp" +#include "arch/video/vga/text.hpp" + +namespace teachos::arch::context_switching::syscall +{ + + namespace + { + auto write_to_vga_buffer(uint64_t buffer) -> response + { + video::vga::text::write(reinterpret_cast<char const *>(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<type>(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<char const *>(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/exception_handling/abort.cpp b/arch/x86_64/pre/src/exception_handling/abort.cpp new file mode 100644 index 0000000..5dc6869 --- /dev/null +++ b/arch/x86_64/pre/src/exception_handling/abort.cpp @@ -0,0 +1,18 @@ +#include "arch/exception_handling/panic.hpp" + +#include <cstdlib> + +namespace teachos::arch::exception_handling +{ + /** + * @brief Override for the newlib abort function. + * + * @note newlib defines @p ::abort as a weak symbol, thus allowing implementations to override it by simply providing + * a matching implementation. Since the default implemenatation calls a number of functions the kernel does not + * currently implement, @p ::abort gets overridden to simply panic. + */ + extern "C" auto abort() -> void + { + panic("Terminate was called, possibly due to an unhandled exception"); + } +} // namespace teachos::arch::exception_handling diff --git a/arch/x86_64/pre/src/exception_handling/assert.cpp b/arch/x86_64/pre/src/exception_handling/assert.cpp new file mode 100644 index 0000000..b2963de --- /dev/null +++ b/arch/x86_64/pre/src/exception_handling/assert.cpp @@ -0,0 +1,15 @@ +#include "arch/exception_handling/assert.hpp" + +#include "arch/exception_handling/panic.hpp" + +namespace teachos::arch::exception_handling +{ + auto assert(bool condition, char const * message) -> void + { + if (condition) + { + return; + } + panic("Assertion Violation: ", message); + } +} // namespace teachos::arch::exception_handling diff --git a/arch/x86_64/pre/src/exception_handling/panic.cpp b/arch/x86_64/pre/src/exception_handling/panic.cpp new file mode 100644 index 0000000..9511a9a --- /dev/null +++ b/arch/x86_64/pre/src/exception_handling/panic.cpp @@ -0,0 +1,25 @@ +#include "arch/exception_handling/panic.hpp" + +#include "arch/kernel/halt.hpp" +#include "arch/video/vga/text.hpp" + +namespace teachos::arch::exception_handling +{ + extern "C" char const message_prefix_panic[]; + + auto panic(char const * reason) -> void + { + panic(message_prefix_panic, reason); + } + + auto panic(char const * prefix, char const * reason) -> void + { + using video::vga::text::common_attributes::white_on_red; + + video::vga::text::newline(); + video::vga::text::write(prefix, white_on_red); + video::vga::text::write(reason, white_on_red); + + kernel::halt(); + }; +} // namespace teachos::arch::exception_handling diff --git a/arch/x86_64/pre/src/exception_handling/pure_virtual.cpp b/arch/x86_64/pre/src/exception_handling/pure_virtual.cpp new file mode 100644 index 0000000..67772f7 --- /dev/null +++ b/arch/x86_64/pre/src/exception_handling/pure_virtual.cpp @@ -0,0 +1,6 @@ +#include "arch/exception_handling/panic.hpp" + +extern "C" auto __cxa_pure_virtual() -> void +{ + teachos::arch::exception_handling::panic("Runtime", "Tried to call a pure virtual function!"); +} diff --git a/arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp b/arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp new file mode 100644 index 0000000..9d061a8 --- /dev/null +++ b/arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp @@ -0,0 +1,13 @@ +#include "arch/interrupt_handling/generic_interrupt_handler.hpp" + +#include "arch/video/vga/text.hpp" + +namespace teachos::arch::interrupt_handling +{ + auto generic_interrupt_handler(interrupt_frame * frame) -> void + { + (void)frame; + video::vga::text::write("An Interrupt occurred.", video::vga::text::common_attributes::green_on_black); + video::vga::text::newline(); + } +} // namespace teachos::arch::interrupt_handling diff --git a/arch/x86_64/pre/src/kernel/cpu/call.cpp b/arch/x86_64/pre/src/kernel/cpu/call.cpp new file mode 100644 index 0000000..98fa248 --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/call.cpp @@ -0,0 +1,9 @@ +#include "arch/kernel/cpu/call.hpp" + +namespace teachos::arch::kernel::cpu +{ + auto call(far_pointer pointer) -> void + { + asm volatile("rex64 lcall *%[input]" : /* no output from call */ : [input] "m"(pointer)); + } +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/cpu/gdtr.cpp b/arch/x86_64/pre/src/kernel/cpu/gdtr.cpp new file mode 100644 index 0000000..74a4e1c --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/gdtr.cpp @@ -0,0 +1,19 @@ +#include "arch/kernel/cpu/gdtr.hpp" + +#include "arch/context_switching/segment_descriptor_table/global_descriptor_table_pointer.hpp" + +namespace teachos::arch::kernel::cpu +{ + auto store_global_descriptor_table() -> context_switching::segment_descriptor_table::global_descriptor_table_pointer + { + context_switching::segment_descriptor_table::global_descriptor_table_pointer current_value{}; + asm("sgdt %[output]" : [output] "=m"(current_value)); + return current_value; + } + + auto load_global_descriptor_table( + context_switching::segment_descriptor_table::global_descriptor_table_pointer const & gdt_pointer) -> void + { + asm volatile("lgdt %[input]" : /* no output from call */ : [input] "m"(gdt_pointer)); + } +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/cpu/idtr.cpp b/arch/x86_64/pre/src/kernel/cpu/idtr.cpp new file mode 100644 index 0000000..7aa20c1 --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/idtr.cpp @@ -0,0 +1,18 @@ +#include "arch/kernel/cpu/idtr.hpp" + +namespace teachos::arch::kernel::cpu +{ + auto store_interrupt_descriptor_table() + -> context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer + { + context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer current_value{}; + asm("sidt %[output]" : [output] "=m"(current_value)); + return current_value; + } + + auto load_interrupt_descriptor_table( + context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer const & idt_pointer) -> void + { + asm volatile("lidt %[input]" : /* no output from call */ : [input] "m"(idt_pointer)); + } +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/cpu/if.cpp b/arch/x86_64/pre/src/kernel/cpu/if.cpp new file mode 100644 index 0000000..5d056fc --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/if.cpp @@ -0,0 +1,13 @@ +namespace teachos::arch::kernel::cpu +{ + auto set_interrupt_flag() -> void + { + asm volatile("sti"); + } + + auto clear_interrupt_flag() -> void + { + asm volatile("cli"); + } + +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/cpu/msr.cpp b/arch/x86_64/pre/src/kernel/cpu/msr.cpp new file mode 100644 index 0000000..9d6a318 --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/msr.cpp @@ -0,0 +1,31 @@ +#include "arch/kernel/cpu/msr.hpp" + +namespace teachos::arch::kernel::cpu +{ + namespace + { + constexpr auto IA32_EFER_ADDRESS = 0xC000'0080; + } + + auto read_msr(uint32_t msr) -> uint64_t + { + uint32_t low, high; + asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(msr)); + return (static_cast<uint64_t>(high) << 32) | low; + } + + auto write_msr(uint32_t msr, uint64_t value) -> void + { + uint32_t low = value; + uint32_t high = value >> 32; + asm volatile("wrmsr" + : /* no output from call */ + : "c"(msr), "a"(low), "d"(high)); + } + + auto set_efer_bit(efer_flags flag) -> void + { + auto const efer = read_msr(IA32_EFER_ADDRESS); + write_msr(IA32_EFER_ADDRESS, static_cast<std::underlying_type<efer_flags>::type>(flag) | efer); + } +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/cpu/segment_register.cpp b/arch/x86_64/pre/src/kernel/cpu/segment_register.cpp new file mode 100644 index 0000000..b08c9c4 --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/segment_register.cpp @@ -0,0 +1,98 @@ +#include "arch/kernel/cpu/segment_register.hpp" + +#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp" +#include "arch/exception_handling/assert.hpp" + +namespace teachos::arch::kernel::cpu +{ + auto reload_data_segment_registers() -> void + { + asm volatile("xor %%rax, %%rax\n" + "mov %%rax, %%ss\n" + "mov %%rax, %%ds\n" + "mov %%rax, %%es\n" + "mov %%rax, %%fs\n" + "mov %%rax, %%gs\n" + : /* no output from call */ + : /* no input to call */ + : "rax"); + } + + auto set_data_segment_registers(context_switching::interrupt_descriptor_table::segment_selector data_segment) -> void + { + asm volatile("xor %%rax, %%rax\n" + "mov %[input], %%ax\n" + "mov %%rax, %%ds\n" + "mov %%rax, %%es\n" + "mov %%rax, %%fs\n" + "mov %%rax, %%gs\n" + : /* no output from call */ + : [input] "m"(data_segment) + : "rax"); + } + + auto read_code_segment_register() -> context_switching::interrupt_descriptor_table::segment_selector + { + context_switching::interrupt_descriptor_table::segment_selector current_value{}; + asm volatile("mov %%cs, %[output]" : [output] "=r"(current_value)); + return current_value; + } + + auto validate_data_segment_registers(context_switching::interrupt_descriptor_table::segment_selector data_segment) + -> void + { + context_switching::interrupt_descriptor_table::segment_selector ss{}; + context_switching::interrupt_descriptor_table::segment_selector ds{}; + context_switching::interrupt_descriptor_table::segment_selector es{}; + context_switching::interrupt_descriptor_table::segment_selector fs{}; + context_switching::interrupt_descriptor_table::segment_selector gs{}; + + asm volatile( + "mov %%ss, %[ss_output]\n" + "mov %%ds, %[ds_output]\n" + "mov %%es, %[es_output]\n" + "mov %%fs, %[fs_output]\n" + "mov %%gs, %[gs_output]\n" + : [ss_output] "=r"(ss), [ds_output] "=r"(ds), [es_output] "=r"(es), [fs_output] "=r"(fs), [gs_output] "=r"(gs)); + + auto result = (ss == ds && ss == es && ss == fs && ss == gs); + exception_handling::assert(result, "[Segment Register] Values in data register are not the same."); + result = (ss == data_segment); + exception_handling::assert( + result, "[Segment Register] Expected Data Segment is not the same as the value in the Stack Segment register."); + } + + auto validate_code_segment_register(context_switching::interrupt_descriptor_table::segment_selector code_segment) + -> void + { + auto const cs = read_code_segment_register(); + exception_handling::assert( + cs == code_segment, + "[Segment Register] Expected Code Segment is not the same as the value in the Code Segment register."); + } + + auto validate_segment_registers(context_switching::interrupt_descriptor_table::segment_selector data_segment, + context_switching::interrupt_descriptor_table::segment_selector code_segment) -> void + { + validate_data_segment_registers(data_segment); + validate_code_segment_register(code_segment); + } + + auto set_code_segment_register(context_switching::interrupt_descriptor_table::segment_selector data_segment, + context_switching::interrupt_descriptor_table::segment_selector code_segment, + uint64_t address) -> void + { + asm volatile("mov %%rsp, %%rax\n" + "push %[data_segment]\n" + "push %%rax\n" + "pushfq\n" + "push %[code_segment]\n" + "mov %[return_function], %%rax\n" + "push %%rax\n" + "iretq\n" + : /* no output from call */ + : [data_segment] "m"(data_segment), [code_segment] "m"(code_segment), [return_function] "r"(address) + : "rax"); + } + +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/cpu/tr.cpp b/arch/x86_64/pre/src/kernel/cpu/tr.cpp new file mode 100644 index 0000000..a435540 --- /dev/null +++ b/arch/x86_64/pre/src/kernel/cpu/tr.cpp @@ -0,0 +1,16 @@ +#include "arch/kernel/cpu/tr.hpp" + +namespace teachos::arch::kernel::cpu +{ + auto store_task_register() -> uint16_t + { + uint16_t current_value{}; + asm("str %[output]" : [output] "=r"(current_value)); + return current_value; + } + + auto load_task_register(uint16_t gdt_offset) -> void + { + asm volatile("ltr %[input]" : /* no output from call */ : [input] "m"(gdt_offset)); + } +} // namespace teachos::arch::kernel::cpu diff --git a/arch/x86_64/pre/src/kernel/main.cpp b/arch/x86_64/pre/src/kernel/main.cpp new file mode 100644 index 0000000..43b5f90 --- /dev/null +++ b/arch/x86_64/pre/src/kernel/main.cpp @@ -0,0 +1,71 @@ +#include "arch/kernel/main.hpp" + +#include "arch/boot/pointers.hpp" +#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp" +#include "arch/context_switching/main.hpp" +#include "arch/kernel/cpu/if.hpp" +#include "arch/kernel/cpu/segment_register.hpp" +#include "arch/memory/heap/bump_allocator.hpp" +#include "arch/memory/heap/global_heap_allocator.hpp" +#include "arch/memory/main.hpp" +#include "arch/memory/multiboot/reader.hpp" +#include "arch/stl/vector.hpp" +#include "arch/video/vga/text.hpp" + +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/memory/heap/bump_allocator.cpp b/arch/x86_64/pre/src/memory/heap/bump_allocator.cpp new file mode 100644 index 0000000..525f45c --- /dev/null +++ b/arch/x86_64/pre/src/memory/heap/bump_allocator.cpp @@ -0,0 +1,54 @@ +#include "arch/memory/heap/bump_allocator.hpp" + +#include "arch/exception_handling/assert.hpp" + +#include <limits> +#include <type_traits> + +namespace teachos::arch::memory::heap +{ + namespace + { + template<typename T> + auto saturating_add(T x, T y) -> T + requires std::is_unsigned_v<T> + { + if (x > std::numeric_limits<T>::max() - y) + { + return std::numeric_limits<T>::max(); + } + T result = x + y; + return result; + } + } // namespace + + auto bump_allocator::allocate(std::size_t size) -> void * + { + // Reading the value only has to be done once, because compare_exchange_weak updates the value as well if the + // exchange failed, becuase the value was not the expected one. + auto alloc_start = next.load(std::memory_order::relaxed); + // Repeat allocation until it succeeds, has to be done, because another allocator could overtake it at any time + // causing the value to differ and the calculation to have to be redone. + for (;;) + { + auto const alloc_end = saturating_add(alloc_start, size); + arch::exception_handling::assert(alloc_end <= heap_end, "[Heap Allocator] Out of memory"); + // Check if the atomic value is still the one initally loaded, if it isn't we have been overtaken by another + // thread and need to redo the calculation. Spurious failure by weak can be ignored, because the whole allocation + // is wrapped in an infinite for loop so a failure that wasn't actually one will simply be retried until it works. + auto const updated = next.compare_exchange_weak(alloc_start, alloc_end, std::memory_order::relaxed); + if (updated) + { + return reinterpret_cast<void *>(alloc_start); + } + } + } + + auto bump_allocator::deallocate(void * pointer) noexcept -> void + { + if (pointer) + { + } + } + +} // namespace teachos::arch::memory::heap diff --git a/arch/x86_64/pre/src/memory/heap/global_heap_allocator.cpp b/arch/x86_64/pre/src/memory/heap/global_heap_allocator.cpp new file mode 100644 index 0000000..709cda1 --- /dev/null +++ b/arch/x86_64/pre/src/memory/heap/global_heap_allocator.cpp @@ -0,0 +1,148 @@ +#include "arch/memory/heap/global_heap_allocator.hpp" + +#include "arch/context_switching/syscall/main.hpp" +#include "arch/exception_handling/assert.hpp" +#include "arch/kernel/cpu/segment_register.hpp" +#include "arch/memory/heap/bump_allocator.hpp" +#include "arch/memory/heap/linked_list_allocator.hpp" +#include "arch/memory/heap/user_heap_allocator.hpp" + +namespace teachos::arch::memory::heap +{ + namespace + { + constexpr char NOT_REGISTRED_ERROR_MESSAGE[] = + "Attempted to allocate or deallocate using the global_heap_allocator before " + "register_heap_allocation_type was called."; + constexpr uint16_t KERNEL_CODE_INDEX = 1U; + + [[gnu::section(".user_text")]] + auto os_in_kernel_mode() -> bool + { + auto const cs = teachos::arch::kernel::cpu::read_code_segment_register(); + return cs.get_index() == KERNEL_CODE_INDEX; + } + } // namespace + + heap_allocator * global_heap_allocator::kernel_allocator_instance = nullptr; + user_heap_allocator * global_heap_allocator::user_allocator_instance = nullptr; + + auto global_heap_allocator::kmalloc(std::size_t size) -> void * + { + return kernel().allocate(size); + } + + auto global_heap_allocator::kfree(void * pointer) noexcept -> void + { + kernel().deallocate(pointer); + } + + auto global_heap_allocator::malloc(std::size_t size) -> void * + { + return user().allocate(size); + } + + auto global_heap_allocator::free(void * pointer) noexcept -> void + { + user().deallocate(pointer); + } + + auto global_heap_allocator::register_heap_allocator(heap_allocator_type new_type) -> void + { + exception_handling::assert(kernel_allocator_instance == nullptr, + "Calling register_heap_allocator_type can only be done once."); + + switch (new_type) + { + case heap_allocator_type::NONE: + // Nothing to do + break; + case heap_allocator_type::BUMP: + { + bump_allocator static kernel_allocator{KERNEL_HEAP_START, KERNEL_HEAP_START + KERNEL_HEAP_SIZE}; + kernel_allocator_instance = &kernel_allocator; + break; + } + case heap_allocator_type::LINKED_LIST: + { + linked_list_allocator static kernel_allocator{KERNEL_HEAP_START, KERNEL_HEAP_START + KERNEL_HEAP_SIZE}; + kernel_allocator_instance = &kernel_allocator; + break; + } + } + + [[gnu::section(".user_data")]] user_heap_allocator static user_allocator{}; + user_allocator_instance = &user_allocator; + } + + auto global_heap_allocator::kernel() -> heap_allocator & + { + exception_handling::assert(kernel_allocator_instance != nullptr, NOT_REGISTRED_ERROR_MESSAGE); + + return *kernel_allocator_instance; + } + + auto global_heap_allocator::user() -> user_heap_allocator & + { + context_switching::syscall::syscall( + context_switching::syscall::type::ASSERT, + {user_allocator_instance != nullptr, reinterpret_cast<uint64_t>(&NOT_REGISTRED_ERROR_MESSAGE)}); + return *user_allocator_instance; + } +} // namespace teachos::arch::memory::heap + +auto operator new(std::size_t size) -> void * +{ + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + return teachos::arch::memory::heap::global_heap_allocator::kmalloc(size); + } + return teachos::arch::memory::heap::global_heap_allocator::malloc(size); +} + +auto operator delete(void * pointer) noexcept -> void +{ + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); +} + +auto operator delete(void * pointer, std::size_t size) noexcept -> void +{ + (void)size; + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); +} + +auto operator new[](std::size_t size) -> void * +{ + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + return teachos::arch::memory::heap::global_heap_allocator::kmalloc(size); + } + return teachos::arch::memory::heap::global_heap_allocator::malloc(size); +} + +auto operator delete[](void * pointer) noexcept -> void +{ + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); +} + +auto operator delete[](void * pointer, std::size_t size) noexcept -> void +{ + (void)size; + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); +} diff --git a/arch/x86_64/pre/src/memory/heap/linked_list_allocator.cpp b/arch/x86_64/pre/src/memory/heap/linked_list_allocator.cpp new file mode 100644 index 0000000..00ca366 --- /dev/null +++ b/arch/x86_64/pre/src/memory/heap/linked_list_allocator.cpp @@ -0,0 +1,177 @@ +#include "arch/memory/heap/linked_list_allocator.hpp" + +#include "arch/exception_handling/assert.hpp" +#include "arch/exception_handling/panic.hpp" + +#include <algorithm> + +namespace teachos::arch::memory::heap +{ + linked_list_allocator::linked_list_allocator(std::size_t heap_start, std::size_t heap_end) + : first(nullptr) + , mutex{kstd::mutex{}} + { + auto const heap_size = heap_end - heap_start; + exception_handling::assert( + heap_size > min_allocatable_size(), + "[Linked List Allocator] Total heap size can not be smaller than minimum of 16 bytes to hold " + "atleast one memory hole entry"); + first = new (reinterpret_cast<void *>(heap_start)) memory_block(heap_size, nullptr); + } + + auto linked_list_allocator::allocate(std::size_t size) -> void * + { + // Add size of size_t to the total allocated size, because we add a header that includes the size of the allocated + // block, to allow for deallocation without the need to call with the corresponding size + auto const total_size = size + sizeof(std::size_t); + mutex.lock(); + + memory_block * previous = nullptr; + auto current = first; + + while (current != nullptr) + { + if (current->size == total_size) + { + auto const memory_address = remove_free_memory_block(previous, current); + new (memory_address) std::size_t(total_size); + mutex.unlock(); + return reinterpret_cast<void *>(reinterpret_cast<std::size_t>(memory_address) + sizeof(std::size_t)); + } + else if (current->size >= total_size + min_allocatable_size()) + { + // Ensure that the allocated size block is atleast 16 bytes (required because if we free the hole afterwards + // there needs to be enough space for a memory block). Therefore we allocate more than is actually required if + // the total size was less and simply deallocate it as well + auto const max_size = std::max(total_size, min_allocatable_size()); + auto const memory_address = split_free_memory_block(previous, current, max_size); + new (memory_address) std::size_t(max_size); + mutex.unlock(); + return reinterpret_cast<void *>(reinterpret_cast<std::size_t>(memory_address) + sizeof(std::size_t)); + } + + previous = current; + current = current->next; + } + + exception_handling::panic("[Linked List Allocator] Out of memory"); + } + + auto linked_list_allocator::deallocate(void * pointer) noexcept -> void + { + mutex.lock(); + + // Read configured header size of the complete allocated block + auto const header_pointer = reinterpret_cast<void *>(reinterpret_cast<std::size_t>(pointer) - sizeof(std::size_t)); + auto const total_size = *reinterpret_cast<std::size_t *>(header_pointer); + + auto const start_address = reinterpret_cast<std::size_t>(header_pointer); + auto const end_address = start_address + total_size; + + memory_block * previous = nullptr; + auto current = first; + + while (current != nullptr) + { + // Current address of the free memory block now points to an address that is after our block to deallocate in heap + // memory space. + if (reinterpret_cast<std::size_t>(current) >= end_address) + { + break; + } + + previous = current; + current = current->next; + } + + coalesce_free_memory_block(previous, current, header_pointer, total_size); + mutex.unlock(); + } + + auto linked_list_allocator::remove_free_memory_block(memory_block * previous_block, memory_block * current_block) + -> void * + { + return replace_free_memory_block(previous_block, current_block, current_block->next); + } + + auto linked_list_allocator::split_free_memory_block(memory_block * previous_block, memory_block * current_block, + std::size_t size) -> void * + { + auto const end_address = reinterpret_cast<std::size_t>(current_block) + size; + auto const new_block = + new (reinterpret_cast<void *>(end_address)) memory_block(current_block->size - size, current_block->next); + return replace_free_memory_block(previous_block, current_block, new_block); + } + + auto linked_list_allocator::replace_free_memory_block(memory_block * previous_block, memory_block * current_block, + memory_block * new_block) -> void * + { + auto const start_address = reinterpret_cast<std::size_t>(current_block); + // If we want to allocate into the first block that is before any other free block, then there exists no previous + // free block (nullptr). Therefore we have to overwrite the first block instead of overwriting its next value. + if (previous_block == nullptr) + { + first = new_block; + } + else + { + previous_block->next = new_block; + } + current_block->~memory_block(); + return reinterpret_cast<void *>(start_address); + } + + auto linked_list_allocator::coalesce_free_memory_block(memory_block * previous_block, memory_block * current_block, + void * pointer, std::size_t size) -> void + { + auto const start_address = reinterpret_cast<std::size_t>(pointer); + auto const end_address = start_address + size; + + // Inital values if there are no adjacent blocks either before or after, meaning we have to simply create a free + // memory block that is placed in between the previous and next block. + auto block_size = size; + auto next_block = current_block; + + // If the block we want to deallocate is before another free block and we can therefore combine both into one. + // This is done by deleting the current free block and creating a new block at the start address of the block to + // deallocate with both the size of the block to deallcoate and the free block next to it. + if (end_address == reinterpret_cast<std::size_t>(current_block)) + { + block_size += current_block->size; + next_block = current_block->next; + current_block->~memory_block(); + } + + // If the block we want to deallocate is behind another free block and we can therefore combine both into one. + // This is done by simply changin the size of the previous block to include the size of the block to deallocate. + // This is done, because the previous block might still be referencered by the next field of other memory blocks. + if (previous_block != nullptr && + start_address == (reinterpret_cast<std::size_t>(previous_block) + previous_block->size)) + { + block_size += previous_block->size; + + previous_block->size = block_size; + previous_block->next = next_block; + return; + } + + // Check if the block we want to deallocate is contained in the previous block, because if it is it can only mean + // that the block has already been deallocated and we therefore attempted a double free. + exception_handling::assert(previous_block == nullptr || + start_address >= + (reinterpret_cast<std::size_t>(previous_block) + previous_block->size), + "[Linked List Allocator] Attempted double free detected"); + + auto const new_block = new (pointer) memory_block(block_size, next_block); + // If we want to deallocate the first block that is before any other free block, then there exists no previous free + // block (nullptr). Therefore we have to overwrite the first block instead of overwriting its + // next value. + if (previous_block == nullptr) + { + first = new_block; + return; + } + previous_block->next = new_block; + } + +} // namespace teachos::arch::memory::heap diff --git a/arch/x86_64/pre/src/memory/heap/memory_block.cpp b/arch/x86_64/pre/src/memory/heap/memory_block.cpp new file mode 100644 index 0000000..4c07454 --- /dev/null +++ b/arch/x86_64/pre/src/memory/heap/memory_block.cpp @@ -0,0 +1,18 @@ +#include "arch/memory/heap/memory_block.hpp" + +#include <string.h> + +namespace teachos::arch::memory::heap +{ + memory_block::memory_block(std::size_t size, memory_block * next) + { + memset(static_cast<void *>(this), 0U, size); + this->size = size; + this->next = next; + } + + memory_block::~memory_block() + { + memset(static_cast<void *>(this), 0U, sizeof(memory_block)); + } +} // namespace teachos::arch::memory::heap diff --git a/arch/x86_64/pre/src/memory/heap/user_heap_allocator.cpp b/arch/x86_64/pre/src/memory/heap/user_heap_allocator.cpp new file mode 100644 index 0000000..96de005 --- /dev/null +++ b/arch/x86_64/pre/src/memory/heap/user_heap_allocator.cpp @@ -0,0 +1,200 @@ +#include "arch/memory/heap/user_heap_allocator.hpp" + +#include "arch/context_switching/syscall/main.hpp" + +#include <algorithm> + +namespace teachos::arch::memory::heap +{ + auto user_heap_allocator::allocate(std::size_t size) -> void * + { + // Add size of size_t to the total allocated size, because we add a header that includes the size of the allocated + // block, to allow for deallocation without the need to call with the corresponding size + auto const total_size = size + sizeof(std::size_t); + mutex.lock(); + + memory_block * previous = nullptr; + auto current = first; + + while (current != nullptr) + { + auto memory = allocate_into_memory_block_if_big_enough(current, previous, total_size); + if (memory.has_value()) + { + return memory.value(); + } + + previous = current; + current = current->next; + } + + current = expand_heap_if_full(); + + if (current != nullptr) + { + auto memory = allocate_into_memory_block_if_big_enough(current, previous, total_size); + if (memory.has_value()) + { + return memory.value(); + } + } + + constexpr char OUT_OF_MEMORY_ERROR_MESSAGE[] = "[Linked List Allocator] Out of memory"; + context_switching::syscall::syscall(context_switching::syscall::type::ASSERT, + {false, reinterpret_cast<uint64_t>(&OUT_OF_MEMORY_ERROR_MESSAGE)}); + return nullptr; + } + + auto user_heap_allocator::deallocate(void * pointer) noexcept -> void + { + mutex.lock(); + + // Read configured header size of the complete allocated block + auto const header_pointer = reinterpret_cast<void *>(reinterpret_cast<std::size_t>(pointer) - sizeof(std::size_t)); + auto const total_size = *reinterpret_cast<std::size_t *>(header_pointer); + + auto const start_address = reinterpret_cast<std::size_t>(header_pointer); + auto const end_address = start_address + total_size; + + memory_block * previous = nullptr; + auto current = first; + + while (current != nullptr) + { + // Current address of the free memory block now points to an address that is after our block to deallocate in heap + // memory space. + if (reinterpret_cast<std::size_t>(current) >= end_address) + { + break; + } + + previous = current; + current = current->next; + } + + coalesce_free_memory_block(previous, current, header_pointer, total_size); + mutex.unlock(); + } + + auto user_heap_allocator::allocate_into_memory_block_if_big_enough(memory_block * current, memory_block * previous, + std::size_t total_size) -> std::optional<void *> + { + if (current->size == total_size) + { + auto const memory_address = remove_free_memory_block(previous, current); + new (memory_address) std::size_t(total_size); + mutex.unlock(); + return reinterpret_cast<void *>(reinterpret_cast<std::size_t>(memory_address) + sizeof(std::size_t)); + } + else if (current->size >= total_size + min_allocatable_size()) + { + // Ensure that the allocated size block is atleast 16 bytes (required because if we free the hole afterwards + // there needs to be enough space for a memory block). Therefore we allocate more than is actually required if + // the total size was less and simply deallocate it as well + auto const max_size = std::max(total_size, min_allocatable_size()); + auto const memory_address = split_free_memory_block(previous, current, max_size); + new (memory_address) std::size_t(max_size); + mutex.unlock(); + return reinterpret_cast<void *>(reinterpret_cast<std::size_t>(memory_address) + sizeof(std::size_t)); + } + return std::nullopt; + } + + auto user_heap_allocator::expand_heap_if_full() -> memory_block * + { + auto const result = context_switching::syscall::syscall(context_switching::syscall::type::EXPAND_HEAP); + + uint64_t const heap_start = result.values.arg_0; + uint64_t const heap_size = result.values.arg_1; + return !result.error_code ? new (reinterpret_cast<void *>(heap_start)) memory_block(heap_size, nullptr) : nullptr; + } + + auto user_heap_allocator::remove_free_memory_block(memory_block * previous_block, memory_block * current_block) + -> void * + { + return replace_free_memory_block(previous_block, current_block, current_block->next); + } + + auto user_heap_allocator::split_free_memory_block(memory_block * previous_block, memory_block * current_block, + std::size_t size) -> void * + { + auto const end_address = reinterpret_cast<std::size_t>(current_block) + size; + auto const new_block = + new (reinterpret_cast<void *>(end_address)) memory_block(current_block->size - size, current_block->next); + return replace_free_memory_block(previous_block, current_block, new_block); + } + + auto user_heap_allocator::replace_free_memory_block(memory_block * previous_block, memory_block * current_block, + memory_block * new_block) -> void * + { + auto const start_address = reinterpret_cast<std::size_t>(current_block); + // If we want to allocate into the first block that is before any other free block, then there exists no previous + // free block (nullptr). Therefore we have to overwrite the first block instead of overwriting its next value. + if (previous_block == nullptr) + { + first = new_block; + } + else + { + previous_block->next = new_block; + } + current_block->~memory_block(); + return reinterpret_cast<void *>(start_address); + } + + auto user_heap_allocator::coalesce_free_memory_block(memory_block * previous_block, memory_block * current_block, + void * pointer, std::size_t size) -> void + { + auto const start_address = reinterpret_cast<std::size_t>(pointer); + auto const end_address = start_address + size; + + // Inital values if there are no adjacent blocks either before or after, meaning we have to simply create a free + // memory block that is placed in between the previous and next block. + auto block_size = size; + auto next_block = current_block; + + // If the block we want to deallocate is before another free block and we can therefore combine both into one. + // This is done by deleting the current free block and creating a new block at the start address of the block to + // deallocate with both the size of the block to deallcoate and the free block next to it. + if (end_address == reinterpret_cast<std::size_t>(current_block)) + { + block_size += current_block->size; + next_block = current_block->next; + current_block->~memory_block(); + } + + // If the block we want to deallocate is behind another free block and we can therefore combine both into one. + // This is done by simply changin the size of the previous block to include the size of the block to deallocate. + // This is done, because the previous block might still be referencered by the next field of other memory blocks. + if (previous_block != nullptr && + start_address == (reinterpret_cast<std::size_t>(previous_block) + previous_block->size)) + { + block_size += previous_block->size; + + previous_block->size = block_size; + previous_block->next = next_block; + return; + } + + // Check if the block we want to deallocate is contained in the previous block, because if it is it can only mean + // that the block has already been deallocated and we therefore attempted a double free. + constexpr char DOUBLE_FREE_ERROR_MESSAGE[] = "[Linked List Allocator] Attempted double free detected"; + context_switching::syscall::syscall( + context_switching::syscall::type::ASSERT, + {previous_block == nullptr || + start_address >= (reinterpret_cast<std::size_t>(previous_block) + previous_block->size), + reinterpret_cast<uint64_t>(&DOUBLE_FREE_ERROR_MESSAGE)}); + + auto const new_block = new (pointer) memory_block(block_size, next_block); + // If we want to deallocate the first block that is before any other free block, then there exists no previous free + // block (nullptr). Therefore we have to overwrite the first block instead of overwriting its + // next value. + if (previous_block == nullptr) + { + first = new_block; + return; + } + previous_block->next = new_block; + } + +} // namespace teachos::arch::memory::heap diff --git a/arch/x86_64/pre/src/user/main.cpp b/arch/x86_64/pre/src/user/main.cpp new file mode 100644 index 0000000..8b07e4a --- /dev/null +++ b/arch/x86_64/pre/src/user/main.cpp @@ -0,0 +1,35 @@ +#include "arch/user/main.hpp" + +#include "arch/context_switching/syscall/main.hpp" +#include "arch/memory/heap/global_heap_allocator.hpp" + +#include <algorithm> +#include <array> +#include <atomic> +#include <ranges> + +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<uint64_t>(&syscall_message)}); + + // Test C++ standard library + std::array<std::atomic<uint8_t>, 4> array_test = {std::atomic<uint8_t>{5}, std::atomic<uint8_t>{10}, + std::atomic<uint8_t>{15}, std::atomic<uint8_t>{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 |
