Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(x86_64): real abstraction for ISA IRQs, unfuck I/O APIC #490

Merged
merged 11 commits into from
Dec 29, 2024
171 changes: 112 additions & 59 deletions hal-x86_64/src/interrupt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{cpu, mm, segment, time, PAddr, VAddr};
use crate::{cpu, mm, segment, time, VAddr};
use core::{arch::asm, marker::PhantomData, time::Duration};
use hal_core::interrupt::Control;
use hal_core::interrupt::{ctx, Handlers};
Expand All @@ -15,7 +15,7 @@
pub mod idt;
pub mod pic;

use self::apic::{IoApic, LocalApic};
use self::apic::{IoApicSet, LocalApic};
pub use idt::Idt;
pub use pic::CascadedPic;

Expand Down Expand Up @@ -69,10 +69,7 @@
/// [apics]: apic
Apic {
local: apic::LocalApic,
// TODO(eliza): allow further configuration of the I/O APIC (e.g.
// masking/unmasking stuff...)
#[allow(dead_code)]
io: Mutex<apic::IoApic, Spinlock>,
io: apic::IoApicSet,
},
}

Expand Down Expand Up @@ -134,6 +131,67 @@
static IDT: Mutex<idt::Idt, Spinlock> = Mutex::new_with_raw_mutex(idt::Idt::new(), Spinlock::new());
static INTERRUPT_CONTROLLER: InitOnce<Controller> = InitOnce::uninitialized();

pub enum MaskError {
NotHwIrq,
}

/// ISA interrupt vectors
///
/// See: https://wiki.osdev.org/Interrupts#General_IBM-PC_Compatible_Interrupt_Information

Check failure on line 140 in hal-x86_64/src/interrupt.rs

View workflow job for this annotation

GitHub Actions / docs

error: this URL is not a hyperlink --> hal-x86_64/src/interrupt.rs:140:10 | 140 | /// See: https://wiki.osdev.org/Interrupts#General_IBM-PC_Compatible_Interrupt_Information | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://wiki.osdev.org/Interrupts#General_IBM-PC_Compatible_Interrupt_Information>` | = note: bare URLs are not automatically turned into clickable links = note: `-D rustdoc::bare-urls` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(rustdoc::bare_urls)]`
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum IsaInterrupt {
/// Programmable Interval Timer (PIT) timer interrupt.
PitTimer = 0,
/// PS/2 keyboard controller interrupt.
Ps2Keyboard = 1,
// IRQ 2 is reserved for the PIC cascade interrupt and isn't user accessible!
/// COM2 / COM4 serial port interrupt.
Com2 = 3,
/// COM1 / COM3 serial port interrupt.
Com1 = 4,
/// LPT2 parallel port interrupt.
Lpt2 = 5,
/// Floppy disk
Floppy = 6,
/// LPT1 parallel port interrupt, or spurious.
Lpt1 = 7,
/// CMOS real-time clock.
CmosRtc = 8,
/// Free for peripherals/SCSI/NIC
Periph1 = 9,
Periph2 = 10,
Periph3 = 11,
/// PS/2 Mouse
Ps2Mouse = 12,
/// FPU
Fpu = 13,
/// Primary ATA hard disk
AtaPrimary = 14,
/// Secondary ATA hard disk
AtaSecondary = 15,
}

impl IsaInterrupt {
pub const ALL: [IsaInterrupt; 15] = [
IsaInterrupt::PitTimer,
IsaInterrupt::Ps2Keyboard,
IsaInterrupt::Com2,
IsaInterrupt::Com1,
IsaInterrupt::Lpt2,
IsaInterrupt::Floppy,
IsaInterrupt::Lpt1,
IsaInterrupt::CmosRtc,
IsaInterrupt::Periph1,
IsaInterrupt::Periph2,
IsaInterrupt::Periph3,
IsaInterrupt::Ps2Mouse,
IsaInterrupt::Fpu,
IsaInterrupt::AtaPrimary,
IsaInterrupt::AtaSecondary,
];
}

impl Controller {
// const DEFAULT_IOAPIC_BASE_PADDR: u64 = 0xFEC00000;

Expand All @@ -152,6 +210,31 @@
}
}

pub fn mask_isa_irq(&self, irq: IsaInterrupt) {
match self.model {
InterruptModel::Pic(ref pics) => pics.lock().mask(irq),
InterruptModel::Apic { ref io, .. } => io.set_isa_masked(irq, true),
}
}

pub fn unmask_isa_irq(&self, irq: IsaInterrupt) {
match self.model {
InterruptModel::Pic(ref pics) => pics.lock().unmask(irq),
InterruptModel::Apic { ref io, .. } => io.set_isa_masked(irq, false),
}
}

