From 5daa173ce4b153a532b4daa9e94c7a248231f25b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 17 Aug 2022 23:40:16 +0200 Subject: Split embassy-time from embassy-executor. --- embassy-executor/Cargo.toml | 38 +- embassy-executor/README.md | 11 + embassy-executor/src/arch/cortex_m.rs | 59 ++++ embassy-executor/src/arch/riscv32.rs | 74 ++++ embassy-executor/src/arch/std.rs | 84 +++++ embassy-executor/src/arch/wasm.rs | 74 ++++ embassy-executor/src/arch/xtensa.rs | 75 ++++ embassy-executor/src/executor/arch/cortex_m.rs | 59 ---- embassy-executor/src/executor/arch/riscv32.rs | 74 ---- embassy-executor/src/executor/arch/std.rs | 84 ----- embassy-executor/src/executor/arch/wasm.rs | 74 ---- embassy-executor/src/executor/arch/xtensa.rs | 75 ---- embassy-executor/src/executor/mod.rs | 44 --- embassy-executor/src/executor/raw/mod.rs | 427 ---------------------- embassy-executor/src/executor/raw/run_queue.rs | 74 ---- embassy-executor/src/executor/raw/timer_queue.rs | 85 ----- embassy-executor/src/executor/raw/util.rs | 33 -- embassy-executor/src/executor/raw/waker.rs | 53 --- embassy-executor/src/executor/spawner.rs | 202 ----------- embassy-executor/src/fmt.rs | 3 - embassy-executor/src/lib.rs | 42 ++- embassy-executor/src/raw/mod.rs | 428 +++++++++++++++++++++++ embassy-executor/src/raw/run_queue.rs | 74 ++++ embassy-executor/src/raw/timer_queue.rs | 85 +++++ embassy-executor/src/raw/util.rs | 33 ++ embassy-executor/src/raw/waker.rs | 53 +++ embassy-executor/src/spawner.rs | 202 +++++++++++ embassy-executor/src/time/delay.rs | 98 ------ embassy-executor/src/time/driver.rs | 170 --------- embassy-executor/src/time/driver_std.rs | 208 ----------- embassy-executor/src/time/driver_wasm.rs | 134 ------- embassy-executor/src/time/duration.rs | 184 ---------- embassy-executor/src/time/instant.rs | 159 --------- embassy-executor/src/time/mod.rs | 91 ----- embassy-executor/src/time/timer.rs | 151 -------- 35 files changed, 1291 insertions(+), 2523 deletions(-) create mode 100644 embassy-executor/README.md create mode 100644 embassy-executor/src/arch/cortex_m.rs create mode 100644 embassy-executor/src/arch/riscv32.rs create mode 100644 embassy-executor/src/arch/std.rs create mode 100644 embassy-executor/src/arch/wasm.rs create mode 100644 embassy-executor/src/arch/xtensa.rs delete mode 100644 embassy-executor/src/executor/arch/cortex_m.rs delete mode 100644 embassy-executor/src/executor/arch/riscv32.rs delete mode 100644 embassy-executor/src/executor/arch/std.rs delete mode 100644 embassy-executor/src/executor/arch/wasm.rs delete mode 100644 embassy-executor/src/executor/arch/xtensa.rs delete mode 100644 embassy-executor/src/executor/mod.rs delete mode 100644 embassy-executor/src/executor/raw/mod.rs delete mode 100644 embassy-executor/src/executor/raw/run_queue.rs delete mode 100644 embassy-executor/src/executor/raw/timer_queue.rs delete mode 100644 embassy-executor/src/executor/raw/util.rs delete mode 100644 embassy-executor/src/executor/raw/waker.rs delete mode 100644 embassy-executor/src/executor/spawner.rs create mode 100644 embassy-executor/src/raw/mod.rs create mode 100644 embassy-executor/src/raw/run_queue.rs create mode 100644 embassy-executor/src/raw/timer_queue.rs create mode 100644 embassy-executor/src/raw/util.rs create mode 100644 embassy-executor/src/raw/waker.rs create mode 100644 embassy-executor/src/spawner.rs delete mode 100644 embassy-executor/src/time/delay.rs delete mode 100644 embassy-executor/src/time/driver.rs delete mode 100644 embassy-executor/src/time/driver_std.rs delete mode 100644 embassy-executor/src/time/driver_wasm.rs delete mode 100644 embassy-executor/src/time/duration.rs delete mode 100644 embassy-executor/src/time/instant.rs delete mode 100644 embassy-executor/src/time/mod.rs delete mode 100644 embassy-executor/src/time/timer.rs (limited to 'embassy-executor') diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 45d0d2de6..25c3f0abd 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" -features = ["nightly", "defmt", "unstable-traits", "time", "time-tick-1mhz"] +features = ["nightly", "defmt", "unstable-traits"] flavors = [ { name = "std", target = "x86_64-unknown-linux-gnu", features = ["std"] }, { name = "wasm", target = "wasm32-unknown-unknown", features = ["wasm"] }, @@ -22,49 +22,25 @@ flavors = [ [features] default = [] -std = ["time", "time-tick-1mhz", "embassy-macros/std"] -wasm = ["wasm-bindgen", "js-sys", "embassy-macros/wasm", "wasm-timer", "time", "time-tick-1mhz"] +std = ["embassy-macros/std"] +wasm = ["dep:wasm-bindgen", "dep:js-sys", "embassy-macros/wasm"] # Enable nightly-only features -nightly = ["embedded-hal-async"] +nightly = [] -# Implement embedded-hal 1.0 alpha and embedded-hal-async traits. -# Implement embedded-hal-async traits if `nightly` is set as well. -unstable-traits = ["embedded-hal-1"] - -# Display a timestamp of the number of seconds since startup next to defmt log messages -# To use this you must have a time driver provided. -defmt-timestamp-uptime = ["defmt"] - -# Enable `embassy_executor::time` module. -# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. -# Enabling it directly without supplying a time driver will fail to link. -time = [] - -# Set the `embassy_executor::time` tick rate. -# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. -# If you're not writing your own driver, check the driver documentation to customize the tick rate. -# If you're writing a driver and your tick rate is not listed here, please add it and send a PR! -time-tick-32768hz = ["time"] -time-tick-1000hz = ["time"] -time-tick-1mhz = ["time"] -time-tick-16mhz = ["time"] +integrated-timers = ["dep:embassy-time"] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} -embedded-hal-async = { version = "0.1.0-alpha.1", optional = true} - futures-util = { version = "0.3.17", default-features = false } embassy-macros = { version = "0.1.0", path = "../embassy-macros"} +embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true} atomic-polyfill = "1.0.1" critical-section = "1.1" cfg-if = "1.0.0" # WASM dependencies wasm-bindgen = { version = "0.2.76", features = ["nightly"], optional = true } -js-sys = { version = "0.3", optional = true } -wasm-timer = { version = "0.2.5", optional = true } \ No newline at end of file +js-sys = { version = "0.3", optional = true } \ No newline at end of file diff --git a/embassy-executor/README.md b/embassy-executor/README.md new file mode 100644 index 000000000..47d0cb8a2 --- /dev/null +++ b/embassy-executor/README.md @@ -0,0 +1,11 @@ +# embassy-executor + +An async/await executor designed for embedded usage. + +- No `alloc`, no heap needed. Task futures are statically allocated. +- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. +- Integrated timer queue: sleeping is easy, just do `Timer::after(Duration::from_secs(1)).await;`. +- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. +- Efficient polling: a wake will only poll the woken task, not all of them. +- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. +- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs new file mode 100644 index 000000000..d6e758dfb --- /dev/null +++ b/embassy-executor/src/arch/cortex_m.rs @@ -0,0 +1,59 @@ +use core::arch::asm; +use core::marker::PhantomData; +use core::ptr; + +use super::{raw, Spawner}; + +/// 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(|_| unsafe { asm!("sev") }, ptr::null_mut()), + not_send: PhantomData, + } + } + + /// 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(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + asm!("wfe"); + }; + } + } +} diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs new file mode 100644 index 000000000..7a7d5698c --- /dev/null +++ b/embassy-executor/src/arch/riscv32.rs @@ -0,0 +1,74 @@ +use core::marker::PhantomData; +use core::ptr; + +use atomic_polyfill::{AtomicBool, Ordering}; + +use super::{raw, Spawner}; + +/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV +/// +static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + +/// RISCV32 Executor +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, +} + +impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + // use Signal_Work_Thread_Mode as substitute for local interrupt register + inner: raw::Executor::new( + |_| { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + }, + ptr::null_mut(), + ), + not_send: PhantomData, + } + } + + /// 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(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + // we do not care about race conditions between the load and store operations, interrupts + //will only set this value to true. + critical_section::with(|_| { + // if there is work to do, loop back to polling + // TODO can we relax this? + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } + // if not, wait for interrupt + else { + core::arch::asm!("wfi"); + } + }); + // if an interrupt occurred while waiting, it will be serviced here + } + } + } +} diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs new file mode 100644 index 000000000..b93ab8a79 --- /dev/null +++ b/embassy-executor/src/arch/std.rs @@ -0,0 +1,84 @@ +use std::marker::PhantomData; +use std::sync::{Condvar, Mutex}; + +use super::{raw, Spawner}; + +/// Single-threaded std-based executor. +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + signaler: &'static Signaler, +} + +impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let signaler = &*Box::leak(Box::new(Signaler::new())); + Self { + inner: raw::Executor::new( + |p| unsafe { + let s = &*(p as *const () as *const Signaler); + s.signal() + }, + signaler as *const _ as _, + ), + not_send: PhantomData, + signaler, + } + } + + /// 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(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + self.signaler.wait() + } + } +} + +struct Signaler { + mutex: Mutex, + condvar: Condvar, +} + +impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait(&self) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + signaled = self.condvar.wait(signaled).unwrap(); + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } +} diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs new file mode 100644 index 000000000..9d5aa31ed --- /dev/null +++ b/embassy-executor/src/arch/wasm.rs @@ -0,0 +1,74 @@ +use core::marker::PhantomData; + +use js_sys::Promise; +use wasm_bindgen::prelude::*; + +use super::raw::util::UninitCell; +use super::raw::{self}; +use super::Spawner; + +/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. +pub struct Executor { + inner: raw::Executor, + ctx: &'static WasmContext, + not_send: PhantomData<*mut ()>, +} + +pub(crate) struct WasmContext { + promise: Promise, + closure: UninitCell>, +} + +impl WasmContext { + pub fn new() -> Self { + Self { + promise: Promise::resolve(&JsValue::undefined()), + closure: UninitCell::uninit(), + } + } +} + +impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let ctx = &*Box::leak(Box::new(WasmContext::new())); + let inner = raw::Executor::new( + |p| unsafe { + let ctx = &*(p as *const () as *const WasmContext); + let _ = ctx.promise.then(ctx.closure.as_mut()); + }, + ctx as *const _ as _, + ); + Self { + inner, + not_send: PhantomData, + ctx, + } + } + + /// 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) + pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { + unsafe { + let executor = &self.inner; + self.ctx.closure.write(Closure::new(move |_| { + executor.poll(); + })); + init(self.inner.spawner()); + } + } +} diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs new file mode 100644 index 000000000..20bd7b8a5 --- /dev/null +++ b/embassy-executor/src/arch/xtensa.rs @@ -0,0 +1,75 @@ +use core::marker::PhantomData; +use core::ptr; + +use atomic_polyfill::{AtomicBool, Ordering}; + +use super::{raw, Spawner}; + +/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa +/// +static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + +/// Xtensa Executor +pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, +} + +impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + // use Signal_Work_Thread_Mode as substitute for local interrupt register + inner: raw::Executor::new( + |_| { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + }, + ptr::null_mut(), + ), + not_send: PhantomData, + } + } + + /// 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(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + // we do not care about race conditions between the load and store operations, interrupts + // will only set this value to true. + // if there is work to do, loop back to polling + // TODO can we relax this? + critical_section::with(|_| { + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } else { + // waiti sets the PS.INTLEVEL when slipping into sleep + // because critical sections in Xtensa are implemented via increasing + // PS.INTLEVEL the critical section ends here + // take care not add code after `waiti` if it needs to be inside the CS + core::arch::asm!("waiti 0"); // critical section ends here + } + }); + } + } + } +} diff --git a/embassy-executor/src/executor/arch/cortex_m.rs b/embassy-executor/src/executor/arch/cortex_m.rs deleted file mode 100644 index d6e758dfb..000000000 --- a/embassy-executor/src/executor/arch/cortex_m.rs +++ /dev/null @@ -1,59 +0,0 @@ -use core::arch::asm; -use core::marker::PhantomData; -use core::ptr; - -use super::{raw, Spawner}; - -/// 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(|_| unsafe { asm!("sev") }, ptr::null_mut()), - not_send: PhantomData, - } - } - - /// 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(self.inner.spawner()); - - loop { - unsafe { - self.inner.poll(); - asm!("wfe"); - }; - } - } -} diff --git a/embassy-executor/src/executor/arch/riscv32.rs b/embassy-executor/src/executor/arch/riscv32.rs deleted file mode 100644 index 7a7d5698c..000000000 --- a/embassy-executor/src/executor/arch/riscv32.rs +++ /dev/null @@ -1,74 +0,0 @@ -use core::marker::PhantomData; -use core::ptr; - -use atomic_polyfill::{AtomicBool, Ordering}; - -use super::{raw, Spawner}; - -/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV -/// -static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); - -/// RISCV32 Executor -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, -} - -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - // use Signal_Work_Thread_Mode as substitute for local interrupt register - inner: raw::Executor::new( - |_| { - SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); - }, - ptr::null_mut(), - ), - not_send: PhantomData, - } - } - - /// 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(self.inner.spawner()); - - loop { - unsafe { - self.inner.poll(); - // we do not care about race conditions between the load and store operations, interrupts - //will only set this value to true. - critical_section::with(|_| { - // if there is work to do, loop back to polling - // TODO can we relax this? - if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { - SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); - } - // if not, wait for interrupt - else { - core::arch::asm!("wfi"); - } - }); - // if an interrupt occurred while waiting, it will be serviced here - } - } - } -} diff --git a/embassy-executor/src/executor/arch/std.rs b/embassy-executor/src/executor/arch/std.rs deleted file mode 100644 index b93ab8a79..000000000 --- a/embassy-executor/src/executor/arch/std.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::marker::PhantomData; -use std::sync::{Condvar, Mutex}; - -use super::{raw, Spawner}; - -/// Single-threaded std-based executor. -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, - signaler: &'static Signaler, -} - -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - let signaler = &*Box::leak(Box::new(Signaler::new())); - Self { - inner: raw::Executor::new( - |p| unsafe { - let s = &*(p as *const () as *const Signaler); - s.signal() - }, - signaler as *const _ as _, - ), - not_send: PhantomData, - signaler, - } - } - - /// 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(self.inner.spawner()); - - loop { - unsafe { self.inner.poll() }; - self.signaler.wait() - } - } -} - -struct Signaler { - mutex: Mutex, - condvar: Condvar, -} - -impl Signaler { - fn new() -> Self { - Self { - mutex: Mutex::new(false), - condvar: Condvar::new(), - } - } - - fn wait(&self) { - let mut signaled = self.mutex.lock().unwrap(); - while !*signaled { - signaled = self.condvar.wait(signaled).unwrap(); - } - *signaled = false; - } - - fn signal(&self) { - let mut signaled = self.mutex.lock().unwrap(); - *signaled = true; - self.condvar.notify_one(); - } -} diff --git a/embassy-executor/src/executor/arch/wasm.rs b/embassy-executor/src/executor/arch/wasm.rs deleted file mode 100644 index 9d5aa31ed..000000000 --- a/embassy-executor/src/executor/arch/wasm.rs +++ /dev/null @@ -1,74 +0,0 @@ -use core::marker::PhantomData; - -use js_sys::Promise; -use wasm_bindgen::prelude::*; - -use super::raw::util::UninitCell; -use super::raw::{self}; -use super::Spawner; - -/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. -pub struct Executor { - inner: raw::Executor, - ctx: &'static WasmContext, - not_send: PhantomData<*mut ()>, -} - -pub(crate) struct WasmContext { - promise: Promise, - closure: UninitCell>, -} - -impl WasmContext { - pub fn new() -> Self { - Self { - promise: Promise::resolve(&JsValue::undefined()), - closure: UninitCell::uninit(), - } - } -} - -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - let ctx = &*Box::leak(Box::new(WasmContext::new())); - let inner = raw::Executor::new( - |p| unsafe { - let ctx = &*(p as *const () as *const WasmContext); - let _ = ctx.promise.then(ctx.closure.as_mut()); - }, - ctx as *const _ as _, - ); - Self { - inner, - not_send: PhantomData, - ctx, - } - } - - /// 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) - pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { - unsafe { - let executor = &self.inner; - self.ctx.closure.write(Closure::new(move |_| { - executor.poll(); - })); - init(self.inner.spawner()); - } - } -} diff --git a/embassy-executor/src/executor/arch/xtensa.rs b/embassy-executor/src/executor/arch/xtensa.rs deleted file mode 100644 index 20bd7b8a5..000000000 --- a/embassy-executor/src/executor/arch/xtensa.rs +++ /dev/null @@ -1,75 +0,0 @@ -use core::marker::PhantomData; -use core::ptr; - -use atomic_polyfill::{AtomicBool, Ordering}; - -use super::{raw, Spawner}; - -/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa -/// -static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); - -/// Xtensa Executor -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, -} - -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - // use Signal_Work_Thread_Mode as substitute for local interrupt register - inner: raw::Executor::new( - |_| { - SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); - }, - ptr::null_mut(), - ), - not_send: PhantomData, - } - } - - /// 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(self.inner.spawner()); - - loop { - unsafe { - self.inner.poll(); - // we do not care about race conditions between the load and store operations, interrupts - // will only set this value to true. - // if there is work to do, loop back to polling - // TODO can we relax this? - critical_section::with(|_| { - if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { - SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); - } else { - // waiti sets the PS.INTLEVEL when slipping into sleep - // because critical sections in Xtensa are implemented via increasing - // PS.INTLEVEL the critical section ends here - // take care not add code after `waiti` if it needs to be inside the CS - core::arch::asm!("waiti 0"); // critical section ends here - } - }); - } - } - } -} diff --git a/embassy-executor/src/executor/mod.rs b/embassy-executor/src/executor/mod.rs deleted file mode 100644 index 45d00c568..000000000 --- a/embassy-executor/src/executor/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Async task executor. -//! -//! This module provides an async/await executor designed for embedded usage. -//! -//! - No `alloc`, no heap needed. Task futures are statically allocated. -//! - No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. -//! - Integrated timer queue: sleeping is easy, just do `Timer::after(Duration::from_secs(1)).await;`. -//! - No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. -//! - Efficient polling: a wake will only poll the woken task, not all of them. -//! - Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. -//! - Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. - -cfg_if::cfg_if! { - if #[cfg(cortex_m)] { - #[path="arch/cortex_m.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(target_arch="riscv32")] { - #[path="arch/riscv32.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] { - #[path="arch/xtensa.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(feature="wasm")] { - #[path="arch/wasm.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(feature="std")] { - #[path="arch/std.rs"] - mod arch; - pub use arch::*; - } -} - -pub mod raw; - -mod spawner; -pub use spawner::*; diff --git a/embassy-executor/src/executor/raw/mod.rs b/embassy-executor/src/executor/raw/mod.rs deleted file mode 100644 index fb4cc6288..000000000 --- a/embassy-executor/src/executor/raw/mod.rs +++ /dev/null @@ -1,427 +0,0 @@ -//! 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 [`executor`](crate::executor) and the [`embassy_executor::task`](embassy_macros::task) macro, which are fully safe. - -mod run_queue; -#[cfg(feature = "time")] -mod timer_queue; -pub(crate) mod util; -mod waker; - -use core::cell::Cell; -use core::future::Future; -use core::pin::Pin; -use core::ptr::NonNull; -use core::task::{Context, Poll}; -use core::{mem, ptr}; - -use atomic_polyfill::{AtomicU32, Ordering}; -use critical_section::CriticalSection; - -use self::run_queue::{RunQueue, RunQueueItem}; -use self::util::UninitCell; -pub use self::waker::task_from_waker; -use super::SpawnToken; -#[cfg(feature = "time")] -use crate::time::driver::{self, AlarmHandle}; -#[cfg(feature = "time")] -use crate::time::Instant; - -/// Task is spawned (has a future) -pub(crate) const STATE_SPAWNED: u32 = 1 << 0; -/// Task is in the executor run queue -pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; -/// Task is in the executor timer queue -#[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, - pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 - pub(crate) poll_fn: UninitCell)>, // Valid if STATE_SPAWNED - - #[cfg(feature = "time")] - pub(crate) expires_at: Cell, - #[cfg(feature = "time")] - pub(crate) timer_queue_item: timer_queue::TimerQueueItem, -} - -impl TaskHeader { - pub(crate) const fn new() -> Self { - Self { - state: AtomicU32::new(0), - run_queue_item: RunQueueItem::new(), - executor: Cell::new(ptr::null()), - poll_fn: UninitCell::uninit(), - - #[cfg(feature = "time")] - expires_at: Cell::new(Instant::from_ticks(0)), - #[cfg(feature = "time")] - timer_queue_item: timer_queue::TimerQueueItem::new(), - } - } -} - -/// 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 `TaskStorage` may be in spawned or not-spawned state. You -/// may spawn it with [`TaskStorage::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_executor::task](embassy_macros::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 TaskHeader and TaskStorage pointers. -#[repr(C)] -pub struct TaskStorage { - raw: TaskHeader, - future: UninitCell, // Valid if STATE_SPAWNED -} - -impl TaskStorage { - const NEW: Self = Self::new(); - - /// Create a new TaskStorage, in not-spawned state. - pub const fn new() -> Self { - Self { - raw: TaskHeader::new(), - future: UninitCell::uninit(), - } - } - - /// 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 [`Spawner::spawn()`](super::Spawner::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_mark_used() { - return unsafe { SpawnToken::::new(self.spawn_initialize(future)) }; - } - - SpawnToken::::new_failed() - } - - fn spawn_mark_used(&'static self) -> bool { - let state = STATE_SPAWNED | STATE_RUN_QUEUED; - self.raw - .state - .compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire) - .is_ok() - } - - unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull { - // Initialize the task - self.raw.poll_fn.write(Self::poll); - self.future.write(future()); - NonNull::new_unchecked(self as *const TaskStorage as *const TaskHeader as *mut TaskHeader) - } - - unsafe fn poll(p: NonNull) { - let this = &*(p.as_ptr() as *const TaskStorage); - - let future = Pin::new_unchecked(this.future.as_mut()); - let waker = waker::from_task(p); - let mut cx = Context::from_waker(&waker); - match future.poll(&mut cx) { - Poll::Ready(_) => { - this.future.drop_in_place(); - this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); - } - Poll::Pending => {} - } - - // the compiler is emitting a virtual call for waker drop, but we know - // it's a noop for our waker. - mem::forget(waker); - } -} - -unsafe impl Sync for TaskStorage {} - -/// Raw storage that can hold up to N tasks of the same type. -/// -/// This is essentially a `[TaskStorage; N]`. -pub struct TaskPool { - pool: [TaskStorage; N], -} - -impl TaskPool { - /// Create a new TaskPool, with all tasks in non-spawned state. - pub const fn new() -> Self { - Self { - pool: [TaskStorage::NEW; N], - } - } - - /// Try to spawn a task in the pool. - /// - /// See [`TaskStorage::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, a "poisoned" SpawnToken is returned, - /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. - pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - for task in &self.pool { - if task.spawn_mark_used() { - return unsafe { SpawnToken::::new(task.spawn_initialize(future)) }; - } - } - - SpawnToken::::new_failed() - } - - /// Like spawn(), but allows the task to be send-spawned if the args are Send even if - /// the future is !Send. - /// - /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used - /// by the Embassy macros ONLY. - /// - /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` - /// is an `async fn`, NOT a hand-written `Future`. - #[doc(hidden)] - pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken - where - FutFn: FnOnce() -> F, - { - // When send-spawning a task, we construct the future in this thread, and effectively - // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, - // send-spawning should require the future `F` to be `Send`. - // - // The problem is this is more restrictive than needed. Once the future is executing, - // it is never sent to another thread. It is only sent when spawning. It should be - // enough for the task's arguments to be Send. (and in practice it's super easy to - // accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.) - // - // We can do it by sending the task args and constructing the future in the executor thread - // on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy - // of the args. - // - // Luckily, an `async fn` future contains just the args when freshly constructed. So, if the - // args are Send, it's OK to send a !Send future, as long as we do it before first polling it. - // - // (Note: this is how the generators are implemented today, it's not officially guaranteed yet, - // but it's possible it'll be guaranteed in the future. See zulip thread: - // https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures ) - // - // The `FutFn` captures all the args, so if it's Send, the task can be send-spawned. - // This is why we return `SpawnToken` below. - // - // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly - // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. - - for task in &self.pool { - if task.spawn_mark_used() { - return SpawnToken::::new(task.spawn_initialize(future)); - } - } - - SpawnToken::::new_failed() - } -} - -/// 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 ()), - signal_ctx: *mut (), - - #[cfg(feature = "time")] - pub(crate) timer_queue: timer_queue::TimerQueue, - #[cfg(feature = "time")] - alarm: AlarmHandle, -} - -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()) }; - #[cfg(feature = "time")] - driver::set_alarm_callback(alarm, signal_fn, signal_ctx); - - Self { - run_queue: RunQueue::new(), - signal_fn, - signal_ctx, - - #[cfg(feature = "time")] - timer_queue: timer_queue::TimerQueue::new(), - #[cfg(feature = "time")] - alarm, - } - } - - /// 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). - #[inline(always)] - unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull) { - if self.run_queue.enqueue(cs, task) { - (self.signal_fn)(self.signal_ctx) - } - } - - /// 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) { - task.as_ref().executor.set(self); - - critical_section::with(|cs| { - self.enqueue(cs, task); - }) - } - - /// 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(), |task| wake_task(task)); - - self.run_queue.dequeue_all(|p| { - let task = p.as_ref(); - - #[cfg(feature = "time")] - task.expires_at.set(Instant::MAX); - - let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); - if state & STATE_SPAWNED == 0 { - // If task is not running, ignore it. This can happen in the following scenario: - // - Task gets dequeued, poll starts - // - While task is being polled, it gets woken. It gets placed in the queue. - // - Task poll finishes, returning done=true - // - RUNNING bit is cleared, but the task is already in the queue. - return; - } - - // Run the task - task.poll_fn.read()(p as _); - - // Enqueue or update into timer_queue - #[cfg(feature = "time")] - self.timer_queue.update(p); - }); - - #[cfg(feature = "time")] - { - // 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()); - } - } - - /// 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`]. -/// -/// # Safety -/// -/// `task` must be a valid task pointer obtained from [`task_from_waker`]. -pub unsafe fn wake_task(task: NonNull) { - critical_section::with(|cs| { - let header = task.as_ref(); - let state = header.state.load(Ordering::Relaxed); - - // If already scheduled, or if not started, - if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { - return; - } - - // Mark it as scheduled - header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed); - - // We have just marked the task as scheduled, so enqueue it. - let executor = &*header.executor.get(); - executor.enqueue(cs, task); - }) -} - -#[cfg(feature = "time")] -pub(crate) unsafe fn register_timer(at: Instant, waker: &core::task::Waker) { - let task = waker::task_from_waker(waker); - let task = task.as_ref(); - let expires_at = task.expires_at.get(); - task.expires_at.set(expires_at.min(at)); -} diff --git a/embassy-executor/src/executor/raw/run_queue.rs b/embassy-executor/src/executor/raw/run_queue.rs deleted file mode 100644 index ed8c82a5c..000000000 --- a/embassy-executor/src/executor/raw/run_queue.rs +++ /dev/null @@ -1,74 +0,0 @@ -use core::ptr; -use core::ptr::NonNull; - -use atomic_polyfill::{AtomicPtr, Ordering}; -use critical_section::CriticalSection; - -use super::TaskHeader; - -pub(crate) struct RunQueueItem { - next: AtomicPtr, -} - -impl RunQueueItem { - pub const fn new() -> Self { - Self { - next: AtomicPtr::new(ptr::null_mut()), - } - } -} - -/// Atomic task queue using a very, very simple lock-free linked-list queue: -/// -/// To enqueue a task, task.next is set to the old head, and head is atomically set to task. -/// -/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with -/// null. Then the batch is iterated following the next pointers until null is reached. -/// -/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK -/// for our purposes: it can't create fairness problems since the next batch won't run until the -/// current batch is completely processed, so even if a task enqueues itself instantly (for example -/// by waking its own waker) can't prevent other tasks from running. -pub(crate) struct RunQueue { - head: AtomicPtr, -} - -impl RunQueue { - pub const fn new() -> Self { - Self { - head: AtomicPtr::new(ptr::null_mut()), - } - } - - /// Enqueues an item. Returns true if the queue was empty. - /// - /// # Safety - /// - /// `item` must NOT be already enqueued in any queue. - #[inline(always)] - pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull) -> bool { - let prev = self.head.load(Ordering::Relaxed); - task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed); - self.head.store(task.as_ptr(), Ordering::Relaxed); - prev.is_null() - } - - /// 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); - - // 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 = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed); - - on_task(task); - - ptr = next - } - } -} diff --git a/embassy-executor/src/executor/raw/timer_queue.rs b/embassy-executor/src/executor/raw/timer_queue.rs deleted file mode 100644 index 62fcfc531..000000000 --- a/embassy-executor/src/executor/raw/timer_queue.rs +++ /dev/null @@ -1,85 +0,0 @@ -use core::cell::Cell; -use core::cmp::min; -use core::ptr; -use core::ptr::NonNull; - -use atomic_polyfill::Ordering; - -use super::{TaskHeader, STATE_TIMER_QUEUED}; -use crate::time::Instant; - -pub(crate) struct TimerQueueItem { - next: Cell<*mut TaskHeader>, -} - -impl TimerQueueItem { - pub const fn new() -> Self { - Self { - next: Cell::new(ptr::null_mut()), - } - } -} - -pub(crate) struct TimerQueue { - head: Cell<*mut TaskHeader>, -} - -impl TimerQueue { - pub const fn new() -> Self { - Self { - head: Cell::new(ptr::null_mut()), - } - } - - pub(crate) unsafe fn update(&self, p: NonNull) { - let task = p.as_ref(); - if task.expires_at.get() != Instant::MAX { - let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); - let is_new = old_state & STATE_TIMER_QUEUED == 0; - - if is_new { - task.timer_queue_item.next.set(self.head.get()); - self.head.set(p.as_ptr()); - } - } - } - - pub(crate) unsafe fn next_expiration(&self) -> Instant { - let mut res = Instant::MAX; - self.retain(|p| { - let task = p.as_ref(); - let expires = task.expires_at.get(); - res = min(res, expires); - expires != Instant::MAX - }); - res - } - - pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull)) { - self.retain(|p| { - let task = p.as_ref(); - if task.expires_at.get() <= now { - on_task(p); - false - } else { - true - } - }); - } - - pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull) -> bool) { - let mut prev = &self.head; - while !prev.get().is_null() { - let p = NonNull::new_unchecked(prev.get()); - let task = &*p.as_ptr(); - if f(p) { - // Skip to next - prev = &task.timer_queue_item.next; - } else { - // Remove it - prev.set(task.timer_queue_item.next.get()); - task.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel); - } - } - } -} diff --git a/embassy-executor/src/executor/raw/util.rs b/embassy-executor/src/executor/raw/util.rs deleted file mode 100644 index ed5822188..000000000 --- a/embassy-executor/src/executor/raw/util.rs +++ /dev/null @@ -1,33 +0,0 @@ -use core::cell::UnsafeCell; -use core::mem::MaybeUninit; -use core::ptr; - -pub(crate) struct UninitCell(MaybeUninit>); -impl UninitCell { - pub const fn uninit() -> Self { - Self(MaybeUninit::uninit()) - } - - pub unsafe fn as_mut_ptr(&self) -> *mut T { - (*self.0.as_ptr()).get() - } - - #[allow(clippy::mut_from_ref)] - pub unsafe fn as_mut(&self) -> &mut T { - &mut *self.as_mut_ptr() - } - - pub unsafe fn write(&self, val: T) { - ptr::write(self.as_mut_ptr(), val) - } - - pub unsafe fn drop_in_place(&self) { - ptr::drop_in_place(self.as_mut_ptr()) - } -} - -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) - } -} diff --git a/embassy-executor/src/executor/raw/waker.rs b/embassy-executor/src/executor/raw/waker.rs deleted file mode 100644 index 6b9c03a62..000000000 --- a/embassy-executor/src/executor/raw/waker.rs +++ /dev/null @@ -1,53 +0,0 @@ -use core::mem; -use core::ptr::NonNull; -use core::task::{RawWaker, RawWakerVTable, Waker}; - -use super::{wake_task, TaskHeader}; - -const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); - -unsafe fn clone(p: *const ()) -> RawWaker { - RawWaker::new(p, &VTABLE) -} - -unsafe fn wake(p: *const ()) { - wake_task(NonNull::new_unchecked(p as *mut TaskHeader)) -} - -unsafe fn drop(_: *const ()) { - // nop -} - -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 be 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 fn task_from_waker(waker: &Waker) -> NonNull { - // safety: OK because WakerHack has the same layout as Waker. - // This is not really guaranteed because the structs are `repr(Rust)`, it is - // indeed the case in the current implementation. - // TODO use waker_getters when stable. https://github.com/rust-lang/rust/issues/96992 - let hack: &WakerHack = unsafe { mem::transmute(waker) }; - if hack.vtable != &VTABLE { - panic!("Found waker not created by the Embassy executor. `embassy_executor::time::Timer` only works with the Embassy executor.") - } - - // safety: we never create a waker with a null data pointer. - unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) } -} - -struct WakerHack { - data: *const (), - vtable: &'static RawWakerVTable, -} diff --git a/embassy-executor/src/executor/spawner.rs b/embassy-executor/src/executor/spawner.rs deleted file mode 100644 index 25a0d7dbb..000000000 --- a/embassy-executor/src/executor/spawner.rs +++ /dev/null @@ -1,202 +0,0 @@ -use core::marker::PhantomData; -use core::mem; -use core::ptr::NonNull; -use core::task::Poll; - -use futures_util::future::poll_fn; - -use super::raw; - -/// Token to spawn a newly-created task in an executor. -/// -/// When calling a task function (like `#[embassy_executor::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()`]. -/// -/// The generic parameter `S` determines whether the task can be spawned in executors -/// in other threads or not. If `S: Send`, it can, which allows spawning it into a [`SendSpawner`]. -/// If not, it can't, so it can only be spawned into the current thread's executor, with [`Spawner`]. -/// -/// # 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 S>, -} - -impl SpawnToken { - pub(crate) unsafe fn new(raw_task: NonNull) -> Self { - Self { - raw_task: Some(raw_task), - phantom: PhantomData, - } - } - - pub(crate) fn new_failed() -> Self { - Self { - raw_task: None, - phantom: PhantomData, - } - } -} - -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 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_executor::task]` can only have one instance - /// running at a time. You may allow multiple instances to run in parallel with - /// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage. - Busy, -} - -/// Handle to spawn tasks into 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]. -#[derive(Copy, Clone)] -pub struct Spawner { - executor: &'static raw::Executor, - not_send: PhantomData<*mut ()>, -} - -impl Spawner { - pub(crate) fn new(executor: &'static raw::Executor) -> Self { - Self { - executor, - not_send: PhantomData, - } - } - - /// Get a Spawner for the current executor. - /// - /// This function is `async` just to get access to the current async - /// context. It returns instantly, it does not block/yield. - /// - /// # Panics - /// - /// Panics if the current executor is not an Embassy executor. - pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { - let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); - Poll::Ready(Self::new(&*executor)) - }) - .await - } - - /// Spawn a task into an executor. - /// - /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). - pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { - let task = token.raw_task; - mem::forget(token); - - match task { - Some(task) => { - unsafe { self.executor.spawn(task) }; - Ok(()) - } - None => Err(SpawnError::Busy), - } - } - - // Used by the `embassy_macros::main!` macro to throw an error when spawn - // fails. This is here to allow conditional use of `defmt::unwrap!` - // without introducing a `defmt` feature in the `embassy_macros` package, - // which would require use of `-Z namespaced-features`. - /// Spawn a task into an executor, panicking on failure. - /// - /// # Panics - /// - /// Panics if the spawning fails. - pub fn must_spawn(&self, token: SpawnToken) { - unwrap!(self.spawn(token)); - } - - /// Convert this Spawner to a SendSpawner. This allows you to send the - /// spawner to other threads, but the spawner loses the ability to spawn - /// non-Send tasks. - pub fn make_send(&self) -> SendSpawner { - SendSpawner { - executor: self.executor, - } - } -} - -/// Handle to spawn tasks into an executor from any thread. -/// -/// 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 non-Send tasks, use [Spawner]. -#[derive(Copy, Clone)] -pub struct SendSpawner { - executor: &'static raw::Executor, -} - -unsafe impl Send for SendSpawner {} -unsafe impl Sync for SendSpawner {} - -impl SendSpawner { - pub(crate) fn new(executor: &'static raw::Executor) -> Self { - Self { executor } - } - - /// Get a Spawner for the current executor. - /// - /// This function is `async` just to get access to the current async - /// context. It returns instantly, it does not block/yield. - /// - /// # Panics - /// - /// Panics if the current executor is not an Embassy executor. - pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { - let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); - Poll::Ready(Self::new(&*executor)) - }) - .await - } - - /// Spawn a task into an executor. - /// - /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). - pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { - let header = token.raw_task; - mem::forget(token); - - match header { - Some(header) => { - unsafe { self.executor.spawn(header) }; - Ok(()) - } - None => Err(SpawnError::Busy), - } - } - - /// Spawn a task into an executor, panicking on failure. - /// - /// # Panics - /// - /// Panics if the spawning fails. - pub fn must_spawn(&self, token: SpawnToken) { - unwrap!(self.spawn(token)); - } -} diff --git a/embassy-executor/src/fmt.rs b/embassy-executor/src/fmt.rs index f8bb0a035..066970813 100644 --- a/embassy-executor/src/fmt.rs +++ b/embassy-executor/src/fmt.rs @@ -195,9 +195,6 @@ macro_rules! unwrap { } } -#[cfg(feature = "defmt-timestamp-uptime")] -defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() } - #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct NoneError; diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 69e4aeb4b..9328a7378 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -1,22 +1,44 @@ #![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] #![cfg_attr(all(feature = "nightly", target_arch = "xtensa"), feature(asm_experimental_arch))] #![allow(clippy::new_without_default)] -#![doc = include_str!("../../README.md")] +#![doc = include_str!("../README.md")] #![warn(missing_docs)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -pub mod executor; -#[cfg(feature = "time")] -pub mod time; - #[cfg(feature = "nightly")] pub use embassy_macros::{main, task}; -#[doc(hidden)] -/// Implementation details for embassy macros. DO NOT USE. -pub mod export { - pub use atomic_polyfill as atomic; +cfg_if::cfg_if! { + if #[cfg(cortex_m)] { + #[path="arch/cortex_m.rs"] + mod arch; + pub use arch::*; + } + else if #[cfg(target_arch="riscv32")] { + #[path="arch/riscv32.rs"] + mod arch; + pub use arch::*; + } + else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] { + #[path="arch/xtensa.rs"] + mod arch; + pub use arch::*; + } + else if #[cfg(feature="wasm")] { + #[path="arch/wasm.rs"] + mod arch; + pub use arch::*; + } + else if #[cfg(feature="std")] { + #[path="arch/std.rs"] + mod arch; + pub use arch::*; + } } + +pub mod raw; + +mod spawner; +pub use spawner::*; diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs new file mode 100644 index 000000000..afe67decb --- /dev/null +++ b/embassy-executor/src/raw/mod.rs @@ -0,0 +1,428 @@ +//! 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 [`executor`](crate::executor) and the [`embassy_executor::task`](embassy_macros::task) macro, which are fully safe. + +mod run_queue; +#[cfg(feature = "integrated-timers")] +mod timer_queue; +pub(crate) mod util; +mod waker; + +use core::cell::Cell; +use core::future::Future; +use core::pin::Pin; +use core::ptr::NonNull; +use core::task::{Context, Poll}; +use core::{mem, ptr}; + +use atomic_polyfill::{AtomicU32, Ordering}; +use critical_section::CriticalSection; +#[cfg(feature = "integrated-timers")] +use embassy_time::driver::{self, AlarmHandle}; +#[cfg(feature = "integrated-timers")] +use embassy_time::Instant; + +use self::run_queue::{RunQueue, RunQueueItem}; +use self::util::UninitCell; +pub use self::waker::task_from_waker; +use super::SpawnToken; + +/// Task is spawned (has a future) +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +/// Task is in the executor run queue +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; +/// Task is in the executor timer queue +#[cfg(feature = "integrated-timers")] +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, + pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 + pub(crate) poll_fn: UninitCell)>, // Valid if STATE_SPAWNED + + #[cfg(feature = "integrated-timers")] + pub(crate) expires_at: Cell, + #[cfg(feature = "integrated-timers")] + pub(crate) timer_queue_item: timer_queue::TimerQueueItem, +} + +impl TaskHeader { + pub(crate) const fn new() -> Self { + Self { + state: AtomicU32::new(0), + run_queue_item: RunQueueItem::new(), + executor: Cell::new(ptr::null()), + poll_fn: UninitCell::uninit(), + + #[cfg(feature = "integrated-timers")] + expires_at: Cell::new(Instant::from_ticks(0)), + #[cfg(feature = "integrated-timers")] + timer_queue_item: timer_queue::TimerQueueItem::new(), + } + } +} + +/// 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 `TaskStorage` may be in spawned or not-spawned state. You +/// may spawn it with [`TaskStorage::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_executor::task](embassy_macros::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 TaskHeader and TaskStorage pointers. +#[repr(C)] +pub struct TaskStorage { + raw: TaskHeader, + future: UninitCell, // Valid if STATE_SPAWNED +} + +impl TaskStorage { + const NEW: Self = Self::new(); + + /// Create a new TaskStorage, in not-spawned state. + pub const fn new() -> Self { + Self { + raw: TaskHeader::new(), + future: UninitCell::uninit(), + } + } + + /// 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 [`Spawner::spawn()`](super::Spawner::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_mark_used() { + return unsafe { SpawnToken::::new(self.spawn_initialize(future)) }; + } + + SpawnToken::::new_failed() + } + + fn spawn_mark_used(&'static self) -> bool { + let state = STATE_SPAWNED | STATE_RUN_QUEUED; + self.raw + .state + .compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + } + + unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull { + // Initialize the task + self.raw.poll_fn.write(Self::poll); + self.future.write(future()); + NonNull::new_unchecked(self as *const TaskStorage as *const TaskHeader as *mut TaskHeader) + } + + unsafe fn poll(p: NonNull) { + let this = &*(p.as_ptr() as *const TaskStorage); + + let future = Pin::new_unchecked(this.future.as_mut()); + let waker = waker::from_task(p); + let mut cx = Context::from_waker(&waker); + match future.poll(&mut cx) { + Poll::Ready(_) => { + this.future.drop_in_place(); + this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); + } + Poll::Pending => {} + } + + // the compiler is emitting a virtual call for waker drop, but we know + // it's a noop for our waker. + mem::forget(waker); + } +} + +unsafe impl Sync for TaskStorage {} + +/// Raw storage that can hold up to N tasks of the same type. +/// +/// This is essentially a `[TaskStorage; N]`. +pub struct TaskPool { + pool: [TaskStorage; N], +} + +impl TaskPool { + /// Create a new TaskPool, with all tasks in non-spawned state. + pub const fn new() -> Self { + Self { + pool: [TaskStorage::NEW; N], + } + } + + /// Try to spawn a task in the pool. + /// + /// See [`TaskStorage::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, a "poisoned" SpawnToken is returned, + /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. + pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + for task in &self.pool { + if task.spawn_mark_used() { + return unsafe { SpawnToken::::new(task.spawn_initialize(future)) }; + } + } + + SpawnToken::::new_failed() + } + + /// Like spawn(), but allows the task to be send-spawned if the args are Send even if + /// the future is !Send. + /// + /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used + /// by the Embassy macros ONLY. + /// + /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken + where + FutFn: FnOnce() -> F, + { + // When send-spawning a task, we construct the future in this thread, and effectively + // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, + // send-spawning should require the future `F` to be `Send`. + // + // The problem is this is more restrictive than needed. Once the future is executing, + // it is never sent to another thread. It is only sent when spawning. It should be + // enough for the task's arguments to be Send. (and in practice it's super easy to + // accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.) + // + // We can do it by sending the task args and constructing the future in the executor thread + // on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy + // of the args. + // + // Luckily, an `async fn` future contains just the args when freshly constructed. So, if the + // args are Send, it's OK to send a !Send future, as long as we do it before first polling it. + // + // (Note: this is how the generators are implemented today, it's not officially guaranteed yet, + // but it's possible it'll be guaranteed in the future. See zulip thread: + // https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures ) + // + // The `FutFn` captures all the args, so if it's Send, the task can be send-spawned. + // This is why we return `SpawnToken` below. + // + // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly + // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. + + for task in &self.pool { + if task.spawn_mark_used() { + return SpawnToken::::new(task.spawn_initialize(future)); + } + } + + SpawnToken::::new_failed() + } +} + +/// 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 ()), + signal_ctx: *mut (), + + #[cfg(feature = "integrated-timers")] + pub(crate) timer_queue: timer_queue::TimerQueue, + #[cfg(feature = "integrated-timers")] + alarm: AlarmHandle, +} + +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 = "integrated-timers")] + let alarm = unsafe { unwrap!(driver::allocate_alarm()) }; + #[cfg(feature = "integrated-timers")] + driver::set_alarm_callback(alarm, signal_fn, signal_ctx); + + Self { + run_queue: RunQueue::new(), + signal_fn, + signal_ctx, + + #[cfg(feature = "integrated-timers")] + timer_queue: timer_queue::TimerQueue::new(), + #[cfg(feature = "integrated-timers")] + alarm, + } + } + + /// 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). + #[inline(always)] + unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull) { + if self.run_queue.enqueue(cs, task) { + (self.signal_fn)(self.signal_ctx) + } + } + + /// 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) { + task.as_ref().executor.set(self); + + critical_section::with(|cs| { + self.enqueue(cs, task); + }) + } + + /// 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 = "integrated-timers")] + self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); + + self.run_queue.dequeue_all(|p| { + let task = p.as_ref(); + + #[cfg(feature = "integrated-timers")] + task.expires_at.set(Instant::MAX); + + let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); + if state & STATE_SPAWNED == 0 { + // If task is not running, ignore it. This can happen in the following scenario: + // - Task gets dequeued, poll starts + // - While task is being polled, it gets woken. It gets placed in the queue. + // - Task poll finishes, returning done=true + // - RUNNING bit is cleared, but the task is already in the queue. + return; + } + + // Run the task + task.poll_fn.read()(p as _); + + // Enqueue or update into timer_queue + #[cfg(feature = "integrated-timers")] + self.timer_queue.update(p); + }); + + #[cfg(feature = "integrated-timers")] + { + // 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()); + } + } + + /// 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`]. +/// +/// # Safety +/// +/// `task` must be a valid task pointer obtained from [`task_from_waker`]. +pub unsafe fn wake_task(task: NonNull) { + critical_section::with(|cs| { + let header = task.as_ref(); + let state = header.state.load(Ordering::Relaxed); + + // If already scheduled, or if not started, + if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { + return; + } + + // Mark it as scheduled + header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed); + + // We have just marked the task as scheduled, so enqueue it. + let executor = &*header.executor.get(); + executor.enqueue(cs, task); + }) +} + +#[cfg(feature = "integrated-timers")] +#[no_mangle] +unsafe fn _embassy_time_schedule_wake(at: Instant, waker: &core::task::Waker) { + let task = waker::task_from_waker(waker); + let task = task.as_ref(); + let expires_at = task.expires_at.get(); + task.expires_at.set(expires_at.min(at)); +} diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs new file mode 100644 index 000000000..ed8c82a5c --- /dev/null +++ b/embassy-executor/src/raw/run_queue.rs @@ -0,0 +1,74 @@ +use core::ptr; +use core::ptr::NonNull; + +use atomic_polyfill::{AtomicPtr, Ordering}; +use critical_section::CriticalSection; + +use super::TaskHeader; + +pub(crate) struct RunQueueItem { + next: AtomicPtr, +} + +impl RunQueueItem { + pub const fn new() -> Self { + Self { + next: AtomicPtr::new(ptr::null_mut()), + } + } +} + +/// Atomic task queue using a very, very simple lock-free linked-list queue: +/// +/// To enqueue a task, task.next is set to the old head, and head is atomically set to task. +/// +/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with +/// null. Then the batch is iterated following the next pointers until null is reached. +/// +/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK +/// for our purposes: it can't create fairness problems since the next batch won't run until the +/// current batch is completely processed, so even if a task enqueues itself instantly (for example +/// by waking its own waker) can't prevent other tasks from running. +pub(crate) struct RunQueue { + head: AtomicPtr, +} + +impl RunQueue { + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Enqueues an item. Returns true if the queue was empty. + /// + /// # Safety + /// + /// `item` must NOT be already enqueued in any queue. + #[inline(always)] + pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull) -> bool { + let prev = self.head.load(Ordering::Relaxed); + task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed); + self.head.store(task.as_ptr(), Ordering::Relaxed); + prev.is_null() + } + + /// 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); + + // 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 = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed); + + on_task(task); + + ptr = next + } + } +} diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs new file mode 100644 index 000000000..24c31892a --- /dev/null +++ b/embassy-executor/src/raw/timer_queue.rs @@ -0,0 +1,85 @@ +use core::cell::Cell; +use core::cmp::min; +use core::ptr; +use core::ptr::NonNull; + +use atomic_polyfill::Ordering; +use embassy_time::Instant; + +use super::{TaskHeader, STATE_TIMER_QUEUED}; + +pub(crate) struct TimerQueueItem { + next: Cell<*mut TaskHeader>, +} + +impl TimerQueueItem { + pub const fn new() -> Self { + Self { + next: Cell::new(ptr::null_mut()), + } + } +} + +pub(crate) struct TimerQueue { + head: Cell<*mut TaskHeader>, +} + +impl TimerQueue { + pub const fn new() -> Self { + Self { + head: Cell::new(ptr::null_mut()), + } + } + + pub(crate) unsafe fn update(&self, p: NonNull) { + let task = p.as_ref(); + if task.expires_at.get() != Instant::MAX { + let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); + let is_new = old_state & STATE_TIMER_QUEUED == 0; + + if is_new { + task.timer_queue_item.next.set(self.head.get()); + self.head.set(p.as_ptr()); + } + } + } + + pub(crate) unsafe fn next_expiration(&self) -> Instant { + let mut res = Instant::MAX; + self.retain(|p| { + let task = p.as_ref(); + let expires = task.expires_at.get(); + res = min(res, expires); + expires != Instant::MAX + }); + res + } + + pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull)) { + self.retain(|p| { + let task = p.as_ref(); + if task.expires_at.get() <= now { + on_task(p); + false + } else { + true + } + }); + } + + pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull) -> bool) { + let mut prev = &self.head; + while !prev.get().is_null() { + let p = NonNull::new_unchecked(prev.get()); + let task = &*p.as_ptr(); + if f(p) { + // Skip to next + prev = &task.timer_queue_item.next; + } else { + // Remove it + prev.set(task.timer_queue_item.next.get()); + task.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel); + } + } + } +} diff --git a/embassy-executor/src/raw/util.rs b/embassy-executor/src/raw/util.rs new file mode 100644 index 000000000..ed5822188 --- /dev/null +++ b/embassy-executor/src/raw/util.rs @@ -0,0 +1,33 @@ +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::ptr; + +pub(crate) struct UninitCell(MaybeUninit>); +impl UninitCell { + pub const fn uninit() -> Self { + Self(MaybeUninit::uninit()) + } + + pub unsafe fn as_mut_ptr(&self) -> *mut T { + (*self.0.as_ptr()).get() + } + + #[allow(clippy::mut_from_ref)] + pub unsafe fn as_mut(&self) -> &mut T { + &mut *self.as_mut_ptr() + } + + pub unsafe fn write(&self, val: T) { + ptr::write(self.as_mut_ptr(), val) + } + + pub unsafe fn drop_in_place(&self) { + ptr::drop_in_place(self.as_mut_ptr()) + } +} + +impl UninitCell { + pub unsafe fn read(&self) -> T { + ptr::read(self.as_mut_ptr()) + } +} diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs new file mode 100644 index 000000000..5765259f2 --- /dev/null +++ b/embassy-executor/src/raw/waker.rs @@ -0,0 +1,53 @@ +use core::mem; +use core::ptr::NonNull; +use core::task::{RawWaker, RawWakerVTable, Waker}; + +use super::{wake_task, TaskHeader}; + +const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); + +unsafe fn clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &VTABLE) +} + +unsafe fn wake(p: *const ()) { + wake_task(NonNull::new_unchecked(p as *mut TaskHeader)) +} + +unsafe fn drop(_: *const ()) { + // nop +} + +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 be 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 fn task_from_waker(waker: &Waker) -> NonNull { + // safety: OK because WakerHack has the same layout as Waker. + // This is not really guaranteed because the structs are `repr(Rust)`, it is + // indeed the case in the current implementation. + // TODO use waker_getters when stable. https://github.com/rust-lang/rust/issues/96992 + let hack: &WakerHack = unsafe { mem::transmute(waker) }; + if hack.vtable != &VTABLE { + panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") + } + + // safety: we never create a waker with a null data pointer. + unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) } +} + +struct WakerHack { + data: *const (), + vtable: &'static RawWakerVTable, +} diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs new file mode 100644 index 000000000..25a0d7dbb --- /dev/null +++ b/embassy-executor/src/spawner.rs @@ -0,0 +1,202 @@ +use core::marker::PhantomData; +use core::mem; +use core::ptr::NonNull; +use core::task::Poll; + +use futures_util::future::poll_fn; + +use super::raw; + +/// Token to spawn a newly-created task in an executor. +/// +/// When calling a task function (like `#[embassy_executor::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()`]. +/// +/// The generic parameter `S` determines whether the task can be spawned in executors +/// in other threads or not. If `S: Send`, it can, which allows spawning it into a [`SendSpawner`]. +/// If not, it can't, so it can only be spawned into the current thread's executor, with [`Spawner`]. +/// +/// # 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 S>, +} + +impl SpawnToken { + pub(crate) unsafe fn new(raw_task: NonNull) -> Self { + Self { + raw_task: Some(raw_task), + phantom: PhantomData, + } + } + + pub(crate) fn new_failed() -> Self { + Self { + raw_task: None, + phantom: PhantomData, + } + } +} + +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 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_executor::task]` can only have one instance + /// running at a time. You may allow multiple instances to run in parallel with + /// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage. + Busy, +} + +/// Handle to spawn tasks into 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]. +#[derive(Copy, Clone)] +pub struct Spawner { + executor: &'static raw::Executor, + not_send: PhantomData<*mut ()>, +} + +impl Spawner { + pub(crate) fn new(executor: &'static raw::Executor) -> Self { + Self { + executor, + not_send: PhantomData, + } + } + + /// Get a Spawner for the current executor. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + /// + /// # Panics + /// + /// Panics if the current executor is not an Embassy executor. + pub async fn for_current_executor() -> Self { + poll_fn(|cx| unsafe { + let task = raw::task_from_waker(cx.waker()); + let executor = (*task.as_ptr()).executor.get(); + Poll::Ready(Self::new(&*executor)) + }) + .await + } + + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). + pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { + let task = token.raw_task; + mem::forget(token); + + match task { + Some(task) => { + unsafe { self.executor.spawn(task) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } + + // Used by the `embassy_macros::main!` macro to throw an error when spawn + // fails. This is here to allow conditional use of `defmt::unwrap!` + // without introducing a `defmt` feature in the `embassy_macros` package, + // which would require use of `-Z namespaced-features`. + /// Spawn a task into an executor, panicking on failure. + /// + /// # Panics + /// + /// Panics if the spawning fails. + pub fn must_spawn(&self, token: SpawnToken) { + unwrap!(self.spawn(token)); + } + + /// Convert this Spawner to a SendSpawner. This allows you to send the + /// spawner to other threads, but the spawner loses the ability to spawn + /// non-Send tasks. + pub fn make_send(&self) -> SendSpawner { + SendSpawner { + executor: self.executor, + } + } +} + +/// Handle to spawn tasks into an executor from any thread. +/// +/// 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 non-Send tasks, use [Spawner]. +#[derive(Copy, Clone)] +pub struct SendSpawner { + executor: &'static raw::Executor, +} + +unsafe impl Send for SendSpawner {} +unsafe impl Sync for SendSpawner {} + +impl SendSpawner { + pub(crate) fn new(executor: &'static raw::Executor) -> Self { + Self { executor } + } + + /// Get a Spawner for the current executor. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + /// + /// # Panics + /// + /// Panics if the current executor is not an Embassy executor. + pub async fn for_current_executor() -> Self { + poll_fn(|cx| unsafe { + let task = raw::task_from_waker(cx.waker()); + let executor = (*task.as_ptr()).executor.get(); + Poll::Ready(Self::new(&*executor)) + }) + .await + } + + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). + pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { + let header = token.raw_task; + mem::forget(token); + + match header { + Some(header) => { + unsafe { self.executor.spawn(header) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } + + /// Spawn a task into an executor, panicking on failure. + /// + /// # Panics + /// + /// Panics if the spawning fails. + pub fn must_spawn(&self, token: SpawnToken) { + unwrap!(self.spawn(token)); + } +} diff --git a/embassy-executor/src/time/delay.rs b/embassy-executor/src/time/delay.rs deleted file mode 100644 index d76ed32eb..000000000 --- a/embassy-executor/src/time/delay.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::{Duration, Instant}; - -/// Blocks for at least `duration`. -pub fn block_for(duration: Duration) { - let expires_at = Instant::now() + duration; - while Instant::now() < expires_at {} -} - -/// Type implementing async delays and blocking `embedded-hal` delays. -/// -/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least -/// the amount provided, but accuracy can be affected by many factors, including interrupt usage. -/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently -/// active driver. -pub struct Delay; - -#[cfg(feature = "unstable-traits")] -mod eh1 { - use super::*; - - impl embedded_hal_1::delay::blocking::DelayUs for Delay { - type Error = core::convert::Infallible; - - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { - Ok(block_for(Duration::from_micros(us as u64))) - } - - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { - Ok(block_for(Duration::from_millis(ms as u64))) - } - } -} - -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use crate::time::Timer; - use core::future::Future; - use futures_util::FutureExt; - - impl embedded_hal_async::delay::DelayUs for Delay { - type Error = core::convert::Infallible; - - type DelayUsFuture<'a> = impl Future> + 'a where Self: 'a; - - fn delay_us(&mut self, micros: u32) -> Self::DelayUsFuture<'_> { - Timer::after(Duration::from_micros(micros as _)).map(Ok) - } - - type DelayMsFuture<'a> = impl Future> + 'a where Self: 'a; - - fn delay_ms(&mut self, millis: u32) -> Self::DelayMsFuture<'_> { - Timer::after(Duration::from_millis(millis as _)).map(Ok) - } - } - } -} - -mod eh02 { - use embedded_hal_02::blocking::delay::{DelayMs, DelayUs}; - - use super::*; - - impl DelayMs for Delay { - fn delay_ms(&mut self, ms: u8) { - block_for(Duration::from_millis(ms as u64)) - } - } - - impl DelayMs for Delay { - fn delay_ms(&mut self, ms: u16) { - block_for(Duration::from_millis(ms as u64)) - } - } - - impl DelayMs for Delay { - fn delay_ms(&mut self, ms: u32) { - block_for(Duration::from_millis(ms as u64)) - } - } - - impl DelayUs for Delay { - fn delay_us(&mut self, us: u8) { - block_for(Duration::from_micros(us as u64)) - } - } - - impl DelayUs for Delay { - fn delay_us(&mut self, us: u16) { - block_for(Duration::from_micros(us as u64)) - } - } - - impl DelayUs for Delay { - fn delay_us(&mut self, us: u32) { - block_for(Duration::from_micros(us as u64)) - } - } -} diff --git a/embassy-executor/src/time/driver.rs b/embassy-executor/src/time/driver.rs deleted file mode 100644 index 48e2f1c7d..000000000 --- a/embassy-executor/src/time/driver.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Time driver interface -//! -//! This module defines the interface a driver needs to implement to power the `embassy_executor::time` module. -//! -//! # Implementing a driver -//! -//! - Define a struct `MyDriver` -//! - Implement [`Driver`] for it -//! - Register it as the global driver with [`time_driver_impl`]. -//! - Enable the Cargo features `embassy-executor/time` and one of `embassy-executor/time-tick-*` corresponding to the -//! tick rate of your driver. -//! -//! If you wish to make the tick rate configurable by the end user, you should do so by exposing your own -//! Cargo features and having each enable the corresponding `embassy-executor/time-tick-*`. -//! -//! # Linkage details -//! -//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions. -//! -//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them. -//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the -//! calls from the `embassy` crate to call into the driver crate. -//! -//! If there is none or multiple drivers in the crate tree, linking will fail. -//! -//! This method has a few key advantages for something as foundational as timekeeping: -//! -//! - The time driver is available everywhere easily, without having to thread the implementation -//! through generic parameters. This is especially helpful for libraries. -//! - It means comparing `Instant`s will always make sense: if there were multiple drivers -//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which -//! would yield incorrect results. -//! -//! # Example -//! -//! ``` -//! use embassy_executor::time::driver::{Driver, AlarmHandle}; -//! -//! struct MyDriver{}; // not public! -//! embassy_executor::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); -//! -//! impl Driver for MyDriver { -//! fn now(&self) -> u64 { -//! todo!() -//! } -//! unsafe fn allocate_alarm(&self) -> Option { -//! todo!() -//! } -//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { -//! todo!() -//! } -//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { -//! todo!() -//! } -//! } -//! ``` - -/// Alarm handle, assigned by the driver. -#[derive(Clone, Copy)] -pub struct AlarmHandle { - id: u8, -} - -impl AlarmHandle { - /// Create an AlarmHandle - /// - /// Safety: May only be called by the current global Driver impl. - /// The impl is allowed to rely on the fact that all `AlarmHandle` instances - /// are created by itself in unsafe code (e.g. indexing operations) - pub unsafe fn new(id: u8) -> Self { - Self { id } - } - - /// Get the ID of the AlarmHandle. - pub fn id(&self) -> u8 { - self.id - } -} - -/// Time driver -pub trait Driver: Send + Sync + 'static { - /// Return the current timestamp in ticks. - /// - /// Implementations MUST ensure that: - /// - This is guaranteed to be monotonic, i.e. a call to now() will always return - /// a greater or equal value than earler calls. Time can't "roll backwards". - /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say - /// in 10_000 years (Human civilization is likely to already have self-destructed - /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers - /// you MUST extend them to 64-bit, for example by counting overflows in software, - /// or chaining multiple timers together. - fn now(&self) -> u64; - - /// Try allocating an alarm handle. Returns None if no alarms left. - /// Initially the alarm has no callback set, and a null `ctx` pointer. - /// - /// # Safety - /// It is UB to make the alarm fire before setting a callback. - unsafe fn allocate_alarm(&self) -> Option; - - /// Sets the callback function to be called when the alarm triggers. - /// 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 the alarm - /// timestamp, the provided callback function 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 for each AlarmHandle. This overwrites any previously-set alarm if any. - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); -} - -extern "Rust" { - fn _embassy_time_now() -> u64; - fn _embassy_time_allocate_alarm() -> Option; - fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64); -} - -pub(crate) fn now() -> u64 { - unsafe { _embassy_time_now() } -} -/// Safety: it is UB to make the alarm fire before setting a callback. -pub(crate) unsafe fn allocate_alarm() -> Option { - _embassy_time_allocate_alarm() -} -pub(crate) fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) } -} -pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) { - unsafe { _embassy_time_set_alarm(alarm, timestamp) } -} - -/// Set the time Driver implementation. -/// -/// See the module documentation for an example. -#[macro_export] -macro_rules! time_driver_impl { - (static $name:ident: $t: ty = $val:expr) => { - static $name: $t = $val; - - #[no_mangle] - fn _embassy_time_now() -> u64 { - <$t as $crate::time::driver::Driver>::now(&$name) - } - - #[no_mangle] - unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::time::driver::AlarmHandle> { - <$t as $crate::time::driver::Driver>::allocate_alarm(&$name) - } - - #[no_mangle] - fn _embassy_time_set_alarm_callback( - alarm: $crate::time::driver::AlarmHandle, - callback: fn(*mut ()), - ctx: *mut (), - ) { - <$t as $crate::time::driver::Driver>::set_alarm_callback(&$name, alarm, callback, ctx) - } - - #[no_mangle] - fn _embassy_time_set_alarm(alarm: $crate::time::driver::AlarmHandle, timestamp: u64) { - <$t as $crate::time::driver::Driver>::set_alarm(&$name, alarm, timestamp) - } - }; -} diff --git a/embassy-executor/src/time/driver_std.rs b/embassy-executor/src/time/driver_std.rs deleted file mode 100644 index cb66f7c19..000000000 --- a/embassy-executor/src/time/driver_std.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::cell::UnsafeCell; -use std::mem::MaybeUninit; -use std::sync::{Condvar, Mutex, Once}; -use std::time::{Duration as StdDuration, Instant as StdInstant}; -use std::{mem, ptr, thread}; - -use atomic_polyfill::{AtomicU8, Ordering}; - -use crate::time::driver::{AlarmHandle, Driver}; - -const ALARM_COUNT: usize = 4; - -struct AlarmState { - timestamp: u64, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - callback: *const (), - ctx: *mut (), -} - -unsafe impl Send for AlarmState {} - -impl AlarmState { - const fn new() -> Self { - Self { - timestamp: u64::MAX, - callback: ptr::null(), - ctx: ptr::null_mut(), - } - } -} - -struct TimeDriver { - alarm_count: AtomicU8, - - once: Once, - alarms: UninitCell>, - zero_instant: UninitCell, - signaler: UninitCell, -} - -const ALARM_NEW: AlarmState = AlarmState::new(); -crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { - alarm_count: AtomicU8::new(0), - - once: Once::new(), - alarms: UninitCell::uninit(), - zero_instant: UninitCell::uninit(), - signaler: UninitCell::uninit(), -}); - -impl TimeDriver { - fn init(&self) { - self.once.call_once(|| unsafe { - self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); - self.zero_instant.write(StdInstant::now()); - self.signaler.write(Signaler::new()); - - thread::spawn(Self::alarm_thread); - }); - } - - fn alarm_thread() { - let zero = unsafe { DRIVER.zero_instant.read() }; - loop { - let now = DRIVER.now(); - - let mut next_alarm = u64::MAX; - { - let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); - for alarm in alarms { - if alarm.timestamp <= now { - alarm.timestamp = u64::MAX; - - // Call after clearing alarm, so the callback can set another alarm. - - // safety: - // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; - f(alarm.ctx); - } else { - next_alarm = next_alarm.min(alarm.timestamp); - } - } - } - - // Ensure we don't overflow - let until = zero - .checked_add(StdDuration::from_micros(next_alarm)) - .unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1)); - - unsafe { DRIVER.signaler.as_ref() }.wait_until(until); - } - } -} - -impl Driver for TimeDriver { - fn now(&self) -> u64 { - self.init(); - - let zero = unsafe { self.zero_instant.read() }; - StdInstant::now().duration_since(zero).as_micros() as u64 - } - - unsafe fn allocate_alarm(&self) -> Option { - let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, - } - } - - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.callback = callback as *const (); - alarm.ctx = ctx; - } - - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { - self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.timestamp = timestamp; - unsafe { self.signaler.as_ref() }.signal(); - } -} - -struct Signaler { - mutex: Mutex, - condvar: Condvar, -} - -impl Signaler { - fn new() -> Self { - Self { - mutex: Mutex::new(false), - condvar: Condvar::new(), - } - } - - fn wait_until(&self, until: StdInstant) { - let mut signaled = self.mutex.lock().unwrap(); - while !*signaled { - let now = StdInstant::now(); - - if now >= until { - break; - } - - let dur = until - now; - let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap(); - signaled = signaled2; - if timeout.timed_out() { - break; - } - } - *signaled = false; - } - - fn signal(&self) { - let mut signaled = self.mutex.lock().unwrap(); - *signaled = true; - self.condvar.notify_one(); - } -} - -pub(crate) struct UninitCell(MaybeUninit>); -unsafe impl Send for UninitCell {} -unsafe impl Sync for UninitCell {} - -impl UninitCell { - pub const fn uninit() -> Self { - Self(MaybeUninit::uninit()) - } - - pub unsafe fn as_ptr(&self) -> *const T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_mut_ptr(&self) -> *mut T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_ref(&self) -> &T { - &*self.as_ptr() - } - - pub unsafe fn write(&self, val: T) { - ptr::write(self.as_mut_ptr(), val) - } -} - -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) - } -} diff --git a/embassy-executor/src/time/driver_wasm.rs b/embassy-executor/src/time/driver_wasm.rs deleted file mode 100644 index 5f585a19a..000000000 --- a/embassy-executor/src/time/driver_wasm.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::cell::UnsafeCell; -use std::mem::MaybeUninit; -use std::ptr; -use std::sync::{Mutex, Once}; - -use atomic_polyfill::{AtomicU8, Ordering}; -use wasm_bindgen::prelude::*; -use wasm_timer::Instant as StdInstant; - -use crate::time::driver::{AlarmHandle, Driver}; - -const ALARM_COUNT: usize = 4; - -struct AlarmState { - token: Option, - closure: Option>, -} - -unsafe impl Send for AlarmState {} - -impl AlarmState { - const fn new() -> Self { - Self { - token: None, - closure: None, - } - } -} - -#[wasm_bindgen] -extern "C" { - fn setTimeout(closure: &Closure, millis: u32) -> f64; - fn clearTimeout(token: f64); -} - -struct TimeDriver { - alarm_count: AtomicU8, - - once: Once, - alarms: UninitCell>, - zero_instant: UninitCell, -} - -const ALARM_NEW: AlarmState = AlarmState::new(); -crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { - alarm_count: AtomicU8::new(0), - once: Once::new(), - alarms: UninitCell::uninit(), - zero_instant: UninitCell::uninit(), -}); - -impl TimeDriver { - fn init(&self) { - self.once.call_once(|| unsafe { - self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); - self.zero_instant.write(StdInstant::now()); - }); - } -} - -impl Driver for TimeDriver { - fn now(&self) -> u64 { - self.init(); - - let zero = unsafe { self.zero_instant.read() }; - StdInstant::now().duration_since(zero).as_micros() as u64 - } - - unsafe fn allocate_alarm(&self) -> Option { - let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, - } - } - - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.closure.replace(Closure::new(move || { - callback(ctx); - })); - } - - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { - self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - let timeout = (timestamp - self.now()) as u32; - if let Some(token) = alarm.token { - clearTimeout(token); - } - alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); - } -} - -pub(crate) struct UninitCell(MaybeUninit>); -unsafe impl Send for UninitCell {} -unsafe impl Sync for UninitCell {} - -impl UninitCell { - pub const fn uninit() -> Self { - Self(MaybeUninit::uninit()) - } - unsafe fn as_ptr(&self) -> *const T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_mut_ptr(&self) -> *mut T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_ref(&self) -> &T { - &*self.as_ptr() - } - - pub unsafe fn write(&self, val: T) { - ptr::write(self.as_mut_ptr(), val) - } -} - -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) - } -} diff --git a/embassy-executor/src/time/duration.rs b/embassy-executor/src/time/duration.rs deleted file mode 100644 index dc4f16bd4..000000000 --- a/embassy-executor/src/time/duration.rs +++ /dev/null @@ -1,184 +0,0 @@ -use core::fmt; -use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; - -use super::{GCD_1K, GCD_1M, TICKS_PER_SECOND}; - -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Represents the difference between two [Instant](struct.Instant.html)s -pub struct Duration { - pub(crate) ticks: u64, -} - -impl Duration { - /// The smallest value that can be represented by the `Duration` type. - pub const MIN: Duration = Duration { ticks: u64::MIN }; - /// The largest value that can be represented by the `Duration` type. - pub const MAX: Duration = Duration { ticks: u64::MAX }; - - /// Tick count of the `Duration`. - pub const fn as_ticks(&self) -> u64 { - self.ticks - } - - /// Convert the `Duration` to seconds, rounding down. - pub const fn as_secs(&self) -> u64 { - self.ticks / TICKS_PER_SECOND - } - - /// Convert the `Duration` to milliseconds, rounding down. - pub const fn as_millis(&self) -> u64 { - self.ticks * (1000 / GCD_1K) / (TICKS_PER_SECOND / GCD_1K) - } - - /// Convert the `Duration` to microseconds, rounding down. - pub const fn as_micros(&self) -> u64 { - self.ticks * (1_000_000 / GCD_1M) / (TICKS_PER_SECOND / GCD_1M) - } - - /// Creates a duration from the specified number of clock ticks - pub const fn from_ticks(ticks: u64) -> Duration { - Duration { ticks } - } - - /// Creates a duration from the specified number of seconds, rounding up. - pub const fn from_secs(secs: u64) -> Duration { - Duration { - ticks: secs * TICKS_PER_SECOND, - } - } - - /// Creates a duration from the specified number of milliseconds, rounding up. - pub const fn from_millis(millis: u64) -> Duration { - Duration { - ticks: div_ceil(millis * (TICKS_PER_SECOND / GCD_1K), 1000 / GCD_1K), - } - } - - /// Creates a duration from the specified number of microseconds, rounding up. - /// NOTE: Delays this small may be inaccurate. - pub const fn from_micros(micros: u64) -> Duration { - Duration { - ticks: div_ceil(micros * (TICKS_PER_SECOND / GCD_1M), 1_000_000 / GCD_1M), - } - } - - /// Creates a duration from the specified number of seconds, rounding down. - pub const fn from_secs_floor(secs: u64) -> Duration { - Duration { - ticks: secs * TICKS_PER_SECOND, - } - } - - /// Creates a duration from the specified number of milliseconds, rounding down. - pub const fn from_millis_floor(millis: u64) -> Duration { - Duration { - ticks: millis * (TICKS_PER_SECOND / GCD_1K) / (1000 / GCD_1K), - } - } - - /// Creates a duration from the specified number of microseconds, rounding down. - /// NOTE: Delays this small may be inaccurate. - pub const fn from_micros_floor(micros: u64) -> Duration { - Duration { - ticks: micros * (TICKS_PER_SECOND / GCD_1M) / (1_000_000 / GCD_1M), - } - } - - /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. - pub fn checked_add(self, rhs: Duration) -> Option { - self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) - } - - /// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow. - pub fn checked_sub(self, rhs: Duration) -> Option { - self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks }) - } - - /// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow. - pub fn checked_mul(self, rhs: u32) -> Option { - self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks }) - } - - /// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow. - pub fn checked_div(self, rhs: u32) -> Option { - self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks }) - } -} - -impl Add for Duration { - type Output = Duration; - - fn add(self, rhs: Duration) -> Duration { - self.checked_add(rhs).expect("overflow when adding durations") - } -} - -impl AddAssign for Duration { - fn add_assign(&mut self, rhs: Duration) { - *self = *self + rhs; - } -} - -impl Sub for Duration { - type Output = Duration; - - fn sub(self, rhs: Duration) -> Duration { - self.checked_sub(rhs).expect("overflow when subtracting durations") - } -} - -impl SubAssign for Duration { - fn sub_assign(&mut self, rhs: Duration) { - *self = *self - rhs; - } -} - -impl Mul for Duration { - type Output = Duration; - - fn mul(self, rhs: u32) -> Duration { - self.checked_mul(rhs) - .expect("overflow when multiplying duration by scalar") - } -} - -impl Mul for u32 { - type Output = Duration; - - fn mul(self, rhs: Duration) -> Duration { - rhs * self - } -} - -impl MulAssign for Duration { - fn mul_assign(&mut self, rhs: u32) { - *self = *self * rhs; - } -} - -impl Div for Duration { - type Output = Duration; - - fn div(self, rhs: u32) -> Duration { - self.checked_div(rhs) - .expect("divide by zero error when dividing duration by scalar") - } -} - -impl DivAssign for Duration { - fn div_assign(&mut self, rhs: u32) { - *self = *self / rhs; - } -} - -impl<'a> fmt::Display for Duration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} ticks", self.ticks) - } -} - -#[inline] -const fn div_ceil(num: u64, den: u64) -> u64 { - (num + den - 1) / den -} diff --git a/embassy-executor/src/time/instant.rs b/embassy-executor/src/time/instant.rs deleted file mode 100644 index 6a4925f47..000000000 --- a/embassy-executor/src/time/instant.rs +++ /dev/null @@ -1,159 +0,0 @@ -use core::fmt; -use core::ops::{Add, AddAssign, Sub, SubAssign}; - -use super::{driver, Duration, GCD_1K, GCD_1M, TICKS_PER_SECOND}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// An Instant in time, based on the MCU's clock ticks since startup. -pub struct Instant { - ticks: u64, -} - -impl Instant { - /// The smallest (earliest) value that can be represented by the `Instant` type. - pub const MIN: Instant = Instant { ticks: u64::MIN }; - /// The largest (latest) value that can be represented by the `Instant` type. - pub const MAX: Instant = Instant { ticks: u64::MAX }; - - /// Returns an Instant representing the current time. - pub fn now() -> Instant { - Instant { ticks: driver::now() } - } - - /// Create an Instant from a tick count since system boot. - pub const fn from_ticks(ticks: u64) -> Self { - Self { ticks } - } - - /// Create an Instant from a microsecond count since system boot. - pub const fn from_micros(micros: u64) -> Self { - Self { - ticks: micros * (TICKS_PER_SECOND / GCD_1M) / (1_000_000 / GCD_1M), - } - } - - /// Create an Instant from a millisecond count since system boot. - pub const fn from_millis(millis: u64) -> Self { - Self { - ticks: millis * (TICKS_PER_SECOND / GCD_1K) / (1000 / GCD_1K), - } - } - - /// Create an Instant from a second count since system boot. - pub const fn from_secs(seconds: u64) -> Self { - Self { - ticks: seconds * TICKS_PER_SECOND, - } - } - - /// Tick count since system boot. - pub const fn as_ticks(&self) -> u64 { - self.ticks - } - - /// Seconds since system boot. - pub const fn as_secs(&self) -> u64 { - self.ticks / TICKS_PER_SECOND - } - - /// Milliseconds since system boot. - pub const fn as_millis(&self) -> u64 { - self.ticks * (1000 / GCD_1K) / (TICKS_PER_SECOND / GCD_1K) - } - - /// Microseconds since system boot. - pub const fn as_micros(&self) -> u64 { - self.ticks * (1_000_000 / GCD_1M) / (TICKS_PER_SECOND / GCD_1M) - } - - /// Duration between this Instant and another Instant - /// Panics on over/underflow. - pub fn duration_since(&self, earlier: Instant) -> Duration { - Duration { - ticks: self.ticks.checked_sub(earlier.ticks).unwrap(), - } - } - - /// Duration between this Instant and another Instant - pub fn checked_duration_since(&self, earlier: Instant) -> Option { - if self.ticks < earlier.ticks { - None - } else { - Some(Duration { - ticks: self.ticks - earlier.ticks, - }) - } - } - - /// Returns the duration since the "earlier" Instant. - /// If the "earlier" instant is in the future, the duration is set to zero. - pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { - Duration { - ticks: if self.ticks < earlier.ticks { - 0 - } else { - self.ticks - earlier.ticks - }, - } - } - - /// Duration elapsed since this Instant. - pub fn elapsed(&self) -> Duration { - Instant::now() - *self - } - - /// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow. - pub fn checked_add(&self, duration: Duration) -> Option { - self.ticks.checked_add(duration.ticks).map(|ticks| Instant { ticks }) - } - - /// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow. - pub fn checked_sub(&self, duration: Duration) -> Option { - self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks }) - } -} - -impl Add for Instant { - type Output = Instant; - - fn add(self, other: Duration) -> Instant { - self.checked_add(other) - .expect("overflow when adding duration to instant") - } -} - -impl AddAssign for Instant { - fn add_assign(&mut self, other: Duration) { - *self = *self + other; - } -} - -impl Sub for Instant { - type Output = Instant; - - fn sub(self, other: Duration) -> Instant { - self.checked_sub(other) - .expect("overflow when subtracting duration from instant") - } -} - -impl SubAssign for Instant { - fn sub_assign(&mut self, other: Duration) { - *self = *self - other; - } -} - -impl Sub for Instant { - type Output = Duration; - - fn sub(self, other: Instant) -> Duration { - self.duration_since(other) - } -} - -impl<'a> fmt::Display for Instant { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} ticks", self.ticks) - } -} diff --git a/embassy-executor/src/time/mod.rs b/embassy-executor/src/time/mod.rs deleted file mode 100644 index b787a5cf2..000000000 --- a/embassy-executor/src/time/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Timekeeping, delays and timeouts. -//! -//! Timekeeping is done with elapsed time since system boot. Time is represented in -//! ticks, where the tick rate is defined by the current driver, usually to match -//! the tick rate of the hardware. -//! -//! Tick counts are 64 bits. At the highest supported tick rate of 1Mhz this supports -//! representing time spans of up to ~584558 years, which is big enough for all practical -//! purposes and allows not having to worry about overflows. -//! -//! [`Instant`] represents a given instant of time (relative to system boot), and [`Duration`] -//! represents the duration of a span of time. They implement the math operations you'd expect, -//! like addition and substraction. -//! -//! # Delays and timeouts -//! -//! [`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time. -//! -//! An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility -//! with libraries from the ecosystem. -//! -//! # Wall-clock time -//! -//! The `time` module deals exclusively with a monotonically increasing tick count. -//! Therefore it has no direct support for wall-clock time ("real life" datetimes -//! like `2021-08-24 13:33:21`). -//! -//! If persistence across reboots is not needed, support can be built on top of -//! `embassy_executor::time` by storing the offset between "seconds elapsed since boot" -//! and "seconds since unix epoch". -//! -//! # Time driver -//! -//! The `time` module is backed by a global "time driver" specified at build time. -//! Only one driver can be active in a program. -//! -//! All methods and structs transparently call into the active driver. This makes it -//! possible for libraries to use `embassy_executor::time` in a driver-agnostic way without -//! requiring generic parameters. -//! -//! For more details, check the [`driver`] module. - -#![deny(missing_docs)] - -mod delay; -pub mod driver; -mod duration; -mod instant; -mod timer; - -#[cfg(feature = "std")] -mod driver_std; - -#[cfg(feature = "wasm")] -mod driver_wasm; - -pub use delay::{block_for, Delay}; -pub use duration::Duration; -pub use instant::Instant; -pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; - -#[cfg(feature = "time-tick-1000hz")] -const TPS: u64 = 1_000; - -#[cfg(feature = "time-tick-32768hz")] -const TPS: u64 = 32_768; - -#[cfg(feature = "time-tick-1mhz")] -const TPS: u64 = 1_000_000; - -#[cfg(feature = "time-tick-16mhz")] -const TPS: u64 = 16_000_000; - -/// Ticks per second of the global timebase. -/// -/// This value is specified by the `time-tick-*` Cargo features, which -/// should be set by the time driver. Some drivers support a fixed tick rate, others -/// allow you to choose a tick rate with Cargo features of their own. You should not -/// set the `time-tick-*` features for embassy yourself as an end user. -pub const TICKS_PER_SECOND: u64 = TPS; - -const fn gcd(a: u64, b: u64) -> u64 { - if b == 0 { - a - } else { - gcd(b, a % b) - } -} - -pub(crate) const GCD_1K: u64 = gcd(TICKS_PER_SECOND, 1_000); -pub(crate) const GCD_1M: u64 = gcd(TICKS_PER_SECOND, 1_000_000); diff --git a/embassy-executor/src/time/timer.rs b/embassy-executor/src/time/timer.rs deleted file mode 100644 index b9cdb1be5..000000000 --- a/embassy-executor/src/time/timer.rs +++ /dev/null @@ -1,151 +0,0 @@ -use core::future::Future; -use core::pin::Pin; -use core::task::{Context, Poll}; - -use futures_util::future::{select, Either}; -use futures_util::{pin_mut, Stream}; - -use crate::executor::raw; -use crate::time::{Duration, Instant}; - -/// Error returned by [`with_timeout`] on timeout. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TimeoutError; - -/// Runs a given future with a timeout. -/// -/// If the future completes before the timeout, its output is returned. Otherwise, on timeout, -/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. -pub async fn with_timeout(timeout: Duration, fut: F) -> Result { - let timeout_fut = Timer::after(timeout); - pin_mut!(fut); - match select(fut, timeout_fut).await { - Either::Left((r, _)) => Ok(r), - Either::Right(_) => Err(TimeoutError), - } -} - -/// A future that completes at a specified [Instant](struct.Instant.html). -pub struct Timer { - expires_at: Instant, - yielded_once: bool, -} - -impl Timer { - /// Expire at specified [Instant](struct.Instant.html) - pub fn at(expires_at: Instant) -> Self { - Self { - expires_at, - yielded_once: false, - } - } - - /// Expire after specified [Duration](struct.Duration.html). - /// This can be used as a `sleep` abstraction. - /// - /// Example: - /// ``` no_run - /// # #![feature(type_alias_impl_trait)] - /// # - /// # fn foo() {} - /// use embassy_executor::time::{Duration, Timer}; - /// - /// #[embassy_executor::task] - /// async fn demo_sleep_seconds() { - /// // suspend this task for one second. - /// Timer::after(Duration::from_secs(1)).await; - /// } - /// ``` - pub fn after(duration: Duration) -> Self { - Self { - expires_at: Instant::now() + duration, - yielded_once: false, - } - } -} - -impl Unpin for Timer {} - -impl Future for Timer { - type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.yielded_once && self.expires_at <= Instant::now() { - Poll::Ready(()) - } else { - unsafe { raw::register_timer(self.expires_at, cx.waker()) }; - self.yielded_once = true; - Poll::Pending - } - } -} - -/// Asynchronous stream that yields every Duration, indefinitely. -/// -/// This stream will tick at uniform intervals, even if blocking work is performed between ticks. -/// -/// For instance, consider the following code fragment. -/// ``` no_run -/// # #![feature(type_alias_impl_trait)] -/// # -/// use embassy_executor::time::{Duration, Timer}; -/// # fn foo() {} -/// -/// #[embassy_executor::task] -/// async fn ticker_example_0() { -/// loop { -/// foo(); -/// Timer::after(Duration::from_secs(1)).await; -/// } -/// } -/// ``` -/// -/// This fragment will not call `foo` every second. -/// Instead, it will call it every second + the time it took to previously call `foo`. -/// -/// Example using ticker, which will consistently call `foo` once a second. -/// -/// ``` no_run -/// # #![feature(type_alias_impl_trait)] -/// # -/// use embassy_executor::time::{Duration, Ticker}; -/// use futures::StreamExt; -/// # fn foo(){} -/// -/// #[embassy_executor::task] -/// async fn ticker_example_1() { -/// let mut ticker = Ticker::every(Duration::from_secs(1)); -/// loop { -/// foo(); -/// ticker.next().await; -/// } -/// } -/// ``` -pub struct Ticker { - expires_at: Instant, - duration: Duration, -} - -impl Ticker { - /// Creates a new ticker that ticks at the specified duration interval. - pub fn every(duration: Duration) -> Self { - let expires_at = Instant::now() + duration; - Self { expires_at, duration } - } -} - -impl Unpin for Ticker {} - -impl Stream for Ticker { - type Item = (); - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.expires_at <= Instant::now() { - let dur = self.duration; - self.expires_at += dur; - Poll::Ready(Some(())) - } else { - unsafe { raw::register_timer(self.expires_at, cx.waker()) }; - Poll::Pending - } - } -} -- cgit