From 0fcc24a4df5713303428b87cc7e9529a403d993b Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Sun, 8 Sep 2024 22:09:03 +0000 Subject: [PATCH] Replace nostd_async with a simple test executor --- on-target-tests/Cargo.toml | 2 +- on-target-tests/tests/i2c_tests/mod.rs | 1 + .../tests/i2c_tests/non_blocking.rs | 3 +- .../tests/i2c_tests/test_executor.rs | 78 +++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 on-target-tests/tests/i2c_tests/test_executor.rs 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..f8b76be98 --- /dev/null +++ b/on-target-tests/tests/i2c_tests/test_executor.rs @@ -0,0 +1,78 @@ +//! 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::addr_of, + 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); + +fn wake_fn(_data: *const ()) { + if !POLLING.load(Ordering::Relaxed) { + defmt::info!("waker called while not polling"); + } + WOKE.store(true, Ordering::Relaxed); +} + +const fn clone_fn(data: *const ()) -> RawWaker { + RawWaker::new(data, raw_waker_vtable()) +} + +fn drop_fn(_data: *const ()) {} + +const fn raw_waker_vtable() -> &'static RawWakerVTable { + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone_fn, wake_fn, wake_fn, drop_fn); + &VTABLE +} + +fn context() -> Context<'static> { + static DATA: () = (); + + 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(clone_fn(addr_of!(DATA))) }); + + // Starting from rust 1.82.0, this could be used: + // static WAKER: Waker = unsafe { Waker::from_raw(clone_fn(addr_of!(DATA))) }; + + 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. +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) { + core::hint::spin_loop(); + // TODO WFI or similar? + // But probably not important for this test executor + } + } +}