Merge branch 'embassy-rs:main' into u5_adc

This commit is contained in:
Olof 2024-12-18 01:48:25 +01:00 committed by GitHub
commit 7cf96e4730
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
518 changed files with 23345 additions and 11146 deletions

37
.github/ci/build-xtensa.sh vendored Executable file
View 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
# 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 '{}' \+

View File

@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
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 --features nightly
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml

3
.github/ci/test.sh vendored
View File

@ -12,11 +12,12 @@ export CARGO_TARGET_DIR=/ci/cache/target
# 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-sync/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-time/Cargo.toml --features generic-queue,mock-driver
cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver,embassy-time-queue-driver/generic-queue-8
cargo test --manifest-path ./embassy-time-driver/Cargo.toml
cargo test --manifest-path ./embassy-boot/Cargo.toml

3
.gitignore vendored
View File

@ -5,4 +5,7 @@ Cargo.lock
third_party
/Cargo.toml
out/
# editor artifacts
.zed
.neoconf.json
*.vim

View File

@ -13,20 +13,13 @@ 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,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,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,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,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,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,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,integrated-timers \
--- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \
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
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
View 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-driver/Cargo.toml --target xtensa-esp32s2-none-elf \
--- build --release --manifest-path embassy-time-queue-driver/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 \

28
ci.sh
View File

@ -29,23 +29,18 @@ 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 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,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,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,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,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,integrated-timers \
--- 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,integrated-timers \
--- 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-driver/Cargo.toml --target thumbv6m-none-eabi \
--- build --release --manifest-path embassy-time-queue-driver/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,multicast,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
@ -70,6 +65,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-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 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-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time,time-driver-rtc1 \
@ -166,11 +163,13 @@ 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 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 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 thumbv7em-none-eabi --features stm32wb35ce,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 stm32u083rc,defmt,exti,time-driver-any,time \
--- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf \
--- 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 'defmt' \
@ -195,6 +194,7 @@ cargo batch \
--- build --release --manifest-path examples/nrf52810/Cargo.toml --target thumbv7em-none-eabi --out-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/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \
--- build --release --manifest-path examples/nrf54l15/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf54l15 \
--- build --release --manifest-path examples/nrf9160/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9160 \
--- 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/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/ns \
@ -214,6 +214,7 @@ cargo batch \
--- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
--- build --release --manifest-path examples/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7b0 \
--- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \
--- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \
--- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \
@ -227,6 +228,7 @@ cargo batch \
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wb \
--- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wl \
--- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/lpc55s69 \
--- 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/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/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \
@ -300,14 +302,12 @@ rm out/tests/stm32wb55rg/wpan_ble
# unstable, I think it's running out of RAM?
rm out/tests/stm32f207zg/eth
# temporarily disabled, hard faults for unknown reasons
rm out/tests/stm32f207zg/usart_rx_ringbuffered
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
rm out/tests/stm32u5a5zj/usart
# flaky, probably due to bad ringbuffered dma code.
rm out/tests/stm32l152re/usart_rx_ringbuffered
rm out/tests/stm32f207zg/usart_rx_ringbuffered
rm out/tests/stm32wl55jc/usart_rx_ringbuffered
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
echo No teleprobe token found, skipping running HIL tests
exit

View File

@ -19,7 +19,7 @@ firmware-logs = []
[dependencies]
embassy-time = { version = "0.3.2", path = "../embassy-time"}
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
embassy-sync = { version = "0.6.1", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}

View File

@ -9,7 +9,8 @@
pub(crate) mod fmt;
#[cfg(feature = "bluetooth")]
mod bluetooth;
/// Bluetooth module.
pub mod bluetooth;
mod bus;
mod consts;
mod control;

View File

@ -6,7 +6,7 @@ version = "0.1.0"
license = "MIT OR Apache-2.0"
[dependencies]
embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] }
embassy-executor = { version = "0.6.3", path = "../../../embassy-executor", features = ["defmt", "arch-cortex-m", "executor-thread"] }
embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] }
embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }

View File

@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
cortex-m = "0.7"
cortex-m-rt = "0.7"
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] }
embassy-executor = { version = "0.6.0", features = ["arch-cortex-m", "executor-thread"] }
embassy-executor = { version = "0.6.3", features = ["arch-cortex-m", "executor-thread"] }
defmt = "0.3.0"
defmt-rtt = "0.3.0"

View File

@ -2,6 +2,8 @@
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]

View File

@ -372,3 +372,62 @@ Issues like these while implementing drivers often fall into one of the followin
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.

View File

@ -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, well only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`.
At the time of writing, the latest version of embassy isnt 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`
* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32`
* 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 were 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, its necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crates `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, its necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crates `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]
----
[dependencies]
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"] }
[patch.crates-io]

View File

@ -52,7 +52,7 @@ link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot]
== 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.
@ -80,4 +80,4 @@ For more reading material on async Rust and Embassy:
Videos:
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]

View File

@ -24,7 +24,7 @@ target = "thumbv7em-none-eabi"
defmt = { version = "0.3", optional = true }
log = { version = "0.4.17", optional = true }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-nrf = { version = "0.2.0", path = "../embassy-nrf", default-features = false }
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
cortex-m = { version = "0.7.6" }

View File

@ -6,6 +6,25 @@ An adaptation of `embassy-boot` for nRF.
## Features
* Load applications with or without the softdevice.
* Configure bootloader partitions based on linker script.
* Using watchdog timer to detect application failure.
- Load applications with or without the softdevice.
- Configure bootloader partitions based on linker script.
- 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 applications 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.

View File

@ -24,7 +24,7 @@ features = ["embassy-rp/rp2040"]
defmt = { version = "0.3", optional = true }
log = { version = "0.4", optional = true }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-rp = { version = "0.2.0", path = "../embassy-rp", default-features = false }
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
embassy-time = { version = "0.3.2", path = "../embassy-time" }

View File

@ -24,7 +24,7 @@ target = "thumbv7em-none-eabi"
defmt = { version = "0.3", optional = true }
log = { version = "0.4", optional = true }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false }
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
cortex-m = { version = "0.7.6" }

View File

@ -29,7 +29,7 @@ digest = "0.10"
log = { version = "0.4", 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-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embedded-storage = "0.3.1"
embedded-storage-async = { version = "0.4.1" }
salty = { version = "0.3", optional = true }

View File

@ -304,7 +304,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
/// `mark_booted`.
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, &mut self.aligned).await?;
Ok(State::from(&self.aligned))
Ok(State::from(&self.aligned[..STATE::WRITE_SIZE]))
}
/// Mark to trigger firmware swap on next boot.

View File

@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
/// 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.
/// 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.
pub struct FirmwareUpdaterConfig<DFU, STATE> {
/// The dfu flash partition

View File

@ -28,7 +28,7 @@ default = ["time"]
[dependencies]
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
"unproven",

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-executor-macros"
version = "0.5.0"
version = "0.6.2"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"
@ -13,7 +13,7 @@ categories = [
]
[dependencies]
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
syn = { version = "2.0.15", features = ["full", "visit"] }
quote = "1.0.9"
darling = "0.20.1"
proc-macro2 = "1.0.29"

View File

@ -1,28 +1,11 @@
#![doc = include_str!("../README.md")]
extern crate proc_macro;
use darling::ast::NestedMeta;
use proc_macro::TokenStream;
mod macros;
mod util;
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
/// many concurrent tasks can be spawned (default is 1) for the function.
@ -56,17 +39,12 @@ impl Parse for Args {
/// ```
#[proc_macro_attribute]
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
task::run(&args.meta, f).unwrap_or_else(|x| x).into()
task::run(args.into(), item.into()).into()
}
#[proc_macro_attribute]
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::avr()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_AVR).into()
}
/// 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,32 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).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.
@ -124,11 +125,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
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()
main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
}
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
@ -151,9 +148,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_STD).into()
}
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
@ -176,7 +171,5 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(args as Args);
let f = syn::parse_macro_input!(item as syn::ItemFn);
main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into()
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
}

View File

