Skip to content

Commit

Permalink
Merge pull request #848 from jannic/replace-nostd_async
Browse files Browse the repository at this point in the history
Replace nostd_async with a simple test executor
  • Loading branch information
jannic authored Sep 11, 2024
2 parents 800dec5 + 985274e commit 7816958
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 3 deletions.
2 changes: 1 addition & 1 deletion on-target-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ futures = {version = "0.3.30", default-features = false, features = ["async-awai
heapless = {version = "0.8.0", features = ["portable-atomic-critical-section", "defmt-03"]}
i2c-write-iter = {version = "1.0.0", features = ["async"]}
itertools = {version = "0.12.0", default-features = false}
nostd_async = {version = "0.6.1", features = ["wfe"]}
once_cell = { version = "1.19.0", default-features = false, features = ["critical-section"] }
panic-probe = {version = "0.3", features = ["print-defmt"]}
rp2040-boot2 = "0.3.0"
rp2040-hal = {path = "../rp2040-hal", features = ["critical-section-impl", "defmt", "rt", "i2c-write-iter"]}
Expand Down
1 change: 1 addition & 0 deletions on-target-tests/tests/i2c_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rp2040_hal::{

pub mod blocking;
pub mod non_blocking;
pub mod test_executor;

pub const ADDR_7BIT: u8 = 0x2c;
pub const ADDR_10BIT: u16 = 0x12c;
Expand Down
3 changes: 1 addition & 2 deletions on-target-tests/tests/i2c_tests/non_blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ pub struct State {
}

pub fn run_test(f: impl Future) {
let runtime = nostd_async::Runtime::new();
nostd_async::Task::new(f).spawn(&runtime).join();
super::test_executor::execute(f);
}
async fn wait_with(payload: &RefCell<TargetState>, mut f: impl FnMut(&TargetState) -> bool) {
while f(payload.borrow().deref()) {
Expand Down
79 changes: 79 additions & 0 deletions on-target-tests/tests/i2c_tests/test_executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! Simplistic test executor
//!
//! Compared to a real executor, this has some limitations:
//!
//! - Can only run to completion (like block_on, but without busy polling)
//! - Can't spawn additional tasks
//! - Must not be called multiple times concurrently
use core::{
future::Future,
pin::{self, Pin},
ptr,
sync::atomic::{AtomicBool, Ordering},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

use once_cell::sync::OnceCell;

static WOKE: AtomicBool = AtomicBool::new(false);
static POLLING: AtomicBool = AtomicBool::new(false);

static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_fn, wake_fn, wake_fn, drop_fn);

fn wake_fn(_data: *const ()) {
if !POLLING.load(Ordering::Relaxed) {
defmt::info!("waker called while not polling");
}
WOKE.store(true, Ordering::Relaxed);
}

fn clone_fn(data: *const ()) -> RawWaker {
RawWaker::new(data, &VTABLE)
}

fn drop_fn(_data: *const ()) {}

fn context() -> Context<'static> {
static WAKER: OnceCell<Waker> = OnceCell::new();
// Safety: The functions in the vtable of this executor only modify static atomics.
let waker =
WAKER.get_or_init(|| unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &VTABLE)) });

// Starting from rust 1.82.0, this could be used:
// static WAKER: Waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &VTABLE)) };
// (stabilized by https://github.com/rust-lang/rust/pull/128228)

Context::from_waker(waker)
}

/// Run future to completion
///
/// poll() will only be called when the waker was invoked, so this is suitable to test
/// if the waker is properly triggered from an interrupt.
///
/// This won't work as expected of multiple calls to `execute` happen concurrently.
///
/// (Calling this function from multiple threads concurrently doesn't violate any
/// safety guarantees, but wakers may wake the wrong task, making futures stall.)
pub fn execute<T>(future: impl Future<Output = T>) -> T {
let mut pinned: Pin<&mut _> = pin::pin!(future);
if WOKE.load(Ordering::Relaxed) {
defmt::info!("woken before poll - ignoring");
}
POLLING.store(true, Ordering::Relaxed);
loop {
WOKE.store(false, Ordering::Relaxed);
if let Poll::Ready(result) = pinned.as_mut().poll(&mut context()) {
WOKE.store(false, Ordering::Relaxed);
POLLING.store(false, Ordering::Relaxed);
break result;
}
while !WOKE.load(Ordering::Relaxed) {
// In a real executor, there should be a WFI/WFE or similar here, to avoid
// busy looping.
// As this is only a test executor, we don't care.
core::hint::spin_loop();
}
}
}

0 comments on commit 7816958

Please sign in to comment.