diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 334fb16a..1daea575 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -301,27 +301,6 @@ jobs: - name: run Miri tests run: just miri maitake - ### mycelium-util ### - - # run loom tests - util_loom: - needs: changed_paths - if: needs.changed_paths.outputs.should_skip != 'true' || !fromJSON(needs.changed_paths.outputs.paths_result).util.should_skip - runs-on: ubuntu-latest - name: Loom tests (mycelium-util) - steps: - - name: install rust toolchain - run: rustup show - - uses: actions/checkout@v4 - - name: install Just - uses: extractions/setup-just@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: install nextest - uses: taiki-e/install-action@nextest - - name: run loom tests - run: just loom mycelium-util - # dummy job that depends on all required checks all_systems_go: needs: @@ -337,7 +316,6 @@ jobs: - maitake_no_default_features - maitake_loom - maitake_miri - - util_loom runs-on: ubuntu-latest steps: - run: exit 0 diff --git a/bitfield/README.md b/bitfield/README.md index 1551ac41..239b2c50 100644 --- a/bitfield/README.md +++ b/bitfield/README.md @@ -3,6 +3,7 @@ 🍄 bitfield utilities, courtesy of [Mycelium]. [![crates.io][crates-badge]][crates-url] +[![Changelog][changelog-badge]][changelog-url] [![Documentation][docs-badge]][docs-url] [![Documentation (HEAD)][docs-main-badge]][docs-main-url] [![MIT licensed][mit-badge]][mit-url] @@ -11,6 +12,9 @@ [crates-badge]: https://img.shields.io/crates/v/mycelium-bitfield.svg [crates-url]: https://crates.io/crates/mycelium-bitfield +[changelog-badge]: + https://img.shields.io/crates/v/mycelium-bitfield?label=changelog&color=blue +[changelog-url]: https://github.com/hawkw/mycelium/blob/main/mycelium-bitfield/CHANGELOG.md [docs-badge]: https://docs.rs/mycelium-bitfield/badge.svg [docs-url]: https://docs.rs/mycelium-bitfield [docs-main-badge]: https://img.shields.io/netlify/3ec00bb5-251a-4f83-ac7f-3799d95db0e6?label=docs%20%28main%20branch%29 diff --git a/cordyceps/README.md b/cordyceps/README.md index 8ed3f2ce..ccb71a5b 100644 --- a/cordyceps/README.md +++ b/cordyceps/README.md @@ -3,6 +3,7 @@ 🍄 the [Mycelium] intrusive data structures library. [![crates.io][crates-badge]][crates-url] +[![Changelog][changelog-badge]][changelog-url] [![Documentation][docs-badge]][docs-url] [![Documentation (HEAD)][docs-main-badge]][docs-main-url] [![MIT licensed][mit-badge]][mit-url] @@ -11,6 +12,9 @@ [crates-badge]: https://img.shields.io/crates/v/cordyceps.svg [crates-url]: https://crates.io/crates/cordyceps +[changelog-badge]: + https://img.shields.io/crates/v/cordyceps?label=changelog&color=blue +[changelog-url]: https://github.com/hawkw/mycelium/blob/main/cordyceps/CHANGELOG.md [docs-badge]: https://docs.rs/cordyceps/badge.svg [docs-url]: https://docs.rs/cordyceps [docs-main-badge]: https://img.shields.io/netlify/3ec00bb5-251a-4f83-ac7f-3799d95db0e6?label=docs%20%28main%20branch%29 @@ -106,4 +110,4 @@ The following features are available (this list is incomplete; you can help by [ [queue]: https://docs.rs/cordyceps/latest/cordyceps/mpsc_queue/struct.MpscQueue.html [`Linked`]: https://docs.rs/cordyceps/latest/cordyceps/trait.Linked.html [`liballoc`]: https://doc.rust-lang.org/alloc/ -[`libstd`]: https://doc.rust-lang.org/std/ \ No newline at end of file +[`libstd`]: https://doc.rust-lang.org/std/ diff --git a/default.nix b/default.nix index faf941c4..5bbf29d0 100644 --- a/default.nix +++ b/default.nix @@ -28,6 +28,6 @@ pkgs.buildEnv { CURL_CA_BUNDLE = "${cacert}/etc/ca-bundle.crt"; CARGO_TERM_COLOR = "always"; RUST_BACKTRACE = "full"; - LD_LIBRARY_PATH = "${lib.makeLibraryPath [ zlib ]}"; + # LD_LIBRARY_PATH = "${lib.makeLibraryPath [ zlib ]}"; }; } diff --git a/hal-x86_64/src/cpu/msr.rs b/hal-x86_64/src/cpu/msr.rs index 69547ed5..abfb8126 100644 --- a/hal-x86_64/src/cpu/msr.rs +++ b/hal-x86_64/src/cpu/msr.rs @@ -281,9 +281,7 @@ impl Msr { options(nomem, nostack, preserves_flags) ); } - let result = (hi as u64) << 32 | (lo as u64); - tracing::trace!(rdmsr = %self, value = fmt::hex(result)); - result + (hi as u64) << 32 | (lo as u64) } /// Writes the given raw `u64` value to this MSR. diff --git a/hal-x86_64/src/interrupt.rs b/hal-x86_64/src/interrupt.rs index c66f3f89..43cfadf7 100644 --- a/hal-x86_64/src/interrupt.rs +++ b/hal-x86_64/src/interrupt.rs @@ -1,7 +1,8 @@ -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}; +use hal_core::mem::page; use mycelium_util::{ bits, fmt, sync::{ @@ -15,7 +16,7 @@ pub mod apic; pub mod idt; pub mod pic; -use self::apic::{IoApic, LocalApic}; +use self::apic::{IoApicSet, LocalApic}; pub use idt::Idt; pub use pic::CascadedPic; @@ -44,7 +45,8 @@ pub type Isr = extern "x86-interrupt" fn(&mut Context); #[derive(Debug)] pub enum PeriodicTimerError { Pit(time::PitError), - Apic(time::InvalidDuration), + InvalidDuration(time::InvalidDuration), + Apic(apic::local::LocalApicError), } #[derive(Debug)] @@ -68,11 +70,8 @@ enum InterruptModel { /// [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: Mutex, + io: apic::IoApicSet, + local: apic::local::Handle, }, } @@ -134,6 +133,77 @@ pub struct Registers { static IDT: Mutex = Mutex::new_with_raw_mutex(idt::Idt::new(), Spinlock::new()); static INTERRUPT_CONTROLLER: InitOnce = InitOnce::uninitialized(); +pub enum MaskError { + NotHwIrq, +} + +/// ISA interrupt vectors +/// +/// See: [the other wiki](https://wiki.osdev.org/Interrupts#General_IBM-PC_Compatible_Interrupt_Information) +#[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, + ]; +} + +#[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; @@ -152,9 +222,65 @@ impl Controller { } } + 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), + } + } + + 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( + &self, + f: impl FnOnce(&LocalApic) -> T, + ) -> Result { + self.local_apic_handle()?.with(f) + } + + /// This should *not* be called by the boot processor + pub fn initialize_local_apic( + &self, + frame_alloc: &A, + pagectrl: &mut impl page::Map, + ) -> Result<(), apic::local::LocalApicError> + where + A: page::Alloc, + { + 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.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, + frame_alloc: &impl page::Alloc, ) -> &'static Self { let mut pics = pic::CascadedPic::new(); // regardless of whether APIC or PIC interrupt handling will be used, @@ -169,7 +295,7 @@ impl Controller { 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"); @@ -181,38 +307,20 @@ impl Controller { } 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); + // configure the I/O APIC(s) + let io = IoApicSet::new(apic_info, frame_alloc, &mut pagectrl, Idt::ISA_BASE 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); + // 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); + } - // unmask the PS/2 keyboard interrupt as well. - io.set_masked(IoApic::PS2_KEYBOARD_IRQ, false); + let model = InterruptModel::Apic { local, io }; - // enable the local APIC - let local = LocalApic::new(&mut pagectrl, frame_alloc); - local.enable(Idt::LOCAL_APIC_SPURIOUS as u8); + tracing::trace!(interrupt_model = ?model); - InterruptModel::Apic { - local, - io: Mutex::new_with_raw_mutex(io, Spinlock::new()), - } + INTERRUPT_CONTROLLER.init(Self { model }) } model => { if model.is_none() { @@ -229,21 +337,37 @@ impl Controller { // 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); unsafe { crate::cpu::intrinsics::sti(); } + // There's a weird behavior in QEMU where, apparently, when we unmask + // the PIT interrupt, it might fire spuriously *as soon as its + // unmasked*, even if we haven't actually done an `sti`. I don't get + // this and it seems wrong, but it seems to happen occasionally with the + // default QEMU acceleration, and pretty consistently with `-machine + // accel=kvm`, so *maybe* it can also happen on a real computer? + // + // Anyway, because of this, we can't unmask the PIT interrupt until + // after we've actually initialized the interrupt controller static. + // Otherwise, if we unmask it before initializing the static (like we + // used to), the interrupt gets raised immediately, and when the ISR + // tries to actually send an EOI to ack it, it dereferences + // uninitialized memory and the kernel just hangs. So, we wait to do it + // until here. + // + // The fact that this behavior exists at all makes me really, profoundly + // uncomfortable: shouldn't `cli` like, actually do what it's supposed + // to? But, we can just choose to unmask the PIT later and it's fine, I + // guess... + controller.unmask_isa_irq(IsaInterrupt::PitTimer); + controller.unmask_isa_irq(IsaInterrupt::Ps2Keyboard); controller } @@ -256,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)?, } } } @@ -345,267 +472,74 @@ impl hal_core::interrupt::Control for Idt { where H: Handlers, { - macro_rules! gen_code_faults { - ($self:ident, $h:ty, $($vector:path => fn $name:ident($($rest:tt)+),)+) => { - $( - gen_code_faults! {@ $name($($rest)+); } - $self.set_isr($vector, $name::<$h> as *const ()); - )+ - }; - (@ $name:ident($kind:literal);) => { - extern "x86-interrupt" fn $name>(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>( - 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>( - mut registers: Registers, - code: PageFaultCode, - ) { - H::page_fault(Context { - registers: &mut registers, - code, - }); - } - - extern "x86-interrupt" fn double_fault_isr>( - mut registers: Registers, - code: u64, - ) { - H::double_fault(Context { - registers: &mut registers, - code, - }); - } - - extern "x86-interrupt" fn pit_timer_isr>(_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>(_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) { - // 0x60 is a magic PC/AT number. - static PORT: cpu::Port = cpu::Port::at(0x60); - // load-bearing read - if we don't read from the keyboard controller it won't - // send another interrupt on later keystrokes. - 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>(mut registers: Registers) { - H::test_interrupt(Context { - registers: &mut registers, - code: (), - }); - } - - extern "x86-interrupt" fn invalid_tss_isr>( - mut registers: Registers, - code: u64, - ) { - unsafe { - // Safety: who cares! - 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>( - mut registers: Registers, - code: u64, - ) { - unsafe { - // Safety: who cares! - 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>( - mut registers: Registers, - code: u64, - ) { - unsafe { - // Safety: who cares! - 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>( - mut registers: Registers, - code: u64, - ) { - unsafe { - // Safety: who cares! - - 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"); - } - // === exceptions === // these exceptions are mapped to the HAL `Handlers` trait's "code // fault" handler, and indicate that the code that was executing did a // Bad Thing - 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::DIVIDE_BY_ZERO, isr::div_0:: as *const ()); + self.register_isr(Self::OVERFLOW, isr::overflow:: as *const ()); + self.register_isr(Self::BOUND_RANGE_EXCEEDED, isr::br:: as *const ()); + self.register_isr(Self::INVALID_OPCODE, isr::ud:: as *const ()); + self.register_isr(Self::DEVICE_NOT_AVAILABLE, isr::no_fpu:: as *const ()); + self.register_isr( + Self::ALIGNMENT_CHECK, + isr::alignment_check:: as *const (), + ); + self.register_isr( + Self::SIMD_FLOATING_POINT, + isr::simd_fp_exn:: as *const (), + ); + self.register_isr(Self::X87_FPU_EXCEPTION, isr::x87_exn:: as *const ()); // other exceptions, not mapped to the "code fault" handler - self.set_isr(Self::PAGE_FAULT, page_fault_isr:: as *const ()); - self.set_isr(Self::INVALID_TSS, invalid_tss_isr:: as *const ()); - self.set_isr( + self.register_isr(Self::PAGE_FAULT, isr::page_fault:: as *const ()); + self.register_isr(Self::INVALID_TSS, isr::invalid_tss:: as *const ()); + self.register_isr( Self::SEGMENT_NOT_PRESENT, - segment_not_present_isr:: as *const (), + isr::segment_not_present:: as *const (), ); - self.set_isr( + self.register_isr( Self::STACK_SEGMENT_FAULT, - stack_segment_isr:: as *const (), + isr::stack_segment:: as *const (), ); - self.set_isr(Self::GENERAL_PROTECTION_FAULT, gpf_isr:: as *const ()); - self.set_isr(Self::DOUBLE_FAULT, double_fault_isr:: as *const ()); + self.register_isr(Self::GENERAL_PROTECTION_FAULT, isr::gpf:: as *const ()); + self.register_isr(Self::DOUBLE_FAULT, isr::double_fault:: as *const ()); // === hardware interrupts === // ISA standard hardware interrupts mapped on both the PICs and IO APIC // interrupt models. - 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::PIC_PS2_KEYBOARD, keyboard_isr:: as *const ()); - self.set_isr(Self::IOAPIC_PS2_KEYBOARD, keyboard_isr:: as *const ()); - // local APIC specific hardware itnerrupts - self.set_isr(Self::LOCAL_APIC_SPURIOUS, spurious_isr as *const ()); - self.set_isr(Self::LOCAL_APIC_TIMER, apic_timer_isr:: as *const ()); + self.register_isa_isr(IsaInterrupt::PitTimer, isr::pit_timer:: as *const ()); + self.register_isa_isr(IsaInterrupt::Ps2Keyboard, isr::keyboard:: as *const ()); + + // local APIC specific hardware interrupts + self.register_isr(Self::LOCAL_APIC_SPURIOUS, isr::spurious as *const ()); + self.register_isr(Self::LOCAL_APIC_TIMER, isr::apic_timer:: as *const ()); // vector 69 (nice) is reserved by the HAL for testing the IDT. - self.set_isr(69, test_isr:: as *const ()); + self.register_isr(69, isr::test:: as *const ()); Ok(()) } } +/// Forcefully unlock the VGA port and COM1 serial port (used by tracing), so +/// that an ISR can log stuff without deadlocking. +/// +/// # Safety +/// +/// This forcefully unlocks a mutex, which is probably bad to do. Only do this +/// in ISRs that definitely represent real actual faults, and not just because +/// "you wanted to log something". +unsafe fn force_unlock_tracing() { + crate::vga::writer().force_unlock(); + if let Some(com1) = crate::serial::com1() { + com1.force_unlock(); + } +} + impl fmt::Debug for Registers { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { @@ -686,6 +620,232 @@ impl fmt::Display for NamedSelectorErrorCode { } } +mod isr { + use super::*; + + macro_rules! gen_code_faults { + ($(fn $name:ident($($rest:tt)+),)+) => { + $( + gen_code_faults! {@ $name($($rest)+); } + )+ + }; + (@ $name:ident($kind:literal);) => { + pub(super) extern "x86-interrupt" fn $name>(mut registers: Registers) { + let code = CodeFault { + error_code: None, + kind: $kind, + }; + H::code_fault(Context { registers: &mut registers, code }); + } + }; + (@ $name:ident($kind:literal, code);) => { + pub(super) extern "x86-interrupt" fn $name>( + mut registers: Registers, + code: u64, + ) { + let code = CodeFault { + error_code: Some(&code), + kind: $kind, + }; + H::code_fault(Context { registers: &mut registers, code }); + } + }; + } + + gen_code_faults! { + fn div_0("Divide-By-Zero (0x0)"), + fn overflow("Overflow (0x4)"), + fn br("Bound Range Exceeded (0x5)"), + fn ud("Invalid Opcode (0x6)"), + fn no_fpu("Device (FPU) Not Available (0x7)"), + fn alignment_check("Alignment Check (0x11)", code), + fn simd_fp_exn("SIMD Floating-Point Exception (0x13)"), + fn x87_exn("x87 Floating-Point Exception (0x10)"), + } + + pub(super) extern "x86-interrupt" fn page_fault>( + mut registers: Registers, + code: PageFaultCode, + ) { + H::page_fault(Context { + registers: &mut registers, + code, + }); + } + + pub(super) extern "x86-interrupt" fn double_fault>( + mut registers: Registers, + code: u64, + ) { + H::double_fault(Context { + registers: &mut registers, + code, + }); + } + + pub(super) extern "x86-interrupt" fn pit_timer>(_regs: Registers) { + if crate::time::Pit::handle_interrupt() { + H::timer_tick() + } + unsafe { + INTERRUPT_CONTROLLER + .get_unchecked() + .end_isa_irq(IsaInterrupt::PitTimer); + } + } + + pub(super) extern "x86-interrupt" fn apic_timer>(_regs: Registers) { + H::timer_tick(); + unsafe { + match INTERRUPT_CONTROLLER.get_unchecked().model { + InterruptModel::Pic(_) => unreachable!(), + 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:?}", + ), + } + } + } + } + } + + pub(super) extern "x86-interrupt" fn keyboard>(_regs: Registers) { + // 0x60 is a magic PC/AT number. + static PORT: cpu::Port = cpu::Port::at(0x60); + // load-bearing read - if we don't read from the keyboard controller it won't + // send another interrupt on later keystrokes. + let scancode = unsafe { PORT.readb() }; + H::ps2_keyboard(scancode); + unsafe { + INTERRUPT_CONTROLLER + .get_unchecked() + .end_isa_irq(IsaInterrupt::Ps2Keyboard); + } + } + + pub(super) extern "x86-interrupt" fn test>(mut registers: Registers) { + H::test_interrupt(Context { + registers: &mut registers, + code: (), + }); + } + + pub(super) extern "x86-interrupt" fn invalid_tss>( + mut registers: Registers, + code: u64, + ) { + unsafe { + // Safety: An invalid TSS exception is always an oops. Since we're + // not coming back from this, it's okay to forcefully unlock the + // tracing outputs. + force_unlock_tracing(); + } + 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, + }); + } + + pub(super) extern "x86-interrupt" fn segment_not_present>( + mut registers: Registers, + code: u64, + ) { + unsafe { + // Safety: An segment not present exception is always an oops. + // Since we're not coming back from this, it's okay to + // forcefully unlock the tracing outputs. + force_unlock_tracing(); + } + 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, + }); + } + + pub(super) extern "x86-interrupt" fn stack_segment>( + mut registers: Registers, + code: u64, + ) { + unsafe { + // Safety: An stack-segment fault exeption is always an oops. + // Since we're not coming back from this, it's okay to + // forcefully unlock the tracing outputs. + force_unlock_tracing(); + } + 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, + }); + } + + pub(super) extern "x86-interrupt" fn gpf>( + mut registers: Registers, + code: u64, + ) { + unsafe { + // Safety: A general protection fault is (currently) always an + // oops. Since we're not coming back from this, it's okay to + // forcefully unlock the tracing outputs. + // + // TODO(eliza): in the future, if we allow the kernel to + // recover from general protection faults in user mode programs, + // rather than treating them as invariably fatal, we should + // probably not always do this. Instead, we should just handle + // the user-mode GPF non-fatally, and only unlock the tracing + // stuff if we know we're going to do a kernel oops... + force_unlock_tracing(); + } + + 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, + }); + } + + pub(super) extern "x86-interrupt" fn spurious() { + // TODO(eliza): do we need to actually do something here? + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/hal-x86_64/src/interrupt/apic.rs b/hal-x86_64/src/interrupt/apic.rs index 54687809..846a5613 100644 --- a/hal-x86_64/src/interrupt/apic.rs +++ b/hal-x86_64/src/interrupt/apic.rs @@ -1,7 +1,7 @@ //! Advanced Programmable Interrupt Controller (APIC). -pub mod io; +pub mod ioapic; pub mod local; -pub use io::IoApic; +pub use ioapic::{IoApic, IoApicSet}; pub use local::LocalApic; use mycelium_util::bits::enum_from_bits; diff --git a/hal-x86_64/src/interrupt/apic/io.rs b/hal-x86_64/src/interrupt/apic/ioapic.rs similarity index 50% rename from hal-x86_64/src/interrupt/apic/io.rs rename to hal-x86_64/src/interrupt/apic/ioapic.rs index 3c1b28b9..0a0426df 100644 --- a/hal-x86_64/src/interrupt/apic/io.rs +++ b/hal-x86_64/src/interrupt/apic/ioapic.rs @@ -1,12 +1,22 @@ use super::{PinPolarity, TriggerMode}; use crate::{ cpu::FeatureNotSupported, + interrupt::IsaInterrupt, mm::{self, page, size::Size4Kb, PhysPage, VirtPage}, }; use hal_core::PAddr; -use mycelium_util::bits::{bitfield, enum_from_bits}; +use mycelium_util::{ + bits::{bitfield, enum_from_bits}, + sync::blocking::Mutex, +}; use volatile::Volatile; +#[derive(Debug)] +pub struct IoApicSet { + ioapics: alloc::vec::Vec>, + isa_map: [IsaOverride; 16], +} + #[derive(Debug)] #[must_use] pub struct IoApic { @@ -81,11 +91,219 @@ struct MmioRegisters { data: u32, } +#[derive(Copy, Clone, Debug)] +struct IsaOverride { + apic: u8, + vec: u8, +} + +// === impl IoApicSet === + +impl IoApicSet { + pub fn new( + madt: &acpi::platform::interrupt::Apic, + frame_alloc: &impl hal_core::mem::page::Alloc, + pagectrl: &mut crate::mm::PageCtrl, + isa_base: u8, + ) -> Self { + // The ACPI Multiple APIC Descriptor Table (MADT) tells us where to find + // the I/O APICs, as well as information about how the ISA standard + // interrupts are routed to I/O APIC inputs on this system. + // + // See: https://wiki.osdev.org/MADT + + // There may be multiple I/O APICs in the system, so we'll collect them + // all into a `Vec`. We're also going to build a table of how the ISA + // standard interrupts are mapped across those I/O APICs, so we can look + // up which one a particular interrupt lives on. + let n_ioapics = madt.io_apics.len(); + tracing::trace!(?madt.io_apics, "found {n_ioapics} IO APICs", ); + assert_ne!( + n_ioapics, 0, + "why would a computer have the APIC interrupt model but not \ + have any IO APICs???" + ); + let mut this = IoApicSet { + ioapics: alloc::vec::Vec::with_capacity(n_ioapics), + isa_map: [IsaOverride { apic: 0, vec: 0 }; 16], + }; + + for (n, ioapic) in madt.io_apics.iter().enumerate() { + let addr = PAddr::from_u64(ioapic.address as u64); + tracing::debug!(ioapic.paddr = ?addr, "IOAPIC {n}"); + this.ioapics + .push(Mutex::new(IoApic::new(addr, pagectrl, frame_alloc))); + } + + // Okay, so here's where it gets ~*weird*~. + // + // On the Platonic Ideal Normal Computer, the ISA PC interrupts would be + // mapped to I/O APIC input pins by number, so ISA IRQ 0 would go to pin + // 0 on I/O APIC 0, and so on. + // + // However, motherboard manufacturers can do whatever they like when + // routing these interrupts, so they could go to different pins, + // potentially on different I/O APICs (if the system has more than one). + // Also, some of these interrupts might have different pin polarity or + // edge/level-triggered-iness than what we might expect. + // + // Fortunately, ACPI is here to help (statements dreamed up by the + // utterly deranged). + // + // The MADT includes a list of "interrupt source overrides" that + // describe any ISA interrupts that are not mapped to I/O APIC pins in + // numeric order. Each entry in the interrupt source overrides list will + // contain: + // - an ISA IRQ number (naturally), + // - the "global system interrupt", which is NOT the IDT vector for that + // interrupt but instead the I/O APIC input pin number that the IRQ is + // routed to, + // - the polarity and trigger mode of the interrupt pin, if these are + // different from the default bus trigger mode. + // + // For each of the 16 ISA interrupt vectors, we'll configure the + // redirection entry in the appropriate I/O APIC and record which I/O + // APIC (and which pin on that I/O APIC) the interrupt is routed to. We + // do this by first checking if the MADT contains an override matching + // that ISA interrupt, using that if so, and if not, falling back to the + // ISA interrupt number. + // + // Yes, this is a big pile of nested loops. But, consider the following: + // + // - the MADT override entries can probably come in any order, if the + // motherboard firmware chooses to be maximally perverse, so we have + // to scan the whole list of them to find out if each ISA interrupt is + // overridden. + // - there are only ever 16 ISA interrupts, so the outer loop iterates + // exactly 16 times; and there can't be *more* overrides than there + // are ISA interrupts (and there's generally substantially fewer). + // similarly, if there's more than 1-8 I/O APICs, you probably have + // some kind of really weird computer and should tell me about it + // because i bet it's awesome. + // so, neither inner loop actually loops that many times. + // - finally, we only do this once on boot, so who cares? + + let base_entry = RedirectionEntry::new() + .with(RedirectionEntry::DELIVERY, DeliveryMode::Normal) + .with(RedirectionEntry::REMOTE_IRR, false) + .with(RedirectionEntry::MASKED, true) + .with(RedirectionEntry::DESTINATION, 0xff); + for irq in IsaInterrupt::ALL { + // Assume the IRQ is mapped to the I/O APIC pin corresponding to + // that ISA IRQ number, and is active-high and edge-triggered. + let mut global_system_interrupt = irq as u8; + let mut polarity = PinPolarity::High; + let mut trigger = TriggerMode::Edge; + // Is there an override for this IRQ? If there is, clobber the + // assumed defaults with "whatever the override says". + if let Some(src_override) = madt + .interrupt_source_overrides + .iter() + .find(|o| o.isa_source == irq as u8) + { + // put the defaults through an extruder that maybe messes with + // them. + use acpi::platform::interrupt::{ + Polarity as AcpiPolarity, TriggerMode as AcpiTriggerMode, + }; + tracing::debug!( + ?irq, + ?src_override.global_system_interrupt, + ?src_override.polarity, + ?src_override.trigger_mode, + "ISA interrupt {irq:?} is overridden by MADT" + ); + match src_override.polarity { + AcpiPolarity::ActiveHigh => polarity = PinPolarity::High, + AcpiPolarity::ActiveLow => polarity = PinPolarity::Low, + // TODO(eliza): if the MADT override entry says that the pin + // polarity is "same as bus", we should probably actually + // make it be the same as the bus, instead of just assuming + // that it's active high. But...just assuming that "same as + // bus" means active high seems to basically work so far... + AcpiPolarity::SameAsBus => {} + } + match src_override.trigger_mode { + AcpiTriggerMode::Edge => trigger = TriggerMode::Edge, + AcpiTriggerMode::Level => trigger = TriggerMode::Level, + // TODO(eliza): As above, when the MADT says this is "same + // as bus", we should make it be the same as the bus instead + // of going "ITS EDGE TRIGGERED LOL LMAO" which is what + // we're currently doing. But, just like above, this Seems + // To Work? + AcpiTriggerMode::SameAsBus => {} + } + global_system_interrupt = src_override.global_system_interrupt.try_into().expect( + "if this exceeds u8::MAX, what the fuck! \ + that's bigger than the entire IDT...", + ); + } + // Now, scan to find which I/O APIC this IRQ corresponds to. if the + // system only has one I/O APIC, this will always be 0, but we gotta + // handle systems with more than one. So, we'll this by traversing + // the list of I/O APICs, seeing if the GSI number is less than the + // max number of IRQs handled by that I/O APIC, and if it is, we'll + // stick it in there. If not, keep searching for the next one, + // subtracting the max number of interrupts handled by the I/O APIC + // we just looked at. + let mut entry_idx = global_system_interrupt; + let mut got_him = false; + 'apic_scan: for (apic_idx, apic) in this.ioapics.iter_mut().enumerate() { + let apic = apic.get_mut(); + let max_entries = apic.max_entries(); + if entry_idx > max_entries { + entry_idx -= max_entries; + continue; + } + + // Ladies and gentlemen...we got him! + got_him = true; + tracing::debug!( + ?irq, + ?global_system_interrupt, + ?apic_idx, + ?entry_idx, + "found IOAPIC for ISA interrupt" + ); + let entry = base_entry + .with(RedirectionEntry::POLARITY, polarity) + .with(RedirectionEntry::TRIGGER, trigger) + .with(RedirectionEntry::VECTOR, isa_base + irq as u8); + apic.set_entry(entry_idx, entry); + this.isa_map[irq as usize] = IsaOverride { + apic: apic_idx as u8, + vec: entry_idx, + }; + break 'apic_scan; + } + + assert!( + got_him, + "somehow, we didn't find an I/O APIC for MADT global system \ + interrupt {global_system_interrupt} (ISA IRQ {irq:?})!\n \ + this probably means the MADT is corrupted somehow, or maybe \ + your motherboard is just super weird? i have no idea what to \ + do in this situation, so i guess i'll die." + ); + } + + this + } + + fn for_isa_irq(&self, irq: IsaInterrupt) -> (&Mutex, u8) { + let isa_override = self.isa_map[irq as usize]; + (&self.ioapics[isa_override.apic as usize], isa_override.vec) + } + + pub fn set_isa_masked(&self, irq: IsaInterrupt, masked: bool) { + let (ioapic, vec) = self.for_isa_irq(irq); + ioapic.with_lock(|ioapic| ioapic.set_masked(vec, masked)); + } +} + // === impl IoApic === impl IoApic { - pub(crate) const PS2_KEYBOARD_IRQ: u8 = 0x1; - pub(crate) const PIT_TIMER_IRQ: u8 = 0x2; const REDIRECTION_ENTRY_BASE: u32 = 0x10; /// Try to construct an `IoApic`. @@ -150,22 +368,6 @@ impl IoApic { Self::try_new(addr, pagectrl, frame_alloc).unwrap() } - /// 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 { diff --git a/hal-x86_64/src/interrupt/apic/local.rs b/hal-x86_64/src/interrupt/apic/local.rs index c5c3d18d..f16733b8 100644 --- a/hal-x86_64/src/interrupt/apic/local.rs +++ b/hal-x86_64/src/interrupt/apic/local.rs @@ -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; @@ -24,6 +24,9 @@ pub struct LocalApicRegister { _ty: PhantomData, } +#[derive(Debug)] +pub(in crate::interrupt) struct Handle(local::LocalKey>>); + pub trait RegisterAccess { type Access; type Target; @@ -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; @@ -114,43 +127,10 @@ impl LocalApic { tracing::info!(base = ?self.base, spurious_vector, "local APIC enabled"); } - fn timer_frequency_hz(&self) -> u32 { + /// Calibrate the timer frequency using the PIT. + #[inline(always)] + fn calibrate_frequency_hz_pit(&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..."); // lock the PIT now, before actually starting the timer IRQ, so that we @@ -162,16 +142,18 @@ impl LocalApic { let mut pit = crate::time::PIT.lock(); unsafe { + // start the timer + // 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), + LvtTimer::new() + .with(LvtTimer::MODE, TimerMode::OneShot) + .with(LvtTimer::MASKED, false), ); + // set initial count to -1 + self.write_register(TIMER_INITIAL_COUNT, -1i32 as u32); } // use the PIT to sleep for 10ms @@ -182,7 +164,9 @@ impl LocalApic { // stop the timer self.write_register( LVT_TIMER, - LvtTimer::new().with(LvtTimer::MODE, TimerMode::Periodic), + LvtTimer::new() + .with(LvtTimer::MODE, TimerMode::OneShot) + .with(LvtTimer::MASKED, true), ); } @@ -198,6 +182,86 @@ impl LocalApic { frequency_hz } + #[inline(always)] + fn calibrate_frequency_hz_cpuid() -> Option { + let cpuid = CpuId::new(); + if let Some(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_khz = ?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 = (freq_khz.get() * 1000) / Self::TIMER_DIVISOR; + tracing::debug!( + frequency_hz, + "determined APIC timer frequency from CPUID hypervisor info" + ); + return Some(frequency_hz); + } + + // XXX ELIZA THIS IS TSC FREQUENCY, SO IDK IF THAT'S RIGHT? + /* + 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. + None + } + + fn timer_frequency_hz(&self) -> u32 { + // How sloppy do we expect the PIT frequency calibration to be? + // If the delta between the CPUID frequency and the frequency we + // determined using PIT calibration is > this value, we'll yell about + // it. + const PIT_SLOPPINESS: u32 = 1000; + + // Start out by calibrating the APIC frequency using the PIT. + let pit_frequency_hz = self.calibrate_frequency_hz_pit(); + + // See if we can get something from CPUID. + let Some(cpuid_frequency_hz) = Self::calibrate_frequency_hz_cpuid() else { + tracing::info!( + pit.frequency_hz = pit_frequency_hz, + "CPUID does not indicate APIC timer frequency; using PIT \ + calibration only" + ); + return pit_frequency_hz; + }; + + // Cross-check the PIT calibration result and CPUID value. + let distance = if pit_frequency_hz > cpuid_frequency_hz { + pit_frequency_hz - cpuid_frequency_hz + } else { + cpuid_frequency_hz - pit_frequency_hz + }; + if distance > PIT_SLOPPINESS { + tracing::warn!( + pit.frequency_hz = pit_frequency_hz, + cpuid.frequency_hz = cpuid_frequency_hz, + distance, + "APIC timer frequency from PIT calibration differs substantially \ + from CPUID!" + ); + } + + cpuid_frequency_hz + } + #[tracing::instrument( level = tracing::Level::DEBUG, name = "LocalApic::start_periodic_timer", @@ -293,6 +357,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( + &self, + f: impl FnOnce(&LocalApic) -> T, + ) -> Result { + self.0.with(|apic| { + Ok(f(apic + .borrow() + .as_ref() + .ok_or(LocalApicError::Uninitialized)?)) + }) + } + + pub(in crate::interrupt) unsafe fn initialize( + &self, + frame_alloc: &A, + pagectrl: &mut impl page::Map, + spurious_vector: u8, + ) where + A: page::Alloc, + { + 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}; diff --git a/hal-x86_64/src/interrupt/idt.rs b/hal-x86_64/src/interrupt/idt.rs index 13f20dd7..73acdf92 100644 --- a/hal-x86_64/src/interrupt/idt.rs +++ b/hal-x86_64/src/interrupt/idt.rs @@ -1,4 +1,4 @@ -use super::apic::IoApic; +use super::IsaInterrupt; use crate::{cpu, segment}; use mycelium_util::{bits, fmt}; @@ -102,7 +102,7 @@ impl bits::FromBits for GateKind { // === impl Idt === impl Idt { - pub(super) const NUM_VECTORS: usize = 256; + pub const NUM_VECTORS: usize = 256; /// Divide-by-zero interrupt (#D0) pub const DIVIDE_BY_ZERO: usize = 0; @@ -155,18 +155,47 @@ 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 = Self::PIC_BIG_START; - pub const PIC_PS2_KEYBOARD: usize = Self::PIC_BIG_START + 1; + pub const MAX_CPU_EXCEPTION: usize = Self::SECURITY_EXCEPTION; - 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; + /// Base offset for ISA hardware interrupts. + pub const ISA_BASE: usize = 0x20; + + /// Local APIC timer interrupt vector mapped by + /// [`Controller::enable_hardware_interrupts`]. + /// + /// Systems which do not use that function to initialize the local APIC may + /// map this interrupt to a different IDT vector. + /// + /// [`Controller::enable_hardware_interrupts`]: super::Controller::enable_hardware_interrupts + pub const LOCAL_APIC_TIMER: usize = (Self::NUM_VECTORS - 2); + + /// Local APIC spurious interrupt vector mapped by + /// [`Controller::enable_hardware_interrupts`]. + /// + /// Systems which do not use that function to initialize the local APIC may + /// map this interrupt to a different IDT vector. + /// + /// [`Controller::enable_hardware_interrupts`]: super::Controller::enable_hardware_interrupts + pub const LOCAL_APIC_SPURIOUS: usize = (Self::NUM_VECTORS - 1); + + /// Base of the primary PIC's interrupt vector region mapped by + /// [`Controller::enable_hardware_interrupts`]. + /// + /// Systems which do not use that function to initialize the PICs may + /// map this interrupt to a different IDT vector. + /// + /// [`Controller::enable_hardware_interrupts`]: super::Controller::enable_hardware_interrupts + pub const PIC_BIG_START: usize = Self::ISA_BASE; + + /// Base of the secondary PIC's interrupt vector region mapped by + /// [`Controller::enable_hardware_interrupts`]. + /// + /// Systems which do not use that function to initialize the PICs may + /// map this interrupt to a different IDT vector. + /// + /// [`Controller::enable_hardware_interrupts`]: super::Controller::enable_hardware_interrupts + pub const PIC_LITTLE_START: usize = Self::ISA_BASE + 8; // put the IOAPIC right after the PICs - pub(super) const IOAPIC_START: usize = 0x30; - pub(super) const IOAPIC_PIT_TIMER: usize = Self::IOAPIC_START + IoApic::PIT_TIMER_IRQ as usize; - pub(super) const IOAPIC_PS2_KEYBOARD: usize = - Self::IOAPIC_START + IoApic::PS2_KEYBOARD_IRQ as usize; pub const fn new() -> Self { Self { @@ -174,12 +203,19 @@ impl Idt { } } - pub(super) fn set_isr(&mut self, vector: usize, isr: *const ()) { + /// Register an interrupt service routine (ISR) for the given ISA standard + /// PC interrupt. + pub fn register_isa_isr(&mut self, irq: IsaInterrupt, isr: *const ()) { + let vector = irq as usize + Self::ISA_BASE; + self.register_isr(vector, isr); + } + + pub fn register_isr(&mut self, vector: usize, isr: *const ()) { let descr = self.descriptors[vector].set_handler(isr); if vector == Self::DOUBLE_FAULT { descr.set_ist_offset(Self::DOUBLE_FAULT_IST_OFFSET as u8); } - tracing::debug!(vector, ?isr, ?descr, "set isr"); + tracing::debug!(vector, ?isr, ?descr, "set ISR"); } pub fn load(&'static self) { diff --git a/hal-x86_64/src/interrupt/pic.rs b/hal-x86_64/src/interrupt/pic.rs index da37c42f..e61150a0 100644 --- a/hal-x86_64/src/interrupt/pic.rs +++ b/hal-x86_64/src/interrupt/pic.rs @@ -1,3 +1,4 @@ +use super::IsaInterrupt; use crate::cpu; use hal_core::interrupt::{Handlers, RegistrationError}; @@ -16,6 +17,24 @@ impl Pic { data: cpu::Port::at(data), } } + + /// Mask the provided interrupt number. + unsafe fn mask(&mut self, num: u8) { + debug_assert!(num < 8); + // read the current value of the Interrupt Mask Register (IMR). + let imr = self.data.readb(); + // set the bit corresponding to the interrupt number to 1. + self.data.writeb(imr | (1 << num)); + } + + /// Unmask the provided interrupt number. + unsafe fn unmask(&mut self, num: u8) { + debug_assert!(num < 8); + // read the current value of the Interrupt Mask Register (IMR). + let imr = self.data.readb(); + // clear the bit corresponding to the interrupt number. + self.data.writeb(imr & !(1 << num)); + } } #[derive(Debug)] @@ -50,9 +69,33 @@ impl CascadedPic { } } - pub(crate) fn end_interrupt(&mut self, num: u8) { + pub(crate) fn mask(&mut self, irq: IsaInterrupt) { + let (pic, num) = self.pic_for_irq(irq); + unsafe { + pic.mask(num); + } + } + + pub(crate) fn unmask(&mut self, irq: IsaInterrupt) { + let (pic, num) = self.pic_for_irq(irq); + unsafe { + pic.unmask(num); + } + } + + fn pic_for_irq(&mut self, irq: IsaInterrupt) -> (&mut Pic, u8) { + let num = irq as u8; + if num >= 8 { + (&mut self.sisters.little, num - 8) + } else { + (&mut self.sisters.big, num) + } + } + + pub(crate) fn end_interrupt(&mut self, irq: IsaInterrupt) { const END_INTERRUPT: u8 = 0x20; // from osdev wiki - if num >= self.sisters.little.address && num < self.sisters.little.address + 8 { + let num = irq as u8; + if num >= 8 { unsafe { self.sisters.little.command.writeb(END_INTERRUPT); } diff --git a/hal-x86_64/src/time/pit.rs b/hal-x86_64/src/time/pit.rs index df2e5731..6ecaf3a9 100644 --- a/hal-x86_64/src/time/pit.rs +++ b/hal-x86_64/src/time/pit.rs @@ -271,19 +271,14 @@ impl Pit { /// /// [`Controller::init`]: crate::interrupt::Controller::init /// [`interrupt`]: crate::interrupt - #[tracing::instrument( - name = "Pit::sleep_blocking" - level = tracing::Level::DEBUG, - skip(self), - fields(?duration), - err, - )] pub fn sleep_blocking(&mut self, duration: Duration) -> Result<(), PitError> { SLEEPING .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) .map_err(|_| PitError::SleepInProgress)?; self.interrupt_in(duration) .map_err(PitError::InvalidDuration)?; + + // Tracing here is fine, because we are already sleeping... tracing::debug!("started PIT sleep"); // spin until the sleep interrupt fires. @@ -291,11 +286,8 @@ impl Pit { cpu::wait_for_interrupt(); } - tracing::info!(?duration, "slept using PIT channel 0"); - // if we were previously in periodic mode, re-enable it. if let Some(interval) = self.channel0_interval { - tracing::debug!("restarting PIT periodic timer"); self.start_periodic_timer(interval)?; } @@ -366,13 +358,6 @@ impl Pit { /// This configures the PIT in mode 0 (oneshot mode). Once the interrupt has /// fired, in order to use the periodic timer, the pit must be put back into /// periodic mode by calling [`Pit::start_periodic_timer`]. - #[tracing::instrument( - name = "Pit::interrupt_in" - level = tracing::Level::DEBUG, - skip(self), - fields(?duration), - err, - )] pub fn interrupt_in(&mut self, duration: Duration) -> Result<(), InvalidDuration> { let duration_ms = usize::try_from(duration.as_millis()).map_err(|_| { InvalidDuration::new( @@ -388,8 +373,6 @@ impl Pit { ) })?; - tracing::trace!(?duration, duration_ms, target_time, "Pit::interrupt_in"); - let command = Command::new() // use the binary counter .with(Command::BCD_BINARY, false) @@ -410,19 +393,16 @@ impl Pit { } fn set_divisor(&mut self, divisor: u16) { - tracing::trace!(divisor = &fmt::hex(divisor), "Pit::set_divisor"); let low = divisor as u8; let high = (divisor >> 8) as u8; unsafe { self.channel0.writeb(low); // write the low byte - tracing::trace!(lo = &fmt::hex(low), "pit.channel0.writeb(lo)"); self.channel0.writeb(high); // write the high byte - tracing::trace!(hi = &fmt::hex(high), "pit.channel0.writeb(hi)"); } } fn send_command(&self, command: Command) { - tracing::debug!(?command, "Pit::send_command"); + // tracing::debug!(?command, "Pit::send_command"); unsafe { self.command.writeb(command.bits()); } diff --git a/maitake-sync/README.md b/maitake-sync/README.md index e8897d20..3e35ec52 100644 --- a/maitake-sync/README.md +++ b/maitake-sync/README.md @@ -4,6 +4,7 @@ primitives from [`maitake`] [![crates.io][crates-badge]][crates-url] +[![Changelog][changelog-badge]][changelog-url] [![Documentation][docs-badge]][docs-url] [![Documentation (HEAD)][docs-main-badge]][docs-main-url] [![MIT licensed][mit-badge]][mit-url] @@ -11,7 +12,10 @@ primitives from [`maitake`] [![Sponsor @hawkw on GitHub Sponsors][sponsor-badge]][sponsor-url] [crates-badge]: https://img.shields.io/crates/v/maitake-sync.svg -[crates-url]: https://crates.io/crates/maitake-sync-sync +[crates-url]: https://crates.io/crates/maitake-sync +[changelog-badge]: + https://img.shields.io/crates/v/maitake-sync?label=changelog&color=blue +[changelog-url]: https://github.com/hawkw/mycelium/blob/main/maitake-sync/CHANGELOG.md [docs-badge]: https://docs.rs/maitake-sync/badge.svg [docs-url]: https://docs.rs/maitake-sync [docs-main-badge]: https://img.shields.io/netlify/3ec00bb5-251a-4f83-ac7f-3799d95db0e6?label=docs%20%28main%20branch%29 diff --git a/src/arch/x86_64/shell.rs b/src/arch/x86_64/shell.rs index e7f94500..25d531a5 100644 --- a/src/arch/x86_64/shell.rs +++ b/src/arch/x86_64/shell.rs @@ -1,4 +1,5 @@ -use crate::shell::Command; +use crate::shell::{Command, NumberFormat}; +use mycelium_util::fmt; pub const DUMP_ARCH: Command = Command::new("arch") .with_help("dump architecture-specific structures") @@ -17,4 +18,74 @@ pub const DUMP_ARCH: Command = Command::new("arch") tracing::info!(IDT = ?idt); Ok(()) }), + Command::new("msr") + .with_help( + "print the value of the specified model-specific register (MSR)\n \ + MSR_NUM: the MSR number in hexadecimal or binary\n \ + -f, --fmt : format the value of the MSR in hexadecimal, \ + decimal, or binary.", + ) + .with_usage("[-f|--fmt] ") + .with_fn(|mut ctx| { + let fmt = ctx + .parse_optional_flag::(&["-f", "--fmt"])? + .unwrap_or(NumberFormat::Hex); + let num = ctx.parse_u32_hex_or_dec("")?; + + let msr = hal_x86_64::cpu::msr::Msr::try_new(num).ok_or_else(|| { + ctx.other_error( + "CPU does not support model-specific registers (must be pre-pentium...)", + ) + })?; + + let val = msr.read_raw(); + match fmt { + NumberFormat::Binary => tracing::info!("MSR {num:#x} = {val:#b}"), + NumberFormat::Decimal => tracing::info!("MSR {num:#x} = {val}"), + NumberFormat::Hex => tracing::info!("MSR {num:#x} = {val:#x}"), + } + Ok(()) + }), + Command::new("cpuid") + .with_help( + "print the value of the specified CPUID leaf (and subleaf)\n \ + LEAF: the CPUID leaf number in hexadecimal or binary\n \ + SUBLEAF: the CPUID subleaf number in hexadecimal or binary\n \ + -f, --fmt : format the values of the CPUID registers in hexadecimal, \ + decimal, or binary.", + ) + .with_usage("[-f|--fmt] [SUBLEAF]") + .with_fn(|mut ctx| { + use core::arch::x86_64::{CpuidResult, __cpuid_count}; + let fmt = ctx + .parse_optional_flag::(&["-f", "--fmt"])? + .unwrap_or(NumberFormat::Hex); + let leaf = ctx.parse_u32_hex_or_dec("")?; + let subleaf = ctx.parse_optional_u32_hex_or_dec("[SUBLEAF]")?.unwrap_or(0); + + let CpuidResult { eax, ebx, ecx, edx } = unsafe { __cpuid_count(leaf, subleaf) }; + match fmt { + NumberFormat::Binary => tracing::info!( + target: "shell", + eax = fmt::bin(eax), + ebx = fmt::bin(ebx), + ecx = fmt::bin(ecx), + edx = fmt::bin(edx), + "CPUID {leaf:#x}:{subleaf:x}", + ), + NumberFormat::Decimal => tracing::info!( + target: "shell", eax, ebx, ecx, edx, + "CPUID {leaf:#x}:{subleaf:x}", + ), + NumberFormat::Hex => tracing::info!( + target: "shell", + eax = fmt::hex(eax), + ebx = fmt::hex(ebx), + ecx = fmt::hex(ecx), + edx = fmt::hex(edx), + "CPUID {leaf:#x}:{subleaf:x}", + ), + } + Ok(()) + }), ]); diff --git a/src/shell.rs b/src/shell.rs index 5c34bd00..286bc814 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -2,6 +2,7 @@ //! purposes. //! use crate::rt; +use core::str::FromStr; use mycelium_util::fmt::{self, Write}; /// Defines a shell command, including its name, help text, and how the command @@ -21,10 +22,10 @@ pub struct Error<'a> { kind: ErrorKind<'a>, } -pub type Result<'a> = core::result::Result<(), Error<'a>>; +pub type CmdResult<'a> = core::result::Result<(), Error<'a>>; pub trait Run: Send + Sync { - fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> Result<'ctx>; + fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> CmdResult<'ctx>; } #[derive(Debug)] @@ -32,12 +33,19 @@ enum ErrorKind<'a> { UnknownCommand(&'a [Command<'a>]), SubcommandRequired(&'a [Command<'a>]), - InvalidArguments { help: &'a str, arg: &'a str }, + InvalidArguments { + help: &'a str, + arg: &'a str, + flag: Option<&'a str>, + }, + FlagRequired { + flags: &'a [&'a str], + }, Other(&'static str), } enum RunKind<'a> { - Fn(fn(Context<'_>) -> Result<'_>), + Fn(fn(Context<'_>) -> CmdResult<'_>), Runnable(&'a dyn Run), } @@ -75,7 +83,7 @@ pub struct Context<'cmd> { current: &'cmd str, } -pub fn handle_command<'cmd>(ctx: Context<'cmd>, commands: &'cmd [Command]) -> Result<'cmd> { +pub fn handle_command<'cmd>(ctx: Context<'cmd>, commands: &'cmd [Command]) -> CmdResult<'cmd> { let chunk = ctx.current.trim(); for cmd in commands { if let Some(current) = chunk.strip_prefix(cmd.name) { @@ -310,7 +318,7 @@ impl<'cmd> Command<'cmd> { /// If [`Command::with_fn`] or [`Command::with_runnable`] was previously /// called, this overwrites the previously set value. #[must_use] - pub const fn with_fn(self, func: fn(Context<'_>) -> Result<'_>) -> Self { + pub const fn with_fn(self, func: fn(Context<'_>) -> CmdResult<'_>) -> Self { Self { run: Some(RunKind::Fn(func)), ..self @@ -332,7 +340,7 @@ impl<'cmd> Command<'cmd> { } /// Run this command in the provided [`Context`]. - pub fn run<'ctx>(&'cmd self, ctx: Context<'ctx>) -> Result<'ctx> + pub fn run<'ctx>(&'cmd self, ctx: Context<'ctx>) -> CmdResult<'ctx> where 'cmd: 'ctx, { @@ -414,6 +422,17 @@ impl fmt::Display for Error<'_> { .chain(core::iter::once("help")) } + fn fmt_flag_names(f: &mut fmt::Formatter<'_>, flags: &[&str]) -> fmt::Result { + let mut names = flags.iter(); + if let Some(name) = names.next() { + f.write_str(name)?; + for name in names { + write!(f, "|{name}")?; + } + } + Ok(()) + } + let Self { line, kind } = self; match kind { ErrorKind::UnknownCommand(commands) => { @@ -421,8 +440,12 @@ impl fmt::Display for Error<'_> { fmt::comma_delimited(&mut f, command_names(commands))?; f.write_char(']')?; } - ErrorKind::InvalidArguments { arg, help } => { - write!(f, "invalid argument {arg:?}: {help}")? + ErrorKind::InvalidArguments { arg, help, flag } => { + f.write_str("invalid argument")?; + if let Some(flag) = flag { + write!(f, " {flag}")?; + } + write!(f, " {arg:?}: {help}")?; } ErrorKind::SubcommandRequired(subcommands) => { writeln!( @@ -432,6 +455,11 @@ impl fmt::Display for Error<'_> { fmt::comma_delimited(&mut f, command_names(subcommands))?; f.write_char(']')?; } + ErrorKind::FlagRequired { flags } => { + write!(f, "the '{line}' command requires the ")?; + fmt_flag_names(f, flags)?; + write!(f, " flag")?; + } ErrorKind::Other(msg) => write!(f, "could not execute {line:?}: {msg}")?, } @@ -472,6 +500,18 @@ impl<'cmd> Context<'cmd> { line: self.line, kind: ErrorKind::InvalidArguments { arg: self.current, + flag: None, + help, + }, + } + } + + pub fn invalid_argument_named(&self, name: &'static str, help: &'static str) -> Error<'cmd> { + Error { + line: self.line, + kind: ErrorKind::InvalidArguments { + arg: self.current, + flag: Some(name), help, }, } @@ -483,13 +523,130 @@ impl<'cmd> Context<'cmd> { kind: ErrorKind::Other(msg), } } + + pub fn parse_bool_flag(&mut self, flag: &str) -> bool { + if let Some(rest) = self.command().trim().strip_prefix(flag) { + self.current = rest.trim(); + true + } else { + false + } + } + + pub fn parse_optional_u32_hex_or_dec( + &mut self, + name: &'static str, + ) -> Result, Error<'cmd>> { + let (chunk, rest) = match self.command().split_once(" ") { + Some((chunk, rest)) => (chunk.trim(), rest), + None => (self.command(), ""), + }; + + if chunk.is_empty() { + return Ok(None); + } + + let val = if let Some(hex_num) = chunk.strip_prefix("0x") { + u32::from_str_radix(hex_num.trim(), 16).map_err(|_| Error { + line: self.line, + kind: ErrorKind::InvalidArguments { + arg: chunk, + flag: Some(name), + help: "expected a 32-bit hex number", + }, + })? + } else { + u32::from_str(chunk).map_err(|_| Error { + line: self.line, + kind: ErrorKind::InvalidArguments { + arg: chunk, + flag: Some(name), + help: "expected a 32-bit decimal number", + }, + })? + }; + + self.current = rest; + Ok(Some(val)) + } + + pub fn parse_u32_hex_or_dec(&mut self, name: &'static str) -> Result> { + self.parse_optional_u32_hex_or_dec(name).and_then(|val| { + val.ok_or_else(|| self.invalid_argument_named(name, "expected a number")) + }) + } + + pub fn parse_optional_flag( + &mut self, + names: &'static [&'static str], + ) -> Result, Error<'cmd>> + where + T: FromStr, + T::Err: core::fmt::Display, + { + for name in names { + if let Some(rest) = self.command().strip_prefix(name) { + let (chunk, rest) = match rest.trim().split_once(" ") { + Some((chunk, rest)) => (chunk.trim(), rest), + None => (rest, ""), + }; + + if chunk.is_empty() { + return Err(Error { + line: self.line, + kind: ErrorKind::InvalidArguments { + arg: chunk, + flag: Some(name), + help: "expected a value", + }, + }); + } + + match chunk.parse() { + Ok(val) => { + self.current = rest; + return Ok(Some(val)); + } + Err(e) => { + tracing::warn!(target: "shell", "invalid value {chunk:?} for flag {name}: {e}"); + return Err(Error { + line: self.line, + kind: ErrorKind::InvalidArguments { + arg: chunk, + flag: Some(name), + help: "invalid value", + }, + }); + } + } + } + } + + Ok(None) + } + + pub fn parse_required_flag( + &mut self, + names: &'static [&'static str], + ) -> Result> + where + T: FromStr, + T::Err: core::fmt::Display, + { + self.parse_optional_flag(names).and_then(|val| { + val.ok_or_else(|| Error { + line: self.line, + kind: ErrorKind::FlagRequired { flags: names }, + }) + }) + } } // === impl RunKind === impl RunKind<'_> { #[inline] - fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> Result<'ctx> { + fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> CmdResult<'ctx> { match self { Self::Fn(func) => func(ctx), Self::Runnable(runnable) => runnable.run(ctx), @@ -513,9 +670,9 @@ impl fmt::Debug for RunKind<'_> { impl Run for F where - F: Fn(Context<'_>) -> Result<'_> + Send + Sync, + F: Fn(Context<'_>) -> CmdResult<'_> + Send + Sync, { - fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> Result<'ctx> { + fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> CmdResult<'ctx> { self(ctx) } } @@ -527,3 +684,22 @@ fn print_help(parent_cmd: &str, commands: &[Command]) { } tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}help --- prints this help message"); } + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum NumberFormat { + Binary, + Hex, + Decimal, +} + +impl FromStr for NumberFormat { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s.trim() { + "b" | "bin" | "binary" => Ok(Self::Binary), + "h" | "hex" => Ok(Self::Hex), + "d" | "dec" | "decimal" => Ok(Self::Decimal), + _ => Err("expected one of: [b, bin, binary, h, hex, d, decimal]"), + } + } +} diff --git a/util/src/deferred.rs b/util/src/deferred.rs new file mode 100644 index 00000000..73bdf157 --- /dev/null +++ b/util/src/deferred.rs @@ -0,0 +1,53 @@ +//! Deferred closure execution (a.k.a. scope guards). +//! +//! See the [`Deferred`] type and the [`defer`] function for more information. + +/// Defers execution of a closure until a scope is exited. +/// +/// As seen in "The Go Programming Language". +#[must_use = "dropping a `Deferred` guard immediately executes \ + the deferred function"] +pub struct Deferred(Option); + +impl Deferred { + /// Defer execution of `f` until this scope is exited. + #[inline] + pub const fn new(f: F) -> Self { + Self(Some(f)) + } + + /// Cancel the deferred execution.of the closure passed to `defer`. + /// + /// Calling this method will prevent the closure from being executed when + /// this `Deferred` guard is dropped. + #[inline] + pub fn cancel(&mut self) { + self.0 = None; + } +} + +impl Drop for Deferred { + #[inline] + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f() + } + } +} + +impl core::fmt::Debug for Deferred { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("Deferred") + .field(&format_args!("{}", core::any::type_name::())) + .finish() + } +} + +/// Defer execution of `f` until this scope is exited. +/// +/// This is equivalent to calling `Deferred::new(f)`. See [`Deferred`] for more +/// details. +#[inline(always)] +pub fn defer(f: F) -> Deferred { + Deferred::new(f) +} diff --git a/util/src/lib.rs b/util/src/lib.rs index 24b61dee..f30ca142 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -9,6 +9,8 @@ extern crate alloc; #[macro_use] mod macros; +pub use deferred::defer; +pub mod deferred; pub mod error; pub mod fmt; pub mod io;