@ -1,125 +1,107 @@
use std::str::FromStr;
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::TokenStream;
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,
}
pub static ARCH_AVR: Arch = Arch {
default_entry: Some("avr_device::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_RISCV: Arch = Arch {
default_entry: Some("riscv_rt::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_CORTEX_M: Arch = Arch {
default_entry: Some("cortex_m_rt::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_SPIN: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
};
pub static ARCH_STD: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
};
pub static ARCH_WASM: Arch = Arch {
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
flavor: Flavor::Wasm,
};
#[derive(Debug, FromMeta, Default)]
struct Args {
#[darling(default)]
entry: Option<String>,
}
pub fn avr() -> TokenStream {
quote! {
#[avr_device::entry]
fn main() -> ! {
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
let mut errors = TokenStream::new();
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
}
}
}
pub fn riscv(args: &[NestedMeta]) -> TokenStream {
let maybe_entry = match Args::from_list(args) {
Ok(args) => args.entry,
Err(e) => return e.write_errors(),
// 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 entry = maybe_entry.unwrap_or("riscv_rt::entry".into());
let entry = match Expr::from_string(&entry) {
Ok(expr) => expr,
Err(e) => return e.write_errors(),
let args = match NestedMeta::parse_meta_list(args) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
quote! {
#[#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));
})
let args = match Args::from_list(&args) {
Ok(x) => x,
Err(e) => {
errors.extend(e.write_errors());
Args::default()
}
}
}
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 ctxt = Ctxt::new();
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() {
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() {
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() {
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() {
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 {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => ctxt.error_spanned_by(
_ => error(
&mut errors,
&f.sig,
"main function must either not return a value, return `()` or return `!`",
),
@ -127,26 +109,69 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result<Tok
}
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().or(arch.default_entry) {
None => TokenStream::new(),
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 f_body = f.body;
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 = ::embassy_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(::embassy_executor::Executor::new()));
executor.start(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
});
Ok(())
},
),
};
if !errors.is_empty() {
main_body = quote! {loop{}};
}
let result = quote! {
#[::embassy_executor::task()]
async fn __embassy_main(#fargs) #out {
#f_body
}
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
::core::mem::transmute(t)
#entry
fn main() -> #main_ret {
#main_body
}
#main
#errors
};
Ok(result)
result
}

View File

@ -1,48 +1,78 @@
use std::str::FromStr;
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::{Span, TokenStream};
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 {
#[darling(default)]
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> {
let args = Args::from_list(args).map_err(|e| e.write_errors())?;
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
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 {
attrs: vec![],
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() {
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() {
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() {
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() {
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() {
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 {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => ctxt.error_spanned_by(
_ => error(
&mut errors,
&f.sig,
"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() {
match arg {
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::Pat::Ident(id) => {
id.mutability = None;
args.push((id.clone(), t.attrs.clone()));
syn::FnArg::Typed(t) => {
check_arg_ty(&mut errors, &t.ty);
match t.pat.as_mut() {
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_inner_ident = format_ident!("__{}_task", task_ident);
let mut task_inner = f;
let mut task_inner = f.clone();
let visibility = task_inner.vis.clone();
task_inner.vis = syn::Visibility::Inherited;
task_inner.sig.ident = task_inner_ident.clone();
@ -91,35 +126,43 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
}
#[cfg(feature = "nightly")]
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
trait _EmbassyInternalTaskTrait {
type Fut: ::core::future::Future + 'static;
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,)*)) }
let mut task_outer_body = quote! {
trait _EmbassyInternalTaskTrait {
type Fut: ::core::future::Future + 'static;
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,)*)) }
};
#[cfg(not(feature = "nightly"))]
let mut task_outer: ItemFn = parse_quote! {
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
const POOL_SIZE: usize = #pool_size;
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
}
let mut task_outer_body = quote! {
const POOL_SIZE: usize = #pool_size;
static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new();
unsafe { POOL.get::<_, POOL_SIZE>()._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! {
// This is the user's task function, renamed.
@ -129,8 +172,49 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
#[doc(hidden)]
#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);
}

View 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());
});
}
}

View File

@ -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");
}
}
}

View File

@ -1 +0,0 @@
pub mod ctxt;

View File

@ -7,11 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
- embassy-executor no longer provides an `embassy-time-queue-driver` implementation
- Added `TaskRef::executor` to obtain a reference to a task's executor
- integrated-timers are no longer processed when polling the executor.
- Added the option to store data in timer queue items
## 0.6.3 - 2024-11-12
- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82.
- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m.
## 0.6.2 - 2024-11-06
- The `nightly` feature no longer requires `nightly-2024-09-06` or newer.
## 0.6.1 - 2024-10-21
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature.
- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer.
- Improve macro error messages.
## 0.6.0 - 2024-08-05
- Add collapse_debuginfo to fmt.rs macros.
- initial support for avr
- use nightly waker_getters APIs
- initial support for AVR
- use nightly waker_getters APIs
## 0.5.1 - 2024-10-21
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
## 0.5.0 - 2024-01-11

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-executor"
version = "0.6.0"
version = "0.6.3"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
@ -31,11 +31,10 @@ features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
[dependencies]
defmt = { version = "0.3", 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-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true }
critical-section = "1.1"
document-features = "0.2.7"
@ -55,7 +54,8 @@ avr-device = { version = "0.5.3", optional = true }
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
trybuild = "1.0"
embassy-sync = { path = "../embassy-sync" }
[features]
@ -67,9 +67,6 @@ nightly = ["embassy-executor-macros/nightly"]
# See: https://github.com/embassy-rs/embassy/pull/1263
turbowakers = []
## Use the executor-integrated `embassy-time` timer queue.
integrated-timers = ["dep:embassy-time-driver", "dep:embassy-time-queue-driver"]
#! ### Architecture
_arch = [] # some arch was picked
## std
@ -82,6 +79,8 @@ arch-riscv32 = ["_arch"]
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"]
## AVR
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
## spin (architecture agnostic; never sleeps)
arch-spin = ["_arch"]
#! ### Executor
@ -89,6 +88,26 @@ arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
executor-thread = []
## Enable the interrupt-mode executor (available in Cortex-M only)
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"]
#! ### Timer Item Payload Size
#! 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.
#! If these features are not defined, the timer item will contain no payload field.
_timer-item-payload = [] # A size was picked
## 1 bytes
timer-item-payload-size-1 = ["_timer-item-payload"]
## 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"]
#! ### Task Arena Size
#! Sets the [task arena](#task-arena) size. Necessary if youre not using `nightly`.

View File

@ -92,3 +92,35 @@ pub fn set_target_cfgs(cfgs: &mut CfgSet) {
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))
}
}

View 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() };
}
}
}
}

View File

@ -23,7 +23,14 @@ macro_rules! check_at_most_one {
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-riscv32",
"arch-std",
"arch-wasm",
"arch-spin",
);
#[cfg(feature = "_arch")]
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
@ -31,6 +38,7 @@ check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arc
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")]
mod arch;
#[cfg(feature = "_arch")]

View File

