From 7492f4b68de394921bae0880870c76bb3d96e66a Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Fri, 13 Sep 2024 02:09:48 +0200 Subject: [PATCH] x86_64: enable APIC and interrupts and add test code for system timer --- oro-arch-x86_64/src/init.rs | 16 +- oro-arch-x86_64/src/interrupt.rs | 187 ++++++++++++++++++++ oro-arch-x86_64/src/lapic.rs | 288 +++++++++++++++++++++++++++++++ oro-arch-x86_64/src/lib.rs | 1 + 4 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 oro-arch-x86_64/src/interrupt.rs diff --git a/oro-arch-x86_64/src/init.rs b/oro-arch-x86_64/src/init.rs index 0ec9a7d..e49a9bf 100644 --- a/oro-arch-x86_64/src/init.rs +++ b/oro-arch-x86_64/src/init.rs @@ -55,14 +55,26 @@ pub unsafe fn initialize_primary(pat: OffsetTranslator, pfa: crate::Pfa) { /// (i.e. boot, or powerdown/subsequent bringup). pub unsafe fn boot(lapic: Lapic) -> ! { // SAFETY(qix-): THIS MUST ABSOLUTELY BE FIRST. - let _kernel = crate::Kernel::initialize_for_core( + let kernel = crate::Kernel::initialize_for_core( KERNEL_STATE.assume_init_ref(), crate::CoreState { lapic }, ) .expect("failed to initialize kernel"); + crate::interrupt::install_idt(); dbg!("boot"); - crate::asm::hang(); + // XXX DEBUG + let id = kernel.core().lapic.id(); + loop { + dbg!( + "DBG: timer counter: {id}: {}", + crate::interrupt::TIM_COUNT.load(core::sync::atomic::Ordering::Relaxed) + ); + + kernel.core().lapic.set_timer_initial_count(1_000_000); + + crate::asm::halt_once(); + } } diff --git a/oro-arch-x86_64/src/interrupt.rs b/oro-arch-x86_64/src/interrupt.rs new file mode 100644 index 0000000..532fcf1 --- /dev/null +++ b/oro-arch-x86_64/src/interrupt.rs @@ -0,0 +1,187 @@ +//! Interrupt handling for x86_64 architecture. + +use crate::lapic::{ApicSvr, ApicTimerConfig, ApicTimerMode}; + +/// A single IDT (Interrupt Descriptor Table) entry. +#[derive(Debug, Clone, Copy, Default)] +#[repr(C, packed)] +struct IdtEntry { + /// The lower 16 bits of the ISR (Interrupt Service Routine) address. + isr_low: u16, + /// The code segment selector used when handling the interrupt. + kernel_cs: u16, + /// The IST (Interrupt Stack Table) index. + ist: u8, + /// Attributes and flags for the IDT entry, including type. + attributes: u8, + /// The middle 16 bits of the ISR address. + isr_mid: u16, + /// The higher 32 bits of the ISR address. + isr_high: u32, + /// Reserved. + _reserved: u32, +} + +impl IdtEntry { + /// Creates a new, empty IDT entry. + pub const fn new() -> Self { + Self { + isr_low: 0, + kernel_cs: 0, + ist: 0, + attributes: 0, + isr_mid: 0, + isr_high: 0, + _reserved: 0, + } + } + + /// Sets the ISR address for the IDT entry. + /// + /// # Safety + /// Caller must ensure that the given address is + /// a real function that is suitable for handling + /// the interrupt. + pub const unsafe fn with_isr_raw(mut self, isr: u64) -> Self { + self.isr_low = isr as u16; + self.isr_mid = (isr >> 16) as u16; + self.isr_high = (isr >> 32) as u32; + + self + } + + /// Sets the ISR handler as a function pointer. + pub fn with_isr(self, isr: unsafe extern "C" fn() -> !) -> Self { + unsafe { self.with_isr_raw(isr as usize as u64) } + } + + /// Sets the code segment selector for the IDT entry. + /// + /// There is no configurable index for this; it's always + /// set to 0x08 (the kernel code segment). + pub const fn with_kernel_cs(mut self) -> Self { + self.kernel_cs = 0x08; + self + } + + /// Sets the attributes for the IDT entry. + pub const fn with_attributes(mut self, attributes: u8) -> Self { + self.attributes = attributes; + self + } +} + +/// The IDT (Interrupt Descriptor Table) for the kernel. +static mut IDT: Aligned16<[IdtEntry; 256]> = Aligned16([IdtEntry::new(); 256]); + +/// XXX DEBUG +pub static TIM_COUNT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0); + +/// The ISR (Interrupt Service Routine) for the system timer. +#[no_mangle] +unsafe extern "C" fn isr_sys_timer_rust() { + TIM_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + crate::Kernel::get().core().lapic.eoi(); +} + +/// The ISR (Interrupt Service Routine) trampoline stub for the system timer. +#[naked] +unsafe extern "C" fn isr_sys_timer() -> ! { + core::arch::asm!( + "cli", + "call isr_sys_timer_rust", + "sti", + "iretq", + options(noreturn) + ); +} + +/// The ISR (Interrupt Service Routine) for the APIC spurious interrupt. +#[no_mangle] +unsafe extern "C" fn isr_apic_svr_rust() { + crate::Kernel::get().core().lapic.eoi(); +} + +/// The ISR (Interrupt Service Routine) trampoline stub for the APIC spurious interrupt. +#[naked] +unsafe extern "C" fn isr_apic_svr() -> ! { + core::arch::asm!( + "cli", + "call isr_apic_svr_rust", + "sti", + "iretq", + options(noreturn) + ); +} + +/// Aligns a `T` value to 16 bytes. +#[repr(C, align(16))] +struct Aligned16(pub T); + +/// The vector for the main system timer interrupt. +const TIMER_VECTOR: u8 = 32; +/// The vector for the APIC spurious interrupt. +const APIC_SVR_VECTOR: u8 = 255; + +/// Installs the IDT (Interrupt Descriptor Table) for the kernel +/// and enables interrupts. +/// +/// # Safety +/// Modifies global state, and must be called only once. +/// +/// The kernel MUST be fully initialized before calling this function. +pub unsafe fn install_idt() { + // Get the LAPIC. + let lapic = &crate::Kernel::get().core().lapic; + + /// The IDTR (Interrupt Descriptor Table Register) structure, + /// read in by the `lidt` instruction. + #[repr(C, packed)] + struct Idtr { + /// How long the IDT is in bytes, minus 1. + limit: u16, + /// The base address of the IDT. + base: *const IdtEntry, + } + + #[allow(static_mut_refs)] + let idtr = Idtr { + limit: (core::mem::size_of_val(&IDT) - 1) as u16, + base: IDT.0.as_ptr(), + }; + + core::arch::asm!( + "lidt [{}]", + "sti", + in(reg) &idtr, + options(nostack, preserves_flags) + ); + + // Set up the main system timer. + IDT.0[usize::from(TIMER_VECTOR)] = IdtEntry::new() + .with_kernel_cs() + .with_attributes(0x8E) + .with_isr(isr_sys_timer); + + lapic.set_timer_divider(crate::lapic::ApicTimerDivideBy::Div128); + + // Note: this also enables the timer interrupts + lapic.configure_timer( + ApicTimerConfig::new() + .with_vector(TIMER_VECTOR) + .with_mode(ApicTimerMode::OneShot), + ); + + // Set up the APIC spurious interrupt. + // This also enables the APIC if it isn't already. + IDT.0[usize::from(APIC_SVR_VECTOR)] = IdtEntry::new() + .with_kernel_cs() + .with_attributes(0x8E) + .with_isr(isr_apic_svr); + + lapic.set_spurious_vector( + ApicSvr::new() + .with_vector(APIC_SVR_VECTOR) + .with_software_enable(), + ); +} diff --git a/oro-arch-x86_64/src/lapic.rs b/oro-arch-x86_64/src/lapic.rs index 5082ee4..b47628c 100644 --- a/oro-arch-x86_64/src/lapic.rs +++ b/oro-arch-x86_64/src/lapic.rs @@ -3,6 +3,8 @@ //! //! Documentation found in Section 11 of the Intel SDM Volume 3A. +use core::fmt; + /// The LAPIC (Local Advanced Programmable Interrupt Controller (APIC)) /// controller. pub struct Lapic { @@ -163,6 +165,97 @@ impl Lapic { self.wait_for_ipi_ack(); } } + + /// Sends an End Of Interrupt (EOI) signal to the LAPIC. + pub fn eoi(&self) { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + self.base.add(0xB0).cast::().write_volatile(0); + } + } + + /// Configures the LAPIC timer. + pub fn configure_timer(&self, config: ApicTimerConfig) { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + self.base.add(0x320).cast::().write_volatile(config.0); + } + } + + /// Sets the LAPIC timer divider value. + pub fn set_timer_divider(&self, divide_by: ApicTimerDivideBy) { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + self.base + .add(0x3E0) + .cast::() + .write_volatile(divide_by as u32); + } + } + + /// Reads the LAPIC timer's configuration. + #[must_use] + pub fn timer_config(&self) -> ApicTimerConfig { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + ApicTimerConfig(self.base.add(0x320).cast::().read_volatile()) + } + } + + /// Reads the LAPIC timer's divide-by value. + #[must_use] + pub fn timer_divide_by(&self) -> ApicTimerDivideBy { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned, + // SAFETY(qix-): and the transmuted bits are always valid. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + let v = self.base.add(0x3E0).cast::().read_volatile(); + let v = v & 0b1011; + core::mem::transmute(v) + } + } + + /// Sets the LAPIC timer's initial count. + pub fn set_timer_initial_count(&self, count: u32) { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + self.base.add(0x380).cast::().write_volatile(count); + } + } + + /// Reads the LAPIC timer's current count. + #[must_use] + pub fn timer_current_count(&self) -> u32 { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + self.base.add(0x390).cast::().read_volatile() + } + } + + /// Reads the LAPIC's spurrious interrupt vector (SVR) value. + #[must_use] + pub fn spurious_vector(&self) -> ApicSvr { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + ApicSvr(self.base.add(0xF0).cast::().read_volatile()) + } + } + + /// Sets the LAPIC's spurrious interrupt vector (SVR) value. + pub fn set_spurious_vector(&self, svr: ApicSvr) { + // SAFETY(qix-): The LAPIC base address is trusted to be valid and aligned. + #[expect(clippy::cast_ptr_alignment)] + unsafe { + self.base.add(0xF0).cast::().write_volatile(svr.0); + } + } } /// A decoded LAPIC version. @@ -175,3 +268,198 @@ pub struct LapicVersion { /// The LAPIC version. pub version: u8, } + +/// The configuration for the LAPIC timer. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct ApicTimerConfig(u32); + +impl fmt::Debug for ApicTimerConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ApicTimerConfig") + .field("vector", &self.vector()) + .field("mode", &self.mode()) + .field("masked", &self.masked()) + .finish() + } +} + +impl Default for ApicTimerConfig { + fn default() -> Self { + Self::new() + } +} + +impl ApicTimerConfig { + /// Creates a new LAPIC timer configuration. + #[must_use] + pub const fn new() -> Self { + Self(0) + } + + /// Sets the interrupt vector to be called when + /// the timer fires. + #[must_use] + pub const fn with_vector(mut self, vector: u8) -> Self { + self.0 = (self.0 & 0xFFFF_FF00) | (vector as u32); + self + } + + /// Marks the timer interrupt as masked. + #[must_use] + pub const fn with_masked(mut self) -> Self { + self.0 |= 1 << 16; + self + } + + /// Sets the timer mode. + #[must_use] + pub const fn with_mode(mut self, mode: ApicTimerMode) -> Self { + self.0 = (self.0 & 0xFFF9_FFFF) | (mode as u32); + self + } + + /// Gets the interrupt vector. + #[must_use] + pub const fn vector(self) -> u8 { + (self.0 & 0xFF) as u8 + } + + /// Gets the timer mode. Returns `None` if the mode bits are invalid. + #[must_use] + pub const fn mode(self) -> Option { + let bits = (self.0 >> 17) & 0b11; + if bits == 0b11 { + None + } else { + // SAFETY(qix-): The mode bits are always valid. + Some(unsafe { core::mem::transmute::(bits) }) + } + } + + /// Returns whether the timer interrupt is masked. + #[must_use] + pub const fn masked(self) -> bool { + (self.0 & (1 << 16)) != 0 + } +} + +/// The mode of the LAPIC timer. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum ApicTimerMode { + /// Program count-down value in an initial-count register. + OneShot = 0, + /// Program interval value in an initial-count register + Periodic = (0b01 << 17), + /// Program target value in `IA32_TSC_DEADLINE` MSR + TscDeadline = (0b10 << 17), +} + +/// The LAPIC timer divide-by value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum ApicTimerDivideBy { + /// Divide by 2. + Div2 = 0b0000, + /// Divide by 4. + Div4 = 0b0001, + /// Divide by 8. + Div8 = 0b0010, + /// Divide by 16. + Div16 = 0b0011, + /// Divide by 32. + Div32 = 0b1000, + /// Divide by 64. + Div64 = 0b1001, + /// Divide by 128. + Div128 = 0b1010, + /// Divide by 1. + Div1 = 0b1011, +} + +/// The spurious vector register (SVR) value for the LAPIC. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct ApicSvr(u32); + +impl Default for ApicSvr { + fn default() -> Self { + Self::new() + } +} + +impl ApicSvr { + /// Creates a new LAPIC SVR value. + #[must_use] + pub const fn new() -> Self { + Self(0) + } + + /// Sets the LAPIC software enable bit. + #[must_use] + pub const fn with_software_enable(mut self) -> Self { + self.0 |= 1 << 8; + self + } + + /// Sets the LAPIC spurious interrupt vector. + #[must_use] + pub const fn with_vector(mut self, vector: u8) -> Self { + self.0 = (self.0 & 0xFFFF_FF00) | (vector as u32); + self + } + + /// Sets the focus processor bit. + #[must_use] + pub const fn with_focus_processor(mut self) -> Self { + self.0 |= 1 << 9; + self + } + + /// Sets the EOI broadcast suppression bit + /// (calling this *suppresses* EOI broadcast). + #[must_use] + pub const fn with_eoi_broadcast_suppression(mut self) -> Self { + self.0 |= 1 << 12; + self + } + + /// Gets the LAPIC software enable bit. + #[must_use] + pub const fn software_enable(self) -> bool { + (self.0 & (1 << 8)) != 0 + } + + /// Gets the LAPIC spurious interrupt vector. + #[must_use] + pub const fn vector(self) -> u8 { + (self.0 & 0xFF) as u8 + } + + /// Gets the focus processor bit. + #[must_use] + pub const fn focus_processor(self) -> bool { + (self.0 & (1 << 9)) != 0 + } + + /// Gets the EOI broadcast suppression bit. + #[must_use] + pub const fn eoi_broadcast_suppression(self) -> bool { + (self.0 & (1 << 12)) != 0 + } +} + +impl fmt::Debug for ApicSvr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ApicSvr") + .field("software_enable", &self.software_enable()) + .field("vector", &self.vector()) + .field("focus_processor", &self.focus_processor()) + .field( + "eoi_broadcast_suppression", + &self.eoi_broadcast_suppression(), + ) + .finish() + } +} diff --git a/oro-arch-x86_64/src/lib.rs b/oro-arch-x86_64/src/lib.rs index 986c2b0..3533da4 100644 --- a/oro-arch-x86_64/src/lib.rs +++ b/oro-arch-x86_64/src/lib.rs @@ -68,6 +68,7 @@ pub mod asm; pub mod boot; pub mod gdt; +pub mod interrupt; pub mod lapic; pub mod mem; pub mod reg;