Skip to content

Commit 552063c

Browse files
committed
Interrupt-driven async HAL implementation
1 parent 82de921 commit 552063c

File tree

9 files changed

+313
-32
lines changed

9 files changed

+313
-32
lines changed

riscv-peripheral/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9-
embedded-hal = "1.0.0-rc.2"
10-
embedded-hal-async = { version = "1.0.0-rc.2", optional = true }
9+
embedded-hal = "1.0.0-rc.3"
10+
embedded-hal-async = { version = "1.0.0-rc.3", optional = true }
1111
riscv = { path = "../riscv", version = "0.10" }
1212
riscv-pac = { path = "../riscv-pac", version = "0.1.0" }
1313

1414
[features]
15-
hal-async = ["embedded-hal-async"]
15+
aclint-hal-async = ["embedded-hal-async"]
1616

1717
[package.metadata.docs.rs]
1818
default-target = "riscv64imac-unknown-none-elf"

riscv-peripheral/examples/e310x.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ unsafe impl PriorityNumber for Priority {
137137
}
138138
}
139139

140+
#[cfg(feature = "aclint-hal-async")]
141+
riscv_peripheral::clint_codegen!(
142+
base 0x0200_0000,
143+
freq 32_768,
144+
async_delay,
145+
mtimecmps [mtimecmp0=(HartId::H0,"`H0`")],
146+
msips [msip0=(HartId::H0,"`H0`")],
147+
);
148+
149+
#[cfg(not(feature = "aclint-hal-async"))]
140150
riscv_peripheral::clint_codegen!(
141151
base 0x0200_0000,
142152
freq 32_768,

riscv-peripheral/src/aclint/mswi.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ impl MSWI {
3535
// SAFETY: `hart_id` is valid for the target
3636
unsafe { MSIP::new(self.msip0.get_ptr().offset(hart_id.number() as _) as _) }
3737
}
38+
39+
/// Returns the `MSIP` register for the current HART.
40+
///
41+
/// # Note
42+
///
43+
/// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
44+
/// Thus, it can only be used in M-mode. For S-mode, use [`MSWI::msip`] instead.
45+
#[inline]
46+
pub fn msip_mhartid(&self) -> MSIP {
47+
let hart_id = riscv::register::mhartid::read();
48+
// SAFETY: `hart_id` is valid for the target and is the current hart
49+
unsafe { MSIP::new(self.msip0.get_ptr().add(hart_id) as _) }
50+
}
3851
}
3952

4053
unsafe_peripheral!(MSIP, u32, RW);

riscv-peripheral/src/aclint/mtimer.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ impl MTIMER {
3737
// SAFETY: `hart_id` is valid for the target
3838
unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().offset(hart_id.number() as _) as _) }
3939
}
40+
41+
/// Returns the `MTIMECMP` register for the current HART.
42+
///
43+
/// # Note
44+
///
45+
/// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
46+
/// Thus, it can only be used in M-mode. For S-mode, use [`MTIMER::mtimecmp`] instead.
47+
#[inline]
48+
pub fn mtimecmp_mhartid(&self) -> MTIMECMP {
49+
let hart_id = riscv::register::mhartid::read();
50+
// SAFETY: `hart_id` is valid for the target and is the current hart
51+
unsafe { MTIMECMP::new(self.mtimecmp0.get_ptr().add(hart_id) as _) }
52+
}
4053
}
4154

4255
// MTIMECMP register.

riscv-peripheral/src/hal_async.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
33
pub use embedded_hal_async::*; // re-export embedded-hal-async to allow macros to use it
44

5+
#[cfg(feature = "aclint-hal-async")]
56
pub mod aclint; // ACLINT and CLINT peripherals
Lines changed: 220 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,216 @@
11
//! Asynchronous delay implementation for the (A)CLINT peripheral.
22
3-
use crate::aclint::mtimer::MTIME;
4-
pub use crate::hal::aclint::Delay;
3+
use crate::aclint::mtimer::{MTIME, MTIMECMP, MTIMER};
54
pub use crate::hal_async::delay::DelayNs;
65
use core::{
6+
cmp::{Eq, Ord, PartialEq, PartialOrd},
77
future::Future,
88
pin::Pin,
99
task::{Context, Poll, Waker},
1010
};
1111