@ -16,8 +16,9 @@ mod run_queue;
#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")]
mod state;
#[cfg(feature = "integrated-timers")]
mod timer_queue;
pub mod timer_queue;
#[cfg(feature = "trace")]
mod trace;
pub(crate) mod util;
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
mod waker;
@ -27,13 +28,9 @@ use core::marker::PhantomData;
use core::mem;
use core::pin::Pin;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicPtr, Ordering};
use core::task::{Context, Poll};
#[cfg(feature = "integrated-timers")]
use embassy_time_driver::AlarmHandle;
#[cfg(feature = "rtos-trace")]
use rtos_trace::trace;
use self::run_queue::{RunQueue, RunQueueItem};
use self::state::State;
use self::util::{SyncUnsafeCell, UninitCell};
@ -41,20 +38,56 @@ pub use self::waker::task_from_waker;
use super::SpawnToken;
/// 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) state: State,
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)>>,
#[cfg(feature = "integrated-timers")]
pub(crate) expires_at: SyncUnsafeCell<u64>,
#[cfg(feature = "integrated-timers")]
/// Integrated timer queue storage. This field should not be accessed outside of the timer queue.
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
}
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq)]
pub struct TaskRef {
ptr: NonNull<TaskHeader>,
}
@ -76,10 +109,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 {
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.
pub(crate) fn as_ptr(self) -> *const TaskHeader {
self.ptr.as_ptr()
@ -107,6 +161,10 @@ pub struct TaskStorage<F: Future + 'static> {
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> {
const NEW: Self = Self::new();
@ -116,13 +174,10 @@ impl<F: Future + 'static> TaskStorage<F> {
raw: TaskHeader {
state: State::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`
poll_fn: SyncUnsafeCell::new(None),
#[cfg(feature = "integrated-timers")]
expires_at: SyncUnsafeCell::new(0),
#[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(),
},
future: UninitCell::uninit(),
@ -151,18 +206,24 @@ impl<F: Future + 'static> TaskStorage<F> {
}
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 waker = waker::from_task(p);
let mut cx = Context::from_waker(&waker);
match future.poll(&mut cx) {
Poll::Ready(_) => {
// 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.raw.state.despawn();
#[cfg(feature = "integrated-timers")]
this.raw.expires_at.set(u64::MAX);
// 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();
}
Poll::Pending => {}
}
@ -316,26 +377,13 @@ impl Pender {
pub(crate) struct SyncExecutor {
run_queue: RunQueue,
pender: Pender,
#[cfg(feature = "integrated-timers")]
pub(crate) timer_queue: timer_queue::TimerQueue,
#[cfg(feature = "integrated-timers")]
alarm: AlarmHandle,
}
impl SyncExecutor {
pub(crate) fn new(pender: Pender) -> Self {
#[cfg(feature = "integrated-timers")]
let alarm = unsafe { unwrap!(embassy_time_driver::allocate_alarm()) };
Self {
run_queue: RunQueue::new(),
pender,
#[cfg(feature = "integrated-timers")]
timer_queue: timer_queue::TimerQueue::new(),
#[cfg(feature = "integrated-timers")]
alarm,
}
}
@ -346,90 +394,47 @@ impl SyncExecutor {
/// - `task` must be set up to run in this executor.
/// - `task` must NOT be already enqueued (in this executor or another one).
#[inline(always)]
unsafe fn enqueue(&self, task: TaskRef) {
#[cfg(feature = "rtos-trace")]
trace::task_ready_begin(task.as_ptr() as u32);
unsafe fn enqueue(&self, task: TaskRef, l: state::Token) {
#[cfg(feature = "trace")]
trace::task_ready_begin(self, &task);
if self.run_queue.enqueue(task) {
if self.run_queue.enqueue(task, l) {
self.pender.pend();
}
}
#[cfg(feature = "integrated-timers")]
fn alarm_callback(ctx: *mut ()) {
let this: &Self = unsafe { &*(ctx as *const Self) };
this.pender.pend();
}
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
task.header().executor.set(Some(self));
task.header()
.executor
.store((self as *const Self).cast_mut(), Ordering::Relaxed);
#[cfg(feature = "rtos-trace")]
trace::task_new(task.as_ptr() as u32);
#[cfg(feature = "trace")]
trace::task_new(self, &task);
self.enqueue(task);
state::locked(|l| {
self.enqueue(task, l);
})
}
/// # Safety
///
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
pub(crate) unsafe fn poll(&'static self) {
#[cfg(feature = "integrated-timers")]
embassy_time_driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ());
self.run_queue.dequeue_all(|p| {
let task = p.header();
#[allow(clippy::never_loop)]
loop {
#[cfg(feature = "integrated-timers")]
self.timer_queue
.dequeue_expired(embassy_time_driver::now(), wake_task_no_pend);
#[cfg(feature = "trace")]
trace::task_exec_begin(self, &p);
self.run_queue.dequeue_all(|p| {
let task = p.header();
// Run the task
task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "integrated-timers")]
task.expires_at.set(u64::MAX);
#[cfg(feature = "trace")]
trace::task_exec_end(self, &p);
});
if !task.state.run_dequeue() {
// If task is not running, ignore it. This can happen in the following scenario:
// - Task gets dequeued, poll starts
// - While task is being polled, it gets woken. It gets placed in the queue.
// - Task poll finishes, returning done=true
// - RUNNING bit is cleared, but the task is already in the queue.
return;
}
#[cfg(feature = "rtos-trace")]
trace::task_exec_begin(p.as_ptr() as u32);
// Run the task
task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "rtos-trace")]
trace::task_exec_end();
// Enqueue or update into timer_queue
#[cfg(feature = "integrated-timers")]
self.timer_queue.update(p);
});
#[cfg(feature = "integrated-timers")]
{
// If this is already in the past, set_alarm might return false
// In that case do another poll loop iteration.
let next_expiration = self.timer_queue.next_expiration();
if embassy_time_driver::set_alarm(self.alarm, next_expiration) {
break;
}
}
#[cfg(not(feature = "integrated-timers"))]
{
break;
}
}
#[cfg(feature = "rtos-trace")]
trace::system_idle();
#[cfg(feature = "trace")]
trace::executor_idle(self)
}
}
@ -516,6 +521,8 @@ impl Executor {
///
/// # Safety
///
/// You must call `initialize` before calling this method.
///
/// You must NOT call `poll` reentrantly on the same executor.
///
/// In particular, note that `poll` may call the pender synchronously. Therefore, you
@ -540,13 +547,13 @@ impl Executor {
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task(task: TaskRef) {
let header = task.header();
if header.state.run_enqueue() {
header.state.run_enqueue(|l| {
// We have just marked the task as scheduled, so enqueue it.
unsafe {
let executor = header.executor.get().unwrap_unchecked();
executor.enqueue(task);
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
executor.enqueue(task, l);
}
}
});
}
/// Wake a task by `TaskRef` without calling pend.
@ -554,57 +561,11 @@ pub fn wake_task(task: TaskRef) {
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task_no_pend(task: TaskRef) {
let header = task.header();
if header.state.run_enqueue() {
header.state.run_enqueue(|l| {
// We have just marked the task as scheduled, so enqueue it.
unsafe {
let executor = header.executor.get().unwrap_unchecked();
executor.run_queue.enqueue(task);
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
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}

View File

@ -45,7 +45,7 @@ impl RunQueue {
///
/// `item` must NOT be already enqueued in any queue.
#[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;
self.head
@ -81,6 +81,7 @@ impl RunQueue {
// safety: there are no concurrent accesses to `next`
next = unsafe { task.header().run_queue_item.next.get() };
task.header().state.run_dequeue();
on_task(task);
}
}

View File

@ -44,13 +44,11 @@ impl RunQueue {
///
/// `item` must NOT be already enqueued in any queue.
#[inline(always)]
pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
critical_section::with(|cs| {
let prev = self.head.borrow(cs).replace(Some(task));
task.header().run_queue_item.next.borrow(cs).set(prev);
pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool {
let prev = self.head.borrow(cs).replace(Some(task));
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.
@ -65,9 +63,10 @@ impl RunQueue {
// If the task re-enqueues itself, the `next` pointer will get overwritten.
// Therefore, first read the next pointer, and only then process the task.
// safety: we know if the task is enqueued, no one else will touch the `next` pointer.
let cs = unsafe { CriticalSection::new() };
next = task.header().run_queue_item.next.borrow(cs).get();
critical_section::with(|cs| {
next = task.header().run_queue_item.next.borrow(cs).get();
task.header().state.run_dequeue(cs);
});
on_task(task);
}

View File

@ -1,12 +1,19 @@
use core::sync::atomic::{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(()))
}
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
/// Task is in the executor timer queue
#[cfg(feature = "integrated-timers")]
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
pub(crate) struct State {
state: AtomicU32,
@ -33,41 +40,19 @@ impl State {
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)]
pub fn run_enqueue(&self) -> bool {
self.state
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
// If already scheduled, or if not started,
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
None
} else {
// Mark it as scheduled
Some(state | STATE_RUN_QUEUED)
}
})
.is_ok()
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel);
if prev & STATE_RUN_QUEUED == 0 {
locked(f);
}
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) -> bool {
let state = self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
state & STATE_SPAWNED != 0
}
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_enqueue(&self) -> bool {
let old_state = self.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel);
old_state & STATE_TIMER_QUEUED == 0
}
/// Unmark the task as timer-queued.
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_dequeue(&self) {
self.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel);
pub fn run_dequeue(&self) {
self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
}
}

View File

@ -1,6 +1,15 @@
use core::arch::asm;
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`!
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
@ -11,9 +20,8 @@ pub(crate) struct State {
spawned: AtomicBool,
/// Task is in the executor run queue
run_queued: AtomicBool,
/// Task is in the executor timer queue
timer_queued: AtomicBool,
pad: AtomicBool,
pad2: AtomicBool,
}
impl State {
@ -21,8 +29,8 @@ impl State {
Self {
spawned: AtomicBool::new(false),
run_queued: AtomicBool::new(false),
timer_queued: AtomicBool::new(false),
pad: AtomicBool::new(false),
pad2: AtomicBool::new(false),
}
}
@ -54,50 +62,22 @@ impl State {
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)]
pub fn run_enqueue(&self) -> bool {
unsafe {
loop {
let state: u32;
asm!("ldrex {}, [{}]", out(reg) state, in(reg) self, options(nostack));
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
let old = self.run_queued.swap(true, Ordering::AcqRel);
if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
asm!("clrex", options(nomem, nostack));
return false;
}
let outcome: usize;
let new_state = state | STATE_RUN_QUEUED;
asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack));
if outcome == 0 {
return true;
}
}
if !old {
locked(f);
}
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) -> bool {
pub fn run_dequeue(&self) {
compiler_fence(Ordering::Release);
let r = self.spawned.load(Ordering::Relaxed);
self.run_queued.store(false, Ordering::Relaxed);
r
}
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_enqueue(&self) -> bool {
!self.timer_queued.swap(true, Ordering::Relaxed)
}
/// Unmark the task as timer-queued.
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_dequeue(&self) {
self.timer_queued.store(false, Ordering::Relaxed);
}
}

View File

@ -1,14 +1,12 @@
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)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
/// Task is in the executor timer queue
#[cfg(feature = "integrated-timers")]
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
pub(crate) struct State {
state: Mutex<Cell<u32>>,
@ -22,13 +20,15 @@ impl State {
}
fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
critical_section::with(|cs| {
let s = self.state.borrow(cs);
let mut val = s.get();
let r = f(&mut val);
s.set(val);
r
})
critical_section::with(|cs| self.update_with_cs(cs, f))
}
fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R {
let s = self.state.borrow(cs);
let mut val = s.get();
let r = f(&mut val);
s.set(val);
r
}
/// If task is idle, mark it as spawned + run_queued and return true.
@ -50,44 +50,24 @@ impl State {
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)]
pub fn run_enqueue(&self) -> bool {
self.update(|s| {
if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) {
false
} else {
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
critical_section::with(|cs| {
if self.update_with_cs(cs, |s| {
let ok = *s & STATE_RUN_QUEUED == 0;
*s |= STATE_RUN_QUEUED;
true
ok
}) {
f(cs);
}
})
});
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) -> bool {
self.update(|s| {
let ok = *s & STATE_SPAWNED != 0;
*s &= !STATE_RUN_QUEUED;
ok
})
}
/// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before)
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_enqueue(&self) -> bool {
self.update(|s| {
let ok = *s & STATE_TIMER_QUEUED == 0;
*s |= STATE_TIMER_QUEUED;
ok
})
}
/// Unmark the task as timer-queued.
#[cfg(feature = "integrated-timers")]
#[inline(always)]
pub fn timer_dequeue(&self) {
self.update(|s| *s &= !STATE_TIMER_QUEUED);
pub fn run_dequeue(&self, cs: CriticalSection<'_>) {
self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED)
}
}

