diff --git a/on-target-tests/Cargo.toml b/on-target-tests/Cargo.toml index 02c51a1c6..e41f09bba 100644 --- a/on-target-tests/Cargo.toml +++ b/on-target-tests/Cargo.toml @@ -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"]} diff --git a/on-target-tests/tests/i2c_tests/mod.rs b/on-target-tests/tests/i2c_tests/mod.rs index d186e3a69..7cd1c0267 100644 --- a/on-target-tests/tests/i2c_tests/mod.rs +++ b/on-target-tests/tests/i2c_tests/mod.rs @@ -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; diff --git a/on-target-tests/tests/i2c_tests/non_blocking.rs b/on-target-tests/tests/i2c_tests/non_blocking.rs index 43923fd36..87cc111a9 100644 --- a/on-target-tests/tests/i2c_tests/non_blocking.rs +++ b/on-target-tests/tests/i2c_tests/non_blocking.rs @@ -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, mut f: impl FnMut(&TargetState) -> bool) { while f(payload.borrow().deref()) { diff --git a/on-target-tests/tests/i2c_tests/test_executor.rs b/on-target-tests/tests/i2c_tests/test_executor.rs new file mode 100644 index 000000000..dfd1328ce --- /dev/null +++ b/on-target-tests/tests/i2c_tests/test_executor.rs @@ -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 = 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(future: impl Future) -> 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(); + } + } +}