diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-03-21 09:06:25 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-03-21 09:06:25 +0100 |
| commit | fcf0cd73b0978c2db3e719542cf81d7d2c0fe24e (patch) | |
| tree | 134b41bf36ea6b207ebccf6b6924f50c6405dda9 /arch | |
| parent | 96f1511dbe2e80223732bcbef8068c3d5a330cee (diff) | |
| parent | cceaf717405059e8b02132d7c33f9fe3b2645b56 (diff) | |
| download | teachos-fcf0cd73b0978c2db3e719542cf81d7d2c0fe24e.tar.xz teachos-fcf0cd73b0978c2db3e719542cf81d7d2c0fe24e.zip | |
Merge branch 'fmorgner/develop-BA-FS26/gdt_idt' into develop-BA-FS26
This patchset enables the use of interrupts. Specifically, it simplifies
debugging by printing the fault address in case of a page fault.
Diffstat (limited to 'arch')
| -rw-r--r-- | arch/x86_64/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | arch/x86_64/include/arch/cpu/initialization.hpp | 3 | ||||
| -rw-r--r-- | arch/x86_64/include/arch/cpu/interrupts.hpp | 60 | ||||
| -rw-r--r-- | arch/x86_64/kapi/system.cpp | 3 | ||||
| -rw-r--r-- | arch/x86_64/src/cpu/initialization.cpp | 84 | ||||
| -rw-r--r-- | arch/x86_64/src/cpu/interrupt_stubs.S | 112 | ||||
| -rw-r--r-- | arch/x86_64/src/cpu/interrupts.cpp | 120 |
7 files changed, 362 insertions, 22 deletions
diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt index f053982..89d9bc0 100644 --- a/arch/x86_64/CMakeLists.txt +++ b/arch/x86_64/CMakeLists.txt @@ -20,6 +20,8 @@ target_sources("x86_64" PRIVATE # CPU Initialization "src/cpu/initialization.cpp" + "src/cpu/interrupts.cpp" + "src/cpu/interrupt_stubs.S" # Low-level bootstrap "src/boot/boot32.S" diff --git a/arch/x86_64/include/arch/cpu/initialization.hpp b/arch/x86_64/include/arch/cpu/initialization.hpp index b2ce864..71186d4 100644 --- a/arch/x86_64/include/arch/cpu/initialization.hpp +++ b/arch/x86_64/include/arch/cpu/initialization.hpp @@ -3,4 +3,7 @@ namespace arch::cpu { auto initialize_descriptors() -> void; + + auto initialize_legacy_interrupts() -> void; + } // namespace arch::cpu diff --git a/arch/x86_64/include/arch/cpu/interrupts.hpp b/arch/x86_64/include/arch/cpu/interrupts.hpp index 92c5824..8f156a4 100644 --- a/arch/x86_64/include/arch/cpu/interrupts.hpp +++ b/arch/x86_64/include/arch/cpu/interrupts.hpp @@ -30,7 +30,7 @@ namespace arch::cpu //! Reserved std::uint8_t : 5; //! The type of this gate. - gate_type gate_type : 4; + enum gate_type gate_type : 4; //! Reserved std::uint8_t : 1; //! The privilege level required to enter through this gate. @@ -48,14 +48,70 @@ namespace arch::cpu static_assert(sizeof(gate_descriptor) == 2 * sizeof(std::uint64_t)); static_assert(std::is_aggregate_v<gate_descriptor>); + //! 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 + { + std::uint64_t rip; + std::uint64_t cs; + std::uint64_t rflags; + std::uint64_t rsp; + std::uint64_t ss; + } cpu_saved; + }; + struct interrupt_descriptor_table { - interrupt_descriptor_table(); + interrupt_descriptor_table() noexcept; + + auto load() const -> void; private: std::array<gate_descriptor, 256> 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; + }; + + auto enable_interrupts() -> void; + auto disable_interrupts() -> void; + } // namespace arch::cpu #endif
\ No newline at end of file diff --git a/arch/x86_64/kapi/system.cpp b/arch/x86_64/kapi/system.cpp index ffb6a46..301169f 100644 --- a/arch/x86_64/kapi/system.cpp +++ b/arch/x86_64/kapi/system.cpp @@ -1,6 +1,7 @@ #include "kapi/system.hpp" #include "arch/cpu/initialization.hpp" +#include "arch/cpu/interrupts.hpp" namespace kapi::system { @@ -8,6 +9,8 @@ namespace kapi::system auto memory_initialized() -> void { arch::cpu::initialize_descriptors(); + arch::cpu::initialize_legacy_interrupts(); + arch::cpu::enable_interrupts(); } } // namespace kapi::system
\ No newline at end of file diff --git a/arch/x86_64/src/cpu/initialization.cpp b/arch/x86_64/src/cpu/initialization.cpp index aae4f1f..5f4703d 100644 --- a/arch/x86_64/src/cpu/initialization.cpp +++ b/arch/x86_64/src/cpu/initialization.cpp @@ -1,8 +1,10 @@ #include "arch/cpu/initialization.hpp" #include "arch/cpu/global_descriptor_table.hpp" +#include "arch/cpu/interrupts.hpp" #include "arch/cpu/segment_descriptor.hpp" #include "arch/cpu/task_state_segment.hpp" +#include "arch/device_io/port_io.hpp" #include <kstd/print> @@ -83,30 +85,38 @@ namespace arch::cpu .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<std::uintptr_t>(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 = system_segment_descriptor{ - { - .limit_low = (sizeof(tss) - 1) & 0xffff, // NOLINT(readability-magic-numbers) - .base_low = std::bit_cast<std::uintptr_t>(&tss) & 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 = ((sizeof(tss) - 1) >> 16) & 0xf, // NOLINT(readability-magic-numbers) - .long_mode = false, - .protected_mode = false, - .granularity = segment_granularity::byte, - .base_high = (std::bit_cast<std::uintptr_t>(&tss) >> 24) & 0xff, // NOLINT(readability-magic-numbers) - }, - (std::bit_cast<std::uintptr_t>(&tss) >> 32) & 0xffff'ffff, // NOLINT(readability-magic-numbers) - }; + 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, @@ -116,7 +126,41 @@ namespace arch::cpu kstd::println("[x86_64:SYS] Reloading Global Descriptor Table."); gdt.load(1, 2); - kstd::println("[x86_64:SYS] TODO: initialize Interrupt Descriptor Table."); + kstd::println("[x86_64:SYS] Initializing Interrupt Descriptor Table."); + auto static idt = interrupt_descriptor_table{}; + idt.load(); + } + + auto initialize_legacy_interrupts() -> void + { + 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_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); } } // namespace arch::cpu diff --git a/arch/x86_64/src/cpu/interrupt_stubs.S b/arch/x86_64/src/cpu/interrupt_stubs.S new file mode 100644 index 0000000..e59bdd2 --- /dev/null +++ b/arch/x86_64/src/cpu/interrupt_stubs.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/src/cpu/interrupts.cpp b/arch/x86_64/src/cpu/interrupts.cpp new file mode 100644 index 0000000..466389d --- /dev/null +++ b/arch/x86_64/src/cpu/interrupts.cpp @@ -0,0 +1,120 @@ +#include "arch/cpu/interrupts.hpp" + +#include "kapi/cpu.hpp" + +#include "arch/cpu/segment_selector.hpp" + +#include <kstd/print> + +#include <cstdint> + +namespace arch::cpu +{ + + namespace + { + constexpr auto isr_number_page_fault = 0x0e; + constexpr auto isr_number_legacy_start = 0x20; + constexpr auto isr_number_legacy_end = 0x29; + constexpr auto isr_number_cascade_start = 0x2c; + constexpr auto isr_number_cascade_end = 0x2f; + + auto handle_page_fault(interrupt_frame * frame) -> void + { + auto fault_address = std::uintptr_t{}; + asm volatile("mov %%cr2, %0" : "=r"(fault_address)); + + auto const present = (frame->interrupt.error_code & 0x1) != 0; + auto const write = (frame->interrupt.error_code & 0x2) != 0; + auto const user = (frame->interrupt.error_code & 0x4) != 0; + + kstd::println(kstd::print_sink::stderr, "[x86_64:MMU] PAGE FAULT!"); + kstd::println(kstd::print_sink::stderr, "\tFault address: {:#018x}", fault_address); + kstd::println(kstd::print_sink::stderr, "\tPresent: {}", present); + kstd::println(kstd::print_sink::stderr, "\tWrite: {}", write); + kstd::println(kstd::print_sink::stderr, "\tUser: {}", user); + kstd::println(kstd::print_sink::stderr, "\tRIP: {:#018x}", frame->cpu_saved.rip); + kstd::println(kstd::print_sink::stderr, "\tHalting the system now!"); + + kapi::cpu::halt(); + } + + auto handle_legacy_interrupt(interrupt_frame * frame) -> void + { + kstd::println("[x86_64:SYS] Ignoring 8259 legacy interrupt {:#04x}", frame->interrupt.number); + } + } // namespace + + extern "C" + { + extern std::uintptr_t const isr_stub_table[256]; + + auto interrupt_dispatch(interrupt_frame * frame) -> void + { + if ((frame->interrupt.number >= isr_number_legacy_start && frame->interrupt.number <= isr_number_legacy_end) || + (frame->interrupt.number >= isr_number_cascade_start && frame->interrupt.number <= isr_number_cascade_end)) + { + handle_legacy_interrupt(frame); + return; + } + + switch (frame->interrupt.number) + { + case isr_number_page_fault: + handle_page_fault(frame); + break; + default: + kstd::println(kstd::print_sink::stderr, "[x86_64:SYS] Unhandled interrupt {:#04x} received with code {:#04x}", + frame->interrupt.number, frame->interrupt.error_code); + kapi::cpu::halt(); + } + } + } + + interrupt_descriptor_table::interrupt_descriptor_table() noexcept + { + for (auto i = 0uz; i < 256; ++i) + { + m_descriptors[i] = gate_descriptor{ + .offset_low = static_cast<std::uint16_t>(isr_stub_table[i] & 0xffff), // NOLINT(readability-magic-numbers) + .m_code_segment = segment_selector{0, false, 1}, + .interrupt_stack_table_selector = 0, + .gate_type = gate_type::interrupt_gate, + .descriptor_privilege_level = 0, + .present = true, + .offset_middle = + static_cast<std::uint16_t>((isr_stub_table[i] >> 16) & 0xffff), // NOLINT(readability-magic-numbers) + .offset_high = + static_cast<std::uint32_t>((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; + } + + auto enable_interrupts() -> void + { + asm volatile("sti"); + } + + auto disable_interrupts() -> void + { + asm volatile("cli"); + } + +} // namespace arch::cpu
\ No newline at end of file |