View File

@ -1,76 +1,73 @@
use core::cmp::min;
//! Timer queue operations.
use core::cell::Cell;
use super::TaskRef;
use crate::raw::util::SyncUnsafeCell;
pub(crate) struct TimerQueueItem {
next: SyncUnsafeCell<Option<TaskRef>>,
#[cfg(feature = "_timer-item-payload")]
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 {
pub const fn new() -> Self {
pub(crate) const fn new() -> Self {
Self {
next: SyncUnsafeCell::new(None),
}
}
}
pub(crate) struct TimerQueue {
head: SyncUnsafeCell<Option<TaskRef>>,
}
impl TimerQueue {
pub const fn new() -> Self {
Self {
head: SyncUnsafeCell::new(None),
}
}
pub(crate) unsafe fn update(&self, p: TaskRef) {
let task = p.header();
if task.expires_at.get() != u64::MAX {
if task.state.timer_enqueue() {
task.timer_queue_item.next.set(self.head.get());
self.head.set(Some(p));
}
}
}
pub(crate) unsafe fn next_expiration(&self) -> u64 {
let mut res = u64::MAX;
self.retain(|p| {
let task = p.header();
let expires = task.expires_at.get();
res = min(res, expires);
expires != u64::MAX
});
res
}
pub(crate) unsafe fn dequeue_expired(&self, now: u64, on_task: impl Fn(TaskRef)) {
self.retain(|p| {
let task = p.header();
if task.expires_at.get() <= now {
on_task(p);
false
} else {
true
}
});
}
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
let mut prev = &self.head;
while let Some(p) = prev.get() {
let task = p.header();
if f(p) {
// Skip to next
prev = &task.timer_queue_item.next;
} else {
// Remove it
prev.set(task.timer_queue_item.next.get());
task.state.timer_dequeue();
}
next: Cell::new(None),
expires_at: Cell::new(0),
#[cfg(feature = "_timer-item-payload")]
payload: OpaqueData::new(),
}
}
}

View File

@ -0,0 +1,84 @@
#![allow(unused)]
use crate::raw::{SyncExecutor, TaskRef};
#[cfg(not(feature = "rtos-trace"))]
extern "Rust" {
fn _embassy_trace_task_new(executor_id: u32, task_id: u32);
fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32);
fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32);
fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32);
fn _embassy_trace_executor_idle(executor_id: 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);
}
#[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();
}
#[cfg(feature = "rtos-trace")]
impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor {
fn task_list() {
// We don't know what tasks exist, so we can't send them.
}
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}

View File

@ -32,31 +32,11 @@ pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
///
/// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> TaskRef {
let (vtable, data) = {
#[cfg(not(feature = "nightly"))]
{
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")]
{
(waker.vtable(), waker.data())
}
};
if vtable != &VTABLE {
// make sure to compare vtable addresses. Doing `==` on the references
// will compare the contents, which is slower.
if waker.vtable() as *const _ != &VTABLE as *const _ {
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
}
// safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { TaskRef::from_ptr(data as *const TaskHeader) }
unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) }
}

View File

@ -1,6 +1,7 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::mem;
use core::sync::atomic::Ordering;
use core::task::Poll;
use super::raw;
@ -92,7 +93,13 @@ impl Spawner {
pub async fn for_current_executor() -> Self {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
let executor = unsafe {
task.header()
.executor
.load(Ordering::Relaxed)
.as_ref()
.unwrap_unchecked()
};
let executor = unsafe { raw::Executor::wrap(executor) };
Poll::Ready(Self::new(executor))
})
@ -164,7 +171,13 @@ impl SendSpawner {
pub async fn for_current_executor() -> Self {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = unsafe { task.header().executor.get().unwrap_unchecked() };
let executor = unsafe {
task.header()
.executor
.load(Ordering::Relaxed)
.as_ref()
.unwrap_unchecked()
};
Poll::Ready(Self::new(executor))
})
.await

View File

@ -40,6 +40,7 @@ fn setup() -> (&'static Executor, Trace) {
let trace = Trace::new();
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
let executor = &*Box::leak(Box::new(Executor::new(context)));
(executor, trace)
}
@ -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]
fn executor_task_cfg_args() {
// simulate cfg'ing away argument c

View File

@ -0,0 +1,23 @@
#[cfg(not(miri))]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/abi.rs");
t.compile_fail("tests/ui/bad_return.rs");
t.compile_fail("tests/ui/generics.rs");
t.compile_fail("tests/ui/impl_trait_nested.rs");
t.compile_fail("tests/ui/impl_trait.rs");
t.compile_fail("tests/ui/impl_trait_static.rs");
t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
#[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
t.compile_fail("tests/ui/not_async.rs");
t.compile_fail("tests/ui/self_ref.rs");
t.compile_fail("tests/ui/self.rs");
t.compile_fail("tests/ui/where_clause.rs");
}

View 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() {}

View 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() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

View 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() {}

View 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 {
| ^^^^^^^^^^^^^^^^^^^^^^

View 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() {}

View 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) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View 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() {}

View 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) {}
| ^^^^^^^^^^

View 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() {}

View 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>) {}
| ^^^^^^^^^^^^^^^^^^^^

View 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() {}

View 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_static.rs:4:18
|
4 | async fn foo(_x: impl Sized + 'static) {}
| ^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &'_ u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_anon.rs:4:19
|
4 | async fn foo(_x: &'_ u32) {}
| ^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &'static &'_ u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_anon_nested.rs:4:28
|
4 | async fn foo(_x: &'static &'_ u32) {}
| ^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_elided.rs:4:18
|
4 | async fn foo(_x: &u32) {}
| ^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo<'a>(_x: &'a u32) {}
fn main() {}

View File

@ -0,0 +1,11 @@
error: task functions must not be generic
--> tests/ui/nonstatic_ref_generic.rs:4:1
|
4 | async fn foo<'a>(_x: &'a u32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_generic.rs:4:23
|
4 | async fn foo<'a>(_x: &'a u32) {}
| ^^

