diff --git a/Cargo.lock b/Cargo.lock index 109df0a2..3906a521 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1475,7 +1475,7 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cordyceps" version = "0.3.2" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "loom 0.7.2", "tracing 0.1.40", @@ -4121,9 +4121,10 @@ dependencies = [ [[package]] name = "hal-core" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "embedded-graphics-core 0.3.3", + "maitake-sync", "mycelium-util", "tracing 0.2.0", ] @@ -4131,10 +4132,11 @@ dependencies = [ [[package]] name = "hal-x86_64" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "acpi", "hal-core", + "maitake", "mycelium-trace", "mycelium-util", "mycotest", @@ -5167,7 +5169,7 @@ dependencies = [ [[package]] name = "maitake" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "cordyceps", "maitake-sync", @@ -5180,14 +5182,16 @@ dependencies = [ [[package]] name = "maitake-sync" -version = "0.1.1" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +version = "0.1.2" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "cordyceps", "loom 0.7.2", + "mutex-traits", "mycelium-bitfield", "pin-project", "portable-atomic", + "tracing 0.1.40", ] [[package]] @@ -5745,10 +5749,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mutex-traits" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54cb762feb1788c74f5d3387983864d2365ca1819043d2addb76db80169102" + [[package]] name = "mycelium-alloc" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "hal-core", "mycelium-util", @@ -5758,15 +5768,16 @@ dependencies = [ [[package]] name = "mycelium-bitfield" version = "0.1.5" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" [[package]] name = "mycelium-trace" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "embedded-graphics 0.7.1", "hal-core", + "maitake", "mycelium-util", "tracing 0.2.0", "tracing-core 0.2.0", @@ -5775,7 +5786,7 @@ dependencies = [ [[package]] name = "mycelium-util" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "cordyceps", "loom 0.7.2", @@ -5787,7 +5798,7 @@ dependencies = [ [[package]] name = "mycotest" version = "0.1.0" -source = "git+https://github.com/hawkw/mycelium.git?rev=13d0722429ef201f38e4ea47ea22d88f3f72c10e#13d0722429ef201f38e4ea47ea22d88f3f72c10e" +source = "git+https://github.com/hawkw/mycelium?rev=ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5#ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" dependencies = [ "tracing 0.1.40", ] diff --git a/Cargo.toml b/Cargo.toml index 23c55217..fddb86b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,8 @@ default-members = [ # this isn't actually a crate exclude = ["source/notes"] +### workspace dependencies ### + [workspace.package] edition = "2021" repository = "https://github.com/tosc-rs/mnemos" @@ -74,7 +76,13 @@ homepage = "https://mnemos.dev" license = "MIT OR Apache-2.0" [workspace.dependencies] +cordyceps = { version = "0.3", default-features = false } +hal-core = { version = "0.1.0" } +maitake = { version = "0.1.0", default-features = false } miette = "7.2" +mycelium-alloc = { version = "0.1.0", features = ["buddy", "bump"] } +mycelium-bitfield = { version = "0.1.5" } +mycelium-util = { version = "0.1.0" } ### profile settings ### @@ -150,13 +158,12 @@ debug = "line-tables-only" ### patches ### [patch.crates-io.maitake] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" - -[patch.crates-io.mycelium-util] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" +[patch.crates-io.mycelium-alloc] +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" # Use the `mycelium-bitfield` crate from the Mycelium monorepo rather than # crates.io. # NOTE: this patch, unlike the patches for `maitake` and `mycelium-util`, (which @@ -165,24 +172,24 @@ rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" # since it's already in our dependency tree as a transitive dep of `maitake` --- # having both a Git dep and a crates.io dep seems unfortunate. [patch.crates-io.mycelium-bitfield] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" + +[patch.crates-io.mycelium-util] +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" [patch.crates-io.cordyceps] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" [patch.crates-io.hal-core] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" [patch.crates-io.hal-x86_64] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" - -[patch.crates-io.mycelium-alloc] -git = "https://github.com/hawkw/mycelium.git" -rev = "13d0722429ef201f38e4ea47ea22d88f3f72c10e" +git = "https://github.com/hawkw/mycelium" +rev = "ba56bb4d02f46fb59754b7b88bddc2e8ca99c1f5" [patch.crates-io.bbq10kbd] git = "https://github.com/hawkw/bbq10kbd" diff --git a/platforms/allwinner-d1/board-configs/lichee-rv.toml b/platforms/allwinner-d1/board-configs/lichee-rv.toml index f15cc6a7..f3e7fea0 100644 --- a/platforms/allwinner-d1/board-configs/lichee-rv.toml +++ b/platforms/allwinner-d1/board-configs/lichee-rv.toml @@ -1,6 +1,5 @@ [kernel] max_drivers = 16 -timer_granularity = { secs = 0, nanos = 333 } [services.spawnulator] enabled = true diff --git a/platforms/allwinner-d1/d1-core/src/timer.rs b/platforms/allwinner-d1/d1-core/src/timer.rs index 81d94ff5..eab9e399 100644 --- a/platforms/allwinner-d1/d1-core/src/timer.rs +++ b/platforms/allwinner-d1/d1-core/src/timer.rs @@ -1,13 +1,92 @@ +use crate::plic::{Plic, Priority}; +use core::sync::atomic::{AtomicU32, Ordering}; pub use d1_pac::timer::tmr_ctrl::{ TMR_CLK_PRES_A as TimerPrescaler, TMR_CLK_SRC_A as TimerSource, TMR_MODE_A as TimerMode, }; use d1_pac::TIMER; +use kernel::maitake::time::Clock; pub struct Timers { pub timer0: Timer0, pub timer1: Timer1, } +static TIMER0_ROLLOVERS: AtomicU32 = AtomicU32::new(0); + +impl Timer0 { + const INITIAL_VALUE: u32 = u32::MAX; + + pub fn into_maitake_clock(mut self, plic: &Plic) -> Clock { + use d1_pac::Interrupt; + self.set_prescaler(TimerPrescaler::P8); // 24M / 8: 3.00M ticks/s + self.set_mode(TimerMode::PERIODIC); + + // Clear any previous interrupt flag. + let _ = self.get_and_clear_interrupt(); + + // Register the interrupt handler for when the timer rolls over. + unsafe { + plic.register(Interrupt::TIMER0, Self::maitake_timer_interrupt); + plic.activate(Interrupt::TIMER0, Priority::P1).unwrap(); + } + + // Start the timer counting down from u32::MAX;. + self.reset(); + + Clock::new(core::time::Duration::from_nanos(333), || { + let timer0 = unsafe { + // Safety: we are just reading the current value and will not be + // concurrently mutating the timer. + Self::steal() + }; + // Since timer 0 is counting *down*, we have to subtract its current + // value from the intial value to get an i[ncreasing timestamp for + // Maitake. As timer 0 is a 32-bit timer, this forms the lower half + // of our 64-bit timestamp. + let lo = (Self::INITIAL_VALUE - timer0.current_value()) as u64; + // The higher half of the 64-bit timestamp is the current value of + // the 32-bit timer rollover counter --- i.e. the number of times + // that timer 0 has counted down to 0. + let hi = TIMER0_ROLLOVERS.load(Ordering::Relaxed) as u64; + // Combine the two halves to form the full 64-bit timestamp. + (hi << 32) | lo + }) + .named("CLOCK_D1_TIMER0") + } + + fn reset(&mut self) { + self.start_counter(Self::INITIAL_VALUE); + // N.B. that we probably don't *have* to reset the IRQ_EN bit every time + // it fires, but let's make sure it's enabled just in case... + self.set_interrupt_en(true); + } + + /// Handle a TIMER0 interrupt when TIMER0 is used as the maitake timer. + fn maitake_timer_interrupt() { + let mut timer0 = unsafe { + // Safety: we need to do this to be an ISR lol + Self::steal() + }; + + // Clear the interrupt flag + let _ = timer0.get_and_clear_interrupt(); + + // Increment the rollover counter + TIMER0_ROLLOVERS.fetch_add(1, Ordering::Relaxed); + + // Wait for the interrupt to clear to avoid repeat interrupts + let timers = unsafe { &*TIMER::PTR }; + while timers.tmr_irq_sta.read().tmr0_irq_pend().bit_is_set() {} + + // RESET THE CLOCK! + timer0.reset(); + } + + unsafe fn steal() -> Self { + Self { _x: () } + } +} + mod sealed { use d1_pac::{ generic::Reg, diff --git a/platforms/allwinner-d1/src/lib.rs b/platforms/allwinner-d1/src/lib.rs index 88521139..7839a8c0 100644 --- a/platforms/allwinner-d1/src/lib.rs +++ b/platforms/allwinner-d1/src/lib.rs @@ -163,7 +163,7 @@ pub fn kernel_entry(config: mnemos_config::MnemosConfig) -> ! { pub struct D1 { pub kernel: &'static Kernel, - pub timers: Timers, + pub timer1: mnemos_d1_core::timer::Timer1, pub plic: Plic, pub dmac: Dmac, _uart: Uart, @@ -192,10 +192,13 @@ impl D1 { kernel_settings: KernelSettings, service_settings: KernelServiceSettings, ) -> Self { + let timer0_clock = timers.timer0.into_maitake_clock(&plic); let k = unsafe { - Box::into_raw(Kernel::new(kernel_settings).expect("cannot initialize kernel")) - .as_ref() - .unwrap() + Box::into_raw( + Kernel::new(kernel_settings, timer0_clock).expect("cannot initialize kernel"), + ) + .as_ref() + .unwrap() }; k.initialize_default_services(service_settings); @@ -240,7 +243,7 @@ impl D1 { kernel: k, _uart: uart, _spim: spim, - timers, + timer1: timers.timer1, plic, dmac, i2c0_int, @@ -293,7 +296,7 @@ impl D1 { pub fn run(self) -> ! { let Self { kernel: k, - timers, + mut timer1, plic, dmac: _, _uart, @@ -308,19 +311,9 @@ impl D1 { // // In the future, we probably want to rework this to use the RTC timer for // both purposes, as this will likely play better with sleep power usage. - let Timers { - mut timer0, - mut timer1, - } = timers; - // NOTE: if you change the timer frequency, make sure you update - // initialize_kernel() below to correct the kernel timer wheel - // granularity setting! - timer0.set_prescaler(TimerPrescaler::P8); // 24M / 8: 3.00M ticks/s timer1.set_prescaler(TimerPrescaler::P8); - timer0.set_mode(TimerMode::PERIODIC); timer1.set_mode(TimerMode::SINGLE_COUNTING); - let _ = timer0.get_and_clear_interrupt(); let _ = timer1.get_and_clear_interrupt(); unsafe { @@ -344,22 +337,16 @@ impl D1 { } } - timer0.start_counter(0xFFFF_FFFF); - loop { // Tick the scheduler - let start = timer0.current_value(); let tick = k.tick(); // Timer is downcounting - let elapsed = start.wrapping_sub(timer0.current_value()); - let turn = k.timer().force_advance_ticks(elapsed.into()); + let turn = k.timer().turn(); // If there is nothing else scheduled, and we didn't just wake something up, // sleep for some amount of time if turn.expired == 0 && !tick.has_remaining { - let wfi_start = timer0.current_value(); - // TODO(AJM): Sometimes there is no "next" in the timer wheel, even though there should // be. Don't take lack of timer wheel presence as the ONLY heuristic of whether we // should just wait for SOME interrupt to occur. For now, force a max sleep of 100ms @@ -384,8 +371,7 @@ impl D1 { timer1.stop(); // Account for time slept - let elapsed = wfi_start.wrapping_sub(timer0.current_value()); - let _turn = k.timer().force_advance_ticks(elapsed.into()); + let _turn = k.timer().turn(); } } } diff --git a/platforms/esp32c3-buddy/src/lib.rs b/platforms/esp32c3-buddy/src/lib.rs index b032788d..78318002 100644 --- a/platforms/esp32c3-buddy/src/lib.rs +++ b/platforms/esp32c3-buddy/src/lib.rs @@ -16,21 +16,25 @@ use esp32c3_hal::{ use esp_backtrace as _; use core::{cell::RefCell, time::Duration}; -use kernel::{daemons, mnemos_alloc::containers::Box, services, Kernel, KernelSettings}; +use kernel::{daemons, maitake, mnemos_alloc::containers::Box, services, Kernel, KernelSettings}; static ALARM1: Mutex>>> = Mutex::new(RefCell::new(None)); pub fn init() -> &'static Kernel { - let k_settings = KernelSettings { - max_drivers: 16, + let k_settings = KernelSettings { max_drivers: 16 }; + let clock = { // the system timer has a period of `SystemTimer::TICKS_PER_SECOND` ticks. // `TICKS_PER_SECOND` is 16_000_000, so the base granularity is // 62.5ns. let's multiply it by 2 so that we have a non-fractional // number of nanoseconds. - timer_granularity: Duration::from_nanos(125), + maitake::time::Clock::new( + Duration::from_nanos(125), + || SystemTimer::now() / 2u64, // well...that was easy! + ) + .named("CLOCK_SYSTEM_TIMER_NOW") }; unsafe { - Box::into_raw(Kernel::new(k_settings).expect("cannot initialize kernel")) + Box::into_raw(Kernel::new(k_settings, clock).expect("cannot initialize kernel")) .as_ref() .unwrap() } @@ -90,20 +94,12 @@ pub fn run(k: &'static Kernel, alarm1: Alarm) -> ! { loop { tracing::debug!("tick"); - // Tick the scheduler - let start = SystemTimer::now(); let tick = k.tick(); - - // Timer is downcounting - let elapsed = SystemTimer::now() - start; - - let turn = k.timer().force_advance_ticks(elapsed / 2u64); + let turn = k.timer().turn(); // If there is nothing else scheduled, and we didn't just wake something up, // sleep for some amount of time if turn.expired == 0 && !tick.has_remaining { - let wfi_start = SystemTimer::now(); - // TODO(AJM): Sometimes there is no "next" in the timer wheel, even though there should // be. Don't take lack of timer wheel presence as the ONLY heuristic of whether we // should just wait for SOME interrupt to occur. For now, force a max sleep of 100ms @@ -132,9 +128,7 @@ pub fn run(k: &'static Kernel, alarm1: Alarm) -> ! { }); // Account for time slept - let elapsed = SystemTimer::now() - wfi_start; - - let _turn = k.timer().force_advance_ticks(elapsed / 2u64); + let _turn = k.timer().turn(); } } } @@ -144,6 +138,7 @@ pub fn run(k: &'static Kernel, alarm1: Alarm) -> ! { /// We don't actually do anything in the ALARM0 interrupt. It is only here to /// knock us out of WFI. Just disable the IRQ to prevent refires #[interrupt] +#[allow(non_snake_case)] fn SYSTIMER_TARGET1() { critical_section::with(|cs| { ALARM1 diff --git a/platforms/melpomene/Cargo.toml b/platforms/melpomene/Cargo.toml index 099c9693..450eca67 100644 --- a/platforms/melpomene/Cargo.toml +++ b/platforms/melpomene/Cargo.toml @@ -97,7 +97,8 @@ version = "0.4" # which is needed to support the Tokio Console while running in the simulator, # but is not needed by actual builds of mnemOS running on real hardware. [dependencies.maitake] -version = "0.1.0" +workspace = true +default-features = true features = ["tracing-01"] [dependencies.forth3] diff --git a/platforms/melpomene/src/main.rs b/platforms/melpomene/src/main.rs index b77bfcb1..bdf41d04 100644 --- a/platforms/melpomene/src/main.rs +++ b/platforms/melpomene/src/main.rs @@ -10,7 +10,7 @@ use melpomene::{ use mnemos_alloc::heap::MnemosAlloc; use mnemos_kernel::{ daemons::shells::{graphical_shell_mono, GraphicalShellSettings}, - Kernel, + maitake, Kernel, }; use tokio::{ task, @@ -63,8 +63,19 @@ async fn kernel_entry() { "Loaded settings", ); + let clock = { + use std::time::{Duration, SystemTime}; + maitake::time::Clock::new(Duration::from_micros(1), || { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_micros() as u64 + }) + .named("CLOCK_SYSTEMTIME_NOW") + }; let k = unsafe { - mnemos_alloc::containers::Box::into_raw(Kernel::new(config.kernel).unwrap()) + let kernel = Kernel::new(config.kernel, clock).unwrap(); + mnemos_alloc::containers::Box::into_raw(kernel) .as_ref() .unwrap() }; @@ -136,13 +147,11 @@ async fn kernel_entry() { .as_micros() as u64; loop { // Tick the scheduler - let t0 = tokio::time::Instant::now(); let tick = k.tick(); // advance the timer (don't take more than 500k years) - let ticks = t0.elapsed().as_micros() as u64; - let turn = k.timer().force_advance_ticks(ticks); - tracing::trace!("advanced timer by {ticks:?}"); + let turn = k.timer().turn(); + tracing::trace!(?turn, "turned the wheel"); // If there is nothing else scheduled, and we didn't just wake something up, // sleep for some amount of time @@ -158,16 +167,22 @@ async fn kernel_entry() { // wait for an "interrupt" futures::select! { _ = irq.notified().fuse() => { - tracing::trace!("...woken by I/O interrupt"); + tracing::trace!( + slept_for = ?wfi_start.elapsed(), + "...woken by I/O interrupt", + ); }, _ = tokio::time::sleep(Duration::from_micros(amount)).fuse() => { - tracing::trace!("woken by timer"); + tracing::trace!( + slept_for = ?wfi_start.elapsed(), + "woken by timer", + ); } } // Account for time slept - let elapsed = wfi_start.elapsed().as_micros() as u64; - let _turn = k.timer().force_advance_ticks(elapsed); + let turn = k.timer().turn(); + tracing::trace!(?turn, "turned the wheel"); } else { // let other tokio tasks (simulated hardware devices) run. tokio::task::yield_now().await; diff --git a/platforms/pomelo/Cargo.toml b/platforms/pomelo/Cargo.toml index 390b559d..85ec26a3 100644 --- a/platforms/pomelo/Cargo.toml +++ b/platforms/pomelo/Cargo.toml @@ -56,7 +56,8 @@ features = ["use-std"] # which is needed to support the Tokio Console while running in the simulator, # but is not needed by actual builds of mnemOS running on real hardware. [dependencies.maitake] -version = "0.1.0" +workspace = true +default-features = true features = ["tracing-01"] [dependencies.forth3] diff --git a/platforms/pomelo/src/main.rs b/platforms/pomelo/src/main.rs index 498c4755..05d7c8d0 100644 --- a/platforms/pomelo/src/main.rs +++ b/platforms/pomelo/src/main.rs @@ -65,14 +65,26 @@ async fn run_pomelo() { #[tracing::instrument(name = "Kernel", level = "info")] async fn kernel_entry() { - let settings = KernelSettings { - max_drivers: 16, - // TODO(eliza): chosen totally arbitrarily - timer_granularity: maitake::time::Duration::from_micros(1), + let settings = KernelSettings { max_drivers: 16 }; + + let clock = { + maitake::time::Clock::new( + // TODO(eliza): timer granularity chosen totally arbitrarily + // Anatol: the WASM app runs in a callback loop from request_animation_frame, whose + // max speed is typically (:rolleyes:) capped at the display refresh rate - + // I guess 1ms granularity should therefore be plenty? + Duration::from_micros(1), + || { + chrono::Utc::now() + .timestamp_micros() + .try_into() + .expect("could not convert i64 timestamp to u64") + }, + ) + .named("chrono-wasm") }; - let kernel = unsafe { - mnemos_alloc::containers::Box::into_raw(Kernel::new(settings).unwrap()) + mnemos_alloc::containers::Box::into_raw(Kernel::new(settings, clock).unwrap()) .as_ref() .unwrap() }; @@ -173,19 +185,21 @@ async fn kernel_entry() { cmd.dispatch(kernel); } }); + init_term(&eternal_cb); + eternal_cb.forget(); let timer = kernel.timer(); loop { - let mut then = chrono::Local::now(); + let mut then = chrono::Utc::now(); let tick = kernel.tick(); - let dt = chrono::Local::now() + let dt = chrono::Utc::now() .signed_duration_since(then) .to_std() .unwrap(); trace!("timer - before sleep: advance {dt:?}"); - let next_turn = timer.force_advance(dt); + let next_turn = timer.turn(); trace!("timer: before sleep: next turn in {next_turn:?}"); @@ -202,7 +216,7 @@ async fn kernel_entry() { ) .fuse(); - then = chrono::Local::now(); + then = chrono::Utc::now(); select! { _ = irq_rx.dequeue_async().fuse() => { trace!("timer: WAKE: \"irq\" {tick:?}"); @@ -211,10 +225,10 @@ async fn kernel_entry() { trace!("timer: WAKE: timer {tick:?}"); } } - let now = chrono::Local::now(); + let now = chrono::Utc::now(); let dt = now.signed_duration_since(then).to_std().unwrap(); trace!("timer: slept for {dt:?}"); - kernel.timer().force_advance(dt); + kernel.timer().turn(); } } } diff --git a/platforms/x86_64/core/Cargo.toml b/platforms/x86_64/core/Cargo.toml index ce8058aa..b64fc971 100644 --- a/platforms/x86_64/core/Cargo.toml +++ b/platforms/x86_64/core/Cargo.toml @@ -40,18 +40,16 @@ features = ["attributes"] default-features = false [dependencies.hal-core] -version = "0.1.0" -default-features = false +workspace = true [dependencies.hal-x86_64] version = "0.1.0" [dependencies.mycelium-util] -version = "0.1.0" +workspace = true [dependencies.mycelium-alloc] -version = "0.1.0" -features = ["buddy", "bump"] +workspace = true [dependencies.futures] version = "0.3.21" diff --git a/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs b/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs index 9fdcb13a..54322c1a 100644 --- a/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs +++ b/platforms/x86_64/core/src/bin/bootloader/bootinfo.rs @@ -69,12 +69,7 @@ impl BootInfo for BootloaderApiBootInfo { impl BootloaderApiBootInfo { fn vm_offset(&self) -> VAddr { - VAddr::from_u64( - self.inner - .physical_memory_offset - .into_option() - .expect("haha wtf"), - ) + VAddr::from_u64(self.inner.physical_memory_offset.into_option().unwrap_or(0)) } pub(super) fn from_bootloader(inner: &'static mut info::BootInfo) -> Self { diff --git a/platforms/x86_64/core/src/bin/bootloader/framebuf.rs b/platforms/x86_64/core/src/bin/bootloader/framebuf.rs index 066dc0d2..284d9195 100644 --- a/platforms/x86_64/core/src/bin/bootloader/framebuf.rs +++ b/platforms/x86_64/core/src/bin/bootloader/framebuf.rs @@ -5,10 +5,13 @@ use core::{ ops::{Deref, DerefMut}, }; use hal_x86_64::framebuffer::{self, Framebuffer}; -use mycelium_util::sync::{spin, InitOnce}; +use kernel::maitake::sync::{ + blocking::{Mutex, MutexGuard}, + spin::{InitOnce, Spinlock}, +}; #[derive(Debug)] -pub struct FramebufGuard(spin::MutexGuard<'static, info::FrameBuffer>); +pub struct FramebufGuard(MutexGuard<'static, info::FrameBuffer, Spinlock>); pub type FramebufWriter = Framebuffer<'static, FramebufGuard>; /// Locks the framebuffer and returns a [`FramebufWriter`]. @@ -76,11 +79,11 @@ pub(super) fn init(bootinfo: &mut BootInfo) -> bool { x => unimplemented!("hahaha wtf, found a weird pixel format: {:?}", x), }, }; - FRAMEBUFFER.init((cfg, spin::Mutex::new(framebuffer))); + FRAMEBUFFER.init((cfg, Mutex::new_with_raw_mutex(framebuffer, Spinlock::new()))); true } -static FRAMEBUFFER: InitOnce<(framebuffer::Config, spin::Mutex)> = +static FRAMEBUFFER: InitOnce<(framebuffer::Config, Mutex)> = InitOnce::uninitialized(); impl Deref for FramebufGuard { diff --git a/platforms/x86_64/core/src/interrupt.rs b/platforms/x86_64/core/src/interrupt.rs index 055a91e5..ceca8834 100644 --- a/platforms/x86_64/core/src/interrupt.rs +++ b/platforms/x86_64/core/src/interrupt.rs @@ -1,7 +1,4 @@ -use core::{ - ptr, - sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, -}; +use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use hal_core::{interrupt, VAddr}; pub use hal_x86_64::interrupt::*; @@ -22,16 +19,8 @@ pub fn enable_exceptions() { tracing::info!("IDT initialized!"); } -#[tracing::instrument(skip(acpi, timer))] -pub fn enable_hardware_interrupts( - acpi: Option<&acpi::InterruptModel>, - timer: &'static time::Timer, -) { - // no way to have an atomic `*const` ptr lol :| - let timer = timer as *const _ as *mut _; - let _timer = TIMER.swap(timer, Ordering::Release); - debug_assert_eq!(_timer, ptr::null_mut()); - +#[tracing::instrument(skip(acpi))] +pub fn enable_hardware_interrupts(acpi: Option<&acpi::InterruptModel>) { let controller = Controller::enable_hardware_interrupts(acpi, &crate::allocator::HEAP); controller .start_periodic_timer(TIMER_INTERVAL) @@ -84,8 +73,13 @@ static TSS: sync::Lazy = sync::Lazy::new(|| { pub(crate) static GDT: sync::InitOnce = sync::InitOnce::uninitialized(); pub const TIMER_INTERVAL: time::Duration = time::Duration::from_millis(10); -static TIMER: AtomicPtr = AtomicPtr::new(ptr::null_mut()); +pub const IDIOTIC_CLOCK: time::Clock = time::Clock::new(TIMER_INTERVAL, || { + IDIOTIC_CLOCK_TICKS.load(Ordering::Relaxed) +}) +.named("CLOCK_IDIOTIC"); + +static IDIOTIC_CLOCK_TICKS: AtomicU64 = AtomicU64::new(0); static TEST_INTERRUPT_WAS_FIRED: AtomicUsize = AtomicUsize::new(0); pub(crate) struct InterruptHandlers; @@ -128,9 +122,7 @@ impl hal_core::interrupt::Handlers for InterruptHandlers { } fn timer_tick() { - if let Some(timer) = ptr::NonNull::new(TIMER.load(Ordering::Acquire)) { - unsafe { timer.as_ref() }.advance_ticks(1); - } + IDIOTIC_CLOCK_TICKS.fetch_add(1, Ordering::Relaxed); } fn ps2_keyboard(scancode: u8) { diff --git a/platforms/x86_64/core/src/lib.rs b/platforms/x86_64/core/src/lib.rs index b66622f7..f89acf1f 100644 --- a/platforms/x86_64/core/src/lib.rs +++ b/platforms/x86_64/core/src/lib.rs @@ -26,18 +26,22 @@ pub fn init(bootinfo: &impl BootInfo, cfg: PlatformConfig) -> &'static Kernel { let k = { let settings = KernelSettings { - max_drivers: 64, // we are a big x86 system with lots of RAM, this can probably be an even bigger number! - timer_granularity: interrupt::TIMER_INTERVAL, + // we are a big x86 system with lots of RAM, + // this can probably be an even bigger number! + max_drivers: 64, }; unsafe { - Box::into_raw(Kernel::new(settings).expect("cannot initialize kernel")) - .as_ref() - .unwrap() + Box::into_raw( + Kernel::new(settings, interrupt::IDIOTIC_CLOCK).expect("cannot initialize kernel"), + ) + .as_ref() + .unwrap() } }; + tracing::info!("allocated kernel"); - init_acpi(k, cfg.rsdp_addr); + init_acpi(cfg.rsdp_addr); // TODO: PCI? // init boot processor's core-local data @@ -76,7 +80,7 @@ pub fn run(bootinfo: &impl BootInfo, kernel: &'static Kernel) -> ! { // turn the timer wheel if it wasn't turned recently and no one else is // holding a lock, ensuring any pending timer ticks are consumed. - let turn = kernel.timer().force_advance_ticks(0); + let turn = kernel.timer().turn(); // if there are no woken tasks, wait for an interrupt. otherwise, // continue ticking. @@ -84,17 +88,21 @@ pub fn run(bootinfo: &impl BootInfo, kernel: &'static Kernel) -> ! { if !has_remaining { interrupt::wait_for_interrupt(); } + + // turn the timer a second time to account for time spent in WFI. + kernel.timer().turn(); } } -fn init_acpi(k: &'static Kernel, rsdp_addr: Option) { +fn init_acpi(rsdp_addr: Option) { + tracing::info!("init acpi"); if let Some(rsdp) = 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), k.timer()); + interrupt::enable_hardware_interrupts(Some(&platform.interrupt_model)); acpi::bringup_smp(&platform) .expect("failed to bring up application processors! this is bad news!"); return; @@ -107,5 +115,5 @@ fn init_acpi(k: &'static Kernel, rsdp_addr: Option) { } // no ACPI - interrupt::enable_hardware_interrupts(None, k.timer()) + interrupt::enable_hardware_interrupts(None) } diff --git a/source/alloc/Cargo.toml b/source/alloc/Cargo.toml index 10fa0581..08c16878 100644 --- a/source/alloc/Cargo.toml +++ b/source/alloc/Cargo.toml @@ -18,11 +18,11 @@ categories = [ ] [dependencies.cordyceps] -version = "0.3" +workspace = true default-features = false [dependencies.maitake] -version = "0.1" +workspace = true default-features = false [dependencies.heapless] @@ -45,4 +45,4 @@ stats = [] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] \ No newline at end of file +rustdoc-args = ["--cfg", "docsrs"] diff --git a/source/kernel/Cargo.toml b/source/kernel/Cargo.toml index ac8236f6..1f9d9559 100644 --- a/source/kernel/Cargo.toml +++ b/source/kernel/Cargo.toml @@ -70,8 +70,7 @@ path = "../trace-proto" optional = true [dependencies.maitake] -version = "0.1.0" -default-features = false +workspace = true features = ["alloc"] [dependencies.mycelium-util] diff --git a/source/kernel/src/lib.rs b/source/kernel/src/lib.rs index 7176699c..2c53c7fa 100644 --- a/source/kernel/src/lib.rs +++ b/source/kernel/src/lib.rs @@ -120,7 +120,6 @@ pub struct Rings { #[derive(Debug, Serialize, Deserialize)] pub struct KernelSettings { pub max_drivers: usize, - pub timer_granularity: Duration, } pub struct Message { @@ -164,14 +163,23 @@ impl Kernel { /// /// The allocator MUST be initialized if required, and be ready to allocate /// data. - pub unsafe fn new(settings: KernelSettings) -> Result, &'static str> { + // Always inline `Kernel::new`, since a platform implementation will only + // ever call it once, and it happens very early in initialization, when we + // may not have much stack space! The x86_64 platform implementation would + // boot-loop (hitting a page fault on the guard page) before this was made + // `inline(always)`, so this is actually Super Duper Ultra Load Bearing. + #[inline(always)] + pub unsafe fn new( + settings: KernelSettings, + clock: maitake::time::Clock, + ) -> Result, &'static str> { let registry = registry::Registry::new(settings.max_drivers); let scheduler = LocalScheduler::new(); let inner = KernelInner { scheduler, - timer: Timer::new(settings.timer_granularity), + timer: Timer::new(clock), }; let new_kernel = diff --git a/source/kernel/src/test_util.rs b/source/kernel/src/test_util.rs index 3ae58f0d..9bd1bb10 100644 --- a/source/kernel/src/test_util.rs +++ b/source/kernel/src/test_util.rs @@ -8,7 +8,7 @@ use std::{ atomic::{AtomicBool, Ordering}, Arc, }, - time::Duration, + time::{Duration, SystemTime}, }; #[global_allocator] @@ -21,16 +21,25 @@ pub(crate) struct TestKernel { impl TestKernel { fn new() -> Self { trace_init(); + + // TODO(eliza): this clock implementation is also used in Melpomene, so + // it would be nice if we could share it with melpo... + let clock = { + maitake::time::Clock::new(Duration::from_micros(1), || { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_micros() as u64 + }) + .named("CLOCK_SYSTEMTIME_NOW") + }; + // XXX(eliza): the test kernel is gonna be leaked forever...maybe we // should do something about that, if we wanna have a lot of tests. but, // at least it means we never create a dangling pointer to it. let kernel = unsafe { NonNull::new(mnemos_alloc::containers::Box::into_raw( - Kernel::new(KernelSettings { - max_drivers: 16, - timer_granularity: Duration::from_millis(1), - }) - .unwrap(), + Kernel::new(KernelSettings { max_drivers: 16 }, clock).unwrap(), )) .expect("newly-allocated kernel mustn't be null!") }; diff --git a/source/mstd/Cargo.toml b/source/mstd/Cargo.toml index 2151a709..9f555252 100644 --- a/source/mstd/Cargo.toml +++ b/source/mstd/Cargo.toml @@ -23,12 +23,12 @@ version = "0.3" default-features = false [dependencies.cordyceps] -version = "0.3" +workspace = true default-features = false [dependencies.maitake] -version = "0.1.0" default-features = false +workspace = true [dependencies.abi] package = "mnemos-abi" @@ -49,4 +49,3 @@ default-features = false [features] panic-handler = [] - diff --git a/source/spitebuf/Cargo.toml b/source/spitebuf/Cargo.toml index b748c6ca..ff4da2f1 100644 --- a/source/spitebuf/Cargo.toml +++ b/source/spitebuf/Cargo.toml @@ -19,5 +19,5 @@ license = "MIT OR Apache-2.0" [dependencies] [dependencies.maitake] -version = "0.1.0" +workspace = true default-features = false diff --git a/tools/x86_64-bootimager/src/qemu.rs b/tools/x86_64-bootimager/src/qemu.rs index f816c8eb..0c778c05 100644 --- a/tools/x86_64-bootimager/src/qemu.rs +++ b/tools/x86_64-bootimager/src/qemu.rs @@ -74,6 +74,8 @@ impl Options { "qemu64".to_string(), "-smp".to_string(), "cores=4".to_string(), + // "-d guest_errors,cpu_reset".to_string(), + // "-no-reboot".to_string(), ] } @@ -156,7 +158,7 @@ impl Options { let qemu_tag = tag.named("QEMU"); for line in qemu_stderr.lines() { match line { - Ok(line) => eprintln!("{qemu_tag} {line:?}"), + Ok(line) => eprintln!("{qemu_tag} {line}"), Err(error) => { tracing::warn!(%error, "failed to read from QEMU stderr"); break;