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
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"5d44db04d55615d3bdf98281a42b65b62fdf1525e1403af5be8095828e8aec98","CHANGELOG.md":"b265deabaf9b677930b65d1ff9fc58945d2e0fd0459d9a83674536823ac9eb23","Cargo.toml":"a1f47686bacc29aa96c084cc6c42caa2f427c93d82d2852e375ccb0c68a959aa","Cargo.toml.orig":"d6dbba387ce182ae17c9bf7d571ac15f59fa8aaa238db80819f3ea4359b30eb3","README.md":"8211a20de9fb9cc55b221aeb5a3e102e279550c1dcf85b80056c7a75f356dc0f","build.rs":"00e904e1600f49606b22d9c63694e22874f53364a337b0c20648fe8fd490ba6c","build_common.rs":"52ecc82206a6932c94fabc62ff3e11b41ae0564d021c4ed5eb15a36a57def317","gen_config.py":"e79fac50bdea79ca6de04b5b7becca01442a2bc5eb1961c75d3250f61758dd0c","src/arch/avr.rs":"b882b377491af6e266cc6b814067aad9002ab3918dadc0e19858fd175a222020","src/arch/cortex_m.rs":"5827d7266826c42968d5f0e622fde53a80e1938abe56eedf2bb85f882e578ead","src/arch/riscv32.rs":"16a3a8e5bf7844f455f2171710e48e74809d56768f8194a48430d4cb1f2ffaa8","src/arch/spin.rs":"89a0e60c2937851ade43e63c5133a2aee75098a01a5017420f7c7df1d25de90e","src/arch/std.rs":"3f4f91d930032f7044b3ba2a3eb62080c8ea90a0cc01754440fd781b51072ad4","src/arch/wasm.rs":"70abf47609b90fde957eb92b33fc7b39be0b3fcdf30efd81e9f2772030a944bc","src/fmt.rs":"29efbba075d0bcf8acd4c3f3228bbe5ab3be735c48d3894c86a957d15bab0f12","src/lib.rs":"947f73fe1dd93282a70a76fd0615f5166292ae748c4c18ddcf420f415c47a964","src/raw/mod.rs":"cd7625286b987fe615a4d0003221d73ac099b340fd3dab73ba6ac9438bab4d9e","src/raw/run_queue_atomics.rs":"2af261de90e5787ab451adc10372b79136b95930ce25feffb3a2ce888e0659cd","src/raw/run_queue_critical_section.rs":"51765b5d989929e431902f8b4527bbe1c8181ea104f6c42312ae3ef72f2ea669","src/raw/state_atomics.rs":"e942f1bf3632dffa721d3218200a2abf8432a3561e2ba549061f7d07a4890347","src/raw/state_atomics_arm.rs":"88f10c4bd0c9ce8f53343707df0c45d7341453a0cf0245a9adc8b92703143533","src/raw/state_critical_section.rs":"c6c89f6ccd335cf4e6d458b45702ddd07026f9c6be95996036920a79a5a8a909","src/raw/timer_queue.rs":"a702a9fb529737f01217b09805f5b9c18d0505598ba47a5f6df982b712159e6c","src/raw/util.rs":"e0aca4a6c22ec7971549edc59a81a946a20678a5215092aa95be1c92f562b77d","src/raw/waker.rs":"5e0c23d2d235756d92b87e22b5e7e4d205b0ca459f3ea7927365545fbff5c2d3","src/raw/waker_turbo.rs":"4719bb44561104c6c0a06b3772b515e6b57580fa2e40c04a8fe354c79b02b684","src/spawner.rs":"bce1aed709e0c3e139b04a67b35cf3fb54922c486ce79100a382336e4a673dcd","tests/test.rs":"57add2b7407ba0f28bcf9d9ebc0f8f84e6556987745868d238b4e6bc0bf82825","tests/ui.rs":"89715b622a720545be6a9ba8df9868d76029bba04ffd039ade178c59ab6955f8","tests/ui/abi.rs":"479358b81fac755c0c9e478a357c5f9ca1d0e855c98c89c8f171d65cb023e4fe","tests/ui/abi.stderr":"d9ca675d049e90b21ac2976a446db670abd33953462bb4502608209118954e97","tests/ui/bad_return.rs":"9569544695e3ec5a81434407ba27e2b175c19cec356771219dee72b41a036de6","tests/ui/bad_return.stderr":"55ec589a47c4a86bf1cc2ebe6f729cace2eee52e79c506891b80e75fc13e0d5c","tests/ui/generics.rs":"6b3b1b86ce7fd7f99ccb96245cc0bf4c2f906661cd4749565393f3a87bb563fe","tests/ui/generics.stderr":"ed0ef8a520706f209ed1c0f0295a6a71f208fb0ac3d61066ae94de9dbae72413","tests/ui/impl_trait.rs":"9eb483953b0f33e48ebe5ad74a6c2fb131f52f141ed5b665ac1c50656f17a4e5","tests/ui/impl_trait.stderr":"c9d2e0cf112746ffafcb5cfa7578de0a63dbedfa4dd8e165c888fdaa95980141","tests/ui/impl_trait_nested.rs":"43c5274220bbe079d580701348ed80277c3d1e1d7ab3ea9d6555ecbbda2e5763","tests/ui/impl_trait_nested.stderr":"780431f7d057851be85a346af6ba5bcd83fc337b4217c0b35291df1a7aef8fdf","tests/ui/impl_trait_static.rs":"9f1da37f93a509801bce7fb54a7da88bddcd5577e18de20ddd5d8b92a24093f5","tests/ui/impl_trait_static.stderr":"5485a0e5b8685537f420585e00acb964ba7227cfa83bf786fd6fe2dfe51f34d5","tests/ui/nonstatic_ref_anon.rs":"ea83bcbec364f9382358f1c95dc1964ea020798fdb13a1540162fa9f18063f24","tests/ui/nonstatic_ref_anon.stderr":"f6524d4e6804135cb2470e8159c44c4245ac450e2041a5182280ba472b745757","tests/ui/nonstatic_ref_anon_nested.rs":"852e30053399c87e23dfb33aed34872007c9da3ee0f272ea4c818bff4d28099b","tests/ui/nonstatic_ref_anon_nested.stderr":"8ef438a4f7afb65035f34fd95945a36c87250da14e02a973287ffc4c88227a52","tests/ui/nonstatic_ref_elided.rs":"b04c9cf8af6e282c650acbdda9a4c57e14bd56bb1aab255957586a9e35264276","tests/ui/nonstatic_ref_elided.stderr":"538842c8489e38cc6558bc4c5795c9158ea64cc184a2fc65959bec29181c71c2","tests/ui/nonstatic_ref_generic.rs":"543d2e26aa68c10fdf852696fa93b60040a0dbccf0abdb1f2a5aa33026e853e1","tests/ui/nonstatic_ref_generic.stderr":"b44beaf0b8cf7cc1fd783ad9f61e77e468f30d854cffa06d7c0dcfb368f28a00","tests/ui/nonstatic_struct_anon.rs":"07a89f4b10f1013d8c2dea07af3cf60ba89280bcf224398232406c7435676675","tests/ui/nonstatic_struct_anon.stderr":"fe509a62bdc355f13779ec697939291069b120bde5c21e9efa6a0a5ae698f76a","tests/ui/nonstatic_struct_elided.rs":"bc35a20fae5e07e25a7fc20d558278454b370292dc864256ed10d3e573912b84","tests/ui/nonstatic_struct_elided.stderr":"75bc3959d946c8dbf1b75fa9c6115b9746cc6f958e79ee467446914d0c20b61d","tests/ui/nonstatic_struct_generic.rs":"200c08361063561a9Line truncated
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "015e3aecb410c2d5afefb908b4d4171d5b478be3"
|
||||
},
|
||||
"path_in_vcs": "embassy-executor"
|
||||
}
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.6.3 - 2024-11-12
|
||||
|
||||
- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82.
|
||||
- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m.
|
||||
|
||||
## 0.6.2 - 2024-11-06
|
||||
|
||||
- The `nightly` feature no longer requires `nightly-2024-09-06` or newer.
|
||||
|
||||
## 0.6.1 - 2024-10-21
|
||||
|
||||
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
|
||||
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
|
||||
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
|
||||
- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature.
|
||||
- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer.
|
||||
- Improve macro error messages.
|
||||
|
||||
## 0.6.0 - 2024-08-05
|
||||
|
||||
- Add collapse_debuginfo to fmt.rs macros.
|
||||
- initial support for AVR
|
||||
- use nightly waker_getters APIs
|
||||
|
||||
## 0.5.1 - 2024-10-21
|
||||
|
||||
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
|
||||
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
|
||||
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
|
||||
|
||||
## 0.5.0 - 2024-01-11
|
||||
|
||||
- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher.
|
||||
|
||||
## 0.4.0 - 2023-12-05
|
||||
|
||||
- Removed `arch-xtensa`. Use the executor provided by the HAL crate (`esp-hal`, `esp32s3-hal`, etc...) instead.
|
||||
- Added an arena allocator for tasks, allowing using the `main` and `task` macros on Rust 1.75 stable. (it is only used if the `nightly` feature is not enabled. When `nightly` is enabled, `type_alias_impl_trait` is used to statically allocate tasks, as before).
|
||||
|
||||
## 0.3.3 - 2023-11-15
|
||||
|
||||
- Add `main` macro reexport for Xtensa arch.
|
||||
- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't.
|
||||
|
||||
## 0.3.2 - 2023-11-06
|
||||
|
||||
- Use `atomic-polyfill` for `riscv32`
|
||||
- Removed unused dependencies (static_cell, futures-util)
|
||||
|
||||
## 0.3.1 - 2023-11-01
|
||||
|
||||
- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies.
|
||||
|
||||
## 0.3.0 - 2023-08-25
|
||||
|
||||
- Replaced Pender. Implementations now must define an extern function called `__pender`.
|
||||
- Made `raw::AvailableTask` public
|
||||
- Made `SpawnToken::new_failed` public
|
||||
- You can now use arbitrary expressions to specify `#[task(pool_size = X)]`
|
||||
|
||||
## 0.2.1 - 2023-08-10
|
||||
|
||||
- Avoid calling `pend()` when waking expired timers
|
||||
- Properly reset finished task state with `integrated-timers` enabled
|
||||
- Introduce `InterruptExecutor::spawner()`
|
||||
- Fix incorrect critical section in Xtensa executor
|
||||
|
||||
## 0.2.0 - 2023-04-27
|
||||
|
||||
- Replace unnecessary atomics in runqueue
|
||||
- add Pender, rework Cargo features.
|
||||
- add support for turbo-wakers.
|
||||
- Allow TaskStorage to auto-implement `Sync`
|
||||
- Use AtomicPtr for signal_ctx, removes 1 unsafe.
|
||||
- Replace unsound critical sections with atomics
|
||||
|
||||
## 0.1.1 - 2022-11-23
|
||||
|
||||
- Fix features for documentation
|
||||
|
||||
## 0.1.0 - 2022-11-23
|
||||
|
||||
- First release
|
||||
Vendored
+218
@@ -0,0 +1,218 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "embassy-executor"
|
||||
version = "0.6.3"
|
||||
build = "build.rs"
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "async/await executor designed for embedded usage"
|
||||
documentation = "https://docs.embassy.dev/embassy-executor"
|
||||
readme = "README.md"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv7em-none-eabi"
|
||||
features = [
|
||||
"defmt",
|
||||
"arch-cortex-m",
|
||||
"executor-thread",
|
||||
"executor-interrupt",
|
||||
]
|
||||
targets = ["thumbv7em-none-eabi"]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
features = ["defmt"]
|
||||
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/"
|
||||
|
||||
[[package.metadata.embassy_docs.flavors]]
|
||||
features = [
|
||||
"arch-std",
|
||||
"executor-thread",
|
||||
]
|
||||
name = "std"
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[[package.metadata.embassy_docs.flavors]]
|
||||
features = [
|
||||
"arch-wasm",
|
||||
"executor-thread",
|
||||
]
|
||||
name = "wasm"
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
[[package.metadata.embassy_docs.flavors]]
|
||||
features = [
|
||||
"arch-cortex-m",
|
||||
"executor-thread",
|
||||
"executor-interrupt",
|
||||
]
|
||||
name = "cortex-m"
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[[package.metadata.embassy_docs.flavors]]
|
||||
features = [
|
||||
"arch-riscv32",
|
||||
"executor-thread",
|
||||
]
|
||||
name = "riscv32"
|
||||
target = "riscv32imac-unknown-none-elf"
|
||||
|
||||
[lib]
|
||||
name = "embassy_executor"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[test]]
|
||||
name = "test"
|
||||
path = "tests/test.rs"
|
||||
|
||||
[[test]]
|
||||
name = "ui"
|
||||
path = "tests/ui.rs"
|
||||
|
||||
[dependencies.avr-device]
|
||||
version = "0.5.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.cortex-m]
|
||||
version = "0.7.6"
|
||||
optional = true
|
||||
|
||||
[dependencies.critical-section]
|
||||
version = "1.1"
|
||||
|
||||
[dependencies.defmt]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.document-features]
|
||||
version = "0.2.7"
|
||||
|
||||
[dependencies.embassy-executor-macros]
|
||||
version = "0.6.2"
|
||||
|
||||
[dependencies.embassy-time-driver]
|
||||
version = "0.1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.embassy-time-queue-driver]
|
||||
version = "0.1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.js-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.14"
|
||||
optional = true
|
||||
|
||||
[dependencies.portable-atomic]
|
||||
version = "1.5"
|
||||
optional = true
|
||||
|
||||
[dependencies.rtos-trace]
|
||||
version = "0.1.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.wasm-bindgen]
|
||||
version = "0.2.82"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.critical-section]
|
||||
version = "1.1"
|
||||
features = ["std"]
|
||||
|
||||
[dev-dependencies.trybuild]
|
||||
version = "1.0"
|
||||
|
||||
[features]
|
||||
_arch = []
|
||||
arch-avr = [
|
||||
"_arch",
|
||||
"dep:portable-atomic",
|
||||
"dep:avr-device",
|
||||
]
|
||||
arch-cortex-m = [
|
||||
"_arch",
|
||||
"dep:cortex-m",
|
||||
]
|
||||
arch-riscv32 = ["_arch"]
|
||||
arch-spin = ["_arch"]
|
||||
arch-std = [
|
||||
"_arch",
|
||||
"critical-section/std",
|
||||
]
|
||||
arch-wasm = [
|
||||
"_arch",
|
||||
"dep:wasm-bindgen",
|
||||
"dep:js-sys",
|
||||
"critical-section/std",
|
||||
]
|
||||
executor-interrupt = []
|
||||
executor-thread = []
|
||||
integrated-timers = [
|
||||
"dep:embassy-time-driver",
|
||||
"dep:embassy-time-queue-driver",
|
||||
]
|
||||
nightly = ["embassy-executor-macros/nightly"]
|
||||
task-arena-size-1024 = []
|
||||
task-arena-size-10240 = []
|
||||
task-arena-size-1048576 = []
|
||||
task-arena-size-12288 = []
|
||||
task-arena-size-128 = []
|
||||
task-arena-size-1280 = []
|
||||
task-arena-size-131072 = []
|
||||
task-arena-size-1536 = []
|
||||
task-arena-size-16384 = []
|
||||
task-arena-size-163840 = []
|
||||
task-arena-size-192 = []
|
||||
task-arena-size-196608 = []
|
||||
task-arena-size-2048 = []
|
||||
task-arena-size-20480 = []
|
||||
task-arena-size-24576 = []
|
||||
task-arena-size-256 = []
|
||||
task-arena-size-2560 = []
|
||||
task-arena-size-262144 = []
|
||||
task-arena-size-3072 = []
|
||||
task-arena-size-320 = []
|
||||
task-arena-size-32768 = []
|
||||
task-arena-size-327680 = []
|
||||
task-arena-size-384 = []
|
||||
task-arena-size-393216 = []
|
||||
task-arena-size-4096 = []
|
||||
task-arena-size-40960 = []
|
||||
task-arena-size-49152 = []
|
||||
task-arena-size-512 = []
|
||||
task-arena-size-5120 = []
|
||||
task-arena-size-524288 = []
|
||||
task-arena-size-6144 = []
|
||||
task-arena-size-64 = []
|
||||
task-arena-size-640 = []
|
||||
task-arena-size-65536 = []
|
||||
task-arena-size-655360 = []
|
||||
task-arena-size-768 = []
|
||||
task-arena-size-786432 = []
|
||||
task-arena-size-8192 = []
|
||||
task-arena-size-81920 = []
|
||||
task-arena-size-98304 = []
|
||||
turbowakers = []
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
# embassy-executor
|
||||
|
||||
An async/await executor designed for embedded usage.
|
||||
|
||||
- No `alloc`, no heap needed.
|
||||
- With nightly Rust, task futures can be fully 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_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.
|
||||
|
||||
## Task arena
|
||||
|
||||
When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator).
|
||||
|
||||
If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks.
|
||||
|
||||
Tasks are allocated from the arena when spawned for the first time. If the task exists, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
|
||||
|
||||
The arena size can be configured in two ways:
|
||||
|
||||
- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values
|
||||
is available, see [Task Area Sizes](#task-arena-size) for reference.
|
||||
- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example
|
||||
`EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
|
||||
Any value can be set, unlike with Cargo features.
|
||||
|
||||
Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
|
||||
with different values, compilation fails.
|
||||
|
||||
## Statically allocating tasks
|
||||
|
||||
When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible.
|
||||
|
||||
The configured arena size is ignored, no arena is used at all.
|
||||
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
#[path = "./build_common.rs"]
|
||||
mod common;
|
||||
|
||||
static CONFIGS: &[(&str, usize)] = &[
|
||||
// BEGIN AUTOGENERATED CONFIG FEATURES
|
||||
// Generated by gen_config.py. DO NOT EDIT.
|
||||
("TASK_ARENA_SIZE", 4096),
|
||||
// END AUTOGENERATED CONFIG FEATURES
|
||||
];
|
||||
|
||||
struct ConfigState {
|
||||
value: usize,
|
||||
seen_feature: bool,
|
||||
seen_env: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let crate_name = env::var("CARGO_PKG_NAME")
|
||||
.unwrap()
|
||||
.to_ascii_uppercase()
|
||||
.replace('-', "_");
|
||||
|
||||
// only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
|
||||
// other file changed.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Rebuild if config envvar changed.
|
||||
for (name, _) in CONFIGS {
|
||||
println!("cargo:rerun-if-env-changed={crate_name}_{name}");
|
||||
}
|
||||
|
||||
let mut configs = HashMap::new();
|
||||
for (name, default) in CONFIGS {
|
||||
configs.insert(
|
||||
*name,
|
||||
ConfigState {
|
||||
value: *default,
|
||||
seen_env: false,
|
||||
seen_feature: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let prefix = format!("{crate_name}_");
|
||||
for (var, value) in env::vars() {
|
||||
if let Some(name) = var.strip_prefix(&prefix) {
|
||||
let Some(cfg) = configs.get_mut(name) else {
|
||||
panic!("Unknown env var {name}")
|
||||
};
|
||||
|
||||
let Ok(value) = value.parse::<usize>() else {
|
||||
panic!("Invalid value for env var {name}: {value}")
|
||||
};
|
||||
|
||||
cfg.value = value;
|
||||
cfg.seen_env = true;
|
||||
}
|
||||
|
||||
if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
|
||||
if let Some(i) = feature.rfind('_') {
|
||||
let name = &feature[..i];
|
||||
let value = &feature[i + 1..];
|
||||
if let Some(cfg) = configs.get_mut(name) {
|
||||
let Ok(value) = value.parse::<usize>() else {
|
||||
panic!("Invalid value for feature {name}: {value}")
|
||||
};
|
||||
|
||||
// envvars take priority.
|
||||
if !cfg.seen_env {
|
||||
if cfg.seen_feature {
|
||||
panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
|
||||
}
|
||||
|
||||
cfg.value = value;
|
||||
cfg.seen_feature = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = String::new();
|
||||
|
||||
for (name, cfg) in &configs {
|
||||
writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
|
||||
fs::write(out_file, data).unwrap();
|
||||
|
||||
let mut rustc_cfgs = common::CfgSet::new();
|
||||
common::set_target_cfgs(&mut rustc_cfgs);
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// NOTE: this file is copy-pasted between several Embassy crates, because there is no
|
||||
// straightforward way to share this code:
|
||||
// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path =
|
||||
// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate
|
||||
// reside in the crate's directory,
|
||||
// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because
|
||||
// symlinks don't work on Windows.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
|
||||
/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring
|
||||
/// them (`cargo:rust-check-cfg=cfg(X)`).
|
||||
#[derive(Debug)]
|
||||
pub struct CfgSet {
|
||||
enabled: HashSet<String>,
|
||||
declared: HashSet<String>,
|
||||
}
|
||||
|
||||
impl CfgSet {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: HashSet::new(),
|
||||
declared: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation.
|
||||
///
|
||||
/// All configs that can potentially be enabled should be unconditionally declared using
|
||||
/// [`Self::declare()`].
|
||||
pub fn enable(&mut self, cfg: impl AsRef<str>) {
|
||||
if self.enabled.insert(cfg.as_ref().to_owned()) {
|
||||
println!("cargo:rustc-cfg={}", cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_all(&mut self, cfgs: &[impl AsRef<str>]) {
|
||||
for cfg in cfgs.iter() {
|
||||
self.enable(cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare a valid config for conditional compilation, without enabling it.
|
||||
///
|
||||
/// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid.
|
||||
pub fn declare(&mut self, cfg: impl AsRef<str>) {
|
||||
if self.declared.insert(cfg.as_ref().to_owned()) {
|
||||
println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare_all(&mut self, cfgs: &[impl AsRef<str>]) {
|
||||
for cfg in cfgs.iter() {
|
||||
self.declare(cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, cfg: impl Into<String>, enable: bool) {
|
||||
let cfg = cfg.into();
|
||||
if enable {
|
||||
self.enable(cfg.clone());
|
||||
}
|
||||
self.declare(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets configs that describe the target platform.
|
||||
pub fn set_target_cfgs(cfgs: &mut CfgSet) {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
||||
if target.starts_with("thumbv6m-") {
|
||||
cfgs.enable_all(&["cortex_m", "armv6m"]);
|
||||
} else if target.starts_with("thumbv7m-") {
|
||||
cfgs.enable_all(&["cortex_m", "armv7m"]);
|
||||
} else if target.starts_with("thumbv7em-") {
|
||||
cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]);
|
||||
} else if target.starts_with("thumbv8m.base") {
|
||||
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]);
|
||||
} else if target.starts_with("thumbv8m.main") {
|
||||
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]);
|
||||
}
|
||||
cfgs.declare_all(&[
|
||||
"cortex_m",
|
||||
"armv6m",
|
||||
"armv7m",
|
||||
"armv7em",
|
||||
"armv8m",
|
||||
"armv8m_base",
|
||||
"armv8m_main",
|
||||
]);
|
||||
|
||||
cfgs.set("has_fpu", target.ends_with("-eabihf"));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CompilerDate {
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
}
|
||||
|
||||
impl CompilerDate {
|
||||
fn parse(date: &str) -> Option<Self> {
|
||||
let mut parts = date.split('-');
|
||||
let year = parts.next()?.parse().ok()?;
|
||||
let month = parts.next()?.parse().ok()?;
|
||||
let day = parts.next()?.parse().ok()?;
|
||||
Some(Self { year, month, day })
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for CompilerDate {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
let Some(other) = Self::parse(other) else {
|
||||
return false;
|
||||
};
|
||||
self.eq(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<&str> for CompilerDate {
|
||||
fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
|
||||
Self::parse(other).map(|other| self.cmp(&other))
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
|
||||
abspath = os.path.abspath(__file__)
|
||||
dname = os.path.dirname(abspath)
|
||||
os.chdir(dname)
|
||||
|
||||
features = []
|
||||
|
||||
|
||||
def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]):
|
||||
if vals is None:
|
||||
assert min is not None
|
||||
assert max is not None
|
||||
|
||||
vals = set()
|
||||
val = min
|
||||
while val <= max:
|
||||
vals.add(val)
|
||||
for f in factors:
|
||||
if val * f <= max:
|
||||
vals.add(val * f)
|
||||
if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0:
|
||||
val *= 2
|
||||
else:
|
||||
val += 1
|
||||
vals.add(default)
|
||||
vals = sorted(list(vals))
|
||||
|
||||
features.append(
|
||||
{
|
||||
"name": name,
|
||||
"default": default,
|
||||
"vals": vals,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
feature(
|
||||
"task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5]
|
||||
)
|
||||
|
||||
# ========= Update Cargo.toml
|
||||
|
||||
things = ""
|
||||
for f in features:
|
||||
name = f["name"].replace("_", "-")
|
||||
for val in f["vals"]:
|
||||
things += f"## {val}"
|
||||
if val == f["default"]:
|
||||
things += " (default)\n"
|
||||
else:
|
||||
things += "\n"
|
||||
|
||||
things += f"{name}-{val} = []"
|
||||
if val == f["default"]:
|
||||
things += " # Default"
|
||||
things += "\n"
|
||||
things += "\n"
|
||||
|
||||
SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
|
||||
SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
|
||||
HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
|
||||
with open("Cargo.toml", "r") as f:
|
||||
data = f.read()
|
||||
before, data = data.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = data.split(SEPARATOR_END, maxsplit=1)
|
||||
data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
|
||||
with open("Cargo.toml", "w") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
# ========= Update build.rs
|
||||
|
||||
things = ""
|
||||
for f in features:
|
||||
name = f["name"].upper()
|
||||
things += f' ("{name}", {f["default"]}),\n'
|
||||
|
||||
SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
|
||||
SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
|
||||
HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
|
||||
with open("build.rs", "r") as f:
|
||||
data = f.read()
|
||||
before, data = data.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = data.split(SEPARATOR_END, maxsplit=1)
|
||||
data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
|
||||
with open("build.rs", "w") as f:
|
||||
f.write(data)
|
||||
+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));
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::future::poll_fn;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::Poll;
|
||||
|
||||
use embassy_executor::raw::Executor;
|
||||
use embassy_executor::task;
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
unsafe {
|
||||
let trace = &*(context as *const Trace);
|
||||
trace.push("pend");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Trace {
|
||||
trace: Arc<Mutex<Vec<&'static str>>>,
|
||||
}
|
||||
|
||||
impl Trace {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
trace: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
fn push(&self, value: &'static str) {
|
||||
self.trace.lock().unwrap().push(value)
|
||||
}
|
||||
|
||||
fn get(&self) -> Vec<&'static str> {
|
||||
self.trace.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup() -> (&'static Executor, Trace) {
|
||||
let trace = Trace::new();
|
||||
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
|
||||
let executor = &*Box::leak(Box::new(Executor::new(context)));
|
||||
(executor, trace)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_noop() {
|
||||
let (executor, trace) = setup();
|
||||
unsafe { executor.poll() };
|
||||
assert!(trace.get().is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task() {
|
||||
#[task]
|
||||
async fn task1(trace: Trace) {
|
||||
trace.push("poll task1")
|
||||
}
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone())).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", // poll only once.
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task_self_wake() {
|
||||
#[task]
|
||||
async fn task1(trace: Trace) {
|
||||
poll_fn(|cx| {
|
||||
trace.push("poll task1");
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone())).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task_self_wake_twice() {
|
||||
#[task]
|
||||
async fn task1(trace: Trace) {
|
||||
poll_fn(|cx| {
|
||||
trace.push("poll task1");
|
||||
cx.waker().wake_by_ref();
|
||||
trace.push("poll task1 wake 2");
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone())).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
"poll task1 wake 2", // task self-wakes again, shouldn't pend
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
"poll task1 wake 2", // task self-wakes again, shouldn't pend
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task_cfg_args() {
|
||||
// simulate cfg'ing away argument c
|
||||
#[task]
|
||||
async fn task1(a: u32, b: u32, #[cfg(any())] c: u32) {
|
||||
let (_, _) = (a, b);
|
||||
}
|
||||
|
||||
#[task]
|
||||
async fn task2(a: u32, b: u32, #[cfg(all())] c: u32) {
|
||||
let (_, _, _) = (a, b, c);
|
||||
}
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
#[cfg(not(miri))]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/abi.rs");
|
||||
t.compile_fail("tests/ui/bad_return.rs");
|
||||
t.compile_fail("tests/ui/generics.rs");
|
||||
t.compile_fail("tests/ui/impl_trait_nested.rs");
|
||||
t.compile_fail("tests/ui/impl_trait.rs");
|
||||
t.compile_fail("tests/ui/impl_trait_static.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
|
||||
#[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
|
||||
t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
|
||||
t.compile_fail("tests/ui/not_async.rs");
|
||||
t.compile_fail("tests/ui/self_ref.rs");
|
||||
t.compile_fail("tests/ui/self.rs");
|
||||
t.compile_fail("tests/ui/where_clause.rs");
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async extern "C" fn task() {}
|
||||
|
||||
fn main() {}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
error: task functions must not have an ABI qualifier
|
||||
--> tests/ui/abi.rs:6:1
|
||||
|
|
||||
6 | async extern "C" fn task() {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task() -> u32 {
|
||||
5
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: task functions must either not return a value, return `()` or return `!`
|
||||
--> tests/ui/bad_return.rs:6:1
|
||||
|
|
||||
6 | async fn task() -> u32 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task<T: Sized>(_t: T) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/generics.rs:6:1
|
||||
|
|
||||
6 | async fn task<T: Sized>(_t: T) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: impl Sized) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: impl Sized) {}
|
||||
| ^^^^^^^^^^
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<T>(T);
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: Foo<impl Sized + 'static>) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait_nested.rs:6:22
|
||||
|
|
||||
6 | async fn foo(_x: Foo<impl Sized + 'static>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: impl Sized + 'static) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait_static.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: impl Sized + 'static) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &'_ u32) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_anon.rs:4:19
|
||||
|
|
||||
4 | async fn foo(_x: &'_ u32) {}
|
||||
| ^^
|
||||
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &'static &'_ u32) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_anon_nested.rs:4:28
|
||||
|
|
||||
4 | async fn foo(_x: &'static &'_ u32) {}
|
||||
| ^^
|
||||
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &u32) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_elided.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: &u32) {}
|
||||
| ^
|
||||
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo<'a>(_x: &'a u32) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/nonstatic_ref_generic.rs:4:1
|
||||
|
|
||||
4 | async fn foo<'a>(_x: &'a u32) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_generic.rs:4:23
|
||||
|
|
||||
4 | async fn foo<'a>(_x: &'a u32) {}
|
||||
| ^^
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(_x: Foo<'_>) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_struct_anon.rs:6:23
|
||||
|
|
||||
6 | async fn task(_x: Foo<'_>) {}
|
||||
| ^^
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(_x: Foo) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,10 @@
|
||||
error[E0726]: implicit elided lifetime not allowed here
|
||||
--> tests/ui/nonstatic_struct_elided.rs:6:19
|
||||
|
|
||||
6 | async fn task(_x: Foo) {}
|
||||
| ^^^ expected lifetime parameter
|
||||
|
|
||||
help: indicate the anonymous lifetime
|
||||
|
|
||||
6 | async fn task(_x: Foo<'_>) {}
|
||||
| ++++
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task<'a>(_x: Foo<'a>) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/nonstatic_struct_generic.rs:6:1
|
||||
|
|
||||
6 | async fn task<'a>(_x: Foo<'a>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_struct_generic.rs:6:27
|
||||
|
|
||||
6 | async fn task<'a>(_x: Foo<'a>) {}
|
||||
| ^^
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
fn task() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: task functions must be async
|
||||
--> tests/ui/not_async.rs:6:1
|
||||
|
|
||||
6 | fn task() {}
|
||||
| ^^^^^^^^^
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(self) {}
|
||||
|
||||
fn main() {}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
error: task functions must not have `self` arguments
|
||||
--> tests/ui/self.rs:6:15
|
||||
|
|
||||
6 | async fn task(self) {}
|
||||
| ^^^^
|
||||
|
||||
error: `self` parameter is only allowed in associated functions
|
||||
--> tests/ui/self.rs:6:15
|
||||
|
|
||||
6 | async fn task(self) {}
|
||||
| ^^^^ not semantically valid as function parameter
|
||||
|
|
||||
= note: associated functions are those in `impl` or `trait` definitions
|
||||
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(&mut self) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,13 @@
|
||||
error: task functions must not have `self` arguments
|
||||
--> tests/ui/self_ref.rs:6:15
|
||||
|
|
||||
6 | async fn task(&mut self) {}
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: `self` parameter is only allowed in associated functions
|
||||
--> tests/ui/self_ref.rs:6:15
|
||||
|
|
||||
6 | async fn task(&mut self) {}
|
||||
| ^^^^^^^^^ not semantically valid as function parameter
|
||||
|
|
||||
= note: associated functions are those in `impl` or `trait` definitions
|
||||
@@ -0,0 +1,12 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task()
|
||||
where
|
||||
(): Sized,
|
||||
{
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
error: task functions must not have `where` clauses
|
||||
--> tests/ui/where_clause.rs:6:1
|
||||
|
|
||||
6 | / async fn task()
|
||||
7 | | where
|
||||
8 | | (): Sized,
|
||||
| |______________^
|
||||
Reference in new issue
Block a user