View 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(_x: Foo<'_>) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_struct_anon.rs:6:23
|
6 | async fn task(_x: Foo<'_>) {}
| ^^

View 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(_x: Foo) {}
fn main() {}

View File

@ -0,0 +1,10 @@
error[E0726]: implicit elided lifetime not allowed here
--> tests/ui/nonstatic_struct_elided.rs:6:19
|
6 | async fn task(_x: Foo) {}
| ^^^ expected lifetime parameter
|
help: indicate the anonymous lifetime
|
6 | async fn task(_x: Foo<'_>) {}
| ++++

View 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<'a>(_x: Foo<'a>) {}
fn main() {}

View File

@ -0,0 +1,11 @@
error: task functions must not be generic
--> tests/ui/nonstatic_struct_generic.rs:6:1
|
6 | async fn task<'a>(_x: Foo<'a>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_struct_generic.rs:6:27
|
6 | async fn task<'a>(_x: Foo<'a>) {}
| ^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
fn task() {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must be async
--> tests/ui/not_async.rs:6:1
|
6 | fn task() {}
| ^^^^^^^^^

View 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(self) {}
fn main() {}

View File

@ -0,0 +1,13 @@
error: task functions must not have `self` arguments
--> tests/ui/self.rs:6:15
|
6 | async fn task(self) {}
| ^^^^
error: `self` parameter is only allowed in associated functions
--> tests/ui/self.rs:6:15
|
6 | async fn task(self) {}
| ^^^^ not semantically valid as function parameter
|
= note: associated functions are those in `impl` or `trait` definitions

View 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(&mut self) {}
fn main() {}

View File

@ -0,0 +1,13 @@
error: task functions must not have `self` arguments
--> tests/ui/self_ref.rs:6:15
|
6 | async fn task(&mut self) {}
| ^^^^^^^^^
error: `self` parameter is only allowed in associated functions
--> tests/ui/self_ref.rs:6:15
|
6 | async fn task(&mut self) {}
| ^^^^^^^^^ not semantically valid as function parameter
|
= note: associated functions are those in `impl` or `trait` definitions

View File

@ -0,0 +1,12 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task()
where
(): Sized,
{
}
fn main() {}

View File

@ -0,0 +1,7 @@
error: task functions must not have `where` clauses
--> tests/ui/where_clause.rs:6:1
|
6 | / async fn task()
7 | | where
8 | | (): Sized,
| |______________^

View File

@ -188,6 +188,169 @@ where
// ====================================================================
/// Result for [`select5`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Either5<A, B, C, D, E> {
/// First future finished first.
First(A),
/// Second future finished first.
Second(B),
/// Third future finished first.
Third(C),
/// Fourth future finished first.
Fourth(D),
/// Fifth future finished first.
Fifth(E),
}
/// Same as [`select`], but with more futures.
pub fn select5<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E) -> Select5<A, B, C, D, E>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
{
Select5 { a, b, c, d, e }
}
/// Future for the [`select5`] function.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Select5<A, B, C, D, E> {
a: A,
b: B,
c: C,
d: D,
e: E,
}
impl<A, B, C, D, E> Future for Select5<A, B, C, D, E>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
{
type Output = Either5<A::Output, B::Output, C::Output, D::Output, E::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let a = unsafe { Pin::new_unchecked(&mut this.a) };
let b = unsafe { Pin::new_unchecked(&mut this.b) };
let c = unsafe { Pin::new_unchecked(&mut this.c) };
let d = unsafe { Pin::new_unchecked(&mut this.d) };
let e = unsafe { Pin::new_unchecked(&mut this.e) };
if let Poll::Ready(x) = a.poll(cx) {
return Poll::Ready(Either5::First(x));
}
if let Poll::Ready(x) = b.poll(cx) {
return Poll::Ready(Either5::Second(x));
}
if let Poll::Ready(x) = c.poll(cx) {
return Poll::Ready(Either5::Third(x));
}
if let Poll::Ready(x) = d.poll(cx) {
return Poll::Ready(Either5::Fourth(x));
}
if let Poll::Ready(x) = e.poll(cx) {
return Poll::Ready(Either5::Fifth(x));
}
Poll::Pending
}
}
// ====================================================================
/// Result for [`select6`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Either6<A, B, C, D, E, F> {
/// First future finished first.
First(A),
/// Second future finished first.
Second(B),
/// Third future finished first.
Third(C),
/// Fourth future finished first.
Fourth(D),
/// Fifth future finished first.
Fifth(E),
/// Sixth future finished first.
Sixth(F),
}
/// Same as [`select`], but with more futures.
pub fn select6<A, B, C, D, E, F>(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6<A, B, C, D, E, F>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
F: Future,
{
Select6 { a, b, c, d, e, f }
}
/// Future for the [`select6`] function.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Select6<A, B, C, D, E, F> {
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
}
impl<A, B, C, D, E, F> Future for Select6<A, B, C, D, E, F>
where
A: Future,
B: Future,
C: Future,
D: Future,
E: Future,
F: Future,
{
type Output = Either6<A::Output, B::Output, C::Output, D::Output, E::Output, F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let a = unsafe { Pin::new_unchecked(&mut this.a) };
let b = unsafe { Pin::new_unchecked(&mut this.b) };
let c = unsafe { Pin::new_unchecked(&mut this.c) };
let d = unsafe { Pin::new_unchecked(&mut this.d) };
let e = unsafe { Pin::new_unchecked(&mut this.e) };
let f = unsafe { Pin::new_unchecked(&mut this.f) };
if let Poll::Ready(x) = a.poll(cx) {
return Poll::Ready(Either6::First(x));
}
if let Poll::Ready(x) = b.poll(cx) {
return Poll::Ready(Either6::Second(x));
}
if let Poll::Ready(x) = c.poll(cx) {
return Poll::Ready(Either6::Third(x));
}
if let Poll::Ready(x) = d.poll(cx) {
return Poll::Ready(Either6::Fourth(x));
}
if let Poll::Ready(x) = e.poll(cx) {
return Poll::Ready(Either6::Fifth(x));
}
if let Poll::Ready(x) = f.poll(cx) {
return Poll::Ready(Either6::Sixth(x));
}
Poll::Pending
}
}
// ====================================================================
/// Future for the [`select_array`] function.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]

View File

@ -25,6 +25,6 @@ features = ["defmt"]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }

View File

