Skip to content

Commit

Permalink
feat(hal-x86_64): put the local APIC in local storage
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkw committed Dec 31, 2024
1 parent dfe25fa commit 0ab99e8
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 13 deletions.
77 changes: 67 additions & 10 deletions hal-x86_64/src/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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};
use hal_core::mem::page;
use mycelium_util::{
bits, fmt,
sync::{
Expand Down Expand Up @@ -44,7 +45,8 @@ pub type Isr<T> = extern "x86-interrupt" fn(&mut Context<T>);
#[derive(Debug)]
pub enum PeriodicTimerError {
Pit(time::PitError),
Apic(time::InvalidDuration),
InvalidDuration(time::InvalidDuration),
Apic(apic::local::LocalApicError),
}

#[derive(Debug)]
Expand All @@ -68,8 +70,8 @@ enum InterruptModel {
/// [I/O]: apic::IoApic
/// [apics]: apic
Apic {
local: apic::LocalApic,
io: apic::IoApicSet,
local: apic::local::Handle,
},
}

Expand Down Expand Up @@ -192,6 +194,16 @@ impl IsaInterrupt {
];
}

#[must_use]
fn disable_scoped() -> impl Drop + Send + Sync {
unsafe {
crate::cpu::intrinsics::cli();
}
mycelium_util::defer(|| unsafe {
crate::cpu::intrinsics::sti();
})
}

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

Expand Down Expand Up @@ -224,20 +236,51 @@ impl Controller {
}
}

fn local_apic_handle(&self) -> Result<&apic::local::Handle, apic::local::LocalApicError> {
match self.model {
InterruptModel::Pic(_) => Err(apic::local::LocalApicError::NoApic),
InterruptModel::Apic { ref local, .. } => Ok(local),
}
}

pub fn with_local_apic<T>(
&self,
f: impl FnOnce(&LocalApic) -> T,
) -> Result<T, apic::local::LocalApicError> {
self.local_apic_handle()?.with(f)
}

/// This should *not* be called by the boot processor
pub fn initialize_local_apic<A>(
&self,
frame_alloc: &A,
pagectrl: &mut impl page::Map<mm::size::Size4Kb, A>,
) -> Result<(), apic::local::LocalApicError>
where
A: page::Alloc<mm::size::Size4Kb>,
{
let _deferred = disable_scoped();
let hdl = self.local_apic_handle()?;
unsafe {
hdl.initialize(frame_alloc, pagectrl, Idt::LOCAL_APIC_SPURIOUS as u8);
}
Ok(())
}
/// # 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(),
InterruptModel::Apic { ref local, .. } => local.with(|apic| unsafe { apic.end_interrupt() })
.expect("interrupts should not be handled on this core until the local APIC is initialized")
}
}

