diff --git a/examples/async-idle.rs b/examples/async-idle.rs new file mode 100644 index 0000000000..0161a2eca4 --- /dev/null +++ b/examples/async-idle.rs @@ -0,0 +1,51 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + async fn idle(_: idle::Context) -> ! { + hprintln!("idle"); + + for i in 0..2 { + monotonics::delay(100.millis()).await; + hprintln!("async delay {}").ok(); + } + + debug::exit(debug::EXIT_SUCCESS); + + loop {} + } +} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index 83b85d7ba6..64189a3287 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -63,10 +63,15 @@ pub fn codegen( let attrs = &idle.attrs; let context = &idle.context; let stmts = &idle.stmts; + let async_ = if idle.is_async { + quote!(async) + } else { + quote!() + }; let user_idle = Some(quote!( #(#attrs)* #[allow(non_snake_case)] - fn #name(#context: #name::Context) -> ! { + #async_ fn #name(#context: #name::Context) -> ! { use rtic::Mutex as _; use rtic::mutex::prelude::*; @@ -74,9 +79,16 @@ pub fn codegen( } )); - let call_idle = quote!(#name( - #name::Context::new(&rtic::export::Priority::new(0)) - )); + let call_idle = if idle.is_async { + quote!( + let idle_task = #name(#name::Context::new(&rtic::export::Priority::new(0))); + rtic::export::idle_executor::IdleExecutor::new(idle_task).run(); + ) + } else { + quote!(#name( + #name::Context::new(&rtic::export::Priority::new(0)) + )) + }; (mod_app, root_idle, user_idle, call_idle) } else { diff --git a/src/export.rs b/src/export.rs index 08101cdd49..1d17d46b02 100644 --- a/src/export.rs +++ b/src/export.rs @@ -21,6 +21,60 @@ pub use heapless::BinaryHeap; pub use heapless::Vec; pub use rtic_monotonic as monotonic; +pub mod idle_executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + fn no_op(_: *const ()) {} + fn no_op_clone(_: *const ()) -> RawWaker { + noop_raw_waker() + } + + static IDLE_WAKER_TABLE: RawWakerVTable = RawWakerVTable::new(no_op_clone, no_op, no_op, no_op); + + #[inline] + fn noop_raw_waker() -> RawWaker { + RawWaker::new(core::ptr::null(), &IDLE_WAKER_TABLE) + } + + pub struct IdleExecutor + where + T: Future, + { + idle: T, + } + + impl IdleExecutor + where + T: Future, + { + #[inline(always)] + pub fn new(idle: T) -> Self { + Self { idle } + } + + #[inline(always)] + pub fn run(&mut self) -> ! { + let w = unsafe { Waker::from_raw(noop_raw_waker()) }; + let mut ctxt = Context::from_waker(&w); + loop { + match unsafe { Pin::new_unchecked(&mut self.idle) }.poll(&mut ctxt) { + Poll::Pending => { + // All ok! + } + Poll::Ready(_) => { + // The idle executor will never return + unreachable!() + } + } + } + } + } +} + pub mod executor { use core::{ future::Future,