merge new embassy changes
This commit is contained in:
commit
cfad9798ff
37
.github/ci/build-xtensa.sh
vendored
Executable file
37
.github/ci/build-xtensa.sh
vendored
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## on push branch~=gh-readonly-queue/main/.*
|
||||||
|
## on pull_request
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export RUSTUP_HOME=/ci/cache/rustup
|
||||||
|
export CARGO_HOME=/ci/cache/cargo
|
||||||
|
export CARGO_TARGET_DIR=/ci/cache/target
|
||||||
|
|
||||||
|
# needed for "dumb HTTP" transport support
|
||||||
|
# used when pointing stm32-metapac to a CI-built one.
|
||||||
|
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||||
|
|
||||||
|
cargo install espup
|
||||||
|
/ci/cache/cargo/bin/espup install --toolchain-version 1.84.0.0
|
||||||
|
|
||||||
|
# Restore lockfiles
|
||||||
|
if [ -f /ci/cache/lockfiles.tar ]; then
|
||||||
|
echo Restoring lockfiles...
|
||||||
|
tar xf /ci/cache/lockfiles.tar
|
||||||
|
fi
|
||||||
|
|
||||||
|
hashtime restore /ci/cache/filetime.json || true
|
||||||
|
hashtime save /ci/cache/filetime.json
|
||||||
|
|
||||||
|
mkdir .cargo
|
||||||
|
cat > .cargo/config.toml<< EOF
|
||||||
|
[unstable]
|
||||||
|
build-std = ["alloc", "core"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
./ci-xtensa.sh
|
||||||
|
|
||||||
|
# Save lockfiles
|
||||||
|
echo Saving lockfiles...
|
||||||
|
find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+
|
||||||
3
.github/ci/doc.sh
vendored
3
.github/ci/doc.sh
vendored
@ -25,6 +25,7 @@ docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.z
|
|||||||
docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup
|
docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup
|
||||||
docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup
|
docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup
|
||||||
docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup
|
docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup
|
||||||
|
docserver-builder -i ./embassy-mspm0 -o webroot/crates/embassy-mspm0/git.zup
|
||||||
docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup
|
docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup
|
||||||
docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup
|
docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup
|
||||||
docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup
|
docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup
|
||||||
@ -32,7 +33,7 @@ docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/g
|
|||||||
|
|
||||||
docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup
|
docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup
|
||||||
docserver-builder -i ./embassy-time-driver -o webroot/crates/embassy-time-driver/git.zup
|
docserver-builder -i ./embassy-time-driver -o webroot/crates/embassy-time-driver/git.zup
|
||||||
docserver-builder -i ./embassy-time-queue-driver -o webroot/crates/embassy-time-queue-driver/git.zup
|
docserver-builder -i ./embassy-time-queue-utils -o webroot/crates/embassy-time-queue-utils/git.zup
|
||||||
|
|
||||||
docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup
|
docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup
|
||||||
docserver-builder -i ./embassy-usb-dfu -o webroot/crates/embassy-usb-dfu/git.zup
|
docserver-builder -i ./embassy-usb-dfu -o webroot/crates/embassy-usb-dfu/git.zup
|
||||||
|
|||||||
3
.github/ci/test-nightly.sh
vendored
3
.github/ci/test-nightly.sh
vendored
@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo
|
|||||||
export CARGO_TARGET_DIR=/ci/cache/target
|
export CARGO_TARGET_DIR=/ci/cache/target
|
||||||
mv rust-toolchain-nightly.toml rust-toolchain.toml
|
mv rust-toolchain-nightly.toml rust-toolchain.toml
|
||||||
|
|
||||||
|
cargo test --manifest-path ./embassy-executor/Cargo.toml
|
||||||
|
cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
||||||
|
|
||||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
|
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
|
||||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
||||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml
|
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml
|
||||||
|
|||||||
15
.github/ci/test.sh
vendored
15
.github/ci/test.sh
vendored
@ -8,11 +8,16 @@ export RUSTUP_HOME=/ci/cache/rustup
|
|||||||
export CARGO_HOME=/ci/cache/cargo
|
export CARGO_HOME=/ci/cache/cargo
|
||||||
export CARGO_TARGET_DIR=/ci/cache/target
|
export CARGO_TARGET_DIR=/ci/cache/target
|
||||||
|
|
||||||
|
# needed for "dumb HTTP" transport support
|
||||||
|
# used when pointing stm32-metapac to a CI-built one.
|
||||||
|
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||||
|
|
||||||
|
cargo test --manifest-path ./embassy-executor/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-futures/Cargo.toml
|
cargo test --manifest-path ./embassy-futures/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue,mock-driver
|
cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver,embassy-time-queue-utils/generic-queue-8
|
||||||
cargo test --manifest-path ./embassy-time-driver/Cargo.toml
|
cargo test --manifest-path ./embassy-time-driver/Cargo.toml
|
||||||
|
|
||||||
cargo test --manifest-path ./embassy-boot/Cargo.toml
|
cargo test --manifest-path ./embassy-boot/Cargo.toml
|
||||||
@ -24,8 +29,10 @@ cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --feat
|
|||||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp2040,_test
|
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp2040,_test
|
||||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp235xa,_test
|
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp235xa,_test
|
||||||
|
|
||||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,exti,time-driver-any,exti
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,time-driver-any,exti,single-bank
|
||||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,exti,time-driver-any,exti
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,time-driver-any,exti,dual-bank
|
||||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,exti,time-driver-any,exti
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,time-driver-any,exti
|
||||||
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,time-driver-any,exti,single-bank
|
||||||
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,time-driver-any,exti,dual-bank
|
||||||
|
|
||||||
cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml
|
cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -5,3 +5,8 @@ Cargo.lock
|
|||||||
third_party
|
third_party
|
||||||
/Cargo.toml
|
/Cargo.toml
|
||||||
out/
|
out/
|
||||||
|
# editor artifacts
|
||||||
|
.zed
|
||||||
|
.neoconf.json
|
||||||
|
*.vim
|
||||||
|
**/.idea
|
||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -28,6 +28,11 @@
|
|||||||
// To work on the examples, comment the line above and all of the cargo.features lines,
|
// To work on the examples, comment the line above and all of the cargo.features lines,
|
||||||
// then uncomment ONE line below to select the chip you want to work on.
|
// then uncomment ONE line below to select the chip you want to work on.
|
||||||
// This makes rust-analyzer work on the example crate and all its dependencies.
|
// This makes rust-analyzer work on the example crate and all its dependencies.
|
||||||
|
// "examples/mspm0c1104/Cargo.toml",
|
||||||
|
// "examples/mspm0g3507/Cargo.toml",
|
||||||
|
// "examples/mspm0g3519/Cargo.toml",
|
||||||
|
// "examples/mspm0l1306/Cargo.toml",
|
||||||
|
// "examples/mspm0l2228/Cargo.toml",
|
||||||
// "examples/nrf52840-rtic/Cargo.toml",
|
// "examples/nrf52840-rtic/Cargo.toml",
|
||||||
// "examples/nrf5340/Cargo.toml",
|
// "examples/nrf5340/Cargo.toml",
|
||||||
// "examples/nrf-rtos-trace/Cargo.toml",
|
// "examples/nrf-rtos-trace/Cargo.toml",
|
||||||
|
|||||||
88
README.md
88
README.md
@ -1,49 +1,55 @@
|
|||||||
# Embassy
|
# Embassy
|
||||||
|
|
||||||
Embassy is the next-generation framework for embedded applications. Write safe, correct and energy-efficient embedded code faster, using the Rust programming language, its async facilities, and the Embassy libraries.
|
Embassy is the next-generation framework for embedded applications. Write safe, correct, and energy-efficient embedded code faster, using the Rust programming language, its async facilities, and the Embassy libraries.
|
||||||
|
|
||||||
|
## [Documentation](https://embassy.dev/book/index.html) - [API reference](https://docs.embassy.dev/) - [Website](https://embassy.dev/) - [Chat](https://matrix.to/#/#embassy-rs:matrix.org)
|
||||||
|
|
||||||
## <a href="https://embassy.dev/book/index.html">Documentation</a> - <a href="https://docs.embassy.dev/">API reference</a> - <a href="https://embassy.dev/">Website</a> - <a href="https://matrix.to/#/#embassy-rs:matrix.org">Chat</a>
|
|
||||||
## Rust + async ❤️ embedded
|
## Rust + async ❤️ embedded
|
||||||
|
|
||||||
The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system.
|
The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector, or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system.
|
||||||
|
|
||||||
Rust's <a href="https://rust-lang.github.io/async-book/">async/await</a> allows for unprecedentedly easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation, and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is <a href="https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown">faster and smaller than one!</a>
|
Rust's [async/await](https://rust-lang.github.io/async-book/) allows for unprecedentedly easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is [faster and smaller than one!](https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown)
|
||||||
|
|
||||||
## Batteries included
|
## Batteries included
|
||||||
|
|
||||||
- **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy.
|
- **Hardware Abstraction Layers**
|
||||||
- <a href="https://docs.embassy.dev/embassy-stm32/">embassy-stm32</a>, for all STM32 microcontroller families.
|
- HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy.
|
||||||
- <a href="https://docs.embassy.dev/embassy-nrf/">embassy-nrf</a>, for the Nordic Semiconductor nRF52, nRF53, nRF91 series.
|
- [embassy-stm32](https://docs.embassy.dev/embassy-stm32/), for all STM32 microcontroller families.
|
||||||
- <a href="https://docs.embassy.dev/embassy-rp/">embassy-rp</a>, for the Raspberry Pi RP2040 microcontroller.
|
- [embassy-nrf](https://docs.embassy.dev/embassy-nrf/), for the Nordic Semiconductor nRF52, nRF53, nRF54 and nRF91 series.
|
||||||
- <a href="https://github.com/esp-rs">esp-rs</a>, for the Espressif Systems ESP32 series of chips.
|
- [embassy-rp](https://docs.embassy.dev/embassy-rp/), for the Raspberry Pi RP2040 and RP23xx microcontrollers.
|
||||||
- Embassy HAL support for Espressif chips is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository.
|
- [embassy-mspm0](https://docs.embassy.dev/embassy-mspm0/), for the Texas Instruments MSPM0 microcontrollers.
|
||||||
- Async WiFi, Bluetooth and ESP-NOW is being developed in the [esp-rs/esp-wifi](https://github.com/esp-rs/esp-wifi) repository.
|
- [esp-rs](https://github.com/esp-rs), for the Espressif Systems ESP32 series of chips.
|
||||||
- <a href="https://github.com/ch32-rs/ch32-hal">ch32-hal</a>, for the WCH 32-bit RISC-V(CH32V) series of chips.
|
- Embassy HAL support for Espressif chips, as well as Async Wi-Fi, Bluetooth, and ESP-NOW, is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository.
|
||||||
|
- [ch32-hal](https://github.com/ch32-rs/ch32-hal), for the WCH 32-bit RISC-V(CH32V) series of chips.
|
||||||
|
- [mpfs-hal](https://github.com/AlexCharlton/mpfs-hal), for the Microchip PolarFire SoC.
|
||||||
|
- [py32-hal](https://github.com/py32-rs/py32-hal), for the Puya Semiconductor PY32 series of microcontrollers.
|
||||||
|
|
||||||
- **Time that Just Works** -
|
- **Time that Just Works** -
|
||||||
No more messing with hardware timers. <a href="https://docs.embassy.dev/embassy-time">embassy_time</a> provides Instant, Duration and Timer types that are globally available and never overflow.
|
No more messing with hardware timers. [embassy_time](https://docs.embassy.dev/embassy-time) provides Instant, Duration, and Timer types that are globally available and never overflow.
|
||||||
|
|
||||||
- **Real-time ready** -
|
- **Real-time ready** -
|
||||||
Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the <a href="https://github.com/embassy-rs/embassy/blob/master/examples/nrf52840/src/bin/multiprio.rs">example</a>.
|
Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities so that higher priority tasks preempt lower priority ones. See the [example](https://github.com/embassy-rs/embassy/blob/master/examples/nrf52840/src/bin/multiprio.rs).
|
||||||
|
|
||||||
- **Low-power ready** -
|
- **Low-power ready** -
|
||||||
Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting.
|
Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting.
|
||||||
|
|
||||||
- **Networking** -
|
|
||||||
The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently.
|
|
||||||
|
|
||||||
- **Bluetooth** -
|
- **Networking** -
|
||||||
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
The [embassy-net](https://docs.embassy.dev/embassy-net/) network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP, and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently.
|
||||||
The <a href="https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan">embassy-stm32-wpan</a> crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers.
|
|
||||||
|
|
||||||
- **LoRa** - The <a href="https://github.com/lora-rs/lora-rs">lora-rs</a> project provides an async LoRa and LoRaWAN stack that works well on Embassy.
|
- **Bluetooth**
|
||||||
|
- The [trouble](https://github.com/embassy-rs/trouble) crate provides a Bluetooth Low Energy 4.x and 5.x Host that runs on any microcontroller implementing the [bt-hci](https://github.com/embassy-rs/bt-hci) traits (currently
|
||||||
|
`nRF52`, `rp2040`, `rp23xx` and `esp32` and `serial` controllers are supported).
|
||||||
|
- The [nrf-softdevice](https://github.com/embassy-rs/nrf-softdevice) crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||||
|
- The [embassy-stm32-wpan](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan) crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers.
|
||||||
|
|
||||||
- **USB** -
|
- **LoRa** -
|
||||||
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
The [lora-rs](https://github.com/lora-rs/lora-rs) project provides an async LoRa and LoRaWAN stack that works well on Embassy.
|
||||||
|
|
||||||
- **Bootloader and DFU** -
|
- **USB** -
|
||||||
<a href="https://github.com/embassy-rs/embassy/tree/master/embassy-boot">embassy-boot</a> is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
[embassy-usb](https://docs.embassy.dev/embassy-usb/) implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||||
|
|
||||||
|
- **Bootloader and DFU** -
|
||||||
|
[embassy-boot](https://github.com/embassy-rs/embassy/tree/master/embassy-boot) is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
||||||
|
|
||||||
## Sneak peek
|
## Sneak peek
|
||||||
|
|
||||||
@ -52,11 +58,11 @@ use defmt::info;
|
|||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull};
|
use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull};
|
||||||
use embassy_nrf::Peripherals;
|
use embassy_nrf::{Peri, Peripherals};
|
||||||
|
|
||||||
// Declare async tasks
|
// Declare async tasks
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn blink(pin: AnyPin) {
|
async fn blink(pin: Peri<'static, AnyPin>) {
|
||||||
let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);
|
let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -74,7 +80,7 @@ async fn main(spawner: Spawner) {
|
|||||||
let p = embassy_nrf::init(Default::default());
|
let p = embassy_nrf::init(Default::default());
|
||||||
|
|
||||||
// Spawned tasks run in the background, concurrently.
|
// Spawned tasks run in the background, concurrently.
|
||||||
spawner.spawn(blink(p.P0_13.degrade())).unwrap();
|
spawner.spawn(blink(p.P0_13.into())).unwrap();
|
||||||
|
|
||||||
let mut button = Input::new(p.P0_11, Pull::Up);
|
let mut button = Input::new(p.P0_11, Pull::Up);
|
||||||
loop {
|
loop {
|
||||||
@ -90,13 +96,15 @@ async fn main(spawner: Spawner) {
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Examples are found in the `examples/` folder separated by the chip manufacturer they are designed to run on. For example:
|
Examples are found in the
|
||||||
|
`examples/` folder separated by the chip manufacturer they are designed to run on. For example:
|
||||||
|
|
||||||
* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards.
|
* `examples/nrf52840` run on the
|
||||||
* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095).
|
`nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards.
|
||||||
* `examples/stm32xx` for the various STM32 families.
|
* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095).
|
||||||
* `examples/rp` are for the RP2040 chip.
|
* `examples/stm32xx` for the various STM32 families.
|
||||||
* `examples/std` are designed to run locally on your PC.
|
* `examples/rp` are for the RP2040 chip.
|
||||||
|
* `examples/std` are designed to run locally on your PC.
|
||||||
|
|
||||||
### Running examples
|
### Running examples
|
||||||
|
|
||||||
@ -123,7 +131,7 @@ cargo run --release --bin blinky
|
|||||||
|
|
||||||
For more help getting started, see [Getting Started][1] and [Running the Examples][2].
|
For more help getting started, see [Getting Started][1] and [Running the Examples][2].
|
||||||
|
|
||||||
## Developing Embassy with Rust Analyzer based editors
|
## Developing Embassy with Rust Analyzer-based editors
|
||||||
|
|
||||||
The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/)
|
The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/)
|
||||||
and others. Given the multiple targets that Embassy serves, there is no Cargo workspace file. Instead, the Rust Analyzer
|
and others. Given the multiple targets that Embassy serves, there is no Cargo workspace file. Instead, the Rust Analyzer
|
||||||
@ -133,7 +141,7 @@ please refer to the `.vscode/settings.json` file's `rust-analyzer.linkedProjects
|
|||||||
## Minimum supported Rust version (MSRV)
|
## Minimum supported Rust version (MSRV)
|
||||||
|
|
||||||
Embassy is guaranteed to compile on stable Rust 1.75 and up. It *might*
|
Embassy is guaranteed to compile on stable Rust 1.75 and up. It *might*
|
||||||
compile with older versions but that may change in any new patch release.
|
compile with older versions, but that may change in any new patch release.
|
||||||
|
|
||||||
## Why the name?
|
## Why the name?
|
||||||
|
|
||||||
|
|||||||
@ -13,20 +13,14 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread,integrated-timers \
|
--- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/nrf52840-rtic \
|
||||||
--- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target armv7a-none-eabi --features nightly,arch-cortex-ar,executor-thread \
|
||||||
|
|
||||||
cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-unknown-gnu-atmega328 -Z build-std=core,alloc --features nightly,arch-avr,avr-device/atmega328p
|
RUSTFLAGS="$RUSTFLAGS -C target-cpu=atmega328p" cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-none -Z build-std=core,alloc --features nightly,arch-avr,avr-device/atmega328p
|
||||||
cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-unknown-gnu-atmega328 -Z build-std=core,alloc --features nightly,arch-avr,integrated-timers,avr-device/atmega328p
|
|
||||||
|
|||||||
38
ci-xtensa.sh
Executable file
38
ci-xtensa.sh
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
export RUSTFLAGS=-Dwarnings
|
||||||
|
export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
|
||||||
|
export RUSTUP_TOOLCHAIN=esp
|
||||||
|
if [[ -z "${CARGO_TARGET_DIR}" ]]; then
|
||||||
|
export CARGO_TARGET_DIR=target_ci
|
||||||
|
fi
|
||||||
|
|
||||||
|
cargo batch \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features log \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features defmt \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features defmt,arch-spin,executor-thread \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,arch-spin,executor-thread \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s3-none-elf --features defmt,arch-spin,executor-thread \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,rtos-trace \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread \
|
||||||
|
--- build --release --manifest-path embassy-sync/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \
|
||||||
|
--- build --release --manifest-path embassy-time/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,defmt-timestamp-uptime,mock-driver \
|
||||||
|
--- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target xtensa-esp32s2-none-elf \
|
||||||
|
--- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target xtensa-esp32s2-none-elf --features generic-queue-8 \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \
|
||||||
|
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \
|
||||||
297
ci.sh
297
ci.sh
@ -19,8 +19,8 @@ fi
|
|||||||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||||||
|
|
||||||
BUILD_EXTRA=""
|
BUILD_EXTRA=""
|
||||||
if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then
|
if [ $TARGET = "x86_64-unknown-linux-gnu" ] || [ $TARGET = "aarch64-unknown-linux-gnu" ]; then
|
||||||
BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std"
|
BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --artifact-dir out/examples/std"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# CI intentionally does not use -eabihf on thumbv7em to minimize dep compile time.
|
# CI intentionally does not use -eabihf on thumbv7em to minimize dep compile time.
|
||||||
@ -29,25 +29,23 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,rtos-trace \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,rtos-trace \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers,rtos-trace \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target armv7a-none-eabi --features arch-cortex-ar,executor-thread \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabi --features arch-cortex-ar,executor-thread \
|
||||||
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabihf --features arch-cortex-ar,executor-thread \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \
|
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \
|
||||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \
|
|
||||||
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \
|
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,mock-driver \
|
||||||
|
--- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target thumbv6m-none-eabi \
|
||||||
|
--- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target thumbv6m-none-eabi --features generic-queue-8 \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||||
@ -58,6 +56,8 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \
|
||||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \
|
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \
|
||||||
|
--- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt633s,defmt,unstable-pac,time,time-driver-rtc \
|
||||||
|
--- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt685s,defmt,unstable-pac,time,time-driver-rtc \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,gpiote,time,time-driver-rtc1 \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time,time-driver-rtc1 \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time,time-driver-rtc1 \
|
||||||
@ -70,6 +70,8 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time,time-driver-rtc1 \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time,time-driver-rtc1 \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time,time-driver-rtc1 \
|
||||||
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf54l15-app-s,gpiote,time,time-driver-rtc1 \
|
||||||
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf54l15-app-ns,gpiote,time,time-driver-rtc1 \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \
|
||||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time,time-driver-rtc1 \
|
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time,time-driver-rtc1 \
|
||||||
@ -88,16 +90,18 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio,rp2040 \
|
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio,rp2040 \
|
||||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,defmt,rp235xa \
|
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,defmt,rp235xa \
|
||||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,log,rp235xa \
|
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,log,rp235xa \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,rp235xa,binary-info \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti \
|
||||||
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt \
|
||||||
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,single-bank,defmt \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f038f6,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f038f6,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030c6,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030c6,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f058t8,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f058t8,defmt,exti,time-driver-any,time \
|
||||||
@ -131,7 +135,7 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f730i8,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f730i8,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h735zg,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h735zg,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,time,split-pc2,split-pc3 \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h725re,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h725re,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-tim1,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-tim1,time \
|
||||||
@ -153,10 +157,10 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f378cc,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f378cc,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g0c1ve,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g0c1ve,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,low-power,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time-driver-any,low-power,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32wl54jc-cm0p,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32wl54jc-cm0p,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wle5jb,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wle5jb,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g474pe,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g474pe,dual-bank,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f107vc,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f107vc,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103re,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103re,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f100c4,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f100c4,defmt,exti,time-driver-any,time \
|
||||||
@ -165,11 +169,26 @@ cargo batch \
|
|||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h562ag,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h562ag,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba50ke,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba50ke,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba55ug,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba55ug,defmt,exti,time-driver-any,time \
|
||||||
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5f9zj,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5g9nj,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5g9nj,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb35ce,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb35ce,defmt,exti,time-driver-any,time \
|
||||||
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg,defmt,exti,time-driver-any,low-power,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \
|
||||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \
|
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \
|
||||||
|
--- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0c1104dgs20,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507pm,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3519pz,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1306rhb,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l2228pn,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1345dgs28,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1106dgs28,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1228pm,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g1107ycj,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3105rhb,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g1505pt,defmt,time-driver-any \
|
||||||
|
--- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g1519rhb,defmt,time-driver-any \
|
||||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\
|
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\
|
||||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \
|
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \
|
||||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \
|
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \
|
||||||
@ -178,68 +197,86 @@ cargo batch \
|
|||||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs,bluetooth' \
|
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs,bluetooth' \
|
||||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs,bluetooth' \
|
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs,bluetooth' \
|
||||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \
|
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \
|
||||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040,overclock' \
|
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \
|
||||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||||
|
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf5340-app-s \
|
||||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \
|
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \
|
||||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \
|
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \
|
||||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \
|
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \
|
||||||
--- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi --features embassy-rp/rp2040 \
|
--- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi --features embassy-rp/rp2040 \
|
||||||
--- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
|
--- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --no-default-features \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features log \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features usbd-hid \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-interface-count-1 \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-interface-count-8 \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-handler-count-8 \
|
||||||
|
--- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-handler-count-8 \
|
||||||
--- build --release --manifest-path docs/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
|
--- build --release --manifest-path docs/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
|
||||||
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
|
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
|
||||||
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
|
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
|
||||||
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \
|
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \
|
||||||
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \
|
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \
|
||||||
--- build --release --manifest-path examples/nrf52810/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52810 \
|
--- build --release --manifest-path examples/nrf52810/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/nrf52810 \
|
||||||
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \
|
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/nrf52840 \
|
||||||
--- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \
|
--- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf5340 \
|
||||||
--- build --release --manifest-path examples/nrf9160/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9160 \
|
--- build --release --manifest-path examples/nrf54l15/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf54l15 \
|
||||||
--- build --release --manifest-path examples/nrf9151/s/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/s \
|
--- build --release --manifest-path examples/nrf9160/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf9160 \
|
||||||
--- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/ns \
|
--- build --release --manifest-path examples/nrf9151/s/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf9151/s \
|
||||||
--- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/nrf51 \
|
--- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf9151/ns \
|
||||||
--- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \
|
--- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/nrf51 \
|
||||||
--- build --release --manifest-path examples/rp23/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/rp23 \
|
--- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/rp \
|
||||||
--- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \
|
--- build --release --manifest-path examples/rp235x/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/rp235x \
|
||||||
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \
|
--- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32f0 \
|
||||||
--- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \
|
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --artifact-dir out/examples/stm32f1 \
|
||||||
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f3 \
|
--- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --artifact-dir out/examples/stm32f2 \
|
||||||
--- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f334 \
|
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f3 \
|
||||||
--- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \
|
--- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f334 \
|
||||||
--- build --release --manifest-path examples/stm32f469/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f469 \
|
--- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f4 \
|
||||||
--- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f7 \
|
--- build --release --manifest-path examples/stm32f469/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f469 \
|
||||||
--- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \
|
--- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f7 \
|
||||||
--- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32g0 \
|
--- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32c0 \
|
||||||
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
|
--- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32g0 \
|
||||||
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \
|
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32g4 \
|
||||||
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
|
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32h5 \
|
||||||
--- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \
|
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h7 \
|
||||||
--- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \
|
--- build --release --manifest-path examples/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h7b0 \
|
||||||
--- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \
|
--- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h735 \
|
||||||
--- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \
|
--- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h755cm4 \
|
||||||
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \
|
--- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h755cm7 \
|
||||||
--- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \
|
--- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h7rs \
|
||||||
--- build --release --manifest-path examples/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32l4 \
|
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32l0 \
|
||||||
--- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32l5 \
|
--- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --artifact-dir out/examples/stm32l1 \
|
||||||
--- build --release --manifest-path examples/stm32u0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32u0 \
|
--- build --release --manifest-path examples/stm32l4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32l4 \
|
||||||
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \
|
--- build --release --manifest-path examples/stm32l432/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32l432 \
|
||||||
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wb \
|
--- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32l5 \
|
||||||
--- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \
|
--- build --release --manifest-path examples/stm32u0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32u0 \
|
||||||
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wl \
|
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32u5 \
|
||||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \
|
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wb \
|
||||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \
|
--- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32wba \
|
||||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \
|
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \
|
||||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --out-dir out/examples/boot/nrf9151 \
|
--- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \
|
||||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --out-dir out/examples/boot/nrf9161 \
|
--- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \
|
||||||
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \
|
--- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \
|
--- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \
|
--- build --release --manifest-path examples/mspm0l2228/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l2228 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \
|
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --artifact-dir out/examples/boot/nrf52840 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \
|
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --artifact-dir out/examples/boot/nrf9160 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \
|
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --artifact-dir out/examples/boot/nrf9120 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \
|
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --artifact-dir out/examples/boot/nrf9151 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32wl \
|
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --artifact-dir out/examples/boot/nrf9161 \
|
||||||
--- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32wb-dfu \
|
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --artifact-dir out/examples/boot/rp \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32f3 \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32f7 \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32h7 \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32l0 \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32l1 \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32l4 \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32wl \
|
||||||
|
--- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/boot/stm32wb-dfu \
|
||||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \
|
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \
|
||||||
@ -248,58 +285,92 @@ cargo batch \
|
|||||||
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
|
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||||
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
|
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
|
||||||
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \
|
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \
|
||||||
|
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg,verify \
|
||||||
--- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \
|
--- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \
|
||||||
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
|
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --artifact-dir out/examples/wasm \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --artifact-dir out/tests/stm32f103c8 \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --artifact-dir out/tests/stm32f429zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --out-dir out/tests/stm32f446re \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --artifact-dir out/tests/stm32f446re \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --artifact-dir out/tests/stm32g491re \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --artifact-dir out/tests/stm32g071rb \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --artifact-dir out/tests/stm32c031c6 \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --artifact-dir out/tests/stm32h755zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --out-dir out/tests/stm32h753zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --artifact-dir out/tests/stm32h753zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --out-dir out/tests/stm32h7a3zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --artifact-dir out/tests/stm32h7a3zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --artifact-dir out/tests/stm32wb55rg \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h563zi --out-dir out/tests/stm32h563zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h563zi --artifact-dir out/tests/stm32h563zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u585ai --out-dir out/tests/stm32u585ai \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u585ai --artifact-dir out/tests/stm32u585ai \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5a5zj --artifact-dir out/tests/stm32u5a5zj \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba52cg --out-dir out/tests/stm32wba52cg \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba52cg --artifact-dir out/tests/stm32wba52cg \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --artifact-dir out/tests/stm32l073rz \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --artifact-dir out/tests/stm32l152re \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --artifact-dir out/tests/stm32l4a6zg \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --out-dir out/tests/stm32l4r5zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --artifact-dir out/tests/stm32l4r5zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --out-dir out/tests/stm32l552ze \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --artifact-dir out/tests/stm32l552ze \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f767zi --out-dir out/tests/stm32f767zi \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f767zi --artifact-dir out/tests/stm32f767zi \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --out-dir out/tests/stm32f207zg \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --artifact-dir out/tests/stm32f207zg \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --out-dir out/tests/stm32f303ze \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --artifact-dir out/tests/stm32f303ze \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --out-dir out/tests/stm32l496zg \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --artifact-dir out/tests/stm32l496zg \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --out-dir out/tests/stm32wl55jc \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --artifact-dir out/tests/stm32wl55jc \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7s3l8 --out-dir out/tests/stm32h7s3l8 \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7s3l8 --artifact-dir out/tests/stm32h7s3l8 \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f091rc --out-dir out/tests/stm32f091rc \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f091rc --artifact-dir out/tests/stm32f091rc \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb --out-dir out/tests/stm32h503rb \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb --artifact-dir out/tests/stm32h503rb \
|
||||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc --out-dir out/tests/stm32u083rc \
|
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc --artifact-dir out/tests/stm32u083rc \
|
||||||
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \
|
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --features rp2040 --artifact-dir out/tests/rpi-pico \
|
||||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51422 --out-dir out/tests/nrf51422-dk \
|
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv8m.main-none-eabihf --features rp235xb --artifact-dir out/tests/pimoroni-pico-plus-2 \
|
||||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832 --out-dir out/tests/nrf52832-dk \
|
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51422 --artifact-dir out/tests/nrf51422-dk \
|
||||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833 --out-dir out/tests/nrf52833-dk \
|
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832 --artifact-dir out/tests/nrf52832-dk \
|
||||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840 --out-dir out/tests/nrf52840-dk \
|
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833 --artifact-dir out/tests/nrf52833-dk \
|
||||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340 --out-dir out/tests/nrf5340-dk \
|
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840 --artifact-dir out/tests/nrf52840-dk \
|
||||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160 --out-dir out/tests/nrf9160-dk \
|
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340 --artifact-dir out/tests/nrf5340-dk \
|
||||||
|
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160 --artifact-dir out/tests/nrf9160-dk \
|
||||||
|
--- build --release --manifest-path tests/mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507 --artifact-dir out/tests/mspm0g3507 \
|
||||||
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
|
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
|
||||||
$BUILD_EXTRA
|
$BUILD_EXTRA
|
||||||
|
|
||||||
|
|
||||||
|
# MSPM0C1104 must be built seperately since cargo batch does not consider env vars set in `.cargo/config.toml`.
|
||||||
|
# Since the target has 1KB of ram, we need to limit defmt's buffer size.
|
||||||
|
DEFMT_RTT_BUFFER_SIZE="72" cargo batch \
|
||||||
|
--- build --release --manifest-path examples/mspm0c1104/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0c1104 \
|
||||||
|
|
||||||
|
# temporarily disabled, these boards are dead.
|
||||||
|
rm -rf out/tests/stm32f103c8
|
||||||
|
rm -rf out/tests/nrf52840-dk
|
||||||
|
rm -rf out/tests/nrf52833-dk
|
||||||
|
|
||||||
|
# disabled because these boards are not on the shelf
|
||||||
|
rm -rf out/tests/mspm0g3507
|
||||||
|
|
||||||
rm out/tests/stm32wb55rg/wpan_mac
|
rm out/tests/stm32wb55rg/wpan_mac
|
||||||
rm out/tests/stm32wb55rg/wpan_ble
|
rm out/tests/stm32wb55rg/wpan_ble
|
||||||
|
|
||||||
# unstable, I think it's running out of RAM?
|
# unstable, I think it's running out of RAM?
|
||||||
rm out/tests/stm32f207zg/eth
|
rm out/tests/stm32f207zg/eth
|
||||||
|
|
||||||
|
# temporarily disabled, flaky.
|
||||||
|
rm out/tests/stm32f207zg/usart_rx_ringbuffered
|
||||||
|
rm out/tests/stm32l152re/usart_rx_ringbuffered
|
||||||
|
|
||||||
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
|
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
|
||||||
rm out/tests/stm32u5a5zj/usart
|
rm out/tests/stm32u5a5zj/usart
|
||||||
|
|
||||||
# flaky, perhaps bad wire
|
# probe-rs error: "multi-core ram flash start not implemented yet"
|
||||||
rm out/tests/stm32l152re/usart_rx_ringbuffered
|
# As of 2025-02-17 these tests work when run from flash
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/multicore
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/gpio_multicore
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/spinlock_mutex_multicore
|
||||||
|
# Doesn't work when run from ram on the 2350
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/flash
|
||||||
|
# This test passes locally but fails on the HIL, no idea why
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/i2c
|
||||||
|
# The pico2 plus doesn't have the adcs hooked up like the picoW does.
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/adc
|
||||||
|
# temporarily disabled
|
||||||
|
rm out/tests/pimoroni-pico-plus-2/pwm
|
||||||
|
|
||||||
|
# temporarily disabled, bad hardware connection.
|
||||||
|
rm -f out/tests/rpi-pico/*
|
||||||
|
|
||||||
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
|
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
|
||||||
echo No teleprobe token found, skipping running HIL tests
|
echo No teleprobe token found, skipping running HIL tests
|
||||||
|
|||||||
@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Update embassy-rp to 0.4.0
|
||||||
|
|
||||||
|
## 0.3.0 - 2025-01-05
|
||||||
|
|
||||||
|
- Update embassy-time to 0.4.0
|
||||||
|
- Update cyw43 to 0.3.0
|
||||||
|
- Update embassy-rp to 0.3.0
|
||||||
|
|
||||||
## 0.2.0 - 2024-08-05
|
## 0.2.0 - 2024-08-05
|
||||||
|
|
||||||
- Update to cyw43 0.2.0
|
- Update to cyw43 0.2.0
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cyw43-pio"
|
name = "cyw43-pio"
|
||||||
version = "0.2.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "RP2040 PIO SPI implementation for cyw43"
|
description = "RP2040 PIO SPI implementation for cyw43"
|
||||||
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
|
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
|
||||||
@ -9,18 +9,11 @@ license = "MIT OR Apache-2.0"
|
|||||||
repository = "https://github.com/embassy-rs/embassy"
|
repository = "https://github.com/embassy-rs/embassy"
|
||||||
documentation = "https://docs.embassy.dev/cyw43-pio"
|
documentation = "https://docs.embassy.dev/cyw43-pio"
|
||||||
|
|
||||||
[features]
|
|
||||||
# If disabled, SPI runs at 31.25MHz
|
|
||||||
# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet.
|
|
||||||
overclock = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cyw43 = { version = "0.2.0", path = "../cyw43" }
|
cyw43 = { version = "0.3.0", path = "../cyw43" }
|
||||||
embassy-rp = { version = "0.2.0", path = "../embassy-rp" }
|
embassy-rp = { version = "0.4.0", path = "../embassy-rp" }
|
||||||
pio-proc = "0.2"
|
|
||||||
pio = "0.2.1"
|
|
||||||
fixed = "1.23.1"
|
fixed = "1.23.1"
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
|
|
||||||
[package.metadata.embassy_docs]
|
[package.metadata.embassy_docs]
|
||||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/"
|
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/"
|
||||||
|
|||||||
@ -8,86 +8,106 @@ use core::slice;
|
|||||||
use cyw43::SpiBusCyw43;
|
use cyw43::SpiBusCyw43;
|
||||||
use embassy_rp::dma::Channel;
|
use embassy_rp::dma::Channel;
|
||||||
use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate};
|
use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate};
|
||||||
use embassy_rp::pio::{instr, Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
|
use embassy_rp::pio::program::pio_asm;
|
||||||
use embassy_rp::{Peripheral, PeripheralRef};
|
use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
|
||||||
|
use embassy_rp::Peri;
|
||||||
|
use fixed::types::extra::U8;
|
||||||
use fixed::FixedU32;
|
use fixed::FixedU32;
|
||||||
use pio_proc::pio_asm;
|
|
||||||
|
|
||||||
/// SPI comms driven by PIO.
|
/// SPI comms driven by PIO.
|
||||||
pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA> {
|
pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA: Channel> {
|
||||||
cs: Output<'d>,
|
cs: Output<'d>,
|
||||||
sm: StateMachine<'d, PIO, SM>,
|
sm: StateMachine<'d, PIO, SM>,
|
||||||
irq: Irq<'d, PIO, 0>,
|
irq: Irq<'d, PIO, 0>,
|
||||||
dma: PeripheralRef<'d, DMA>,
|
dma: Peri<'d, DMA>,
|
||||||
wrap_target: u8,
|
wrap_target: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices.
|
||||||
|
/// same speed as pico-sdk, 62.5Mhz
|
||||||
|
/// This is actually the fastest we can go without overclocking.
|
||||||
|
/// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq.
|
||||||
|
/// However, the PIO uses a fractional divider, which works by introducing jitter when
|
||||||
|
/// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz
|
||||||
|
/// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles
|
||||||
|
/// violate the maximum from the data sheet.
|
||||||
|
pub const DEFAULT_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0200);
|
||||||
|
|
||||||
|
/// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices.
|
||||||
|
/// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to
|
||||||
|
/// data sheet, but seems to work fine.
|
||||||
|
pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0100);
|
||||||
|
|
||||||
|
/// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W,
|
||||||
|
/// Pico Plus 2 Non w with the RM2 breakout module, and the Pico 2 with the RM2 breakout module.
|
||||||
|
pub const RM2_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0300);
|
||||||
|
|
||||||
impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA>
|
impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA>
|
||||||
where
|
where
|
||||||
DMA: Channel,
|
DMA: Channel,
|
||||||
PIO: Instance,
|
PIO: Instance,
|
||||||
{
|
{
|
||||||
/// Create a new instance of PioSpi.
|
/// Create a new instance of PioSpi.
|
||||||
pub fn new<DIO, CLK>(
|
pub fn new(
|
||||||
common: &mut Common<'d, PIO>,
|
common: &mut Common<'d, PIO>,
|
||||||
mut sm: StateMachine<'d, PIO, SM>,
|
mut sm: StateMachine<'d, PIO, SM>,
|
||||||
|
clock_divider: FixedU32<U8>,
|
||||||
irq: Irq<'d, PIO, 0>,
|
irq: Irq<'d, PIO, 0>,
|
||||||
cs: Output<'d>,
|
cs: Output<'d>,
|
||||||
dio: DIO,
|
dio: Peri<'d, impl PioPin>,
|
||||||
clk: CLK,
|
clk: Peri<'d, impl PioPin>,
|
||||||
dma: impl Peripheral<P = DMA> + 'd,
|
dma: Peri<'d, DMA>,
|
||||||
) -> Self
|
) -> Self {
|
||||||
where
|
let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER {
|
||||||
DIO: PioPin,
|
let overclock_program = pio_asm!(
|
||||||
CLK: PioPin,
|
".side_set 1"
|
||||||
{
|
|
||||||
#[cfg(feature = "overclock")]
|
|
||||||
let program = pio_asm!(
|
|
||||||
".side_set 1"
|
|
||||||
|
|
||||||
".wrap_target"
|
".wrap_target"
|
||||||
// write out x-1 bits
|
// write out x-1 bits
|
||||||
"lp:"
|
"lp:"
|
||||||
"out pins, 1 side 0"
|
"out pins, 1 side 0"
|
||||||
"jmp x-- lp side 1"
|
"jmp x-- lp side 1"
|
||||||
// switch directions
|
// switch directions
|
||||||
"set pindirs, 0 side 0"
|
"set pindirs, 0 side 0"
|
||||||
"nop side 1" // necessary for clkdiv=1.
|
"nop side 1" // necessary for clkdiv=1.
|
||||||
"nop side 0"
|
"nop side 0"
|
||||||
// read in y-1 bits
|
// read in y-1 bits
|
||||||
"lp2:"
|
"lp2:"
|
||||||
"in pins, 1 side 1"
|
"in pins, 1 side 1"
|
||||||
"jmp y-- lp2 side 0"
|
"jmp y-- lp2 side 0"
|
||||||
|
|
||||||
// wait for event and irq host
|
// wait for event and irq host
|
||||||
"wait 1 pin 0 side 0"
|
"wait 1 pin 0 side 0"
|
||||||
"irq 0 side 0"
|
"irq 0 side 0"
|
||||||
|
|
||||||
".wrap"
|
".wrap"
|
||||||
);
|
);
|
||||||
#[cfg(not(feature = "overclock"))]
|
common.load_program(&overclock_program.program)
|
||||||
let program = pio_asm!(
|
} else {
|
||||||
".side_set 1"
|
let default_program = pio_asm!(
|
||||||
|
".side_set 1"
|
||||||
|
|
||||||
".wrap_target"
|
".wrap_target"
|
||||||
// write out x-1 bits
|
// write out x-1 bits
|
||||||
"lp:"
|
"lp:"
|
||||||
"out pins, 1 side 0"
|
"out pins, 1 side 0"
|
||||||
"jmp x-- lp side 1"
|
"jmp x-- lp side 1"
|
||||||
// switch directions
|
// switch directions
|
||||||
"set pindirs, 0 side 0"
|
"set pindirs, 0 side 0"
|
||||||
"nop side 0"
|
"nop side 0"
|
||||||
// read in y-1 bits
|
// read in y-1 bits
|
||||||
"lp2:"
|
"lp2:"
|
||||||
"in pins, 1 side 1"
|
"in pins, 1 side 1"
|
||||||
"jmp y-- lp2 side 0"
|
"jmp y-- lp2 side 0"
|
||||||
|
|
||||||
// wait for event and irq host
|
// wait for event and irq host
|
||||||
"wait 1 pin 0 side 0"
|
"wait 1 pin 0 side 0"
|
||||||
"irq 0 side 0"
|
"irq 0 side 0"
|
||||||
|
|
||||||
".wrap"
|
".wrap"
|
||||||
);
|
);
|
||||||
|
common.load_program(&default_program.program)
|
||||||
|
};
|
||||||
|
|
||||||
let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);
|
let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);
|
||||||
pin_io.set_pull(Pull::None);
|
pin_io.set_pull(Pull::None);
|
||||||
@ -101,7 +121,6 @@ where
|
|||||||
pin_clk.set_slew_rate(SlewRate::Fast);
|
pin_clk.set_slew_rate(SlewRate::Fast);
|
||||||
|
|
||||||
let mut cfg = Config::default();
|
let mut cfg = Config::default();
|
||||||
let loaded_program = common.load_program(&program.program);
|
|
||||||
cfg.use_program(&loaded_program, &[&pin_clk]);
|
cfg.use_program(&loaded_program, &[&pin_clk]);
|
||||||
cfg.set_out_pins(&[&pin_io]);
|
cfg.set_out_pins(&[&pin_io]);
|
||||||
cfg.set_in_pins(&[&pin_io]);
|
cfg.set_in_pins(&[&pin_io]);
|
||||||
@ -112,25 +131,7 @@ where
|
|||||||
cfg.shift_in.direction = ShiftDirection::Left;
|
cfg.shift_in.direction = ShiftDirection::Left;
|
||||||
cfg.shift_in.auto_fill = true;
|
cfg.shift_in.auto_fill = true;
|
||||||
//cfg.shift_in.threshold = 32;
|
//cfg.shift_in.threshold = 32;
|
||||||
|
cfg.clock_divider = clock_divider;
|
||||||
#[cfg(feature = "overclock")]
|
|
||||||
{
|
|
||||||
// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to
|
|
||||||
// data sheet, but seems to work fine.
|
|
||||||
cfg.clock_divider = FixedU32::from_bits(0x0100);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "overclock"))]
|
|
||||||
{
|
|
||||||
// same speed as pico-sdk, 62.5Mhz
|
|
||||||
// This is actually the fastest we can go without overclocking.
|
|
||||||
// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq.
|
|
||||||
// However, the PIO uses a fractional divider, which works by introducing jitter when
|
|
||||||
// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz
|
|
||||||
// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles
|
|
||||||
// violate the maximum from the data sheet.
|
|
||||||
cfg.clock_divider = FixedU32::from_bits(0x0200);
|
|
||||||
}
|
|
||||||
|
|
||||||
sm.set_config(&cfg);
|
sm.set_config(&cfg);
|
||||||
|
|
||||||
@ -141,7 +142,7 @@ where
|
|||||||
cs,
|
cs,
|
||||||
sm,
|
sm,
|
||||||
irq,
|
irq,
|
||||||
dma: dma.into_ref(),
|
dma: dma,
|
||||||
wrap_target: loaded_program.wrap.target,
|
wrap_target: loaded_program.wrap.target,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,20 +157,20 @@ where
|
|||||||
defmt::trace!("write={} read={}", write_bits, read_bits);
|
defmt::trace!("write={} read={}", write_bits, read_bits);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
instr::set_x(&mut self.sm, write_bits as u32);
|
self.sm.set_x(write_bits as u32);
|
||||||
instr::set_y(&mut self.sm, read_bits as u32);
|
self.sm.set_y(read_bits as u32);
|
||||||
instr::set_pindir(&mut self.sm, 0b1);
|
self.sm.set_pindir(0b1);
|
||||||
instr::exec_jmp(&mut self.sm, self.wrap_target);
|
self.sm.exec_jmp(self.wrap_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sm.set_enable(true);
|
self.sm.set_enable(true);
|
||||||
|
|
||||||
self.sm.tx().dma_push(self.dma.reborrow(), write).await;
|
self.sm.tx().dma_push(self.dma.reborrow(), write, false).await;
|
||||||
|
|
||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
self.sm
|
self.sm
|
||||||
.rx()
|
.rx()
|
||||||
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
|
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false)
|
||||||
.await;
|
.await;
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
@ -187,22 +188,25 @@ where
|
|||||||
defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len());
|
defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len());
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
instr::set_y(&mut self.sm, read_bits as u32);
|
self.sm.set_y(read_bits as u32);
|
||||||
instr::set_x(&mut self.sm, write_bits as u32);
|
self.sm.set_x(write_bits as u32);
|
||||||
instr::set_pindir(&mut self.sm, 0b1);
|
self.sm.set_pindir(0b1);
|
||||||
instr::exec_jmp(&mut self.sm, self.wrap_target);
|
self.sm.exec_jmp(self.wrap_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// self.cs.set_low();
|
// self.cs.set_low();
|
||||||
self.sm.set_enable(true);
|
self.sm.set_enable(true);
|
||||||
|
|
||||||
self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await;
|
self.sm
|
||||||
self.sm.rx().dma_pull(self.dma.reborrow(), read).await;
|
.tx()
|
||||||
|
.dma_push(self.dma.reborrow(), slice::from_ref(&cmd), false)
|
||||||
|
.await;
|
||||||
|
self.sm.rx().dma_pull(self.dma.reborrow(), read, false).await;
|
||||||
|
|
||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
self.sm
|
self.sm
|
||||||
.rx()
|
.rx()
|
||||||
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
|
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
#[cfg(feature = "defmt")]
|
||||||
|
|||||||
@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.3.0 - 2025-01-05
|
||||||
|
|
||||||
|
- Update `embassy-time` to 0.4.0
|
||||||
|
- Add Bluetooth support.
|
||||||
|
- Add WPA3 support.
|
||||||
|
- Expand wifi security configuration options.
|
||||||
|
|
||||||
## 0.2.0 - 2024-08-05
|
## 0.2.0 - 2024-08-05
|
||||||
|
|
||||||
- Update to new versions of embassy-{time,sync}
|
- Update to new versions of embassy-{time,sync}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cyw43"
|
name = "cyw43"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W."
|
description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W."
|
||||||
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
|
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
|
||||||
@ -18,12 +18,12 @@ bluetooth = ["dep:bt-hci", "dep:embedded-io-async"]
|
|||||||
firmware-logs = []
|
firmware-logs = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy-time = { version = "0.3.2", path = "../embassy-time"}
|
embassy-time = { version = "0.4.0", path = "../embassy-time"}
|
||||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
|
embassy-sync = { version = "0.7.0", path = "../embassy-sync"}
|
||||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
|
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
|
||||||
|
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
log = { version = "0.4.17", optional = true }
|
log = { version = "0.4.17", optional = true }
|
||||||
|
|
||||||
cortex-m = "0.7.6"
|
cortex-m = "0.7.6"
|
||||||
@ -36,7 +36,7 @@ heapless = "0.8.0"
|
|||||||
|
|
||||||
# Bluetooth deps
|
# Bluetooth deps
|
||||||
embedded-io-async = { version = "0.6.0", optional = true }
|
embedded-io-async = { version = "0.6.0", optional = true }
|
||||||
bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3", optional = true, default-features = false }
|
bt-hci = { version = "0.3.0", optional = true }
|
||||||
|
|
||||||
[package.metadata.embassy_docs]
|
[package.metadata.embassy_docs]
|
||||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
|
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
|
||||||
|
|||||||
@ -1,27 +1,36 @@
|
|||||||
# cyw43
|
# cyw43
|
||||||
|
|
||||||
Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
|
Rust driver for the CYW43439 wifi+bluetooth chip. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
|
||||||
|
|
||||||
## Current status
|
Works on the following boards:
|
||||||
|
|
||||||
|
- Raspberry Pi Pico W (RP2040)
|
||||||
|
- Raspberry Pi Pico 2 W (RP2350A)
|
||||||
|
- Pimoroni Pico Plus 2 W (RP2350B)
|
||||||
|
- Any board with Raspberry Pi RM2 radio module.
|
||||||
|
- Any board with the CYW43439 chip, and possibly others if the protocol is similar enough.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
Working:
|
Working:
|
||||||
|
|
||||||
- Station mode (joining an AP).
|
- WiFi support
|
||||||
- AP mode (creating an AP)
|
- Station mode (joining an AP).
|
||||||
- Scanning
|
- AP mode (creating an AP)
|
||||||
- Sending and receiving Ethernet frames.
|
- Scanning
|
||||||
- Using the default MAC address.
|
- Sending and receiving Ethernet frames.
|
||||||
- [`embassy-net`](https://embassy.dev) integration.
|
- Using the default MAC address.
|
||||||
- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W.
|
- [`embassy-net`](https://embassy.dev) integration.
|
||||||
- Using IRQ for device events
|
- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W.
|
||||||
- GPIO support (for LED on the Pico W)
|
- Using IRQ for device events, no busy polling.
|
||||||
|
- GPIO support (for LED on the Pico W).
|
||||||
|
- Bluetooth support
|
||||||
|
- Bluetooth Classic + LE HCI commands.
|
||||||
|
- Concurrent operation with WiFi.
|
||||||
|
- Implements the [bt-hci](https://crates.io/crates/bt-hci) controller traits.
|
||||||
|
- Works with the [TrouBLE](https://github.com/embassy-rs/trouble) bluetooth LE stack. Check its repo for examples using `cyw43`.
|
||||||
|
|
||||||
TODO:
|
## Running the WiFi examples
|
||||||
|
|
||||||
- Setting a custom MAC address.
|
|
||||||
- Bus sleep (for power consumption optimization)
|
|
||||||
|
|
||||||
## Running the examples
|
|
||||||
|
|
||||||
- Install `probe-rs` following the instructions at <https://probe.rs>.
|
- Install `probe-rs` following the instructions at <https://probe.rs>.
|
||||||
- `cd examples/rp`
|
- `cd examples/rp`
|
||||||
|
|||||||
@ -113,17 +113,6 @@ pub(crate) const IRQ_F1_INTR: u16 = 0x2000;
|
|||||||
pub(crate) const IRQ_F2_INTR: u16 = 0x4000;
|
pub(crate) const IRQ_F2_INTR: u16 = 0x4000;
|
||||||
pub(crate) const IRQ_F3_INTR: u16 = 0x8000;
|
pub(crate) const IRQ_F3_INTR: u16 = 0x8000;
|
||||||
|
|
||||||
pub(crate) const IOCTL_CMD_UP: u32 = 2;
|
|
||||||
pub(crate) const IOCTL_CMD_DOWN: u32 = 3;
|
|
||||||
pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26;
|
|
||||||
pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30;
|
|
||||||
pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52;
|
|
||||||
pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64;
|
|
||||||
pub(crate) const IOCTL_CMD_SET_AP: u32 = 118;
|
|
||||||
pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263;
|
|
||||||
pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262;
|
|
||||||
pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268;
|
|
||||||
|
|
||||||
pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0;
|
pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0;
|
||||||
pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1;
|
pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1;
|
||||||
pub(crate) const CHANNEL_TYPE_DATA: u8 = 2;
|
pub(crate) const CHANNEL_TYPE_DATA: u8 = 2;
|
||||||
@ -376,3 +365,306 @@ impl core::fmt::Display for FormatInterrupt {
|
|||||||
core::fmt::Debug::fmt(self, f)
|
core::fmt::Debug::fmt(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub(crate) enum Ioctl {
|
||||||
|
GetMagic = 0,
|
||||||
|
GetVersion = 1,
|
||||||
|
Up = 2,
|
||||||
|
Down = 3,
|
||||||
|
GetLoop = 4,
|
||||||
|
SetLoop = 5,
|
||||||
|
Dump = 6,
|
||||||
|
GetMsglevel = 7,
|
||||||
|
SetMsglevel = 8,
|
||||||
|
GetPromisc = 9,
|
||||||
|
SetPromisc = 10,
|
||||||
|
GetRate = 12,
|
||||||
|
GetInstance = 14,
|
||||||
|
GetInfra = 19,
|
||||||
|
SetInfra = 20,
|
||||||
|
GetAuth = 21,
|
||||||
|
SetAuth = 22,
|
||||||
|
GetBssid = 23,
|
||||||
|
SetBssid = 24,
|
||||||
|
GetSsid = 25,
|
||||||
|
SetSsid = 26,
|
||||||
|
Restart = 27,
|
||||||
|
GetChannel = 29,
|
||||||
|
SetChannel = 30,
|
||||||
|
GetSrl = 31,
|
||||||
|
SetSrl = 32,
|
||||||
|
GetLrl = 33,
|
||||||
|
SetLrl = 34,
|
||||||
|
GetPlcphdr = 35,
|
||||||
|
SetPlcphdr = 36,
|
||||||
|
GetRadio = 37,
|
||||||
|
SetRadio = 38,
|
||||||
|
GetPhytype = 39,
|
||||||
|
DumpRate = 40,
|
||||||
|
SetRateParams = 41,
|
||||||
|
GetKey = 44,
|
||||||
|
SetKey = 45,
|
||||||
|
GetRegulatory = 46,
|
||||||
|
SetRegulatory = 47,
|
||||||
|
GetPassiveScan = 48,
|
||||||
|
SetPassiveScan = 49,
|
||||||
|
Scan = 50,
|
||||||
|
ScanResults = 51,
|
||||||
|
Disassoc = 52,
|
||||||
|
Reassoc = 53,
|
||||||
|
GetRoamTrigger = 54,
|
||||||
|
SetRoamTrigger = 55,
|
||||||
|
GetRoamDelta = 56,
|
||||||
|
SetRoamDelta = 57,
|
||||||
|
GetRoamScanPeriod = 58,
|
||||||
|
SetRoamScanPeriod = 59,
|
||||||
|
Evm = 60,
|
||||||
|
GetTxant = 61,
|
||||||
|
SetTxant = 62,
|
||||||
|
GetAntdiv = 63,
|
||||||
|
SetAntdiv = 64,
|
||||||
|
GetClosed = 67,
|
||||||
|
SetClosed = 68,
|
||||||
|
GetMaclist = 69,
|
||||||
|
SetMaclist = 70,
|
||||||
|
GetRateset = 71,
|
||||||
|
SetRateset = 72,
|
||||||
|
Longtrain = 74,
|
||||||
|
GetBcnprd = 75,
|
||||||
|
SetBcnprd = 76,
|
||||||
|
GetDtimprd = 77,
|
||||||
|
SetDtimprd = 78,
|
||||||
|
GetSrom = 79,
|
||||||
|
SetSrom = 80,
|
||||||
|
GetWepRestrict = 81,
|
||||||
|
SetWepRestrict = 82,
|
||||||
|
GetCountry = 83,
|
||||||
|
SetCountry = 84,
|
||||||
|
GetPm = 85,
|
||||||
|
SetPm = 86,
|
||||||
|
GetWake = 87,
|
||||||
|
SetWake = 88,
|
||||||
|
GetForcelink = 90,
|
||||||
|
SetForcelink = 91,
|
||||||
|
FreqAccuracy = 92,
|
||||||
|
CarrierSuppress = 93,
|
||||||
|
GetPhyreg = 94,
|
||||||
|
SetPhyreg = 95,
|
||||||
|
GetRadioreg = 96,
|
||||||
|
SetRadioreg = 97,
|
||||||
|
GetRevinfo = 98,
|
||||||
|
GetUcantdiv = 99,
|
||||||
|
SetUcantdiv = 100,
|
||||||
|
RReg = 101,
|
||||||
|
WReg = 102,
|
||||||
|
GetMacmode = 105,
|
||||||
|
SetMacmode = 106,
|
||||||
|
GetMonitor = 107,
|
||||||
|
SetMonitor = 108,
|
||||||
|
GetGmode = 109,
|
||||||
|
SetGmode = 110,
|
||||||
|
GetLegacyErp = 111,
|
||||||
|
SetLegacyErp = 112,
|
||||||
|
GetRxAnt = 113,
|
||||||
|
GetCurrRateset = 114,
|
||||||
|
GetScansuppress = 115,
|
||||||
|
SetScansuppress = 116,
|
||||||
|
GetAp = 117,
|
||||||
|
SetAp = 118,
|
||||||
|
GetEapRestrict = 119,
|
||||||
|
SetEapRestrict = 120,
|
||||||
|
ScbAuthorize = 121,
|
||||||
|
ScbDeauthorize = 122,
|
||||||
|
GetWdslist = 123,
|
||||||
|
SetWdslist = 124,
|
||||||
|
GetAtim = 125,
|
||||||
|
SetAtim = 126,
|
||||||
|
GetRssi = 127,
|
||||||
|
GetPhyantdiv = 128,
|
||||||
|
SetPhyantdiv = 129,
|
||||||
|
ApRxOnly = 130,
|
||||||
|
GetTxPathPwr = 131,
|
||||||
|
SetTxPathPwr = 132,
|
||||||
|
GetWsec = 133,
|
||||||
|
SetWsec = 134,
|
||||||
|
GetPhyNoise = 135,
|
||||||
|
GetBssInfo = 136,
|
||||||
|
GetPktcnts = 137,
|
||||||
|
GetLazywds = 138,
|
||||||
|
SetLazywds = 139,
|
||||||
|
GetBandlist = 140,
|
||||||
|
GetBand = 141,
|
||||||
|
SetBand = 142,
|
||||||
|
ScbDeauthenticate = 143,
|
||||||
|
GetShortslot = 144,
|
||||||
|
GetShortslotOverride = 145,
|
||||||
|
SetShortslotOverride = 146,
|
||||||
|
GetShortslotRestrict = 147,
|
||||||
|
SetShortslotRestrict = 148,
|
||||||
|
GetGmodeProtection = 149,
|
||||||
|
GetGmodeProtectionOverride = 150,
|
||||||
|
SetGmodeProtectionOverride = 151,
|
||||||
|
Upgrade = 152,
|
||||||
|
GetIgnoreBcns = 155,
|
||||||
|
SetIgnoreBcns = 156,
|
||||||
|
GetScbTimeout = 157,
|
||||||
|
SetScbTimeout = 158,
|
||||||
|
GetAssoclist = 159,
|
||||||
|
GetClk = 160,
|
||||||
|
SetClk = 161,
|
||||||
|
GetUp = 162,
|
||||||
|
Out = 163,
|
||||||
|
GetWpaAuth = 164,
|
||||||
|
SetWpaAuth = 165,
|
||||||
|
GetUcflags = 166,
|
||||||
|
SetUcflags = 167,
|
||||||
|
GetPwridx = 168,
|
||||||
|
SetPwridx = 169,
|
||||||
|
GetTssi = 170,
|
||||||
|
GetSupRatesetOverride = 171,
|
||||||
|
SetSupRatesetOverride = 172,
|
||||||
|
GetProtectionControl = 178,
|
||||||
|
SetProtectionControl = 179,
|
||||||
|
GetPhylist = 180,
|
||||||
|
EncryptStrength = 181,
|
||||||
|
DecryptStatus = 182,
|
||||||
|
GetKeySeq = 183,
|
||||||
|
GetScanChannelTime = 184,
|
||||||
|
SetScanChannelTime = 185,
|
||||||
|
GetScanUnassocTime = 186,
|
||||||
|
SetScanUnassocTime = 187,
|
||||||
|
GetScanHomeTime = 188,
|
||||||
|
SetScanHomeTime = 189,
|
||||||
|
GetScanNprobes = 190,
|
||||||
|
SetScanNprobes = 191,
|
||||||
|
GetPrbRespTimeout = 192,
|
||||||
|
SetPrbRespTimeout = 193,
|
||||||
|
GetAtten = 194,
|
||||||
|
SetAtten = 195,
|
||||||
|
GetShmem = 196,
|
||||||
|
SetShmem = 197,
|
||||||
|
SetWsecTest = 200,
|
||||||
|
ScbDeauthenticateForReason = 201,
|
||||||
|
TkipCountermeasures = 202,
|
||||||
|
GetPiomode = 203,
|
||||||
|
SetPiomode = 204,
|
||||||
|
SetAssocPrefer = 205,
|
||||||
|
GetAssocPrefer = 206,
|
||||||
|
SetRoamPrefer = 207,
|
||||||
|
GetRoamPrefer = 208,
|
||||||
|
SetLed = 209,
|
||||||
|
GetLed = 210,
|
||||||
|
GetInterferenceMode = 211,
|
||||||
|
SetInterferenceMode = 212,
|
||||||
|
GetChannelQa = 213,
|
||||||
|
StartChannelQa = 214,
|
||||||
|
GetChannelSel = 215,
|
||||||
|
StartChannelSel = 216,
|
||||||
|
GetValidChannels = 217,
|
||||||
|
GetFakefrag = 218,
|
||||||
|
SetFakefrag = 219,
|
||||||
|
GetPwroutPercentage = 220,
|
||||||
|
SetPwroutPercentage = 221,
|
||||||
|
SetBadFramePreempt = 222,
|
||||||
|
GetBadFramePreempt = 223,
|
||||||
|
SetLeapList = 224,
|
||||||
|
GetLeapList = 225,
|
||||||
|
GetCwmin = 226,
|
||||||
|
SetCwmin = 227,
|
||||||
|
GetCwmax = 228,
|
||||||
|
SetCwmax = 229,
|
||||||
|
GetWet = 230,
|
||||||
|
SetWet = 231,
|
||||||
|
GetPub = 232,
|
||||||
|
GetKeyPrimary = 235,
|
||||||
|
SetKeyPrimary = 236,
|
||||||
|
GetAciArgs = 238,
|
||||||
|
SetAciArgs = 239,
|
||||||
|
UnsetCallback = 240,
|
||||||
|
SetCallback = 241,
|
||||||
|
GetRadar = 242,
|
||||||
|
SetRadar = 243,
|
||||||
|
SetSpectManagment = 244,
|
||||||
|
GetSpectManagment = 245,
|
||||||
|
WdsGetRemoteHwaddr = 246,
|
||||||
|
WdsGetWpaSup = 247,
|
||||||
|
SetCsScanTimer = 248,
|
||||||
|
GetCsScanTimer = 249,
|
||||||
|
MeasureRequest = 250,
|
||||||
|
Init = 251,
|
||||||
|
SendQuiet = 252,
|
||||||
|
Keepalive = 253,
|
||||||
|
SendPwrConstraint = 254,
|
||||||
|
UpgradeStatus = 255,
|
||||||
|
CurrentPwr = 256,
|
||||||
|
GetScanPassiveTime = 257,
|
||||||
|
SetScanPassiveTime = 258,
|
||||||
|
LegacyLinkBehavior = 259,
|
||||||
|
GetChannelsInCountry = 260,
|
||||||
|
GetCountryList = 261,
|
||||||
|
GetVar = 262,
|
||||||
|
SetVar = 263,
|
||||||
|
NvramGet = 264,
|
||||||
|
NvramSet = 265,
|
||||||
|
NvramDump = 266,
|
||||||
|
Reboot = 267,
|
||||||
|
SetWsecPmk = 268,
|
||||||
|
GetAuthMode = 269,
|
||||||
|
SetAuthMode = 270,
|
||||||
|
GetWakeentry = 271,
|
||||||
|
SetWakeentry = 272,
|
||||||
|
NdconfigItem = 273,
|
||||||
|
Nvotpw = 274,
|
||||||
|
Otpw = 275,
|
||||||
|
IovBlockGet = 276,
|
||||||
|
IovModulesGet = 277,
|
||||||
|
SoftReset = 278,
|
||||||
|
GetAllowMode = 279,
|
||||||
|
SetAllowMode = 280,
|
||||||
|
GetDesiredBssid = 281,
|
||||||
|
SetDesiredBssid = 282,
|
||||||
|
DisassocMyap = 283,
|
||||||
|
GetNbands = 284,
|
||||||
|
GetBandstates = 285,
|
||||||
|
GetWlcBssInfo = 286,
|
||||||
|
GetAssocInfo = 287,
|
||||||
|
GetOidPhy = 288,
|
||||||
|
SetOidPhy = 289,
|
||||||
|
SetAssocTime = 290,
|
||||||
|
GetDesiredSsid = 291,
|
||||||
|
GetChanspec = 292,
|
||||||
|
GetAssocState = 293,
|
||||||
|
SetPhyState = 294,
|
||||||
|
GetScanPending = 295,
|
||||||
|
GetScanreqPending = 296,
|
||||||
|
GetPrevRoamReason = 297,
|
||||||
|
SetPrevRoamReason = 298,
|
||||||
|
GetBandstatesPi = 299,
|
||||||
|
GetPhyState = 300,
|
||||||
|
GetBssWpaRsn = 301,
|
||||||
|
GetBssWpa2Rsn = 302,
|
||||||
|
GetBssBcnTs = 303,
|
||||||
|
GetIntDisassoc = 304,
|
||||||
|
SetNumPeers = 305,
|
||||||
|
GetNumBss = 306,
|
||||||
|
GetWsecPmk = 318,
|
||||||
|
GetRandomBytes = 319,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const WSEC_TKIP: u32 = 0x02;
|
||||||
|
pub(crate) const WSEC_AES: u32 = 0x04;
|
||||||
|
|
||||||
|
pub(crate) const AUTH_OPEN: u32 = 0x00;
|
||||||
|
pub(crate) const AUTH_SAE: u32 = 0x03;
|
||||||
|
|
||||||
|
pub(crate) const MFP_NONE: u32 = 0;
|
||||||
|
pub(crate) const MFP_CAPABLE: u32 = 1;
|
||||||
|
pub(crate) const MFP_REQUIRED: u32 = 2;
|
||||||
|
|
||||||
|
pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000;
|
||||||
|
pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004;
|
||||||
|
pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080;
|
||||||
|
pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000;
|
||||||
|
|||||||
@ -35,16 +35,22 @@ pub struct Control<'a> {
|
|||||||
ioctl_state: &'a IoctlState,
|
ioctl_state: &'a IoctlState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
/// WiFi scan type.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum ScanType {
|
pub enum ScanType {
|
||||||
|
/// Active scan: the station actively transmits probes that make APs respond.
|
||||||
|
/// Faster, but uses more power.
|
||||||
Active,
|
Active,
|
||||||
|
/// Passive scan: the station doesn't transmit any probes, just listens for beacons.
|
||||||
|
/// Slower, but uses less power.
|
||||||
Passive,
|
Passive,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scan options.
|
/// Scan options.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct ScanOptions {
|
pub struct ScanOptions {
|
||||||
/// SSID to scan for.
|
/// SSID to scan for.
|
||||||
pub ssid: Option<heapless::String<32>>,
|
pub ssid: Option<heapless::String<32>>,
|
||||||
@ -74,6 +80,79 @@ impl Default for ScanOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Authentication type, used in [`JoinOptions::auth`].
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum JoinAuth {
|
||||||
|
/// Open network
|
||||||
|
Open,
|
||||||
|
/// WPA only
|
||||||
|
Wpa,
|
||||||
|
/// WPA2 only
|
||||||
|
Wpa2,
|
||||||
|
/// WPA3 only
|
||||||
|
Wpa3,
|
||||||
|
/// WPA2 + WPA3
|
||||||
|
Wpa2Wpa3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Options for [`Control::join`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct JoinOptions<'a> {
|
||||||
|
/// Authentication type. Default `Wpa2Wpa3`.
|
||||||
|
pub auth: JoinAuth,
|
||||||
|
/// Enable TKIP encryption. Default false.
|
||||||
|
pub cipher_tkip: bool,
|
||||||
|
/// Enable AES encryption. Default true.
|
||||||
|
pub cipher_aes: bool,
|
||||||
|
/// Passphrase. Default empty.
|
||||||
|
pub passphrase: &'a [u8],
|
||||||
|
/// If false, `passphrase` is the human-readable passphrase string.
|
||||||
|
/// If true, `passphrase` is the result of applying the PBKDF2 hash to the
|
||||||
|
/// passphrase string. This makes it possible to avoid storing unhashed passwords.
|
||||||
|
///
|
||||||
|
/// This is not compatible with WPA3.
|
||||||
|
/// Default false.
|
||||||
|
pub passphrase_is_prehashed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> JoinOptions<'a> {
|
||||||
|
/// Create a new `JoinOptions` for joining open networks.
|
||||||
|
pub fn new_open() -> Self {
|
||||||
|
Self {
|
||||||
|
auth: JoinAuth::Open,
|
||||||
|
cipher_tkip: false,
|
||||||
|
cipher_aes: false,
|
||||||
|
passphrase: &[],
|
||||||
|
passphrase_is_prehashed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `JoinOptions` for joining encrypted networks.
|
||||||
|
///
|
||||||
|
/// Defaults to supporting WPA2+WPA3 with AES only, you may edit
|
||||||
|
/// the returned options to change this.
|
||||||
|
pub fn new(passphrase: &'a [u8]) -> Self {
|
||||||
|
let mut this = Self::default();
|
||||||
|
this.passphrase = passphrase;
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for JoinOptions<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
auth: JoinAuth::Wpa2Wpa3,
|
||||||
|
cipher_tkip: false,
|
||||||
|
cipher_aes: true,
|
||||||
|
passphrase: &[],
|
||||||
|
passphrase_is_prehashed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Control<'a> {
|
impl<'a> Control<'a> {
|
||||||
pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self {
|
pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -109,7 +188,7 @@ impl<'a> Control<'a> {
|
|||||||
buf[0..8].copy_from_slice(b"clmload\x00");
|
buf[0..8].copy_from_slice(b"clmload\x00");
|
||||||
buf[8..20].copy_from_slice(&header.to_bytes());
|
buf[8..20].copy_from_slice(&header.to_bytes());
|
||||||
buf[20..][..chunk.len()].copy_from_slice(&chunk);
|
buf[20..][..chunk.len()].copy_from_slice(&chunk);
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()])
|
self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()])
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +224,7 @@ impl<'a> Control<'a> {
|
|||||||
Timer::after_millis(100).await;
|
Timer::after_millis(100).await;
|
||||||
|
|
||||||
// Set antenna to chip antenna
|
// Set antenna to chip antenna
|
||||||
self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await;
|
self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await;
|
||||||
|
|
||||||
self.set_iovar_u32("bus:txglom", 0).await;
|
self.set_iovar_u32("bus:txglom", 0).await;
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(100).await;
|
||||||
@ -183,8 +262,8 @@ impl<'a> Control<'a> {
|
|||||||
|
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(100).await;
|
||||||
|
|
||||||
self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto
|
self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto
|
||||||
self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any
|
self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any
|
||||||
|
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(100).await;
|
||||||
|
|
||||||
@ -195,12 +274,12 @@ impl<'a> Control<'a> {
|
|||||||
|
|
||||||
/// Set the WiFi interface up.
|
/// Set the WiFi interface up.
|
||||||
async fn up(&mut self) {
|
async fn up(&mut self) {
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
|
self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the interface down.
|
/// Set the interface down.
|
||||||
async fn down(&mut self) {
|
async fn down(&mut self) {
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
|
self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set power management mode.
|
/// Set power management mode.
|
||||||
@ -213,17 +292,74 @@ impl<'a> Control<'a> {
|
|||||||
self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await;
|
self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await;
|
||||||
self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await;
|
self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await;
|
||||||
}
|
}
|
||||||
self.ioctl_set_u32(86, 0, mode_num).await;
|
self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join an unprotected network with the provided ssid.
|
/// Join an unprotected network with the provided ssid.
|
||||||
pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> {
|
pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> {
|
||||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||||
|
|
||||||
self.ioctl_set_u32(134, 0, 0).await; // wsec = open
|
if options.auth == JoinAuth::Open {
|
||||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
|
self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await;
|
||||||
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
|
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
|
||||||
self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0)
|
self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await;
|
||||||
|
self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await;
|
||||||
|
self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await;
|
||||||
|
} else {
|
||||||
|
let mut wsec = 0;
|
||||||
|
if options.cipher_aes {
|
||||||
|
wsec |= WSEC_AES;
|
||||||
|
}
|
||||||
|
if options.cipher_tkip {
|
||||||
|
wsec |= WSEC_TKIP;
|
||||||
|
}
|
||||||
|
self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await;
|
||||||
|
|
||||||
|
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await;
|
||||||
|
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
|
||||||
|
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
|
||||||
|
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
|
||||||
|
let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth {
|
||||||
|
JoinAuth::Open => unreachable!(),
|
||||||
|
JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK),
|
||||||
|
JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK),
|
||||||
|
JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK),
|
||||||
|
JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK),
|
||||||
|
};
|
||||||
|
|
||||||
|
if wpa12 {
|
||||||
|
let mut flags = 0;
|
||||||
|
if !options.passphrase_is_prehashed {
|
||||||
|
flags |= 1;
|
||||||
|
}
|
||||||
|
let mut pfi = PassphraseInfo {
|
||||||
|
len: options.passphrase.len() as _,
|
||||||
|
flags,
|
||||||
|
passphrase: [0; 64],
|
||||||
|
};
|
||||||
|
pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase);
|
||||||
|
Timer::after_millis(3).await;
|
||||||
|
self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if wpa3 {
|
||||||
|
let mut pfi = SaePassphraseInfo {
|
||||||
|
len: options.passphrase.len() as _,
|
||||||
|
passphrase: [0; 128],
|
||||||
|
};
|
||||||
|
pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase);
|
||||||
|
Timer::after_millis(3).await;
|
||||||
|
self.set_iovar("sae_password", &pfi.to_bytes()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await;
|
||||||
|
self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await;
|
||||||
|
self.set_iovar_u32("mfp", mfp).await;
|
||||||
|
self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await;
|
||||||
|
}
|
||||||
|
|
||||||
let mut i = SsidInfo {
|
let mut i = SsidInfo {
|
||||||
len: ssid.len() as _,
|
len: ssid.len() as _,
|
||||||
@ -234,69 +370,13 @@ impl<'a> Control<'a> {
|
|||||||
self.wait_for_join(i).await
|
self.wait_for_join(i).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join a protected network with the provided ssid and [`PassphraseInfo`].
|
|
||||||
async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> {
|
|
||||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
|
||||||
|
|
||||||
self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2
|
|
||||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await;
|
|
||||||
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
|
|
||||||
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
|
|
||||||
|
|
||||||
Timer::after_millis(100).await;
|
|
||||||
|
|
||||||
self.ioctl(
|
|
||||||
IoctlType::Set,
|
|
||||||
IOCTL_CMD_SET_PASSPHRASE,
|
|
||||||
0,
|
|
||||||
&mut passphrase_info.to_bytes(),
|
|
||||||
)
|
|
||||||
.await; // WLC_SET_WSEC_PMK
|
|
||||||
|
|
||||||
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
|
|
||||||
self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open)
|
|
||||||
self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth
|
|
||||||
|
|
||||||
let mut i = SsidInfo {
|
|
||||||
len: ssid.len() as _,
|
|
||||||
ssid: [0; 32],
|
|
||||||
};
|
|
||||||
i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
|
|
||||||
|
|
||||||
self.wait_for_join(i).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join a protected network with the provided ssid and passphrase.
|
|
||||||
pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> {
|
|
||||||
let mut pfi = PassphraseInfo {
|
|
||||||
len: passphrase.len() as _,
|
|
||||||
flags: 1,
|
|
||||||
passphrase: [0; 64],
|
|
||||||
};
|
|
||||||
pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes());
|
|
||||||
self.join_wpa2_passphrase_info(ssid, &pfi).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join a protected network with the provided ssid and precomputed PSK.
|
|
||||||
pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> {
|
|
||||||
let mut pfi = PassphraseInfo {
|
|
||||||
len: psk.len() as _,
|
|
||||||
flags: 0,
|
|
||||||
passphrase: [0; 64],
|
|
||||||
};
|
|
||||||
pfi.passphrase[..psk.len()].copy_from_slice(psk);
|
|
||||||
self.join_wpa2_passphrase_info(ssid, &pfi).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> {
|
async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> {
|
||||||
self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]);
|
self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]);
|
||||||
let mut subscriber = self.events.queue.subscriber().unwrap();
|
let mut subscriber = self.events.queue.subscriber().unwrap();
|
||||||
// the actual join operation starts here
|
// the actual join operation starts here
|
||||||
// we make sure to enable events before so we don't miss any
|
// we make sure to enable events before so we don't miss any
|
||||||
|
|
||||||
// set_ssid
|
self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await;
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// to complete the join, we wait for a SET_SSID event
|
// to complete the join, we wait for a SET_SSID event
|
||||||
// we also save the AUTH status for the user, it may be interesting
|
// we also save the AUTH status for the user, it may be interesting
|
||||||
@ -357,7 +437,7 @@ impl<'a> Control<'a> {
|
|||||||
self.up().await;
|
self.up().await;
|
||||||
|
|
||||||
// Turn on AP mode
|
// Turn on AP mode
|
||||||
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await;
|
self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await;
|
||||||
|
|
||||||
// Set SSID
|
// Set SSID
|
||||||
let mut i = SsidInfoWithIndex {
|
let mut i = SsidInfoWithIndex {
|
||||||
@ -371,7 +451,7 @@ impl<'a> Control<'a> {
|
|||||||
self.set_iovar("bsscfg:ssid", &i.to_bytes()).await;
|
self.set_iovar("bsscfg:ssid", &i.to_bytes()).await;
|
||||||
|
|
||||||
// Set channel number
|
// Set channel number
|
||||||
self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await;
|
self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await;
|
||||||
|
|
||||||
// Set security
|
// Set security
|
||||||
self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await;
|
self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await;
|
||||||
@ -388,7 +468,7 @@ impl<'a> Control<'a> {
|
|||||||
passphrase: [0; 64],
|
passphrase: [0; 64],
|
||||||
};
|
};
|
||||||
pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes());
|
pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes());
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes())
|
self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +485,7 @@ impl<'a> Control<'a> {
|
|||||||
self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN
|
self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN
|
||||||
|
|
||||||
// Turn off AP mode
|
// Turn off AP mode
|
||||||
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 0).await;
|
self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await;
|
||||||
|
|
||||||
// Temporarily set wifi down
|
// Temporarily set wifi down
|
||||||
self.down().await;
|
self.down().await;
|
||||||
@ -451,7 +531,7 @@ impl<'a> Control<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the list of configured multicast hardware addresses.
|
/// Retrieve the list of configured multicast hardware addresses.
|
||||||
pub async fn list_mulistcast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize {
|
pub async fn list_multicast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize {
|
||||||
let mut buf = [0; 64];
|
let mut buf = [0; 64];
|
||||||
self.get_iovar("mcast_list", &mut buf).await;
|
self.get_iovar("mcast_list", &mut buf).await;
|
||||||
|
|
||||||
@ -484,11 +564,11 @@ impl<'a> Control<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn set_iovar(&mut self, name: &str, val: &[u8]) {
|
async fn set_iovar(&mut self, name: &str, val: &[u8]) {
|
||||||
self.set_iovar_v::<64>(name, val).await
|
self.set_iovar_v::<196>(name, val).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
|
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
|
||||||
debug!("set {} = {:02x}", name, Bytes(val));
|
debug!("iovar set {} = {:02x}", name, Bytes(val));
|
||||||
|
|
||||||
let mut buf = [0; BUFSIZE];
|
let mut buf = [0; BUFSIZE];
|
||||||
buf[..name.len()].copy_from_slice(name.as_bytes());
|
buf[..name.len()].copy_from_slice(name.as_bytes());
|
||||||
@ -496,13 +576,13 @@ impl<'a> Control<'a> {
|
|||||||
buf[name.len() + 1..][..val.len()].copy_from_slice(val);
|
buf[name.len() + 1..][..val.len()].copy_from_slice(val);
|
||||||
|
|
||||||
let total_len = name.len() + 1 + val.len();
|
let total_len = name.len() + 1 + val.len();
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len])
|
self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len])
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is not really working, it always returns all zeros.
|
// TODO this is not really working, it always returns all zeros.
|
||||||
async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize {
|
async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize {
|
||||||
debug!("get {}", name);
|
debug!("iovar get {}", name);
|
||||||
|
|
||||||
let mut buf = [0; 64];
|
let mut buf = [0; 64];
|
||||||
buf[..name.len()].copy_from_slice(name.as_bytes());
|
buf[..name.len()].copy_from_slice(name.as_bytes());
|
||||||
@ -510,7 +590,7 @@ impl<'a> Control<'a> {
|
|||||||
|
|
||||||
let total_len = max(name.len() + 1, res.len());
|
let total_len = max(name.len() + 1, res.len());
|
||||||
let res_len = self
|
let res_len = self
|
||||||
.ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len])
|
.ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len])
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let out_len = min(res.len(), res_len);
|
let out_len = min(res.len(), res_len);
|
||||||
@ -518,12 +598,20 @@ impl<'a> Control<'a> {
|
|||||||
out_len
|
out_len
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) {
|
async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) {
|
||||||
let mut buf = val.to_le_bytes();
|
let mut buf = val.to_le_bytes();
|
||||||
self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await;
|
self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
|
async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
|
||||||
|
if kind == IoctlType::Set {
|
||||||
|
debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf));
|
||||||
|
}
|
||||||
|
let n = self.ioctl_inner(kind, cmd, iface, buf).await;
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
|
||||||
struct CancelOnDrop<'a>(&'a IoctlState);
|
struct CancelOnDrop<'a>(&'a IoctlState);
|
||||||
|
|
||||||
impl CancelOnDrop<'_> {
|
impl CancelOnDrop<'_> {
|
||||||
@ -615,7 +703,7 @@ impl<'a> Control<'a> {
|
|||||||
}
|
}
|
||||||
/// Leave the wifi, with which we are currently associated.
|
/// Leave the wifi, with which we are currently associated.
|
||||||
pub async fn leave(&mut self) {
|
pub async fn leave(&mut self) {
|
||||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await;
|
self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await;
|
||||||
info!("Disassociated")
|
info!("Disassociated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -296,10 +296,10 @@ pub struct Events {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Events {
|
impl Events {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
queue: EventQueue::new(),
|
queue: EventQueue::new(),
|
||||||
mask: SharedEventMask::default(),
|
mask: SharedEventMask::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +333,6 @@ impl Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct EventMask {
|
struct EventMask {
|
||||||
mask: [u32; Self::WORD_COUNT],
|
mask: [u32; Self::WORD_COUNT],
|
||||||
}
|
}
|
||||||
@ -341,6 +340,12 @@ struct EventMask {
|
|||||||
impl EventMask {
|
impl EventMask {
|
||||||
const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize;
|
const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize;
|
||||||
|
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
mask: [0; Self::WORD_COUNT],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn enable(&mut self, event: Event) {
|
fn enable(&mut self, event: Event) {
|
||||||
let n = event as u32;
|
let n = event as u32;
|
||||||
let word = n / u32::BITS;
|
let word = n / u32::BITS;
|
||||||
@ -366,13 +371,17 @@ impl EventMask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
|
|
||||||
pub struct SharedEventMask {
|
pub struct SharedEventMask {
|
||||||
mask: RefCell<EventMask>,
|
mask: RefCell<EventMask>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedEventMask {
|
impl SharedEventMask {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
mask: RefCell::new(EventMask::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enable(&self, events: &[Event]) {
|
pub fn enable(&self, events: &[Event]) {
|
||||||
let mut mask = self.mask.borrow_mut();
|
let mut mask = self.mask.borrow_mut();
|
||||||
for event in events {
|
for event in events {
|
||||||
@ -398,3 +407,9 @@ impl SharedEventMask {
|
|||||||
mask.is_enabled(event)
|
mask.is_enabled(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SharedEventMask {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
use core::cell::{Cell, RefCell};
|
use core::cell::{Cell, RefCell};
|
||||||
use core::future::poll_fn;
|
use core::future::{poll_fn, Future};
|
||||||
use core::task::{Poll, Waker};
|
use core::task::{Poll, Waker};
|
||||||
|
|
||||||
use embassy_sync::waitqueue::WakerRegistration;
|
use embassy_sync::waitqueue::WakerRegistration;
|
||||||
|
|
||||||
|
use crate::consts::Ioctl;
|
||||||
use crate::fmt::Bytes;
|
use crate::fmt::Bytes;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum IoctlType {
|
pub enum IoctlType {
|
||||||
Get = 0,
|
Get = 0,
|
||||||
Set = 2,
|
Set = 2,
|
||||||
@ -16,7 +17,7 @@ pub enum IoctlType {
|
|||||||
pub struct PendingIoctl {
|
pub struct PendingIoctl {
|
||||||
pub buf: *mut [u8],
|
pub buf: *mut [u8],
|
||||||
pub kind: IoctlType,
|
pub kind: IoctlType,
|
||||||
pub cmd: u32,
|
pub cmd: Ioctl,
|
||||||
pub iface: u32,
|
pub iface: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,8 +33,8 @@ struct Wakers {
|
|||||||
runner: WakerRegistration,
|
runner: WakerRegistration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Wakers {
|
impl Wakers {
|
||||||
fn default() -> Self {
|
const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
control: WakerRegistration::new(),
|
control: WakerRegistration::new(),
|
||||||
runner: WakerRegistration::new(),
|
runner: WakerRegistration::new(),
|
||||||
@ -47,10 +48,10 @@ pub struct IoctlState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IoctlState {
|
impl IoctlState {
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: Cell::new(IoctlStateInner::Done { resp_len: 0 }),
|
state: Cell::new(IoctlStateInner::Done { resp_len: 0 }),
|
||||||
wakers: Default::default(),
|
wakers: RefCell::new(Wakers::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ impl IoctlState {
|
|||||||
self.wakers.borrow_mut().runner.register(waker);
|
self.wakers.borrow_mut().runner.register(waker);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_complete(&self) -> usize {
|
pub fn wait_complete(&self) -> impl Future<Output = usize> + '_ {
|
||||||
poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
if let IoctlStateInner::Done { resp_len } = self.state.get() {
|
if let IoctlStateInner::Done { resp_len } = self.state.get() {
|
||||||
Poll::Ready(resp_len)
|
Poll::Ready(resp_len)
|
||||||
@ -79,29 +80,25 @@ impl IoctlState {
|
|||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_pending(&self) -> PendingIoctl {
|
pub fn wait_pending(&self) -> impl Future<Output = PendingIoctl> + '_ {
|
||||||
let pending = poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
if let IoctlStateInner::Pending(pending) = self.state.get() {
|
if let IoctlStateInner::Pending(pending) = self.state.get() {
|
||||||
|
self.state.set(IoctlStateInner::Sent { buf: pending.buf });
|
||||||
Poll::Ready(pending)
|
Poll::Ready(pending)
|
||||||
} else {
|
} else {
|
||||||
self.register_runner(cx.waker());
|
self.register_runner(cx.waker());
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
|
||||||
|
|
||||||
self.state.set(IoctlStateInner::Sent { buf: pending.buf });
|
|
||||||
pending
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cancel_ioctl(&self) {
|
pub fn cancel_ioctl(&self) {
|
||||||
self.state.set(IoctlStateInner::Done { resp_len: 0 });
|
self.state.set(IoctlStateInner::Done { resp_len: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
|
pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
|
||||||
self.state
|
self.state
|
||||||
.set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface }));
|
.set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface }));
|
||||||
self.wake_runner();
|
self.wake_runner();
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
pub(crate) mod fmt;
|
pub(crate) mod fmt;
|
||||||
|
|
||||||
#[cfg(feature = "bluetooth")]
|
#[cfg(feature = "bluetooth")]
|
||||||
mod bluetooth;
|
/// Bluetooth module.
|
||||||
|
pub mod bluetooth;
|
||||||
mod bus;
|
mod bus;
|
||||||
mod consts;
|
mod consts;
|
||||||
mod control;
|
mod control;
|
||||||
@ -28,7 +29,9 @@ use ioctl::IoctlState;
|
|||||||
|
|
||||||
use crate::bus::Bus;
|
use crate::bus::Bus;
|
||||||
pub use crate::bus::SpiBusCyw43;
|
pub use crate::bus::SpiBusCyw43;
|
||||||
pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, ScanOptions, Scanner};
|
pub use crate::control::{
|
||||||
|
AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, ScanType, Scanner,
|
||||||
|
};
|
||||||
pub use crate::runner::Runner;
|
pub use crate::runner::Runner;
|
||||||
pub use crate::structs::BssInfo;
|
pub use crate::structs::BssInfo;
|
||||||
|
|
||||||
@ -121,7 +124,7 @@ struct NetState {
|
|||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Create new driver state holder.
|
/// Create new driver state holder.
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ioctl_state: IoctlState::new(),
|
ioctl_state: IoctlState::new(),
|
||||||
net: NetState {
|
net: NetState {
|
||||||
|
|||||||
@ -560,7 +560,7 @@ where
|
|||||||
self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0
|
self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8], buf: &mut [u32; 512]) {
|
async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) {
|
||||||
let buf8 = slice8_mut(buf);
|
let buf8 = slice8_mut(buf);
|
||||||
|
|
||||||
let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len();
|
let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len();
|
||||||
@ -582,7 +582,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let cdc_header = CdcHeader {
|
let cdc_header = CdcHeader {
|
||||||
cmd: cmd,
|
cmd: cmd as u32,
|
||||||
len: data.len() as _,
|
len: data.len() as _,
|
||||||
flags: kind as u16 | (iface as u16) << 12,
|
flags: kind as u16 | (iface as u16) << 12,
|
||||||
id: self.ioctl_id,
|
id: self.ioctl_id,
|
||||||
|
|||||||
@ -394,6 +394,15 @@ pub struct PassphraseInfo {
|
|||||||
}
|
}
|
||||||
impl_bytes!(PassphraseInfo);
|
impl_bytes!(PassphraseInfo);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct SaePassphraseInfo {
|
||||||
|
pub len: u16,
|
||||||
|
pub passphrase: [u8; 128],
|
||||||
|
}
|
||||||
|
impl_bytes!(SaePassphraseInfo);
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
# replace nRF82840_xxAA with your chip as listed in `probe-run --list-chips`
|
# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list`
|
||||||
runner = "probe-run --chip nRF52840_xxAA"
|
runner = "probe-rs run --chip nRF52840_xxAA"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
target = "thumbv7em-none-eabi"
|
target = "thumbv7em-none-eabi"
|
||||||
|
|||||||
@ -6,13 +6,13 @@ version = "0.1.0"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
embassy-executor = { version = "0.7.0", path = "../../../embassy-executor", features = ["defmt", "arch-cortex-m", "executor-thread"] }
|
||||||
embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] }
|
embassy-time = { version = "0.4.0", path = "../../../embassy-time", features = ["defmt"] }
|
||||||
embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
|
embassy-nrf = { version = "0.3.1", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
|
||||||
|
|
||||||
defmt = "0.3"
|
defmt = "1.0.1"
|
||||||
defmt-rtt = "0.3"
|
defmt-rtt = "1.0.0"
|
||||||
|
|
||||||
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7.0"
|
cortex-m-rt = "0.7.0"
|
||||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
runner = "probe-run --chip STM32L475VG"
|
# replace your chip as listed in `probe-rs chip list`
|
||||||
|
runner = "probe-rs run --chip STM32L475VG"
|
||||||
|
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "link-arg=--nmagic",
|
"-C", "link-arg=--nmagic",
|
||||||
|
|||||||
@ -3,14 +3,9 @@ resolver = "2"
|
|||||||
members = [
|
members = [
|
||||||
"blinky-pac",
|
"blinky-pac",
|
||||||
"blinky-hal",
|
"blinky-hal",
|
||||||
"blinky-irq",
|
|
||||||
"blinky-async",
|
"blinky-async",
|
||||||
]
|
]
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
embassy-executor = { path = "../../../embassy-executor" }
|
|
||||||
embassy-stm32 = { path = "../../../embassy-stm32" }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
debug = 2
|
debug = 2
|
||||||
|
|||||||
@ -5,11 +5,11 @@ edition = "2021"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] }
|
embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x", "exti"] }
|
||||||
embassy-executor = { version = "0.6.0", features = ["arch-cortex-m", "executor-thread"] }
|
embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] }
|
||||||
|
|
||||||
defmt = "0.3.0"
|
defmt = "1.0.1"
|
||||||
defmt-rtt = "0.3.0"
|
defmt-rtt = "1.0.0"
|
||||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
|
|||||||
@ -5,10 +5,10 @@ edition = "2021"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
|
embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x"] }
|
||||||
|
|
||||||
defmt = "0.3.0"
|
defmt = "1.0.1"
|
||||||
defmt-rtt = "0.3.0"
|
defmt-rtt = "1.0.0"
|
||||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
[workspace]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "blinky-irq"
|
name = "blinky-irq"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -5,10 +7,10 @@ edition = "2021"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = { version = "0.7" }
|
cortex-m-rt = { version = "0.7" }
|
||||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "unstable-pac"] }
|
embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x", "unstable-pac"] }
|
||||||
|
|
||||||
defmt = "0.3.0"
|
defmt = "1.0.1"
|
||||||
defmt-rtt = "0.3.0"
|
defmt-rtt = "1.0.0"
|
||||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
|
|||||||
@ -5,10 +5,10 @@ edition = "2021"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "0.7"
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
stm32-metapac = { version = "1", features = ["stm32l475vg", "memory-x"] }
|
stm32-metapac = { version = "16", features = ["stm32l475vg"] }
|
||||||
|
|
||||||
defmt = "0.3.0"
|
defmt = "1.0.1"
|
||||||
defmt-rtt = "0.3.0"
|
defmt-rtt = "1.0.0"
|
||||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
|
|||||||
@ -8,46 +8,38 @@ use {defmt_rtt as _, panic_probe as _, stm32_metapac as pac};
|
|||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
// Enable GPIO clock
|
// Enable GPIO clock
|
||||||
let rcc = pac::RCC;
|
let rcc = pac::RCC;
|
||||||
unsafe {
|
rcc.ahb2enr().modify(|w| {
|
||||||
rcc.ahb2enr().modify(|w| {
|
w.set_gpioben(true);
|
||||||
w.set_gpioben(true);
|
w.set_gpiocen(true);
|
||||||
w.set_gpiocen(true);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
rcc.ahb2rstr().modify(|w| {
|
rcc.ahb2rstr().modify(|w| {
|
||||||
w.set_gpiobrst(true);
|
w.set_gpiobrst(true);
|
||||||
w.set_gpiocrst(true);
|
w.set_gpiocrst(true);
|
||||||
w.set_gpiobrst(false);
|
w.set_gpiobrst(false);
|
||||||
w.set_gpiocrst(false);
|
w.set_gpiocrst(false);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Setup button
|
// Setup button
|
||||||
let gpioc = pac::GPIOC;
|
let gpioc = pac::GPIOC;
|
||||||
const BUTTON_PIN: usize = 13;
|
const BUTTON_PIN: usize = 13;
|
||||||
unsafe {
|
gpioc.pupdr().modify(|w| w.set_pupdr(BUTTON_PIN, vals::Pupdr::PULL_UP));
|
||||||
gpioc.pupdr().modify(|w| w.set_pupdr(BUTTON_PIN, vals::Pupdr::PULLUP));
|
gpioc.otyper().modify(|w| w.set_ot(BUTTON_PIN, vals::Ot::PUSH_PULL));
|
||||||
gpioc.otyper().modify(|w| w.set_ot(BUTTON_PIN, vals::Ot::PUSHPULL));
|
gpioc.moder().modify(|w| w.set_moder(BUTTON_PIN, vals::Moder::INPUT));
|
||||||
gpioc.moder().modify(|w| w.set_moder(BUTTON_PIN, vals::Moder::INPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup LED
|
// Setup LED
|
||||||
let gpiob = pac::GPIOB;
|
let gpiob = pac::GPIOB;
|
||||||
const LED_PIN: usize = 14;
|
const LED_PIN: usize = 14;
|
||||||
unsafe {
|
gpiob.pupdr().modify(|w| w.set_pupdr(LED_PIN, vals::Pupdr::FLOATING));
|
||||||
gpiob.pupdr().modify(|w| w.set_pupdr(LED_PIN, vals::Pupdr::FLOATING));
|
gpiob.otyper().modify(|w| w.set_ot(LED_PIN, vals::Ot::PUSH_PULL));
|
||||||
gpiob.otyper().modify(|w| w.set_ot(LED_PIN, vals::Ot::PUSHPULL));
|
gpiob.moder().modify(|w| w.set_moder(LED_PIN, vals::Moder::OUTPUT));
|
||||||
gpiob.moder().modify(|w| w.set_moder(LED_PIN, vals::Moder::OUTPUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
loop {
|
loop {
|
||||||
unsafe {
|
if gpioc.idr().read().idr(BUTTON_PIN) == vals::Idr::LOW {
|
||||||
if gpioc.idr().read().idr(BUTTON_PIN) == vals::Idr::LOW {
|
gpiob.bsrr().write(|w| w.set_bs(LED_PIN, true));
|
||||||
gpiob.bsrr().write(|w| w.set_bs(LED_PIN, true));
|
} else {
|
||||||
} else {
|
gpiob.bsrr().write(|w| w.set_br(LED_PIN, true));
|
||||||
gpiob.bsrr().write(|w| w.set_br(LED_PIN, true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
docs/examples/layer-by-layer/memory.x
Normal file
5
docs/examples/layer-by-layer/memory.x
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x08000000, LENGTH = 2048K /* BANK_1 */
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 640K /* SRAM */
|
||||||
|
}
|
||||||
@ -35,7 +35,7 @@ After the processing, another 1024 byte buffer will be placed on the stack to be
|
|||||||
|
|
||||||
Pass the data by reference and not by value on both, the way in and the way out.
|
Pass the data by reference and not by value on both, the way in and the way out.
|
||||||
For example, you could return a slice of the input buffer as the output.
|
For example, you could return a slice of the input buffer as the output.
|
||||||
Requiring the lifetime of the input slice and the output slice to be the same, the memory safetly of this procedure will be enforced by the compiler.
|
Requiring the lifetime of the input slice and the output slice to be the same, the memory safety of this procedure will be enforced by the compiler.
|
||||||
|
|
||||||
[,rust]
|
[,rust]
|
||||||
----
|
----
|
||||||
|
|||||||
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
`embassy-boot` a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
`embassy-boot` a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
||||||
|
|
||||||
|
The update method used is referred to as an A/B partition update scheme.
|
||||||
|
|
||||||
|
With a general-purpose OS, A/B partition update is accomplished by directly booting either the A or B partition depending on the update state.
|
||||||
|
To accomplish the same goal in a way that is portable across all microcontrollers, `embassy-boot` swaps data page by page (in both directions) between the DFU and the Active partition when a firmware update is triggered. +
|
||||||
|
Because the original Active application is moved into the DFU partition during this update, the operation can be reversed if the update is interrupted or the new firmware does not flag that it booted successfully. +
|
||||||
|
See the design section for more details on how this is implemented.
|
||||||
|
|
||||||
The bootloader can be used either as a library or be flashed directly if you are happy with the default configuration and capabilities.
|
The bootloader can be used either as a library or be flashed directly if you are happy with the default configuration and capabilities.
|
||||||
|
|
||||||
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
|
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
|
||||||
@ -19,6 +26,8 @@ The bootloader supports
|
|||||||
|
|
||||||
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
|
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
|
||||||
|
|
||||||
|
STM32L0x1 devices require the `flash-erase-zero` feature to be enabled.
|
||||||
|
|
||||||
== Design
|
== Design
|
||||||
|
|
||||||
image::bootloader_flash.png[Bootloader flash layout]
|
image::bootloader_flash.png[Bootloader flash layout]
|
||||||
@ -86,8 +95,7 @@ Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware
|
|||||||
|
|
||||||
[source, bash]
|
[source, bash]
|
||||||
----
|
----
|
||||||
shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt
|
shasum -a 512 -b $FIRMWARE_DIR/myfirmware | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt
|
||||||
cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt
|
|
||||||
signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
|
signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
|
||||||
cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
|
cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
|
||||||
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed
|
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]!
|
Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]!
|
||||||
|
|
||||||
|
* link:https://github.com/1-rafael-1/simple-robot[A simple tracked robot based on Raspberry Pi Pico 2]
|
||||||
|
** A hobbyist project building a tracked robot with basic autonomous and manual drive mode.
|
||||||
|
* link:https://github.com/1-rafael-1/pi-pico-alarmclock-rust[A Raspberry Pi Pico W Alarmclock]
|
||||||
|
** A hobbyist project building an alarm clock around a Pi Pico W complete with code, components list and enclosure design files.
|
||||||
* link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware]
|
* link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware]
|
||||||
** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more!
|
** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more!
|
||||||
** Targets STM32, RP2040, nRF52 and ESP32 MCUs
|
** Targets STM32, RP2040, nRF52 and ESP32 MCUs
|
||||||
|
|||||||
@ -4,7 +4,7 @@ These are a list of unsorted, commonly asked questions and answers.
|
|||||||
|
|
||||||
Please feel free to add items to link:https://github.com/embassy-rs/embassy/edit/main/docs/pages/faq.adoc[this page], especially if someone in the chat answered a question for you!
|
Please feel free to add items to link:https://github.com/embassy-rs/embassy/edit/main/docs/pages/faq.adoc[this page], especially if someone in the chat answered a question for you!
|
||||||
|
|
||||||
== How to deploy to RP2040 without a debugging probe.
|
== How to deploy to RP2040 or RP235x without a debugging probe.
|
||||||
|
|
||||||
Install link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] for converting the generated elf binary into a uf2 file.
|
Install link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] for converting the generated elf binary into a uf2 file.
|
||||||
|
|
||||||
@ -92,17 +92,9 @@ If you see linker error like this:
|
|||||||
>>> referenced by driver.rs:127 (src/driver.rs:127)
|
>>> referenced by driver.rs:127 (src/driver.rs:127)
|
||||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::now::hefb1f99d6e069842) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::now::hefb1f99d6e069842) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
||||||
|
|
||||||
rust-lld: error: undefined symbol: _embassy_time_allocate_alarm
|
rust-lld: error: undefined symbol: _embassy_time_schedule_wake
|
||||||
>>> referenced by driver.rs:134 (src/driver.rs:134)
|
|
||||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::allocate_alarm::hf5145b6bd46706b2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
|
||||||
|
|
||||||
rust-lld: error: undefined symbol: _embassy_time_set_alarm_callback
|
|
||||||
>>> referenced by driver.rs:139 (src/driver.rs:139)
|
|
||||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm_callback::h24f92388d96eafd2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
|
||||||
|
|
||||||
rust-lld: error: undefined symbol: _embassy_time_set_alarm
|
|
||||||
>>> referenced by driver.rs:144 (src/driver.rs:144)
|
>>> referenced by driver.rs:144 (src/driver.rs:144)
|
||||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::schedule_wake::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
||||||
----
|
----
|
||||||
|
|
||||||
You probably need to enable a time driver for your HAL (not in `embassy-time`!). For example with `embassy-stm32`, you might need to enable `time-driver-any`:
|
You probably need to enable a time driver for your HAL (not in `embassy-time`!). For example with `embassy-stm32`, you might need to enable `time-driver-any`:
|
||||||
@ -125,6 +117,20 @@ If you are in the early project setup phase and not using anything from the HAL,
|
|||||||
use embassy_stm32 as _;
|
use embassy_stm32 as _;
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Another common error you may experience is:
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
= note: rust-lld: error: undefined symbol: __pender
|
||||||
|
>>> referenced by mod.rs:373 (src/raw/mod.rs:373)
|
||||||
|
>>> embassy_executor-e78174e249bca7f4.embassy_executor.1e9d60fc90940543-cgu.0.rcgu.o:(embassy_executor::raw::Pender::pend::h0f19b6e01762e4cd) in archive [...]libembassy_executor-e78174e249bca7f4.rlib
|
||||||
|
----
|
||||||
|
|
||||||
|
There are two possible causes to this error:
|
||||||
|
|
||||||
|
* You are using `embassy-executor` withuout enabling one of the architecture-specific features, but you are using a HAL that does not bring its own executors. For example, for Cortex-M (like the RP2040), you need to enable the `arch-cortex-m` feature of `embassy-executor`.
|
||||||
|
* You are not using `embassy-executor`. In this case, you need to enable the one of the `generic-queue-X` features of `embassy-time`.
|
||||||
|
|
||||||
== Error: `Only one package in the dependency graph may specify the same links value.`
|
== Error: `Only one package in the dependency graph may specify the same links value.`
|
||||||
|
|
||||||
You have multiple versions of the same crate in your dependency tree. This means that some of your
|
You have multiple versions of the same crate in your dependency tree. This means that some of your
|
||||||
@ -140,9 +146,9 @@ Example:
|
|||||||
[source,toml]
|
[source,toml]
|
||||||
----
|
----
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
|
embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" }
|
||||||
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
|
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" }
|
||||||
# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
|
# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" }
|
||||||
----
|
----
|
||||||
|
|
||||||
Note that the git revision should match any other embassy patches or git dependencies that you are using!
|
Note that the git revision should match any other embassy patches or git dependencies that you are using!
|
||||||
@ -158,35 +164,11 @@ Note that the git revision should match any other embassy patches or git depende
|
|||||||
* Set the following keys in the `[unstable]` section of your `.cargo/config.toml`
|
* Set the following keys in the `[unstable]` section of your `.cargo/config.toml`
|
||||||
** `build-std = ["core"]`
|
** `build-std = ["core"]`
|
||||||
** `build-std-features = ["panic_immediate_abort"]`
|
** `build-std-features = ["panic_immediate_abort"]`
|
||||||
* Enable feature `embassy-time/generic-queue`, disable feature `embassy-executor/integrated-timers`
|
|
||||||
* When using `InterruptExecutor`:
|
* When using `InterruptExecutor`:
|
||||||
** disable `executor-thread`
|
** disable `executor-thread`
|
||||||
** make `main`` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }`
|
** make `main` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }`
|
||||||
** *Note:* If you need 2 priority levels, using 2 interrupt executors is better than 1 thread executor + 1 interrupt executor.
|
** *Note:* If you need 2 priority levels, using 2 interrupt executors is better than 1 thread executor + 1 interrupt executor.
|
||||||
|
|
||||||
== How do I set up the task arenas on stable?
|
|
||||||
|
|
||||||
When you aren't using the `nightly` feature of `embassy-executor`, the executor uses a bump allocator, which may require configuration.
|
|
||||||
|
|
||||||
Something like this error will occur at **compile time** if the task arena is *too large* for the target's RAM:
|
|
||||||
|
|
||||||
[source,plain]
|
|
||||||
----
|
|
||||||
rust-lld: error: section '.bss' will not fit in region 'RAM': overflowed by _ bytes
|
|
||||||
rust-lld: error: section '.uninit' will not fit in region 'RAM': overflowed by _ bytes
|
|
||||||
----
|
|
||||||
|
|
||||||
And this message will appear at **runtime** if the task arena is *too small* for the tasks running:
|
|
||||||
|
|
||||||
[source,plain]
|
|
||||||
----
|
|
||||||
ERROR panicked at 'embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/'
|
|
||||||
----
|
|
||||||
|
|
||||||
NOTE: If all tasks are spawned at startup, this panic will occur immediately.
|
|
||||||
|
|
||||||
Check out link:https://docs.embassy.dev/embassy-executor/git/cortex-m/index.html#task-arena[Task Arena Documentation] for more details.
|
|
||||||
|
|
||||||
== Can I use manual ISRs alongside Embassy?
|
== Can I use manual ISRs alongside Embassy?
|
||||||
|
|
||||||
Yes! This can be useful if you need to respond to an event as fast as possible, and the latency caused by the usual “ISR, wake, return from ISR, context switch to woken task” flow is too much for your application. Simply define a `#[interrupt] fn INTERRUPT_NAME() {}` handler as you would link:https://docs.rust-embedded.org/book/start/interrupts.html[in any other embedded rust project].
|
Yes! This can be useful if you need to respond to an event as fast as possible, and the latency caused by the usual “ISR, wake, return from ISR, context switch to woken task” flow is too much for your application. Simply define a `#[interrupt] fn INTERRUPT_NAME() {}` handler as you would link:https://docs.rust-embedded.org/book/start/interrupts.html[in any other embedded rust project].
|
||||||
@ -230,7 +212,7 @@ _stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|||||||
Please refer to the STM32 documentation for the specific values suitable for your board and setup. The STM32 Cube examples often contain a linker script `.ld` file.
|
Please refer to the STM32 documentation for the specific values suitable for your board and setup. The STM32 Cube examples often contain a linker script `.ld` file.
|
||||||
Look for the `MEMORY` section and try to determine the FLASH and RAM sizes and section start.
|
Look for the `MEMORY` section and try to determine the FLASH and RAM sizes and section start.
|
||||||
|
|
||||||
If you find a case where the memory.x is wrong, please report it on [this Github issue](https://github.com/embassy-rs/stm32-data/issues/301) so other users are not caught by surprise.
|
If you find a case where the memory.x is wrong, please report it on link:https://github.com/embassy-rs/stm32-data/issues/301[this Github issue] so other users are not caught by surprise.
|
||||||
|
|
||||||
== The USB examples are not working on my board, is there anything else I need to configure?
|
== The USB examples are not working on my board, is there anything else I need to configure?
|
||||||
|
|
||||||
@ -286,7 +268,7 @@ General steps:
|
|||||||
1. Find out which memory region BDMA has access to. You can get this information from the bus matrix and the memory mapping table in the STM32 datasheet.
|
1. Find out which memory region BDMA has access to. You can get this information from the bus matrix and the memory mapping table in the STM32 datasheet.
|
||||||
2. Add the memory region to `memory.x`, you can modify the generated one from https://github.com/embassy-rs/stm32-data-generated/tree/main/data/chips.
|
2. Add the memory region to `memory.x`, you can modify the generated one from https://github.com/embassy-rs/stm32-data-generated/tree/main/data/chips.
|
||||||
3. You might need to modify `build.rs` to make cargo pick up the modified `memory.x`.
|
3. You might need to modify `build.rs` to make cargo pick up the modified `memory.x`.
|
||||||
4. In your code, access the defined memory region using `#[link_section = ".xxx"]`
|
4. In your code, access the defined memory region using `#[unsafe(link_section = ".xxx")]`
|
||||||
5. Copy data to that region before using BDMA.
|
5. Copy data to that region before using BDMA.
|
||||||
|
|
||||||
See link:https://github.com/embassy-rs/embassy/blob/main/examples/stm32h7/src/bin/spi_bdma.rs[SMT32H7 SPI BDMA example] for more details.
|
See link:https://github.com/embassy-rs/embassy/blob/main/examples/stm32h7/src/bin/spi_bdma.rs[SMT32H7 SPI BDMA example] for more details.
|
||||||
@ -361,4 +343,73 @@ Practically, there's not a LOT of difference either way - so go with what makes
|
|||||||
|
|
||||||
== splitting peripherals resources between tasks
|
== splitting peripherals resources between tasks
|
||||||
|
|
||||||
There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example]
|
There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example]
|
||||||
|
|
||||||
|
== My code/driver works in debug mode, but not release mode (or with LTO)
|
||||||
|
|
||||||
|
Issues like these while implementing drivers often fall into one of the following general causes, which are a good list of common errors to check for:
|
||||||
|
|
||||||
|
1. Some kind of race condition - the faster code means you miss an interrupt or something
|
||||||
|
2. Some kind of UB, if you have unsafe code, or something like DMA with fences missing
|
||||||
|
3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds
|
||||||
|
4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary
|
||||||
|
5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time
|
||||||
|
|
||||||
|
== How can I prevent the thread-mode executor from going to sleep? ==
|
||||||
|
|
||||||
|
In some cases you might want to prevent the thread-mode executor from going to sleep, for example when doing so would result in current spikes that reduce analog performance.
|
||||||
|
As a workaround, you can spawn a task that yields in a loop, preventing the executor from going to sleep. Note that this may increase power consumption.
|
||||||
|
|
||||||
|
[source,rust]
|
||||||
|
----
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn idle() {
|
||||||
|
loop { embassy_futures::yield_now().await; }
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
== Why is my bootloader restarting in loop?
|
||||||
|
|
||||||
|
== Troubleshooting Bootloader Restart Loops
|
||||||
|
|
||||||
|
If your bootloader restarts in a loop, there could be multiple reasons. Here are some things to check:
|
||||||
|
|
||||||
|
=== Validate the `memory.x` File
|
||||||
|
The bootloader performs critical checks when creating partitions using the addresses defined in `memory.x`. Ensure the following assertions hold true:
|
||||||
|
|
||||||
|
[source,rust]
|
||||||
|
----
|
||||||
|
const {
|
||||||
|
core::assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0);
|
||||||
|
core::assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0);
|
||||||
|
core::assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0);
|
||||||
|
core::assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure enough progress pages to store copy progress
|
||||||
|
assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
|
||||||
|
assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
|
||||||
|
assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
|
||||||
|
assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
|
||||||
|
----
|
||||||
|
|
||||||
|
If any of these assertions fail, the bootloader will likely restart in a loop. This failure might not log any messages (e.g., when using `defmt`). Confirm that your `memory.x` file and flash memory align with these requirements.
|
||||||
|
|
||||||
|
=== Handling Panic Logging
|
||||||
|
Some panic errors might log messages, but certain microcontrollers reset before the message is fully printed. To ensure panic messages are logged, add a delay using no-operation (NOP) instructions before the reset:
|
||||||
|
|
||||||
|
[source,rust]
|
||||||
|
----
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
for _ in 0..10_000_000 {
|
||||||
|
cortex_m::asm::nop();
|
||||||
|
}
|
||||||
|
cortex_m::asm::udf();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Feed the watchdog
|
||||||
|
|
||||||
|
|
||||||
|
Some `embassy-boot` implementations (like `embassy-boot-nrf` and `embassy-boot-rp`) rely on a watchdog timer to detect application failure. The bootloader will restart if your application code does not properly feed the watchdog timer. Make sure to feed it correctly.
|
||||||
|
|||||||
@ -66,7 +66,7 @@ If everything worked correctly, you should see a blinking LED on your board, and
|
|||||||
[source]
|
[source]
|
||||||
----
|
----
|
||||||
Finished dev [unoptimized + debuginfo] target(s) in 1m 56s
|
Finished dev [unoptimized + debuginfo] target(s) in 1m 56s
|
||||||
Running `probe-run --chip STM32F407VGTx target/thumbv7em-none-eabi/debug/blinky`
|
Running `probe-rs run --chip STM32F407VGTx target/thumbv7em-none-eabi/debug/blinky`
|
||||||
(HOST) INFO flashing program (71.36 KiB)
|
(HOST) INFO flashing program (71.36 KiB)
|
||||||
(HOST) INFO success!
|
(HOST) INFO success!
|
||||||
────────────────────────────────────────────────────────────────────────────────
|
────────────────────────────────────────────────────────────────────────────────
|
||||||
@ -86,7 +86,7 @@ NOTE: How does the `+cargo run+` command know how to connect to our board and pr
|
|||||||
|
|
||||||
=== It didn’t work!
|
=== It didn’t work!
|
||||||
|
|
||||||
If you hare having issues when running `+cargo run --release+`, please check the following:
|
If you are having issues when running `+cargo run --release+`, please check the following:
|
||||||
|
|
||||||
* You are specifying the correct `+--chip+` on the command line, OR
|
* You are specifying the correct `+--chip+` on the command line, OR
|
||||||
* You have set `+.cargo/config.toml+`’s run line to the correct chip, AND
|
* You have set `+.cargo/config.toml+`’s run line to the correct chip, AND
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Embassy provides HALs for several microcontroller families:
|
|||||||
|
|
||||||
* `embassy-nrf` for the nRF microcontrollers from Nordic Semiconductor
|
* `embassy-nrf` for the nRF microcontrollers from Nordic Semiconductor
|
||||||
* `embassy-stm32` for STM32 microcontrollers from ST Microelectronics
|
* `embassy-stm32` for STM32 microcontrollers from ST Microelectronics
|
||||||
* `embassy-rp` for the Raspberry Pi RP2040 microcontrollers
|
* `embassy-rp` for the Raspberry Pi RP2040 and RP235x microcontrollers
|
||||||
|
|
||||||
These HALs implement async/await functionality for most peripherals while also implementing the
|
These HALs implement async/await functionality for most peripherals while also implementing the
|
||||||
async traits in `embedded-hal` and `embedded-hal-async`. You can also use these HALs with another executor.
|
async traits in `embedded-hal` and `embedded-hal-async`. You can also use these HALs with another executor.
|
||||||
@ -12,3 +12,7 @@ async traits in `embedded-hal` and `embedded-hal-async`. You can also use these
|
|||||||
For the ESP32 series, there is an link:https://github.com/esp-rs/esp-hal[esp-hal] which you can use.
|
For the ESP32 series, there is an link:https://github.com/esp-rs/esp-hal[esp-hal] which you can use.
|
||||||
|
|
||||||
For the WCH 32-bit RISC-V series, there is an link:https://github.com/ch32-rs/ch32-hal[ch32-hal], which you can use.
|
For the WCH 32-bit RISC-V series, there is an link:https://github.com/ch32-rs/ch32-hal[ch32-hal], which you can use.
|
||||||
|
|
||||||
|
For the Microchip PolarFire SoC, there is link:https://github.com/AlexCharlton/mpfs-hal[mpfs-hal].
|
||||||
|
|
||||||
|
For the Puya Semiconductor PY32 series, there is link:https://github.com/py32-rs/py32-hal[py32-hal].
|
||||||
16
docs/pages/imxrt.adoc
Normal file
16
docs/pages/imxrt.adoc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
= Embassy iMXRT HAL
|
||||||
|
|
||||||
|
The link: link:https://github.com/embassy-rs/embassy/tree/main/embassy-imxrt[Embassy iMXRT HAL] is based on the following PACs (Peripheral Access Crate):
|
||||||
|
|
||||||
|
* link:https://github.com/OpenDevicePartnership/mimxrt685s-pac[mimxrt685s-pac]
|
||||||
|
* link:https://github.com/OpenDevicePartnership/mimxrt633s-pac[mimxrt633s-pac]
|
||||||
|
|
||||||
|
== Peripherals
|
||||||
|
|
||||||
|
The following peripherals have a HAL implementation at present
|
||||||
|
|
||||||
|
* CRC
|
||||||
|
* DMA
|
||||||
|
* GPIO
|
||||||
|
* RNG
|
||||||
|
* UART
|
||||||
@ -76,7 +76,7 @@ The async version looks very similar to the HAL version, apart from a few minor
|
|||||||
* The peripheral initialization is done by the main macro, and is handed to the main task.
|
* The peripheral initialization is done by the main macro, and is handed to the main task.
|
||||||
* Before checking the button state, the application is awaiting a transition in the pin state (low -> high or high -> low).
|
* Before checking the button state, the application is awaiting a transition in the pin state (low -> high or high -> low).
|
||||||
|
|
||||||
When `button.await_for_any_edge().await` is called, the executor will pause the main task and put the microcontroller in sleep mode, unless there are other tasks that can run. Internally, the Embassy HAL has configured the interrupt handler for the button (in `ExtiInput`), so that whenever an interrupt is raised, the task awaiting the button will be woken up.
|
When `button.wait_for_any_edge().await` is called, the executor will pause the main task and put the microcontroller in sleep mode, unless there are other tasks that can run. Internally, the Embassy HAL has configured the interrupt handler for the button (in `ExtiInput`), so that whenever an interrupt is raised, the task awaiting the button will be woken up.
|
||||||
|
|
||||||
The minimal overhead of the executor and the ability to run multiple tasks "concurrently" combined with the enormous simplification of the application, makes `async` a great fit for embedded.
|
The minimal overhead of the executor and the ability to run multiple tasks "concurrently" combined with the enormous simplification of the application, makes `async` a great fit for embedded.
|
||||||
|
|
||||||
|
|||||||
@ -73,22 +73,34 @@ Now that cargo knows what target to compile for (and probe-rs knows what chip to
|
|||||||
|
|
||||||
Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`.
|
Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`.
|
||||||
|
|
||||||
At the time of writing, the latest version of embassy isn‘t available on crates.io, so we need to install it straight from the git repository. The recommended way of doing so is as follows:
|
|
||||||
|
At the time of writing, embassy is already published to crates.io. Therefore, dependencies can easily added via Cargo.toml.
|
||||||
|
|
||||||
|
[source,toml]
|
||||||
|
----
|
||||||
|
[dependencies]
|
||||||
|
embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"] }
|
||||||
|
embassy-executor = { version = "0.6.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt"] }
|
||||||
|
embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
||||||
|
----
|
||||||
|
|
||||||
|
Prior, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published.
|
||||||
|
The recommended way of doing so is as follows:
|
||||||
|
|
||||||
* Copy the required `embassy-*` lines from the example `Cargo.toml`
|
* Copy the required `embassy-*` lines from the example `Cargo.toml`
|
||||||
* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32`
|
* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32`
|
||||||
* Remove the `path = ""` keys in the `embassy-*` entries
|
* Remove the `path = ""` keys in the `embassy-*` entries
|
||||||
* Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit we’re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD`
|
* Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit we’re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD`
|
||||||
|
|
||||||
NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. Hopefully this will no longer be necessary once embassy is released on crates.io!
|
NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed.
|
||||||
|
|
||||||
At the time of writing, this method produces the following results:
|
An example Cargo.toml file might look as follows:
|
||||||
|
|
||||||
[source,toml]
|
[source,toml]
|
||||||
----
|
----
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy-stm32 = {version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"]}
|
embassy-stm32 = {version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"]}
|
||||||
embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
|
embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt"] }
|
||||||
embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
@ -97,7 +109,7 @@ embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "7703f
|
|||||||
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" }
|
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" }
|
||||||
----
|
----
|
||||||
|
|
||||||
There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the the `[dependencies]` section in the new `Cargo.toml`:
|
There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the `[dependencies]` section in the new `Cargo.toml`:
|
||||||
|
|
||||||
[source,toml]
|
[source,toml]
|
||||||
----
|
----
|
||||||
@ -138,7 +150,7 @@ stm32g474-example
|
|||||||
# Before upgrading check that everything is available on all tier1 targets here:
|
# Before upgrading check that everything is available on all tier1 targets here:
|
||||||
# https://rust-lang.github.io/rustup-components-history
|
# https://rust-lang.github.io/rustup-components-history
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2023-11-01"
|
channel = "1.85"
|
||||||
components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ]
|
components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ]
|
||||||
targets = ["thumbv7em-none-eabi"]
|
targets = ["thumbv7em-none-eabi"]
|
||||||
----
|
----
|
||||||
|
|||||||
@ -6,7 +6,7 @@ Embassy is a project to make async/await a first-class option for embedded devel
|
|||||||
|
|
||||||
When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task (known as a “thread”) can be executed if available, or the CPU can be put to sleep until another task is ready.
|
When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task (known as a “thread”) can be executed if available, or the CPU can be put to sleep until another task is ready.
|
||||||
|
|
||||||
Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread.
|
Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted if they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread.
|
||||||
|
|
||||||
In other programming languages, these lightweight tasks are known as “coroutines” or ”goroutines”. In Rust, they are implemented with async. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute.
|
In other programming languages, these lightweight tasks are known as “coroutines” or ”goroutines”. In Rust, they are implemented with async. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute.
|
||||||
|
|
||||||
@ -28,9 +28,12 @@ The Embassy project maintains HALs for select hardware, but you can still use HA
|
|||||||
|
|
||||||
* link:https://docs.embassy.dev/embassy-stm32/[embassy-stm32], for all STM32 microcontroller families.
|
* link:https://docs.embassy.dev/embassy-stm32/[embassy-stm32], for all STM32 microcontroller families.
|
||||||
* link:https://docs.embassy.dev/embassy-nrf/[embassy-nrf], for the Nordic Semiconductor nRF52, nRF53, nRF91 series.
|
* link:https://docs.embassy.dev/embassy-nrf/[embassy-nrf], for the Nordic Semiconductor nRF52, nRF53, nRF91 series.
|
||||||
* link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 microcontroller.
|
* link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 as well as RP235x microcontroller.
|
||||||
|
* link:https://docs.embassy.dev/embassy-mspm0/[embassy-mspm0], for the Texas Instruments MSPM0 microcontrollers.
|
||||||
* link:https://github.com/esp-rs[esp-rs], for the Espressif Systems ESP32 series of chips.
|
* link:https://github.com/esp-rs[esp-rs], for the Espressif Systems ESP32 series of chips.
|
||||||
* link:https://github.com/ch32-rs/ch32-hal[ch32-hal], for the WCH 32-bit RISC-V(CH32V) series of chips.
|
* link:https://github.com/ch32-rs/ch32-hal[ch32-hal], for the WCH 32-bit RISC-V(CH32V) series of chips.
|
||||||
|
* link:https://github.com/AlexCharlton/mpfs-hal[mpfs-hal], for the Microchip PolarFire SoC.
|
||||||
|
* link:https://github.com/py32-rs/py32-hal[py32-hal], for the Puya Semiconductor PY32 series of chips.
|
||||||
|
|
||||||
NOTE: A common question is if one can use the Embassy HALs standalone. Yes, it is possible! There are no dependency on the executor within the HALs. You can even use them without async,
|
NOTE: A common question is if one can use the Embassy HALs standalone. Yes, it is possible! There are no dependency on the executor within the HALs. You can even use them without async,
|
||||||
as they implement both the link:https://github.com/rust-embedded/embedded-hal[Embedded HAL] blocking and async traits.
|
as they implement both the link:https://github.com/rust-embedded/embedded-hal[Embedded HAL] blocking and async traits.
|
||||||
@ -52,7 +55,7 @@ link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot]
|
|||||||
|
|
||||||
== What is DMA?
|
== What is DMA?
|
||||||
|
|
||||||
For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bits at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller.
|
For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bytes at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller.
|
||||||
|
|
||||||
The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them.
|
The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them.
|
||||||
|
|
||||||
@ -74,6 +77,11 @@ include::embassy_in_the_wild.adoc[leveloffset = 2]
|
|||||||
|
|
||||||
For more reading material on async Rust and Embassy:
|
For more reading material on async Rust and Embassy:
|
||||||
|
|
||||||
* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy]
|
* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparison of FreeRTOS and Embassy]
|
||||||
* link:https://dev.to/apollolabsbin/series/20707[Tutorials]
|
* link:https://dev.to/apollolabsbin/series/20707[Tutorials]
|
||||||
* link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy]
|
* link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy]
|
||||||
|
|
||||||
|
Videos:
|
||||||
|
|
||||||
|
* link:https://www.youtube.com/watch?v=pDd5mXBF4tY[Intro to Embassy]
|
||||||
|
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]
|
||||||
|
|||||||
@ -85,9 +85,9 @@ A minimal example:
|
|||||||
[source,toml]
|
[source,toml]
|
||||||
----
|
----
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2023-08-19" # <- as of writing, this is the exact rust version embassy uses
|
channel = "1.85" # <- as of writing, this is the exact rust version embassy uses
|
||||||
components = [ "rust-src", "rustfmt" ] # <- optionally add "llvm-tools-preview" for some extra features like "cargo size"
|
components = [ "rust-src", "rustfmt" ] # <- optionally add "llvm-tools-preview" for some extra features like "cargo size"
|
||||||
targets = [
|
targets = [
|
||||||
"thumbv6m-none-eabi" # <-change for your platform
|
"thumbv6m-none-eabi" # <- change for your platform
|
||||||
]
|
]
|
||||||
----
|
----
|
||||||
|
|||||||
@ -6,6 +6,7 @@ include::runtime.adoc[leveloffset = 2]
|
|||||||
include::bootloader.adoc[leveloffset = 2]
|
include::bootloader.adoc[leveloffset = 2]
|
||||||
include::time_keeping.adoc[leveloffset = 2]
|
include::time_keeping.adoc[leveloffset = 2]
|
||||||
include::hal.adoc[leveloffset = 2]
|
include::hal.adoc[leveloffset = 2]
|
||||||
|
include::imxrt.adoc[leveloffset = 2]
|
||||||
include::nrf.adoc[leveloffset = 2]
|
include::nrf.adoc[leveloffset = 2]
|
||||||
include::stm32.adoc[leveloffset = 2]
|
include::stm32.adoc[leveloffset = 2]
|
||||||
include::sharing_peripherals.adoc[leveloffset = 2]
|
include::sharing_peripherals.adoc[leveloffset = 2]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "embassy-boot-nrf"
|
name = "embassy-boot-nrf"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
description = "Bootloader lib for nRF chips"
|
description = "Bootloader lib for nRF chips"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/embassy-rs/embassy"
|
repository = "https://github.com/embassy-rs/embassy"
|
||||||
@ -21,12 +21,12 @@ target = "thumbv7em-none-eabi"
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
log = { version = "0.4.17", optional = true }
|
log = { version = "0.4.17", optional = true }
|
||||||
|
|
||||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
embassy-sync = { version = "0.7.0", path = "../embassy-sync" }
|
||||||
embassy-nrf = { version = "0.2.0", path = "../embassy-nrf", default-features = false }
|
embassy-nrf = { version = "0.3.1", path = "../embassy-nrf", default-features = false }
|
||||||
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
|
embassy-boot = { version = "0.4.0", path = "../embassy-boot" }
|
||||||
cortex-m = { version = "0.7.6" }
|
cortex-m = { version = "0.7.6" }
|
||||||
cortex-m-rt = { version = "0.7" }
|
cortex-m-rt = { version = "0.7" }
|
||||||
embedded-storage = "0.3.1"
|
embedded-storage = "0.3.1"
|
||||||
|
|||||||
@ -6,6 +6,25 @@ An adaptation of `embassy-boot` for nRF.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Load applications with or without the softdevice.
|
- Load applications with or without the softdevice.
|
||||||
* Configure bootloader partitions based on linker script.
|
- Configure bootloader partitions based on linker script.
|
||||||
* Using watchdog timer to detect application failure.
|
- Using watchdog timer to detect application failure.
|
||||||
|
|
||||||
|
## Working with a SoftDevice
|
||||||
|
|
||||||
|
When a SoftDevice is present, it handles starting the bootloader and the application as needed.
|
||||||
|
|
||||||
|
The SoftDevice architecture supports the bootloader via a configurable base address, referred to as `BOOTLOADERADDR`, in the application flash region. This address can be specified either:
|
||||||
|
|
||||||
|
1. At the `MBR_BOOTLOADER_ADDR` location in flash memory (defined in `nrf_mbr.h`), or
|
||||||
|
2. In the `UICR.NRFFW[0]` register.
|
||||||
|
|
||||||
|
The `UICR.NRFFW[0]` register is used only if `MBR_BOOTLOADER_ADDR` has its default value of `0xFFFFFFFF`. This bootloader relies on the latter approach.
|
||||||
|
|
||||||
|
In the `memory.x` linker script, there is a section `.uicr_bootloader_start_address` (origin `0x10001014`, length `0x4`) that stores the `BOOTLOADERADDR` value.
|
||||||
|
Ensure that `__bootloader_start` is set to the origin address of the bootloader partition.
|
||||||
|
|
||||||
|
When a bootloader is present, the SoftDevice forwards interrupts to it and executes the bootloader reset handler, defined in the bootloader's vector table at `BOOTLOADERADDR`.
|
||||||
|
|
||||||
|
Once the bootloader loads the application, the SoftDevice initiates the Application Reset Handler, defined in the application’s vector table at APP_CODE_BASE hardcoded in the SoftDevice.
|
||||||
|
The active partition's origin **must** match the `APP_CODE_BASE` value hardcoded within the SoftDevice. This value can be found in the release notes for each SoftDevice version.
|
||||||
|
|||||||
@ -8,8 +8,7 @@ pub use embassy_boot::{
|
|||||||
FirmwareUpdater, FirmwareUpdaterConfig,
|
FirmwareUpdater, FirmwareUpdaterConfig,
|
||||||
};
|
};
|
||||||
use embassy_nrf::nvmc::PAGE_SIZE;
|
use embassy_nrf::nvmc::PAGE_SIZE;
|
||||||
use embassy_nrf::peripherals::WDT;
|
use embassy_nrf::{wdt, Peri};
|
||||||
use embassy_nrf::wdt;
|
|
||||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||||
|
|
||||||
/// A bootloader for nRF devices.
|
/// A bootloader for nRF devices.
|
||||||
@ -113,7 +112,7 @@ pub struct WatchdogFlash<FLASH> {
|
|||||||
|
|
||||||
impl<FLASH> WatchdogFlash<FLASH> {
|
impl<FLASH> WatchdogFlash<FLASH> {
|
||||||
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
|
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
|
||||||
pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self {
|
pub fn start(flash: FLASH, wdt: Peri<'static, impl wdt::Instance>, config: wdt::Config) -> Self {
|
||||||
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
|
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "embassy-boot-rp"
|
name = "embassy-boot-rp"
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
description = "Bootloader lib for RP2040 chips"
|
description = "Bootloader lib for RP2040 chips"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/embassy-rs/embassy"
|
repository = "https://github.com/embassy-rs/embassy"
|
||||||
@ -21,13 +21,13 @@ features = ["embassy-rp/rp2040"]
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
log = { version = "0.4", optional = true }
|
log = { version = "0.4", optional = true }
|
||||||
|
|
||||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
embassy-sync = { version = "0.7.0", path = "../embassy-sync" }
|
||||||
embassy-rp = { version = "0.2.0", path = "../embassy-rp", default-features = false }
|
embassy-rp = { version = "0.4.0", path = "../embassy-rp", default-features = false }
|
||||||
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
|
embassy-boot = { version = "0.4.0", path = "../embassy-boot" }
|
||||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
embassy-time = { version = "0.4.0", path = "../embassy-time" }
|
||||||
|
|
||||||
cortex-m = { version = "0.7.6" }
|
cortex-m = { version = "0.7.6" }
|
||||||
cortex-m-rt = { version = "0.7" }
|
cortex-m-rt = { version = "0.7" }
|
||||||
|
|||||||
@ -10,11 +10,15 @@ pub use embassy_boot::{
|
|||||||
use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE};
|
use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE};
|
||||||
use embassy_rp::peripherals::{FLASH, WATCHDOG};
|
use embassy_rp::peripherals::{FLASH, WATCHDOG};
|
||||||
use embassy_rp::watchdog::Watchdog;
|
use embassy_rp::watchdog::Watchdog;
|
||||||
|
use embassy_rp::Peri;
|
||||||
use embassy_time::Duration;
|
use embassy_time::Duration;
|
||||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||||
|
|
||||||
/// A bootloader for RP2040 devices.
|
/// A bootloader for RP2040 devices.
|
||||||
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>;
|
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
|
||||||
|
/// The reported state of the bootloader after preparing for boot
|
||||||
|
pub state: State,
|
||||||
|
}
|
||||||
|
|
||||||
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
|
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
|
||||||
@ -36,8 +40,8 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
|||||||
) -> Result<Self, BootError> {
|
) -> Result<Self, BootError> {
|
||||||
let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
|
let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
|
||||||
let mut boot = embassy_boot::BootLoader::new(config);
|
let mut boot = embassy_boot::BootLoader::new(config);
|
||||||
let _state = boot.prepare_boot(aligned_buf.as_mut())?;
|
let state = boot.prepare_boot(aligned_buf.as_mut())?;
|
||||||
Ok(Self)
|
Ok(Self { state })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Boots the application.
|
/// Boots the application.
|
||||||
@ -65,7 +69,7 @@ pub struct WatchdogFlash<'d, const SIZE: usize> {
|
|||||||
|
|
||||||
impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
|
impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
|
||||||
/// Start a new watchdog with a given flash and watchdog peripheral and a timeout
|
/// Start a new watchdog with a given flash and watchdog peripheral and a timeout
|
||||||
pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self {
|
pub fn start(flash: Peri<'static, FLASH>, watchdog: Peri<'static, WATCHDOG>, timeout: Duration) -> Self {
|
||||||
let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash);
|
let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash);
|
||||||
let mut watchdog = Watchdog::new(watchdog);
|
let mut watchdog = Watchdog::new(watchdog);
|
||||||
watchdog.start(timeout);
|
watchdog.start(timeout);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "embassy-boot-stm32"
|
name = "embassy-boot-stm32"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
description = "Bootloader lib for STM32 chips"
|
description = "Bootloader lib for STM32 chips"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/embassy-rs/embassy"
|
repository = "https://github.com/embassy-rs/embassy"
|
||||||
@ -21,12 +21,12 @@ target = "thumbv7em-none-eabi"
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
log = { version = "0.4", optional = true }
|
log = { version = "0.4", optional = true }
|
||||||
|
|
||||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
embassy-sync = { version = "0.7.0", path = "../embassy-sync" }
|
||||||
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false }
|
embassy-stm32 = { version = "0.2.0", path = "../embassy-stm32", default-features = false }
|
||||||
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
|
embassy-boot = { version = "0.4.0", path = "../embassy-boot" }
|
||||||
cortex-m = { version = "0.7.6" }
|
cortex-m = { version = "0.7.6" }
|
||||||
cortex-m-rt = { version = "0.7" }
|
cortex-m-rt = { version = "0.7" }
|
||||||
embedded-storage = "0.3.1"
|
embedded-storage = "0.3.1"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "embassy-boot"
|
name = "embassy-boot"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
|
description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/embassy-rs/embassy"
|
repository = "https://github.com/embassy-rs/embassy"
|
||||||
@ -24,12 +24,12 @@ features = ["defmt"]
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
digest = "0.10"
|
digest = "0.10"
|
||||||
log = { version = "0.4", optional = true }
|
log = { version = "0.4", optional = true }
|
||||||
ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true }
|
ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true }
|
||||||
embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" }
|
embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" }
|
||||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
embassy-sync = { version = "0.7.0", path = "../embassy-sync" }
|
||||||
embedded-storage = "0.3.1"
|
embedded-storage = "0.3.1"
|
||||||
embedded-storage-async = { version = "0.4.1" }
|
embedded-storage-async = { version = "0.4.1" }
|
||||||
salty = { version = "0.3", optional = true }
|
salty = { version = "0.3", optional = true }
|
||||||
@ -42,11 +42,12 @@ rand = "0.8"
|
|||||||
futures = { version = "0.3", features = ["executor"] }
|
futures = { version = "0.3", features = ["executor"] }
|
||||||
sha1 = "0.10.5"
|
sha1 = "0.10.5"
|
||||||
critical-section = { version = "1.1.1", features = ["std"] }
|
critical-section = { version = "1.1.1", features = ["std"] }
|
||||||
ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] }
|
ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
|
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
|
||||||
ed25519-salty = ["dep:salty", "_verify"]
|
ed25519-salty = ["dep:salty", "_verify"]
|
||||||
|
flash-erase-zero = []
|
||||||
|
|
||||||
#Internal features
|
#Internal features
|
||||||
_verify = []
|
_verify = []
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
|||||||
use embassy_sync::blocking_mutex::Mutex;
|
use embassy_sync::blocking_mutex::Mutex;
|
||||||
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
||||||
|
|
||||||
use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||||
|
|
||||||
/// Errors returned by bootloader
|
/// Errors returned by bootloader
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
@ -276,7 +276,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
|||||||
self.state.erase(0, self.state.capacity() as u32)?;
|
self.state.erase(0, self.state.capacity() as u32)?;
|
||||||
|
|
||||||
// Set magic
|
// Set magic
|
||||||
state_word.fill(BOOT_MAGIC);
|
state_word.fill(REVERT_MAGIC);
|
||||||
self.state.write(0, state_word)?;
|
self.state.write(0, state_word)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,6 +411,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
|||||||
Ok(State::Swap)
|
Ok(State::Swap)
|
||||||
} else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
} else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||||
Ok(State::DfuDetach)
|
Ok(State::DfuDetach)
|
||||||
|
} else if !state_word.iter().any(|&b| b != REVERT_MAGIC) {
|
||||||
|
Ok(State::Revert)
|
||||||
} else {
|
} else {
|
||||||
Ok(State::Boot)
|
Ok(State::Boot)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
|||||||
let mut message = [0; 64];
|
let mut message = [0; 64];
|
||||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||||
|
|
||||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
public_key.verify(&message, &signature).map_err(into_signature_error)?;
|
||||||
|
return self.state.mark_updated().await;
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ed25519-salty")]
|
#[cfg(feature = "ed25519-salty")]
|
||||||
{
|
{
|
||||||
@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
|||||||
message,
|
message,
|
||||||
r.is_ok()
|
r.is_ok()
|
||||||
);
|
);
|
||||||
r.map_err(into_signature_error)?
|
r.map_err(into_signature_error)?;
|
||||||
|
return self.state.mark_updated().await;
|
||||||
|
}
|
||||||
|
#[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
|
||||||
|
{
|
||||||
|
Err(FirmwareUpdaterError::Signature(signature::Error::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state.mark_updated().await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the update in DFU with any digest.
|
/// Verify the update in DFU with any digest.
|
||||||
@ -157,6 +161,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a slice of data from the DFU storage peripheral, starting the read
|
||||||
|
/// operation at the given address offset, and reading `buf.len()` bytes.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the arguments are not aligned or out of bounds.
|
||||||
|
pub async fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||||
|
self.dfu.read(offset, buf).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Mark to trigger firmware swap on next boot.
|
/// Mark to trigger firmware swap on next boot.
|
||||||
#[cfg(not(feature = "_verify"))]
|
#[cfg(not(feature = "_verify"))]
|
||||||
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||||
@ -285,7 +300,8 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
|
|||||||
|
|
||||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||||
async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||||
if self.get_state().await? == State::Boot {
|
let state = self.get_state().await?;
|
||||||
|
if state == State::Boot || state == State::DfuDetach || state == State::Revert {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(FirmwareUpdaterError::BadState)
|
Err(FirmwareUpdaterError::BadState)
|
||||||
@ -299,12 +315,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
|
|||||||
/// `mark_booted`.
|
/// `mark_booted`.
|
||||||
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||||
self.state.read(0, &mut self.aligned).await?;
|
self.state.read(0, &mut self.aligned).await?;
|
||||||
|
Ok(State::from(&self.aligned[..STATE::WRITE_SIZE]))
|
||||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
|
||||||
Ok(State::Swap)
|
|
||||||
} else {
|
|
||||||
Ok(State::Boot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark to trigger firmware swap on next boot.
|
/// Mark to trigger firmware swap on next boot.
|
||||||
|
|||||||
@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
|
|||||||
let mut chunk_buf = [0; 2];
|
let mut chunk_buf = [0; 2];
|
||||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||||
|
|
||||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
public_key.verify(&message, &signature).map_err(into_signature_error)?;
|
||||||
|
return self.state.mark_updated();
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ed25519-salty")]
|
#[cfg(feature = "ed25519-salty")]
|
||||||
{
|
{
|
||||||
@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
|
|||||||
message,
|
message,
|
||||||
r.is_ok()
|
r.is_ok()
|
||||||
);
|
);
|
||||||
r.map_err(into_signature_error)?
|
r.map_err(into_signature_error)?;
|
||||||
|
return self.state.mark_updated();
|
||||||
|
}
|
||||||
|
#[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
|
||||||
|
{
|
||||||
|
Err(FirmwareUpdaterError::Signature(signature::Error::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state.mark_updated()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the update in DFU with any digest.
|
/// Verify the update in DFU with any digest.
|
||||||
@ -192,6 +196,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a slice of data from the DFU storage peripheral, starting the read
|
||||||
|
/// operation at the given address offset, and reading `buf.len()` bytes.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the arguments are not aligned or out of bounds.
|
||||||
|
pub fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||||
|
self.dfu.read(offset, buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Mark to trigger firmware swap on next boot.
|
/// Mark to trigger firmware swap on next boot.
|
||||||
#[cfg(not(feature = "_verify"))]
|
#[cfg(not(feature = "_verify"))]
|
||||||
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||||
@ -320,7 +335,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
|
|||||||
|
|
||||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||||
fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||||
if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
|
let state = self.get_state()?;
|
||||||
|
if state == State::Boot || state == State::DfuDetach || state == State::Revert {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(FirmwareUpdaterError::BadState)
|
Err(FirmwareUpdaterError::BadState)
|
||||||
@ -334,14 +350,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
|
|||||||
/// `mark_booted`.
|
/// `mark_booted`.
|
||||||
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||||
self.state.read(0, &mut self.aligned)?;
|
self.state.read(0, &mut self.aligned)?;
|
||||||
|
Ok(State::from(&self.aligned))
|
||||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
|
||||||
Ok(State::Swap)
|
|
||||||
} else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
|
||||||
Ok(State::DfuDetach)
|
|
||||||
} else {
|
|
||||||
Ok(State::Boot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark to trigger firmware swap on next boot.
|
/// Mark to trigger firmware swap on next boot.
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
|||||||
/// Firmware updater flash configuration holding the two flashes used by the updater
|
/// Firmware updater flash configuration holding the two flashes used by the updater
|
||||||
///
|
///
|
||||||
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
|
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
|
||||||
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
|
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
|
||||||
/// the provided flash according to symbols defined in the linkerfile.
|
/// the provided flash according to symbols defined in the linkerfile.
|
||||||
pub struct FirmwareUpdaterConfig<DFU, STATE> {
|
pub struct FirmwareUpdaterConfig<DFU, STATE> {
|
||||||
/// The dfu flash partition
|
/// The dfu flash partition
|
||||||
|
|||||||
@ -14,13 +14,18 @@ mod test_flash;
|
|||||||
|
|
||||||
// The expected value of the flash after an erase
|
// The expected value of the flash after an erase
|
||||||
// TODO: Use the value provided by NorFlash when available
|
// TODO: Use the value provided by NorFlash when available
|
||||||
|
#[cfg(not(feature = "flash-erase-zero"))]
|
||||||
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
|
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||||
|
#[cfg(feature = "flash-erase-zero")]
|
||||||
|
pub(crate) const STATE_ERASE_VALUE: u8 = 0x00;
|
||||||
|
|
||||||
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
|
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
|
||||||
pub use firmware_updater::{
|
pub use firmware_updater::{
|
||||||
BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
|
BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
|
||||||
FirmwareUpdaterError,
|
FirmwareUpdaterError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub(crate) const REVERT_MAGIC: u8 = 0xC0;
|
||||||
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
|
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
|
||||||
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
|
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
|
||||||
pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
|
pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
|
||||||
@ -33,10 +38,30 @@ pub enum State {
|
|||||||
Boot,
|
Boot,
|
||||||
/// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
|
/// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
|
||||||
Swap,
|
Swap,
|
||||||
|
/// Bootloader has reverted the active partition with the dfu partition and will attempt boot.
|
||||||
|
Revert,
|
||||||
/// Application has received a request to reboot into DFU mode to apply an update.
|
/// Application has received a request to reboot into DFU mode to apply an update.
|
||||||
DfuDetach,
|
DfuDetach,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for State
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
fn from(magic: T) -> State {
|
||||||
|
let magic = magic.as_ref();
|
||||||
|
if !magic.iter().any(|&b| b != SWAP_MAGIC) {
|
||||||
|
State::Swap
|
||||||
|
} else if !magic.iter().any(|&b| b != REVERT_MAGIC) {
|
||||||
|
State::Revert
|
||||||
|
} else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||||
|
State::DfuDetach
|
||||||
|
} else {
|
||||||
|
State::Boot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
|
/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
|
||||||
#[repr(align(32))]
|
#[repr(align(32))]
|
||||||
pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
|
pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
|
||||||
@ -153,6 +178,9 @@ mod tests {
|
|||||||
// Running again should cause a revert
|
// Running again should cause a revert
|
||||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||||
|
|
||||||
|
// Next time we know it was reverted
|
||||||
|
assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap());
|
||||||
|
|
||||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||||
flash.active().read(0, &mut read_buf).unwrap();
|
flash.active().read(0, &mut read_buf).unwrap();
|
||||||
assert_eq!(ORIGINAL, read_buf);
|
assert_eq!(ORIGINAL, read_buf);
|
||||||
|
|||||||
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.3.0 - 2025-01-05
|
||||||
|
|
||||||
|
- The `std` feature has been removed
|
||||||
|
- Updated `embassy-time` to v0.4
|
||||||
|
|
||||||
## 0.2.0 - 2024-08-05
|
## 0.2.0 - 2024-08-05
|
||||||
|
|
||||||
- Add Clone derive to flash Partition in embassy-embedded-hal
|
- Add Clone derive to flash Partition in embassy-embedded-hal
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "embassy-embedded-hal"
|
name = "embassy-embedded-hal"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy."
|
description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy."
|
||||||
@ -15,21 +15,17 @@ categories = [
|
|||||||
[package.metadata.embassy_docs]
|
[package.metadata.embassy_docs]
|
||||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/"
|
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/"
|
||||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/"
|
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/"
|
||||||
features = ["std"]
|
|
||||||
target = "x86_64-unknown-linux-gnu"
|
target = "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = ["std"]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = []
|
|
||||||
time = ["dep:embassy-time"]
|
time = ["dep:embassy-time"]
|
||||||
default = ["time"]
|
default = ["time"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" }
|
||||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
embassy-sync = { version = "0.7.0", path = "../embassy-sync" }
|
||||||
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
|
embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true }
|
||||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||||
"unproven",
|
"unproven",
|
||||||
] }
|
] }
|
||||||
@ -39,7 +35,7 @@ embedded-storage = "0.3.1"
|
|||||||
embedded-storage-async = { version = "0.4.1" }
|
embedded-storage-async = { version = "0.4.1" }
|
||||||
nb = "1.0.0"
|
nb = "1.0.0"
|
||||||
|
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
critical-section = { version = "1.1.1", features = ["std"] }
|
critical-section = { version = "1.1.1", features = ["std"] }
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![no_std]
|
||||||
#![allow(async_fn_in_trait)]
|
#![allow(async_fn_in_trait)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128);
|
//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128);
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use embassy_hal_internal::drop::OnDrop;
|
||||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||||
use embassy_sync::mutex::Mutex;
|
use embassy_sync::mutex::Mutex;
|
||||||
use embedded_hal_1::digital::OutputPin;
|
use embedded_hal_1::digital::OutputPin;
|
||||||
@ -70,6 +71,14 @@ where
|
|||||||
let mut bus = self.bus.lock().await;
|
let mut bus = self.bus.lock().await;
|
||||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||||
|
|
||||||
|
let cs_drop = OnDrop::new(|| {
|
||||||
|
// This drop guard deasserts CS pin if the async operation is cancelled.
|
||||||
|
// Errors are ignored in this drop handler, as there's nothing we can do about them.
|
||||||
|
// If the async operation is completed without cancellation, this handler will not
|
||||||
|
// be run, and the CS pin will be deasserted with proper error handling.
|
||||||
|
let _ = self.cs.set_high();
|
||||||
|
});
|
||||||
|
|
||||||
let op_res = 'ops: {
|
let op_res = 'ops: {
|
||||||
for op in operations {
|
for op in operations {
|
||||||
let res = match op {
|
let res = match op {
|
||||||
@ -97,6 +106,10 @@ where
|
|||||||
|
|
||||||
// On failure, it's important to still flush and deassert CS.
|
// On failure, it's important to still flush and deassert CS.
|
||||||
let flush_res = bus.flush().await;
|
let flush_res = bus.flush().await;
|
||||||
|
|
||||||
|
// Now that all the async operations are done, we defuse the CS guard,
|
||||||
|
// and manually set the CS pin low (to better handle the possible errors).
|
||||||
|
cs_drop.defuse();
|
||||||
let cs_res = self.cs.set_high();
|
let cs_res = self.cs.set_high();
|
||||||
|
|
||||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||||
@ -155,6 +168,11 @@ where
|
|||||||
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
|
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
|
||||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||||
|
|
||||||
|
let cs_drop = OnDrop::new(|| {
|
||||||
|
// Please see comment in SpiDevice for an explanation of this drop handler.
|
||||||
|
let _ = self.cs.set_high();
|
||||||
|
});
|
||||||
|
|
||||||
let op_res = 'ops: {
|
let op_res = 'ops: {
|
||||||
for op in operations {
|
for op in operations {
|
||||||
let res = match op {
|
let res = match op {
|
||||||
@ -182,6 +200,7 @@ where
|
|||||||
|
|
||||||
// On failure, it's important to still flush and deassert CS.
|
// On failure, it's important to still flush and deassert CS.
|
||||||
let flush_res = bus.flush().await;
|
let flush_res = bus.flush().await;
|
||||||
|
cs_drop.defuse();
|
||||||
let cs_res = self.cs.set_high();
|
let cs_res = self.cs.set_high();
|
||||||
|
|
||||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "embassy-executor-macros"
|
name = "embassy-executor-macros"
|
||||||
version = "0.5.0"
|
version = "0.6.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "macros for creating the entry point and tasks for embassy-executor"
|
description = "macros for creating the entry point and tasks for embassy-executor"
|
||||||
@ -13,7 +13,7 @@ categories = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
|
syn = { version = "2.0.15", features = ["full", "visit"] }
|
||||||
quote = "1.0.9"
|
quote = "1.0.9"
|
||||||
darling = "0.20.1"
|
darling = "0.20.1"
|
||||||
proc-macro2 = "1.0.29"
|
proc-macro2 = "1.0.29"
|
||||||
|
|||||||
@ -1,28 +1,11 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use darling::ast::NestedMeta;
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
mod macros;
|
mod macros;
|
||||||
mod util;
|
mod util;
|
||||||
use macros::*;
|
use macros::*;
|
||||||
use syn::parse::{Parse, ParseBuffer};
|
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::Token;
|
|
||||||
|
|
||||||
struct Args {
|
|
||||||
meta: Vec<NestedMeta>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Args {
|
|
||||||
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
|
|
||||||
let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
|
|
||||||
Ok(Args {
|
|
||||||
meta: meta.into_iter().collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
|
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
|
||||||
/// many concurrent tasks can be spawned (default is 1) for the function.
|
/// many concurrent tasks can be spawned (default is 1) for the function.
|
||||||
@ -56,17 +39,12 @@ impl Parse for Args {
|
|||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = syn::parse_macro_input!(args as Args);
|
task::run(args.into(), item.into()).into()
|
||||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
|
||||||
|
|
||||||
task::run(&args.meta, f).unwrap_or_else(|x| x).into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = syn::parse_macro_input!(args as Args);
|
main::run(args.into(), item.into(), &main::ARCH_AVR).into()
|
||||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
|
||||||
main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
|
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
|
||||||
@ -89,9 +67,57 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = syn::parse_macro_input!(args as Args);
|
main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into()
|
||||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
}
|
||||||
main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
|
|
||||||
|
/// Creates a new `executor` instance and declares an application entry point for Cortex-A/R
|
||||||
|
/// spawning the corresponding function body as an async task.
|
||||||
|
///
|
||||||
|
/// The following restrictions apply:
|
||||||
|
///
|
||||||
|
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it
|
||||||
|
/// can use to spawn additional tasks.
|
||||||
|
/// * The function must be declared `async`.
|
||||||
|
/// * The function must not use generics.
|
||||||
|
/// * Only a single `main` task may be declared.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
/// Spawning a task:
|
||||||
|
///
|
||||||
|
/// ``` rust
|
||||||
|
/// #[embassy_executor::main]
|
||||||
|
/// async fn main(_s: embassy_executor::Spawner) {
|
||||||
|
/// // Function body
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn main_cortex_ar(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
main::run(args.into(), item.into(), &main::ARCH_CORTEX_AR).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
|
||||||
|
/// the corresponding function body as an async task.
|
||||||
|
///
|
||||||
|
/// The following restrictions apply:
|
||||||
|
///
|
||||||
|
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||||
|
/// * The function must be declared `async`.
|
||||||
|
/// * The function must not use generics.
|
||||||
|
/// * Only a single `main` task may be declared.
|
||||||
|
///
|
||||||
|
/// A user-defined entry macro must provided via the `entry` argument
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
/// Spawning a task:
|
||||||
|
/// ``` rust
|
||||||
|
/// #[embassy_executor::main(entry = "qingke_rt::entry")]
|
||||||
|
/// async fn main(_s: embassy_executor::Spawner) {
|
||||||
|
/// // Function body
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
|
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
|
||||||
@ -124,11 +150,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = syn::parse_macro_input!(args as Args);
|
main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
|
||||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
|
||||||
main::run(&args.meta, f, main::riscv(&args.meta))
|
|
||||||
.unwrap_or_else(|x| x)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
|
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
|
||||||
@ -151,9 +173,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = syn::parse_macro_input!(args as Args);
|
main::run(args.into(), item.into(), &main::ARCH_STD).into()
|
||||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
|
||||||
main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
|
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
|
||||||
@ -176,7 +196,29 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = syn::parse_macro_input!(args as Args);
|
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
|
||||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
}
|
||||||
main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
|
|
||||||
|
/// Creates a new `executor` instance and declares an application entry point for an unspecified architecture, spawning the corresponding function body as an async task.
|
||||||
|
///
|
||||||
|
/// The following restrictions apply:
|
||||||
|
///
|
||||||
|
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
|
||||||
|
/// * The function must be declared `async`.
|
||||||
|
/// * The function must not use generics.
|
||||||
|
/// * Only a single `main` task may be declared.
|
||||||
|
///
|
||||||
|
/// A user-defined entry macro and executor type must be provided via the `entry` and `executor` arguments of the `main` macro.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
/// Spawning a task:
|
||||||
|
/// ``` rust
|
||||||
|
/// #[embassy_executor::main(entry = "your_hal::entry", executor = "your_hal::Executor")]
|
||||||
|
/// async fn main(_s: embassy_executor::Spawner) {
|
||||||
|
/// // Function body
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,125 +1,128 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use darling::export::NestedMeta;
|
use darling::export::NestedMeta;
|
||||||
use darling::FromMeta;
|
use darling::FromMeta;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Expr, ReturnType, Type};
|
use syn::{ReturnType, Type};
|
||||||
|
|
||||||
use crate::util::ctxt::Ctxt;
|
use crate::util::*;
|
||||||
|
|
||||||
#[derive(Debug, FromMeta)]
|
enum Flavor {
|
||||||
|
Standard,
|
||||||
|
Wasm,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Arch {
|
||||||
|
default_entry: Option<&'static str>,
|
||||||
|
flavor: Flavor,
|
||||||
|
executor_required: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static ARCH_AVR: Arch = Arch {
|
||||||
|
default_entry: Some("avr_device::entry"),
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_RISCV: Arch = Arch {
|
||||||
|
default_entry: Some("riscv_rt::entry"),
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_CORTEX_M: Arch = Arch {
|
||||||
|
default_entry: Some("cortex_m_rt::entry"),
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_CORTEX_AR: Arch = Arch {
|
||||||
|
default_entry: None,
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_SPIN: Arch = Arch {
|
||||||
|
default_entry: None,
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_STD: Arch = Arch {
|
||||||
|
default_entry: None,
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_WASM: Arch = Arch {
|
||||||
|
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
|
||||||
|
flavor: Flavor::Wasm,
|
||||||
|
executor_required: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ARCH_UNSPECIFIED: Arch = Arch {
|
||||||
|
default_entry: None,
|
||||||
|
flavor: Flavor::Standard,
|
||||||
|
executor_required: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, FromMeta, Default)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
entry: Option<String>,
|
entry: Option<String>,
|
||||||
|
#[darling(default)]
|
||||||
|
executor: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn avr() -> TokenStream {
|
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
|
||||||
quote! {
|
let mut errors = TokenStream::new();
|
||||||
#[avr_device::entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
let mut executor = ::embassy_executor::Executor::new();
|
|
||||||
let executor = unsafe { __make_static(&mut executor) };
|
|
||||||
|
|
||||||
executor.run(|spawner| {
|
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||||
spawner.must_spawn(__embassy_main(spawner));
|
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||||
})
|
// related features keep working.
|
||||||
}
|
let f: ItemFn = match syn::parse2(item.clone()) {
|
||||||
}
|
Ok(x) => x,
|
||||||
}
|
Err(e) => return token_stream_with_error(item, e),
|
||||||
|
|
||||||
pub fn riscv(args: &[NestedMeta]) -> TokenStream {
|
|
||||||
let maybe_entry = match Args::from_list(args) {
|
|
||||||
Ok(args) => args.entry,
|
|
||||||
Err(e) => return e.write_errors(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let entry = maybe_entry.unwrap_or("riscv_rt::entry".into());
|
let args = match NestedMeta::parse_meta_list(args) {
|
||||||
let entry = match Expr::from_string(&entry) {
|
Ok(x) => x,
|
||||||
Ok(expr) => expr,
|
Err(e) => return token_stream_with_error(item, e),
|
||||||
Err(e) => return e.write_errors(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
let args = match Args::from_list(&args) {
|
||||||
#[#entry]
|
Ok(x) => x,
|
||||||
fn main() -> ! {
|
Err(e) => {
|
||||||
let mut executor = ::embassy_executor::Executor::new();
|
errors.extend(e.write_errors());
|
||||||
let executor = unsafe { __make_static(&mut executor) };
|
Args::default()
|
||||||
executor.run(|spawner| {
|
|
||||||
spawner.must_spawn(__embassy_main(spawner));
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cortex_m() -> TokenStream {
|
|
||||||
quote! {
|
|
||||||
#[cortex_m_rt::entry]
|
|
||||||
fn main() -> ! {
|
|
||||||
let mut executor = ::embassy_executor::Executor::new();
|
|
||||||
let executor = unsafe { __make_static(&mut executor) };
|
|
||||||
executor.run(|spawner| {
|
|
||||||
spawner.must_spawn(__embassy_main(spawner));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wasm() -> TokenStream {
|
|
||||||
quote! {
|
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen(start)]
|
|
||||||
pub fn main() -> Result<(), wasm_bindgen::JsValue> {
|
|
||||||
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
|
|
||||||
|
|
||||||
executor.start(|spawner| {
|
|
||||||
spawner.must_spawn(__embassy_main(spawner));
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn std() -> TokenStream {
|
|
||||||
quote! {
|
|
||||||
fn main() -> ! {
|
|
||||||
let mut executor = ::embassy_executor::Executor::new();
|
|
||||||
let executor = unsafe { __make_static(&mut executor) };
|
|
||||||
|
|
||||||
executor.run(|spawner| {
|
|
||||||
spawner.must_spawn(__embassy_main(spawner));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<TokenStream, TokenStream> {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
|
|
||||||
|
|
||||||
let fargs = f.sig.inputs.clone();
|
let fargs = f.sig.inputs.clone();
|
||||||
|
|
||||||
let ctxt = Ctxt::new();
|
|
||||||
|
|
||||||
if f.sig.asyncness.is_none() {
|
if f.sig.asyncness.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "main function must be async");
|
error(&mut errors, &f.sig, "main function must be async");
|
||||||
}
|
}
|
||||||
if !f.sig.generics.params.is_empty() {
|
if !f.sig.generics.params.is_empty() {
|
||||||
ctxt.error_spanned_by(&f.sig, "main function must not be generic");
|
error(&mut errors, &f.sig, "main function must not be generic");
|
||||||
}
|
}
|
||||||
if !f.sig.generics.where_clause.is_none() {
|
if !f.sig.generics.where_clause.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
|
error(&mut errors, &f.sig, "main function must not have `where` clauses");
|
||||||
}
|
}
|
||||||
if !f.sig.abi.is_none() {
|
if !f.sig.abi.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
|
error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
|
||||||
}
|
}
|
||||||
if !f.sig.variadic.is_none() {
|
if !f.sig.variadic.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
|
error(&mut errors, &f.sig, "main function must not be variadic");
|
||||||
}
|
}
|
||||||
match &f.sig.output {
|
match &f.sig.output {
|
||||||
ReturnType::Default => {}
|
ReturnType::Default => {}
|
||||||
ReturnType::Type(_, ty) => match &**ty {
|
ReturnType::Type(_, ty) => match &**ty {
|
||||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||||
Type::Never(_) => {}
|
Type::Never(_) => {}
|
||||||
_ => ctxt.error_spanned_by(
|
_ => error(
|
||||||
|
&mut errors,
|
||||||
&f.sig,
|
&f.sig,
|
||||||
"main function must either not return a value, return `()` or return `!`",
|
"main function must either not return a value, return `()` or return `!`",
|
||||||
),
|
),
|
||||||
@ -127,26 +130,99 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<Tok
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fargs.len() != 1 {
|
if fargs.len() != 1 {
|
||||||
ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
|
error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt.check()?;
|
let entry = match (args.entry.as_deref(), arch.default_entry.as_deref()) {
|
||||||
|
(None, None) => TokenStream::new(),
|
||||||
|
(Some(x), _) | (None, Some(x)) if x == "" => TokenStream::new(),
|
||||||
|
(Some(x), _) | (None, Some(x)) => match TokenStream::from_str(x) {
|
||||||
|
Ok(x) => quote!(#[#x]),
|
||||||
|
Err(e) => {
|
||||||
|
error(&mut errors, &f.sig, e);
|
||||||
|
TokenStream::new()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let f_body = f.block;
|
let executor = match (args.executor.as_deref(), arch.executor_required) {
|
||||||
|
(None, true) => {
|
||||||
|
error(
|
||||||
|
&mut errors,
|
||||||
|
&f.sig,
|
||||||
|
"\
|
||||||
|
No architecture selected for embassy-executor. Make sure you've enabled one of the `arch-*` features in your Cargo.toml.
|
||||||
|
|
||||||
|
Alternatively, if you would like to use a custom executor implementation, specify it with the `executor` argument.
|
||||||
|
For example: `#[embassy_executor::main(entry = ..., executor = \"some_crate::Executor\")]",
|
||||||
|
);
|
||||||
|
""
|
||||||
|
}
|
||||||
|
(Some(x), _) => x,
|
||||||
|
(None, _) => "::embassy_executor::Executor",
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = TokenStream::from_str(executor).unwrap_or_else(|e| {
|
||||||
|
error(&mut errors, &f.sig, e);
|
||||||
|
TokenStream::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
let f_body = f.body;
|
||||||
let out = &f.sig.output;
|
let out = &f.sig.output;
|
||||||
|
|
||||||
|
let (main_ret, mut main_body) = match arch.flavor {
|
||||||
|
Flavor::Standard => (
|
||||||
|
quote!(!),
|
||||||
|
quote! {
|
||||||
|
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
|
||||||
|
::core::mem::transmute(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut executor = #executor::new();
|
||||||
|
let executor = unsafe { __make_static(&mut executor) };
|
||||||
|
executor.run(|spawner| {
|
||||||
|
spawner.must_spawn(__embassy_main(spawner));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Flavor::Wasm => (
|
||||||
|
quote!(Result<(), wasm_bindgen::JsValue>),
|
||||||
|
quote! {
|
||||||
|
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(#executor::new()));
|
||||||
|
|
||||||
|
executor.start(|spawner| {
|
||||||
|
spawner.must_spawn(__embassy_main(spawner));
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut main_attrs = TokenStream::new();
|
||||||
|
for attr in f.attrs {
|
||||||
|
main_attrs.extend(quote!(#attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
main_body = quote! {loop{}};
|
||||||
|
}
|
||||||
|
|
||||||
let result = quote! {
|
let result = quote! {
|
||||||
#[::embassy_executor::task()]
|
#[::embassy_executor::task()]
|
||||||
|
#[allow(clippy::future_not_send)]
|
||||||
async fn __embassy_main(#fargs) #out {
|
async fn __embassy_main(#fargs) #out {
|
||||||
#f_body
|
#f_body
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
|
#entry
|
||||||
::core::mem::transmute(t)
|
#main_attrs
|
||||||
|
fn main() -> #main_ret {
|
||||||
|
#main_body
|
||||||
}
|
}
|
||||||
|
|
||||||
#main
|
#errors
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +1,78 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use darling::export::NestedMeta;
|
use darling::export::NestedMeta;
|
||||||
use darling::FromMeta;
|
use darling::FromMeta;
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type};
|
use syn::visit::{self, Visit};
|
||||||
|
use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
|
||||||
|
|
||||||
use crate::util::ctxt::Ctxt;
|
use crate::util::*;
|
||||||
|
|
||||||
#[derive(Debug, FromMeta)]
|
#[derive(Debug, FromMeta, Default)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
pool_size: Option<syn::Expr>,
|
pool_size: Option<syn::Expr>,
|
||||||
|
/// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`.
|
||||||
|
#[darling(default)]
|
||||||
|
embassy_executor: Option<syn::Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStream> {
|
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
|
let mut errors = TokenStream::new();
|
||||||
|
|
||||||
|
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||||
|
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||||
|
// related features keep working.
|
||||||
|
let f: ItemFn = match syn::parse2(item.clone()) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return token_stream_with_error(item, e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = match NestedMeta::parse_meta_list(args) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return token_stream_with_error(item, e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = match Args::from_list(&args) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
errors.extend(e.write_errors());
|
||||||
|
Args::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
|
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
|
||||||
attrs: vec![],
|
attrs: vec![],
|
||||||
lit: Lit::Int(LitInt::new("1", Span::call_site())),
|
lit: Lit::Int(LitInt::new("1", Span::call_site())),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let ctxt = Ctxt::new();
|
let embassy_executor = args
|
||||||
|
.embassy_executor
|
||||||
|
.unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap()));
|
||||||
|
|
||||||
if f.sig.asyncness.is_none() {
|
if f.sig.asyncness.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "task functions must be async");
|
error(&mut errors, &f.sig, "task functions must be async");
|
||||||
}
|
}
|
||||||
if !f.sig.generics.params.is_empty() {
|
if !f.sig.generics.params.is_empty() {
|
||||||
ctxt.error_spanned_by(&f.sig, "task functions must not be generic");
|
error(&mut errors, &f.sig, "task functions must not be generic");
|
||||||
}
|
}
|
||||||
if !f.sig.generics.where_clause.is_none() {
|
if !f.sig.generics.where_clause.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses");
|
error(&mut errors, &f.sig, "task functions must not have `where` clauses");
|
||||||
}
|
}
|
||||||
if !f.sig.abi.is_none() {
|
if !f.sig.abi.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier");
|
error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
|
||||||
}
|
}
|
||||||
if !f.sig.variadic.is_none() {
|
if !f.sig.variadic.is_none() {
|
||||||
ctxt.error_spanned_by(&f.sig, "task functions must not be variadic");
|
error(&mut errors, &f.sig, "task functions must not be variadic");
|
||||||
}
|
}
|
||||||
match &f.sig.output {
|
match &f.sig.output {
|
||||||
ReturnType::Default => {}
|
ReturnType::Default => {}
|
||||||
ReturnType::Type(_, ty) => match &**ty {
|
ReturnType::Type(_, ty) => match &**ty {
|
||||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||||
Type::Never(_) => {}
|
Type::Never(_) => {}
|
||||||
_ => ctxt.error_spanned_by(
|
_ => error(
|
||||||
|
&mut errors,
|
||||||
&f.sig,
|
&f.sig,
|
||||||
"task functions must either not return a value, return `()` or return `!`",
|
"task functions must either not return a value, return `()` or return `!`",
|
||||||
),
|
),
|
||||||
@ -55,26 +85,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
|||||||
for arg in fargs.iter_mut() {
|
for arg in fargs.iter_mut() {
|
||||||
match arg {
|
match arg {
|
||||||
syn::FnArg::Receiver(_) => {
|
syn::FnArg::Receiver(_) => {
|
||||||
ctxt.error_spanned_by(arg, "task functions must not have receiver arguments");
|
error(&mut errors, arg, "task functions must not have `self` arguments");
|
||||||
}
|
}
|
||||||
syn::FnArg::Typed(t) => match t.pat.as_mut() {
|
syn::FnArg::Typed(t) => {
|
||||||
syn::Pat::Ident(id) => {
|
check_arg_ty(&mut errors, &t.ty);
|
||||||
id.mutability = None;
|
match t.pat.as_mut() {
|
||||||
args.push((id.clone(), t.attrs.clone()));
|
syn::Pat::Ident(id) => {
|
||||||
|
id.mutability = None;
|
||||||
|
args.push((id.clone(), t.attrs.clone()));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error(
|
||||||
|
&mut errors,
|
||||||
|
arg,
|
||||||
|
"pattern matching in task arguments is not yet supported",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
}
|
||||||
ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt.check()?;
|
|
||||||
|
|
||||||
let task_ident = f.sig.ident.clone();
|
let task_ident = f.sig.ident.clone();
|
||||||
let task_inner_ident = format_ident!("__{}_task", task_ident);
|
let task_inner_ident = format_ident!("__{}_task", task_ident);
|
||||||
|
|
||||||
let mut task_inner = f;
|
let mut task_inner = f.clone();
|
||||||
let visibility = task_inner.vis.clone();
|
let visibility = task_inner.vis.clone();
|
||||||
task_inner.vis = syn::Visibility::Inherited;
|
task_inner.vis = syn::Visibility::Inherited;
|
||||||
task_inner.sig.ident = task_inner_ident.clone();
|
task_inner.sig.ident = task_inner_ident.clone();
|
||||||
@ -91,35 +126,54 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
let mut task_outer: ItemFn = parse_quote! {
|
let mut task_outer_body = quote! {
|
||||||
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
trait _EmbassyInternalTaskTrait {
|
||||||
trait _EmbassyInternalTaskTrait {
|
type Fut: ::core::future::Future + 'static;
|
||||||
type Fut: ::core::future::Future + 'static;
|
fn construct(#fargs) -> Self::Fut;
|
||||||
fn construct(#fargs) -> Self::Fut;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl _EmbassyInternalTaskTrait for () {
|
|
||||||
type Fut = impl core::future::Future + 'static;
|
|
||||||
fn construct(#fargs) -> Self::Fut {
|
|
||||||
#task_inner_ident(#(#full_args,)*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const POOL_SIZE: usize = #pool_size;
|
|
||||||
static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new();
|
|
||||||
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl _EmbassyInternalTaskTrait for () {
|
||||||
|
type Fut = impl core::future::Future + 'static;
|
||||||
|
fn construct(#fargs) -> Self::Fut {
|
||||||
|
#task_inner_ident(#(#full_args,)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const POOL_SIZE: usize = #pool_size;
|
||||||
|
static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new();
|
||||||
|
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "nightly"))]
|
#[cfg(not(feature = "nightly"))]
|
||||||
let mut task_outer: ItemFn = parse_quote! {
|
let mut task_outer_body = quote! {
|
||||||
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
const fn __task_pool_get<F, Args, Fut>(_: F) -> &'static #embassy_executor::raw::TaskPool<Fut, POOL_SIZE>
|
||||||
const POOL_SIZE: usize = #pool_size;
|
where
|
||||||
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
|
F: #embassy_executor::_export::TaskFn<Args, Fut = Fut>,
|
||||||
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
|
Fut: ::core::future::Future + 'static,
|
||||||
|
{
|
||||||
|
unsafe { &*POOL.get().cast() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const POOL_SIZE: usize = #pool_size;
|
||||||
|
static POOL: #embassy_executor::_export::TaskPoolHolder<
|
||||||
|
{#embassy_executor::_export::task_pool_size::<_, _, _, POOL_SIZE>(#task_inner_ident)},
|
||||||
|
{#embassy_executor::_export::task_pool_align::<_, _, _, POOL_SIZE>(#task_inner_ident)},
|
||||||
|
> = unsafe { ::core::mem::transmute(#embassy_executor::_export::task_pool_new::<_, _, _, POOL_SIZE>(#task_inner_ident)) };
|
||||||
|
unsafe { __task_pool_get(#task_inner_ident)._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
|
||||||
};
|
};
|
||||||
|
|
||||||
task_outer.attrs.append(&mut task_inner.attrs.clone());
|
let task_outer_attrs = task_inner.attrs.clone();
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
task_outer_body = quote! {
|
||||||
|
#![allow(unused_variables, unreachable_code)]
|
||||||
|
let _x: #embassy_executor::SpawnToken<()> = ::core::todo!();
|
||||||
|
_x
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the generics + where clause to avoid more spurious errors.
|
||||||
|
let generics = &f.sig.generics;
|
||||||
|
let where_clause = &f.sig.generics.where_clause;
|
||||||
|
|
||||||
let result = quote! {
|
let result = quote! {
|
||||||
// This is the user's task function, renamed.
|
// This is the user's task function, renamed.
|
||||||
@ -129,8 +183,49 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#task_inner
|
#task_inner
|
||||||
|
|
||||||
#task_outer
|
#(#task_outer_attrs)*
|
||||||
|
#visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken<impl Sized> #where_clause{
|
||||||
|
#task_outer_body
|
||||||
|
}
|
||||||
|
|
||||||
|
#errors
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
|
||||||
|
struct Visitor<'a> {
|
||||||
|
errors: &'a mut TokenStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
|
||||||
|
fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
|
||||||
|
// only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
|
||||||
|
if i.lifetime.is_none() {
|
||||||
|
error(
|
||||||
|
self.errors,
|
||||||
|
i.and_token,
|
||||||
|
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
visit::visit_type_reference(self, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
|
||||||
|
if i.ident.to_string() != "static" {
|
||||||
|
error(
|
||||||
|
self.errors,
|
||||||
|
i,
|
||||||
|
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
|
||||||
|
error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Visit::visit_type(&mut Visitor { errors }, ty);
|
||||||
}
|
}
|
||||||
|
|||||||
74
embassy-executor-macros/src/util.rs
Normal file
74
embassy-executor-macros/src/util.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use proc_macro2::{TokenStream, TokenTree};
|
||||||
|
use quote::{ToTokens, TokenStreamExt};
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility};
|
||||||
|
|
||||||
|
pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||||
|
tokens.extend(error.into_compile_error());
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
|
||||||
|
s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function signature and body.
|
||||||
|
///
|
||||||
|
/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of
|
||||||
|
/// parsing it. This makes the macro not error if there's a syntax error in the body,
|
||||||
|
/// which helps IDE autocomplete work better.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ItemFn {
|
||||||
|
pub attrs: Vec<Attribute>,
|
||||||
|
pub vis: Visibility,
|
||||||
|
pub sig: Signature,
|
||||||
|
pub brace_token: token::Brace,
|
||||||
|
pub body: TokenStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ItemFn {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let mut attrs = input.call(Attribute::parse_outer)?;
|
||||||
|
let vis: Visibility = input.parse()?;
|
||||||
|
let sig: Signature = input.parse()?;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
let brace_token = braced!(content in input);
|
||||||
|
while content.peek(Token![#]) && content.peek2(Token![!]) {
|
||||||
|
let content2;
|
||||||
|
attrs.push(Attribute {
|
||||||
|
pound_token: content.parse()?,
|
||||||
|
style: AttrStyle::Inner(content.parse()?),
|
||||||
|
bracket_token: bracketed!(content2 in content),
|
||||||
|
meta: content2.parse()?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body = Vec::new();
|
||||||
|
while !content.is_empty() {
|
||||||
|
body.push(content.parse::<TokenTree>()?);
|
||||||
|
}
|
||||||
|
let body = body.into_iter().collect();
|
||||||
|
|
||||||
|
Ok(ItemFn {
|
||||||
|
attrs,
|
||||||
|
vis,
|
||||||
|
sig,
|
||||||
|
brace_token,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ItemFn {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer)));
|
||||||
|
self.vis.to_tokens(tokens);
|
||||||
|
self.sig.to_tokens(tokens);
|
||||||
|
self.brace_token.surround(tokens, |tokens| {
|
||||||
|
tokens.append_all(self.body.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,72 +0,0 @@
|
|||||||
// nifty utility borrowed from serde :)
|
|
||||||
// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::{quote, ToTokens};
|
|
||||||
|
|
||||||
/// A type to collect errors together and format them.
|
|
||||||
///
|
|
||||||
/// Dropping this object will cause a panic. It must be consumed using `check`.
|
|
||||||
///
|
|
||||||
/// References can be shared since this type uses run-time exclusive mut checking.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Ctxt {
|
|
||||||
// The contents will be set to `None` during checking. This is so that checking can be
|
|
||||||
// enforced.
|
|
||||||
errors: RefCell<Option<Vec<syn::Error>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ctxt {
|
|
||||||
/// Create a new context object.
|
|
||||||
///
|
|
||||||
/// This object contains no errors, but will still trigger a panic if it is not `check`ed.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Ctxt {
|
|
||||||
errors: RefCell::new(Some(Vec::new())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an error to the context object with a tokenenizable object.
|
|
||||||
///
|
|
||||||
/// The object is used for spanning in error messages.
|
|
||||||
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
|
|
||||||
self.errors
|
|
||||||
.borrow_mut()
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
// Curb monomorphization from generating too many identical methods.
|
|
||||||
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add one of Syn's parse errors.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn syn_error(&self, err: syn::Error) {
|
|
||||||
self.errors.borrow_mut().as_mut().unwrap().push(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consume this object, producing a formatted error string if there are errors.
|
|
||||||
pub fn check(self) -> Result<(), TokenStream> {
|
|
||||||
let errors = self.errors.borrow_mut().take().unwrap();
|
|
||||||
match errors.len() {
|
|
||||||
0 => Ok(()),
|
|
||||||
_ => Err(to_compile_errors(errors)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
|
|
||||||
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
|
|
||||||
quote!(#(#compile_errors)*)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Ctxt {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if !thread::panicking() && self.errors.borrow().is_some() {
|
|
||||||
panic!("forgot to check for errors");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
pub mod ctxt;
|
|
||||||
@ -5,13 +5,46 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## unreleased
|
||||||
|
|
||||||
|
- Added support for Cortex-A and Cortex-R
|
||||||
|
|
||||||
|
## 0.7.0 - 2025-01-02
|
||||||
|
|
||||||
|
- Performance optimizations.
|
||||||
|
- Remove feature `integrated-timers`. Starting with `embassy-time-driver` v0.2, `embassy-time` v0.4 the timer queue is now part of the time driver, so it's no longer the executor's responsibility. Therefore, `embassy-executor` no longer provides an `embassy-time-queue-driver` implementation.
|
||||||
|
- Added the possibility for timer driver implementations to store arbitrary data in task headers. This can be used to make a timer queue intrusive list, similar to the previous `integrated-timers` feature. Payload size is controlled by the `timer-item-payload-size-X` features.
|
||||||
|
- Added `TaskRef::executor` to obtain a reference to a task's executor
|
||||||
|
|
||||||
|
## 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
|
## 0.6.0 - 2024-08-05
|
||||||
|
|
||||||
- Add collapse_debuginfo to fmt.rs macros.
|
- Add collapse_debuginfo to fmt.rs macros.
|
||||||
- initial support for avr
|
- initial support for AVR
|
||||||
- use nightly waker_getters APIs
|
- 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
|
## 0.5.0 - 2024-01-11
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "embassy-executor"
|
name = "embassy-executor"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "async/await executor designed for embedded usage"
|
description = "async/await executor designed for embedded usage"
|
||||||
@ -29,13 +29,12 @@ targets = ["thumbv7em-none-eabi"]
|
|||||||
features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
|
features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "1.0.1", optional = true }
|
||||||
log = { version = "0.4.14", optional = true }
|
log = { version = "0.4.14", optional = true }
|
||||||
rtos-trace = { version = "0.1.2", optional = true }
|
rtos-trace = { version = "0.1.3", optional = true }
|
||||||
|
|
||||||
embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" }
|
embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" }
|
||||||
embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true }
|
embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true }
|
||||||
embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true }
|
|
||||||
critical-section = "1.1"
|
critical-section = "1.1"
|
||||||
|
|
||||||
document-features = "0.2.7"
|
document-features = "0.2.7"
|
||||||
@ -46,16 +45,20 @@ portable-atomic = { version = "1.5", optional = true }
|
|||||||
# arch-cortex-m dependencies
|
# arch-cortex-m dependencies
|
||||||
cortex-m = { version = "0.7.6", optional = true }
|
cortex-m = { version = "0.7.6", optional = true }
|
||||||
|
|
||||||
|
# arch-cortex-ar dependencies
|
||||||
|
cortex-ar = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# arch-wasm dependencies
|
# arch-wasm dependencies
|
||||||
wasm-bindgen = { version = "0.2.82", optional = true }
|
wasm-bindgen = { version = "0.2.82", optional = true }
|
||||||
js-sys = { version = "0.3", optional = true }
|
js-sys = { version = "0.3", optional = true }
|
||||||
|
|
||||||
# arch-avr dependencies
|
# arch-avr dependencies
|
||||||
avr-device = { version = "0.5.3", optional = true }
|
avr-device = { version = "0.7.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
critical-section = { version = "1.1", features = ["std"] }
|
critical-section = { version = "1.1", features = ["std"] }
|
||||||
|
trybuild = "1.0"
|
||||||
|
embassy-sync = { path = "../embassy-sync" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
||||||
@ -67,21 +70,22 @@ nightly = ["embassy-executor-macros/nightly"]
|
|||||||
# See: https://github.com/embassy-rs/embassy/pull/1263
|
# See: https://github.com/embassy-rs/embassy/pull/1263
|
||||||
turbowakers = []
|
turbowakers = []
|
||||||
|
|
||||||
## Use the executor-integrated `embassy-time` timer queue.
|
|
||||||
integrated-timers = ["dep:embassy-time-driver", "dep:embassy-time-queue-driver"]
|
|
||||||
|
|
||||||
#! ### Architecture
|
#! ### Architecture
|
||||||
_arch = [] # some arch was picked
|
_arch = [] # some arch was picked
|
||||||
## std
|
## std
|
||||||
arch-std = ["_arch", "critical-section/std"]
|
arch-std = ["_arch"]
|
||||||
## Cortex-M
|
## Cortex-M
|
||||||
arch-cortex-m = ["_arch", "dep:cortex-m"]
|
arch-cortex-m = ["_arch", "dep:cortex-m"]
|
||||||
|
## Cortex-A/R
|
||||||
|
arch-cortex-ar = ["_arch", "dep:cortex-ar"]
|
||||||
## RISC-V 32
|
## RISC-V 32
|
||||||
arch-riscv32 = ["_arch"]
|
arch-riscv32 = ["_arch"]
|
||||||
## WASM
|
## WASM
|
||||||
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
|
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
|
||||||
## AVR
|
## AVR
|
||||||
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
|
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
|
||||||
|
## spin (architecture agnostic; never sleeps)
|
||||||
|
arch-spin = ["_arch"]
|
||||||
|
|
||||||
#! ### Executor
|
#! ### Executor
|
||||||
|
|
||||||
@ -89,98 +93,23 @@ arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
|
|||||||
executor-thread = []
|
executor-thread = []
|
||||||
## Enable the interrupt-mode executor (available in Cortex-M only)
|
## Enable the interrupt-mode executor (available in Cortex-M only)
|
||||||
executor-interrupt = []
|
executor-interrupt = []
|
||||||
|
## Enable tracing support (adds some overhead)
|
||||||
|
trace = []
|
||||||
|
## Enable support for rtos-trace framework
|
||||||
|
rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"]
|
||||||
|
|
||||||
#! ### Task Arena Size
|
#! ### Timer Item Payload Size
|
||||||
#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`.
|
#! Sets the size of the payload for timer items, allowing integrated timer implementors to store
|
||||||
#!
|
#! additional data in the timer item. The payload field will be aligned to this value as well.
|
||||||
#! <details>
|
#! If these features are not defined, the timer item will contain no payload field.
|
||||||
#! <summary>Preconfigured Task Arena Sizes:</summary>
|
|
||||||
#! <!-- rustdoc requires the following blank line for the feature list to render correctly! -->
|
|
||||||
#!
|
|
||||||
|
|
||||||
# BEGIN AUTOGENERATED CONFIG FEATURES
|
_timer-item-payload = [] # A size was picked
|
||||||
# Generated by gen_config.py. DO NOT EDIT.
|
|
||||||
## 64
|
|
||||||
task-arena-size-64 = []
|
|
||||||
## 128
|
|
||||||
task-arena-size-128 = []
|
|
||||||
## 192
|
|
||||||
task-arena-size-192 = []
|
|
||||||
## 256
|
|
||||||
task-arena-size-256 = []
|
|
||||||
## 320
|
|
||||||
task-arena-size-320 = []
|
|
||||||
## 384
|
|
||||||
task-arena-size-384 = []
|
|
||||||
## 512
|
|
||||||
task-arena-size-512 = []
|
|
||||||
## 640
|
|
||||||
task-arena-size-640 = []
|
|
||||||
## 768
|
|
||||||
task-arena-size-768 = []
|
|
||||||
## 1024
|
|
||||||
task-arena-size-1024 = []
|
|
||||||
## 1280
|
|
||||||
task-arena-size-1280 = []
|
|
||||||
## 1536
|
|
||||||
task-arena-size-1536 = []
|
|
||||||
## 2048
|
|
||||||
task-arena-size-2048 = []
|
|
||||||
## 2560
|
|
||||||
task-arena-size-2560 = []
|
|
||||||
## 3072
|
|
||||||
task-arena-size-3072 = []
|
|
||||||
## 4096 (default)
|
|
||||||
task-arena-size-4096 = [] # Default
|
|
||||||
## 5120
|
|
||||||
task-arena-size-5120 = []
|
|
||||||
## 6144
|
|
||||||
task-arena-size-6144 = []
|
|
||||||
## 8192
|
|
||||||
task-arena-size-8192 = []
|
|
||||||
## 10240
|
|
||||||
task-arena-size-10240 = []
|
|
||||||
## 12288
|
|
||||||
task-arena-size-12288 = []
|
|
||||||
## 16384
|
|
||||||
task-arena-size-16384 = []
|
|
||||||
## 20480
|
|
||||||
task-arena-size-20480 = []
|
|
||||||
## 24576
|
|
||||||
task-arena-size-24576 = []
|
|
||||||
## 32768
|
|
||||||
task-arena-size-32768 = []
|
|
||||||
## 40960
|
|
||||||
task-arena-size-40960 = []
|
|
||||||
## 49152
|
|
||||||
task-arena-size-49152 = []
|
|
||||||
## 65536
|
|
||||||
task-arena-size-65536 = []
|
|
||||||
## 81920
|
|
||||||
task-arena-size-81920 = []
|
|
||||||
## 98304
|
|
||||||
task-arena-size-98304 = []
|
|
||||||
## 131072
|
|
||||||
task-arena-size-131072 = []
|
|
||||||
## 163840
|
|
||||||
task-arena-size-163840 = []
|
|
||||||
## 196608
|
|
||||||
task-arena-size-196608 = []
|
|
||||||
## 262144
|
|
||||||
task-arena-size-262144 = []
|
|
||||||
## 327680
|
|
||||||
task-arena-size-327680 = []
|
|
||||||
## 393216
|
|
||||||
task-arena-size-393216 = []
|
|
||||||
## 524288
|
|
||||||
task-arena-size-524288 = []
|
|
||||||
## 655360
|
|
||||||
task-arena-size-655360 = []
|
|
||||||
## 786432
|
|
||||||
task-arena-size-786432 = []
|
|
||||||
## 1048576
|
|
||||||
task-arena-size-1048576 = []
|
|
||||||
|
|
||||||
# END AUTOGENERATED CONFIG FEATURES
|
## 1 bytes
|
||||||
|
timer-item-payload-size-1 = ["_timer-item-payload"]
|
||||||
#! </details>
|
## 2 bytes
|
||||||
|
timer-item-payload-size-2 = ["_timer-item-payload"]
|
||||||
|
## 4 bytes
|
||||||
|
timer-item-payload-size-4 = ["_timer-item-payload"]
|
||||||
|
## 8 bytes
|
||||||
|
timer-item-payload-size-8 = ["_timer-item-payload"]
|
||||||
|
|||||||
@ -3,35 +3,10 @@
|
|||||||
An async/await executor designed for embedded usage.
|
An async/await executor designed for embedded usage.
|
||||||
|
|
||||||
- No `alloc`, no heap needed.
|
- No `alloc`, no heap needed.
|
||||||
- With nightly Rust, task futures can be fully statically allocated.
|
- Tasks are statically allocated. 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.
|
||||||
- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
|
- 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;`.
|
- 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`.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
|
||||||
|
|||||||
@ -1,99 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
#[path = "./build_common.rs"]
|
#[path = "./build_common.rs"]
|
||||||
mod common;
|
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() {
|
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();
|
let mut rustc_cfgs = common::CfgSet::new();
|
||||||
common::set_target_cfgs(&mut rustc_cfgs);
|
common::set_target_cfgs(&mut rustc_cfgs);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,3 +92,35 @@ pub fn set_target_cfgs(cfgs: &mut CfgSet) {
|
|||||||
|
|
||||||
cfgs.set("has_fpu", target.ends_with("-eabihf"));
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
84
embassy-executor/src/arch/cortex_ar.rs
Normal file
84
embassy-executor/src/arch/cortex_ar.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#[cfg(feature = "executor-interrupt")]
|
||||||
|
compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`.");
|
||||||
|
|
||||||
|
#[export_name = "__pender"]
|
||||||
|
#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
|
||||||
|
fn __pender(context: *mut ()) {
|
||||||
|
// `context` is always `usize::MAX` created by `Executor::run`.
|
||||||
|
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 {
|
||||||
|
cortex_ar::asm::sev();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "executor-thread")]
|
||||||
|
pub use thread::*;
|
||||||
|
#[cfg(feature = "executor-thread")]
|
||||||
|
mod thread {
|
||||||
|
pub(super) const THREAD_PENDER: usize = usize::MAX;
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use cortex_ar::asm::wfe;
|
||||||
|
pub use embassy_executor_macros::main_cortex_ar 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();
|
||||||
|
}
|
||||||
|
wfe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -143,7 +143,7 @@ mod interrupt {
|
|||||||
/// If this is not the case, you may use an interrupt from any unused peripheral.
|
/// 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
|
/// It is somewhat more complex to use, it's recommended to use the thread-mode
|
||||||
/// [`Executor`] instead, if it works for your use case.
|
/// [`Executor`](crate::Executor) instead, if it works for your use case.
|
||||||
pub struct InterruptExecutor {
|
pub struct InterruptExecutor {
|
||||||
started: Mutex<Cell<bool>>,
|
started: Mutex<Cell<bool>>,
|
||||||
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
|
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
|
||||||
@ -179,11 +179,11 @@ mod interrupt {
|
|||||||
/// The executor keeps running in the background through the interrupt.
|
/// The executor keeps running in the background through the interrupt.
|
||||||
///
|
///
|
||||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
|
/// 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
|
/// is returned instead of a [`Spawner`](crate::Spawner) because the executor effectively runs in a
|
||||||
/// different "thread" (the interrupt), so spawning tasks on it is effectively
|
/// different "thread" (the interrupt), so spawning tasks on it is effectively
|
||||||
/// sending them.
|
/// sending them.
|
||||||
///
|
///
|
||||||
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
|
/// To obtain a [`Spawner`](crate::Spawner) for this executor, use [`Spawner::for_current_executor()`](crate::Spawner::for_current_executor()) from
|
||||||
/// a task running in it.
|
/// a task running in it.
|
||||||
///
|
///
|
||||||
/// # Interrupt requirements
|
/// # Interrupt requirements
|
||||||
@ -195,6 +195,7 @@ mod interrupt {
|
|||||||
/// You must set the interrupt priority before calling this method. You MUST NOT
|
/// You must set the interrupt priority before calling this method. You MUST NOT
|
||||||
/// do it after.
|
/// do it after.
|
||||||
///
|
///
|
||||||
|
/// [`SendSpawner`]: crate::SendSpawner
|
||||||
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
|
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
|
||||||
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
|
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
|
||||||
panic!("InterruptExecutor::start() called multiple times on the same executor.");
|
panic!("InterruptExecutor::start() called multiple times on the same executor.");
|
||||||
@ -215,7 +216,7 @@ mod interrupt {
|
|||||||
|
|
||||||
/// Get a SendSpawner for this executor
|
/// Get a SendSpawner for this executor
|
||||||
///
|
///
|
||||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on this
|
/// This returns a [`SendSpawner`](crate::SendSpawner) you can use to spawn tasks on this
|
||||||
/// executor.
|
/// executor.
|
||||||
///
|
///
|
||||||
/// This MUST only be called on an executor that has already been started.
|
/// This MUST only be called on an executor that has already been started.
|
||||||
|
|||||||
58
embassy-executor/src/arch/spin.rs
Normal file
58
embassy-executor/src/arch/spin.rs
Normal file
@ -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() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
||||||
#![cfg_attr(feature = "nightly", feature(waker_getters))]
|
|
||||||
#![allow(clippy::new_without_default)]
|
#![allow(clippy::new_without_default)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
@ -24,120 +23,186 @@ macro_rules! check_at_most_one {
|
|||||||
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
|
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",);
|
check_at_most_one!(
|
||||||
|
"arch-avr",
|
||||||
|
"arch-cortex-m",
|
||||||
|
"arch-cortex-ar",
|
||||||
|
"arch-riscv32",
|
||||||
|
"arch-std",
|
||||||
|
"arch-wasm",
|
||||||
|
"arch-spin",
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(feature = "_arch")]
|
#[cfg(feature = "_arch")]
|
||||||
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
|
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
|
||||||
#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
|
#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
|
||||||
|
#[cfg_attr(feature = "arch-cortex-ar", path = "arch/cortex_ar.rs")]
|
||||||
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
|
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
|
||||||
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
|
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
|
||||||
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
|
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
|
||||||
|
#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")]
|
||||||
mod arch;
|
mod arch;
|
||||||
|
|
||||||
#[cfg(feature = "_arch")]
|
#[cfg(feature = "_arch")]
|
||||||
#[allow(unused_imports)] // don't warn if the module is empty.
|
#[allow(unused_imports)] // don't warn if the module is empty.
|
||||||
pub use arch::*;
|
pub use arch::*;
|
||||||
|
#[cfg(not(feature = "_arch"))]
|
||||||
|
pub use embassy_executor_macros::main_unspecified as main;
|
||||||
|
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
|
|
||||||
mod spawner;
|
mod spawner;
|
||||||
pub use spawner::*;
|
pub use spawner::*;
|
||||||
|
|
||||||
mod config {
|
|
||||||
#![allow(unused)]
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation details for embassy macros.
|
/// Implementation details for embassy macros.
|
||||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg(not(feature = "nightly"))]
|
#[cfg(not(feature = "nightly"))]
|
||||||
pub mod _export {
|
pub mod _export {
|
||||||
use core::alloc::Layout;
|
use core::cell::UnsafeCell;
|
||||||
use core::cell::{Cell, UnsafeCell};
|
|
||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use core::ptr::null_mut;
|
|
||||||
|
|
||||||
use critical_section::{CriticalSection, Mutex};
|
|
||||||
|
|
||||||
use crate::raw::TaskPool;
|
use crate::raw::TaskPool;
|
||||||
|
|
||||||
struct Arena<const N: usize> {
|
pub trait TaskFn<Args>: Copy {
|
||||||
buf: UnsafeCell<MaybeUninit<[u8; N]>>,
|
type Fut: Future + 'static;
|
||||||
ptr: Mutex<Cell<*mut u8>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<const N: usize> Sync for Arena<N> {}
|
macro_rules! task_fn_impl {
|
||||||
unsafe impl<const N: usize> Send for Arena<N> {}
|
($($Tn:ident),*) => {
|
||||||
|
impl<F, Fut, $($Tn,)*> TaskFn<($($Tn,)*)> for F
|
||||||
impl<const N: usize> Arena<N> {
|
where
|
||||||
const fn new() -> Self {
|
F: Copy + FnOnce($($Tn,)*) -> Fut,
|
||||||
Self {
|
Fut: Future + 'static,
|
||||||
buf: UnsafeCell::new(MaybeUninit::uninit()),
|
{
|
||||||
ptr: Mutex::new(Cell::new(null_mut())),
|
type Fut = Fut;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
|
task_fn_impl!();
|
||||||
let layout = Layout::new::<T>();
|
task_fn_impl!(T0);
|
||||||
|
task_fn_impl!(T0, T1);
|
||||||
|
task_fn_impl!(T0, T1, T2);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
|
||||||
|
task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
|
||||||
|
|
||||||
let start = self.buf.get().cast::<u8>();
|
#[allow(private_bounds)]
|
||||||
let end = unsafe { start.add(N) };
|
#[repr(C)]
|
||||||
|
pub struct TaskPoolHolder<const SIZE: usize, const ALIGN: usize>
|
||||||
|
where
|
||||||
|
Align<ALIGN>: Alignment,
|
||||||
|
{
|
||||||
|
data: UnsafeCell<[MaybeUninit<u8>; SIZE]>,
|
||||||
|
align: Align<ALIGN>,
|
||||||
|
}
|
||||||
|
|
||||||
let mut ptr = self.ptr.borrow(cs).get();
|
unsafe impl<const SIZE: usize, const ALIGN: usize> Send for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {}
|
||||||
if ptr.is_null() {
|
unsafe impl<const SIZE: usize, const ALIGN: usize> Sync for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {}
|
||||||
ptr = self.buf.get().cast::<u8>();
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes_left = (end as usize) - (ptr as usize);
|
#[allow(private_bounds)]
|
||||||
let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
|
impl<const SIZE: usize, const ALIGN: usize> TaskPoolHolder<SIZE, ALIGN>
|
||||||
|
where
|
||||||
if align_offset + layout.size() > bytes_left {
|
Align<ALIGN>: Alignment,
|
||||||
panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
|
{
|
||||||
}
|
pub const fn get(&self) -> *const u8 {
|
||||||
|
self.data.get().cast()
|
||||||
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 const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
|
||||||
|
where
|
||||||
pub struct TaskPoolRef {
|
F: TaskFn<Args, Fut = Fut>,
|
||||||
// type-erased `&'static mut TaskPool<F, N>`
|
Fut: Future + 'static,
|
||||||
// Needed because statics can't have generics.
|
{
|
||||||
ptr: Mutex<Cell<*mut ()>>,
|
size_of::<TaskPool<Fut, POOL_SIZE>>()
|
||||||
}
|
}
|
||||||
unsafe impl Sync for TaskPoolRef {}
|
|
||||||
unsafe impl Send for TaskPoolRef {}
|
|
||||||
|
|
||||||
impl TaskPoolRef {
|
pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
|
||||||
pub const fn new() -> Self {
|
where
|
||||||
Self {
|
F: TaskFn<Args, Fut = Fut>,
|
||||||
ptr: Mutex::new(Cell::new(null_mut())),
|
Fut: Future + 'static,
|
||||||
}
|
{
|
||||||
}
|
align_of::<TaskPool<Fut, POOL_SIZE>>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the pool for this ref, allocating it from the arena the first time.
|
pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE>
|
||||||
///
|
where
|
||||||
/// safety: for a given TaskPoolRef instance, must always call with the exact
|
F: TaskFn<Args, Fut = Fut>,
|
||||||
/// same generic params.
|
Fut: Future + 'static,
|
||||||
pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
|
{
|
||||||
critical_section::with(|cs| {
|
TaskPool::new()
|
||||||
let ptr = self.ptr.borrow(cs);
|
}
|
||||||
if ptr.get().is_null() {
|
|
||||||
let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
|
#[allow(private_bounds)]
|
||||||
pool.write(TaskPool::new());
|
#[repr(transparent)]
|
||||||
ptr.set(pool as *mut _ as _);
|
pub struct Align<const N: usize>([<Self as Alignment>::Archetype; 0])
|
||||||
|
where
|
||||||
|
Self: Alignment;
|
||||||
|
|
||||||
|
trait Alignment {
|
||||||
|
/// A zero-sized type of particular alignment.
|
||||||
|
type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! aligns {
|
||||||
|
($($AlignX:ident: $n:literal,)*) => {
|
||||||
|
$(
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
#[repr(align($n))]
|
||||||
|
struct $AlignX {}
|
||||||
|
impl Alignment for Align<$n> {
|
||||||
|
type Archetype = $AlignX;
|
||||||
}
|
}
|
||||||
|
)*
|
||||||
unsafe { &*(ptr.get() as *const _) }
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aligns!(
|
||||||
|
Align1: 1,
|
||||||
|
Align2: 2,
|
||||||
|
Align4: 4,
|
||||||
|
Align8: 8,
|
||||||
|
Align16: 16,
|
||||||
|
Align32: 32,
|
||||||
|
Align64: 64,
|
||||||
|
Align128: 128,
|
||||||
|
Align256: 256,
|
||||||
|
Align512: 512,
|
||||||
|
Align1024: 1024,
|
||||||
|
Align2048: 2048,
|
||||||
|
Align4096: 4096,
|
||||||
|
Align8192: 8192,
|
||||||
|
Align16384: 16384,
|
||||||
|
);
|
||||||
|
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
|
||||||
|
aligns!(
|
||||||
|
Align32768: 32768,
|
||||||
|
Align65536: 65536,
|
||||||
|
Align131072: 131072,
|
||||||
|
Align262144: 262144,
|
||||||
|
Align524288: 524288,
|
||||||
|
Align1048576: 1048576,
|
||||||
|
Align2097152: 2097152,
|
||||||
|
Align4194304: 4194304,
|
||||||
|
Align8388608: 8388608,
|
||||||
|
Align16777216: 16777216,
|
||||||
|
Align33554432: 33554432,
|
||||||
|
Align67108864: 67108864,
|
||||||
|
Align134217728: 134217728,
|
||||||
|
Align268435456: 268435456,
|
||||||
|
Align536870912: 536870912,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,13 +11,14 @@
|
|||||||
#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
|
#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
|
||||||
mod run_queue;
|
mod run_queue;
|
||||||
|
|
||||||
#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")]
|
#[cfg_attr(all(cortex_m, target_has_atomic = "32"), path = "state_atomics_arm.rs")]
|
||||||
#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.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")]
|
#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")]
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
pub mod timer_queue;
|
||||||
mod timer_queue;
|
#[cfg(feature = "trace")]
|
||||||
|
pub mod trace;
|
||||||
pub(crate) mod util;
|
pub(crate) mod util;
|
||||||
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
|
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
|
||||||
mod waker;
|
mod waker;
|
||||||
@ -27,12 +28,13 @@ use core::marker::PhantomData;
|
|||||||
use core::mem;
|
use core::mem;
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
use core::ptr::NonNull;
|
use core::ptr::NonNull;
|
||||||
|
#[cfg(not(feature = "arch-avr"))]
|
||||||
|
use core::sync::atomic::AtomicPtr;
|
||||||
|
use core::sync::atomic::Ordering;
|
||||||
use core::task::{Context, Poll};
|
use core::task::{Context, Poll};
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
#[cfg(feature = "arch-avr")]
|
||||||
use embassy_time_driver::AlarmHandle;
|
use portable_atomic::AtomicPtr;
|
||||||
#[cfg(feature = "rtos-trace")]
|
|
||||||
use rtos_trace::trace;
|
|
||||||
|
|
||||||
use self::run_queue::{RunQueue, RunQueueItem};
|
use self::run_queue::{RunQueue, RunQueueItem};
|
||||||
use self::state::State;
|
use self::state::State;
|
||||||
@ -41,20 +43,62 @@ pub use self::waker::task_from_waker;
|
|||||||
use super::SpawnToken;
|
use super::SpawnToken;
|
||||||
|
|
||||||
/// Raw task header for use in task pointers.
|
/// Raw task header for use in task pointers.
|
||||||
|
///
|
||||||
|
/// A task can be in one of the following states:
|
||||||
|
///
|
||||||
|
/// - Not spawned: the task is ready to spawn.
|
||||||
|
/// - `SPAWNED`: the task is currently spawned and may be running.
|
||||||
|
/// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`.
|
||||||
|
/// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without
|
||||||
|
/// polling the task's future.
|
||||||
|
///
|
||||||
|
/// A task's complete life cycle is as follows:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ┌────────────┐ ┌────────────────────────┐
|
||||||
|
/// │Not spawned │◄─5┤Not spawned|Run enqueued│
|
||||||
|
/// │ ├6─►│ │
|
||||||
|
/// └─────┬──────┘ └──────▲─────────────────┘
|
||||||
|
/// 1 │
|
||||||
|
/// │ ┌────────────┘
|
||||||
|
/// │ 4
|
||||||
|
/// ┌─────▼────┴─────────┐
|
||||||
|
/// │Spawned|Run enqueued│
|
||||||
|
/// │ │
|
||||||
|
/// └─────┬▲─────────────┘
|
||||||
|
/// 2│
|
||||||
|
/// │3
|
||||||
|
/// ┌─────▼┴─────┐
|
||||||
|
/// │ Spawned │
|
||||||
|
/// │ │
|
||||||
|
/// └────────────┘
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Transitions:
|
||||||
|
/// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn`
|
||||||
|
/// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue`
|
||||||
|
/// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue`
|
||||||
|
/// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready`
|
||||||
|
/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`.
|
||||||
|
/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue`
|
||||||
pub(crate) struct TaskHeader {
|
pub(crate) struct TaskHeader {
|
||||||
pub(crate) state: State,
|
pub(crate) state: State,
|
||||||
pub(crate) run_queue_item: RunQueueItem,
|
pub(crate) run_queue_item: RunQueueItem,
|
||||||
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
|
pub(crate) executor: AtomicPtr<SyncExecutor>,
|
||||||
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
|
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
/// Integrated timer queue storage. This field should not be accessed outside of the timer queue.
|
||||||
pub(crate) expires_at: SyncUnsafeCell<u64>,
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
|
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
pub(crate) name: Option<&'static str>,
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
pub(crate) id: u32,
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
all_tasks_next: AtomicPtr<TaskHeader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
|
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct TaskRef {
|
pub struct TaskRef {
|
||||||
ptr: NonNull<TaskHeader>,
|
ptr: NonNull<TaskHeader>,
|
||||||
}
|
}
|
||||||
@ -76,10 +120,31 @@ impl TaskRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The result of this function must only be compared
|
||||||
|
/// for equality, or stored, but not used.
|
||||||
|
pub const unsafe fn dangling() -> Self {
|
||||||
|
Self {
|
||||||
|
ptr: NonNull::dangling(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn header(self) -> &'static TaskHeader {
|
pub(crate) fn header(self) -> &'static TaskHeader {
|
||||||
unsafe { self.ptr.as_ref() }
|
unsafe { self.ptr.as_ref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the executor that the task is currently running on.
|
||||||
|
pub unsafe fn executor(self) -> Option<&'static Executor> {
|
||||||
|
let executor = self.header().executor.load(Ordering::Relaxed);
|
||||||
|
executor.as_ref().map(|e| Executor::wrap(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the timer queue item.
|
||||||
|
pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem {
|
||||||
|
&self.header().timer_queue_item
|
||||||
|
}
|
||||||
|
|
||||||
/// The returned pointer is valid for the entire TaskStorage.
|
/// The returned pointer is valid for the entire TaskStorage.
|
||||||
pub(crate) fn as_ptr(self) -> *const TaskHeader {
|
pub(crate) fn as_ptr(self) -> *const TaskHeader {
|
||||||
self.ptr.as_ptr()
|
self.ptr.as_ptr()
|
||||||
@ -107,6 +172,10 @@ pub struct TaskStorage<F: Future + 'static> {
|
|||||||
future: UninitCell<F>, // Valid if STATE_SPAWNED
|
future: UninitCell<F>, // Valid if STATE_SPAWNED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn poll_exited(_p: TaskRef) {
|
||||||
|
// Nothing to do, the task is already !SPAWNED and dequeued.
|
||||||
|
}
|
||||||
|
|
||||||
impl<F: Future + 'static> TaskStorage<F> {
|
impl<F: Future + 'static> TaskStorage<F> {
|
||||||
const NEW: Self = Self::new();
|
const NEW: Self = Self::new();
|
||||||
|
|
||||||
@ -116,14 +185,17 @@ impl<F: Future + 'static> TaskStorage<F> {
|
|||||||
raw: TaskHeader {
|
raw: TaskHeader {
|
||||||
state: State::new(),
|
state: State::new(),
|
||||||
run_queue_item: RunQueueItem::new(),
|
run_queue_item: RunQueueItem::new(),
|
||||||
executor: SyncUnsafeCell::new(None),
|
executor: AtomicPtr::new(core::ptr::null_mut()),
|
||||||
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
|
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
|
||||||
poll_fn: SyncUnsafeCell::new(None),
|
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(),
|
timer_queue_item: timer_queue::TimerQueueItem::new(),
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
name: None,
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
id: 0,
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
all_tasks_next: AtomicPtr::new(core::ptr::null_mut()),
|
||||||
},
|
},
|
||||||
future: UninitCell::uninit(),
|
future: UninitCell::uninit(),
|
||||||
}
|
}
|
||||||
@ -151,18 +223,30 @@ impl<F: Future + 'static> TaskStorage<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn poll(p: TaskRef) {
|
unsafe fn poll(p: TaskRef) {
|
||||||
let this = &*(p.as_ptr() as *const TaskStorage<F>);
|
let this = &*p.as_ptr().cast::<TaskStorage<F>>();
|
||||||
|
|
||||||
let future = Pin::new_unchecked(this.future.as_mut());
|
let future = Pin::new_unchecked(this.future.as_mut());
|
||||||
let waker = waker::from_task(p);
|
let waker = waker::from_task(p);
|
||||||
let mut cx = Context::from_waker(&waker);
|
let mut cx = Context::from_waker(&waker);
|
||||||
match future.poll(&mut cx) {
|
match future.poll(&mut cx) {
|
||||||
Poll::Ready(_) => {
|
Poll::Ready(_) => {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
// As the future has finished and this function will not be called
|
||||||
|
// again, we can safely drop the future here.
|
||||||
this.future.drop_in_place();
|
this.future.drop_in_place();
|
||||||
|
|
||||||
|
// We replace the poll_fn with a despawn function, so that the task is cleaned up
|
||||||
|
// when the executor polls it next.
|
||||||
|
this.raw.poll_fn.set(Some(poll_exited));
|
||||||
|
|
||||||
|
// Make sure we despawn last, so that other threads can only spawn the task
|
||||||
|
// after we're done with it.
|
||||||
this.raw.state.despawn();
|
this.raw.state.despawn();
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
#[cfg(feature = "trace")]
|
||||||
this.raw.expires_at.set(u64::MAX);
|
trace::task_end(exec_ptr, &p);
|
||||||
}
|
}
|
||||||
Poll::Pending => {}
|
Poll::Pending => {}
|
||||||
}
|
}
|
||||||
@ -316,26 +400,13 @@ impl Pender {
|
|||||||
pub(crate) struct SyncExecutor {
|
pub(crate) struct SyncExecutor {
|
||||||
run_queue: RunQueue,
|
run_queue: RunQueue,
|
||||||
pender: Pender,
|
pender: Pender,
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
pub(crate) timer_queue: timer_queue::TimerQueue,
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
alarm: AlarmHandle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncExecutor {
|
impl SyncExecutor {
|
||||||
pub(crate) fn new(pender: Pender) -> Self {
|
pub(crate) fn new(pender: Pender) -> Self {
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
let alarm = unsafe { unwrap!(embassy_time_driver::allocate_alarm()) };
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
run_queue: RunQueue::new(),
|
run_queue: RunQueue::new(),
|
||||||
pender,
|
pender,
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
timer_queue: timer_queue::TimerQueue::new(),
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
alarm,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,90 +417,50 @@ impl SyncExecutor {
|
|||||||
/// - `task` must be set up to run in this executor.
|
/// - `task` must be set up to run in this executor.
|
||||||
/// - `task` must NOT be already enqueued (in this executor or another one).
|
/// - `task` must NOT be already enqueued (in this executor or another one).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
unsafe fn enqueue(&self, task: TaskRef) {
|
unsafe fn enqueue(&self, task: TaskRef, l: state::Token) {
|
||||||
#[cfg(feature = "rtos-trace")]
|
#[cfg(feature = "trace")]
|
||||||
trace::task_ready_begin(task.as_ptr() as u32);
|
trace::task_ready_begin(self, &task);
|
||||||
|
|
||||||
if self.run_queue.enqueue(task) {
|
if self.run_queue.enqueue(task, l) {
|
||||||
self.pender.pend();
|
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) {
|
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
|
||||||
task.header().executor.set(Some(self));
|
task.header()
|
||||||
|
.executor
|
||||||
|
.store((self as *const Self).cast_mut(), Ordering::Relaxed);
|
||||||
|
|
||||||
#[cfg(feature = "rtos-trace")]
|
#[cfg(feature = "trace")]
|
||||||
trace::task_new(task.as_ptr() as u32);
|
trace::task_new(self, &task);
|
||||||
|
|
||||||
self.enqueue(task);
|
state::locked(|l| {
|
||||||
|
self.enqueue(task, l);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
|
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
|
||||||
pub(crate) unsafe fn poll(&'static self) {
|
pub(crate) unsafe fn poll(&'static self) {
|
||||||
#[cfg(feature = "integrated-timers")]
|
#[cfg(feature = "trace")]
|
||||||
embassy_time_driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ());
|
trace::poll_start(self);
|
||||||
|
|
||||||
#[allow(clippy::never_loop)]
|
self.run_queue.dequeue_all(|p| {
|
||||||
loop {
|
let task = p.header();
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
self.timer_queue
|
|
||||||
.dequeue_expired(embassy_time_driver::now(), wake_task_no_pend);
|
|
||||||
|
|
||||||
self.run_queue.dequeue_all(|p| {
|
#[cfg(feature = "trace")]
|
||||||
let task = p.header();
|
trace::task_exec_begin(self, &p);
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
// Run the task
|
||||||
task.expires_at.set(u64::MAX);
|
task.poll_fn.get().unwrap_unchecked()(p);
|
||||||
|
|
||||||
if !task.state.run_dequeue() {
|
#[cfg(feature = "trace")]
|
||||||
// If task is not running, ignore it. This can happen in the following scenario:
|
trace::task_exec_end(self, &p);
|
||||||
// - 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")]
|
#[cfg(feature = "trace")]
|
||||||
trace::task_exec_begin(p.as_ptr() as u32);
|
trace::executor_idle(self)
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,6 +547,8 @@ impl Executor {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
|
/// You must call `initialize` before calling this method.
|
||||||
|
///
|
||||||
/// You must NOT call `poll` reentrantly on the same executor.
|
/// You must NOT call `poll` reentrantly on the same executor.
|
||||||
///
|
///
|
||||||
/// In particular, note that `poll` may call the pender synchronously. Therefore, you
|
/// In particular, note that `poll` may call the pender synchronously. Therefore, you
|
||||||
@ -533,6 +566,11 @@ impl Executor {
|
|||||||
pub fn spawner(&'static self) -> super::Spawner {
|
pub fn spawner(&'static self) -> super::Spawner {
|
||||||
super::Spawner::new(self)
|
super::Spawner::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a unique ID for this Executor.
|
||||||
|
pub fn id(&'static self) -> usize {
|
||||||
|
&self.inner as *const SyncExecutor as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wake a task by `TaskRef`.
|
/// Wake a task by `TaskRef`.
|
||||||
@ -540,13 +578,13 @@ impl Executor {
|
|||||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||||
pub fn wake_task(task: TaskRef) {
|
pub fn wake_task(task: TaskRef) {
|
||||||
let header = task.header();
|
let header = task.header();
|
||||||
if header.state.run_enqueue() {
|
header.state.run_enqueue(|l| {
|
||||||
// We have just marked the task as scheduled, so enqueue it.
|
// We have just marked the task as scheduled, so enqueue it.
|
||||||
unsafe {
|
unsafe {
|
||||||
let executor = header.executor.get().unwrap_unchecked();
|
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
|
||||||
executor.enqueue(task);
|
executor.enqueue(task, l);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wake a task by `TaskRef` without calling pend.
|
/// Wake a task by `TaskRef` without calling pend.
|
||||||
@ -554,57 +592,11 @@ pub fn wake_task(task: TaskRef) {
|
|||||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||||
pub fn wake_task_no_pend(task: TaskRef) {
|
pub fn wake_task_no_pend(task: TaskRef) {
|
||||||
let header = task.header();
|
let header = task.header();
|
||||||
if header.state.run_enqueue() {
|
header.state.run_enqueue(|l| {
|
||||||
// We have just marked the task as scheduled, so enqueue it.
|
// We have just marked the task as scheduled, so enqueue it.
|
||||||
unsafe {
|
unsafe {
|
||||||
let executor = header.executor.get().unwrap_unchecked();
|
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
|
||||||
executor.run_queue.enqueue(task);
|
executor.run_queue.enqueue(task, l);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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}
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ impl RunQueue {
|
|||||||
///
|
///
|
||||||
/// `item` must NOT be already enqueued in any queue.
|
/// `item` must NOT be already enqueued in any queue.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
|
pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool {
|
||||||
let mut was_empty = false;
|
let mut was_empty = false;
|
||||||
|
|
||||||
self.head
|
self.head
|
||||||
@ -81,6 +81,7 @@ impl RunQueue {
|
|||||||
// safety: there are no concurrent accesses to `next`
|
// safety: there are no concurrent accesses to `next`
|
||||||
next = unsafe { task.header().run_queue_item.next.get() };
|
next = unsafe { task.header().run_queue_item.next.get() };
|
||||||
|
|
||||||
|
task.header().state.run_dequeue();
|
||||||
on_task(task);
|
on_task(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,13 +44,11 @@ impl RunQueue {
|
|||||||
///
|
///
|
||||||
/// `item` must NOT be already enqueued in any queue.
|
/// `item` must NOT be already enqueued in any queue.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
|
pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool {
|
||||||
critical_section::with(|cs| {
|
let prev = self.head.borrow(cs).replace(Some(task));
|
||||||
let prev = self.head.borrow(cs).replace(Some(task));
|
task.header().run_queue_item.next.borrow(cs).set(prev);
|
||||||
task.header().run_queue_item.next.borrow(cs).set(prev);
|
|
||||||
|
|
||||||
prev.is_none()
|
prev.is_none()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Empty the queue, then call `on_task` for each task that was in the queue.
|
/// Empty the queue, then call `on_task` for each task that was in the queue.
|
||||||
@ -65,9 +63,10 @@ impl RunQueue {
|
|||||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||||
// Therefore, first read the next pointer, and only then process the task.
|
// 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.
|
critical_section::with(|cs| {
|
||||||
let cs = unsafe { CriticalSection::new() };
|
next = task.header().run_queue_item.next.borrow(cs).get();
|
||||||
next = task.header().run_queue_item.next.borrow(cs).get();
|
task.header().state.run_dequeue(cs);
|
||||||
|
});
|
||||||
|
|
||||||
on_task(task);
|
on_task(task);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,28 @@
|
|||||||
use core::sync::atomic::{AtomicU32, Ordering};
|
use core::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct Token(());
|
||||||
|
|
||||||
|
/// Creates a token and passes it to the closure.
|
||||||
|
///
|
||||||
|
/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
|
||||||
|
pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
|
||||||
|
f(Token(()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Task is spawned (has a future)
|
/// Task is spawned (has a future)
|
||||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
pub(crate) const STATE_SPAWNED: u8 = 1 << 0;
|
||||||
/// Task is in the executor run queue
|
/// Task is in the executor run queue
|
||||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
|
pub(crate) const STATE_RUN_QUEUED: u8 = 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 {
|
pub(crate) struct State {
|
||||||
state: AtomicU32,
|
state: AtomicU8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub const fn new() -> State {
|
pub const fn new() -> State {
|
||||||
Self {
|
Self {
|
||||||
state: AtomicU32::new(0),
|
state: AtomicU8::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,41 +40,19 @@ impl State {
|
|||||||
self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
|
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.
|
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
|
||||||
|
/// function if the task was successfully marked.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_enqueue(&self) -> bool {
|
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
|
||||||
self.state
|
let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel);
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
|
if prev & STATE_RUN_QUEUED == 0 {
|
||||||
// If already scheduled, or if not started,
|
locked(f);
|
||||||
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.
|
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_dequeue(&self) -> bool {
|
pub fn run_dequeue(&self) {
|
||||||
let state = self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
use core::arch::asm;
|
|
||||||
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
|
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct Token(());
|
||||||
|
|
||||||
|
/// Creates a token and passes it to the closure.
|
||||||
|
///
|
||||||
|
/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
|
||||||
|
pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
|
||||||
|
f(Token(()))
|
||||||
|
}
|
||||||
|
|
||||||
// Must be kept in sync with the layout of `State`!
|
// Must be kept in sync with the layout of `State`!
|
||||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
||||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
|
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
|
||||||
@ -11,9 +20,8 @@ pub(crate) struct State {
|
|||||||
spawned: AtomicBool,
|
spawned: AtomicBool,
|
||||||
/// Task is in the executor run queue
|
/// Task is in the executor run queue
|
||||||
run_queued: AtomicBool,
|
run_queued: AtomicBool,
|
||||||
/// Task is in the executor timer queue
|
|
||||||
timer_queued: AtomicBool,
|
|
||||||
pad: AtomicBool,
|
pad: AtomicBool,
|
||||||
|
pad2: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@ -21,8 +29,8 @@ impl State {
|
|||||||
Self {
|
Self {
|
||||||
spawned: AtomicBool::new(false),
|
spawned: AtomicBool::new(false),
|
||||||
run_queued: AtomicBool::new(false),
|
run_queued: AtomicBool::new(false),
|
||||||
timer_queued: AtomicBool::new(false),
|
|
||||||
pad: AtomicBool::new(false),
|
pad: AtomicBool::new(false),
|
||||||
|
pad2: AtomicBool::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,50 +62,22 @@ impl State {
|
|||||||
self.spawned.store(false, Ordering::Relaxed);
|
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.
|
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
|
||||||
|
/// function if the task was successfully marked.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_enqueue(&self) -> bool {
|
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
|
||||||
unsafe {
|
let old = self.run_queued.swap(true, Ordering::AcqRel);
|
||||||
loop {
|
|
||||||
let state: u32;
|
|
||||||
asm!("ldrex {}, [{}]", out(reg) state, in(reg) self, options(nostack));
|
|
||||||
|
|
||||||
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
|
if !old {
|
||||||
asm!("clrex", options(nomem, nostack));
|
locked(f);
|
||||||
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.
|
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_dequeue(&self) -> bool {
|
pub fn run_dequeue(&self) {
|
||||||
compiler_fence(Ordering::Release);
|
compiler_fence(Ordering::Release);
|
||||||
|
|
||||||
let r = self.spawned.load(Ordering::Relaxed);
|
|
||||||
self.run_queued.store(false, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
|
|
||||||
use critical_section::Mutex;
|
pub(crate) use critical_section::{with as locked, CriticalSection as Token};
|
||||||
|
use critical_section::{CriticalSection, Mutex};
|
||||||
|
|
||||||
/// Task is spawned (has a future)
|
/// Task is spawned (has a future)
|
||||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
pub(crate) const STATE_SPAWNED: u8 = 1 << 0;
|
||||||
/// Task is in the executor run queue
|
/// Task is in the executor run queue
|
||||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
|
pub(crate) const STATE_RUN_QUEUED: u8 = 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 {
|
pub(crate) struct State {
|
||||||
state: Mutex<Cell<u32>>,
|
state: Mutex<Cell<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@ -21,14 +19,16 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
|
fn update<R>(&self, f: impl FnOnce(&mut u8) -> R) -> R {
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| self.update_with_cs(cs, f))
|
||||||
let s = self.state.borrow(cs);
|
}
|
||||||
let mut val = s.get();
|
|
||||||
let r = f(&mut val);
|
fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u8) -> R) -> R {
|
||||||
s.set(val);
|
let s = self.state.borrow(cs);
|
||||||
r
|
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.
|
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||||
@ -50,44 +50,24 @@ impl State {
|
|||||||
self.update(|s| *s &= !STATE_SPAWNED);
|
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.
|
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
|
||||||
|
/// function if the task was successfully marked.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_enqueue(&self) -> bool {
|
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
|
||||||
self.update(|s| {
|
critical_section::with(|cs| {
|
||||||
if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) {
|
if self.update_with_cs(cs, |s| {
|
||||||
false
|
let ok = *s & STATE_RUN_QUEUED == 0;
|
||||||
} else {
|
|
||||||
*s |= STATE_RUN_QUEUED;
|
*s |= STATE_RUN_QUEUED;
|
||||||
true
|
ok
|
||||||
|
}) {
|
||||||
|
f(cs);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn run_dequeue(&self) -> bool {
|
pub fn run_dequeue(&self, cs: CriticalSection<'_>) {
|
||||||
self.update(|s| {
|
self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED)
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,76 +1,73 @@
|
|||||||
use core::cmp::min;
|
//! Timer queue operations.
|
||||||
|
|
||||||
|
use core::cell::Cell;
|
||||||
|
|
||||||
use super::TaskRef;
|
use super::TaskRef;
|
||||||
use crate::raw::util::SyncUnsafeCell;
|
|
||||||
|
|
||||||
pub(crate) struct TimerQueueItem {
|
#[cfg(feature = "_timer-item-payload")]
|
||||||
next: SyncUnsafeCell<Option<TaskRef>>,
|
macro_rules! define_opaque {
|
||||||
|
($size:tt) => {
|
||||||
|
/// An opaque data type.
|
||||||
|
#[repr(align($size))]
|
||||||
|
pub struct OpaqueData {
|
||||||
|
data: [u8; $size],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpaqueData {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self { data: [0; $size] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the data as a reference to a type `T`.
|
||||||
|
///
|
||||||
|
/// Safety:
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the size of the type `T` is less than, or equal to
|
||||||
|
/// the size of the payload, and must ensure that the alignment of the type `T` is
|
||||||
|
/// less than, or equal to the alignment of the payload.
|
||||||
|
///
|
||||||
|
/// The type must be valid when zero-initialized.
|
||||||
|
pub unsafe fn as_ref<T>(&self) -> &T {
|
||||||
|
&*(self.data.as_ptr() as *const T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "timer-item-payload-size-1")]
|
||||||
|
define_opaque!(1);
|
||||||
|
#[cfg(feature = "timer-item-payload-size-2")]
|
||||||
|
define_opaque!(2);
|
||||||
|
#[cfg(feature = "timer-item-payload-size-4")]
|
||||||
|
define_opaque!(4);
|
||||||
|
#[cfg(feature = "timer-item-payload-size-8")]
|
||||||
|
define_opaque!(8);
|
||||||
|
|
||||||
|
/// An item in the timer queue.
|
||||||
|
pub struct TimerQueueItem {
|
||||||
|
/// The next item in the queue.
|
||||||
|
///
|
||||||
|
/// If this field contains `Some`, the item is in the queue. The last item in the queue has a
|
||||||
|
/// value of `Some(dangling_pointer)`
|
||||||
|
pub next: Cell<Option<TaskRef>>,
|
||||||
|
|
||||||
|
/// The time at which this item expires.
|
||||||
|
pub expires_at: Cell<u64>,
|
||||||
|
|
||||||
|
/// Some implementation-defined, zero-initialized piece of data.
|
||||||
|
#[cfg(feature = "_timer-item-payload")]
|
||||||
|
pub payload: OpaqueData,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for TimerQueueItem {}
|
||||||
|
|
||||||
impl TimerQueueItem {
|
impl TimerQueueItem {
|
||||||
pub const fn new() -> Self {
|
pub(crate) const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
next: SyncUnsafeCell::new(None),
|
next: Cell::new(None),
|
||||||
}
|
expires_at: Cell::new(0),
|
||||||
}
|
#[cfg(feature = "_timer-item-payload")]
|
||||||
}
|
payload: OpaqueData::new(),
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
412
embassy-executor/src/raw/trace.rs
Normal file
412
embassy-executor/src/raw/trace.rs
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
//! # Tracing
|
||||||
|
//!
|
||||||
|
//! The `trace` feature enables a number of callbacks that can be used to track the
|
||||||
|
//! lifecycle of tasks and/or executors.
|
||||||
|
//!
|
||||||
|
//! Callbacks will have one or both of the following IDs passed to them:
|
||||||
|
//!
|
||||||
|
//! 1. A `task_id`, a `u32` value unique to a task for the duration of the time it is valid
|
||||||
|
//! 2. An `executor_id`, a `u32` value unique to an executor for the duration of the time it is
|
||||||
|
//! valid
|
||||||
|
//!
|
||||||
|
//! Today, both `task_id` and `executor_id` are u32s containing the least significant 32 bits of
|
||||||
|
//! the address of the task or executor, however this is NOT a stable guarantee, and MAY change
|
||||||
|
//! at any time.
|
||||||
|
//!
|
||||||
|
//! IDs are only guaranteed to be unique for the duration of time the item is valid. If a task
|
||||||
|
//! ends, and is re-spawned, it MAY or MAY NOT have the same ID. For tasks, this valid time is defined
|
||||||
|
//! as the time between `_embassy_trace_task_new` and `_embassy_trace_task_end` for a given task.
|
||||||
|
//! For executors, this time is not defined, but is often "forever" for practical embedded
|
||||||
|
//! programs.
|
||||||
|
//!
|
||||||
|
//! Callbacks can be used by enabling the `trace` feature, and providing implementations of the
|
||||||
|
//! `extern "Rust"` functions below. All callbacks must be implemented.
|
||||||
|
//!
|
||||||
|
//! ## Task Tracing lifecycle
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
//! │(1) │
|
||||||
|
//! │ │
|
||||||
|
//! ╔════▼════╗ (2) ┌─────────┐ (3) ┌─────────┐ │
|
||||||
|
//! │ ║ SPAWNED ║────▶│ WAITING │────▶│ RUNNING │
|
||||||
|
//! ╚═════════╝ └─────────┘ └─────────┘ │
|
||||||
|
//! │ ▲ ▲ │ │ │
|
||||||
|
//! │ (4) │ │(6) │
|
||||||
|
//! │ │(7) └ ─ ─ ┘ │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ ┌──────┐ (5) │ │ ┌─────┐
|
||||||
|
//! │ IDLE │◀────────────────┘ └─▶│ END │ │
|
||||||
|
//! │ └──────┘ └─────┘
|
||||||
|
//! ┌──────────────────────┐ │
|
||||||
|
//! └ ┤ Task Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
//! └──────────────────────┘
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! 1. A task is spawned, `_embassy_trace_task_new` is called
|
||||||
|
//! 2. A task is enqueued for the first time, `_embassy_trace_task_ready_begin` is called
|
||||||
|
//! 3. A task is polled, `_embassy_trace_task_exec_begin` is called
|
||||||
|
//! 4. WHILE a task is polled, the task is re-awoken, and `_embassy_trace_task_ready_begin` is
|
||||||
|
//! called. The task does not IMMEDIATELY move state, until polling is complete and the
|
||||||
|
//! RUNNING state is existed. `_embassy_trace_task_exec_end` is called when polling is
|
||||||
|
//! complete, marking the transition to WAITING
|
||||||
|
//! 5. Polling is complete, `_embassy_trace_task_exec_end` is called
|
||||||
|
//! 6. The task has completed, and `_embassy_trace_task_end` is called
|
||||||
|
//! 7. A task is awoken, `_embassy_trace_task_ready_begin` is called
|
||||||
|
//!
|
||||||
|
//! ## Executor Tracing lifecycle
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
//! │(1) │
|
||||||
|
//! │ │
|
||||||
|
//! ╔═══▼══╗ (2) ┌────────────┐ (3) ┌─────────┐ │
|
||||||
|
//! │ ║ IDLE ║──────────▶│ SCHEDULING │──────▶│ POLLING │
|
||||||
|
//! ╚══════╝ └────────────┘ └─────────┘ │
|
||||||
|
//! │ ▲ │ ▲ │
|
||||||
|
//! │ (5) │ │ (4) │ │
|
||||||
|
//! │ └──────────────┘ └────────────┘
|
||||||
|
//! ┌──────────────────────────┐ │
|
||||||
|
//! └ ┤ Executor Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
//! └──────────────────────────┘
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! 1. The executor is started (no associated trace)
|
||||||
|
//! 2. A task on this executor is awoken. `_embassy_trace_task_ready_begin` is called
|
||||||
|
//! when this occurs, and `_embassy_trace_poll_start` is called when the executor
|
||||||
|
//! actually begins running
|
||||||
|
//! 3. The executor has decided a task to poll. `_embassy_trace_task_exec_begin` is called
|
||||||
|
//! 4. The executor finishes polling the task. `_embassy_trace_task_exec_end` is called
|
||||||
|
//! 5. The executor has finished polling tasks. `_embassy_trace_executor_idle` is called
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use rtos_trace::TaskInfo;
|
||||||
|
|
||||||
|
use crate::raw::{SyncExecutor, TaskHeader, TaskRef};
|
||||||
|
use crate::spawner::{SpawnError, SpawnToken, Spawner};
|
||||||
|
|
||||||
|
/// Global task tracker instance
|
||||||
|
///
|
||||||
|
/// This static provides access to the global task tracker which maintains
|
||||||
|
/// a list of all tasks in the system. It's automatically updated by the
|
||||||
|
/// task lifecycle hooks in the trace module.
|
||||||
|
pub static TASK_TRACKER: TaskTracker = TaskTracker::new();
|
||||||
|
|
||||||
|
/// A thread-safe tracker for all tasks in the system
|
||||||
|
///
|
||||||
|
/// This struct uses an intrusive linked list approach to track all tasks
|
||||||
|
/// without additional memory allocations. It maintains a global list of
|
||||||
|
/// tasks that can be traversed to find all currently existing tasks.
|
||||||
|
pub struct TaskTracker {
|
||||||
|
head: AtomicPtr<TaskHeader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskTracker {
|
||||||
|
/// Creates a new empty task tracker
|
||||||
|
///
|
||||||
|
/// Initializes a tracker with no tasks in its list.
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
head: AtomicPtr::new(core::ptr::null_mut()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a task to the tracker
|
||||||
|
///
|
||||||
|
/// This method inserts a task at the head of the intrusive linked list.
|
||||||
|
/// The operation is thread-safe and lock-free, using atomic operations
|
||||||
|
/// to ensure consistency even when called from different contexts.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `task` - The task reference to add to the tracker
|
||||||
|
pub fn add(&self, task: TaskRef) {
|
||||||
|
let task_ptr = task.as_ptr() as *mut TaskHeader;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let current_head = self.head.load(Ordering::Acquire);
|
||||||
|
unsafe {
|
||||||
|
(*task_ptr).all_tasks_next.store(current_head, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.head
|
||||||
|
.compare_exchange(current_head, task_ptr, Ordering::Release, Ordering::Relaxed)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an operation on each task in the tracker
|
||||||
|
///
|
||||||
|
/// This method traverses the entire list of tasks and calls the provided
|
||||||
|
/// function for each task. This allows inspecting or processing all tasks
|
||||||
|
/// in the system without modifying the tracker's structure.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `f` - A function to call for each task in the tracker
|
||||||
|
pub fn for_each<F>(&self, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(TaskRef),
|
||||||
|
{
|
||||||
|
let mut current = self.head.load(Ordering::Acquire);
|
||||||
|
while !current.is_null() {
|
||||||
|
let task = unsafe { TaskRef::from_ptr(current) };
|
||||||
|
f(task);
|
||||||
|
|
||||||
|
current = unsafe { (*current).all_tasks_next.load(Ordering::Acquire) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait for `TaskRef` that provides tracing functionality.
|
||||||
|
///
|
||||||
|
/// This trait is only available when the `trace` feature is enabled.
|
||||||
|
/// It extends `TaskRef` with methods for accessing and modifying task identifiers
|
||||||
|
/// and names, which are useful for debugging, logging, and performance analysis.
|
||||||
|
pub trait TaskRefTrace {
|
||||||
|
/// Get the name for a task
|
||||||
|
fn name(&self) -> Option<&'static str>;
|
||||||
|
|
||||||
|
/// Set the name for a task
|
||||||
|
fn set_name(&self, name: Option<&'static str>);
|
||||||
|
|
||||||
|
/// Get the ID for a task
|
||||||
|
fn id(&self) -> u32;
|
||||||
|
|
||||||
|
/// Set the ID for a task
|
||||||
|
fn set_id(&self, id: u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskRefTrace for TaskRef {
|
||||||
|
fn name(&self) -> Option<&'static str> {
|
||||||
|
self.header().name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_name(&self, name: Option<&'static str>) {
|
||||||
|
unsafe {
|
||||||
|
let header_ptr = self.ptr.as_ptr() as *mut TaskHeader;
|
||||||
|
(*header_ptr).name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> u32 {
|
||||||
|
self.header().id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&self, id: u32) {
|
||||||
|
unsafe {
|
||||||
|
let header_ptr = self.ptr.as_ptr() as *mut TaskHeader;
|
||||||
|
(*header_ptr).id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
extern "Rust" {
|
||||||
|
/// This callback is called when the executor begins polling. This will always
|
||||||
|
/// be paired with a later call to `_embassy_trace_executor_idle`.
|
||||||
|
///
|
||||||
|
/// This marks the EXECUTOR state transition from IDLE -> SCHEDULING.
|
||||||
|
fn _embassy_trace_poll_start(executor_id: u32);
|
||||||
|
|
||||||
|
/// This callback is called AFTER a task is initialized/allocated, and BEFORE
|
||||||
|
/// it is enqueued to run for the first time. If the task ends (and does not
|
||||||
|
/// loop "forever"), there will be a matching call to `_embassy_trace_task_end`.
|
||||||
|
///
|
||||||
|
/// Tasks start life in the SPAWNED state.
|
||||||
|
fn _embassy_trace_task_new(executor_id: u32, task_id: u32);
|
||||||
|
|
||||||
|
/// This callback is called AFTER a task is destructed/freed. This will always
|
||||||
|
/// have a prior matching call to `_embassy_trace_task_new`.
|
||||||
|
fn _embassy_trace_task_end(executor_id: u32, task_id: u32);
|
||||||
|
|
||||||
|
/// This callback is called AFTER a task has been dequeued from the runqueue,
|
||||||
|
/// and BEFORE the task is polled. There will always be a matching call to
|
||||||
|
/// `_embassy_trace_task_exec_end`.
|
||||||
|
///
|
||||||
|
/// This marks the TASK state transition from WAITING -> RUNNING
|
||||||
|
/// This marks the EXECUTOR state transition from SCHEDULING -> POLLING
|
||||||
|
fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32);
|
||||||
|
|
||||||
|
/// This callback is called AFTER a task has completed polling. There will
|
||||||
|
/// always be a matching call to `_embassy_trace_task_exec_begin`.
|
||||||
|
///
|
||||||
|
/// This marks the TASK state transition from either:
|
||||||
|
/// * RUNNING -> IDLE - if there were no `_embassy_trace_task_ready_begin` events
|
||||||
|
/// for this task since the last `_embassy_trace_task_exec_begin` for THIS task
|
||||||
|
/// * RUNNING -> WAITING - if there WAS a `_embassy_trace_task_ready_begin` event
|
||||||
|
/// for this task since the last `_embassy_trace_task_exec_begin` for THIS task
|
||||||
|
///
|
||||||
|
/// This marks the EXECUTOR state transition from POLLING -> SCHEDULING
|
||||||
|
fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32);
|
||||||
|
|
||||||
|
/// This callback is called AFTER the waker for a task is awoken, and BEFORE it
|
||||||
|
/// is added to the run queue.
|
||||||
|
///
|
||||||
|
/// If the given task is currently RUNNING, this marks no state change, BUT the
|
||||||
|
/// RUNNING task will then move to the WAITING stage when polling is complete.
|
||||||
|
///
|
||||||
|
/// If the given task is currently IDLE, this marks the TASK state transition
|
||||||
|
/// from IDLE -> WAITING.
|
||||||
|
///
|
||||||
|
/// NOTE: This may be called from an interrupt, outside the context of the current
|
||||||
|
/// task or executor.
|
||||||
|
fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32);
|
||||||
|
|
||||||
|
/// This callback is called AFTER all dequeued tasks in a single call to poll
|
||||||
|
/// have been processed. This will always be paired with a call to
|
||||||
|
/// `_embassy_trace_executor_idle`.
|
||||||
|
///
|
||||||
|
/// This marks the EXECUTOR state transition from SCHEDULING -> IDLE
|
||||||
|
fn _embassy_trace_executor_idle(executor_id: u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn poll_start(executor: &SyncExecutor) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_poll_start(executor as *const _ as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
rtos_trace::trace::task_new(task.as_ptr() as u32);
|
||||||
|
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
TASK_TRACKER.add(*task);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_task_end(executor as u32, task.as_ptr() as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
rtos_trace::trace::task_ready_begin(task.as_ptr() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
rtos_trace::trace::task_exec_begin(task.as_ptr() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
rtos_trace::trace::task_exec_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn executor_idle(executor: &SyncExecutor) {
|
||||||
|
#[cfg(not(feature = "rtos-trace"))]
|
||||||
|
unsafe {
|
||||||
|
_embassy_trace_executor_idle(executor as *const _ as u32)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
rtos_trace::trace::system_idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all active tasks in the system
|
||||||
|
///
|
||||||
|
/// This function provides a convenient way to iterate over all tasks
|
||||||
|
/// that are currently tracked in the system. The returned iterator
|
||||||
|
/// yields each task in the global task tracker.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An iterator that yields `TaskRef` items for each task
|
||||||
|
fn get_all_active_tasks() -> impl Iterator<Item = TaskRef> + 'static {
|
||||||
|
struct TaskIterator<'a> {
|
||||||
|
tracker: &'a TaskTracker,
|
||||||
|
current: *mut TaskHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for TaskIterator<'a> {
|
||||||
|
type Item = TaskRef;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.current.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = unsafe { TaskRef::from_ptr(self.current) };
|
||||||
|
self.current = unsafe { (*self.current).all_tasks_next.load(Ordering::Acquire) };
|
||||||
|
|
||||||
|
Some(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskIterator {
|
||||||
|
tracker: &TASK_TRACKER,
|
||||||
|
current: TASK_TRACKER.head.load(Ordering::Acquire),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform an action on each active task
|
||||||
|
fn with_all_active_tasks<F>(f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(TaskRef),
|
||||||
|
{
|
||||||
|
TASK_TRACKER.for_each(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rtos-trace")]
|
||||||
|
impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor {
|
||||||
|
fn task_list() {
|
||||||
|
with_all_active_tasks(|task| {
|
||||||
|
let name = task.name().unwrap_or("unnamed task\0");
|
||||||
|
let info = rtos_trace::TaskInfo {
|
||||||
|
name,
|
||||||
|
priority: 0,
|
||||||
|
stack_base: 0,
|
||||||
|
stack_size: 0,
|
||||||
|
};
|
||||||
|
rtos_trace::trace::task_send_info(task.id(), info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn time() -> u64 {
|
||||||
|
const fn gcd(a: u64, b: u64) -> u64 {
|
||||||
|
if b == 0 {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
gcd(b, a % b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(feature = "rtos-trace")]
|
||||||
|
rtos_trace::global_os_callbacks! {SyncExecutor}
|
||||||
@ -26,38 +26,17 @@ pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
|
|||||||
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
|
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
|
||||||
/// avoid dynamic dispatch.
|
/// avoid dynamic dispatch.
|
||||||
///
|
///
|
||||||
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
|
/// You can use the returned task pointer to wake the task with [`wake_task`].
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the waker is not created by the Embassy executor.
|
/// Panics if the waker is not created by the Embassy executor.
|
||||||
pub fn task_from_waker(waker: &Waker) -> TaskRef {
|
pub fn task_from_waker(waker: &Waker) -> TaskRef {
|
||||||
let (vtable, data) = {
|
// make sure to compare vtable addresses. Doing `==` on the references
|
||||||
#[cfg(not(feature = "nightly"))]
|
// will compare the contents, which is slower.
|
||||||
{
|
if waker.vtable() as *const _ != &VTABLE as *const _ {
|
||||||
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) };
|
|
||||||
(hack.vtable, hack.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
{
|
|
||||||
let raw_waker = waker.as_raw();
|
|
||||||
(raw_waker.vtable(), raw_waker.data())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if vtable != &VTABLE {
|
|
||||||
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
|
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`
|
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||||
unsafe { TaskRef::from_ptr(data as *const TaskHeader) }
|
unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
use core::future::poll_fn;
|
use core::future::{poll_fn, Future};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use core::mem;
|
use core::mem;
|
||||||
|
use core::sync::atomic::Ordering;
|
||||||
use core::task::Poll;
|
use core::task::Poll;
|
||||||
|
|
||||||
use super::raw;
|
use super::raw;
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
use crate::raw::trace::TaskRefTrace;
|
||||||
|
|
||||||
/// Token to spawn a newly-created task in an executor.
|
/// Token to spawn a newly-created task in an executor.
|
||||||
///
|
///
|
||||||
@ -21,7 +24,7 @@ use super::raw;
|
|||||||
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
|
/// 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()"]
|
#[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> {
|
pub struct SpawnToken<S> {
|
||||||
raw_task: Option<raw::TaskRef>,
|
pub(crate) raw_task: Option<raw::TaskRef>,
|
||||||
phantom: PhantomData<*mut S>,
|
phantom: PhantomData<*mut S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +36,15 @@ impl<S> SpawnToken<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the task id if available, otherwise 0
|
||||||
|
/// This can be used in combination with rtos-trace to match task names with id's
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
match self.raw_task {
|
||||||
|
None => 0,
|
||||||
|
Some(t) => t.as_ptr() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a SpawnToken that represents a failed spawn.
|
/// Return a SpawnToken that represents a failed spawn.
|
||||||
pub fn new_failed() -> Self {
|
pub fn new_failed() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -50,8 +62,7 @@ impl<S> Drop for SpawnToken<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Error returned when spawning a task.
|
/// Error returned when spawning a task.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
||||||
pub enum SpawnError {
|
pub enum SpawnError {
|
||||||
/// Too many instances of this task are already running.
|
/// Too many instances of this task are already running.
|
||||||
///
|
///
|
||||||
@ -61,6 +72,31 @@ pub enum SpawnError {
|
|||||||
Busy,
|
Busy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for SpawnError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
core::fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for SpawnError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SpawnError::Busy => write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
impl defmt::Format for SpawnError {
|
||||||
|
fn format(&self, f: defmt::Formatter) {
|
||||||
|
match self {
|
||||||
|
SpawnError::Busy => defmt::write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::error::Error for SpawnError {}
|
||||||
|
|
||||||
/// Handle to spawn tasks into an executor.
|
/// Handle to spawn tasks into an executor.
|
||||||
///
|
///
|
||||||
/// This Spawner can spawn any task (Send and non-Send ones), but it can
|
/// This Spawner can spawn any task (Send and non-Send ones), but it can
|
||||||
@ -69,7 +105,7 @@ pub enum SpawnError {
|
|||||||
/// If you want to spawn tasks from another thread, use [SendSpawner].
|
/// If you want to spawn tasks from another thread, use [SendSpawner].
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Spawner {
|
pub struct Spawner {
|
||||||
executor: &'static raw::Executor,
|
pub(crate) executor: &'static raw::Executor,
|
||||||
not_send: PhantomData<*mut ()>,
|
not_send: PhantomData<*mut ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,14 +125,19 @@ impl Spawner {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the current executor is not an Embassy executor.
|
/// Panics if the current executor is not an Embassy executor.
|
||||||
pub async fn for_current_executor() -> Self {
|
pub fn for_current_executor() -> impl Future<Output = Self> {
|
||||||
poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
let task = raw::task_from_waker(cx.waker());
|
let task = raw::task_from_waker(cx.waker());
|
||||||
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
|
let executor = unsafe {
|
||||||
|
task.header()
|
||||||
|
.executor
|
||||||
|
.load(Ordering::Relaxed)
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
};
|
||||||
let executor = unsafe { raw::Executor::wrap(executor) };
|
let executor = unsafe { raw::Executor::wrap(executor) };
|
||||||
Poll::Ready(Self::new(executor))
|
Poll::Ready(Self::new(executor))
|
||||||
})
|
})
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a task into an executor.
|
/// Spawn a task into an executor.
|
||||||
@ -134,6 +175,58 @@ impl Spawner {
|
|||||||
pub fn make_send(&self) -> SendSpawner {
|
pub fn make_send(&self) -> SendSpawner {
|
||||||
SendSpawner::new(&self.executor.inner)
|
SendSpawner::new(&self.executor.inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the unique ID of this Spawner's Executor.
|
||||||
|
pub fn executor_id(&self) -> usize {
|
||||||
|
self.executor.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait adding tracing capabilities to the Spawner
|
||||||
|
///
|
||||||
|
/// This trait provides an additional method to spawn tasks with an associated name,
|
||||||
|
/// which can be useful for debugging and tracing purposes.
|
||||||
|
pub trait SpawnerTraceExt {
|
||||||
|
/// Spawns a new task with a specified name.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `name` - Static string name to associate with the task
|
||||||
|
/// * `token` - Token representing the task to spawn
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Result indicating whether the spawn was successful
|
||||||
|
fn spawn_named<S>(&self, name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of the SpawnerTraceExt trait for Spawner when trace is enabled
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
impl SpawnerTraceExt for Spawner {
|
||||||
|
fn spawn_named<S>(&self, name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError> {
|
||||||
|
let task = token.raw_task;
|
||||||
|
core::mem::forget(token);
|
||||||
|
|
||||||
|
match task {
|
||||||
|
Some(task) => {
|
||||||
|
// Set the name and ID when trace is enabled
|
||||||
|
task.set_name(Some(name));
|
||||||
|
let task_id = task.as_ptr() as u32;
|
||||||
|
task.set_id(task_id);
|
||||||
|
|
||||||
|
unsafe { self.executor.spawn(task) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(SpawnError::Busy),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of the SpawnerTraceExt trait for Spawner when trace is disabled
|
||||||
|
#[cfg(not(feature = "trace"))]
|
||||||
|
impl SpawnerTraceExt for Spawner {
|
||||||
|
fn spawn_named<S>(&self, _name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError> {
|
||||||
|
// When trace is disabled, just forward to regular spawn and ignore the name
|
||||||
|
self.spawn(token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle to spawn tasks into an executor from any thread.
|
/// Handle to spawn tasks into an executor from any thread.
|
||||||
@ -161,13 +254,18 @@ impl SendSpawner {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the current executor is not an Embassy executor.
|
/// Panics if the current executor is not an Embassy executor.
|
||||||
pub async fn for_current_executor() -> Self {
|
pub fn for_current_executor() -> impl Future<Output = Self> {
|
||||||
poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
let task = raw::task_from_waker(cx.waker());
|
let task = raw::task_from_waker(cx.waker());
|
||||||
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
|
let executor = unsafe {
|
||||||
|
task.header()
|
||||||
|
.executor
|
||||||
|
.load(Ordering::Relaxed)
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_unchecked()
|
||||||
|
};
|
||||||
Poll::Ready(Self::new(executor))
|
Poll::Ready(Self::new(executor))
|
||||||
})
|
})
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a task into an executor.
|
/// Spawn a task into an executor.
|
||||||
|
|||||||
@ -40,6 +40,7 @@ fn setup() -> (&'static Executor, Trace) {
|
|||||||
let trace = Trace::new();
|
let trace = Trace::new();
|
||||||
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
|
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
|
||||||
let executor = &*Box::leak(Box::new(Executor::new(context)));
|
let executor = &*Box::leak(Box::new(Executor::new(context)));
|
||||||
|
|
||||||
(executor, trace)
|
(executor, trace)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +137,139 @@ fn executor_task_self_wake_twice() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn waking_after_completion_does_not_poll() {
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
async fn task1(trace: Trace, waker: &'static AtomicWaker) {
|
||||||
|
poll_fn(|cx| {
|
||||||
|
trace.push("poll task1");
|
||||||
|
waker.register(cx.waker());
|
||||||
|
Poll::Ready(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
let waker = Box::leak(Box::new(AtomicWaker::new()));
|
||||||
|
|
||||||
|
let (executor, trace) = setup();
|
||||||
|
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
|
||||||
|
|
||||||
|
unsafe { executor.poll() };
|
||||||
|
waker.wake();
|
||||||
|
unsafe { executor.poll() };
|
||||||
|
|
||||||
|
// Exited task may be waken but is not polled
|
||||||
|
waker.wake();
|
||||||
|
waker.wake();
|
||||||
|
unsafe { executor.poll() }; // Clears running status
|
||||||
|
|
||||||
|
// Can respawn waken-but-dead task
|
||||||
|
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
|
||||||
|
|
||||||
|
unsafe { executor.poll() };
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
trace.get(),
|
||||||
|
&[
|
||||||
|
"pend", // spawning a task pends the executor
|
||||||
|
"poll task1", //
|
||||||
|
"pend", // manual wake, gets cleared by poll
|
||||||
|
"pend", // manual wake, single pend for two wakes
|
||||||
|
"pend", // respawning a task pends the executor
|
||||||
|
"poll task1", //
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn waking_with_old_waker_after_respawn() {
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
|
||||||
|
async fn yield_now(trace: Trace) {
|
||||||
|
let mut yielded = false;
|
||||||
|
poll_fn(|cx| {
|
||||||
|
if yielded {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
trace.push("yield_now");
|
||||||
|
yielded = true;
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
async fn task1(trace: Trace, waker: &'static AtomicWaker) {
|
||||||
|
yield_now(trace.clone()).await;
|
||||||
|
poll_fn(|cx| {
|
||||||
|
trace.push("poll task1");
|
||||||
|
waker.register(cx.waker());
|
||||||
|
Poll::Ready(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let waker = Box::leak(Box::new(AtomicWaker::new()));
|
||||||
|
|
||||||
|
let (executor, trace) = setup();
|
||||||
|
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
|
||||||
|
|
||||||
|
unsafe { executor.poll() };
|
||||||
|
unsafe { executor.poll() }; // progress to registering the waker
|
||||||
|
waker.wake();
|
||||||
|
unsafe { executor.poll() };
|
||||||
|
// Task has exited
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
trace.get(),
|
||||||
|
&[
|
||||||
|
"pend", // spawning a task pends the executor
|
||||||
|
"yield_now", //
|
||||||
|
"pend", // yield_now wakes the task
|
||||||
|
"poll task1", //
|
||||||
|
"pend", // task self-wakes
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Can respawn task on another executor
|
||||||
|
let (other_executor, other_trace) = setup();
|
||||||
|
other_executor
|
||||||
|
.spawner()
|
||||||
|
.spawn(task1(other_trace.clone(), waker))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
unsafe { other_executor.poll() }; // just run to the yield_now
|
||||||
|
waker.wake(); // trigger old waker registration
|
||||||
|
unsafe { executor.poll() };
|
||||||
|
unsafe { other_executor.poll() };
|
||||||
|
|
||||||
|
// First executor's trace has not changed
|
||||||
|
assert_eq!(
|
||||||
|
trace.get(),
|
||||||
|
&[
|
||||||
|
"pend", // spawning a task pends the executor
|
||||||
|
"yield_now", //
|
||||||
|
"pend", // yield_now wakes the task
|
||||||
|
"poll task1", //
|
||||||
|
"pend", // task self-wakes
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
other_trace.get(),
|
||||||
|
&[
|
||||||
|
"pend", // spawning a task pends the executor
|
||||||
|
"yield_now", //
|
||||||
|
"pend", // manual wake, gets cleared by poll
|
||||||
|
"poll task1", //
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn executor_task_cfg_args() {
|
fn executor_task_cfg_args() {
|
||||||
// simulate cfg'ing away argument c
|
// simulate cfg'ing away argument c
|
||||||
|
|||||||
24
embassy-executor/tests/ui.rs
Normal file
24
embassy-executor/tests/ui.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#[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/type_error.rs");
|
||||||
|
t.compile_fail("tests/ui/where_clause.rs");
|
||||||
|
}
|
||||||
8
embassy-executor/tests/ui/abi.rs
Normal file
8
embassy-executor/tests/ui/abi.rs
Normal file
@ -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
embassy-executor/tests/ui/abi.stderr
Normal file
5
embassy-executor/tests/ui/abi.stderr
Normal file
@ -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
embassy-executor/tests/ui/bad_return.rs
Normal file
10
embassy-executor/tests/ui/bad_return.rs
Normal file
@ -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() {}
|
||||||
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
@ -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 {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
8
embassy-executor/tests/ui/generics.rs
Normal file
8
embassy-executor/tests/ui/generics.rs
Normal file
@ -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() {}
|
||||||
5
embassy-executor/tests/ui/generics.stderr
Normal file
5
embassy-executor/tests/ui/generics.stderr
Normal file
@ -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) {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
@ -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() {}
|
||||||
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
@ -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) {}
|
||||||
|
| ^^^^^^^^^^
|
||||||
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
@ -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() {}
|
||||||
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
@ -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>) {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
@ -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() {}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user