diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs index ddcee0cb..54cf6fb8 100644 --- a/embassy-macros/src/lib.rs +++ b/embassy-macros/src/lib.rs @@ -115,12 +115,12 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { let result = quote! { #(#attrs)* #visibility fn #name(#args) -> #embassy_path::executor::SpawnToken<#impl_ty> { - use #embassy_path::executor::raw::Task; + use #embassy_path::executor::raw::TaskStorage; #task_fn type F = #impl_ty; - const NEW_TASK: Task = Task::new(); - static POOL: [Task; #pool_size] = [NEW_TASK; #pool_size]; - unsafe { Task::spawn_pool(&POOL, move || task(#arg_names)) } + const NEW_TASK: TaskStorage = TaskStorage::new(); + static POOL: [TaskStorage; #pool_size] = [NEW_TASK; #pool_size]; + unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) } } }; result.into() diff --git a/embassy/src/executor/arch/arm.rs b/embassy/src/executor/arch/arm.rs index 4fd734cd..d23a595f 100644 --- a/embassy/src/executor/arch/arm.rs +++ b/embassy/src/executor/arch/arm.rs @@ -4,12 +4,23 @@ use core::ptr; use super::{raw, Spawner}; use crate::interrupt::{Interrupt, InterruptExt}; +/// Thread mode executor, using WFE/SEV. +/// +/// This is the simplest and most common kind of executor. It runs on +/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction +/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction +/// is executed, to make the `WFE` exit from sleep and poll the task. +/// +/// This executor allows for ultra low power consumption for chips where `WFE` +/// triggers low-power sleep without extra steps. If your chip requires extra steps, +/// you may use [`raw::Executor`] directly to program custom behavior. pub struct Executor { inner: raw::Executor, not_send: PhantomData<*mut ()>, } impl Executor { + /// Create a new Executor. pub fn new() -> Self { Self { inner: raw::Executor::new(|_| cortex_m::asm::sev(), ptr::null_mut()), @@ -17,14 +28,29 @@ impl Executor { } } - /// Runs the executor. + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [Forever](crate::util::Forever) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) /// /// This function never returns. pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(unsafe { self.inner.spawner() }); + init(self.inner.spawner()); loop { - unsafe { self.inner.run_queued() }; + unsafe { self.inner.poll() }; cortex_m::asm::wfe(); } } @@ -41,6 +67,27 @@ fn pend_by_number(n: u16) { cortex_m::peripheral::NVIC::pend(N(n)) } +/// Interrupt mode executor. +/// +/// This executor runs tasks in interrupt mode. The interrupt handler is set up +/// to poll tasks, and when a task is woken the interrupt is pended from software. +/// +/// This allows running async tasks at a priority higher than thread mode. One +/// use case is to leave thread mode free for non-async tasks. Another use case is +/// to run multiple executors: one in thread mode for low priority tasks and another in +/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower +/// priority ones. +/// +/// It is even possible to run multiple interrupt mode executors at different priorities, +/// by assigning different priorities to the interrupts. For an example on how to do this, +/// See the 'multiprio' example for 'embassy-nrf'. +/// +/// To use it, you have to pick an interrupt that won't be used by the hardware. +/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). +/// If this is not the case, you may use an interrupt from any unused peripheral. +/// +/// It is somewhat more complex to use, it's recommended to use the thread-mode +/// [`Executor`] instead, if it works for your use case. pub struct InterruptExecutor { irq: I, inner: raw::Executor, @@ -48,6 +95,7 @@ pub struct InterruptExecutor { } impl InterruptExecutor { + /// Create a new Executor. pub fn new(irq: I) -> Self { let ctx = irq.number() as *mut (); Self { @@ -59,16 +107,29 @@ impl InterruptExecutor { /// Start the executor. /// - /// `init` is called in the interrupt context, then the interrupt is - /// configured to run the executor. + /// The `init` closure is called from interrupt mode, with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the interrupt is configured so that the executor starts running the tasks. + /// Once the executor is started, `start` returns. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [Forever](crate::util::Forever) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) pub fn start(&'static mut self, init: impl FnOnce(Spawner) + Send) { self.irq.disable(); - init(unsafe { self.inner.spawner() }); + init(self.inner.spawner()); self.irq.set_handler(|ctx| unsafe { let executor = &*(ctx as *const raw::Executor); - executor.run_queued(); + executor.poll(); }); self.irq.set_handler_context(&self.inner as *const _ as _); self.irq.enable(); diff --git a/embassy/src/executor/arch/std.rs b/embassy/src/executor/arch/std.rs index fb788054..b93ab8a7 100644 --- a/embassy/src/executor/arch/std.rs +++ b/embassy/src/executor/arch/std.rs @@ -3,6 +3,7 @@ use std::sync::{Condvar, Mutex}; use super::{raw, Spawner}; +/// Single-threaded std-based executor. pub struct Executor { inner: raw::Executor, not_send: PhantomData<*mut ()>, @@ -10,6 +11,7 @@ pub struct Executor { } impl Executor { + /// Create a new Executor. pub fn new() -> Self { let signaler = &*Box::leak(Box::new(Signaler::new())); Self { @@ -25,14 +27,29 @@ impl Executor { } } - /// Runs the executor. + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [Forever](crate::util::Forever) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) /// /// This function never returns. pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(unsafe { self.inner.spawner() }); + init(self.inner.spawner()); loop { - unsafe { self.inner.run_queued() }; + unsafe { self.inner.poll() }; self.signaler.wait() } } diff --git a/embassy/src/executor/mod.rs b/embassy/src/executor/mod.rs index 5ffbe689..1f6bdd27 100644 --- a/embassy/src/executor/mod.rs +++ b/embassy/src/executor/mod.rs @@ -1,3 +1,7 @@ +//! Async task executor. + +#![deny(missing_docs)] + #[cfg_attr(feature = "std", path = "arch/std.rs")] #[cfg_attr(not(feature = "std"), path = "arch/arm.rs")] mod arch; diff --git a/embassy/src/executor/raw/mod.rs b/embassy/src/executor/raw/mod.rs index 235a0919..05fb2975 100644 --- a/embassy/src/executor/raw/mod.rs +++ b/embassy/src/executor/raw/mod.rs @@ -1,3 +1,12 @@ +//! Raw executor. +//! +//! This module exposes "raw" Executor and Task structs for more low level control. +//! +//! ## WARNING: here be dragons! +//! +//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe +//! executor wrappers in [`crate::executor`] and the [`crate::task`] macro, which are fully safe. + mod run_queue; #[cfg(feature = "time")] mod timer_queue; @@ -30,6 +39,10 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; #[cfg(feature = "time")] pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; +/// Raw task header for use in task pointers. +/// +/// This is an opaque struct, used for raw pointers to tasks, for use +/// with funtions like [`wake_task`] and [`task_from_waker`]. pub struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, @@ -85,15 +98,29 @@ impl TaskHeader { } } +/// Raw storage in which a task can be spawned. +/// +/// This struct holds the necessary memory to spawn one task whose future is `F`. +/// At a given time, the `Task` may be in spawned or not-spawned state. You may spawn it +/// with [`Task::spawn()`], which will fail if it is already spawned. +/// +/// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished +/// running. Hence the relevant methods require `&'static self`. It may be reused, however. +/// +/// Internally, the [embassy::task](crate::task) macro allocates an array of `TaskStorage`s +/// in a `static`. The most common reason to use the raw `Task` is to have control of where +/// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc. + // repr(C) is needed to guarantee that the Task is located at offset 0 // This makes it safe to cast between Task and Task pointers. #[repr(C)] -pub struct Task { +pub struct TaskStorage { raw: TaskHeader, future: UninitCell, // Valid if STATE_SPAWNED } -impl Task { +impl TaskStorage { + /// Create a new Task, in not-spawned state. pub const fn new() -> Self { Self { raw: TaskHeader::new(), @@ -101,6 +128,12 @@ impl Task { } } + /// Try to spawn a task in a pool. + /// + /// See [`Self::spawn()`] for details. + /// + /// This will loop over the pool and spawn the task in the first storage that + /// is currently free. If none is free, pub fn spawn_pool(pool: &'static [Self], future: impl FnOnce() -> F) -> SpawnToken { for task in pool { if task.spawn_allocate() { @@ -111,6 +144,19 @@ impl Task { SpawnToken::new_failed() } + /// Try to spawn the task. + /// + /// The `future` closure constructs the future. It's only called if spawning is + /// actually possible. It is a closure instead of a simple `future: F` param to ensure + /// the future is constructed in-place, avoiding a temporary copy in the stack thanks to + /// NRVO optimizations. + /// + /// This function will fail if the task is already spawned and has not finished running. + /// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will + /// cause [`Executor::spawn()`] to return the error. + /// + /// Once the task has finished running, you may spawn it again. It is allowed to spawn it + /// on a different executor. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { if self.spawn_allocate() { unsafe { self.spawn_initialize(future) } @@ -136,7 +182,7 @@ impl Task { } unsafe fn poll(p: NonNull) { - let this = &*(p.as_ptr() as *const Task); + let this = &*(p.as_ptr() as *const TaskStorage); let future = Pin::new_unchecked(this.future.as_mut()); let waker = waker::from_task(p); @@ -155,8 +201,27 @@ impl Task { } } -unsafe impl Sync for Task {} +unsafe impl Sync for TaskStorage {} +/// Raw executor. +/// +/// This is the core of the Embassy executor. It is low-level, requiring manual +/// handling of wakeups and task polling. If you can, prefer using one of the +/// higher level executors in [`crate::executor`]. +/// +/// The raw executor leaves it up to you to handle wakeups and scheduling: +/// +/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks +/// that "want to run"). +/// - You must supply a `signal_fn`. The executor will call it to notify you it has work +/// to do. You must arrange for `poll()` to be called as soon as possible. +/// +/// `signal_fn` can be called from *any* context: any thread, any interrupt priority +/// level, etc. It may be called synchronously from any `Executor` method call as well. +/// You must deal with this correctly. +/// +/// In particular, you must NOT call `poll` directly from `signal_fn`, as this violates +/// the requirement for `poll` to not be called reentrantly. pub struct Executor { run_queue: RunQueue, signal_fn: fn(*mut ()), @@ -169,6 +234,12 @@ pub struct Executor { } impl Executor { + /// Create a new executor. + /// + /// When the executor has work to do, it will call `signal_fn` with + /// `signal_ctx` as argument. + /// + /// See [`Executor`] docs for details on `signal_fn`. pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self { #[cfg(feature = "time")] let alarm = unsafe { unwrap!(driver::allocate_alarm()) }; @@ -187,23 +258,51 @@ impl Executor { } } - pub fn set_signal_ctx(&mut self, signal_ctx: *mut ()) { - self.signal_ctx = signal_ctx; - } - - unsafe fn enqueue(&self, item: *mut TaskHeader) { - if self.run_queue.enqueue(item) { + /// Enqueue a task in the task queue + /// + /// # Safety + /// - `task` must be a valid pointer to a spawned task. + /// - `task` must be set up to run in this executor. + /// - `task` must NOT be already enqueued (in this executor or another one). + unsafe fn enqueue(&self, task: *mut TaskHeader) { + if self.run_queue.enqueue(task) { (self.signal_fn)(self.signal_ctx) } } - pub unsafe fn spawn(&'static self, task: NonNull) { + /// Spawn a task in this executor. + /// + /// # Safety + /// + /// `task` must be a valid pointer to an initialized but not-already-spawned task. + /// + /// It is OK to use `unsafe` to call this from a thread that's not the executor thread. + /// In this case, the task's Future must be Send. This is because this is effectively + /// sending the task to the executor thread. + pub(super) unsafe fn spawn(&'static self, task: NonNull) { let task = task.as_ref(); task.executor.set(self); self.enqueue(task as *const _ as _); } - pub unsafe fn run_queued(&'static self) { + /// Poll all queued tasks in this executor. + /// + /// This loops over all tasks that are queued to be polled (i.e. they're + /// freshly spawned or they've been woken). Other tasks are not polled. + /// + /// You must call `poll` after receiving a call to `signal_fn`. It is OK + /// to call `poll` even when not requested by `signal_fn`, but it wastes + /// energy. + /// + /// # Safety + /// + /// You must NOT call `poll` reentrantly on the same executor. + /// + /// In particular, note that `poll` may call `signal_fn` synchronously. Therefore, you + /// must NOT directly call `poll()` from your `signal_fn`. Instead, `signal_fn` has to + /// somehow schedule for `poll()` to be called later, at a time you know for sure there's + /// no `poll()` already running. + pub unsafe fn poll(&'static self) { #[cfg(feature = "time")] self.timer_queue.dequeue_expired(Instant::now(), |p| { p.as_ref().enqueue(); @@ -235,18 +334,26 @@ impl Executor { #[cfg(feature = "time")] { - // If this is in the past, set_alarm will immediately trigger the alarm, - // which will make the wfe immediately return so we do another loop iteration. + // If this is already in the past, set_alarm will immediately trigger the alarm. + // This will cause `signal_fn` to be called, which will cause `poll()` to be called again, + // so we immediately do another poll loop iteration. let next_expiration = self.timer_queue.next_expiration(); driver::set_alarm(self.alarm, next_expiration.as_ticks()); } } - pub unsafe fn spawner(&'static self) -> super::Spawner { + /// Get a spawner that spawns tasks in this executor. + /// + /// It is OK to call this method multiple times to obtain multiple + /// `Spawner`s. You may also copy `Spawner`s. + pub fn spawner(&'static self) -> super::Spawner { super::Spawner::new(self) } } +/// Wake a task by raw pointer. +/// +/// You can obtain task pointers from `Waker`s using [`task_from_waker`]. pub unsafe fn wake_task(task: NonNull) { task.as_ref().enqueue(); } diff --git a/embassy/src/executor/raw/run_queue.rs b/embassy/src/executor/raw/run_queue.rs index 8e8bc8ff..d0258c71 100644 --- a/embassy/src/executor/raw/run_queue.rs +++ b/embassy/src/executor/raw/run_queue.rs @@ -39,13 +39,17 @@ impl RunQueue { } /// Enqueues an item. Returns true if the queue was empty. - pub(crate) unsafe fn enqueue(&self, item: *mut TaskHeader) -> bool { + /// + /// # Safety + /// + /// `item` must NOT be already enqueued in any queue. + pub(crate) unsafe fn enqueue(&self, task: *mut TaskHeader) -> bool { let mut prev = self.head.load(Ordering::Acquire); loop { - (*item).run_queue_item.next.store(prev, Ordering::Relaxed); + (*task).run_queue_item.next.store(prev, Ordering::Relaxed); match self .head - .compare_exchange_weak(prev, item, Ordering::AcqRel, Ordering::Acquire) + .compare_exchange_weak(prev, task, Ordering::AcqRel, Ordering::Acquire) { Ok(_) => break, Err(next_prev) => prev = next_prev, @@ -55,17 +59,25 @@ impl RunQueue { prev.is_null() } - pub(crate) unsafe fn dequeue_all(&self, on_task: impl Fn(NonNull)) { - let mut task = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + /// Empty the queue, then call `on_task` for each task that was in the queue. + /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue + /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. + pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull)) { + // Atomically empty the queue. + let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); - while !task.is_null() { + // Iterate the linked list of tasks that were previously in the queue. + while let Some(task) = NonNull::new(ptr) { // If the task re-enqueues itself, the `next` pointer will get overwritten. // Therefore, first read the next pointer, and only then process the task. - let next = (*task).run_queue_item.next.load(Ordering::Relaxed); + let next = unsafe { task.as_ref() } + .run_queue_item + .next + .load(Ordering::Relaxed); - on_task(NonNull::new_unchecked(task)); + on_task(task); - task = next + ptr = next } } } diff --git a/embassy/src/executor/raw/waker.rs b/embassy/src/executor/raw/waker.rs index e53190f1..7cd84769 100644 --- a/embassy/src/executor/raw/waker.rs +++ b/embassy/src/executor/raw/waker.rs @@ -22,6 +22,17 @@ pub(crate) unsafe fn from_task(p: NonNull) -> Waker { Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) } +/// Get a task pointer from a waker. +/// +/// This can used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. pub unsafe fn task_from_waker(waker: &Waker) -> NonNull { let hack: &WakerHack = mem::transmute(waker); if hack.vtable != &VTABLE { diff --git a/embassy/src/executor/spawner.rs b/embassy/src/executor/spawner.rs index 36100aec..908e139a 100644 --- a/embassy/src/executor/spawner.rs +++ b/embassy/src/executor/spawner.rs @@ -4,7 +4,17 @@ use core::ptr::NonNull; use super::raw; -#[must_use = "Calling a task function does nothing on its own. You must pass the returned SpawnToken to Executor::spawn()"] +/// Token to spawn a newly-created task in an executor. +/// +/// When calling a task function (like `#[embassy::task] async fn my_task() { ... }`), the returned +/// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must +/// then spawn it into an executor, typically with [`Spawner::spawn()`]. +/// +/// # Panics +/// +/// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way. +/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. +#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] pub struct SpawnToken { raw_task: Option>, phantom: PhantomData<*mut F>, @@ -29,13 +39,19 @@ impl SpawnToken { impl Drop for SpawnToken { fn drop(&mut self) { // TODO deallocate the task instead. - panic!("SpawnToken instances may not be dropped. You must pass them to Executor::spawn()") + panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()") } } +/// Error returned when spawning a task. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SpawnError { + /// Too many instances of this task are already running. + /// + /// By default, a task marked with `#[embassy::task]` can only have one instance + /// running at a time. You may allow multiple instances to run in parallel with + /// `#[embassy::task(pool_size = 4)]`, at the cost of higher RAM usage. Busy, } @@ -52,13 +68,16 @@ pub struct Spawner { } impl Spawner { - pub(crate) unsafe fn new(executor: &'static raw::Executor) -> Self { + pub(crate) fn new(executor: &'static raw::Executor) -> Self { Self { executor, not_send: PhantomData, } } + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy::task]). pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { let task = token.raw_task; mem::forget(token); @@ -93,10 +112,11 @@ impl Spawner { /// Handle to spawn tasks into an executor from any thread. /// -/// This Spawner can be used from any thread (it implements Send and Sync, so after any task (Send and non-Send ones), but it can -/// only be used in the executor thread (it is not Send itself). +/// This Spawner can be used from any thread (it is Send), but it can +/// only spawn Send tasks. The reason for this is spawning is effectively +/// "sending" the tasks to the executor thread. /// -/// If you want to spawn tasks from another thread, use [SendSpawner]. +/// If you want to spawn non-Send tasks, use [Spawner]. #[derive(Copy, Clone)] pub struct SendSpawner { executor: &'static raw::Executor, @@ -106,13 +126,10 @@ pub struct SendSpawner { unsafe impl Send for SendSpawner {} unsafe impl Sync for SendSpawner {} -/// Handle to spawn tasks to an executor. -/// -/// This Spawner can spawn any task (Send and non-Send ones), but it can -/// only be used in the executor thread (it is not Send itself). -/// -/// If you want to spawn tasks from another thread, use [SendSpawner]. impl SendSpawner { + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy::task]). pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { let header = token.raw_task; mem::forget(token); diff --git a/embassy/src/time/driver.rs b/embassy/src/time/driver.rs index 1b8949ae..a21a29d4 100644 --- a/embassy/src/time/driver.rs +++ b/embassy/src/time/driver.rs @@ -95,12 +95,15 @@ pub trait Driver: Send + Sync + 'static { /// The callback may be called from any context (interrupt or thread mode). fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - /// Sets an alarm at the given timestamp. When the current timestamp reaches that + /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm /// timestamp, the provided callback funcion will be called. /// + /// If `timestamp` is already in the past, the alarm callback must be immediately fired. + /// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`. + /// /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. /// - /// Only one alarm can be active at a time. This overwrites any previously-set alarm if any. + /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); } diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs index e5074204..6ce18d47 100644 --- a/embassy/src/time/mod.rs +++ b/embassy/src/time/mod.rs @@ -40,6 +40,8 @@ //! //! For more details, check the [`driver`] module. +#![deny(missing_docs)] + mod delay; pub mod driver; mod duration; diff --git a/examples/nrf/src/bin/raw_spawn.rs b/examples/nrf/src/bin/raw_spawn.rs index 326dd9aa..d0bd6867 100644 --- a/examples/nrf/src/bin/raw_spawn.rs +++ b/examples/nrf/src/bin/raw_spawn.rs @@ -8,7 +8,7 @@ use example_common::*; use core::mem; use cortex_m_rt::entry; -use embassy::executor::raw::Task; +use embassy::executor::raw::TaskStorage; use embassy::executor::Executor; use embassy::time::{Duration, Timer}; use embassy::util::Forever; @@ -36,8 +36,8 @@ fn main() -> ! { let _p = embassy_nrf::init(Default::default()); let executor = EXECUTOR.put(Executor::new()); - let run1_task = Task::new(); - let run2_task = Task::new(); + let run1_task = TaskStorage::new(); + let run2_task = TaskStorage::new(); // Safety: these variables do live forever if main never returns. let run1_task = unsafe { make_static(&run1_task) };