From e2f6737db02b7f396c2fd4b4630c23a6a6d2ba5e Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sat, 22 Oct 2022 11:28:31 -0700 Subject: [PATCH] feat(x86_64): implement APIC interrupt handling (#351) This branch adds support for APIC interrupt handling on x86_64 systems. In order to support Survival MultiPlayer, we need to be able to handle interrupts using the APIC rather than the PICs, because the APIC interrupt model allows multiple CPU cores to handle interrupts. This is part of the work towards SMP support (#345). This is a large change to the `hal-x86_64` crate's `interrupt` module in order to add types representing the `LocalApic` and `IoApic`, and to change the interrupt controller abstraction to switch to handling interrupts using APICs if the system has APICs. If APICs are not detected, we continue to use the PICs, but cannot support SMP. In addition, the kernel is updated to use the new HAL APIs. Several smaller changes to other parts of mycelium have been factored out from this PR to merge separately: * b99fb81 refac(hal-x86_64): move `pit` and `tsc` into `time` (#356) * 9994cd5 feat(hal-x86_64): add PIT driver (#355) * 30431a9 fix(trace): weird indentation on new spans (#354) * 91e97b7 feat(hal-x86_64): add `cpu::Msr` type for accessing MSRs (#353) We'll probably want to make additional changes to the new interrupt code in subsequent PRs, but this is enough to get mycelium to use the APICs, and demonstrate that he `maitake` timer wheel still works using the local APIC timer). --- Cargo.lock | 1 + hal-x86_64/Cargo.toml | 1 + hal-x86_64/src/cpu.rs | 8 + hal-x86_64/src/interrupt.rs | 194 ++++++++-- hal-x86_64/src/interrupt/apic.rs | 114 ++++++ hal-x86_64/src/interrupt/apic/io.rs | 362 ++++++++++++++++++ hal-x86_64/src/interrupt/apic/local.rs | 504 +++++++++++++++++++++++++ hal-x86_64/src/interrupt/idt.rs | 26 +- hal-x86_64/src/interrupt/pic.rs | 4 + src/arch/x86_64.rs | 23 +- src/arch/x86_64/acpi.rs | 24 +- src/arch/x86_64/boot.rs | 9 +- src/arch/x86_64/interrupt.rs | 27 +- src/arch/x86_64/tests.rs | 1 - src/lib.rs | 22 +- 15 files changed, 1251 insertions(+), 69 deletions(-) create mode 100644 hal-x86_64/src/interrupt/apic.rs create mode 100644 hal-x86_64/src/interrupt/apic/io.rs create mode 100644 hal-x86_64/src/interrupt/apic/local.rs diff --git a/Cargo.lock b/Cargo.lock index f994db59..0d934594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,6 +602,7 @@ dependencies = [ name = "hal-x86_64" version = "0.1.0" dependencies = [ + "acpi", "hal-core", "mycelium-trace", "mycelium-util", diff --git a/hal-x86_64/Cargo.toml b/hal-x86_64/Cargo.toml index 730f633e..a5489ef9 100644 --- a/hal-x86_64/Cargo.toml +++ b/hal-x86_64/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT" log = ["tracing/log"] [dependencies] +acpi = "4.1.1" hal-core = { path = "../hal-core" } mycelium-util = { path = "../util" } mycelium-trace = { path = "../trace" } diff --git a/hal-x86_64/src/cpu.rs b/hal-x86_64/src/cpu.rs index 314d52e2..41cdd175 100644 --- a/hal-x86_64/src/cpu.rs +++ b/hal-x86_64/src/cpu.rs @@ -212,6 +212,14 @@ impl bits::FromBits for Ring { impl DtablePtr { pub(crate) fn new(t: &'static T) -> Self { + unsafe { + // safety: the `'static` lifetime ensures the pointed dtable is + // never going away + Self::new_unchecked(t) + } + } + + pub(crate) unsafe fn new_unchecked(t: &T) -> Self { let limit = (mem::size_of::() - 1) as u16; let base = t as *const _ as *const (); diff --git a/hal-x86_64/src/interrupt.rs b/hal-x86_64/src/interrupt.rs index 8c383295..d2881004 100644 --- a/hal-x86_64/src/interrupt.rs +++ b/hal-x86_64/src/interrupt.rs @@ -1,15 +1,24 @@ -use crate::{cpu, segment, VAddr}; -use core::{arch::asm, marker::PhantomData}; +use crate::{cpu, mm, segment, 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}; +use mycelium_util::{ + bits, fmt, + sync::{spin, InitOnce}, +}; +pub mod apic; pub mod idt; pub mod pic; +use self::apic::{IoApic, LocalApic}; pub use idt::Idt; pub use pic::CascadedPic; -pub type Control = &'static mut Idt; +#[derive(Debug)] +pub struct Controller { + model: InterruptModel, +} #[derive(Debug)] #[repr(C)] @@ -35,6 +44,28 @@ pub struct Interrupt { _t: PhantomData, } +/// The interrupt controller's active interrupt model. +#[derive(Debug)] + +enum InterruptModel { + /// Interrupts are handled by the [8259 Programmable Interrupt Controller + /// (PIC)](pic). + Pic(spin::Mutex), + /// Interrupts are handled by the [local] and [I/O] [Advanced Programmable + /// Interrupt Controller (APIC)s][apics]. + /// + /// [local]: apic::LocalApic + /// [I/O]: apic::IoApic + /// [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: spin::Mutex, + }, +} + #[derive(Copy, Clone)] #[repr(transparent)] pub struct PageFaultCode(u32); @@ -63,30 +94,112 @@ pub struct Registers { _pad2: [u16; 3], } -static mut IDT: idt::Idt = idt::Idt::new(); -static mut PIC: pic::CascadedPic = pic::CascadedPic::new(); +static IDT: spin::Mutex = spin::Mutex::new(idt::Idt::new()); +static INTERRUPT_CONTROLLER: InitOnce = InitOnce::uninitialized(); + +impl Controller { + // const DEFAULT_IOAPIC_BASE_PADDR: u64 = 0xFEC00000; -#[tracing::instrument(level = "info", name = "interrupts::init")] -pub fn init>() -> Control { - use hal_core::interrupt::Control; - tracing::info!("configuring 8259 PIC interrupts..."); + #[tracing::instrument(level = "info", name = "interrupt::Controller::init")] + pub fn init>() { + tracing::info!("intializing IDT..."); - unsafe { - PIC.set_irq_address(0x20, 0x28); - // functionally a no-op, since interrupts from PC/AT PIC are enabled at boot, just being - // clear for you, the reader, that at this point they are definitely intentionally enabled. - PIC.enable(); + let mut idt = IDT.lock(); + idt.register_handlers::().unwrap(); + unsafe { + idt.load_raw(); + } } - tracing::info!("intializing IDT..."); + pub fn enable_hardware_interrupts(acpi: Option<&acpi::InterruptModel>) -> &'static Self { + let mut pics = pic::CascadedPic::new(); + // regardless of whether APIC or PIC interrupt handling will be used, + // the PIC interrupt vectors must be remapped so that they do not + // conflict with CPU exceptions. + 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"); + + // disable the 8259 PICs so that we can use APIC interrupts instead + unsafe { + pics.disable(); + } + 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?? + let io_apic = &apic_info.io_apics[0]; + let paddr = PAddr::from_u64(io_apic.address as u64); + let vaddr = mm::kernel_vaddr_of(paddr); + IoApic::new(vaddr) + }; + + 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); + + // enable the local APIC + let local = LocalApic::new(); + local.enable(Idt::LOCAL_APIC_SPURIOUS as u8); + + InterruptModel::Apic { + local, + io: spin::Mutex::new(io), + } + } + 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 { + // functionally a no-op, since interrupts from PC/AT PIC are enabled at boot, just being + // clear for you, the reader, that at this point they are definitely intentionally enabled. + pics.enable(); + } + InterruptModel::Pic(spin::Mutex::new(pics)) + } + }; + tracing::trace!(interrupt_model = ?model); + + unsafe { + crate::cpu::intrinsics::sti(); + } - unsafe { - IDT.register_handlers::().unwrap(); - IDT.load(); - IDT.enable(); + let controller = Self { model }; + INTERRUPT_CONTROLLER.init(controller) } - unsafe { &mut IDT } + /// Starts a periodic timer which fires the `timer_tick` interrupt of the + /// provided [`Handlers`] every time `interval` elapses. + pub fn start_periodic_timer( + &self, + interval: Duration, + ) -> Result<(), crate::time::InvalidDuration> { + match self.model { + InterruptModel::Pic(_) => crate::time::PIT.lock().start_periodic_timer(interval), + InterruptModel::Apic { ref local, .. } => { + local.start_periodic_timer(interval, Idt::LOCAL_APIC_TIMER as u8) + } + } + } } impl<'a, T> hal_core::interrupt::Context for Context<'a, T> { @@ -230,16 +343,36 @@ impl hal_core::interrupt::Control for Idt { .is_ok(); if !was_sleeping { H::timer_tick(); + } else { + tracing::trace!("PIT sleep completed"); } unsafe { - PIC.end_interrupt(0x20); + 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>(_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>(_regs: Registers) { H::keyboard_controller(); unsafe { - PIC.end_interrupt(0x21); + match INTERRUPT_CONTROLLER.get_unchecked().model { + InterruptModel::Pic(ref pics) => pics.lock().end_interrupt(0x21), + InterruptModel::Apic { ref local, .. } => local.end_interrupt(), + } } } @@ -356,6 +489,10 @@ impl hal_core::interrupt::Control for Idt { }); } + 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)"), @@ -368,7 +505,16 @@ impl hal_core::interrupt::Control for Idt { Self::X87_FPU_EXCEPTION => fn x87_exn_isr("x87 Floating-Point Exception (0x10)"), } - self.set_isr(0x20, pit_timer_isr:: as *const ()); + self.set_isr(Self::PIC_PIT_TIMER, pit_timer_isr:: as *const ()); + self.set_isr(Self::IOAPIC_PIT_TIMER, pit_timer_isr:: as *const ()); + self.set_isr( + Self::LOCAL_APIC_SPURIOUS as usize, + spurious_isr as *const (), + ); + self.set_isr( + Self::LOCAL_APIC_TIMER as usize, + apic_timer_isr:: as *const (), + ); self.set_isr(0x21, keyboard_isr:: as *const ()); self.set_isr(69, test_isr:: as *const ()); self.set_isr(Self::PAGE_FAULT, page_fault_isr:: as *const ()); diff --git a/hal-x86_64/src/interrupt/apic.rs b/hal-x86_64/src/interrupt/apic.rs new file mode 100644 index 00000000..d14a7f1d --- /dev/null +++ b/hal-x86_64/src/interrupt/apic.rs @@ -0,0 +1,114 @@ +//! Advanced Programmable Interrupt Controller (APIC). +pub mod io; +pub mod local; +pub use io::IoApic; +pub use local::LocalApic; + +use mycelium_util::bits::FromBits; +use raw_cpuid::CpuId; + +pub fn is_supported() -> bool { + CpuId::new() + .get_feature_info() + .map(|features| features.has_apic()) + .unwrap_or(false) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum PinPolarity { + High = 0, + Low = 1, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum TriggerMode { + Edge = 0, + Level = 1, +} + +// === impl PinPolarity === + +impl PinPolarity { + #[inline(always)] + #[must_use] + fn from_u8(bits: u8) -> Self { + match bits { + 0 => Self::High, + 1 => Self::Low, + bits => unreachable!( + "APIC PinPolarity should be a single bit, but got {:#b}", + bits + ), + } + } +} + +impl FromBits for PinPolarity { + const BITS: u32 = 1; + type Error = core::convert::Infallible; + + fn into_bits(self) -> u64 { + self as u8 as u64 + } + + fn try_from_bits(bits: u64) -> Result { + Ok(Self::from_u8(bits as u8)) + } +} + +impl FromBits for PinPolarity { + const BITS: u32 = 1; + type Error = core::convert::Infallible; + + fn into_bits(self) -> u32 { + self as u8 as u32 + } + + fn try_from_bits(bits: u32) -> Result { + Ok(Self::from_u8(bits as u8)) + } +} +// === impl TriggerMode === + +impl TriggerMode { + #[inline(always)] + #[must_use] + fn from_u8(bits: u8) -> Self { + match bits { + 0 => Self::Edge, + 1 => Self::Level, + _ => unreachable!( + "APIC TriggerMode should be a single bit, but got {:#b}", + bits + ), + } + } +} + +impl FromBits for TriggerMode { + const BITS: u32 = 1; + type Error = core::convert::Infallible; + + fn into_bits(self) -> u64 { + self as u8 as u64 + } + + fn try_from_bits(bits: u64) -> Result { + Ok(Self::from_u8(bits as u8)) + } +} + +impl FromBits for TriggerMode { + const BITS: u32 = 1; + type Error = core::convert::Infallible; + + fn into_bits(self) -> u32 { + self as u8 as u32 + } + + fn try_from_bits(bits: u32) -> Result { + Ok(Self::from_u8(bits as u8)) + } +} diff --git a/hal-x86_64/src/interrupt/apic/io.rs b/hal-x86_64/src/interrupt/apic/io.rs new file mode 100644 index 00000000..b7302c71 --- /dev/null +++ b/hal-x86_64/src/interrupt/apic/io.rs @@ -0,0 +1,362 @@ +use super::{PinPolarity, TriggerMode}; +use hal_core::VAddr; +use mycelium_util::bits::{bitfield, FromBits}; +use volatile::Volatile; + +#[derive(Debug)] +pub struct IoApic { + registers: Volatile<&'static mut MmioRegisters>, +} + +bitfield! { + pub struct RedirectionEntry { + pub const VECTOR: u8; + pub const DELIVERY: DeliveryMode; + /// Destination mode. + /// + /// Physical (0) or logical (1). If this is physical mode, then bits + /// 56-59 should contain an APIC ID. If this is logical mode, then those + /// bits contain a set of processors. + pub const DEST_MODE: DestinationMode; + /// Set if this interrupt is going to be sent, but the APIC is busy. Read only. + pub const QUEUED: bool; + pub const POLARITY: PinPolarity; + /// Remote IRR. + /// + /// Used for level triggered interrupts only to show if a local APIC has + /// received the interrupt (= 1), or has sent an EOI (= 0). Read only. + pub const REMOTE_IRR: bool; + pub const TRIGGER: TriggerMode; + pub const MASKED: bool; + const _RESERVED = 39; + /// Destination field. + /// + /// If the destination mode bit was clear, then the + /// lower 4 bits contain the bit APIC ID to sent the interrupt to. If + /// the bit was set, the upper 4 bits also contain a set of processors. + /// (See below) + pub const DESTINATION: u8; + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum DestinationMode { + Physical = 0, + Logical = 1, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum DeliveryMode { + /// Normal interrupt delivery. + Normal = 0b000, + /// Lowest priority. + LowPriority = 0b001, + /// System Management Interrupt (SMI). + SystemManagement = 0b010, + /// Non-Maskable Interrupt (NMI). + NonMaskable = 0b100, + /// "INIT" (what does this mean? i don't know!) + Init = 0b101, + /// External interrupt. + External = 0b111, +} + +/// Memory-mapped IOAPIC registers +#[derive(Copy, Clone, Debug)] +#[repr(C)] +struct MmioRegisters { + /// Selects the address to read/write from + address: u32, + _pad: [u32; 3], + /// The data to read/write + data: u32, +} + +// === impl IoApic === + +impl IoApic { + pub(crate) const PIT_TIMER_IRQ: u8 = 0x2; + const REDIRECTION_ENTRY_BASE: u32 = 0x10; + /// Try to construct an `IoApic`. + /// + /// # Returns + /// - `Some(IoApic)` if this CPU supports the APIC interrupt model. + /// - `None` if this CPU does not support APIC interrupt handling. + #[must_use] + pub fn try_new(addr: VAddr) -> Option { + if !super::is_supported() { + tracing::warn!("tried to construct an IO APIC, but the CPU does not support the APIC interrupt model"); + return None; + } + + let registers = unsafe { Volatile::new(&mut *addr.as_ptr::()) }; + let mut ioapic = Self { registers }; + tracing::info!( + ?addr, + id = ioapic.id(), + version = ioapic.version(), + max_entries = ioapic.max_entries(), + "IO APIC" + ); + Some(ioapic) + } + + #[must_use] + pub fn new(addr: VAddr) -> Self { + Self::try_new(addr).expect("CPU does not support APIC interrupt model!") + } + + /// Map all ISA interrupts starting at `base`. + #[tracing::instrument(level = tracing::Level::DEBUG, skip(self))] + pub fn map_isa_irqs(&mut self, base: u8) { + let flags = RedirectionEntry::new() + .with(RedirectionEntry::DELIVERY, DeliveryMode::Normal) + .with(RedirectionEntry::POLARITY, PinPolarity::High) + .with(RedirectionEntry::REMOTE_IRR, false) + .with(RedirectionEntry::TRIGGER, TriggerMode::Edge) + .with(RedirectionEntry::MASKED, true) + .with(RedirectionEntry::DESTINATION, 0xff); + for irq in 0..16 { + let entry = flags.with(RedirectionEntry::VECTOR, base + irq); + self.set_entry(irq, entry); + } + } + + /// Returns the IO APIC's ID. + #[must_use] + pub fn id(&mut self) -> u8 { + let val = self.read(0); + (val >> 24) as u8 + } + + /// Returns the IO APIC's version. + #[must_use] + pub fn version(&mut self) -> u8 { + self.read(0x1) as u8 + } + + /// Returns the maximum number of redirection entries. + #[must_use] + pub fn max_entries(&mut self) -> u8 { + (self.read(0x1) >> 16) as u8 + } + + #[must_use] + pub fn entry(&mut self, irq: u8) -> RedirectionEntry { + let register_low = self + .entry_offset(irq) + .expect("IRQ number exceeds max redirection entries"); + self.entry_raw(register_low) + } + + pub fn set_entry(&mut self, irq: u8, entry: RedirectionEntry) { + tracing::debug!(irq, ?entry, "setting IOAPIC redirection entry"); + let register_low = self + .entry_offset(irq) + .expect("IRQ number exceeds max redirection entries"); + let bits = entry.bits(); + let low = bits as u32; + let high = (bits >> 32) as u32; + self.write(register_low, low); + self.write(register_low + 1, high); + } + + /// Convenience function to mask/unmask an IRQ. + pub fn set_masked(&mut self, irq: u8, masked: bool) { + tracing::debug!(irq, masked, "IoApic::set_masked"); + self.update_entry(irq, |entry| entry.with(RedirectionEntry::MASKED, masked)) + } + + pub fn update_entry( + &mut self, + irq: u8, + update: impl FnOnce(RedirectionEntry) -> RedirectionEntry, + ) { + let register_low = self + .entry_offset(irq) + .expect("IRQ number exceeds max redirection entries"); + let entry = self.entry_raw(register_low); + let new_entry = update(entry); + self.set_entry_raw(register_low, new_entry); + } + + fn entry_offset(&mut self, irq: u8) -> Option { + let max_entries = self.max_entries(); + if irq > max_entries { + tracing::warn!("tried to access redirection entry {irq}, but the IO APIC only supports supports up to {max_entries}"); + return None; + } + + Some(Self::REDIRECTION_ENTRY_BASE + irq as u32 * 2) + } + + #[inline] + fn entry_raw(&mut self, register_low: u32) -> RedirectionEntry { + let low = self.read(register_low); + let high = self.read(register_low + 1); + RedirectionEntry::from_bits((high as u64) << 32 | low as u64) + } + + #[inline] + fn set_entry_raw(&mut self, register_low: u32, entry: RedirectionEntry) { + let bits = entry.bits(); + let low = bits as u32; + let high = (bits >> 32) as u32; + self.write(register_low, low); + self.write(register_low + 1, high); + } + + #[must_use] + + fn read(&mut self, offset: u32) -> u32 { + self.set_offset(offset); + self.registers.map_mut(|ioapic| &mut ioapic.data).read() + } + + fn write(&mut self, offset: u32, value: u32) { + self.set_offset(offset); + self.registers + .map_mut(|ioapic| &mut ioapic.data) + .write(value) + } + + fn set_offset(&mut self, offset: u32) { + assert!( + offset <= 0xff, + "invalid IOAPIC register offset {:#x}", + offset + ); + self.registers + .map_mut(|ioapic| &mut ioapic.address) + .write(offset); + } +} + +// === impl DeliveryMode === + +impl Default for DeliveryMode { + fn default() -> Self { + Self::Normal + } +} + +impl FromBits for DeliveryMode { + const BITS: u32 = 3; + type Error = &'static str; + + fn into_bits(self) -> u64 { + self as u8 as u64 + } + + fn try_from_bits(bits: u64) -> Result { + match bits { + bits if bits as u8 == Self::Normal as u8 => Ok(Self::Normal), + bits if bits as u8 == Self::LowPriority as u8 => Ok(Self::LowPriority), + bits if bits as u8 == Self::SystemManagement as u8 => Ok(Self::SystemManagement), + bits if bits as u8 == Self::NonMaskable as u8 => Ok(Self::NonMaskable), + bits if bits as u8 == Self::Init as u8 => Ok(Self::Init), + bits if bits as u8 == Self::External as u8 => Ok(Self::External), + _ => Err( + "IOAPIC delivery mode must be one of 0b000, 0b001, 0b010, 0b100, 0b101, or 0b111", + ), + } + } +} + +// === impl DestinationMode === + +impl FromBits for DestinationMode { + const BITS: u32 = 1; + type Error = core::convert::Infallible; + + fn into_bits(self) -> u64 { + self as u8 as u64 + } + + fn try_from_bits(bits: u64) -> Result { + Ok(match bits { + 0 => Self::Physical, + 1 => Self::Logical, + _ => unreachable!(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn redirection_entry_is_valid() { + RedirectionEntry::assert_valid(); + + let entry = RedirectionEntry::new() + .with(RedirectionEntry::DELIVERY, DeliveryMode::Normal) + .with(RedirectionEntry::POLARITY, PinPolarity::High) + .with(RedirectionEntry::TRIGGER, TriggerMode::Edge) + .with(RedirectionEntry::MASKED, true) + .with(RedirectionEntry::DESTINATION, 0xff) + .with(RedirectionEntry::VECTOR, 0x30); + println!("{}", entry); + } + + #[test] + fn redirection_entry_offsets() { + assert_eq!( + RedirectionEntry::DELIVERY.least_significant_index(), + 8, + "delivery" + ); + assert_eq!( + RedirectionEntry::DEST_MODE.least_significant_index(), + 11, + "destination mode" + ); + assert_eq!( + RedirectionEntry::QUEUED.least_significant_index(), + 12, + "queued" + ); + assert_eq!( + RedirectionEntry::POLARITY.least_significant_index(), + 13, + "pin polarity" + ); + assert_eq!( + RedirectionEntry::REMOTE_IRR.least_significant_index(), + 14, + "remote IRR" + ); + assert_eq!( + RedirectionEntry::TRIGGER.least_significant_index(), + 15, + "trigger mode" + ); + assert_eq!( + RedirectionEntry::MASKED.least_significant_index(), + 16, + "masked" + ); + assert_eq!( + RedirectionEntry::DESTINATION.least_significant_index(), + 56, + "destination field" + ); + } + + #[test] + fn offsetof() { + let mmregs = MmioRegisters { + address: 0, + _pad: [0, 0, 0], + data: 0, + }; + let addrof = core::ptr::addr_of!(mmregs.data); + assert_eq!( + addrof as *const () as usize, + (&mmregs as *const _ as usize) + 0x10 + ) + } +} diff --git a/hal-x86_64/src/interrupt/apic/local.rs b/hal-x86_64/src/interrupt/apic/local.rs new file mode 100644 index 00000000..b86ad602 --- /dev/null +++ b/hal-x86_64/src/interrupt/apic/local.rs @@ -0,0 +1,504 @@ +use super::{PinPolarity, TriggerMode}; +use crate::{ + cpu::{FeatureNotSupported, Msr}, + mm, + time::{Duration, InvalidDuration}, +}; +use core::{convert::TryInto, marker::PhantomData, num::NonZeroU32}; +use hal_core::{PAddr, VAddr}; +use mycelium_util::fmt; +use raw_cpuid::CpuId; +use volatile::{access, Volatile}; + +#[derive(Debug)] +pub struct LocalApic { + msr: Msr, + base: VAddr, +} + +/// Represents a register in the local APIC's configuration area. +#[derive(Debug)] +pub struct LocalApicRegister { + offset: usize, + name: &'static str, + _ty: PhantomData, +} + +pub trait RegisterAccess { + type Access; + type Target; + fn volatile( + ptr: &'static mut Self::Target, + ) -> Volatile<&'static mut Self::Target, Self::Access>; +} + +impl LocalApic { + const BASE_PADDR_MASK: u64 = 0xffff_ffff_f000; + + // divisor for the APIC timer. + // + // it would be nicer if we could set this to 1, but apparently some + // platforms "don't like" that... + const TIMER_DIVISOR: u32 = 16; + + /// Try to construct a `LocalApic`. + /// + /// # Returns + /// - [`Ok`]`(LocalApic)` if this CPU supports the APIC interrupt model. + /// - [`Err`]`(`[`FeatureNotSupported`]`)` if this CPU does not support APIC + /// interrupt handling. + pub fn try_new() -> Result { + if !super::is_supported() { + return Err(FeatureNotSupported::new("APIC interrupt model")); + } + + let msr = Msr::ia32_apic_base(); + let base_paddr = PAddr::from_u64(msr.read() & Self::BASE_PADDR_MASK); + let base = mm::kernel_vaddr_of(base_paddr); + tracing::debug!(?base, "found local APIC base address"); + assert_ne!(base, VAddr::from_u64(0)); + + Ok(Self { msr, base }) + } + + #[must_use] + pub fn new() -> Self { + Self::try_new().unwrap() + } + + pub fn enable(&self, spurious_vector: u8) { + /// Writing this to the IA32_APIC_BASE MSR enables the local APIC. + const MSR_ENABLE: u64 = 0x800; + /// Bit 8 in the spurious interrupt vector register enables the APIC. + const SPURIOUS_VECTOR_ENABLE_BIT: u32 = 1 << 8; + + // Write the enable bit to the MSR + unsafe { + self.msr.update(|base| base | MSR_ENABLE); + } + + // Enable the APIC by writing the spurious vector to the APIC's + // SPURIOUS_VECTOR register. + let value = spurious_vector as u32 | SPURIOUS_VECTOR_ENABLE_BIT; + unsafe { self.register(register::SPURIOUS_VECTOR).write(value) } + tracing::info!(base = ?self.base, spurious_vector, "local APIC enabled"); + } + + fn timer_frequency_hz(&self) -> u32 { + use register::*; + + let cpuid = CpuId::new(); + + if let Some(undivided_freq_khz) = cpuid.get_hypervisor_info().and_then(|hypervisor| { + tracing::trace!("CPUID contains hypervisor info"); + let freq = hypervisor.apic_frequency(); + tracing::trace!(hypervisor.apic_frequency = ?freq); + NonZeroU32::new(freq?) + }) { + // the hypervisor info CPUID leaf expresses the frequency in kHz, + // and the frequency is not divided by the target timer divisor. + let frequency_hz = undivided_freq_khz.get() / 1000 / Self::TIMER_DIVISOR; + tracing::debug!( + frequency_hz, + "determined APIC frequency from CPUID hypervisor info" + ); + return frequency_hz; + } + + if let Some(undivided_freq_hz) = cpuid.get_tsc_info().and_then(|tsc| { + tracing::trace!("CPUID contains TSC info"); + let freq = tsc.nominal_frequency(); + NonZeroU32::new(freq) + }) { + // divide by the target timer divisor. + let frequency_hz = undivided_freq_hz.get() / Self::TIMER_DIVISOR; + tracing::debug!( + frequency_hz, + "determined APIC frequency from CPUID TSC info" + ); + return frequency_hz; + } + + // CPUID didn't help, so fall back to calibrating the APIC frequency + // using the PIT. + tracing::debug!("calibrating APIC timer frequency using PIT..."); + unsafe { + // set timer divisor to 16 + self.write_register(TIMER_DIVISOR, 0b11); + // set initial count to -1 + self.write_register(TIMER_INITIAL_COUNT, -1i32 as u32); + + // start the timer + self.write_register( + LVT_TIMER, + LvtTimer::new().with(LvtTimer::MODE, TimerMode::OneShot), + ); + } + + // use the PIT to sleep for 10ms + crate::time::PIT + .lock() + .sleep_blocking(Duration::from_millis(10)) + .expect("the PIT should be able to send a 10ms interrupt..."); + + unsafe { + // stop the timer + self.write_register( + LVT_TIMER, + LvtTimer::new().with(LvtTimer::MODE, TimerMode::Periodic), + ); + } + + let elapsed_ticks = unsafe { self.register(TIMER_CURRENT_COUNT).read() }; + // since we slept for ten milliseconds, each tick is 10 kHz. we don't + // need to account for the divisor since we ran the timer at that + // divisor already. + let ticks_per_10ms = (-1i32 as u32).wrapping_sub(elapsed_ticks); + tracing::debug!(?ticks_per_10ms); + // convert the frequency to Hz. + let frequency_hz = ticks_per_10ms * 100; + tracing::debug!(frequency_hz, "calibrated local APIC timer using PIT"); + frequency_hz + } + + #[tracing::instrument( + level = tracing::Level::DEBUG, + name = "LocalApic::start_periodic_timer", + skip(self, interval), + fields(?interval, vector), + err + )] + pub fn start_periodic_timer( + &self, + interval: Duration, + vector: u8, + ) -> Result<(), InvalidDuration> { + let timer_frequency_hz = self.timer_frequency_hz(); + let ticks_per_ms = timer_frequency_hz / 1000; + tracing::trace!( + timer_frequency_hz, + ticks_per_ms, + "starting local APIC timer" + ); + let interval_ms: u32 = interval.as_millis().try_into().map_err(|_| { + InvalidDuration::new( + interval, + "local APIC periodic timer interval exceeds a `u32`", + ) + })?; + let ticks_per_interval = interval_ms.checked_mul(ticks_per_ms).ok_or_else(|| { + InvalidDuration::new( + interval, + "local APIC periodic timer interval requires a number of ticks that exceed a `u32`", + ) + })?; + + unsafe { + let lvt_entry = register::LvtTimer::new() + .with(register::LvtTimer::VECTOR, vector) + .with(register::LvtTimer::MODE, register::TimerMode::Periodic); + // set the divisor to 16. (ed. note: how does 3 say this? idk lol...) + self.write_register(register::TIMER_DIVISOR, 0b11); + self.write_register(register::LVT_TIMER, lvt_entry); + self.write_register(register::TIMER_INITIAL_COUNT, ticks_per_interval); + } + + tracing::info!( + ?interval, + timer_frequency_hz, + ticks_per_ms, + vector, + "started local APIC timer" + ); + + Ok(()) + } + + /// Sends an End of Interrupt (EOI) to the local APIC. + /// + /// This should be called by an interrupt handler after handling a local + /// APIC interrupt. + /// + /// # Safety + /// + /// This should only be called when an interrupt has been triggered by this + /// local APIC. + pub unsafe fn end_interrupt(&self) { + // Write a 0 to the EOI register. + self.register(register::END_OF_INTERRUPT).write(0); + } + + unsafe fn write_register(&self, register: LocalApicRegister, value: T) + where + LocalApicRegister: RegisterAccess, + A: access::Writable, + T: Copy + fmt::Debug + 'static, + { + tracing::trace!(%register, write = ?value); + self.register(register).write(value); + } + + #[must_use] + unsafe fn register( + &self, + register: LocalApicRegister, + ) -> Volatile<&'static mut T, A> + where + LocalApicRegister: RegisterAccess, + { + let addr = self.base + register.offset; + assert!( + addr.is_aligned(16usize), + "Local APIC memory-mapped registers must be 16-byte aligned!" + ); + let reference = &mut *addr.as_ptr::(); + LocalApicRegister::::volatile(reference) + } +} + +impl Default for LocalApic { + fn default() -> Self { + Self::new() + } +} + +pub mod register { + use super::*; + use mycelium_util::bits::{bitfield, FromBits}; + use volatile::access::*; + + impl RegisterAccess for LocalApicRegister { + type Access = ReadOnly; + type Target = T; + fn volatile( + ptr: &'static mut Self::Target, + ) -> Volatile<&'static mut Self::Target, Self::Access> { + Volatile::new_read_only(ptr) + } + } + + impl RegisterAccess for LocalApicRegister { + type Access = ReadWrite; + type Target = T; + fn volatile( + ptr: &'static mut Self::Target, + ) -> Volatile<&'static mut Self::Target, Self::Access> { + Volatile::new(ptr) + } + } + + impl RegisterAccess for LocalApicRegister { + type Access = WriteOnly; + type Target = T; + fn volatile( + ptr: &'static mut Self::Target, + ) -> Volatile<&'static mut Self::Target, Self::Access> { + Volatile::new_write_only(ptr) + } + } + + impl fmt::Display for LocalApicRegister { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { name, offset, _ty } = self; + write!(f, "{name} ({offset:#x})") + } + } + + macro_rules! registers { + ( $( $(#[$m:meta])* $NAME:ident$(<$T:ty>)? = $offset:literal, $access:ident);+ $(;)? ) => { + $( + registers! {@ $(#[$m])* $NAME$(<$T>)? = $offset, $access } + )+ + }; + (@ $(#[$m:meta])* $NAME:ident = $offset:literal, $access:ident) => { + registers!{@ $(#[$m])* $NAME = $offset, $access } + }; + (@ $(#[$m:meta])* $NAME:ident<$T:ty> = $offset:literal, $access:ident )=> { + $(#[$m])* + pub const $NAME: LocalApicRegister<$T, $access> = LocalApicRegister { + offset: $offset, + name: stringify!($NAME), + _ty: PhantomData, + }; + }; + } + + registers! { + + /// Local APIC ID + /// + /// **Access**: read/write + ID = 0x020, ReadWrite; + + /// Local APIC version + /// + /// **Access**: read-only + VERSION = 0x030, ReadOnly; + + /// Task Priority Register (TPR) + /// + /// **Access**: read/write + TASK_PRIORITY = 0x080, ReadWrite; + + /// Arbitration Priority Register (APR) + /// + /// **Access**: read-only + ARBITRATION_PRIORITY = 0x090, ReadOnly; + + /// Processor Priority Register (APR) + /// + /// **Access**: read-only + PROCESSOR_PRIORITY = 0x0a0, ReadOnly; + + /// End of Interrupt (EOI) Register + /// + /// **Access**: write-only + END_OF_INTERRUPT = 0x0b0, WriteOnly; + + /// Remote Read Register (RRD) + /// + /// **Access**: read-only + REMOTE_READ = 0x0c0, ReadOnly; + + /// Logical Destination Register + /// + /// **Access**: read/write + LOGICAL_DEST = 0x0d0, ReadWrite; + + /// Destination Format Register + /// + /// **Access**: read/write + DEST_FORMAT = 0x0e0, ReadWrite; + + /// Spurious Interrupt Vector Register + /// + /// **Access**: read/write + SPURIOUS_VECTOR = 0x0f0, ReadWrite; + + /// In-Service Register (ISR) 0 + /// + /// **Access**: read-only + IN_SERVICE_0 = 0x100, ReadOnly; + + /// Error Status Register (ESR) + /// + /// **Access**: read-only + ERROR_STATUS = 0x280, ReadOnly; + + /// LVT Corrected Machine Check Interrupt (CMCI) Register + /// + /// *Access**: read/write + LVT_CMCI = 0x2f0, ReadWrite; + + ICR_LOW = 0x300, ReadWrite; + ICR_HIGH = 0x310, ReadWrite; + + LVT_TIMER = 0x320, ReadWrite; + LVT_THERMAL = 0x330, ReadWrite; + LVT_PERF = 0x340, ReadWrite; + LVT_LINT0 = 0x350, ReadWrite; + LVT_LINT1 = 0x360, ReadWrite; + LVT_ERROR = 0x370, ReadWrite; + + TIMER_INITIAL_COUNT = 0x380, ReadWrite; + TIMER_CURRENT_COUNT = 0x390, ReadOnly; + TIMER_DIVISOR = 0x3e0, ReadWrite; + } + + bitfield! { + pub struct LvtTimer { + pub const VECTOR: u8; + const _RESERVED_0 = 4; + pub const SEND_PENDING: bool; + const _RESERVED_1 = 3; + pub const MASKED: bool; + pub const MODE: TimerMode; + } + } + + bitfield! { + pub struct LvtEntry { + pub const VECTOR: u8; + const _RESERVED_0 = 2; + pub const NMI: bool; + pub const SEND_PENDING: bool; + pub const POLARITY: PinPolarity; + pub const REMOTE_IRR: bool; + pub const TRIGGER: TriggerMode; + pub const MASKED: bool; + } + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + #[repr(u8)] + pub enum TimerMode { + /// One-shot mode, program count-down value in an initial-count register. + OneShot = 0b00, + /// Periodic mode, program interval value in an initial-count register. + Periodic = 0b01, + /// TSC-Deadline mode, program target value in IA32_TSC_DEADLINE MSR. + TscDeadline = 0b10, + } + + impl FromBits for TimerMode { + const BITS: u32 = 2; + type Error = &'static str; + + fn try_from_bits(bits: u32) -> Result { + match bits { + bits if bits as u8 == Self::OneShot as u8 => Ok(Self::OneShot), + bits if bits as u8 == Self::Periodic as u8 => Ok(Self::Periodic), + bits if bits as u8 == Self::TscDeadline as u8 => Ok(Self::TscDeadline), + _ => Err("0b11 is not a valid local APIC timer mode"), + } + } + + fn into_bits(self) -> u32 { + self as u8 as u32 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lvt_entry_is_valid() { + register::LvtEntry::assert_valid(); + } + + #[test] + fn lvt_timer_is_valid() { + register::LvtTimer::assert_valid(); + } + + #[test] + fn lvt_timer_offsets() { + assert_eq!( + register::LvtTimer::VECTOR.least_significant_index(), + 0, + "vector LSB" + ); + assert_eq!( + register::LvtTimer::VECTOR.most_significant_index(), + 8, + "vector MSB" + ); + assert_eq!( + register::LvtTimer::SEND_PENDING.least_significant_index(), + 12, + "send pending" + ); + assert_eq!( + register::LvtTimer::MASKED.least_significant_index(), + 16, + "masked MSB" + ); + assert_eq!( + register::LvtTimer::MODE.least_significant_index(), + 17, + "mode LSB" + ); + } +} diff --git a/hal-x86_64/src/interrupt/idt.rs b/hal-x86_64/src/interrupt/idt.rs index 4dd2c2c9..1d4b9441 100644 --- a/hal-x86_64/src/interrupt/idt.rs +++ b/hal-x86_64/src/interrupt/idt.rs @@ -102,7 +102,7 @@ impl bits::FromBits for GateKind { // === impl Idt === impl Idt { - const NUM_VECTORS: usize = 256; + pub(super) const NUM_VECTORS: usize = 256; /// Divide-by-zero interrupt (#D0) pub const DIVIDE_BY_ZERO: usize = 0; @@ -155,6 +155,17 @@ impl Idt { /// Chosen by fair die roll, guaranteed to be random. pub const DOUBLE_FAULT_IST_OFFSET: usize = 4; + pub const PIC_PIT_TIMER: usize = 0x20; + + pub(super) const LOCAL_APIC_TIMER: usize = (Self::NUM_VECTORS - 2); + pub(super) const LOCAL_APIC_SPURIOUS: usize = (Self::NUM_VECTORS - 1); + pub(super) const PIC_BIG_START: usize = 0x20; + pub(super) const PIC_LITTLE_START: usize = 0x28; + // put the IOAPIC right after the PICs + pub(super) const IOAPIC_START: usize = 0x30; + pub(super) const IOAPIC_PIT_TIMER: usize = + Self::IOAPIC_START + super::apic::IoApic::PIT_TIMER_IRQ as usize; + pub const fn new() -> Self { Self { descriptors: [Descriptor::null(); Self::NUM_VECTORS], @@ -170,13 +181,20 @@ impl Idt { } pub fn load(&'static self) { - let ptr = cpu::DtablePtr::new(self); - tracing::debug!(?ptr, "loading IDT"); unsafe { // Safety: the `'static` bound ensures the IDT isn't going away // unless you did something really evil. - cpu::intrinsics::lidt(ptr) + self.load_raw() } + } + + /// # Safety + /// + /// The referenced IDT must be valid for the `'static` lifetime. + pub unsafe fn load_raw(&self) { + let ptr = cpu::DtablePtr::new_unchecked(self); + tracing::debug!(?ptr, "loading IDT"); + cpu::intrinsics::lidt(ptr); tracing::debug!("IDT loaded!"); } } diff --git a/hal-x86_64/src/interrupt/pic.rs b/hal-x86_64/src/interrupt/pic.rs index 54fa1dac..da37c42f 100644 --- a/hal-x86_64/src/interrupt/pic.rs +++ b/hal-x86_64/src/interrupt/pic.rs @@ -1,6 +1,7 @@ use crate::cpu; use hal_core::interrupt::{Handlers, RegistrationError}; +#[derive(Debug)] pub(crate) struct Pic { address: u8, command: cpu::Port, @@ -17,11 +18,14 @@ impl Pic { } } +#[derive(Debug)] pub struct CascadedPic { sisters: PicSisters, } // two of them + +#[derive(Debug)] struct PicSisters { big: Pic, little: Pic, diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs index ffad1908..415dc9f4 100644 --- a/src/arch/x86_64.rs +++ b/src/arch/x86_64.rs @@ -18,8 +18,6 @@ mod tests; pub type MinPageSize = mm::size::Size4Kb; -pub use self::interrupt::init_interrupts; - pub fn tick_timer() { interrupt::TIMER.advance_ticks(0); } @@ -46,16 +44,29 @@ pub fn arch_entry(info: &'static mut bootloader::BootInfo) -> ! { crate::kernel_start(boot_info, archinfo); } -pub fn init(info: &impl BootInfo, archinfo: &ArchInfo) { +pub fn init(_info: &impl BootInfo, archinfo: &ArchInfo) { pci::init_pci(); - if let Some(rsdp_addr) = archinfo.rsdp_addr { - acpi::bringup_smp(rsdp_addr) - .expect("failed to bring up application processors! this is bad news!"); + if let Some(rsdp) = archinfo.rsdp_addr { + let acpi = acpi::acpi_tables(rsdp); + let platform_info = acpi.and_then(|acpi| acpi.platform_info()); + match platform_info { + Ok(platform) => { + tracing::debug!("found ACPI platform info"); + interrupt::enable_hardware_interrupts(Some(&platform.interrupt_model)); + acpi::bringup_smp(&platform) + .expect("failed to bring up application processors! this is bad news!"); + return; + } + Err(error) => tracing::warn!(?error, "missing ACPI platform info"), + } } else { // TODO(eliza): try using MP Table to bringup application processors? tracing::warn!("no RSDP from bootloader, skipping SMP bringup"); } + + // no ACPI + interrupt::enable_hardware_interrupts(None); } // TODO(eliza): this is now in arch because it uses the serial port, would be diff --git a/src/arch/x86_64/acpi.rs b/src/arch/x86_64/acpi.rs index 5a4c95b3..c99db914 100644 --- a/src/arch/x86_64/acpi.rs +++ b/src/arch/x86_64/acpi.rs @@ -9,18 +9,23 @@ pub enum Error { Other(&'static str), } -#[tracing::instrument(err)] -pub fn bringup_smp(rsdp_addr: PAddr) -> Result<(), Error> { - use acpi::platform::{self, interrupt::InterruptModel}; +pub(super) fn acpi_tables( + rsdp_addr: PAddr, +) -> Result, AcpiError> { tracing::info!("trying to parse ACPI tables from RSDP..."); let tables = unsafe { AcpiTables::from_rsdp(IdentityMappedAcpiHandler, rsdp_addr.as_usize()) }?; tracing::info!("found ACPI tables!"); + Ok(tables) +} + +#[tracing::instrument(err, skip(platform))] +pub fn bringup_smp(platform: &acpi::PlatformInfo) -> Result<(), Error> { + use acpi::platform::{self, interrupt::InterruptModel}; - let platform = tables.platform_info()?; tracing::info!(?platform.power_profile); let apic = match platform.interrupt_model { - acpi::InterruptModel::Apic(apic) => { + acpi::InterruptModel::Apic(ref apic) => { tracing::info!("APIC interrupt model detected"); apic } @@ -29,7 +34,7 @@ pub fn bringup_smp(rsdp_addr: PAddr) -> Result<(), Error> { "MADT does not indicate support for APIC interrupt model!", )); } - model => { + ref model => { tracing::warn!(?model, "unknown interrupt model detected"); return Err(Error::Other( "MADT does not indicate support for APIC interrupt model!", @@ -40,10 +45,11 @@ pub fn bringup_smp(rsdp_addr: PAddr) -> Result<(), Error> { tracing::debug!(?apic); let platform::ProcessorInfo { - application_processors, - boot_processor, + ref application_processors, + ref boot_processor, } = platform .processor_info + .as_ref() .ok_or(Error::Other("no processor information found in MADT!"))?; tracing::info!("boot processor seems normalish"); tracing::debug!(?boot_processor); @@ -57,7 +63,7 @@ pub fn bringup_smp(rsdp_addr: PAddr) -> Result<(), Error> { } #[derive(Clone)] -struct IdentityMappedAcpiHandler; +pub(super) struct IdentityMappedAcpiHandler; impl AcpiHandler for IdentityMappedAcpiHandler { unsafe fn map_physical_region( diff --git a/src/arch/x86_64/boot.rs b/src/arch/x86_64/boot.rs index e46ee22f..9c4f1182 100644 --- a/src/arch/x86_64/boot.rs +++ b/src/arch/x86_64/boot.rs @@ -3,7 +3,7 @@ use super::framebuf::{self, FramebufWriter}; use bootloader::boot_info; use hal_core::{boot::BootInfo, mem, PAddr, VAddr}; -use hal_x86_64::{cpu, mm, serial, vga}; +use hal_x86_64::{mm, serial, vga}; use mycelium_util::sync::InitOnce; #[derive(Debug)] @@ -94,7 +94,12 @@ impl BootInfo for RustbootBootInfo { // disable really noisy traces from maitake // TODO(eliza): it would be nice if this was configured by // non-arch-specific OS code... - const DISABLED_TARGETS: &[&str] = &["maitake::time"]; + const DISABLED_TARGETS: &[&str] = &[ + "maitake::time", + "maitake::task", + "runtime::waker", + "mycelium_alloc", + ]; DISABLED_TARGETS .iter() .all(|target| !meta.target().starts_with(target)) diff --git a/src/arch/x86_64/interrupt.rs b/src/arch/x86_64/interrupt.rs index 5610ba87..ea494f4d 100644 --- a/src/arch/x86_64/interrupt.rs +++ b/src/arch/x86_64/interrupt.rs @@ -11,17 +11,23 @@ use maitake::time; use mycelium_util::{fmt, sync}; #[tracing::instrument] -pub fn init_interrupts() { +pub fn enable_exceptions() { init_gdt(); tracing::info!("GDT initialized!"); - init::(); + Controller::init::(); tracing::info!("IDT initialized!"); +} - match time::set_global_timer(&TIMER) { - Ok(_) => tracing::info!(granularity = ?TIMER_INTERVAL, "global timer initialized"), - Err(_) => unreachable!("failed to initialize global timer, as it was already initialized (this shouldn't happen!)"), - } +#[tracing::instrument(skip(acpi))] +pub fn enable_hardware_interrupts(acpi: Option<&acpi::InterruptModel>) { + let controller = Controller::enable_hardware_interrupts(acpi); + controller + .start_periodic_timer(TIMER_INTERVAL) + .expect("10ms should be a reasonable interval for the PIT or local APIC timer..."); + time::set_global_timer(&TIMER) + .expect("`enable_hardware_interrupts` should only be called once!"); + tracing::info!(granularity = ?TIMER_INTERVAL, "global timer initialized") } // TODO(eliza): put this somewhere good. @@ -50,11 +56,8 @@ static TSS: sync::Lazy = sync::Lazy::new(|| { static GDT: sync::InitOnce = sync::InitOnce::uninitialized(); -/// The IBM PC's [8253 PIT timer] fires timer 0 (interrupt 8) every 55ms. -/// -/// [8253 PIT timer]: https://en.wikipedia.org/wiki/Intel_8253#IBM_PC_programming_tips_and_hints -const TIMER_INTERVAL: time::Duration = time::Duration::from_millis(55); -pub(super) static TIMER: time::Timer = maitake::time::Timer::new(TIMER_INTERVAL); +const TIMER_INTERVAL: time::Duration = time::Duration::from_millis(10); +pub(super) static TIMER: time::Timer = time::Timer::new(TIMER_INTERVAL); static TEST_INTERRUPT_WAS_FIRED: AtomicUsize = AtomicUsize::new(0); @@ -119,7 +122,7 @@ impl hal_core::interrupt::Handlers for InterruptHandlers { } #[inline] -#[tracing::instrument(level = "debug")] +#[tracing::instrument(level = tracing::Level::DEBUG)] pub(super) fn init_gdt() { tracing::trace!("initializing GDT..."); let mut gdt = Gdt::new(); diff --git a/src/arch/x86_64/tests.rs b/src/arch/x86_64/tests.rs index 14820c64..9e399e5f 100644 --- a/src/arch/x86_64/tests.rs +++ b/src/arch/x86_64/tests.rs @@ -1,4 +1,3 @@ -use super::*; use hal_x86_64::mm; mycotest::decl_test! { diff --git a/src/lib.rs b/src/lib.rs index 58cf108a..5548d71b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,7 +103,7 @@ pub fn kernel_start(bootinfo: impl BootInfo, archinfo: crate::arch::ArchInfo) -> tracing::trace!("hahahaha yayyyy we drew a screen!"); } - arch::init_interrupts(); + arch::interrupt::enable_exceptions(); bootinfo.init_paging(); ALLOC.init(&bootinfo); @@ -164,16 +164,16 @@ fn kernel_main() -> ! { }) } - // rt::spawn(async move { - // loop { - // let result = futures_util::try_join! { - // spawn_sleep(time::Duration::from_secs(2)), - // spawn_sleep(time::Duration::from_secs(5)), - // spawn_sleep(time::Duration::from_secs(10)), - // }; - // tracing::info!(?result); - // } - // }); + rt::spawn(async move { + loop { + futures_util::try_join! { + spawn_sleep(time::Duration::from_secs(2)), + spawn_sleep(time::Duration::from_secs(5)), + spawn_sleep(time::Duration::from_secs(10)), + } + .expect("sleep futures failed!"); + } + }); let mut core = rt::Core::new(); loop {