feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+72
@@ -0,0 +1,72 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-avr`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_avr as main;
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {
|
||||
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// avr Executor
|
||||
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(core::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 [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (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 {
|
||||
avr_device::interrupt::disable();
|
||||
if !SIGNAL_WORK_THREAD_MODE.swap(false, Ordering::SeqCst) {
|
||||
avr_device::interrupt::enable();
|
||||
avr_device::asm::sleep();
|
||||
} else {
|
||||
avr_device::interrupt::enable();
|
||||
self.inner.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
#[export_name = "__pender"]
|
||||
#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
|
||||
fn __pender(context: *mut ()) {
|
||||
unsafe {
|
||||
// Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt
|
||||
// request number given to `InterruptExecutor::start`.
|
||||
|
||||
let context = context as usize;
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
// Try to make Rust optimize the branching away if we only use thread mode.
|
||||
if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER {
|
||||
core::arch::asm!("sev");
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
{
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use cortex_m::peripheral::NVIC;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Irq(u16);
|
||||
unsafe impl InterruptNumber for Irq {
|
||||
fn number(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
let irq = Irq(context as u16);
|
||||
|
||||
// STIR is faster, but is only available in v7 and higher.
|
||||
#[cfg(not(armv6m))]
|
||||
{
|
||||
let mut nvic: NVIC = core::mem::transmute(());
|
||||
nvic.request(irq);
|
||||
}
|
||||
|
||||
#[cfg(armv6m)]
|
||||
NVIC::pend(irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
pub(super) const THREAD_PENDER: usize = usize::MAX;
|
||||
|
||||
use core::arch::asm;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_cortex_m as main;
|
||||
|
||||
use crate::{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(THREAD_PENDER as *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 [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (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");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
pub use interrupt::*;
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
mod interrupt {
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use cortex_m::peripheral::NVIC;
|
||||
use critical_section::Mutex;
|
||||
|
||||
use crate::raw;
|
||||
|
||||
/// Interrupt mode executor.
|
||||
///
|
||||
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
|
||||
/// to poll tasks, and when a task is woken the interrupt is pended from software.
|
||||
///
|
||||
/// This allows running async tasks at a priority higher than thread mode. One
|
||||
/// use case is to leave thread mode free for non-async tasks. Another use case is
|
||||
/// to run multiple executors: one in thread mode for low priority tasks and another in
|
||||
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
|
||||
/// priority ones.
|
||||
///
|
||||
/// It is even possible to run multiple interrupt mode executors at different priorities,
|
||||
/// by assigning different priorities to the interrupts. For an example on how to do this,
|
||||
/// See the 'multiprio' example for 'embassy-nrf'.
|
||||
///
|
||||
/// To use it, you have to pick an interrupt that won't be used by the hardware.
|
||||
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
|
||||
/// If this is not the case, you may use an interrupt from any unused peripheral.
|
||||
///
|
||||
/// It is somewhat more complex to use, it's recommended to use the thread-mode
|
||||
/// [`Executor`] instead, if it works for your use case.
|
||||
pub struct InterruptExecutor {
|
||||
started: Mutex<Cell<bool>>,
|
||||
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for InterruptExecutor {}
|
||||
unsafe impl Sync for InterruptExecutor {}
|
||||
|
||||
impl InterruptExecutor {
|
||||
/// Create a new, not started `InterruptExecutor`.
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
started: Mutex::new(Cell::new(false)),
|
||||
executor: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Executor interrupt callback.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - You MUST call this from the interrupt handler, and from nowhere else.
|
||||
/// - You must not call this before calling `start()`.
|
||||
pub unsafe fn on_interrupt(&'static self) {
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
executor.poll();
|
||||
}
|
||||
|
||||
/// Start the executor.
|
||||
///
|
||||
/// This initializes the executor, enables the interrupt, and returns.
|
||||
/// The executor keeps running in the background through the interrupt.
|
||||
///
|
||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
|
||||
/// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
|
||||
/// different "thread" (the interrupt), so spawning tasks on it is effectively
|
||||
/// sending them.
|
||||
///
|
||||
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
|
||||
/// a task running in it.
|
||||
///
|
||||
/// # Interrupt requirements
|
||||
///
|
||||
/// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
|
||||
///
|
||||
/// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
|
||||
///
|
||||
/// You must set the interrupt priority before calling this method. You MUST NOT
|
||||
/// do it after.
|
||||
///
|
||||
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
|
||||
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
|
||||
panic!("InterruptExecutor::start() called multiple times on the same executor.");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(&mut *self.executor.get())
|
||||
.as_mut_ptr()
|
||||
.write(raw::Executor::new(irq.number() as *mut ()))
|
||||
}
|
||||
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
|
||||
unsafe { NVIC::unmask(irq) }
|
||||
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
|
||||
/// Get a SendSpawner for this executor
|
||||
///
|
||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on this
|
||||
/// executor.
|
||||
///
|
||||
/// This MUST only be called on an executor that has already been started.
|
||||
/// The function will panic otherwise.
|
||||
pub fn spawner(&'static self) -> crate::SendSpawner {
|
||||
if !critical_section::with(|cs| self.started.borrow(cs).get()) {
|
||||
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
|
||||
}
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-riscv32`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub use embassy_executor_macros::main_riscv as main;
|
||||
|
||||
use crate::{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);
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {
|
||||
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// RISCV32 Executor
|
||||
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(core::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 [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-spin`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_spin as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {}
|
||||
|
||||
/// Spin Executor
|
||||
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(core::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 [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (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() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-std`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
|
||||
pub use embassy_executor_macros::main_std as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
let signaler: &'static Signaler = unsafe { std::mem::transmute(context) };
|
||||
signaler.signal()
|
||||
}
|
||||
|
||||
/// 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(signaler as *mut Signaler as *mut ()),
|
||||
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 [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (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<bool>,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-wasm`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_wasm as main;
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::raw::util::UninitCell;
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) };
|
||||
let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() });
|
||||
}
|
||||
|
||||
pub(crate) struct WasmContext {
|
||||
promise: Promise,
|
||||
closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
|
||||
}
|
||||
|
||||
impl WasmContext {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
promise: Promise::resolve(&JsValue::undefined()),
|
||||
closure: UninitCell::uninit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
let ctx = Box::leak(Box::new(WasmContext::new()));
|
||||
Self {
|
||||
inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()),
|
||||
ctx,
|
||||
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 [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (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;
|
||||
let future = Closure::new(move |_| {
|
||||
executor.poll();
|
||||
});
|
||||
self.ctx.closure.write_in_place(|| future);
|
||||
init(self.inner.spawner());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+270
@@ -0,0 +1,270 @@
|
||||
#![macro_use]
|
||||
#![allow(unused)]
|
||||
|
||||
use core::fmt::{Debug, Display, LowerHex};
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! todo {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::todo!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::todo!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! panic {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::panic!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::panic!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! trace {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::trace!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::debug!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! info {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::info!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! warn {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::warn!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! error {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::error!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unwrap!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($arg:expr) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
pub trait Try {
|
||||
type Ok;
|
||||
type Error;
|
||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
||||
#![allow(clippy::new_without_default)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! ## Feature flags
|
||||
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
pub use embassy_executor_macros::task;
|
||||
|
||||
macro_rules! check_at_most_one {
|
||||
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
|
||||
#[cfg(any($($res)*))]
|
||||
compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*));
|
||||
};
|
||||
(@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => {
|
||||
check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]);
|
||||
};
|
||||
($($f:literal),*$(,)?) => {
|
||||
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
|
||||
};
|
||||
}
|
||||
check_at_most_one!(
|
||||
"arch-avr",
|
||||
"arch-cortex-m",
|
||||
"arch-riscv32",
|
||||
"arch-std",
|
||||
"arch-wasm",
|
||||
"arch-spin",
|
||||
);
|
||||
|
||||
#[cfg(feature = "_arch")]
|
||||
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
|
||||
#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
|
||||
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
|
||||
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
|
||||
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
|
||||
#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")]
|
||||
mod arch;
|
||||
|
||||
#[cfg(feature = "_arch")]
|
||||
#[allow(unused_imports)] // don't warn if the module is empty.
|
||||
pub use arch::*;
|
||||
|
||||
pub mod raw;
|
||||
|
||||
mod spawner;
|
||||
pub use spawner::*;
|
||||
|
||||
mod config {
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
||||
}
|
||||
|
||||
/// Implementation details for embassy macros.
|
||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
pub mod _export {
|
||||
use core::alloc::Layout;
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
use core::future::Future;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::ptr::null_mut;
|
||||
|
||||
use critical_section::{CriticalSection, Mutex};
|
||||
|
||||
use crate::raw::TaskPool;
|
||||
|
||||
struct Arena<const N: usize> {
|
||||
buf: UnsafeCell<MaybeUninit<[u8; N]>>,
|
||||
ptr: Mutex<Cell<*mut u8>>,
|
||||
}
|
||||
|
||||
unsafe impl<const N: usize> Sync for Arena<N> {}
|
||||
unsafe impl<const N: usize> Send for Arena<N> {}
|
||||
|
||||
impl<const N: usize> Arena<N> {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
buf: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
ptr: Mutex::new(Cell::new(null_mut())),
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
|
||||
let layout = Layout::new::<T>();
|
||||
|
||||
let start = self.buf.get().cast::<u8>();
|
||||
let end = unsafe { start.add(N) };
|
||||
|
||||
let mut ptr = self.ptr.borrow(cs).get();
|
||||
if ptr.is_null() {
|
||||
ptr = self.buf.get().cast::<u8>();
|
||||
}
|
||||
|
||||
let bytes_left = (end as usize) - (ptr as usize);
|
||||
let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
|
||||
|
||||
if align_offset + layout.size() > bytes_left {
|
||||
panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
|
||||
}
|
||||
|
||||
let res = unsafe { ptr.add(align_offset) };
|
||||
let ptr = unsafe { ptr.add(align_offset + layout.size()) };
|
||||
|
||||
self.ptr.borrow(cs).set(ptr);
|
||||
|
||||
unsafe { &mut *(res as *mut MaybeUninit<T>) }
|
||||
}
|
||||
}
|
||||
|
||||
static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new();
|
||||
|
||||
pub struct TaskPoolRef {
|
||||
// type-erased `&'static mut TaskPool<F, N>`
|
||||
// Needed because statics can't have generics.
|
||||
ptr: Mutex<Cell<*mut ()>>,
|
||||
}
|
||||
unsafe impl Sync for TaskPoolRef {}
|
||||
unsafe impl Send for TaskPoolRef {}
|
||||
|
||||
impl TaskPoolRef {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ptr: Mutex::new(Cell::new(null_mut())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pool for this ref, allocating it from the arena the first time.
|
||||
///
|
||||
/// safety: for a given TaskPoolRef instance, must always call with the exact
|
||||
/// same generic params.
|
||||
pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
|
||||
critical_section::with(|cs| {
|
||||
let ptr = self.ptr.borrow(cs);
|
||||
if ptr.get().is_null() {
|
||||
let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
|
||||
pool.write(TaskPool::new());
|
||||
ptr.set(pool as *mut _ as _);
|
||||
}
|
||||
|
||||
unsafe { &*(ptr.get() as *const _) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+610
@@ -0,0 +1,610 @@
|
||||
//! 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](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe.
|
||||
|
||||
#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")]
|
||||
#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
|
||||
mod run_queue;
|
||||
|
||||
#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")]
|
||||
#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")]
|
||||
#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")]
|
||||
mod state;
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
mod timer_queue;
|
||||
pub(crate) mod util;
|
||||
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
|
||||
mod waker;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::ptr::NonNull;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
use embassy_time_driver::AlarmHandle;
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
use rtos_trace::trace;
|
||||
|
||||
use self::run_queue::{RunQueue, RunQueueItem};
|
||||
use self::state::State;
|
||||
use self::util::{SyncUnsafeCell, UninitCell};
|
||||
pub use self::waker::task_from_waker;
|
||||
use super::SpawnToken;
|
||||
|
||||
/// Raw task header for use in task pointers.
|
||||
pub(crate) struct TaskHeader {
|
||||
pub(crate) state: State,
|
||||
pub(crate) run_queue_item: RunQueueItem,
|
||||
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
|
||||
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
pub(crate) expires_at: SyncUnsafeCell<u64>,
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
|
||||
}
|
||||
|
||||
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TaskRef {
|
||||
ptr: NonNull<TaskHeader>,
|
||||
}
|
||||
|
||||
unsafe impl Send for TaskRef where &'static TaskHeader: Send {}
|
||||
unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {}
|
||||
|
||||
impl TaskRef {
|
||||
fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
|
||||
Self {
|
||||
ptr: NonNull::from(task).cast(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: The pointer must have been obtained with `Task::as_ptr`
|
||||
pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self {
|
||||
Self {
|
||||
ptr: NonNull::new_unchecked(ptr as *mut TaskHeader),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn header(self) -> &'static TaskHeader {
|
||||
unsafe { self.ptr.as_ref() }
|
||||
}
|
||||
|
||||
/// The returned pointer is valid for the entire TaskStorage.
|
||||
pub(crate) fn as_ptr(self) -> *const TaskHeader {
|
||||
self.ptr.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_executor_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<F: Future + 'static> {
|
||||
raw: TaskHeader,
|
||||
future: UninitCell<F>, // Valid if STATE_SPAWNED
|
||||
}
|
||||
|
||||
impl<F: Future + 'static> TaskStorage<F> {
|
||||
const NEW: Self = Self::new();
|
||||
|
||||
/// Create a new TaskStorage, in not-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
raw: TaskHeader {
|
||||
state: State::new(),
|
||||
run_queue_item: RunQueueItem::new(),
|
||||
executor: SyncUnsafeCell::new(None),
|
||||
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
|
||||
poll_fn: SyncUnsafeCell::new(None),
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
expires_at: SyncUnsafeCell::new(0),
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
timer_queue_item: timer_queue::TimerQueueItem::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<impl Sized> {
|
||||
let task = AvailableTask::claim(self);
|
||||
match task {
|
||||
Some(task) => task.initialize(future),
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn poll(p: TaskRef) {
|
||||
let this = &*(p.as_ptr() as *const TaskStorage<F>);
|
||||
|
||||
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.despawn();
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
this.raw.expires_at.set(u64::MAX);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(dead_code)]
|
||||
fn _assert_sync(self) {
|
||||
fn assert_sync<T: Sync>(_: T) {}
|
||||
|
||||
assert_sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An uninitialized [`TaskStorage`].
|
||||
pub struct AvailableTask<F: Future + 'static> {
|
||||
task: &'static TaskStorage<F>,
|
||||
}
|
||||
|
||||
impl<F: Future + 'static> AvailableTask<F> {
|
||||
/// Try to claim a [`TaskStorage`].
|
||||
///
|
||||
/// This function returns `None` if a task has already been spawned and has not finished running.
|
||||
pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
|
||||
task.raw.state.spawn().then(|| Self { task })
|
||||
}
|
||||
|
||||
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
|
||||
unsafe {
|
||||
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
|
||||
self.task.future.write_in_place(future);
|
||||
|
||||
let task = TaskRef::new(self.task);
|
||||
|
||||
SpawnToken::new(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the [`TaskStorage`] to run the given future.
|
||||
pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken<F> {
|
||||
self.initialize_impl::<F>(future)
|
||||
}
|
||||
|
||||
/// Initialize the [`TaskStorage`] to run the given future.
|
||||
///
|
||||
/// # 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 __initialize_async_fn<FutFn>(self, future: impl FnOnce() -> F) -> SpawnToken<FutFn> {
|
||||
// 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<FutFn>` 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<F>`.
|
||||
self.initialize_impl::<FutFn>(future)
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage that can hold up to N tasks of the same type.
|
||||
///
|
||||
/// This is essentially a `[TaskStorage<F>; N]`.
|
||||
pub struct TaskPool<F: Future + 'static, const N: usize> {
|
||||
pool: [TaskStorage<F>; N],
|
||||
}
|
||||
|
||||
impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
|
||||
/// Create a new TaskPool, with all tasks in non-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pool: [TaskStorage::NEW; N],
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> SpawnToken<T> {
|
||||
match self.pool.iter().find_map(AvailableTask::claim) {
|
||||
Some(task) => task.initialize_impl::<T>(future),
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<impl Sized> {
|
||||
self.spawn_impl::<F>(future)
|
||||
}
|
||||
|
||||
/// 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<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized>
|
||||
where
|
||||
FutFn: FnOnce() -> F,
|
||||
{
|
||||
// See the comment in AvailableTask::__initialize_async_fn for explanation.
|
||||
self.spawn_impl::<FutFn>(future)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Pender(*mut ());
|
||||
|
||||
unsafe impl Send for Pender {}
|
||||
unsafe impl Sync for Pender {}
|
||||
|
||||
impl Pender {
|
||||
pub(crate) fn pend(self) {
|
||||
extern "Rust" {
|
||||
fn __pender(context: *mut ());
|
||||
}
|
||||
unsafe { __pender(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SyncExecutor {
|
||||
run_queue: RunQueue,
|
||||
pender: Pender,
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
pub(crate) timer_queue: timer_queue::TimerQueue,
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
alarm: AlarmHandle,
|
||||
}
|
||||
|
||||
impl SyncExecutor {
|
||||
pub(crate) fn new(pender: Pender) -> Self {
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
let alarm = unsafe { unwrap!(embassy_time_driver::allocate_alarm()) };
|
||||
|
||||
Self {
|
||||
run_queue: RunQueue::new(),
|
||||
pender,
|
||||
|
||||
#[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, task: TaskRef) {
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
trace::task_ready_begin(task.as_ptr() as u32);
|
||||
|
||||
if self.run_queue.enqueue(task) {
|
||||
self.pender.pend();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
fn alarm_callback(ctx: *mut ()) {
|
||||
let this: &Self = unsafe { &*(ctx as *const Self) };
|
||||
this.pender.pend();
|
||||
}
|
||||
|
||||
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
|
||||
task.header().executor.set(Some(self));
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
trace::task_new(task.as_ptr() as u32);
|
||||
|
||||
self.enqueue(task);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
|
||||
pub(crate) unsafe fn poll(&'static self) {
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
embassy_time_driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ());
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
loop {
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
self.timer_queue
|
||||
.dequeue_expired(embassy_time_driver::now(), wake_task_no_pend);
|
||||
|
||||
self.run_queue.dequeue_all(|p| {
|
||||
let task = p.header();
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
task.expires_at.set(u64::MAX);
|
||||
|
||||
if !task.state.run_dequeue() {
|
||||
// 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;
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
trace::task_exec_begin(p.as_ptr() as u32);
|
||||
|
||||
// Run the task
|
||||
task.poll_fn.get().unwrap_unchecked()(p);
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
trace::task_exec_end();
|
||||
|
||||
// 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 might return false
|
||||
// In that case do another poll loop iteration.
|
||||
let next_expiration = self.timer_queue.next_expiration();
|
||||
if embassy_time_driver::set_alarm(self.alarm, next_expiration) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "integrated-timers"))]
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
trace::system_idle();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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](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 pender function, as shown below. 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.
|
||||
/// - Enabling `arch-xx` features will define a pender function for you. This means that you
|
||||
/// are limited to using the executors provided to you by the architecture/platform
|
||||
/// implementation. If you need a different executor, you must not enable `arch-xx` features.
|
||||
///
|
||||
/// The pender 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 the pender callback, as this violates
|
||||
/// the requirement for `poll` to not be called reentrantly.
|
||||
///
|
||||
/// The pender function must be exported with the name `__pender` and have the following signature:
|
||||
///
|
||||
/// ```rust
|
||||
/// #[export_name = "__pender"]
|
||||
/// fn pender(context: *mut ()) {
|
||||
/// // schedule `poll()` to be called
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `context` argument is a piece of arbitrary data the executor will pass to the pender.
|
||||
/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example,
|
||||
/// differentiate between executors, or to pass a pointer to a callback that should be called.
|
||||
#[repr(transparent)]
|
||||
pub struct Executor {
|
||||
pub(crate) inner: SyncExecutor,
|
||||
|
||||
_not_sync: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self {
|
||||
mem::transmute(inner)
|
||||
}
|
||||
|
||||
/// Create a new executor.
|
||||
///
|
||||
/// When the executor has work to do, it will call the pender function and pass `context` to it.
|
||||
///
|
||||
/// See [`Executor`] docs for details on the pender.
|
||||
pub fn new(context: *mut ()) -> Self {
|
||||
Self {
|
||||
inner: SyncExecutor::new(Pender(context)),
|
||||
_not_sync: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: TaskRef) {
|
||||
self.inner.spawn(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 the pender. It is OK
|
||||
/// to call `poll` even when not requested by the pender, but it wastes
|
||||
/// energy.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must NOT call `poll` reentrantly on the same executor.
|
||||
///
|
||||
/// In particular, note that `poll` may call the pender synchronously. Therefore, you
|
||||
/// must NOT directly call `poll()` from the pender callback. Instead, the callback 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) {
|
||||
self.inner.poll()
|
||||
}
|
||||
|
||||
/// 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 `TaskRef`.
|
||||
///
|
||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||
pub fn wake_task(task: TaskRef) {
|
||||
let header = task.header();
|
||||
if header.state.run_enqueue() {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
let executor = header.executor.get().unwrap_unchecked();
|
||||
executor.enqueue(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake a task by `TaskRef` without calling pend.
|
||||
///
|
||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||
pub fn wake_task_no_pend(task: TaskRef) {
|
||||
let header = task.header();
|
||||
if header.state.run_enqueue() {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
let executor = header.executor.get().unwrap_unchecked();
|
||||
executor.run_queue.enqueue(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
struct TimerQueue;
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
impl embassy_time_queue_driver::TimerQueue for TimerQueue {
|
||||
fn schedule_wake(&'static self, at: u64, waker: &core::task::Waker) {
|
||||
let task = waker::task_from_waker(waker);
|
||||
let task = task.header();
|
||||
unsafe {
|
||||
let expires_at = task.expires_at.get();
|
||||
task.expires_at.set(expires_at.min(at));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
embassy_time_queue_driver::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue);
|
||||
|
||||
#[cfg(all(feature = "rtos-trace", feature = "integrated-timers"))]
|
||||
const fn gcd(a: u64, b: u64) -> u64 {
|
||||
if b == 0 {
|
||||
a
|
||||
} else {
|
||||
gcd(b, a % b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
impl rtos_trace::RtosTraceOSCallbacks for Executor {
|
||||
fn task_list() {
|
||||
// We don't know what tasks exist, so we can't send them.
|
||||
}
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
fn time() -> u64 {
|
||||
const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000);
|
||||
embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M)
|
||||
}
|
||||
#[cfg(not(feature = "integrated-timers"))]
|
||||
fn time() -> u64 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::global_os_callbacks! {Executor}
|
||||
@@ -0,0 +1,87 @@
|
||||
use core::ptr;
|
||||
use core::ptr::NonNull;
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
use super::{TaskHeader, TaskRef};
|
||||
use crate::raw::util::SyncUnsafeCell;
|
||||
|
||||
pub(crate) struct RunQueueItem {
|
||||
next: SyncUnsafeCell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl RunQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: SyncUnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<TaskHeader>,
|
||||
}
|
||||
|
||||
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, task: TaskRef) -> bool {
|
||||
let mut was_empty = false;
|
||||
|
||||
self.head
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
|
||||
was_empty = prev.is_null();
|
||||
unsafe {
|
||||
// safety: the pointer is either null or valid
|
||||
let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
task.header().run_queue_item.next.set(prev);
|
||||
}
|
||||
Some(task.as_ptr() as *mut _)
|
||||
})
|
||||
.ok();
|
||||
|
||||
was_empty
|
||||
}
|
||||
|
||||
/// 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(TaskRef)) {
|
||||
// Atomically empty the queue.
|
||||
let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
|
||||
// safety: the pointer is either null or valid
|
||||
let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
|
||||
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = next {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
next = unsafe { task.header().run_queue_item.next.get() };
|
||||
|
||||
on_task(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
use critical_section::{CriticalSection, Mutex};
|
||||
|
||||
use super::TaskRef;
|
||||
|
||||
pub(crate) struct RunQueueItem {
|
||||
next: Mutex<Cell<Option<TaskRef>>>,
|
||||
}
|
||||
|
||||
impl RunQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: Mutex::new(Cell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: Mutex<Cell<Option<TaskRef>>>,
|
||||
}
|
||||
|
||||
impl RunQueue {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
head: Mutex::new(Cell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, task: TaskRef) -> bool {
|
||||
critical_section::with(|cs| {
|
||||
let prev = self.head.borrow(cs).replace(Some(task));
|
||||
task.header().run_queue_item.next.borrow(cs).set(prev);
|
||||
|
||||
prev.is_none()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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(TaskRef)) {
|
||||
// Atomically empty the queue.
|
||||
let mut next = critical_section::with(|cs| self.head.borrow(cs).take());
|
||||
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = next {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
|
||||
// safety: we know if the task is enqueued, no one else will touch the `next` pointer.
|
||||
let cs = unsafe { CriticalSection::new() };
|
||||
next = task.header().run_queue_item.next.borrow(cs).get();
|
||||
|
||||
on_task(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// 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;
|
||||
|
||||
pub(crate) struct State {
|
||||
state: AtomicU32,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> State {
|
||||
Self {
|
||||
state: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||
#[inline(always)]
|
||||
pub fn spawn(&self) -> bool {
|
||||
self.state
|
||||
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Unmark the task as spawned.
|
||||
#[inline(always)]
|
||||
pub fn despawn(&self) {
|
||||
self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
|
||||
}
|
||||
|
||||
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
|
||||
#[inline(always)]
|
||||
pub fn run_enqueue(&self) -> bool {
|
||||
self.state
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
|
||||
// If already scheduled, or if not started,
|
||||
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
|
||||
None
|
||||
} else {
|
||||
// Mark it as scheduled
|
||||
Some(state | STATE_RUN_QUEUED)
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||
#[inline(always)]
|
||||
pub fn run_dequeue(&self) -> bool {
|
||||
let state = self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
|
||||
state & STATE_SPAWNED != 0
|
||||
}
|
||||
|
||||
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
#[inline(always)]
|
||||
pub fn timer_enqueue(&self) -> bool {
|
||||
let old_state = self.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
|
||||
old_state & STATE_TIMER_QUEUED == 0
|
||||
}
|
||||
|
||||
/// Unmark the task as timer-queued.
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
#[inline(always)]
|
||||
pub fn timer_dequeue(&self) {
|
||||
self.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use core::arch::asm;
|
||||
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
// Must be kept in sync with the layout of `State`!
|
||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
pub(crate) struct State {
|
||||
/// Task is spawned (has a future)
|
||||
spawned: AtomicBool,
|
||||
/// Task is in the executor run queue
|
||||
run_queued: AtomicBool,
|
||||
/// Task is in the executor timer queue
|
||||
timer_queued: AtomicBool,
|
||||
pad: AtomicBool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> State {
|
||||
Self {
|
||||
spawned: AtomicBool::new(false),
|
||||
run_queued: AtomicBool::new(false),
|
||||
timer_queued: AtomicBool::new(false),
|
||||
pad: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_u32(&self) -> &AtomicU32 {
|
||||
unsafe { &*(self as *const _ as *const AtomicU32) }
|
||||
}
|
||||
|
||||
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||
#[inline(always)]
|
||||
pub fn spawn(&self) -> bool {
|
||||
compiler_fence(Ordering::Release);
|
||||
let r = self
|
||||
.as_u32()
|
||||
.compare_exchange(
|
||||
0,
|
||||
STATE_SPAWNED | STATE_RUN_QUEUED,
|
||||
Ordering::Relaxed,
|
||||
Ordering::Relaxed,
|
||||
)
|
||||
.is_ok();
|
||||
compiler_fence(Ordering::Acquire);
|
||||
r
|
||||
}
|
||||
|
||||
/// Unmark the task as spawned.
|
||||
#[inline(always)]
|
||||
pub fn despawn(&self) {
|
||||
compiler_fence(Ordering::Release);
|
||||
self.spawned.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
|
||||
#[inline(always)]
|
||||
pub fn run_enqueue(&self) -> bool {
|
||||
unsafe {
|
||||
loop {
|
||||
let state: u32;
|
||||
asm!("ldrex {}, [{}]", out(reg) state, in(reg) self, options(nostack));
|
||||
|
||||
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
|
||||
asm!("clrex", options(nomem, nostack));
|
||||
return false;
|
||||
}
|
||||
|
||||
let outcome: usize;
|
||||
let new_state = state | STATE_RUN_QUEUED;
|
||||
asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack));
|
||||
if outcome == 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||
#[inline(always)]
|
||||
pub fn run_dequeue(&self) -> bool {
|
||||
compiler_fence(Ordering::Release);
|
||||
|
||||
let r = self.spawned.load(Ordering::Relaxed);
|
||||
self.run_queued.store(false, Ordering::Relaxed);
|
||||
r
|
||||
}
|
||||
|
||||
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
#[inline(always)]
|
||||
pub fn timer_enqueue(&self) -> bool {
|
||||
!self.timer_queued.swap(true, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Unmark the task as timer-queued.
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
#[inline(always)]
|
||||
pub fn timer_dequeue(&self) {
|
||||
self.timer_queued.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
use critical_section::Mutex;
|
||||
|
||||
/// 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;
|
||||
|
||||
pub(crate) struct State {
|
||||
state: Mutex<Cell<u32>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> State {
|
||||
Self {
|
||||
state: Mutex::new(Cell::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
|
||||
critical_section::with(|cs| {
|
||||
let s = self.state.borrow(cs);
|
||||
let mut val = s.get();
|
||||
let r = f(&mut val);
|
||||
s.set(val);
|
||||
r
|
||||
})
|
||||
}
|
||||
|
||||
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||
#[inline(always)]
|
||||
pub fn spawn(&self) -> bool {
|
||||
self.update(|s| {
|
||||
if *s == 0 {
|
||||
*s = STATE_SPAWNED | STATE_RUN_QUEUED;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Unmark the task as spawned.
|
||||
#[inline(always)]
|
||||
pub fn despawn(&self) {
|
||||
self.update(|s| *s &= !STATE_SPAWNED);
|
||||
}
|
||||
|
||||
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
|
||||
#[inline(always)]
|
||||
pub fn run_enqueue(&self) -> bool {
|
||||
self.update(|s| {
|
||||
if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) {
|
||||
false
|
||||
} else {
|
||||
*s |= STATE_RUN_QUEUED;
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||
#[inline(always)]
|
||||
pub fn run_dequeue(&self) -> bool {
|
||||
self.update(|s| {
|
||||
let ok = *s & STATE_SPAWNED != 0;
|
||||
*s &= !STATE_RUN_QUEUED;
|
||||
ok
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
#[inline(always)]
|
||||
pub fn timer_enqueue(&self) -> bool {
|
||||
self.update(|s| {
|
||||
let ok = *s & STATE_TIMER_QUEUED == 0;
|
||||
*s |= STATE_TIMER_QUEUED;
|
||||
ok
|
||||
})
|
||||
}
|
||||
|
||||
/// Unmark the task as timer-queued.
|
||||
#[cfg(feature = "integrated-timers")]
|
||||
#[inline(always)]
|
||||
pub fn timer_dequeue(&self) {
|
||||
self.update(|s| *s &= !STATE_TIMER_QUEUED);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
use core::cmp::min;
|
||||
|
||||
use super::TaskRef;
|
||||
use crate::raw::util::SyncUnsafeCell;
|
||||
|
||||
pub(crate) struct TimerQueueItem {
|
||||
next: SyncUnsafeCell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl TimerQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: SyncUnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TimerQueue {
|
||||
head: SyncUnsafeCell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl TimerQueue {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
head: SyncUnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn update(&self, p: TaskRef) {
|
||||
let task = p.header();
|
||||
if task.expires_at.get() != u64::MAX {
|
||||
if task.state.timer_enqueue() {
|
||||
task.timer_queue_item.next.set(self.head.get());
|
||||
self.head.set(Some(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn next_expiration(&self) -> u64 {
|
||||
let mut res = u64::MAX;
|
||||
self.retain(|p| {
|
||||
let task = p.header();
|
||||
let expires = task.expires_at.get();
|
||||
res = min(res, expires);
|
||||
expires != u64::MAX
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn dequeue_expired(&self, now: u64, on_task: impl Fn(TaskRef)) {
|
||||
self.retain(|p| {
|
||||
let task = p.header();
|
||||
if task.expires_at.get() <= now {
|
||||
on_task(p);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
|
||||
let mut prev = &self.head;
|
||||
while let Some(p) = prev.get() {
|
||||
let task = p.header();
|
||||
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.timer_dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
use core::cell::UnsafeCell;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::ptr;
|
||||
|
||||
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
||||
impl<T> UninitCell<T> {
|
||||
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()
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub unsafe fn write_in_place(&self, func: impl FnOnce() -> T) {
|
||||
ptr::write(self.as_mut_ptr(), func())
|
||||
}
|
||||
|
||||
pub unsafe fn drop_in_place(&self) {
|
||||
ptr::drop_in_place(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for UninitCell<T> {}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct SyncUnsafeCell<T> {
|
||||
value: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {}
|
||||
|
||||
impl<T> SyncUnsafeCell<T> {
|
||||
#[inline]
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: UnsafeCell::new(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set(&self, value: T) {
|
||||
*self.value.get() = value;
|
||||
}
|
||||
|
||||
pub unsafe fn get(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
*self.value.get()
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use core::task::{RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use super::{wake_task, TaskHeader, TaskRef};
|
||||
|
||||
static 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(TaskRef::from_ptr(p as *const TaskHeader))
|
||||
}
|
||||
|
||||
unsafe fn drop(_: *const ()) {
|
||||
// nop
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_task(p: TaskRef) -> 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) -> TaskRef {
|
||||
struct WakerHack {
|
||||
data: *const (),
|
||||
vtable: &'static RawWakerVTable,
|
||||
}
|
||||
|
||||
// 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 { core::mem::transmute(waker) };
|
||||
|
||||
// make sure to compare vtable addresses. Doing `==` on the references
|
||||
// will compare the contents, which is slower.
|
||||
if hack.vtable as *const _ != &VTABLE as *const _ {
|
||||
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
|
||||
}
|
||||
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||
unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
use core::ptr::NonNull;
|
||||
use core::task::Waker;
|
||||
|
||||
use super::{wake_task, TaskHeader, TaskRef};
|
||||
|
||||
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
|
||||
Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _))
|
||||
}
|
||||
|
||||
/// 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) -> TaskRef {
|
||||
let ptr = waker.as_turbo_ptr().as_ptr();
|
||||
|
||||
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||
unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) }
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[no_mangle]
|
||||
fn _turbo_wake(ptr: NonNull<()>) {
|
||||
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||
let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) };
|
||||
wake_task(task)
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
use core::future::poll_fn;
|
||||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::task::Poll;
|
||||
|
||||
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<S> {
|
||||
raw_task: Option<raw::TaskRef>,
|
||||
phantom: PhantomData<*mut S>,
|
||||
}
|
||||
|
||||
impl<S> SpawnToken<S> {
|
||||
pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
|
||||
Self {
|
||||
raw_task: Some(raw_task),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a SpawnToken that represents a failed spawn.
|
||||
pub fn new_failed() -> Self {
|
||||
Self {
|
||||
raw_task: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Drop for SpawnToken<S> {
|
||||
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| {
|
||||
let task = raw::task_from_waker(cx.waker());
|
||||
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
|
||||
let executor = unsafe { raw::Executor::wrap(executor) };
|
||||
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<S>(&self, token: SpawnToken<S>) -> 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_executor_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_executor_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<S>(&self, token: SpawnToken<S>) {
|
||||
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::new(&self.executor.inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::SyncExecutor,
|
||||
}
|
||||
|
||||
impl SendSpawner {
|
||||
pub(crate) fn new(executor: &'static raw::SyncExecutor) -> 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| {
|
||||
let task = raw::task_from_waker(cx.waker());
|
||||
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
|
||||
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<S: Send>(&self, token: SpawnToken<S>) -> 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<S: Send>(&self, token: SpawnToken<S>) {
|
||||
unwrap!(self.spawn(token));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user