Skip to content

Commit

Permalink
added example for async delays with CLINT
Browse files Browse the repository at this point in the history
  • Loading branch information
romancardenas committed Jan 15, 2024
1 parent f4e29c4 commit e9cd65d
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 27 deletions.
3 changes: 3 additions & 0 deletions riscv-peripheral/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ embedded-hal-async = { version = "1.0.0", optional = true }
riscv = { path = "../riscv", version = "0.11.0" }
riscv-pac = { path = "../riscv-pac", version = "0.1.0" }

[dev-dependencies]
heapless = "0.8.0"

[features]
aclint-hal-async = ["embedded-hal-async"]

Expand Down
42 changes: 42 additions & 0 deletions riscv-peripheral/examples/e310x.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,46 @@ riscv_peripheral::plic_codegen!(
ctxs [ctx0=(HartId::H0,"`H0`")],
);

#[cfg(feature = "aclint-hal-async")]
/// extern functions needed by the `riscv-peripheral` crate for the `async` feature.
///
/// # Note
///
/// The functionality in this module is just to illustrate how to enable the `async` feature
/// The timer queue used here, while functional, is unsound and should not be used in production.
/// In this case, you should protect the timer queue with a mutex or critical section.
/// For a more robust implementation, use proper timer queues such as the ones provided by `embassy-time`
mod async_no_mangle {
use super::CLINT;
use heapless::binary_heap::{BinaryHeap, Min};
use riscv_peripheral::{aclint::mtimer::MTIMER, hal_async::aclint::Timer};

const N_TIMERS: usize = 16;
static mut TIMER_QUEUE: BinaryHeap<Timer, Min, N_TIMERS> = BinaryHeap::new();

#[no_mangle]
fn _riscv_peripheral_aclint_mtimer() -> MTIMER {
CLINT::mtimer()
}

#[no_mangle]
fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer> {
unsafe { TIMER_QUEUE.push(t) }
}

#[no_mangle]
fn _riscv_peripheral_aclint_wake_timers(current_tick: u64) -> Option<u64> {
let mut next_expires = None;
while let Some(t) = unsafe { TIMER_QUEUE.peek() } {
if t.expires() > current_tick {
next_expires = Some(t.expires());
break;
}
let t = unsafe { TIMER_QUEUE.pop() }.unwrap();
t.waker().wake_by_ref();
}
next_expires
}
}

