use crate::{cpu, mm, segment, time, PAddr, VAddr};
use core::{arch::asm, marker::PhantomData, time::Duration};
use hal_core::interrupt::Control;
use hal_core::interrupt::{ctx, Handlers};
use mycelium_util::{
bits, fmt,
sync::{
blocking::{Mutex, MutexGuard},
spin::Spinlock,
InitOnce,
},
};
pub mod apic;
pub mod idt;
pub mod pic;
use self::apic::{IoApic, LocalApic};
pub use idt::Idt;
pub use pic::CascadedPic;
#[derive(Debug)]
pub struct Controller {
model: InterruptModel,
}
#[derive(Debug)]
#[repr(C)]
pub struct Context<'a, T = ()> {
registers: &'a mut Registers,
code: T,
}
pub type ErrorCode = u64;
pub struct CodeFault<'a> {
kind: &'static str,
error_code: Option<&'a dyn fmt::Display>,
}
pub type Isr<T> = extern "x86-interrupt" fn(&mut Context<T>);
#[derive(Debug)]
pub enum PeriodicTimerError {
Pit(time::PitError),
Apic(time::InvalidDuration),
}
#[derive(Debug)]
#[repr(C)]
pub struct Interrupt<T = ()> {
vector: u8,
_t: PhantomData<T>,
}
#[derive(Debug)]
enum InterruptModel {
Pic(Mutex<pic::CascadedPic, Spinlock>),
Apic {
local: apic::LocalApic,
#[allow(dead_code)]
io: Mutex<apic::IoApic, Spinlock>,
},
}
bits::bitfield! {
pub struct PageFaultCode<u32> {
pub const PRESENT: bool;
pub const WRITE: bool;
pub const USER: bool;
pub const RESERVED_WRITE: bool;
pub const INSTRUCTION_FETCH: bool;
pub const PROTECTION_KEY: bool;
pub const SHADOW_STACK: bool;
const _RESERVED0 = 8;
pub const SGX: bool;
}
}
bits::bitfield! {
pub struct SelectorErrorCode<u16> {
const EXTERNAL: bool;
const TABLE: cpu::DescriptorTable;
const INDEX = 13;
}
}
#[repr(C)]
pub struct Registers {
pub instruction_ptr: VAddr, pub code_segment: segment::Selector,
_pad: [u16; 3],
pub cpu_flags: u64, pub stack_ptr: VAddr, pub stack_segment: segment::Selector,
_pad2: [u16; 3],
}
static IDT: Mutex<idt::Idt, Spinlock> = Mutex::new_with_raw_mutex(idt::Idt::new(), Spinlock::new());
static INTERRUPT_CONTROLLER: InitOnce<Controller> = InitOnce::uninitialized();
impl Controller {
pub fn idt() -> MutexGuard<'static, idt::Idt, Spinlock> {
IDT.lock()
}
#[tracing::instrument(level = "info", name = "interrupt::Controller::init")]
pub fn init<H: Handlers<Registers>>() {
tracing::info!("intializing IDT...");
let mut idt = IDT.lock();
idt.register_handlers::<H>().unwrap();
unsafe {
idt.load_raw();
}
}
pub fn enable_hardware_interrupts(
acpi: Option<&acpi::InterruptModel>,
frame_alloc: &impl hal_core::mem::page::Alloc<mm::size::Size4Kb>,
) -> &'static Self {
let mut pics = pic::CascadedPic::new();
unsafe {
tracing::debug!(
big = Idt::PIC_BIG_START,
little = Idt::PIC_LITTLE_START,
"remapping PIC interrupt vectors"
);
pics.set_irq_address(Idt::PIC_BIG_START as u8, Idt::PIC_LITTLE_START as u8);
}
let model = match acpi {
Some(acpi::InterruptModel::Apic(apic_info)) => {
tracing::info!("detected APIC interrupt model");
let mut pagectrl = mm::PageCtrl::current();
unsafe {
pics.disable();
}
tracing::info!("disabled 8259 PICs");
let mut io = {
tracing::trace!(?apic_info.io_apics, "found {} IO APICs", apic_info.io_apics.len());
let io_apic = &apic_info.io_apics[0];
let addr = PAddr::from_u64(io_apic.address as u64);
tracing::debug!(ioapic.paddr = ?addr, "IOAPIC");
IoApic::new(addr, &mut pagectrl, frame_alloc)
};
io.map_isa_irqs(Idt::IOAPIC_START as u8);
io.set_masked(IoApic::PIT_TIMER_IRQ, false);
io.set_masked(IoApic::PS2_KEYBOARD_IRQ, false);
let local = LocalApic::new(&mut pagectrl, frame_alloc);
local.enable(Idt::LOCAL_APIC_SPURIOUS as u8);
InterruptModel::Apic {
local,
io: Mutex::new_with_raw_mutex(io, Spinlock::new()),
}
}
model => {
if model.is_none() {
tracing::warn!("platform does not support ACPI; falling back to 8259 PIC");
} else {
tracing::warn!(
"ACPI does not indicate APIC interrupt model; falling back to 8259 PIC"
)
}
tracing::info!("configuring 8259 PIC interrupts...");
unsafe {
pics.enable();
}
InterruptModel::Pic(Mutex::new_with_raw_mutex(pics, Spinlock::new()))
}
};
tracing::trace!(interrupt_model = ?model);
let controller = INTERRUPT_CONTROLLER.init(Self { model });
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
unsafe {
crate::cpu::intrinsics::sti();
}
controller
}
pub fn start_periodic_timer(&self, interval: Duration) -> Result<(), PeriodicTimerError> {
match self.model {
InterruptModel::Pic(_) => crate::time::PIT
.lock()
.start_periodic_timer(interval)
.map_err(PeriodicTimerError::Pit),
InterruptModel::Apic { ref local, .. } => local
.start_periodic_timer(interval, Idt::LOCAL_APIC_TIMER as u8)
.map_err(PeriodicTimerError::Apic),
}
}
}
impl<'a, T> hal_core::interrupt::Context for Context<'a, T> {
type Registers = Registers;
fn registers(&self) -> &Registers {
self.registers
}
unsafe fn registers_mut(&mut self) -> &mut Registers {
self.registers
}
}
impl<'a> ctx::PageFault for Context<'a, PageFaultCode> {
fn fault_vaddr(&self) -> crate::VAddr {
crate::control_regs::Cr2::read()
}
fn debug_error_code(&self) -> &dyn fmt::Debug {
&self.code
}
fn display_error_code(&self) -> &dyn fmt::Display {
&self.code
}
}
impl<'a> ctx::CodeFault for Context<'a, CodeFault<'a>> {
fn is_user_mode(&self) -> bool {
false }
fn instruction_ptr(&self) -> crate::VAddr {
self.registers.instruction_ptr
}
fn fault_kind(&self) -> &'static str {
self.code.kind
}
fn details(&self) -> Option<&dyn fmt::Display> {
self.code.error_code
}
}
impl<'a> Context<'a, ErrorCode> {
pub fn error_code(&self) -> ErrorCode {
self.code
}
}
impl<'a> Context<'a, PageFaultCode> {
pub fn page_fault_code(&self) -> PageFaultCode {
self.code
}
}
impl hal_core::interrupt::Control for Idt {
type Registers = Registers;
#[inline]
unsafe fn disable(&mut self) {
crate::cpu::intrinsics::cli();
}
#[inline]
unsafe fn enable(&mut self) {
crate::cpu::intrinsics::sti();
tracing::trace!("interrupts enabled");
}
fn is_enabled(&self) -> bool {
unimplemented!("eliza do this one!!!")
}
fn register_handlers<H>(&mut self) -> Result<(), hal_core::interrupt::RegistrationError>
where
H: Handlers<Registers>,
{
macro_rules! gen_code_faults {
($self:ident, $h:ty, $($vector:path => fn $name:ident($($rest:tt)+),)+) => {
$(
gen_code_faults! {@ $name($($rest)+); }
$self.register_isr($vector, $name::<$h> as *const ());
)+
};
(@ $name:ident($kind:literal);) => {
extern "x86-interrupt" fn $name<H: Handlers<Registers>>(mut registers: Registers) {
let code = CodeFault {
error_code: None,
kind: $kind,
};
H::code_fault(Context { registers: &mut registers, code });
}
};
(@ $name:ident($kind:literal, code);) => {
extern "x86-interrupt" fn $name<H: Handlers<Registers>>(
mut registers: Registers,
code: u64,
) {
let code = CodeFault {
error_code: Some(&code),
kind: $kind,
};
H::code_fault(Context { registers: &mut registers, code });
}
};
}
let span = tracing::debug_span!("Idt::register_handlers");
let _enter = span.enter();
extern "x86-interrupt" fn page_fault_isr<H: Handlers<Registers>>(
mut registers: Registers,
code: PageFaultCode,
) {
H::page_fault(Context {
registers: &mut registers,
code,
});
}
extern "x86-interrupt" fn double_fault_isr<H: Handlers<Registers>>(
mut registers: Registers,
code: u64,
) {
H::double_fault(Context {
registers: &mut registers,
code,
});
}
extern "x86-interrupt" fn pit_timer_isr<H: Handlers<Registers>>(_regs: Registers) {
if crate::time::Pit::handle_interrupt() {
H::timer_tick()
} else {
tracing::trace!("PIT sleep completed");
}
unsafe {
match INTERRUPT_CONTROLLER.get_unchecked().model {
InterruptModel::Pic(ref pics) => {
pics.lock().end_interrupt(Idt::PIC_PIT_TIMER as u8)
}
InterruptModel::Apic { ref local, .. } => local.end_interrupt(),
}
}
}
extern "x86-interrupt" fn apic_timer_isr<H: Handlers<Registers>>(_regs: Registers) {
H::timer_tick();
unsafe {
match INTERRUPT_CONTROLLER.get_unchecked().model {
InterruptModel::Pic(_) => unreachable!(),
InterruptModel::Apic { ref local, .. } => local.end_interrupt(),
}
}
}
extern "x86-interrupt" fn keyboard_isr<H: Handlers<Registers>>(_regs: Registers) {
static PORT: cpu::Port = cpu::Port::at(0x60);
let scancode = unsafe { PORT.readb() };
H::ps2_keyboard(scancode);
unsafe {
match INTERRUPT_CONTROLLER.get_unchecked().model {
InterruptModel::Pic(ref pics) => {
pics.lock().end_interrupt(Idt::PIC_PS2_KEYBOARD as u8)
}
InterruptModel::Apic { ref local, .. } => local.end_interrupt(),
}
}
}
extern "x86-interrupt" fn test_isr<H: Handlers<Registers>>(mut registers: Registers) {
H::test_interrupt(Context {
registers: &mut registers,
code: (),
});
}
extern "x86-interrupt" fn invalid_tss_isr<H: Handlers<Registers>>(
mut registers: Registers,
code: u64,
) {
unsafe {
crate::vga::writer().force_unlock();
if let Some(com1) = crate::serial::com1() {
com1.force_unlock();
}
}
let selector = SelectorErrorCode(code as u16);
tracing::error!(?selector, "invalid task-state segment!");
let msg = selector.named("task-state segment (TSS)");
let code = CodeFault {
error_code: Some(&msg),
kind: "Invalid TSS (0xA)",
};
H::code_fault(Context {
registers: &mut registers,
code,
});
}
extern "x86-interrupt" fn segment_not_present_isr<H: Handlers<Registers>>(
mut registers: Registers,
code: u64,
) {
unsafe {
crate::vga::writer().force_unlock();
if let Some(com1) = crate::serial::com1() {
com1.force_unlock();
}
}
let selector = SelectorErrorCode(code as u16);
tracing::error!(?selector, "a segment was not present!");
let msg = selector.named("stack segment");
let code = CodeFault {
error_code: Some(&msg),
kind: "Segment Not Present (0xB)",
};
H::code_fault(Context {
registers: &mut registers,
code,
});
}
extern "x86-interrupt" fn stack_segment_isr<H: Handlers<Registers>>(
mut registers: Registers,
code: u64,
) {
unsafe {
crate::vga::writer().force_unlock();
if let Some(com1) = crate::serial::com1() {
com1.force_unlock();
}
}
let selector = SelectorErrorCode(code as u16);
tracing::error!(?selector, "a stack-segment fault is happening");
let msg = selector.named("stack segment");
let code = CodeFault {
error_code: Some(&msg),
kind: "Stack-Segment Fault (0xC)",
};
H::code_fault(Context {
registers: &mut registers,
code,
});
}
extern "x86-interrupt" fn gpf_isr<H: Handlers<Registers>>(
mut registers: Registers,
code: u64,
) {
unsafe {
crate::vga::writer().force_unlock();
if let Some(com1) = crate::serial::com1() {
com1.force_unlock();
}
}
let segment = if code > 0 {
Some(SelectorErrorCode(code as u16))
} else {
None
};
tracing::error!(?segment, "lmao, a general protection fault is happening");
let error_code = segment.map(|seg| seg.named("selector"));
let code = CodeFault {
error_code: error_code.as_ref().map(|code| code as &dyn fmt::Display),
kind: "General Protection Fault (0xD)",
};
H::code_fault(Context {
registers: &mut registers,
code,
});
}
extern "x86-interrupt" fn spurious_isr() {
tracing::trace!("spurious");
}
gen_code_faults! {
self, H,
Self::DIVIDE_BY_ZERO => fn div_0_isr("Divide-By-Zero (0x0)"),
Self::OVERFLOW => fn overflow_isr("Overflow (0x4)"),
Self::BOUND_RANGE_EXCEEDED => fn br_isr("Bound Range Exceeded (0x5)"),
Self::INVALID_OPCODE => fn ud_isr("Invalid Opcode (0x6)"),
Self::DEVICE_NOT_AVAILABLE => fn no_fpu_isr("Device (FPU) Not Available (0x7)"),
Self::ALIGNMENT_CHECK => fn alignment_check_isr("Alignment Check (0x11)", code),
Self::SIMD_FLOATING_POINT => fn simd_fp_exn_isr("SIMD Floating-Point Exception (0x13)"),
Self::X87_FPU_EXCEPTION => fn x87_exn_isr("x87 Floating-Point Exception (0x10)"),
}
self.register_isr(Self::PAGE_FAULT, page_fault_isr::<H> as *const ());
self.register_isr(Self::INVALID_TSS, invalid_tss_isr::<H> as *const ());
self.register_isr(
Self::SEGMENT_NOT_PRESENT,
segment_not_present_isr::<H> as *const (),
);
self.register_isr(
Self::STACK_SEGMENT_FAULT,
stack_segment_isr::<H> as *const (),
);
self.register_isr(Self::GENERAL_PROTECTION_FAULT, gpf_isr::<H> as *const ());
self.register_isr(Self::DOUBLE_FAULT, double_fault_isr::<H> as *const ());
self.register_isr(Self::PIC_PIT_TIMER, pit_timer_isr::<H> as *const ());
self.register_isr(Self::IOAPIC_PIT_TIMER, pit_timer_isr::<H> as *const ());
self.register_isr(Self::PIC_PS2_KEYBOARD, keyboard_isr::<H> as *const ());
self.register_isr(Self::IOAPIC_PS2_KEYBOARD, keyboard_isr::<H> as *const ());
self.register_isr(Self::LOCAL_APIC_SPURIOUS, spurious_isr as *const ());
self.register_isr(Self::LOCAL_APIC_TIMER, apic_timer_isr::<H> as *const ());
self.register_isr(69, test_isr::<H> as *const ());
Ok(())
}
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
instruction_ptr,
code_segment,
stack_ptr,
stack_segment,
_pad: _,
cpu_flags,
_pad2: _,
} = self;
f.debug_struct("Registers")
.field("instruction_ptr", instruction_ptr)
.field("code_segment", code_segment)
.field("cpu_flags", &format_args!("{cpu_flags:#b}"))
.field("stack_ptr", stack_ptr)
.field("stack_segment", stack_segment)
.finish()
}
}
impl fmt::Display for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, " rip: {:?}", self.instruction_ptr)?;
writeln!(f, " cs: {:?}", self.code_segment)?;
writeln!(f, " flags: {:#b}", self.cpu_flags)?;
writeln!(f, " rsp: {:?}", self.stack_ptr)?;
writeln!(f, " ss: {:?}", self.stack_segment)?;
Ok(())
}
}
pub fn fire_test_interrupt() {
unsafe { asm!("int {0}", const 69) }
}
impl SelectorErrorCode {
#[inline]
fn named(self, segment_kind: &'static str) -> NamedSelectorErrorCode {
NamedSelectorErrorCode {
segment_kind,
code: self,
}
}
fn display(&self) -> impl fmt::Display {
struct PrettyErrorCode(SelectorErrorCode);
impl fmt::Display for PrettyErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let table = self.0.get(SelectorErrorCode::TABLE);
let index = self.0.get(SelectorErrorCode::INDEX);
write!(f, "{table} index {index}")?;
if self.0.get(SelectorErrorCode::EXTERNAL) {
f.write_str(" (from an external source)")?;
}
write!(f, " (error code {:#b})", self.0.bits())?;
Ok(())
}
}
PrettyErrorCode(*self)
}
}
struct NamedSelectorErrorCode {
segment_kind: &'static str,
code: SelectorErrorCode,
}
impl fmt::Display for NamedSelectorErrorCode {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at {}", self.segment_kind, self.code.display())
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::size_of;
#[test]
fn registers_is_correct_size() {
assert_eq!(size_of::<Registers>(), 40);
}
}