12-
struct DelayAsync {
12+
extern "Rust" {
13+
/// Returns the `MTIMER` register for the given HART ID.
14+
/// This is necessary for [`MachineExternal`] to obtain the corresponding `MTIMER` register.
15+
///
16+
/// # Safety
17+
///
18+
/// Do not call this function directly. It is only meant to be called by [`MachineExternal`].
19+
fn _riscv_peripheral_aclint_mtimer(hart_id: usize) -> MTIMER;
20+
21+
/// Tries to push a new timer to the timer queue assigned to the given HART ID.
22+
/// If it fails (e.g., the timer queue is full), it returns back the timer that failed to be pushed.
23+
///
24+
/// # Note
25+
///
26+
/// the [`Delay`] reference allows to access the `MTIME` and `MTIMECMP` registers,
27+
/// as well as handy information such as the HART ID or the clock frequency of the `MTIMER` peripheral.
28+
///
29+
/// # Safety
30+
///
31+
/// Do not call this function directly. It is only meant to be called by [`DelayAsync`].
32+
fn _riscv_peripheral_push_timer(hart_id: usize, delay: &Delay, t: Timer) -> Result<(), Timer>;
33+
34+
/// Pops a expired timer from the timer queue assigned to the given HART ID.
35+
/// If the queue is empty, it returns `Err(None)`.
36+
/// Alternatively, if the queue is not empty but the earliest timer has not expired yet,
37+
/// it returns `Err(Some(next_expires))` where `next_expires` is the tick at which this timer expires.
38+
///
39+
/// # Safety
40+
///
41+
/// It is extremely important that this function only returns a timer that has expired.
42+
/// Otherwise, the timer will be lost and the waker will never be called.
43+
///
44+
/// Do not call this function directly. It is only meant to be called by [`MachineExternal`] and [`DelayAsync`].
45+
fn _riscv_peripheral_pop_timer(hart_id: usize, current_tick: u64)
46+
-> Result<Timer, Option<u64>>;
47+
}
48+
49+
/// Machine-level timer interrupt handler.
50+
/// This handler is triggered whenever the `MTIME` register reaches the value of the `MTIMECMP` register.
51+
#[no_mangle]
52+
#[allow(non_snake_case)]
53+
fn MachineExternal() {
54+
let hart_id = riscv::register::mhartid::read();
55+
let mtimer = unsafe { _riscv_peripheral_aclint_mtimer(hart_id) };
56+
let (mtime, mtimercmp) = (mtimer.mtime, mtimer.mtimecmp_mhartid());
57+
schedule_machine_external(hart_id, mtime, mtimercmp);
58+
}
59+
60+
fn schedule_machine_external(hart_id: usize, mtime: MTIME, mtimercmp: MTIMECMP) {
61+
unsafe { riscv::register::mie::clear_mtimer() }; // disable machine timer interrupts to avoid reentrancy
62+
loop {
63+
let current_tick = mtime.read();
64+
let timer = unsafe { _riscv_peripheral_pop_timer(hart_id, current_tick) };
65+
match timer {
66+
Ok(timer) => {
67+
debug_assert!(timer.expires() <= current_tick);
68+
timer.wake();
69+
}
70+
Err(e) => {
71+
if let Some(next_expires) = e {
72+
debug_assert!(next_expires > current_tick);
73+
mtimercmp.write(next_expires); // schedule next interrupt at next_expires
74+
unsafe { riscv::register::mie::set_mtimer() }; // enable machine timer interrupts again
75+
} else {
76+
mtimercmp.write(u64::MAX); // write max to clear and "disable" the interrupt
77+
}
78+
break;
79+
}
80+
}
81+
}
82+
}
83+
84+
/// Asynchronous delay implementation for (A)CLINT peripherals.
85+
#[derive(Clone)]
86+
pub struct Delay {
1387
mtime: MTIME,
14-
t0: u64,
15-
n_ticks: u64,
16-
waker: Option<Waker>,
88+
hart_id: usize,
89+
mtimecmp: MTIMECMP,
90+
freq: usize,
91+
}
92+
93+
impl Delay {
94+
/// Creates a new `Delay` instance.
95+
#[inline]
96+
pub fn new<H: riscv_pac::HartIdNumber>(mtimer: MTIMER, hart_id: H, freq: usize) -> Self {
97+
Self {
98+
mtime: mtimer.mtime,
99+
hart_id: hart_id.number() as _,
100+
mtimecmp: mtimer.mtimecmp(hart_id),
101+
freq,
102+
}
103+
}
104+
105+
/// Creates a new `Delay` instance for the current HART.
106+
/// This function determines the current HART ID by reading the [`riscv::register::mhartid`] CSR.
107+
///
108+
/// # Note
109+
///
110+
/// This function can only be used in M-mode. For S-mode, use [`Delay::new_mhartid`] instead.
111+
#[inline]
112+
pub fn new_mhartid(mtimer: MTIMER, freq: usize) -> Self {
113+
let hart_id = riscv::register::mhartid::read();
114+
Self {
115+
mtime: mtimer.mtime,
116+
hart_id,
117+
mtimecmp: mtimer.mtimecmp_mhartid(),
118+
freq,
119+
}
120+
}
121+
122+
/// Returns the frequency of the `MTIME` register.
123+
#[inline]
124+
pub const fn get_freq(&self) -> usize {
125+
self.freq
126+
}
127+
128+
/// Sets the frequency of the `MTIME` register.
129+
#[inline]
130+
pub fn set_freq(&mut self, freq: usize) {
131+
self.freq = freq;
132+
}
133+
134+
/// Returns the `MTIME` register.
135+
#[inline]
136+
pub const fn get_mtime(&self) -> MTIME {
137+
self.mtime
138+
}
139+
140+
/// Returns the `MTIMECMP` register.
141+
#[inline]
142+
pub const fn get_mtimecmp(&self) -> MTIMECMP {
143+
self.mtimecmp
144+
}
145+
146+
/// Returns the hart ID.
147+
#[inline]
148+
pub const fn get_hart_id(&self) -> usize {
149+
self.hart_id
150+
}
151+
}
152+
153+
/// Timer queue entry.
154+
#[derive(Debug)]
155+
pub struct Timer {
156+
expires: u64,
157+
waker: Waker,
158+
}
159+
160+
impl Timer {
161+
/// Creates a new timer queue entry.
162+
#[inline]
163+
pub fn new(expires: u64, waker: Waker) -> Self {
164+
Self { expires, waker }
165+
}
166+
167+
/// Returns the tick at which the timer expires.
168+
#[inline]
169+
pub const fn expires(&self) -> u64 {
170+
self.expires
171+
}
172+
173+
/// Wakes the waker associated with this timer.
174+
#[inline]
175+
pub fn wake(&self) {
176+
self.waker.wake_by_ref();
177+
}
178+
}
179+
180+
impl PartialEq for Timer {
181+
fn eq(&self, other: &Self) -> bool {
182+
self.expires == other.expires
183+
}
184+
}
185+
186+
impl Eq for Timer {}
187+
188+
impl Ord for Timer {
189+
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
190+
self.expires.cmp(&other.expires)
191+
}
192+
}
193+
194+
impl PartialOrd for Timer {
195+
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
196+
Some(self.expires.cmp(&other.expires))
197+
}
198+
}
199+
200+
struct DelayAsync {
201+
delay: Delay,
202+
expires: u64,
203+
pushed: bool,
17204
}
18205