@ -26,13 +26,11 @@ pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
}
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
const NEW_PACKET: PacketBuf<MTU> = PacketBuf::new();
/// Create a new channel state.
pub const fn new() -> Self {
Self {
rx: [Self::NEW_PACKET; N_RX],
tx: [Self::NEW_PACKET; N_TX],
rx: [const { PacketBuf::new() }; N_RX],
tx: [const { PacketBuf::new() }; N_TX],
inner: MaybeUninit::uninit(),
}
}
@ -326,8 +324,14 @@ pub struct Device<'d, const MTU: usize> {
}
impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ;
type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ;
type RxToken<'a>
= RxToken<'a, MTU>
where
Self: 'a;
type TxToken<'a>
= TxToken<'a, MTU>
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() {

View File

@ -83,10 +83,12 @@ pub trait Driver {
}
impl<T: ?Sized + Driver> Driver for &mut T {
type RxToken<'a> = T::RxToken<'a>
type RxToken<'a>
= T::RxToken<'a>
where
Self: 'a;
type TxToken<'a> = T::TxToken<'a>
type TxToken<'a>
= T::TxToken<'a>
where
Self: 'a;

View File

@ -635,11 +635,13 @@ where
S: SpiDevice,
O: OutputPin,
{
type RxToken<'a> = RxToken<'a>
type RxToken<'a>
= RxToken<'a>
where
Self: 'a;
type TxToken<'a> = TxToken<'a, S, O>
type TxToken<'a>
= TxToken<'a, S, O>
where
Self: 'a;

View File

@ -18,7 +18,7 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
embassy-time = { version = "0.3.2", path = "../embassy-time" }
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
embassy-sync = { version = "0.6.1", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}

View File

@ -17,10 +17,11 @@ log = [ "dep:log" ]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
nrf9160-pac = { version = "0.12.0" }
nrf-pac = { git = "https://github.com/embassy-rs/nrf-pac", rev = "52e3a757f06035c94291bfc42b0c03f71e4d677e" }
cortex-m = "0.7.7"
embassy-time = { version = "0.3.1", path = "../embassy-time" }
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
embassy-sync = { version = "0.6.1", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}

View File

@ -21,6 +21,8 @@ pub struct Config<'a> {
pub auth_prot: AuthProt,
/// Credentials.
pub auth: Option<(&'a [u8], &'a [u8])>,
/// SIM pin
pub pin: Option<&'a [u8]>,
}
/// Authentication protocol.
@ -133,6 +135,16 @@ impl<'a> Control<'a> {
// info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
if let Some(pin) = config.pin {
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CPIN")
.with_string_parameter(pin)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let _ = self.control.at_command(op, &mut buf).await;
// Ignore ERROR which means no pin required
}
Ok(())
}

View File

@ -17,12 +17,12 @@ use core::slice;
use core::sync::atomic::{compiler_fence, fence, Ordering};
use core::task::{Poll, Waker};
use cortex_m::peripheral::NVIC;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::pipe;
use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration};
use heapless::Vec;
use pac::NVIC;
use {embassy_net_driver_channel as ch, nrf9160_pac as pac};
use {embassy_net_driver_channel as ch, nrf_pac as pac};
const RX_SIZE: usize = 8 * 1024;
const TRACE_SIZE: usize = 16 * 1024;
@ -38,11 +38,9 @@ static WAKER: AtomicWaker = AtomicWaker::new();
/// Call this function on IPC IRQ
pub fn on_ipc_irq() {
let ipc = unsafe { &*pac::IPC_NS::ptr() };
trace!("irq");
ipc.inten.write(|w| w);
pac::IPC_NS.inten().write(|_| ());
WAKER.wake();
}
@ -135,22 +133,21 @@ async fn new_internal<'a>(
"shmem must be in the lower 128kb of RAM"
);
let spu = unsafe { &*pac::SPU_S::ptr() };
let spu = pac::SPU_S;
debug!("Setting IPC RAM as nonsecure...");
let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE;
let region_end = region_start + shmem_len / SPU_REGION_SIZE;
for i in region_start..region_end {
spu.ramregion[i].perm.write(|w| {
w.execute().set_bit();
w.write().set_bit();
w.read().set_bit();
w.secattr().clear_bit();
w.lock().clear_bit();
w
spu.ramregion(i).perm().write(|w| {
w.set_execute(true);
w.set_write(true);
w.set_read(true);
w.set_secattr(false);
w.set_lock(false);
})
}
spu.periphid[42].perm.write(|w| w.secattr().non_secure());
spu.periphid(42).perm().write(|w| w.set_secattr(false));
let mut alloc = Allocator {
start: shmem_ptr,
@ -158,8 +155,8 @@ async fn new_internal<'a>(
_phantom: PhantomData,
};
let ipc = unsafe { &*pac::IPC_NS::ptr() };
let power = unsafe { &*pac::POWER_S::ptr() };
let ipc = pac::IPC_NS;
let power = pac::POWER_S;
let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() });
let rx = alloc.alloc_bytes(RX_SIZE);
@ -177,20 +174,20 @@ async fn new_internal<'a>(
cb.trace.base = trace.as_mut_ptr() as _;
cb.trace.size = TRACE_SIZE;
ipc.gpmem[0].write(|w| unsafe { w.bits(cb as *mut _ as u32) });
ipc.gpmem[1].write(|w| unsafe { w.bits(0) });
ipc.gpmem(0).write_value(cb as *mut _ as u32);
ipc.gpmem(1).write_value(0);
// connect task/event i to channel i
for i in 0..8 {
ipc.send_cnf[i].write(|w| unsafe { w.bits(1 << i) });
ipc.receive_cnf[i].write(|w| unsafe { w.bits(1 << i) });
ipc.send_cnf(i).write(|w| w.0 = 1 << i);
ipc.receive_cnf(i).write(|w| w.0 = 1 << i);
}
compiler_fence(Ordering::SeqCst);
// POWER.LTEMODEM.STARTN = 0
// The reg is missing in the PAC??
let startn = unsafe { (power as *const _ as *mut u32).add(0x610 / 4) };
let startn = unsafe { (power.as_ptr() as *mut u32).add(0x610 / 4) };
unsafe { startn.write_volatile(0) }
unsafe { NVIC::unmask(pac::Interrupt::IPC) };
@ -201,6 +198,7 @@ async fn new_internal<'a>(
cb,
requests: [const { None }; REQ_COUNT],
next_req_serial: 0x12345678,
net_fd: None,
rx_control_list: ptr::null_mut(),
rx_data_list: ptr::null_mut(),
@ -212,6 +210,7 @@ async fn new_internal<'a>(
tx_seq_no: 0,
tx_buf_used: [false; TX_BUF_COUNT],
tx_waker: WakerRegistration::new(),
trace_chans: Vec::new(),
trace_check: PointerChecker {
@ -307,6 +306,8 @@ struct StateInner {
requests: [Option<PendingRequest>; REQ_COUNT],
next_req_serial: u32,
net_fd: Option<u32>,
rx_control_list: *mut List,
rx_data_list: *mut List,
rx_seq_no: u16,
@ -314,6 +315,7 @@ struct StateInner {
tx_seq_no: u16,
tx_buf_used: [bool; TX_BUF_COUNT],
tx_waker: WakerRegistration,
trace_chans: Vec<TraceChannelInfo, TRACE_CHANNEL_COUNT>,
trace_check: PointerChecker,
@ -322,15 +324,15 @@ struct StateInner {
impl StateInner {
fn poll(&mut self, trace_writer: &mut Option<TraceWriter<'_>>, ch: &mut ch::Runner<MTU>) {
trace!("poll!");
let ipc = unsafe { &*pac::IPC_NS::ptr() };
let ipc = pac::IPC_NS;
if ipc.events_receive[0].read().bits() != 0 {
ipc.events_receive[0].reset();
if ipc.events_receive(0).read() != 0 {
ipc.events_receive(0).write_value(0);
trace!("ipc 0");
}
if ipc.events_receive[2].read().bits() != 0 {
ipc.events_receive[2].reset();
if ipc.events_receive(2).read() != 0 {
ipc.events_receive(2).write_value(0);
trace!("ipc 2");
if !self.init {
@ -353,8 +355,8 @@ impl StateInner {
}
}
if ipc.events_receive[4].read().bits() != 0 {
ipc.events_receive[4].reset();
if ipc.events_receive(4).read() != 0 {
ipc.events_receive(4).write_value(0);
trace!("ipc 4");
loop {
@ -368,13 +370,13 @@ impl StateInner {
}
}
if ipc.events_receive[6].read().bits() != 0 {
ipc.events_receive[6].reset();
if ipc.events_receive(6).read() != 0 {
ipc.events_receive(6).write_value(0);
trace!("ipc 6");
}
if ipc.events_receive[7].read().bits() != 0 {
ipc.events_receive[7].reset();
if ipc.events_receive(7).read() != 0 {
ipc.events_receive(7).write_value(0);
trace!("ipc 7: trace");
let msg = unsafe { addr_of!((*self.cb).trace.rx_state).read_volatile() };
@ -437,13 +439,12 @@ impl StateInner {
}
}
ipc.intenset.write(|w| {
w.receive0().set_bit();
w.receive2().set_bit();
w.receive4().set_bit();
w.receive6().set_bit();
w.receive7().set_bit();
w
ipc.intenset().write(|w| {
w.set_receive0(true);
w.set_receive2(true);
w.set_receive4(true);
w.set_receive6(true);
w.set_receive7(true);
});
}
@ -511,6 +512,7 @@ impl StateInner {
if data.is_empty() {
msg.data = ptr::null_mut();
msg.data_len = 0;
self.send_message_raw(msg)
} else {
assert!(data.len() <= TX_BUF_SIZE);
let buf_idx = self.find_free_tx_buf().ok_or(NoFreeBufs)?;
@ -521,10 +523,16 @@ impl StateInner {
self.tx_buf_used[buf_idx] = true;
fence(Ordering::SeqCst); // synchronize copy_nonoverlapping (non-volatile) with volatile writes below.
if let Err(e) = self.send_message_raw(msg) {
msg.data = ptr::null_mut();
msg.data_len = 0;
self.tx_buf_used[buf_idx] = false;
self.tx_waker.wake();
Err(e)
} else {
Ok(())
}
}
// TODO free data buf if send_message_raw fails.
self.send_message_raw(msg)
}
fn send_message_raw(&mut self, msg: &Message) -> Result<(), NoFreeBufs> {
@ -546,8 +554,8 @@ impl StateInner {
unsafe { addr_of_mut!((*list_item).state).write_volatile((self.tx_seq_no as u32) << 16 | 0x01) }
self.tx_seq_no = self.tx_seq_no.wrapping_add(1);
let ipc = unsafe { &*pac::IPC_NS::ptr() };
ipc.tasks_send[ipc_ch].write(|w| unsafe { w.bits(1) });
let ipc = pac::IPC_NS;
ipc.tasks_send(ipc_ch).write_value(1);
Ok(())
}
@ -584,6 +592,7 @@ impl StateInner {
);
}
self.tx_buf_used[idx] = false;
self.tx_waker.wake();
}
fn handle_data(&mut self, msg: &Message, ch: &mut ch::Runner<MTU>) {
@ -759,10 +768,22 @@ impl<'a> Control<'a> {
state.next_req_serial = state.next_req_serial.wrapping_add(1);
}
drop(state); // don't borrow state across awaits.
msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes());
unwrap!(state.send_message(msg, req_data));
poll_fn(|cx| {
let mut state = self.state.borrow_mut();
state.tx_waker.register(cx.waker());
match state.send_message(msg, req_data) {
Ok(_) => Poll::Ready(()),
Err(NoFreeBufs) => Poll::Pending,
}
})
.await;
// Setup the pending request state.
let mut state = self.state.borrow_mut();
let (req_slot_idx, req_slot) = state
.requests
.iter_mut()
@ -867,6 +888,8 @@ impl<'a> Control<'a> {
assert_eq!(status, 0);
assert_eq!(msg.param_len, 16);
let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap());
self.state.borrow_mut().net_fd.replace(fd);
trace!("got FD: {}", fd);
fd
}
@ -906,16 +929,17 @@ impl<'a> Runner<'a> {
state.poll(&mut self.trace_writer, &mut self.ch);
if let Poll::Ready(buf) = self.ch.poll_tx_buf(cx) {
let fd = 128u32; // TODO unhardcode
let mut msg: Message = unsafe { mem::zeroed() };
msg.channel = 2; // data
msg.id = 0x7006_0004; // IP send
msg.param_len = 12;
msg.param[4..8].copy_from_slice(&fd.to_le_bytes());
if let Err(e) = state.send_message(&mut msg, buf) {
warn!("tx failed: {:?}", e);
if let Some(fd) = state.net_fd {
let mut msg: Message = unsafe { mem::zeroed() };
msg.channel = 2; // data
msg.id = 0x7006_0004; // IP send
msg.param_len = 12;
msg.param[4..8].copy_from_slice(&fd.to_le_bytes());
if let Err(e) = state.send_message(&mut msg, buf) {
warn!("tx failed: {:?}", e);
}
self.ch.tx_done();
}
self.ch.tx_done();
}
Poll::Pending

View File

@ -20,8 +20,8 @@ log = { version = "0.4.14", optional = true }
embedded-io-async = { version = "0.6.1" }
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
ppproto = { version = "0.1.2"}
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
ppproto = { version = "0.2.0"}
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/"

View File

@ -152,8 +152,14 @@ impl TunTapDevice {
}
impl Driver for TunTapDevice {
type RxToken<'a> = RxToken where Self: 'a;
type TxToken<'a> = TxToken<'a> where Self: 'a;
type RxToken<'a>
= RxToken
where
Self: 'a;
type TxToken<'a>
= TxToken<'a>
where
Self: 'a;
fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let mut buf = vec![0; self.device.get_ref().mtu];

View File

@ -2,7 +2,7 @@
[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet SPI ethernet chips, operating in MACRAW mode.
See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module.
See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico)) module.
## Supported chips

View File

@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
No unreleased changes yet... Quick, go send a PR!
## 0.5 - 2024-11-28
- Refactor the API structure, simplifying lifetimes and generics.
- Stack is now a thin handle that implements `Copy+Clone`. Instead of passing `&Stack` around, you can now pass `Stack`.
- `Stack` and `DnsSocket` no longer need a generic parameter for the device driver.
- The `run()` method has been moved to a new `Runner` struct.
- Sockets are covariant wrt their lifetime.
- An implication of the refactor is now you need only one `StaticCell` instead of two if you need to share the network stack between tasks.
- Use standard `core::net` IP types instead of custom ones from smoltcp.
- Update to `smoltcp` v0.12.
- Add `mdns` Cargo feature.
- dns: properly handle `AddrType::Either` in `get_host_by_name()`
- dns: truncate instead of panic if the DHCP server gives us more DNS servers than the configured maximum.
- stack: add `wait_link_up()`, `wait_link_down()`, `wait_config_down()`.
- tcp: Add `recv_queue()`, `send_queue()`.
- tcp: Add `wait_read_ready()`, `wait_write_ready()`.
- tcp: allow setting timeout through `embedded-nal` client.
- tcp: fix `flush()` hanging forever if socket is closed with pending data.
- tcp: fix `flush()` not waiting for ACK of FIN.
- tcp: implement `ReadReady`, `WriteReady` traits from `embedded-io`.
- udp, raw: Add `wait_send_ready()`, `wait_recv_ready()`, `flush()`.
- udp: add `recv_from_with()`, `send_to_with()` methods, allowing for IO with one less copy.
- udp: send/recv now takes/returns full `UdpMetadata` instead of just the remote `IpEndpoint`.
- raw: add raw sockets.
## 0.4 - 2024-01-11
- Update to `embassy-time` v0.3.

View File

@ -1,6 +1,6 @@
[package]
name = "embassy-net"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Async TCP/IP network stack for embedded systems"
@ -27,7 +27,7 @@ default = []
std = []
## Enable defmt
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03"]
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03", "defmt?/ip_in_core"]
## Trace all raw received and transmitted packets using defmt or log.
packet-trace = []
@ -65,20 +65,20 @@ multicast = ["smoltcp/multicast"]
[dependencies]
defmt = { version = "0.3", optional = true }
defmt = { version = "0.3.8", optional = true }
log = { version = "0.4.14", optional = true }
smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [
smoltcp = { version = "0.12.0", default-features = false, features = [
"socket",
"async",
] }
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
embassy-time = { version = "0.3.2", path = "../embassy-time" }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embedded-io-async = { version = "0.6.1" }
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
heapless = { version = "0.8", default-features = false }
embedded-nal-async = { version = "0.7.1" }
embedded-nal-async = "0.8.0"
document-features = "0.2.7"

View File

@ -73,8 +73,11 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
&self,
host: &str,
addr_type: embedded_nal_async::AddrType,
) -> Result<embedded_nal_async::IpAddr, Self::Error> {
use embedded_nal_async::{AddrType, IpAddr};
) -> Result<core::net::IpAddr, Self::Error> {
use core::net::IpAddr;
use embedded_nal_async::AddrType;
let (qtype, secondary_qtype) = match addr_type {
AddrType::IPv4 => (DnsQueryType::A, None),
AddrType::IPv6 => (DnsQueryType::Aaaa, None),
@ -98,20 +101,16 @@ impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
if let Some(first) = addrs.get(0) {
Ok(match first {
#[cfg(feature = "proto-ipv4")]
IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()),
IpAddress::Ipv4(addr) => IpAddr::V4(*addr),
#[cfg(feature = "proto-ipv6")]
IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()),
IpAddress::Ipv6(addr) => IpAddr::V6(*addr),
})
} else {
Err(Error::Failed)
}
}
async fn get_host_by_address(
&self,
_addr: embedded_nal_async::IpAddr,
_result: &mut [u8],
) -> Result<usize, Self::Error> {
async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result<usize, Self::Error> {
todo!()
}
}

View File

@ -18,8 +18,14 @@ impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T>
where
T: Driver,
{
type RxToken<'a> = RxTokenAdapter<T::RxToken<'a>> where Self: 'a;
type TxToken<'a> = TxTokenAdapter<T::TxToken<'a>> where Self: 'a;
type RxToken<'a>
= RxTokenAdapter<T::RxToken<'a>>
where
Self: 'a;
type TxToken<'a>
= TxTokenAdapter<T::TxToken<'a>>
where
Self: 'a;
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.inner
@ -78,7 +84,7 @@ where
{
self.0.consume(|buf| {
#[cfg(feature = "packet-trace")]
trace!("rx: {:?}", buf);
trace!("embassy device rx: {:02x}", buf);
f(buf)
})
}
@ -99,7 +105,7 @@ where
self.0.consume(len, |buf| {
let r = f(buf);
#[cfg(feature = "packet-trace")]
trace!("tx: {:?}", buf);
trace!("embassy device tx: {:02x}", buf);
r
})
}

View File

@ -33,14 +33,14 @@ pub use embassy_net_driver as driver;
use embassy_net_driver::{Driver, LinkState};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
#[allow(unused_imports)]
use heapless::Vec;
#[cfg(feature = "dns")]
pub use smoltcp::config::DNS_MAX_SERVER_COUNT;
#[cfg(feature = "multicast")]
pub use smoltcp::iface::MulticastError;
#[allow(unused_imports)]
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
#[cfg(any(feature = "dns", feature = "dhcpv4"))]
use smoltcp::iface::SocketHandle;
use smoltcp::iface::{Interface, SocketSet, SocketStorage};
use smoltcp::phy::Medium;
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4::{self, RetryConfig};
@ -180,7 +180,7 @@ pub struct Config {
impl Config {
/// IPv4 configuration with static addressing.
#[cfg(feature = "proto-ipv4")]
pub fn ipv4_static(config: StaticConfigV4) -> Self {
pub const fn ipv4_static(config: StaticConfigV4) -> Self {
Self {
ipv4: ConfigV4::Static(config),
#[cfg(feature = "proto-ipv6")]
@ -190,7 +190,7 @@ impl Config {
/// IPv6 configuration with static addressing.
#[cfg(feature = "proto-ipv6")]
pub fn ipv6_static(config: StaticConfigV6) -> Self {
pub const fn ipv6_static(config: StaticConfigV6) -> Self {
Self {
#[cfg(feature = "proto-ipv4")]
ipv4: ConfigV4::None,
@ -206,7 +206,7 @@ impl Config {
/// let _cfg = Config::dhcpv4(Default::default());
/// ```
#[cfg(feature = "dhcpv4")]
pub fn dhcpv4(config: DhcpConfig) -> Self {
pub const fn dhcpv4(config: DhcpConfig) -> Self {
Self {
ipv4: ConfigV4::Dhcp(config),
#[cfg(feature = "proto-ipv6")]
@ -379,11 +379,11 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddres
impl<'d> Stack<'d> {
fn with<R>(&self, f: impl FnOnce(&Inner) -> R) -> R {
f(&*self.inner.borrow())
f(&self.inner.borrow())
}
fn with_mut<R>(&self, f: impl FnOnce(&mut Inner) -> R) -> R {
f(&mut *self.inner.borrow_mut())
f(&mut self.inner.borrow_mut())
}
/// Get the hardware address of the network interface.
@ -391,12 +391,12 @@ impl<'d> Stack<'d> {
self.with(|i| i.hardware_address)
}
/// Get whether the link is up.
/// Check whether the link is up.
pub fn is_link_up(&self) -> bool {
self.with(|i| i.link_up)
}
/// Get whether the network stack has a valid IP configuration.
/// Check whether the network stack has a valid IP configuration.
/// This is true if the network stack has a static IP configuration or if DHCP has completed
pub fn is_config_up(&self) -> bool {
let v4_up;
@ -642,7 +642,7 @@ impl<'d> Stack<'d> {
}
impl Inner {
#[allow(clippy::absurd_extreme_comparisons, dead_code)]
#[allow(clippy::absurd_extreme_comparisons)]
pub fn get_local_port(&mut self) -> u16 {
let res = self.next_local_port;
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
@ -732,7 +732,7 @@ impl Inner {
debug!(" Default gateway: {:?}", config.gateway);
unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok());
gateway_v4 = config.gateway.into();
gateway_v4 = config.gateway;
#[cfg(feature = "dns")]
for s in &config.dns_servers {
debug!(" DNS server: {:?}", s);
@ -831,22 +831,19 @@ impl Inner {
self.state_waker.wake();
}
#[allow(unused_mut)]
let mut apply_config = false;
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = self.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up {
let configure = if self.link_up {
if old_link_up != self.link_up {
socket.reset();
}
match socket.poll() {
None => {}
None => false,
Some(dhcpv4::Event::Deconfigured) => {
self.static_v4 = None;
apply_config = true;
true
}
Some(dhcpv4::Event::Configured(config)) => {
self.static_v4 = Some(StaticConfigV4 {
@ -854,20 +851,21 @@ impl Inner {
gateway: config.router,
dns_servers: config.dns_servers,
});
apply_config = true;
true
}
}
} else if old_link_up {
socket.reset();
self.static_v4 = None;
apply_config = true;
true
} else {
false
};
if configure {
self.apply_static_config()
}
}
if apply_config {
self.apply_static_config();
}
if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) {
let t = pin!(Timer::at(instant_from_smoltcp(poll_at)));
if t.poll(cx).is_ready() {

View File

@ -8,7 +8,7 @@ use embassy_net_driver::Driver;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::raw;
pub use smoltcp::socket::raw::PacketMetadata;
use smoltcp::wire::{IpProtocol, IpVersion};
pub use smoltcp::wire::{IpProtocol, IpVersion};
use crate::Stack;
@ -62,6 +62,14 @@ impl<'a> RawSocket<'a> {
})
}
/// Wait until the socket becomes readable.
///
/// A socket is readable when a packet has been received, or when there are queued packets in
/// the buffer.
pub async fn wait_recv_ready(&self) {
poll_fn(move |cx| self.poll_recv_ready(cx)).await
}
/// Receive a datagram.
///
/// This method will wait until a datagram is received.
@ -69,6 +77,24 @@ impl<'a> RawSocket<'a> {
poll_fn(move |cx| self.poll_recv(buf, cx)).await
}
/// Wait until a datagram can be read.
///
/// When no datagram is readable, this method will return `Poll::Pending` and
/// register the current task to be notified when a datagram is received.
///
/// When a datagram is received, this method will return `Poll::Ready`.
pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_recv() {
Poll::Ready(())
} else {
// socket buffer is empty wait until at least one byte has arrived
s.register_recv_waker(cx.waker());
Poll::Pending
}
})
}
/// Receive a datagram.
///
/// When no datagram is available, this method will return `Poll::Pending` and
@ -85,6 +111,33 @@ impl<'a> RawSocket<'a> {
})
}
/// Wait until the socket becomes writable.
///
/// A socket becomes writable when there is space in the buffer, from initial memory or after
/// dispatching datagrams on a full buffer.
pub async fn wait_send_ready(&self) {
poll_fn(move |cx| self.poll_send_ready(cx)).await
}
/// Wait until a datagram can be sent.
///
/// When no datagram can be sent (i.e. the buffer is full), this method will return
/// `Poll::Pending` and register the current task to be notified when
/// space is freed in the buffer after a datagram has been dispatched.
///
/// When a datagram can be sent, this method will return `Poll::Ready`.
pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_send() {
Poll::Ready(())
} else {
// socket buffer is full wait until a datagram has been dispatched
s.register_send_waker(cx.waker());
Poll::Pending
}
})
}
/// Send a datagram.
///
/// This method will wait until the datagram has been sent.`
@ -108,6 +161,23 @@ impl<'a> RawSocket<'a> {
}
})
}
/// Flush the socket.
///
/// This method will wait until the socket is flushed.
pub async fn flush(&mut self) {
poll_fn(move |cx| {
self.with_mut(|s, _| {
if s.send_queue() == 0 {
Poll::Ready(())
} else {
s.register_send_waker(cx.waker());
Poll::Pending
}
})
})
.await
}
}
impl Drop for RawSocket<'_> {

View File

@ -10,7 +10,7 @@
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use core::task::{Context, Poll};
use embassy_time::Duration;
use smoltcp::iface::{Interface, SocketHandle};
@ -73,6 +73,16 @@ pub struct TcpWriter<'a> {
}
impl<'a> TcpReader<'a> {
/// Wait until the socket becomes readable.
///
/// A socket becomes readable when the receive half of the full-duplex connection is open
/// (see [`may_recv()`](TcpSocket::may_recv)), and there is some pending data in the receive buffer.
///
/// This is the equivalent of [read](#method.read), without buffering any data.
pub async fn wait_read_ready(&self) {
poll_fn(move |cx| self.io.poll_read_ready(cx)).await
}
/// Read data from the socket.
///
/// Returns how many bytes were read, or an error. If no data is available, it waits
@ -115,6 +125,16 @@ impl<'a> TcpReader<'a> {
}
impl<'a> TcpWriter<'a> {
/// Wait until the socket becomes writable.
///
/// A socket becomes writable when the transmit half of the full-duplex connection is open
/// (see [`may_send()`](TcpSocket::may_send)), and the transmit buffer is not full.
///
/// This is the equivalent of [write](#method.write), without sending any data.
pub async fn wait_write_ready(&self) {
poll_fn(move |cx| self.io.poll_write_ready(cx)).await
}
/// Write data to the socket.
///
/// Returns how many bytes were written, or an error. If the socket is not ready to
@ -166,7 +186,7 @@ impl<'a> TcpSocket<'a> {
});
Self {
io: TcpIo { stack: stack, handle },
io: TcpIo { stack, handle },
}
}
@ -274,6 +294,16 @@ impl<'a> TcpSocket<'a> {
.await
}
/// Wait until the socket becomes readable.
///
/// A socket becomes readable when the receive half of the full-duplex connection is open
/// (see [may_recv](#method.may_recv)), and there is some pending data in the receive buffer.
///
/// This is the equivalent of [read](#method.read), without buffering any data.
pub async fn wait_read_ready(&self) {
poll_fn(move |cx| self.io.poll_read_ready(cx)).await
}
/// Read data from the socket.
///
/// Returns how many bytes were read, or an error. If no data is available, it waits
@ -285,6 +315,16 @@ impl<'a> TcpSocket<'a> {
self.io.read(buf).await
}
/// Wait until the socket becomes writable.
///
/// A socket becomes writable when the transmit half of the full-duplex connection is open
/// (see [may_send](#method.may_send)), and the transmit buffer is not full.
///
/// This is the equivalent of [write](#method.write), without sending any data.
pub async fn wait_write_ready(&self) {
poll_fn(move |cx| self.io.poll_write_ready(cx)).await
}
/// Write data to the socket.
///
/// Returns how many bytes were written, or an error. If the socket is not ready to
@ -376,11 +416,25 @@ impl<'a> TcpSocket<'a> {
self.io.with_mut(|s, _| s.abort())
}
/// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer.
/// Return whether the transmit half of the full-duplex connection is open.
///
/// This function returns true if it's possible to send data and have it arrive
/// to the remote endpoint. However, it does not make any guarantees about the state
/// of the transmit buffer, and even if it returns true, [write](#method.write) may
/// not be able to enqueue any octets.
///
/// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or
/// `CLOSE-WAIT` state.
pub fn may_send(&self) -> bool {
self.io.with(|s, _| s.may_send())
}
/// Check whether the transmit half of the full-duplex connection is open
/// (see [may_send](#method.may_send)), and the transmit buffer is not full.
pub fn can_send(&self) -> bool {
self.io.with(|s, _| s.can_send())
}
/// return whether the receive half of the full-duplex connection is open.
/// This function returns true if its possible to receive data from the remote endpoint.
/// It will return true while there is data in the receive buffer, and if there isnt,
@ -427,7 +481,7 @@ impl<'d> TcpIo<'d> {
})
}
fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
fn with_mut<R>(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
self.stack.with_mut(|i| {
let socket = i.sockets.get_mut::<tcp::Socket>(self.handle);
let res = f(socket, &mut i.iface);
@ -436,6 +490,17 @@ impl<'d> TcpIo<'d> {
})
}
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_recv() {
Poll::Ready(())
} else {
s.register_recv_waker(cx.waker());
Poll::Pending
}
})
}
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
poll_fn(move |cx| {
// CAUTION: smoltcp semantics around EOF are different to what you'd expect
@ -464,6 +529,17 @@ impl<'d> TcpIo<'d> {
.await
}
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
self.with_mut(|s, _| {
if s.can_send() {
Poll::Ready(())
} else {
s.register_send_waker(cx.waker());
Poll::Pending
}
})
}
async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
poll_fn(move |cx| {
self.with_mut(|s, _| match s.send_slice(buf) {
@ -675,10 +751,9 @@ mod embedded_io_impls {
pub mod client {
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;
use core::net::IpAddr;
use core::ptr::NonNull;
use embedded_nal_async::IpAddr;
use super::*;
/// TCP client connection pool compatible with `embedded-nal-async` traits.
@ -713,25 +788,25 @@ pub mod client {
for TcpClient<'d, N, TX_SZ, RX_SZ>
{
type Error = Error;
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
type Connection<'m>
= TcpConnection<'m, N, TX_SZ, RX_SZ>
where
Self: 'm;
async fn connect<'a>(
&'a self,
remote: embedded_nal_async::SocketAddr,
) -> Result<Self::Connection<'a>, Self::Error> {
async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result<Self::Connection<'a>, Self::Error> {
let addr: crate::IpAddress = match remote.ip() {
#[cfg(feature = "proto-ipv4")]
IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())),
IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr),
#[cfg(not(feature = "proto-ipv4"))]
IpAddr::V4(_) => panic!("ipv4 support not enabled"),
#[cfg(feature = "proto-ipv6")]
IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())),
IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr),
#[cfg(not(feature = "proto-ipv6"))]
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};
let remote_endpoint = (addr, remote.port());
let mut socket = TcpConnection::new(self.stack, self.state)?;
socket.socket.set_timeout(self.socket_timeout.clone());
socket.socket.set_timeout(self.socket_timeout);
socket
.socket
.connect(remote_endpoint)

Some files were not shown because too many files have changed in this diff Show More