pub fn enable_hardware_interrupts(
acpi: Option<&acpi::InterruptModel>,
frame_alloc: &impl hal_core::mem::page::Alloc<mm::size::Size4Kb>,
frame_alloc: &impl page::Alloc<mm::size::Size4Kb>,
) -> &'static Self {
let mut pics = pic::CascadedPic::new();
// regardless of whether APIC or PIC interrupt handling will be used,
Expand Down Expand Up @@ -267,13 +310,16 @@ impl Controller {
// 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);
// configure and initialize the local APIC on the boot processor
let local = apic::local::Handle::new();
unsafe {
local.initialize(frame_alloc, &mut pagectrl, Idt::LOCAL_APIC_SPURIOUS as u8);
}

let model = InterruptModel::Apic { local, io };

tracing::trace!(interrupt_model = ?model);

INTERRUPT_CONTROLLER.init(Self { model })
}
model => {
Expand Down Expand Up @@ -334,8 +380,11 @@ impl Controller {
.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),
.with(|apic| {
apic.start_periodic_timer(interval, Idt::LOCAL_APIC_TIMER as u8)
.map_err(PeriodicTimerError::InvalidDuration)
})
.map_err(PeriodicTimerError::Apic)?,
}
}
}
Expand Down Expand Up @@ -650,7 +699,15 @@ mod isr {
unsafe {
match INTERRUPT_CONTROLLER.get_unchecked().model {
InterruptModel::Pic(_) => unreachable!(),
InterruptModel::Apic { ref local, .. } => local.end_interrupt(),
InterruptModel::Apic { ref local, .. } => {
match local.with(|apic| apic.end_interrupt()) {
Ok(_) => {}
Err(e) => unreachable!(
"the local APIC timer will not have fired if the \
local APIC is uninitialized on this core! {e:?}",
),
}
}
}
}
}
Expand Down
55 changes: 53 additions & 2 deletions hal-x86_64/src/interrupt/apic/local.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use super::{PinPolarity, TriggerMode};
use crate::{
cpu::{FeatureNotSupported, Msr},
cpu::{local, FeatureNotSupported, Msr},
mm::{self, page, size::Size4Kb, PhysPage, VirtPage},
time::{Duration, InvalidDuration},
};
use core::{convert::TryInto, marker::PhantomData, num::NonZeroU32};
use core::{cell::RefCell, convert::TryInto, marker::PhantomData, num::NonZeroU32};
use hal_core::{PAddr, VAddr};
use mycelium_util::fmt;
use raw_cpuid::CpuId;
Expand All @@ -24,6 +24,9 @@ pub struct LocalApicRegister<T = u32, A = access::ReadWrite> {
_ty: PhantomData<fn(T, A)>,
}

#[derive(Debug)]
pub(in crate::interrupt) struct Handle(local::LocalKey<RefCell<Option<LocalApic>>>);

pub trait RegisterAccess {
type Access;
type Target;
Expand All @@ -32,6 +35,16 @@ pub trait RegisterAccess {
) -> Volatile<&'static mut Self::Target, Self::Access>;
}

#[derive(Debug, Eq, PartialEq)]
pub enum LocalApicError {
/// The system is configured to use the PIC interrupt model rather than the
/// APIC interrupt model.
NoApic,

/// The local APIC is uninitialized.
Uninitialized,
}

impl LocalApic {
const BASE_PADDR_MASK: u64 = 0xffff_ffff_f000;

Expand Down Expand Up @@ -293,6 +306,44 @@ impl LocalApic {
}
}

impl Handle {
pub(in crate::interrupt) const fn new() -> Self {
Self(local::LocalKey::new(|| RefCell::new(None)))
}

pub(in crate::interrupt) fn with<T>(
&self,
f: impl FnOnce(&LocalApic) -> T,
) -> Result<T, LocalApicError> {
self.0.with(|apic| {
Ok(f(apic
.borrow()
.as_ref()
.ok_or(LocalApicError::Uninitialized)?))
})
}

pub(in crate::interrupt) unsafe fn initialize<A>(
&self,
frame_alloc: &A,
pagectrl: &mut impl page::Map<Size4Kb, A>,
spurious_vector: u8,
) where
A: page::Alloc<Size4Kb>,
{
self.0.with(|slot| {
let mut slot = slot.borrow_mut();
if slot.is_some() {
// already initialized, bail.
return;
}
let apic = LocalApic::new(pagectrl, frame_alloc);
apic.enable(spurious_vector);
*slot = Some(apic);
})
}
}

pub mod register {
use super::*;
use mycelium_util::bits::{bitfield, enum_from_bits};
Expand Down
4 changes: 3 additions & 1 deletion util/src/deferred.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
/// Defers execution of a closure until a scope is exited.
///
/// As seen in "The Go Programming Language"..
/// As seen in "The Go Programming Language".
#[must_use = "dropping a `Deferred` guard immediately executes \
the deferred function"]
pub struct Deferred<F: FnOnce()>(Option<F>);

impl<F: FnOnce()> Deferred<F> {
Expand Down

0 comments on commit 0ab99e8

Please sign in to comment.