19206
impl DelayAsync {
20-
pub fn new(mtime: MTIME, n_ticks: u64) -> Self {
21-
let t0 = mtime.read();
207+
pub fn new(delay: Delay, n_ticks: u64) -> Self {
208+
let t0 = delay.mtime.read();
209+
let expires = t0.wrapping_add(n_ticks);
22210
Self {
23-
mtime,
24-
t0,
25-
n_ticks,
26-
waker: None,
211+
delay,
212+
expires,
213+
pushed: false,
27214
}
28215
}
29216
}
@@ -32,21 +219,26 @@ impl Future for DelayAsync {
32219
type Output = ();
33220

34221
#[inline]
35-
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
36-
match self.mtime.read().wrapping_sub(self.t0) < self.n_ticks {
37-
true => {
38-
self.get_mut().waker = Some(cx.waker().clone());
39-
Poll::Pending
40-
}
41-
false => {
42-
if let Some(waker) = self.get_mut().waker.take() {
43-
waker.wake();
44-
} else {
45-
// corner case: delay expired before polling for the first time
46-
cx.waker().wake_by_ref();
222+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
223+
if self.delay.mtime.read() < self.expires {
224+
if !self.pushed {
225+
// we only push the timer to the queue the first time we poll
226+
self.pushed = true;
227+
let timer = Timer::new(self.expires, cx.waker().clone());
228+
unsafe {
229+
_riscv_peripheral_push_timer(self.delay.hart_id, &self.delay, timer)
230+
.expect("timer queue is full");
47231
};
48-
Poll::Ready(())
232+
// we also need to schedule the interrupt if the timer we just pushed is the earliest one
233+
schedule_machine_external(
234+
self.delay.hart_id,
235+
self.delay.mtime,
236+
self.delay.mtimecmp,
237+
);
49238
}
239+
Poll::Pending
240+
} else {
241+
Poll::Ready(())
50242
}
51243
}
52244
}
@@ -55,18 +247,18 @@ impl DelayNs for Delay {
55247
#[inline]
56248
async fn delay_ns(&mut self, ns: u32) {
57249
let n_ticks = ns as u64 * self.get_freq() as u64 / 1_000_000_000;
58-
DelayAsync::new(self.get_mtime(), n_ticks).await;
250+
DelayAsync::new(self.clone(), n_ticks).await;
59251
}
60252

61253
#[inline]
62254
async fn delay_us(&mut self, us: u32) {
63255
let n_ticks = us as u64 * self.get_freq() as u64 / 1_000_000;
64-
DelayAsync::new(self.get_mtime(), n_ticks).await;
256+
DelayAsync::new(self.clone(), n_ticks).await;
65257
}
66258

67259
#[inline]
68260
async fn delay_ms(&mut self, ms: u32) {
69261
let n_ticks = ms as u64 * self.get_freq() as u64 / 1_000;
70-
DelayAsync::new(self.get_mtime(), n_ticks).await;
262+
DelayAsync::new(self.clone(), n_ticks).await;
71263
}
72264
}

riscv-peripheral/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub use riscv; // re-export riscv crate to allow macros to use it
77

88
pub mod common; // common definitions for all peripherals
99
pub mod hal; // trait implementations for embedded-hal
10-
#[cfg(feature = "hal-async")]
10+
#[cfg(feature = "embedded-hal-async")]
1111
pub mod hal_async; // async trait implementations for embedded-hal
1212
pub mod macros; // macros for easing the definition of peripherals in PACs
1313

0 commit comments

Comments
 (0)