Skip to content

Instantly share code, notes, and snippets.

@jamesmunns
Last active May 9, 2022 17:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesmunns/3a8abd81185a98be8757df6bbee34e15 to your computer and use it in GitHub Desktop.
Save jamesmunns/3a8abd81185a98be8757df6bbee34e15 to your computer and use it in GitHub Desktop.
//! NOTE: This is done in the lowest priority interrupt for MnemOS, PendSV, which is
//! below the priority of all other system call, interrupt, and exception handlers.
//!
//! The timer interrupt triggered below is the highest prio interrupt, but this code
//! will work as long as it is in a lower priority level than any other interrupt,
//! including the timer interrupt.
//!
//! For MnemOS specifically, this code is triggered by the userspace application
//! requesting a sleep via a syscall.
//!
//! You could probably replicate this in your code's idle function, with a minor
//! penalty to interrupt response latency (probably a couple dozen cycles).
// Implement a loop that counts the amount of time the CPU is idle.
if let Some(dly) = sleep {
// Clear the "timer expired flag". This is set by the timer interrupt task,
// which runs when the hardware one-shot timer has expired.
SNAP.store(false, Ordering::Release);
// Set up the timer to delay for the requested number of microseconds.
// Start the timer, and enable the interrupt
hwtimer.set_oneshot();
hwtimer.shorts.write(|w| w.compare0_stop().enabled());
hwtimer.timer_reset_event();
hwtimer.enable_interrupt();
hwtimer.timer_start(dly);
loop {
// In order to make sure that we measure ALL interrupts, we disable them,
// which will still *pend* them, but not *service* them
cortex_m::interrupt::disable();
// Don't check the "interrupt done" flag until AFTER interrupts are disabled,
// to prevent racing with the interrupt
let done = SNAP.load(Ordering::Acquire);
// If the timer hasn't expired yet, go to sleep with a WFI, and measure how
// long we were in that WFI condition
if !done {
let start = hwtimer.read_counter();
// Despite being in an interrupt at the moment, WFI will still return IF
// a higher priority interrupt is fired (which our timer interrupt is).
//
// This means that we know when we make it past this block, SOME interrupt
// has fired, either our high prio timer, or some other interrupt which will
// cause us to use CPU.
compiler_fence(Ordering::SeqCst);
cortex_m::asm::wfi();
compiler_fence(Ordering::SeqCst);
// Increment the time spent sleeping, based on how long the time was between
// starting WFI, and ending WFI.
let end = hwtimer.read_counter();
IDLE_TICKS.fetch_add(end - start, Ordering::SeqCst);
}
// Whether we are done or not, re-enable interrupts, to allow them to be immediately
// serviced. This will either handle our timer event, or whatever other interrupt
// is pending.
unsafe {
cortex_m::interrupt::enable();
}
// If we noticed our timer interrupt is done, break out of the loop.
if done {
break;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment