Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] attempting kernel runloop abstraction #251

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion platforms/melpomene/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ optional = true

[dependencies.tokio]
version = "1.19"
features = ["rt", "time", "macros", "sync"]
features = ["rt", "rt-multi-thread", "time", "macros", "sync"]

[dependencies.clap]
version = "3.0"
Expand Down
56 changes: 18 additions & 38 deletions platforms/melpomene/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn main() {
#[global_allocator]
static AHEAP: MnemosAlloc<System> = MnemosAlloc::new();

#[tokio::main(flavor = "current_thread")]
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn run_melpomene() {
let local = tokio::task::LocalSet::new();
println!("========================================");
Expand Down Expand Up @@ -128,45 +128,25 @@ async fn kernel_entry() {
let sleep_cap = config
.platform
.sleep_cap
.unwrap_or_else(PlatformConfig::default_sleep_cap)
.as_micros() as u64;
.unwrap_or_else(PlatformConfig::default_sleep_cap);

let t0 = tokio::time::Instant::now();
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:?}");

// 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 = tokio::time::Instant::now();
// if no timers have expired on this tick, we should sleep until the
// next timer expires *or* something is woken by I/O, to simulate a
// hardware platform waiting for an interrupt.
tracing::trace!("waiting for an interrupt...");

let amount = turn.ticks_to_next_deadline().unwrap_or(sleep_cap);
tracing::trace!("next timer expires in {amount:?}us");
// wait for an "interrupt"
futures::select! {
_ = irq.notified().fuse() => {
tracing::trace!("...woken by I/O interrupt");
},
_ = tokio::time::sleep(Duration::from_micros(amount)).fuse() => {
tracing::trace!("woken by timer");
}
}
let sleep = k.run_until_sleepy(|| t0.elapsed());

tracing::trace!("waiting for an interrupt...");

// Account for time slept
let elapsed = wfi_start.elapsed().as_micros() as u64;
let _turn = k.timer().force_advance_ticks(elapsed);
} else {
// let other tokio tasks (simulated hardware devices) run.
tokio::task::yield_now().await;
let amount = sleep.next_deadline.unwrap_or(sleep_cap);
tracing::trace!("next timer expires in {amount:?}us");

// wait for an "interrupt"
futures::select! {
_ = irq.notified().fuse() => {
tracing::trace!("...woken by I/O interrupt");
},
_ = tokio::time::sleep(amount).fuse() => {
tracing::trace!("woken by timer");
}
}
}
}
64 changes: 64 additions & 0 deletions source/kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ pub struct KernelServiceSettings {
pub sermux_trace: serial_trace::SerialTraceSettings,
}

pub struct Sleepy<F> {
pub next_deadline: Option<Duration>,
kernel: &'static Kernel,
wfi_start: Duration,
now: F,
}

impl Kernel {
/// Create a new kernel with the given settings.
///
Expand Down Expand Up @@ -256,6 +263,51 @@ impl Kernel {
self.inner.timer.timeout(duration, f)
}

/// Run the kernel executor continuously until no scheduled work is
/// remaining, returning the duration for which the platform implementation
/// should sleep.
///
/// # Returns
///
/// - [`Some`]`(`[`Duration`]`)` if the platform implementation should set a
/// timer before waiting for an interrupt. If this method returns
/// [`Some`], the platform should set a timer to expire after the returned
/// [`Duration`] prior to sleeping, and must advance the kernel's timer
/// by the period for which the platform slept.
/// - [`None`] if there is no pending timeouts in the kernel's timer wheel.
/// In this case, the platform implementation should sleep until an interrupt
/// occurs. The platform *may* choose to set a timer anyway, to limit the
/// maximum amount of time spent waiting for an interrupt.
pub fn run_until_sleepy<F>(&'static self, mut now: F) -> Sleepy<F>
where
F: FnMut() -> Duration,
{
loop {
let start = now();

// Tick the scheduler.
let tick = self.tick();

// Advance the timer by the tick duration.
let elapsed = now() - start;
let turn = self.timer().force_advance(elapsed);

// If there is nothing else scheduled, and we didn't just wake
// something up, it's time for the platform implementation to
// sleep.
if turn.expired == 0 && !tick.has_remaining {
return Sleepy {
kernel: self,
wfi_start: now(),
next_deadline: turn.time_to_next_deadline(),
now,
};
}

// Otherwise, continue looping.
}
}

/// Initialize the default set of cross-platform kernel [`services`] that
/// are spawned on all hardware platforms.
///
Expand Down Expand Up @@ -367,3 +419,15 @@ impl Kernel {
}
}
}

// === impl Sleepy ===

impl<F> Sleepy<F>
where
F: FnMut() -> Duration,
{
pub fn wake_up(mut self) {
let elapsed = (self.now)().saturating_sub(self.wfi_start);
self.kernel.timer().force_advance(elapsed);
}
}