/// # Safety
///
/// Calling this when there isn't actually an ISA interrupt pending can do
/// arbitrary bad things (which I think is basically just faulting the CPU).
pub unsafe fn end_isa_irq(&self, irq: IsaInterrupt) {
match self.model {
InterruptModel::Pic(ref pics) => pics.lock().end_interrupt(irq),
InterruptModel::Apic { ref local, .. } => local.end_interrupt(),
}
}

pub fn enable_hardware_interrupts(
acpi: Option<&acpi::InterruptModel>,
frame_alloc: &impl hal_core::mem::page::Alloc<mm::size::Size4Kb>,
Expand All @@ -169,7 +252,7 @@
pics.set_irq_address(Idt::PIC_BIG_START as u8, Idt::PIC_LITTLE_START as u8);
}

let model = match acpi {
let controller = match acpi {
Some(acpi::InterruptModel::Apic(apic_info)) => {
tracing::info!("detected APIC interrupt model");

Expand All @@ -181,38 +264,17 @@
}
tracing::info!("disabled 8259 PICs");

// configure the I/O APIC
let mut io = {
// TODO(eliza): consider actually using other I/O APICs? do
// we need them for anything??
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)
};

// map the standard ISA hardware interrupts to I/O APIC
// redirection entries.
io.map_isa_irqs(Idt::IOAPIC_START as u8);

// unmask the PIT timer vector --- we'll need this for calibrating
// the local APIC timer...
io.set_masked(IoApic::PIT_TIMER_IRQ, false);

// unmask the PS/2 keyboard interrupt as well.
io.set_masked(IoApic::PS2_KEYBOARD_IRQ, false);
// configure the I/O APIC(s)
let io = IoApicSet::new(apic_info, frame_alloc, &mut pagectrl, Idt::ISA_BASE as u8);

// enable the local APIC
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()),
}
let model = InterruptModel::Apic { local, io };

tracing::trace!(interrupt_model = ?model);
INTERRUPT_CONTROLLER.init(Self { model })
}
model => {
if model.is_none() {
Expand All @@ -229,21 +291,19 @@
// clear for you, the reader, that at this point they are definitely intentionally enabled.
pics.enable();
}
InterruptModel::Pic(Mutex::new_with_raw_mutex(pics, Spinlock::new()))
INTERRUPT_CONTROLLER.init(Self {
model: InterruptModel::Pic(Mutex::new_with_raw_mutex(pics, Spinlock::new())),
})
}
};
tracing::trace!(interrupt_model = ?model);

let controller = INTERRUPT_CONTROLLER.init(Self { model });

// `sti` may not be called until the interrupt controller static is
// fully initialized, as an interrupt that occurs before it is
// initialized may attempt to access the static to finish the interrupt!
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
tracing::trace!("sti");
unsafe {
crate::cpu::intrinsics::sti();
}

controller.unmask_isa_irq(IsaInterrupt::PitTimer);
controller.unmask_isa_irq(IsaInterrupt::Ps2Keyboard);
controller
}

Expand Down Expand Up @@ -405,12 +465,9 @@
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(),
}
INTERRUPT_CONTROLLER
.get_unchecked()
.end_isa_irq(IsaInterrupt::PitTimer);
}
}

Expand All @@ -432,12 +489,9 @@
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(),
}
INTERRUPT_CONTROLLER
.get_unchecked()
.end_isa_irq(IsaInterrupt::Ps2Keyboard);
}
}

Expand Down Expand Up @@ -591,11 +645,10 @@
// === hardware interrupts ===
// ISA standard hardware interrupts mapped on both the PICs and IO APIC
// interrupt models.
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 ());
// local APIC specific hardware itnerrupts
self.register_isa_isr(IsaInterrupt::PitTimer, pit_timer_isr::<H> as *const ());
self.register_isa_isr(IsaInterrupt::Ps2Keyboard, keyboard_isr::<H> as *const ());

// local APIC specific hardware interrupts
self.register_isr(Self::LOCAL_APIC_SPURIOUS, spurious_isr as *const ());
self.register_isr(Self::LOCAL_APIC_TIMER, apic_timer_isr::<H> as *const ());

Expand Down
2 changes: 1 addition & 1 deletion hal-x86_64/src/interrupt/apic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Advanced Programmable Interrupt Controller (APIC).
pub mod io;
pub mod local;
pub use io::IoApic;
pub use io::{IoApic, IoApicSet};
pub use local::LocalApic;

use mycelium_util::bits::enum_from_bits;
Expand Down
Loading
Loading