fn main() {}
28 changes: 7 additions & 21 deletions riscv-peripheral/src/hal_async/aclint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
//! The following `extern "Rust"` functions must be implemented:
//!
//! - `fn _riscv_peripheral_aclint_mtimer(hart_id: usize) -> MTIMER`: This function returns the `MTIMER` register for the given HART ID.
//! This function is implemented by the [`crate::clint_codegen`] macro when asyn_delay is provided.
//! - `fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer>`: This function pushes a new timer to a timer queue assigned to the given HART ID.
//! If it fails (e.g., the timer queue is full), it returns back the timer that failed to be pushed.
//! The logic of timer queues are application-specific and are not provided by this crate.
Expand All @@ -37,18 +36,18 @@ extern "Rust" {
/// Do not call this function directly. It is only meant to be called by [`MachineTimer`].
fn _riscv_peripheral_aclint_mtimer() -> MTIMER;

/// Tries to push a new timer to the timer queue assigned to the given HART ID.
/// Tries to push a new timer to the timer queue assigned to the `MTIMER` register for the current HART ID.
/// If it fails (e.g., the timer queue is full), it returns back the timer that failed to be pushed.
///
/// # Safety
///
/// Do not call this function directly. It is only meant to be called by [`DelayAsync`].
fn _riscv_peripheral_aclint_push_timer(t: Timer) -> Result<(), Timer>;

/// Pops all the expired timers from the timer queue assigned to the current HART ID and wakes their associated wakers.
/// Once it is done, if the queue is empty, it returns `None`.
/// Alternatively, if the queue is not empty but the earliest timer has not expired yet,
/// it returns `Some(next_expires)` where `next_expires` is the tick at which this timer expires.
/// Pops all the expired timers from the timer queue assigned to the `MTIMER` register for the
/// current HART ID and wakes their associated wakers. Once it is done, if the queue is empty,
/// it returns `None`. Alternatively, if the queue is not empty but the earliest timer has not expired
/// yet, it returns `Some(next_expires)` where `next_expires` is the tick at which this timer expires.
///
/// # Safety
///
Expand All @@ -75,7 +74,7 @@ fn schedule_machine_timer(mtime: MTIME, mtimercmp: MTIMECMP) {
if let Some(next_expires) = unsafe { _riscv_peripheral_aclint_wake_timers(current_tick) } {
debug_assert!(next_expires > current_tick);
mtimercmp.write(next_expires); // schedule next interrupt at next_expires
unsafe { riscv::register::mie::set_mtimer() }; // enable machine timer interrupts again if necessary
unsafe { riscv::register::mie::set_mtimer() }; // enable machine timer interrupts
}
}

Expand All @@ -89,7 +88,6 @@ fn schedule_machine_timer(mtime: MTIME, mtimercmp: MTIMECMP) {
/// Additionally, the rest of the application must not modify the [`MTIMER`] register assigned to the current HART.
#[derive(Clone)]
pub struct Delay {
hart_id: usize,
freq: usize,
mtime: MTIME,
mtimecmp: MTIMECMP,
Expand All @@ -99,11 +97,9 @@ impl Delay {
/// Creates a new `Delay` instance for the current HART.
#[inline]
pub fn new(freq: usize) -> Self {
let hart_id = riscv::register::mhartid::read();
let mtimer = unsafe { _riscv_peripheral_aclint_mtimer() };
let (mtime, mtimecmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid());
Self {
hart_id,
freq,
mtime,
mtimecmp,
Expand Down Expand Up @@ -148,7 +144,6 @@ impl DelayNs for Delay {
/// this entry provides the necessary information to adapt it to the timer queue implementation.
#[derive(Debug)]
pub struct Timer {
hart_id: usize,
freq: usize,
mtime: MTIME,
mtimecmp: MTIMECMP,
Expand All @@ -160,15 +155,13 @@ impl Timer {
/// Creates a new timer queue entry.
#[inline]
const fn new(
hart_id: usize,
freq: usize,
mtime: MTIME,
mtimecmp: MTIMECMP,
expires: u64,
waker: Waker,
) -> Self {
Self {
hart_id,
freq,
mtime,
mtimecmp,
Expand All @@ -177,12 +170,6 @@ impl Timer {
}
}

/// Returns the HART ID associated with this timer.
#[inline]
pub const fn hart_id(&self) -> usize {
self.hart_id
}

/// Returns the frequency of the [`MTIME`] register associated with this timer.
#[inline]
pub const fn freq(&self) -> usize {
Expand Down Expand Up @@ -216,7 +203,7 @@ impl Timer {

impl PartialEq for Timer {
fn eq(&self, other: &Self) -> bool {
self.hart_id == other.hart_id && self.freq == other.freq && self.expires == other.expires
self.freq == other.freq && self.expires == other.expires
}
}

Expand Down Expand Up @@ -262,7 +249,6 @@ impl<'a> Future for DelayAsync<'a> {
// we only push the timer to the queue the first time we poll
self.pushed = true;
let timer = Timer::new(
self.delay.hart_id,
self.delay.freq,
self.delay.mtime,
self.delay.mtimecmp,
Expand Down
6 changes: 0 additions & 6 deletions riscv-peripheral/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,6 @@ macro_rules! clint_codegen {
$crate::clint_codegen!($($tail)*);
};
(async_delay, $($tail:tt)*) => {

#[no_mangle]
const fn _riscv_peripheral_aclint_mtimer() -> $crate::aclint::mtimer::MTIMER {
CLINT::mtimer()
}

impl CLINT {
/// Asynchronous delay implementation for CLINT peripherals.
///
Expand Down

0 comments on commit e9cd65d

Please sign in to comment.