mycelium_kernel/arch/x86_64/
interrupt.rs

1use super::{oops, Oops};
2use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
3use hal_core::{interrupt, VAddr};
4pub use hal_x86_64::interrupt::*;
5use hal_x86_64::{
6    cpu::Ring,
7    segment::{self, Gdt},
8    task,
9};
10use maitake::time;
11use mycelium_util::{fmt, sync};
12
13#[tracing::instrument]
14pub fn enable_exceptions() {
15    init_gdt();
16    tracing::info!("GDT initialized!");
17
18    Controller::init::<InterruptHandlers>();
19    tracing::info!("IDT initialized!");
20}
21
22#[tracing::instrument(skip(acpi))]
23pub fn enable_hardware_interrupts(acpi: Option<&acpi::InterruptModel>) -> &'static Controller {
24    let irq_ctrl = Controller::enable_hardware_interrupts(acpi, &crate::ALLOC);
25
26    irq_ctrl
27        .start_periodic_timer(IDIOTIC_CLOCK_INTERVAL)
28        .expect("failed to start periodic timer");
29    irq_ctrl
30}
31
32// TODO(eliza): put this somewhere good.
33type StackFrame = [u8; 4096];
34
35// chosen by fair dice roll, guaranteed to be random
36const DOUBLE_FAULT_STACK_SIZE: usize = 8;
37
38/// Stack used by ISRs during a double fault.
39///
40/// /!\ EXTREMELY SERIOUS WARNING: this has to be `static mut` or else it
41///     will go in `.bss` and we'll all die or something.
42static mut DOUBLE_FAULT_STACK: [StackFrame; DOUBLE_FAULT_STACK_SIZE] =
43    [[0; 4096]; DOUBLE_FAULT_STACK_SIZE];
44
45static TSS: sync::Lazy<task::StateSegment> = sync::Lazy::new(|| {
46    tracing::trace!("initializing TSS..");
47    let mut tss = task::StateSegment::empty();
48    tss.interrupt_stacks[Idt::DOUBLE_FAULT_IST_OFFSET] =
49        VAddr::from_ptr(core::ptr::addr_of!(DOUBLE_FAULT_STACK))
50            .offset(DOUBLE_FAULT_STACK_SIZE as isize);
51    tracing::debug!(?tss, "TSS initialized");
52    tss
53});
54
55pub(in crate::arch) static GDT: sync::InitOnce<Gdt> = sync::InitOnce::uninitialized();
56
57const IDIOTIC_CLOCK_INTERVAL: time::Duration = time::Duration::from_millis(10);
58
59static IDIOTIC_CLOCK_TICKS: AtomicU64 = AtomicU64::new(0);
60
61static TEST_INTERRUPT_WAS_FIRED: AtomicUsize = AtomicUsize::new(0);
62
63pub const IDIOTIC_CLOCK: maitake::time::Clock =
64    maitake::time::Clock::new(IDIOTIC_CLOCK_INTERVAL, || {
65        IDIOTIC_CLOCK_TICKS.load(Ordering::Relaxed)
66    })
67    .named("CLOCK_IDIOTIC");
68
69pub(crate) struct InterruptHandlers;
70
71/// Forcibly unlock the IOs we write to in an oops (VGA buffer and COM1 serial
72/// port) to prevent deadlocks if the oops occured while either was locked.
73///
74/// # Safety
75///
76///  /!\ only call this when oopsing!!! /!\
77impl hal_core::interrupt::Handlers<Registers> for InterruptHandlers {
78    fn page_fault<C>(cx: C)
79    where
80        C: interrupt::Context<Registers = Registers> + hal_core::interrupt::ctx::PageFault,
81    {
82        let fault_vaddr = cx.fault_vaddr();
83        let code = cx.display_error_code();
84        oops(Oops::fault_with_details(
85            &cx,
86            "PAGE FAULT",
87            &format_args!("at {fault_vaddr:?}\n{code}"),
88        ))
89    }
90
91    fn code_fault<C>(cx: C)
92    where
93        C: interrupt::Context<Registers = Registers> + interrupt::ctx::CodeFault,
94    {
95        let fault = match cx.details() {
96            Some(deets) => Oops::fault_with_details(&cx, cx.fault_kind(), deets),
97            None => Oops::fault(&cx, cx.fault_kind()),
98        };
99        oops(fault)
100    }
101
102    fn double_fault<C>(cx: C)
103    where
104        C: hal_core::interrupt::Context<Registers = Registers>,
105    {
106        oops(Oops::fault(&cx, "DOUBLE FAULT"))
107    }
108
109    fn timer_tick() {
110        IDIOTIC_CLOCK_TICKS.fetch_add(1, Ordering::Release);
111    }
112
113    fn ps2_keyboard(scancode: u8) {
114        crate::drivers::ps2_keyboard::handle_scancode(scancode)
115    }
116
117    fn serial_input(port: u8, byte: u8) {
118        crate::drivers::serial_input::SERIAL_INPUTS.get()[port as usize].handle_input(byte)
119    }
120
121    fn test_interrupt<C>(cx: C)
122    where
123        C: hal_core::interrupt::ctx::Context<Registers = Registers>,
124    {
125        let fired = TEST_INTERRUPT_WAS_FIRED.fetch_add(1, Ordering::Release) + 1;
126        tracing::info!(registers = ?cx.registers(), fired, "lol im in ur test interrupt");
127    }
128}
129
130#[inline]
131#[tracing::instrument(level = tracing::Level::DEBUG)]
132pub(super) fn init_gdt() {
133    tracing::trace!("initializing GDT...");
134    let mut gdt = Gdt::new();
135
136    // add one kernel code segment
137    let code_segment = segment::Descriptor::code().with_ring(Ring::Ring0);
138    let code_selector = gdt.add_segment(code_segment);
139    tracing::debug!(
140        descriptor = fmt::alt(code_segment),
141        selector = fmt::alt(code_selector),
142        "added code segment"
143    );
144
145    // add the TSS.
146
147    let tss = segment::SystemDescriptor::tss(&TSS);
148    let tss_selector = gdt.add_sys_segment(tss);
149    tracing::debug!(
150        tss.descriptor = fmt::alt(tss),
151        tss.selector = fmt::alt(tss_selector),
152        "added TSS"
153    );
154
155    // all done! long mode barely uses this thing lol.
156    GDT.init(gdt);
157
158    // load the GDT
159    let gdt = GDT.get();
160    tracing::debug!(GDT = ?gdt, "GDT initialized");
161    gdt.load();
162
163    tracing::trace!("GDT loaded");
164
165    // set new segment selectors
166    let code_selector = segment::Selector::current_cs();
167    tracing::trace!(code_selector = fmt::alt(code_selector));
168    unsafe {
169        // set the code segment selector
170        code_selector.set_cs();
171
172        // in protected mode and long mode, the code segment, stack segment,
173        // data segment, and extra segment must all have base address 0 and
174        // limit `2^64`, since actual segmentation is not used in those modes.
175        // therefore, we must zero the SS, DS, and ES registers.
176        segment::Selector::null().set_ss();
177        segment::Selector::null().set_ds();
178        segment::Selector::null().set_es();
179
180        task::StateSegment::load_tss(tss_selector);
181    }
182
183    tracing::debug!("segment selectors set");
184}
185
186mycotest::decl_test! {
187    fn interrupts_work() -> mycotest::TestResult {
188        let test_interrupt_fires = TEST_INTERRUPT_WAS_FIRED.load(Ordering::Acquire);
189
190        tracing::debug!("testing interrupts...");
191        fire_test_interrupt();
192        tracing::debug!("it worked");
193
194        mycotest::assert_eq!(
195            test_interrupt_fires + 1,
196            TEST_INTERRUPT_WAS_FIRED.load(Ordering::Acquire),
197            "test interrupt wasn't fired!",
198        );
199
200        Ok(())
201    }
202}