mycelium_kernel/arch/x86_64/
interrupt.rs1use 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
32type StackFrame = [u8; 4096];
34
35const DOUBLE_FAULT_STACK_SIZE: usize = 8;
37
38static 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
71impl 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 test_interrupt<C>(cx: C)
118 where
119 C: hal_core::interrupt::ctx::Context<Registers = Registers>,
120 {
121 let fired = TEST_INTERRUPT_WAS_FIRED.fetch_add(1, Ordering::Release) + 1;
122 tracing::info!(registers = ?cx.registers(), fired, "lol im in ur test interrupt");
123 }
124}
125
126#[inline]
127#[tracing::instrument(level = tracing::Level::DEBUG)]
128pub(super) fn init_gdt() {
129 tracing::trace!("initializing GDT...");
130 let mut gdt = Gdt::new();
131
132 let code_segment = segment::Descriptor::code().with_ring(Ring::Ring0);
134 let code_selector = gdt.add_segment(code_segment);
135 tracing::debug!(
136 descriptor = fmt::alt(code_segment),
137 selector = fmt::alt(code_selector),
138 "added code segment"
139 );
140
141 let tss = segment::SystemDescriptor::tss(&TSS);
144 let tss_selector = gdt.add_sys_segment(tss);
145 tracing::debug!(
146 tss.descriptor = fmt::alt(tss),
147 tss.selector = fmt::alt(tss_selector),
148 "added TSS"
149 );
150
151 GDT.init(gdt);
153
154 let gdt = GDT.get();
156 tracing::debug!(GDT = ?gdt, "GDT initialized");
157 gdt.load();
158
159 tracing::trace!("GDT loaded");
160
161 let code_selector = segment::Selector::current_cs();
163 tracing::trace!(code_selector = fmt::alt(code_selector));
164 unsafe {
165 code_selector.set_cs();
167
168 segment::Selector::null().set_ss();
173 segment::Selector::null().set_ds();
174 segment::Selector::null().set_es();
175
176 task::StateSegment::load_tss(tss_selector);
177 }
178
179 tracing::debug!("segment selectors set");
180}
181
182mycotest::decl_test! {
183 fn interrupts_work() -> mycotest::TestResult {
184 let test_interrupt_fires = TEST_INTERRUPT_WAS_FIRED.load(Ordering::Acquire);
185
186 tracing::debug!("testing interrupts...");
187 fire_test_interrupt();
188 tracing::debug!("it worked");
189
190 mycotest::assert_eq!(
191 test_interrupt_fires + 1,
192 TEST_INTERRUPT_WAS_FIRED.load(Ordering::Acquire),
193 "test interrupt wasn't fired!",
194 );
195
196 Ok(())
197 }
198}