diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..4db9edae
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,41 @@
+* text=auto
+
+*.adoc text
+*.html text
+*.in text
+*.json text
+*.md text
+*.proto text
+*.py text
+*.rs text
+*.service text
+*.sh text
+*.toml text
+*.txt text
+*.x text
+*.yml text
+
+*.raw binary
+*.bin binary
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.ico binary
+*.mov binary
+*.mp4 binary
+*.mp3 binary
+*.flv binary
+*.fla binary
+*.swf binary
+*.gz binary
+*.zip binary
+*.7z binary
+*.ttf binary
+*.eot binary
+*.woff binary
+*.pyc binary
+*.pdf binary
+*.ez binary
+*.bz2 binary
+*.swp binary
\ No newline at end of file
diff --git a/.github/ci/crlf.sh b/.github/ci/crlf.sh
new file mode 100755
index 00000000..45751040
--- /dev/null
+++ b/.github/ci/crlf.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+## on push branch~=gh-readonly-queue/main/.*
+## on pull_request
+
+set -euo pipefail
+
+FILES_WITH_CRLF=$(find ! -path "./.git/*" -not -type d | xargs file -N | (grep " CRLF " || true))
+
+if [ -z "$FILES_WITH_CRLF" ]; then
+ echo -e "No files with CRLF endings found."
+ exit 0
+else
+ NR_FILES=$(echo "$FILES_WITH_CRLF" | wc -l)
+ echo -e "ERROR: Found ${NR_FILES} files with CRLF endings."
+ echo "$FILES_WITH_CRLF"
+ exit "$NR_FILES"
+fi
\ No newline at end of file
diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh
index 9e9c78a4..57184dc1 100755
--- a/.github/ci/doc.sh
+++ b/.github/ci/doc.sh
@@ -15,7 +15,6 @@ export BUILDER_COMPRESS=true
# which makes rustup very sad
rustc --version > /dev/null
-docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup
docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup
docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup
docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup
@@ -36,10 +35,20 @@ docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/g
docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup
docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup
docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup
-docserver-builder -i ./embassy-net-w5500 -o webroot/crates/embassy-net-w5500/git.zup
+docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/git.zup
+docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup
+docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup
docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static
export KUBECONFIG=/ci/secrets/kubeconfig.yml
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
kubectl cp webroot/crates $POD:/data
-kubectl cp webroot/static $POD:/data
\ No newline at end of file
+kubectl cp webroot/static $POD:/data
+
+# build and upload stm32 last
+# so that it doesn't prevent other crates from getting docs updates when it breaks.
+rm -rf webroot
+docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup
+
+POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
+kubectl cp webroot/crates $POD:/data
diff --git a/.github/ci/test.sh b/.github/ci/test.sh
index d014e4bd..2892bcf8 100755
--- a/.github/ci/test.sh
+++ b/.github/ci/test.sh
@@ -13,7 +13,7 @@ hashtime save /ci/cache/filetime.json
cargo test --manifest-path ./embassy-sync/Cargo.toml
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
-cargo test --manifest-path ./embassy-hal-common/Cargo.toml
+cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 725fb69d..d48f7ba1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,16 +6,21 @@
"rust-analyzer.check.allTargets": false,
"rust-analyzer.check.noDefaultFeatures": true,
"rust-analyzer.cargo.noDefaultFeatures": true,
- "rust-analyzer.cargo.target": "thumbv7m-none-eabi",
+ "rust-analyzer.showUnlinkedFileNotification": false,
+ // uncomment the target of your chip.
+ //"rust-analyzer.cargo.target": "thumbv6m-none-eabi",
+ //"rust-analyzer.cargo.target": "thumbv7m-none-eabi",
+ "rust-analyzer.cargo.target": "thumbv7em-none-eabi",
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
"rust-analyzer.cargo.features": [
- ///"nightly",
+ // Uncomment if the example has a "nightly" feature.
+ "nightly",
],
"rust-analyzer.linkedProjects": [
- // Declare for the target you wish to develop
- // "embassy-executor/Cargo.toml",
- // "embassy-sync/Cargo.toml",
- "examples/stm32wl/Cargo.toml",
+ // Uncomment ONE line for the chip you want to work on.
+ // This makes rust-analyzer work on the example crate and all its dependencies.
+ "examples/nrf52840/Cargo.toml",
+ // "examples/nrf52840-rtic/Cargo.toml",
// "examples/nrf5340/Cargo.toml",
// "examples/nrf-rtos-trace/Cargo.toml",
// "examples/rp/Cargo.toml",
@@ -25,6 +30,7 @@
// "examples/stm32f1/Cargo.toml",
// "examples/stm32f2/Cargo.toml",
// "examples/stm32f3/Cargo.toml",
+ // "examples/stm32f334/Cargo.toml",
// "examples/stm32f4/Cargo.toml",
// "examples/stm32f7/Cargo.toml",
// "examples/stm32g0/Cargo.toml",
diff --git a/README.md b/README.md
index b05e55aa..c4c01dfb 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ The embassy-net network stac
- **Bluetooth** -
The nrf-softdevice crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
+The embassy-stm32-wpan crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers.
- **LoRa** -
embassy-lora supports LoRa networking.
@@ -111,6 +112,12 @@ cargo install probe-rs --features cli
cd examples/nrf52840
```
+- Ensure `Cargo.toml` sets the right feature for the name of the chip you are programming.
+ If this name is incorrect, the example may fail to run or immediately crash
+ after being programmed.
+
+- Ensure `.cargo/config.toml` contains the name of the chip you are programming.
+
- Run the example
For example:
@@ -119,6 +126,8 @@ For example:
cargo run --release --bin blinky
```
+For more help getting started, see [Getting Started][1] and [Running the Examples][2].
+
## Developing Embassy with Rust Analyzer based editors
The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/)
@@ -151,3 +160,5 @@ This work is licensed under either of
at your option.
+[1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started
+[2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples
diff --git a/ci.sh b/ci.sh
index 376cc8f4..e83b3b84 100755
--- a/ci.sh
+++ b/ci.sh
@@ -3,7 +3,7 @@
set -euo pipefail
export RUSTFLAGS=-Dwarnings
-export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
+export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
TARGET=$(rustc -vV | sed -n 's|host: ||p')
@@ -20,20 +20,19 @@ cargo batch \
--- 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-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
- --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
+ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \
+ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \
+ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,nightly \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits,nightly \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,nightly \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits,nightly \
+ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,nightly \
+ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,nightly \
+ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154,nightly \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \
@@ -53,6 +52,7 @@ cargo batch \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
+ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,qspi-as-gpio \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any \
@@ -81,11 +81,13 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,unstable-traits \
+ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f378cc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32g0c1ve,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \
+ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32g474pe,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \
@@ -115,6 +117,7 @@ cargo batch \
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \
--- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \
+ --- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f334 \
--- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \
--- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \
--- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \
diff --git a/ci_stable.sh b/ci_stable.sh
index daae9896..4ee5f410 100755
--- a/ci_stable.sh
+++ b/ci_stable.sh
@@ -16,9 +16,7 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
- --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \
@@ -36,6 +34,7 @@ cargo batch \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,defmt \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,log \
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi \
+ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features qspi-as-gpio \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g473cc,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \
diff --git a/cyw43-firmware/43439A0.bin b/cyw43-firmware/43439A0.bin
index b46b3bef..01737527 100755
Binary files a/cyw43-firmware/43439A0.bin and b/cyw43-firmware/43439A0.bin differ
diff --git a/cyw43-firmware/43439A0_clm.bin b/cyw43-firmware/43439A0_clm.bin
index 6e3ba786..1fedd753 100755
Binary files a/cyw43-firmware/43439A0_clm.bin and b/cyw43-firmware/43439A0_clm.bin differ
diff --git a/cyw43-firmware/README.md b/cyw43-firmware/README.md
index 7381fdc5..db3d9c9c 100644
--- a/cyw43-firmware/README.md
+++ b/cyw43-firmware/README.md
@@ -2,4 +2,8 @@
Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439
-Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
\ No newline at end of file
+Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
+
+## Changelog
+
+* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62
diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs
index dca30c74..830a5b44 100644
--- a/cyw43-pio/src/lib.rs
+++ b/cyw43-pio/src/lib.rs
@@ -8,7 +8,6 @@ use cyw43::SpiBusCyw43;
use embassy_rp::dma::Channel;
use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate};
use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
-use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef};
use fixed::FixedU32;
use pio_proc::pio_asm;
@@ -88,8 +87,6 @@ where
".wrap"
);
- let relocated = RelocatedProgram::new(&program.program);
-
let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio);
pin_io.set_pull(Pull::None);
pin_io.set_schmitt(true);
@@ -102,7 +99,8 @@ where
pin_clk.set_slew_rate(SlewRate::Fast);
let mut cfg = Config::default();
- cfg.use_program(&common.load_program(&relocated), &[&pin_clk]);
+ let loaded_program = common.load_program(&program.program);
+ cfg.use_program(&loaded_program, &[&pin_clk]);
cfg.set_out_pins(&[&pin_io]);
cfg.set_in_pins(&[&pin_io]);
cfg.set_set_pins(&[&pin_io]);
@@ -142,7 +140,7 @@ where
sm,
irq,
dma: dma.into_ref(),
- wrap_target: relocated.wrap().target,
+ wrap_target: loaded_program.wrap.target,
}
}
diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml
index 50fb7c5d..855d54b1 100644
--- a/cyw43/Cargo.toml
+++ b/cyw43/Cargo.toml
@@ -24,7 +24,7 @@ cortex-m = "0.7.6"
cortex-m-rt = "0.7.0"
futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
-embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.11" }
+embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-rc.1" }
num_enum = { version = "0.5.7", default-features = false }
[package.metadata.embassy_docs]
diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs
index e26f1112..0b5632cf 100644
--- a/cyw43/src/bus.rs
+++ b/cyw43/src/bus.rs
@@ -102,7 +102,7 @@ where
cmd_buf[0] = cmd;
cmd_buf[1..][..buf.len()].copy_from_slice(buf);
- self.status = self.spi.cmd_write(&cmd_buf).await;
+ self.status = self.spi.cmd_write(&cmd_buf[..buf.len() + 1]).await;
}
#[allow(unused)]
diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs
index fd11f367..30a3d5f2 100644
--- a/cyw43/src/lib.rs
+++ b/cyw43/src/lib.rs
@@ -216,7 +216,7 @@ where
PWR: OutputPin,
SPI: SpiBusCyw43,
{
- let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
+ let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
let state_ch = ch_runner.state_runner();
let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events);
diff --git a/docs/modules/ROOT/examples/basic/Cargo.toml b/docs/modules/ROOT/examples/basic/Cargo.toml
index 237ae0ac..e94358a9 100644
--- a/docs/modules/ROOT/examples/basic/Cargo.toml
+++ b/docs/modules/ROOT/examples/basic/Cargo.toml
@@ -6,7 +6,7 @@ version = "0.1.0"
license = "MIT OR Apache-2.0"
[dependencies]
-embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
+embassy-executor = { version = "0.3.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] }
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] }
diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml
index a7236ed5..b39f02ae 100644
--- a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml
+++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml
@@ -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.2.0", features = ["nightly", "arch-cortex-m", "executor-thread"] }
+embassy-executor = { version = "0.3.0", features = ["nightly", "arch-cortex-m", "executor-thread"] }
defmt = "0.3.0"
defmt-rtt = "0.3.0"
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs
index 20731ee0..ae713bb6 100644
--- a/embassy-boot/boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/boot/src/firmware_updater/asynch.rs
@@ -10,9 +10,9 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAG
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
-pub struct FirmwareUpdater {
+pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
dfu: DFU,
- state: STATE,
+ state: FirmwareState<'d, STATE>,
}
#[cfg(target_os = "none")]
@@ -47,22 +47,12 @@ impl<'a, FLASH: NorFlash>
}
}
-impl FirmwareUpdater {
+impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
- pub fn new(config: FirmwareUpdaterConfig) -> Self {
+ pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self {
Self {
dfu: config.dfu,
- state: config.state,
- }
- }
-
- // Make sure we are running a booted firmware to avoid reverting to a bad state.
- async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- if self.get_state(aligned).await? == State::Boot {
- Ok(())
- } else {
- Err(FirmwareUpdaterError::BadState)
+ state: FirmwareState::new(config.state, aligned),
}
}
@@ -71,14 +61,8 @@ impl FirmwareUpdater {
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
- pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result {
- self.state.read(0, aligned).await?;
-
- if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
- Ok(State::Swap)
- } else {
- Ok(State::Boot)
- }
+ pub async fn get_state(&mut self) -> Result {
+ self.state.get_state().await
}
/// Verify the DFU given a public key. If there is an error then DO NOT
@@ -92,23 +76,16 @@ impl FirmwareUpdater {
///
/// If no signature feature is set then this method will always return a
/// signature error.
- ///
- /// # Safety
- ///
- /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
- /// and written to.
#[cfg(feature = "_verify")]
pub async fn verify_and_mark_updated(
&mut self,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
- _aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
assert!(_update_len <= self.dfu.capacity() as u32);
- self.verify_booted(_aligned).await?;
+ self.state.verify_booted().await?;
#[cfg(feature = "ed25519-dalek")]
{
@@ -121,8 +98,9 @@ impl FirmwareUpdater {
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
+ let mut chunk_buf = [0; 2];
let mut message = [0; 64];
- self.hash::(_update_len, _aligned, &mut message).await?;
+ self.hash::(_update_len, &mut chunk_buf, &mut message).await?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
@@ -143,7 +121,8 @@ impl FirmwareUpdater {
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
- self.hash::(_update_len, _aligned, &mut message).await?;
+ let mut chunk_buf = [0; 2];
+ self.hash::(_update_len, &mut chunk_buf, &mut message).await?;
let r = public_key.verify(&message, &signature);
trace!(
@@ -156,7 +135,7 @@ impl FirmwareUpdater {
r.map_err(into_signature_error)?
}
- self.set_magic(_aligned, SWAP_MAGIC).await
+ self.state.mark_updated().await
}
/// Verify the update in DFU with any digest.
@@ -177,49 +156,14 @@ impl FirmwareUpdater {
}
/// Mark to trigger firmware swap on next boot.
- ///
- /// # Safety
- ///
- /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
- pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.set_magic(aligned, SWAP_MAGIC).await
+ pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.state.mark_updated().await
}
/// Mark firmware boot successful and stop rollback on reset.
- ///
- /// # Safety
- ///
- /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
- pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.set_magic(aligned, BOOT_MAGIC).await
- }
-
- async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
- self.state.read(0, aligned).await?;
-
- if aligned.iter().any(|&b| b != magic) {
- // Read progress validity
- self.state.read(STATE::WRITE_SIZE as u32, aligned).await?;
-
- if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
- // The current progress validity marker is invalid
- } else {
- // Invalidate progress
- aligned.fill(!STATE_ERASE_VALUE);
- self.state.write(STATE::WRITE_SIZE as u32, aligned).await?;
- }
-
- // Clear magic and progress
- self.state.erase(0, self.state.capacity() as u32).await?;
-
- // Set magic
- aligned.fill(magic);
- self.state.write(0, aligned).await?;
- }
- Ok(())
+ pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.state.mark_booted().await
}
/// Write data to a flash page.
@@ -229,16 +173,10 @@ impl FirmwareUpdater {
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
- pub async fn write_firmware(
- &mut self,
- aligned: &mut [u8],
- offset: usize,
- data: &[u8],
- ) -> Result<(), FirmwareUpdaterError> {
+ pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= DFU::ERASE_SIZE);
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.verify_booted(aligned).await?;
+ self.state.verify_booted().await?;
self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
@@ -252,20 +190,94 @@ impl FirmwareUpdater {
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
- ///
- /// # Safety
- ///
- /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
- pub async fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.verify_booted(aligned).await?;
-
+ pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
+ self.state.verify_booted().await?;
self.dfu.erase(0, self.dfu.capacity() as u32).await?;
Ok(&mut self.dfu)
}
}
+/// Manages the state partition of the firmware update.
+///
+/// Can be used standalone for more fine grained control, or as part of the updater.
+pub struct FirmwareState<'d, STATE> {
+ state: STATE,
+ aligned: &'d mut [u8],
+}
+
+impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
+ /// Create a firmware state instance with a buffer for magic content and state partition.
+ ///
+ /// # Safety
+ ///
+ /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
+ /// and written to.
+ pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ Self { state, aligned }
+ }
+
+ // Make sure we are running a booted firmware to avoid reverting to a bad state.
+ async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
+ if self.get_state().await? == State::Boot {
+ Ok(())
+ } else {
+ Err(FirmwareUpdaterError::BadState)
+ }
+ }
+
+ /// Obtain the current state.
+ ///
+ /// This is useful to check if the bootloader has just done a swap, in order
+ /// to do verifications and self-tests of the new image before calling
+ /// `mark_booted`.
+ pub async fn get_state(&mut self) -> Result {
+ self.state.read(0, &mut self.aligned).await?;
+
+ if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
+ Ok(State::Swap)
+ } else {
+ Ok(State::Boot)
+ }
+ }
+
+ /// Mark to trigger firmware swap on next boot.
+ pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.set_magic(SWAP_MAGIC).await
+ }
+
+ /// Mark firmware boot successful and stop rollback on reset.
+ pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.set_magic(BOOT_MAGIC).await
+ }
+
+ async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
+ self.state.read(0, &mut self.aligned).await?;
+
+ if self.aligned.iter().any(|&b| b != magic) {
+ // Read progress validity
+ self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?;
+
+ if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
+ // The current progress validity marker is invalid
+ } else {
+ // Invalidate progress
+ self.aligned.fill(!STATE_ERASE_VALUE);
+ self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?;
+ }
+
+ // Clear magic and progress
+ self.state.erase(0, self.state.capacity() as u32).await?;
+
+ // Set magic
+ self.aligned.fill(magic);
+ self.state.write(0, &self.aligned).await?;
+ }
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use embassy_embedded_hal::flash::partition::Partition;
@@ -288,8 +300,8 @@ mod tests {
let mut to_write = [0; 4096];
to_write[..7].copy_from_slice(update.as_slice());
- let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
- block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap();
+ let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+ block_on(updater.write_firmware(0, to_write.as_slice())).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs
index f03f53e4..76e4264a 100644
--- a/embassy-boot/boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/boot/src/firmware_updater/blocking.rs
@@ -10,9 +10,9 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAG
/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
-pub struct BlockingFirmwareUpdater {
+pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
dfu: DFU,
- state: STATE,
+ state: BlockingFirmwareState<'d, STATE>,
}
#[cfg(target_os = "none")]
@@ -49,22 +49,17 @@ impl<'a, FLASH: NorFlash>
}
}
-impl BlockingFirmwareUpdater {
+impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
- pub fn new(config: FirmwareUpdaterConfig) -> Self {
+ ///
+ /// # Safety
+ ///
+ /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
+ /// and written to.
+ pub fn new(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self {
Self {
dfu: config.dfu,
- state: config.state,
- }
- }
-
- // Make sure we are running a booted firmware to avoid reverting to a bad state.
- fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- if self.get_state(aligned)? == State::Boot {
- Ok(())
- } else {
- Err(FirmwareUpdaterError::BadState)
+ state: BlockingFirmwareState::new(config.state, aligned),
}
}
@@ -73,14 +68,8 @@ impl BlockingFirmwareUpdater {
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
- pub fn get_state(&mut self, aligned: &mut [u8]) -> Result {
- self.state.read(0, aligned)?;
-
- if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
- Ok(State::Swap)
- } else {
- Ok(State::Boot)
- }
+ pub fn get_state(&mut self) -> Result {
+ self.state.get_state()
}
/// Verify the DFU given a public key. If there is an error then DO NOT
@@ -94,23 +83,16 @@ impl BlockingFirmwareUpdater {
///
/// If no signature feature is set then this method will always return a
/// signature error.
- ///
- /// # Safety
- ///
- /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
- /// and written to.
#[cfg(feature = "_verify")]
pub fn verify_and_mark_updated(
&mut self,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
- _aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
assert!(_update_len <= self.dfu.capacity() as u32);
- self.verify_booted(_aligned)?;
+ self.state.verify_booted()?;
#[cfg(feature = "ed25519-dalek")]
{
@@ -124,7 +106,8 @@ impl BlockingFirmwareUpdater {
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut message = [0; 64];
- self.hash::(_update_len, _aligned, &mut message)?;
+ let mut chunk_buf = [0; 2];
+ self.hash::(_update_len, &mut chunk_buf, &mut message)?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
@@ -145,7 +128,8 @@ impl BlockingFirmwareUpdater {
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
- self.hash::(_update_len, _aligned, &mut message)?;
+ let mut chunk_buf = [0; 2];
+ self.hash::(_update_len, &mut chunk_buf, &mut message)?;
let r = public_key.verify(&message, &signature);
trace!(
@@ -158,7 +142,7 @@ impl BlockingFirmwareUpdater {
r.map_err(into_signature_error)?
}
- self.set_magic(_aligned, SWAP_MAGIC)
+ self.state.mark_updated()
}
/// Verify the update in DFU with any digest.
@@ -179,49 +163,14 @@ impl BlockingFirmwareUpdater {
}
/// Mark to trigger firmware swap on next boot.
- ///
- /// # Safety
- ///
- /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
- pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.set_magic(aligned, SWAP_MAGIC)
+ pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.state.mark_updated()
}
/// Mark firmware boot successful and stop rollback on reset.
- ///
- /// # Safety
- ///
- /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
- pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.set_magic(aligned, BOOT_MAGIC)
- }
-
- fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
- self.state.read(0, aligned)?;
-
- if aligned.iter().any(|&b| b != magic) {
- // Read progress validity
- self.state.read(STATE::WRITE_SIZE as u32, aligned)?;
-
- if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
- // The current progress validity marker is invalid
- } else {
- // Invalidate progress
- aligned.fill(!STATE_ERASE_VALUE);
- self.state.write(STATE::WRITE_SIZE as u32, aligned)?;
- }
-
- // Clear magic and progress
- self.state.erase(0, self.state.capacity() as u32)?;
-
- // Set magic
- aligned.fill(magic);
- self.state.write(0, aligned)?;
- }
- Ok(())
+ pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.state.mark_booted()
}
/// Write data to a flash page.
@@ -231,15 +180,9 @@ impl BlockingFirmwareUpdater {
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
- pub fn write_firmware(
- &mut self,
- aligned: &mut [u8],
- offset: usize,
- data: &[u8],
- ) -> Result<(), FirmwareUpdaterError> {
+ pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= DFU::ERASE_SIZE);
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.verify_booted(aligned)?;
+ self.state.verify_booted()?;
self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
@@ -253,19 +196,94 @@ impl BlockingFirmwareUpdater {
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
- ///
- /// # Safety
- ///
- /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
- pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
- assert_eq!(aligned.len(), STATE::WRITE_SIZE);
- self.verify_booted(aligned)?;
+ pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
+ self.state.verify_booted()?;
self.dfu.erase(0, self.dfu.capacity() as u32)?;
Ok(&mut self.dfu)
}
}
+/// Manages the state partition of the firmware update.
+///
+/// Can be used standalone for more fine grained control, or as part of the updater.
+pub struct BlockingFirmwareState<'d, STATE> {
+ state: STATE,
+ aligned: &'d mut [u8],
+}
+
+impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
+ /// Create a firmware state instance with a buffer for magic content and state partition.
+ ///
+ /// # Safety
+ ///
+ /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
+ /// and written to.
+ pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ Self { state, aligned }
+ }
+
+ // Make sure we are running a booted firmware to avoid reverting to a bad state.
+ fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
+ if self.get_state()? == State::Boot {
+ Ok(())
+ } else {
+ Err(FirmwareUpdaterError::BadState)
+ }
+ }
+
+ /// Obtain the current state.
+ ///
+ /// This is useful to check if the bootloader has just done a swap, in order
+ /// to do verifications and self-tests of the new image before calling
+ /// `mark_booted`.
+ pub fn get_state(&mut self) -> Result {
+ self.state.read(0, &mut self.aligned)?;
+
+ if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
+ Ok(State::Swap)
+ } else {
+ Ok(State::Boot)
+ }
+ }
+
+ /// Mark to trigger firmware swap on next boot.
+ pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.set_magic(SWAP_MAGIC)
+ }
+
+ /// Mark firmware boot successful and stop rollback on reset.
+ pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
+ self.set_magic(BOOT_MAGIC)
+ }
+
+ fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
+ self.state.read(0, &mut self.aligned)?;
+
+ if self.aligned.iter().any(|&b| b != magic) {
+ // Read progress validity
+ self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?;
+
+ if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
+ // The current progress validity marker is invalid
+ } else {
+ // Invalidate progress
+ self.aligned.fill(!STATE_ERASE_VALUE);
+ self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?;
+ }
+
+ // Clear magic and progress
+ self.state.erase(0, self.state.capacity() as u32)?;
+
+ // Set magic
+ self.aligned.fill(magic);
+ self.state.write(0, &self.aligned)?;
+ }
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use core::cell::RefCell;
@@ -283,14 +301,14 @@ mod tests {
let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
let state = BlockingPartition::new(&flash, 0, 4096);
let dfu = BlockingPartition::new(&flash, 65536, 65536);
+ let mut aligned = [0; 8];
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let mut to_write = [0; 4096];
to_write[..7].copy_from_slice(update.as_slice());
- let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
- let mut aligned = [0; 8];
- updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap();
+ let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
+ updater.write_firmware(0, to_write.as_slice()).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
updater
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs
index 55ce8f36..937ddcc6 100644
--- a/embassy-boot/boot/src/firmware_updater/mod.rs
+++ b/embassy-boot/boot/src/firmware_updater/mod.rs
@@ -3,8 +3,8 @@ mod asynch;
mod blocking;
#[cfg(feature = "nightly")]
-pub use asynch::FirmwareUpdater;
-pub use blocking::BlockingFirmwareUpdater;
+pub use asynch::{FirmwareState, FirmwareUpdater};
+pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater};
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
/// Firmware updater flash configuration holding the two flashes used by the updater
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 016362b8..47f7c179 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -16,9 +16,11 @@ mod test_flash;
// TODO: Use the value provided by NorFlash when available
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
+pub use firmware_updater::{
+ BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError,
+};
#[cfg(feature = "nightly")]
-pub use firmware_updater::FirmwareUpdater;
-pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
+pub use firmware_updater::{FirmwareState, FirmwareUpdater};
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
@@ -118,15 +120,18 @@ mod tests {
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
- let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
- dfu: flash.dfu(),
- state: flash.state(),
- });
- block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
- block_on(updater.mark_updated(&mut aligned)).unwrap();
+ let mut updater = FirmwareUpdater::new(
+ FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ },
+ &mut aligned,
+ );
+ block_on(updater.write_firmware(0, &UPDATE)).unwrap();
+ block_on(updater.mark_updated()).unwrap();
// Writing after marking updated is not allowed until marked as booted.
- let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE));
+ let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE));
assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
let flash = flash.into_blocking();
@@ -158,11 +163,14 @@ mod tests {
// Mark as booted
let flash = flash.into_async();
- let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
- dfu: flash.dfu(),
- state: flash.state(),
- });
- block_on(updater.mark_booted(&mut aligned)).unwrap();
+ let mut updater = FirmwareUpdater::new(
+ FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ },
+ &mut aligned,
+ );
+ block_on(updater.mark_booted()).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
@@ -190,12 +198,15 @@ mod tests {
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
- let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
- dfu: flash.dfu(),
- state: flash.state(),
- });
- block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
- block_on(updater.mark_updated(&mut aligned)).unwrap();
+ let mut updater = FirmwareUpdater::new(
+ FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ },
+ &mut aligned,
+ );
+ block_on(updater.write_firmware(0, &UPDATE)).unwrap();
+ block_on(updater.mark_updated()).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
@@ -232,12 +243,15 @@ mod tests {
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
- let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
- dfu: flash.dfu(),
- state: flash.state(),
- });
- block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
- block_on(updater.mark_updated(&mut aligned)).unwrap();
+ let mut updater = FirmwareUpdater::new(
+ FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ },
+ &mut aligned,
+ );
+ block_on(updater.write_firmware(0, &UPDATE)).unwrap();
+ block_on(updater.mark_updated()).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
@@ -293,18 +307,19 @@ mod tests {
// On with the test
let flash = flash.into_async();
- let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
- dfu: flash.dfu(),
- state: flash.state(),
- });
-
let mut aligned = [0; 4];
+ let mut updater = FirmwareUpdater::new(
+ FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ },
+ &mut aligned,
+ );
assert!(block_on(updater.verify_and_mark_updated(
&public_key.to_bytes(),
&signature.to_bytes(),
firmware_len as u32,
- &mut aligned,
))
.is_ok());
}
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs
index bb702073..b9d86eb1 100644
--- a/embassy-boot/nrf/src/lib.rs
+++ b/embassy-boot/nrf/src/lib.rs
@@ -3,37 +3,28 @@
#![doc = include_str!("../README.md")]
mod fmt;
+pub use embassy_boot::{
+ AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig,
+};
#[cfg(feature = "nightly")]
-pub use embassy_boot::FirmwareUpdater;
-pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig};
-use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE};
+pub use embassy_boot::{FirmwareState, FirmwareUpdater};
+use embassy_nrf::nvmc::PAGE_SIZE;
use embassy_nrf::peripherals::WDT;
use embassy_nrf::wdt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for nRF devices.
-pub struct BootLoader {
- boot: embassy_boot::BootLoader,
- aligned_buf: AlignedBuffer,
-}
+pub struct BootLoader;
-impl
- BootLoader
-{
- /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
- pub fn new(config: BootLoaderConfig) -> Self {
- Self {
- boot: embassy_boot::BootLoader::new(config),
- aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
- }
- }
-
- /// Inspect the bootloader state and perform actions required before booting, such as swapping
- /// firmware.
- pub fn prepare(&mut self) {
- self.boot
- .prepare_boot(&mut self.aligned_buf.0)
- .expect("Boot prepare error");
+impl BootLoader {
+ /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware.
+ pub fn prepare(
+ config: BootLoaderConfig,
+ ) -> Self {
+ let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
+ let mut boot = embassy_boot::BootLoader::new(config);
+ boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error");
+ Self
}
/// Boots the application without softdevice mechanisms.
@@ -43,8 +34,6 @@ impl
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(not(feature = "softdevice"))]
pub unsafe fn load(self, start: u32) -> ! {
- core::mem::drop(self.boot);
-
let mut p = cortex_m::Peripherals::steal();
p.SCB.invalidate_icache();
p.SCB.vtor.write(start);
@@ -57,7 +46,7 @@ impl
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(feature = "softdevice")]
- pub unsafe fn load(&mut self, _app: u32) -> ! {
+ pub unsafe fn load(self, _app: u32) -> ! {
use nrf_softdevice_mbr as mbr;
const NRF_SUCCESS: u32 = 0;
@@ -104,15 +93,15 @@ impl
}
}
-/// A flash implementation that wraps NVMC and will pet a watchdog when touching flash.
-pub struct WatchdogFlash<'d> {
- flash: Nvmc<'d>,
+/// A flash implementation that wraps any flash and will pet a watchdog when touching flash.
+pub struct WatchdogFlash {
+ flash: FLASH,
wdt: wdt::WatchdogHandle,
}
-impl<'d> WatchdogFlash<'d> {
+impl WatchdogFlash {
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
- pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self {
+ pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self {
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
Ok(x) => x,
Err(_) => {
@@ -127,13 +116,13 @@ impl<'d> WatchdogFlash<'d> {
}
}
-impl<'d> ErrorType for WatchdogFlash<'d> {
- type Error = as ErrorType>::Error;
+impl ErrorType for WatchdogFlash {
+ type Error = FLASH::Error;
}
-impl<'d> NorFlash for WatchdogFlash<'d> {
- const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE;
- const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE;
+impl NorFlash for WatchdogFlash {
+ const WRITE_SIZE: usize = FLASH::WRITE_SIZE;
+ const ERASE_SIZE: usize = FLASH::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.wdt.pet();
@@ -145,8 +134,8 @@ impl<'d> NorFlash for WatchdogFlash<'d> {
}
}
-impl<'d> ReadNorFlash for WatchdogFlash<'d> {
- const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE;
+impl ReadNorFlash for WatchdogFlash {
+ const READ_SIZE: usize = FLASH::READ_SIZE;
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
self.wdt.pet();
self.flash.read(offset, data)
diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs
index 25329f9e..989e7521 100644
--- a/embassy-boot/rp/src/lib.rs
+++ b/embassy-boot/rp/src/lib.rs
@@ -3,38 +3,29 @@
#![doc = include_str!("../README.md")]
mod fmt;
+pub use embassy_boot::{
+ AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State,
+};
#[cfg(feature = "nightly")]
-pub use embassy_boot::FirmwareUpdater;
-pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
-use embassy_rp::flash::{Flash, ERASE_SIZE};
+pub use embassy_boot::{FirmwareState, FirmwareUpdater};
+use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE};
use embassy_rp::peripherals::{FLASH, WATCHDOG};
use embassy_rp::watchdog::Watchdog;
use embassy_time::Duration;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for RP2040 devices.
-pub struct BootLoader {
- boot: embassy_boot::BootLoader,
- aligned_buf: AlignedBuffer,
-}
+pub struct BootLoader;
-impl
- BootLoader
-{
- /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
- pub fn new(config: BootLoaderConfig) -> Self {
- Self {
- boot: embassy_boot::BootLoader::new(config),
- aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
- }
- }
-
- /// Inspect the bootloader state and perform actions required before booting, such as swapping
- /// firmware.
- pub fn prepare(&mut self) {
- self.boot
- .prepare_boot(self.aligned_buf.as_mut())
- .expect("Boot prepare error");
+impl BootLoader {
+ /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
+ pub fn prepare(
+ config: BootLoaderConfig,
+ ) -> Self {
+ let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
+ let mut boot = embassy_boot::BootLoader::new(config);
+ boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
+ Self
}
/// Boots the application.
@@ -43,8 +34,6 @@ impl
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(self, start: u32) -> ! {
- core::mem::drop(self.boot);
-
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
@@ -58,14 +47,14 @@ impl
/// A flash implementation that will feed a watchdog when touching flash.
pub struct WatchdogFlash<'d, const SIZE: usize> {
- flash: Flash<'d, FLASH, SIZE>,
+ flash: Flash<'d, FLASH, Blocking, SIZE>,
watchdog: Watchdog,
}
impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
/// Start a new watchdog with a given flash and watchdog peripheral and a timeout
pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self {
- let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash);
+ let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash);
let mut watchdog = Watchdog::new(watchdog);
watchdog.start(timeout);
Self { flash, watchdog }
@@ -73,28 +62,28 @@ impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
}
impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> {
- type Error = as ErrorType>::Error;
+ type Error = as ErrorType>::Error;
}
impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
- const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE;
- const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE;
+ const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE;
+ const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.watchdog.feed();
- self.flash.erase(from, to)
+ self.flash.blocking_erase(from, to)
}
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
self.watchdog.feed();
- self.flash.write(offset, data)
+ self.flash.blocking_write(offset, data)
}
}
impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> {
- const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE;
+ const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE;
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
self.watchdog.feed();
- self.flash.read(offset, data)
+ self.flash.blocking_read(offset, data)
}
fn capacity(&self) -> usize {
self.flash.capacity()
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
index 069de0d1..c6350c49 100644
--- a/embassy-boot/stm32/src/lib.rs
+++ b/embassy-boot/stm32/src/lib.rs
@@ -3,34 +3,25 @@
#![doc = include_str!("../README.md")]
mod fmt;
+pub use embassy_boot::{
+ AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State,
+};
#[cfg(feature = "nightly")]
-pub use embassy_boot::FirmwareUpdater;
-pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
+pub use embassy_boot::{FirmwareState, FirmwareUpdater};
use embedded_storage::nor_flash::NorFlash;
/// A bootloader for STM32 devices.
-pub struct BootLoader {
- boot: embassy_boot::BootLoader,
- aligned_buf: AlignedBuffer,
-}
+pub struct BootLoader;
-impl
- BootLoader
-{
- /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
- pub fn new(config: BootLoaderConfig) -> Self {
- Self {
- boot: embassy_boot::BootLoader::new(config),
- aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
- }
- }
-
- /// Inspect the bootloader state and perform actions required before booting, such as swapping
- /// firmware.
- pub fn prepare(&mut self) {
- self.boot
- .prepare_boot(self.aligned_buf.as_mut())
- .expect("Boot prepare error");
+impl BootLoader {
+ /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
+ pub fn prepare(
+ config: BootLoaderConfig,
+ ) -> Self {
+ let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
+ let mut boot = embassy_boot::BootLoader::new(config);
+ boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
+ Self
}
/// Boots the application.
@@ -39,8 +30,6 @@ impl
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(self, start: u32) -> ! {
- core::mem::drop(self.boot);
-
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml
index bba3d48b..fd921d27 100644
--- a/embassy-embedded-hal/Cargo.toml
+++ b/embassy-embedded-hal/Cargo.toml
@@ -25,8 +25,8 @@ embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
"unproven",
] }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
-embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" }
+embedded-hal-async = { version = "=1.0.0-rc.1", optional = true }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true }
nb = "1.0.0"
diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs
index 98ae2b02..ae0d0a7f 100644
--- a/embassy-embedded-hal/src/adapter/blocking_async.rs
+++ b/embassy-embedded-hal/src/adapter/blocking_async.rs
@@ -1,4 +1,4 @@
-use embedded_hal_02::{blocking, serial};
+use embedded_hal_02::blocking;
/// Wrapper that implements async traits using blocking implementations.
///
@@ -103,15 +103,6 @@ where
}
}
-// Uart implementatinos
-impl embedded_hal_1::serial::ErrorType for BlockingAsync
-where
- T: serial::Read,
- E: embedded_hal_1::serial::Error + 'static,
-{
- type Error = E;
-}
-
/// NOR flash wrapper
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md
index 4fd3dccf..4853ba29 100644
--- a/embassy-executor/CHANGELOG.md
+++ b/embassy-executor/CHANGELOG.md
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 0.3.0 - TBD
+
+- Replaced Pender. Implementations now must define an extern function called `__pender`.
+- Made `raw::AvailableTask` public
+- Made `SpawnToken::new_failed` public
+
+## 0.2.1 - 2023-08-10
+
+- Avoid calling `pend()` when waking expired timers
+- Properly reset finished task state with `integrated-timers` enabled
+- Introduce `InterruptExecutor::spawner()`
+- Fix incorrect critical section in Xtensa executor
+
## 0.2.0 - 2023-04-27
- Replace unnecessary atomics in runqueue
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index 590718e3..2a67f70d 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "embassy-executor"
-version = "0.2.0"
+version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
@@ -14,7 +14,7 @@ categories = [
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/"
-features = ["nightly", "defmt", "pender-callback"]
+features = ["nightly", "defmt"]
flavors = [
{ name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] },
{ name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] },
@@ -25,7 +25,7 @@ flavors = [
[package.metadata.docs.rs]
default-target = "thumbv7em-none-eabi"
targets = ["thumbv7em-none-eabi"]
-features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-thread", "executor-interrupt"]
+features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
[features]
@@ -37,9 +37,6 @@ arch-xtensa = ["_arch"]
arch-riscv32 = ["_arch"]
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
-# Enable creating a `Pender` from an arbitrary function pointer callback.
-pender-callback = []
-
# Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs)
executor-thread = []
# Enable the interrupt-mode executor (available in Cortex-M only)
diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs
index 94c8134d..0806a22a 100644
--- a/embassy-executor/src/arch/cortex_m.rs
+++ b/embassy-executor/src/arch/cortex_m.rs
@@ -1,3 +1,49 @@
+const THREAD_PENDER: usize = usize::MAX;
+
+#[export_name = "__pender"]
+#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
+fn __pender(context: *mut ()) {
+ unsafe {
+ // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt
+ // request number given to `InterruptExecutor::start`.
+
+ let context = context as usize;
+
+ #[cfg(feature = "executor-thread")]
+ // Try to make Rust optimize the branching away if we only use thread mode.
+ if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER {
+ core::arch::asm!("sev");
+ return;
+ }
+
+ #[cfg(feature = "executor-interrupt")]
+ {
+ use cortex_m::interrupt::InterruptNumber;
+ use cortex_m::peripheral::NVIC;
+
+ #[derive(Clone, Copy)]
+ struct Irq(u16);
+ unsafe impl InterruptNumber for Irq {
+ fn number(self) -> u16 {
+ self.0
+ }
+ }
+
+ let irq = Irq(context as u16);
+
+ // STIR is faster, but is only available in v7 and higher.
+ #[cfg(not(armv6m))]
+ {
+ let mut nvic: NVIC = core::mem::transmute(());
+ nvic.request(irq);
+ }
+
+ #[cfg(armv6m)]
+ NVIC::pend(irq);
+ }
+ }
+}
+
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
@@ -8,18 +54,9 @@ mod thread {
#[cfg(feature = "nightly")]
pub use embassy_macros::main_cortex_m as main;
- use crate::raw::{Pender, PenderInner};
+ use crate::arch::THREAD_PENDER;
use crate::{raw, Spawner};
- #[derive(Copy, Clone)]
- pub(crate) struct ThreadPender;
-
- impl ThreadPender {
- pub(crate) fn pend(self) {
- unsafe { core::arch::asm!("sev") }
- }
- }
-
/// Thread mode executor, using WFE/SEV.
///
/// This is the simplest and most common kind of executor. It runs on
@@ -39,7 +76,7 @@ mod thread {
/// Create a new Executor.
pub fn new() -> Self {
Self {
- inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))),
+ inner: raw::Executor::new(THREAD_PENDER as *mut ()),
not_send: PhantomData,
}
}
@@ -86,30 +123,7 @@ mod interrupt {
use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;
- use crate::raw::{self, Pender, PenderInner};
-
- #[derive(Clone, Copy)]
- pub(crate) struct InterruptPender(u16);
-
- impl InterruptPender {
- pub(crate) fn pend(self) {
- // STIR is faster, but is only available in v7 and higher.
- #[cfg(not(armv6m))]
- {
- let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) };
- nvic.request(self);
- }
-
- #[cfg(armv6m)]
- cortex_m::peripheral::NVIC::pend(self);
- }
- }
-
- unsafe impl cortex_m::interrupt::InterruptNumber for InterruptPender {
- fn number(self) -> u16 {
- self.0
- }
- }
+ use crate::raw;
/// Interrupt mode executor.
///
@@ -194,9 +208,7 @@ mod interrupt {
unsafe {
(&mut *self.executor.get())
.as_mut_ptr()
- .write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender(
- irq.number(),
- )))))
+ .write(raw::Executor::new(irq.number() as *mut ()))
}
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs
index ff7ec157..40c6877e 100644
--- a/embassy-executor/src/arch/riscv32.rs
+++ b/embassy-executor/src/arch/riscv32.rs
@@ -11,22 +11,16 @@ mod thread {
#[cfg(feature = "nightly")]
pub use embassy_macros::main_riscv as main;
- use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
- #[derive(Copy, Clone)]
- pub(crate) struct ThreadPender;
-
- impl ThreadPender {
- #[allow(unused)]
- pub(crate) fn pend(self) {
- SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst);
- }
- }
-
/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
+ #[export_name = "__pender"]
+ fn __pender(_context: *mut ()) {
+ SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
+ }
+
/// RISCV32 Executor
pub struct Executor {
inner: raw::Executor,
@@ -37,7 +31,7 @@ mod thread {
/// Create a new Executor.
pub fn new() -> Self {
Self {
- inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))),
+ inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs
index 4e4a178f..5b2f7e2e 100644
--- a/embassy-executor/src/arch/std.rs
+++ b/embassy-executor/src/arch/std.rs
@@ -11,17 +11,12 @@ mod thread {
#[cfg(feature = "nightly")]
pub use embassy_macros::main_std as main;
- use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
- #[derive(Copy, Clone)]
- pub(crate) struct ThreadPender(&'static Signaler);
-
- impl ThreadPender {
- #[allow(unused)]
- pub(crate) fn pend(self) {
- self.0.signal()
- }
+ #[export_name = "__pender"]
+ fn __pender(context: *mut ()) {
+ let signaler: &'static Signaler = unsafe { std::mem::transmute(context) };
+ signaler.signal()
}
/// Single-threaded std-based executor.
@@ -34,9 +29,9 @@ mod thread {
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
- let signaler = &*Box::leak(Box::new(Signaler::new()));
+ let signaler = Box::leak(Box::new(Signaler::new()));
Self {
- inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))),
+ inner: raw::Executor::new(signaler as *mut Signaler as *mut ()),
not_send: PhantomData,
signaler,
}
diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs
index 08ab16b9..934fd69e 100644
--- a/embassy-executor/src/arch/wasm.rs
+++ b/embassy-executor/src/arch/wasm.rs
@@ -14,14 +14,12 @@ mod thread {
use wasm_bindgen::prelude::*;
use crate::raw::util::UninitCell;
- use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
- /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
- pub struct Executor {
- inner: raw::Executor,
- ctx: &'static WasmContext,
- not_send: PhantomData<*mut ()>,
+ #[export_name = "__pender"]
+ fn __pender(context: *mut ()) {
+ let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) };
+ let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() });
}
pub(crate) struct WasmContext {
@@ -29,16 +27,6 @@ mod thread {
closure: UninitCell>,
}
- #[derive(Copy, Clone)]
- pub(crate) struct ThreadPender(&'static WasmContext);
-
- impl ThreadPender {
- #[allow(unused)]
- pub(crate) fn pend(self) {
- let _ = self.0.promise.then(unsafe { self.0.closure.as_mut() });
- }
- }
-
impl WasmContext {
pub fn new() -> Self {
Self {
@@ -48,14 +36,21 @@ mod thread {
}
}
+ /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
+ pub struct Executor {
+ inner: raw::Executor,
+ ctx: &'static WasmContext,
+ not_send: PhantomData<*mut ()>,
+ }
+
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
- let ctx = &*Box::leak(Box::new(WasmContext::new()));
+ let ctx = Box::leak(Box::new(WasmContext::new()));
Self {
- inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))),
- not_send: PhantomData,
+ inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()),
ctx,
+ not_send: PhantomData,
}
}
diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs
index 017b2c52..601d8500 100644
--- a/embassy-executor/src/arch/xtensa.rs
+++ b/embassy-executor/src/arch/xtensa.rs
@@ -8,22 +8,16 @@ mod thread {
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
- use crate::raw::{Pender, PenderInner};
use crate::{raw, Spawner};
- #[derive(Copy, Clone)]
- pub(crate) struct ThreadPender;
-
- impl ThreadPender {
- #[allow(unused)]
- pub(crate) fn pend(self) {
- SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst);
- }
- }
-
/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
+ #[export_name = "__pender"]
+ fn __pender(_context: *mut ()) {
+ SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
+ }
+
/// Xtensa Executor
pub struct Executor {
inner: raw::Executor,
@@ -34,7 +28,7 @@ mod thread {
/// Create a new Executor.
pub fn new() -> Self {
Self {
- inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))),
+ inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
@@ -77,8 +71,8 @@ mod thread {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
core::arch::asm!(
- "wsr.ps {0}",
- "rsync", in(reg) token)
+ "wsr.ps {0}",
+ "rsync", in(reg) token)
} else {
// waiti sets the PS.INTLEVEL when slipping into sleep
// because critical sections in Xtensa are implemented via increasing
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs
index f3760f58..c1d82e18 100644
--- a/embassy-executor/src/raw/mod.rs
+++ b/embassy-executor/src/raw/mod.rs
@@ -147,10 +147,7 @@ impl TaskStorage {
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken {
let task = AvailableTask::claim(self);
match task {
- Some(task) => {
- let task = task.initialize(future);
- unsafe { SpawnToken::::new(task) }
- }
+ Some(task) => task.initialize(future),
None => SpawnToken::new_failed(),
}
}
@@ -186,12 +183,16 @@ impl TaskStorage {
}
}
-struct AvailableTask {
+/// An uninitialized [`TaskStorage`].
+pub struct AvailableTask {
task: &'static TaskStorage,
}
impl AvailableTask {
- fn claim(task: &'static TaskStorage) -> Option {
+ /// Try to claim a [`TaskStorage`].
+ ///
+ /// This function returns `None` if a task has already been spawned and has not finished running.
+ pub fn claim(task: &'static TaskStorage) -> Option {
task.raw
.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
@@ -199,61 +200,30 @@ impl AvailableTask {
.map(|_| Self { task })
}
- fn initialize(self, future: impl FnOnce() -> F) -> TaskRef {
+ fn initialize_impl(self, future: impl FnOnce() -> F) -> SpawnToken {
unsafe {
self.task.raw.poll_fn.set(Some(TaskStorage::::poll));
self.task.future.write(future());
- }
- TaskRef::new(self.task)
- }
-}
-/// Raw storage that can hold up to N tasks of the same type.
-///
-/// This is essentially a `[TaskStorage; N]`.
-pub struct TaskPool {
- pool: [TaskStorage; N],
-}
+ let task = TaskRef::new(self.task);
-impl TaskPool {
- /// Create a new TaskPool, with all tasks in non-spawned state.
- pub const fn new() -> Self {
- Self {
- pool: [TaskStorage::NEW; N],
+ SpawnToken::new(task)
}
}
- /// Try to spawn a task in the pool.
- ///
- /// See [`TaskStorage::spawn()`] for details.
- ///
- /// This will loop over the pool and spawn the task in the first storage that
- /// is currently free. If none is free, a "poisoned" SpawnToken is returned,
- /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
- pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken {
- let task = self.pool.iter().find_map(AvailableTask::claim);
- match task {
- Some(task) => {
- let task = task.initialize(future);
- unsafe { SpawnToken::::new(task) }
- }
- None => SpawnToken::new_failed(),
- }
+ /// Initialize the [`TaskStorage`] to run the given future.
+ pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken {
+ self.initialize_impl::(future)
}
- /// Like spawn(), but allows the task to be send-spawned if the args are Send even if
- /// the future is !Send.
+ /// Initialize the [`TaskStorage`] to run the given future.
///
- /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used
- /// by the Embassy macros ONLY.
+ /// # Safety
///
- /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
+ /// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
/// is an `async fn`, NOT a hand-written `Future`.
#[doc(hidden)]
- pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken
- where
- FutFn: FnOnce() -> F,
- {
+ pub unsafe fn __initialize_async_fn(self, future: impl FnOnce() -> F) -> SpawnToken {
// When send-spawning a task, we construct the future in this thread, and effectively
// "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory,
// send-spawning should require the future `F` to be `Send`.
@@ -279,66 +249,73 @@ impl TaskPool {
//
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`.
+ self.initialize_impl::(future)
+ }
+}
- let task = self.pool.iter().find_map(AvailableTask::claim);
- match task {
- Some(task) => {
- let task = task.initialize(future);
- unsafe { SpawnToken::::new(task) }
- }
+/// Raw storage that can hold up to N tasks of the same type.
+///
+/// This is essentially a `[TaskStorage; N]`.
+pub struct TaskPool {
+ pool: [TaskStorage; N],
+}
+
+impl TaskPool {
+ /// Create a new TaskPool, with all tasks in non-spawned state.
+ pub const fn new() -> Self {
+ Self {
+ pool: [TaskStorage::NEW; N],
+ }
+ }
+
+ fn spawn_impl(&'static self, future: impl FnOnce() -> F) -> SpawnToken {
+ match self.pool.iter().find_map(AvailableTask::claim) {
+ Some(task) => task.initialize_impl::(future),
None => SpawnToken::new_failed(),
}
}
+
+ /// Try to spawn a task in the pool.
+ ///
+ /// See [`TaskStorage::spawn()`] for details.
+ ///
+ /// This will loop over the pool and spawn the task in the first storage that
+ /// is currently free. If none is free, a "poisoned" SpawnToken is returned,
+ /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
+ pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken {
+ self.spawn_impl::(future)
+ }
+
+ /// Like spawn(), but allows the task to be send-spawned if the args are Send even if
+ /// the future is !Send.
+ ///
+ /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used
+ /// by the Embassy macros ONLY.
+ ///
+ /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
+ /// is an `async fn`, NOT a hand-written `Future`.
+ #[doc(hidden)]
+ pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken
+ where
+ FutFn: FnOnce() -> F,
+ {
+ // See the comment in AvailableTask::__initialize_async_fn for explanation.
+ self.spawn_impl::(future)
+ }
}
#[derive(Clone, Copy)]
-pub(crate) enum PenderInner {
- #[cfg(feature = "executor-thread")]
- Thread(crate::arch::ThreadPender),
- #[cfg(feature = "executor-interrupt")]
- Interrupt(crate::arch::InterruptPender),
- #[cfg(feature = "pender-callback")]
- Callback { func: fn(*mut ()), context: *mut () },
-}
+pub(crate) struct Pender(*mut ());
-unsafe impl Send for PenderInner {}
-unsafe impl Sync for PenderInner {}
-
-/// Platform/architecture-specific action executed when an executor has pending work.
-///
-/// When a task within an executor is woken, the `Pender` is called. This does a
-/// platform/architecture-specific action to signal there is pending work in the executor.
-/// When this happens, you must arrange for [`Executor::poll`] to be called.
-///
-/// You can think of it as a waker, but for the whole executor.
-pub struct Pender(pub(crate) PenderInner);
+unsafe impl Send for Pender {}
+unsafe impl Sync for Pender {}
impl Pender {
- /// Create a `Pender` that will call an arbitrary function pointer.
- ///
- /// # Arguments
- ///
- /// - `func`: The function pointer to call.
- /// - `context`: Opaque context pointer, that will be passed to the function pointer.
- #[cfg(feature = "pender-callback")]
- pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self {
- Self(PenderInner::Callback {
- func,
- context: context.into(),
- })
- }
-}
-
-impl Pender {
- pub(crate) fn pend(&self) {
- match self.0 {
- #[cfg(feature = "executor-thread")]
- PenderInner::Thread(x) => x.pend(),
- #[cfg(feature = "executor-interrupt")]
- PenderInner::Interrupt(x) => x.pend(),
- #[cfg(feature = "pender-callback")]
- PenderInner::Callback { func, context } => func(context),
+ pub(crate) fn pend(self) {
+ extern "Rust" {
+ fn __pender(context: *mut ());
}
+ unsafe { __pender(self.0) };
}
}
@@ -409,7 +386,7 @@ impl SyncExecutor {
#[allow(clippy::never_loop)]
loop {
#[cfg(feature = "integrated-timers")]
- self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task));
+ self.timer_queue.dequeue_expired(Instant::now(), wake_task_no_pend);
self.run_queue.dequeue_all(|p| {
let task = p.header();
@@ -472,15 +449,31 @@ impl SyncExecutor {
///
/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks
/// that "want to run").
-/// - You must supply a [`Pender`]. The executor will call it to notify you it has work
-/// to do. You must arrange for `poll()` to be called as soon as possible.
+/// - You must supply a pender function, as shown below. The executor will call it to notify you
+/// it has work to do. You must arrange for `poll()` to be called as soon as possible.
+/// - Enabling `arch-xx` features will define a pender function for you. This means that you
+/// are limited to using the executors provided to you by the architecture/platform
+/// implementation. If you need a different executor, you must not enable `arch-xx` features.
///
-/// The [`Pender`] can be called from *any* context: any thread, any interrupt priority
+/// The pender can be called from *any* context: any thread, any interrupt priority
/// level, etc. It may be called synchronously from any `Executor` method call as well.
/// You must deal with this correctly.
///
/// In particular, you must NOT call `poll` directly from the pender callback, as this violates
/// the requirement for `poll` to not be called reentrantly.
+///
+/// The pender function must be exported with the name `__pender` and have the following signature:
+///
+/// ```rust
+/// #[export_name = "__pender"]
+/// fn pender(context: *mut ()) {
+/// // schedule `poll()` to be called
+/// }
+/// ```
+///
+/// The `context` argument is a piece of arbitrary data the executor will pass to the pender.
+/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example,
+/// differentiate between executors, or to pass a pointer to a callback that should be called.
#[repr(transparent)]
pub struct Executor {
pub(crate) inner: SyncExecutor,
@@ -495,12 +488,12 @@ impl Executor {
/// Create a new executor.
///
- /// When the executor has work to do, it will call the [`Pender`].
+ /// When the executor has work to do, it will call the pender function and pass `context` to it.
///
- /// See [`Executor`] docs for details on `Pender`.
- pub fn new(pender: Pender) -> Self {
+ /// See [`Executor`] docs for details on the pender.
+ pub fn new(context: *mut ()) -> Self {
Self {
- inner: SyncExecutor::new(pender),
+ inner: SyncExecutor::new(Pender(context)),
_not_sync: PhantomData,
}
}
@@ -523,16 +516,16 @@ impl Executor {
/// This loops over all tasks that are queued to be polled (i.e. they're
/// freshly spawned or they've been woken). Other tasks are not polled.
///
- /// You must call `poll` after receiving a call to the [`Pender`]. It is OK
- /// to call `poll` even when not requested by the `Pender`, but it wastes
+ /// You must call `poll` after receiving a call to the pender. It is OK
+ /// to call `poll` even when not requested by the pender, but it wastes
/// energy.
///
/// # Safety
///
/// You must NOT call `poll` reentrantly on the same executor.
///
- /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you
- /// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to
+ /// In particular, note that `poll` may call the pender synchronously. Therefore, you
+ /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
/// no `poll()` already running.
pub unsafe fn poll(&'static self) {
@@ -573,6 +566,31 @@ pub fn wake_task(task: TaskRef) {
}
}
+/// Wake a task by `TaskRef` without calling pend.
+///
+/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
+pub fn wake_task_no_pend(task: TaskRef) {
+ let header = task.header();
+
+ let res = header.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)
+ }
+ });
+
+ if res.is_ok() {
+ // We have just marked the task as scheduled, so enqueue it.
+ unsafe {
+ let executor = header.executor.get().unwrap_unchecked();
+ executor.run_queue.enqueue(task);
+ }
+ }
+}
+
#[cfg(feature = "integrated-timers")]
struct TimerQueue;
diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs
index 2b622404..5a3a0dee 100644
--- a/embassy-executor/src/spawner.rs
+++ b/embassy-executor/src/spawner.rs
@@ -33,7 +33,8 @@ impl SpawnToken {
}
}
- pub(crate) fn new_failed() -> Self {
+ /// Return a SpawnToken that represents a failed spawn.
+ pub fn new_failed() -> Self {
Self {
raw_task: None,
phantom: PhantomData,
diff --git a/embassy-hal-common/Cargo.toml b/embassy-hal-internal/Cargo.toml
similarity index 95%
rename from embassy-hal-common/Cargo.toml
rename to embassy-hal-internal/Cargo.toml
index 18c758d7..42e03199 100644
--- a/embassy-hal-common/Cargo.toml
+++ b/embassy-hal-internal/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "embassy-hal-common"
+name = "embassy-hal-internal"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
diff --git a/embassy-hal-internal/README.md b/embassy-hal-internal/README.md
new file mode 100644
index 00000000..d6539701
--- /dev/null
+++ b/embassy-hal-internal/README.md
@@ -0,0 +1,16 @@
+# embassy-macros
+
+An [Embassy](https://embassy.dev) project.
+
+Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY. Embassy HALs (`embassy-nrf`, `embassy-stm32`, `embassy-rp`) already reexport
+everything you need to use them effectively.
+
+## License
+
+This work is licensed under either of
+
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+ )
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
+
+at your option.
diff --git a/embassy-hal-common/build.rs b/embassy-hal-internal/build.rs
similarity index 100%
rename from embassy-hal-common/build.rs
rename to embassy-hal-internal/build.rs
diff --git a/embassy-hal-common/src/atomic_ring_buffer.rs b/embassy-hal-internal/src/atomic_ring_buffer.rs
similarity index 100%
rename from embassy-hal-common/src/atomic_ring_buffer.rs
rename to embassy-hal-internal/src/atomic_ring_buffer.rs
diff --git a/embassy-hal-common/src/drop.rs b/embassy-hal-internal/src/drop.rs
similarity index 100%
rename from embassy-hal-common/src/drop.rs
rename to embassy-hal-internal/src/drop.rs
diff --git a/embassy-hal-common/src/fmt.rs b/embassy-hal-internal/src/fmt.rs
similarity index 100%
rename from embassy-hal-common/src/fmt.rs
rename to embassy-hal-internal/src/fmt.rs
diff --git a/embassy-hal-common/src/interrupt.rs b/embassy-hal-internal/src/interrupt.rs
similarity index 100%
rename from embassy-hal-common/src/interrupt.rs
rename to embassy-hal-internal/src/interrupt.rs
diff --git a/embassy-hal-common/src/lib.rs b/embassy-hal-internal/src/lib.rs
similarity index 89%
rename from embassy-hal-common/src/lib.rs
rename to embassy-hal-internal/src/lib.rs
index 235964aa..3640ea18 100644
--- a/embassy-hal-common/src/lib.rs
+++ b/embassy-hal-internal/src/lib.rs
@@ -1,5 +1,6 @@
#![no_std]
#![allow(clippy::new_without_default)]
+#![doc = include_str!("../README.md")]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
diff --git a/embassy-hal-common/src/macros.rs b/embassy-hal-internal/src/macros.rs
similarity index 98%
rename from embassy-hal-common/src/macros.rs
rename to embassy-hal-internal/src/macros.rs
index f06b4600..0eea4b66 100644
--- a/embassy-hal-common/src/macros.rs
+++ b/embassy-hal-internal/src/macros.rs
@@ -116,6 +116,7 @@ macro_rules! impl_peripheral {
#[inline]
unsafe fn clone_unchecked(&self) -> Self::P {
+ #[allow(clippy::needless_update)]
$type { ..*self }
}
}
diff --git a/embassy-hal-common/src/peripheral.rs b/embassy-hal-internal/src/peripheral.rs
similarity index 100%
rename from embassy-hal-common/src/peripheral.rs
rename to embassy-hal-internal/src/peripheral.rs
diff --git a/embassy-hal-common/src/ratio.rs b/embassy-hal-internal/src/ratio.rs
similarity index 100%
rename from embassy-hal-common/src/ratio.rs
rename to embassy-hal-internal/src/ratio.rs
diff --git a/embassy-hal-common/src/ring_buffer.rs b/embassy-hal-internal/src/ring_buffer.rs
similarity index 100%
rename from embassy-hal-common/src/ring_buffer.rs
rename to embassy-hal-internal/src/ring_buffer.rs
diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml
index e4524af5..feea0658 100644
--- a/embassy-lora/Cargo.toml
+++ b/embassy-lora/Cargo.toml
@@ -12,7 +12,7 @@ target = "thumbv7em-none-eabi"
[features]
stm32wl = ["dep:embassy-stm32"]
-time = []
+time = ["embassy-time", "lorawan-device"]
defmt = ["dep:defmt", "lorawan-device/defmt"]
[dependencies]
@@ -20,18 +20,15 @@ defmt = ["dep:defmt", "lorawan-device/defmt"]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
-embassy-time = { version = "0.1.2", path = "../embassy-time" }
+embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
-embedded-hal-async = { version = "=0.2.0-alpha.2" }
-embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false }
-futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
+embedded-hal-async = { version = "=1.0.0-rc.1" }
embedded-hal = { version = "0.2", features = ["unproven"] }
-bit_field = { version = "0.10" }
+futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
lora-phy = { version = "1" }
-lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
+lorawan-device = { version = "0.10.0", default-features = false, features = ["async"], optional = true }
[patch.crates-io]
-lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" }
+lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"}
\ No newline at end of file
diff --git a/embassy-net-driver-channel/README.md b/embassy-net-driver-channel/README.md
index dd90e7ad..8f904ce9 100644
--- a/embassy-net-driver-channel/README.md
+++ b/embassy-net-driver-channel/README.md
@@ -76,7 +76,7 @@ These `embassy-net` drivers are implemented using this crate. You can look at th
- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W
- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support.
-- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip.
+- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips.
- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU.
diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs
index 02a4c00d..076238ba 100644
--- a/embassy-net-driver-channel/src/lib.rs
+++ b/embassy-net-driver-channel/src/lib.rs
@@ -42,7 +42,7 @@ struct StateInner<'d, const MTU: usize> {
struct Shared {
link_state: LinkState,
waker: WakerRegistration,
- ethernet_address: [u8; 6],
+ hardware_address: driver::HardwareAddress,
}
pub struct Runner<'d, const MTU: usize> {
@@ -85,10 +85,10 @@ impl<'d, const MTU: usize> Runner<'d, MTU> {
});
}
- pub fn set_ethernet_address(&mut self, address: [u8; 6]) {
+ pub fn set_hardware_address(&mut self, address: driver::HardwareAddress) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
- s.ethernet_address = address;
+ s.hardware_address = address;
s.waker.wake();
});
}
@@ -150,7 +150,15 @@ impl<'d> StateRunner<'d> {
pub fn set_ethernet_address(&self, address: [u8; 6]) {
self.shared.lock(|s| {
let s = &mut *s.borrow_mut();
- s.ethernet_address = address;
+ s.hardware_address = driver::HardwareAddress::Ethernet(address);
+ s.waker.wake();
+ });
+ }
+
+ pub fn set_ieee802154_address(&self, address: [u8; 8]) {
+ self.shared.lock(|s| {
+ let s = &mut *s.borrow_mut();
+ s.hardware_address = driver::HardwareAddress::Ieee802154(address);
s.waker.wake();
});
}
@@ -206,7 +214,7 @@ impl<'d, const MTU: usize> TxRunner<'d, MTU> {
pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
state: &'d mut State,
- ethernet_address: [u8; 6],
+ hardware_address: driver::HardwareAddress,
) -> (Runner<'d, MTU>, Device<'d, MTU>) {
let mut caps = Capabilities::default();
caps.max_transmission_unit = MTU;
@@ -222,7 +230,7 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>(
tx: zerocopy_channel::Channel::new(&mut state.tx[..]),
shared: Mutex::new(RefCell::new(Shared {
link_state: LinkState::Down,
- ethernet_address,
+ hardware_address,
waker: WakerRegistration::new(),
})),
});
@@ -289,8 +297,8 @@ impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> {
self.caps.clone()
}
- fn ethernet_address(&self) -> [u8; 6] {
- self.shared.lock(|s| s.borrow().ethernet_address)
+ fn hardware_address(&self) -> driver::HardwareAddress {
+ self.shared.lock(|s| s.borrow().hardware_address)
}
fn link_state(&mut self, cx: &mut Context) -> LinkState {
diff --git a/embassy-net-driver/Cargo.toml b/embassy-net-driver/Cargo.toml
index da6d9ad6..e25950b6 100644
--- a/embassy-net-driver/Cargo.toml
+++ b/embassy-net-driver/Cargo.toml
@@ -21,4 +21,4 @@ target = "thumbv7em-none-eabi"
features = ["defmt"]
[dependencies]
-defmt = { version = "0.3", optional = true }
\ No newline at end of file
+defmt = { version = "0.3", optional = true }
diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs
index 4149bf4a..b64c1000 100644
--- a/embassy-net-driver/src/lib.rs
+++ b/embassy-net-driver/src/lib.rs
@@ -4,6 +4,18 @@
use core::task::Context;
+/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum HardwareAddress {
+ /// A six-octet Ethernet address
+ Ethernet([u8; 6]),
+ /// An eight-octet IEEE802.15.4 address
+ Ieee802154([u8; 8]),
+ /// Indicates that a Driver is IP-native, and has no hardware address
+ Ip,
+}
+
/// Main `embassy-net` driver API.
///
/// This is essentially an interface for sending and receiving raw network frames.
@@ -51,8 +63,8 @@ pub trait Driver {
/// Get a description of device capabilities.
fn capabilities(&self) -> Capabilities;
- /// Get the device's Ethernet address.
- fn ethernet_address(&self) -> [u8; 6];
+ /// Get the device's hardware address.
+ fn hardware_address(&self) -> HardwareAddress;
}
impl Driver for &mut T {
@@ -75,8 +87,8 @@ impl Driver for &mut T {
fn link_state(&mut self, cx: &mut Context) -> LinkState {
T::link_state(self, cx)
}
- fn ethernet_address(&self) -> [u8; 6] {
- T::ethernet_address(self)
+ fn hardware_address(&self) -> HardwareAddress {
+ T::hardware_address(self)
}
}
@@ -164,6 +176,9 @@ pub enum Medium {
///
/// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
Ip,
+
+ /// IEEE 802_15_4 medium
+ Ieee802154,
}
impl Default for Medium {
diff --git a/embassy-net-enc28j60/Cargo.toml b/embassy-net-enc28j60/Cargo.toml
new file mode 100644
index 00000000..e02c984e
--- /dev/null
+++ b/embassy-net-enc28j60/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "embassy-net-enc28j60"
+version = "0.1.0"
+description = "embassy-net driver for the ENC28J60 ethernet chip"
+keywords = ["embedded", "enc28j60", "embassy-net", "embedded-hal-async", "ethernet", "async"]
+categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
+license = "MIT OR Apache-2.0"
+edition = "2021"
+
+[dependencies]
+embedded-hal = { version = "1.0.0-rc.1" }
+embedded-hal-async = { version = "=1.0.0-rc.1" }
+embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
+embassy-time = { version = "0.1.2", path = "../embassy-time" }
+embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
+
+defmt = { version = "0.3", optional = true }
+log = { version = "0.4.14", optional = true }
+
+[package.metadata.embassy_docs]
+src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-enc28j60-v$VERSION/embassy-net-enc28j60/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-enc28j60/src/"
+target = "thumbv7em-none-eabi"
\ No newline at end of file
diff --git a/embassy-net-enc28j60/README.md b/embassy-net-enc28j60/README.md
new file mode 100644
index 00000000..39011ca1
--- /dev/null
+++ b/embassy-net-enc28j60/README.md
@@ -0,0 +1,19 @@
+# `embassy-net-enc28j60`
+
+[`embassy-net`](https://crates.io/crates/embassy-net) integration for the Microchip ENC28J60 Ethernet chip.
+
+Based on [@japaric](https://github.com/japaric)'s [`enc28j60`](https://github.com/japaric/enc28j60) crate.
+
+## Interoperability
+
+This crate can run on any executor.
+
+## License
+
+This work is licensed under either of
+
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+ http://www.apache.org/licenses/LICENSE-2.0)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
diff --git a/embassy-net-enc28j60/src/bank0.rs b/embassy-net-enc28j60/src/bank0.rs
new file mode 100644
index 00000000..1c1b3a7f
--- /dev/null
+++ b/embassy-net-enc28j60/src/bank0.rs
@@ -0,0 +1,69 @@
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum Register {
+ ERDPTL = 0x00,
+ ERDPTH = 0x01,
+ EWRPTL = 0x02,
+ EWRPTH = 0x03,
+ ETXSTL = 0x04,
+ ETXSTH = 0x05,
+ ETXNDL = 0x06,
+ ETXNDH = 0x07,
+ ERXSTL = 0x08,
+ ERXSTH = 0x09,
+ ERXNDL = 0x0a,
+ ERXNDH = 0x0b,
+ ERXRDPTL = 0x0c,
+ ERXRDPTH = 0x0d,
+ ERXWRPTL = 0x0e,
+ ERXWRPTH = 0x0f,
+ EDMASTL = 0x10,
+ EDMASTH = 0x11,
+ EDMANDL = 0x12,
+ EDMANDH = 0x13,
+ EDMADSTL = 0x14,
+ EDMADSTH = 0x15,
+ EDMACSL = 0x16,
+ EDMACSH = 0x17,
+}
+
+impl Register {
+ pub(crate) fn addr(&self) -> u8 {
+ *self as u8
+ }
+
+ pub(crate) fn is_eth_register(&self) -> bool {
+ match *self {
+ Register::ERDPTL => true,
+ Register::ERDPTH => true,
+ Register::EWRPTL => true,
+ Register::EWRPTH => true,
+ Register::ETXSTL => true,
+ Register::ETXSTH => true,
+ Register::ETXNDL => true,
+ Register::ETXNDH => true,
+ Register::ERXSTL => true,
+ Register::ERXSTH => true,
+ Register::ERXNDL => true,
+ Register::ERXNDH => true,
+ Register::ERXRDPTL => true,
+ Register::ERXRDPTH => true,
+ Register::ERXWRPTL => true,
+ Register::ERXWRPTH => true,
+ Register::EDMASTL => true,
+ Register::EDMASTH => true,
+ Register::EDMANDL => true,
+ Register::EDMANDH => true,
+ Register::EDMADSTL => true,
+ Register::EDMADSTH => true,
+ Register::EDMACSL => true,
+ Register::EDMACSH => true,
+ }
+ }
+}
+
+impl Into for Register {
+ fn into(self) -> super::Register {
+ super::Register::Bank0(self)
+ }
+}
diff --git a/embassy-net-enc28j60/src/bank1.rs b/embassy-net-enc28j60/src/bank1.rs
new file mode 100644
index 00000000..30560edf
--- /dev/null
+++ b/embassy-net-enc28j60/src/bank1.rs
@@ -0,0 +1,84 @@
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum Register {
+ EHT0 = 0x00,
+ EHT1 = 0x01,
+ EHT2 = 0x02,
+ EHT3 = 0x03,
+ EHT4 = 0x04,
+ EHT5 = 0x05,
+ EHT6 = 0x06,
+ EHT7 = 0x07,
+ EPMM0 = 0x08,
+ EPMM1 = 0x09,
+ EPMM2 = 0x0a,
+ EPMM3 = 0x0b,
+ EPMM4 = 0x0c,
+ EPMM5 = 0x0d,
+ EPMM6 = 0x0e,
+ EPMM7 = 0x0f,
+ EPMCSL = 0x10,
+ EPMCSH = 0x11,
+ EPMOL = 0x14,
+ EPMOH = 0x15,
+ ERXFCON = 0x18,
+ EPKTCNT = 0x19,
+}
+
+impl Register {
+ pub(crate) fn addr(&self) -> u8 {
+ *self as u8
+ }
+
+ pub(crate) fn is_eth_register(&self) -> bool {
+ match *self {
+ Register::EHT0 => true,
+ Register::EHT1 => true,
+ Register::EHT2 => true,
+ Register::EHT3 => true,
+ Register::EHT4 => true,
+ Register::EHT5 => true,
+ Register::EHT6 => true,
+ Register::EHT7 => true,
+ Register::EPMM0 => true,
+ Register::EPMM1 => true,
+ Register::EPMM2 => true,
+ Register::EPMM3 => true,
+ Register::EPMM4 => true,
+ Register::EPMM5 => true,
+ Register::EPMM6 => true,
+ Register::EPMM7 => true,
+ Register::EPMCSL => true,
+ Register::EPMCSH => true,
+ Register::EPMOL => true,
+ Register::EPMOH => true,
+ Register::ERXFCON => true,
+ Register::EPKTCNT => true,
+ }
+ }
+}
+
+impl Into for Register {
+ fn into(self) -> super::Register {
+ super::Register::Bank1(self)
+ }
+}
+
+register!(ERXFCON, 0b1010_0001, u8, {
+ #[doc = "Broadcast Filter Enable bit"]
+ bcen @ 0,
+ #[doc = "Multicast Filter Enable bit"]
+ mcen @ 1,
+ #[doc = "Hash Table Filter Enable bit"]
+ hten @ 2,
+ #[doc = "Magic Packetâ„¢ Filter Enable bit"]
+ mpen @ 3,
+ #[doc = "Pattern Match Filter Enable bit"]
+ pmen @ 4,
+ #[doc = "Post-Filter CRC Check Enable bit"]
+ crcen @ 5,
+ #[doc = "AND/OR Filter Select bit"]
+ andor @ 6,
+ #[doc = "Unicast Filter Enable bit"]
+ ucen @ 7,
+});
diff --git a/embassy-net-enc28j60/src/bank2.rs b/embassy-net-enc28j60/src/bank2.rs
new file mode 100644
index 00000000..74a1d245
--- /dev/null
+++ b/embassy-net-enc28j60/src/bank2.rs
@@ -0,0 +1,86 @@
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum Register {
+ MACON1 = 0x00,
+ MACON3 = 0x02,
+ MACON4 = 0x03,
+ MABBIPG = 0x04,
+ MAIPGL = 0x06,
+ MAIPGH = 0x07,
+ MACLCON1 = 0x08,
+ MACLCON2 = 0x09,
+ MAMXFLL = 0x0a,
+ MAMXFLH = 0x0b,
+ MICMD = 0x12,
+ MIREGADR = 0x14,
+ MIWRL = 0x16,
+ MIWRH = 0x17,
+ MIRDL = 0x18,
+ MIRDH = 0x19,
+}
+
+impl Register {
+ pub(crate) fn addr(&self) -> u8 {
+ *self as u8
+ }
+
+ pub(crate) fn is_eth_register(&self) -> bool {
+ match *self {
+ Register::MACON1 => false,
+ Register::MACON3 => false,
+ Register::MACON4 => false,
+ Register::MABBIPG => false,
+ Register::MAIPGL => false,
+ Register::MAIPGH => false,
+ Register::MACLCON1 => false,
+ Register::MACLCON2 => false,
+ Register::MAMXFLL => false,
+ Register::MAMXFLH => false,
+ Register::MICMD => false,
+ Register::MIREGADR => false,
+ Register::MIWRL => false,
+ Register::MIWRH => false,
+ Register::MIRDL => false,
+ Register::MIRDH => false,
+ }
+ }
+}
+
+impl Into for Register {
+ fn into(self) -> super::Register {
+ super::Register::Bank2(self)
+ }
+}
+
+register!(MACON1, 0, u8, {
+ #[doc = "Enable packets to be received by the MAC"]
+ marxen @ 0,
+ #[doc = "Control frames will be discarded after being processed by the MAC"]
+ passall @ 1,
+ #[doc = "Inhibit transmissions when pause control frames are received"]
+ rxpaus @ 2,
+ #[doc = "Allow the MAC to transmit pause control frames"]
+ txpaus @ 3,
+});
+
+register!(MACON3, 0, u8, {
+ #[doc = "MAC will operate in Full-Duplex mode"]
+ fuldpx @ 0,
+ #[doc = "The type/length field of transmitted and received frames will be checked"]
+ frmlnen @ 1,
+ #[doc = "Frames bigger than MAMXFL will be aborted when transmitted or received"]
+ hfrmen @ 2,
+ #[doc = "No proprietary header is present"]
+ phdren @ 3,
+ #[doc = "MAC will append a valid CRC to all frames transmitted regardless of PADCFG bit"]
+ txcrcen @ 4,
+ #[doc = "All short frames will be zero-padded to 64 bytes and a valid CRC will then be appended"]
+ padcfg @ 5..7,
+});
+
+register!(MICMD, 0, u8, {
+ #[doc = "MII Read Enable bit"]
+ miird @ 0,
+ #[doc = "MII Scan Enable bit"]
+ miiscan @ 1,
+});
diff --git a/embassy-net-enc28j60/src/bank3.rs b/embassy-net-enc28j60/src/bank3.rs
new file mode 100644
index 00000000..4f7eb940
--- /dev/null
+++ b/embassy-net-enc28j60/src/bank3.rs
@@ -0,0 +1,53 @@
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum Register {
+ MAADR5 = 0x00,
+ MAADR6 = 0x01,
+ MAADR3 = 0x02,
+ MAADR4 = 0x03,
+ MAADR1 = 0x04,
+ MAADR2 = 0x05,
+ EBSTSD = 0x06,
+ EBSTCON = 0x07,
+ EBSTCSL = 0x08,
+ EBSTCSH = 0x09,
+ MISTAT = 0x0a,
+ EREVID = 0x12,
+ ECOCON = 0x15,
+ EFLOCON = 0x17,
+ EPAUSL = 0x18,
+ EPAUSH = 0x19,
+}
+
+impl Register {
+ pub(crate) fn addr(&self) -> u8 {
+ *self as u8
+ }
+
+ pub(crate) fn is_eth_register(&self) -> bool {
+ match *self {
+ Register::MAADR5 => false,
+ Register::MAADR6 => false,
+ Register::MAADR3 => false,
+ Register::MAADR4 => false,
+ Register::MAADR1 => false,
+ Register::MAADR2 => false,
+ Register::EBSTSD => true,
+ Register::EBSTCON => true,
+ Register::EBSTCSL => true,
+ Register::EBSTCSH => true,
+ Register::MISTAT => false,
+ Register::EREVID => true,
+ Register::ECOCON => true,
+ Register::EFLOCON => true,
+ Register::EPAUSL => true,
+ Register::EPAUSH => true,
+ }
+ }
+}
+
+impl Into for Register {
+ fn into(self) -> super::Register {
+ super::Register::Bank3(self)
+ }
+}
diff --git a/embassy-net-enc28j60/src/common.rs b/embassy-net-enc28j60/src/common.rs
new file mode 100644
index 00000000..ef339dd2
--- /dev/null
+++ b/embassy-net-enc28j60/src/common.rs
@@ -0,0 +1,106 @@
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum Register {
+ ECON1 = 0x1f,
+ ECON2 = 0x1e,
+ EIE = 0x1b,
+ EIR = 0x1c,
+ ESTAT = 0x1d,
+}
+
+impl Register {
+ pub(crate) fn addr(&self) -> u8 {
+ *self as u8
+ }
+
+ pub(crate) fn is_eth_register(&self) -> bool {
+ match *self {
+ Register::ECON1 => true,
+ Register::ECON2 => true,
+ Register::EIE => true,
+ Register::EIR => true,
+ Register::ESTAT => true,
+ }
+ }
+}
+
+impl Into for Register {
+ fn into(self) -> super::Register {
+ super::Register::Common(self)
+ }
+}
+
+register!(EIE, 0, u8, {
+ #[doc = "Receive Error Interrupt Enable bit"]
+ rxerie @ 0,
+ #[doc = "Transmit Error Interrupt Enable bit"]
+ txerie @ 1,
+ #[doc = "Transmit Enable bit"]
+ txie @ 3,
+ #[doc = "Link Status Change Interrupt Enable bit"]
+ linkie @ 4,
+ #[doc = "DMA Interrupt Enable bit"]
+ dmaie @ 5,
+ #[doc = "Receive Packet Pending Interrupt Enable bit"]
+ pktie @ 6,
+ #[doc = "Global INT Interrupt Enable bit"]
+ intie @ 7,
+});
+
+register!(EIR, 0, u8, {
+ #[doc = "Receive Error Interrupt Flag bit"]
+ rxerif @ 0,
+ #[doc = "Transmit Error Interrupt Flag bit"]
+ txerif @ 1,
+ #[doc = "Transmit Interrupt Flag bit"]
+ txif @ 3,
+ #[doc = "Link Change Interrupt Flag bit"]
+ linkif @ 4,
+ #[doc = "DMA Interrupt Flag bit"]
+ dmaif @ 5,
+ #[doc = "Receive Packet Pending Interrupt Flag bit"]
+ pktif @ 6,
+});
+
+register!(ESTAT, 0, u8, {
+ #[doc = "Clock Ready bit"]
+ clkrdy @ 0,
+ #[doc = "Transmit Abort Error bit"]
+ txabrt @ 1,
+ #[doc = "Receive Busy bit"]
+ rxbusy @ 2,
+ #[doc = "Late Collision Error bit"]
+ latecol @ 4,
+ #[doc = "Ethernet Buffer Error Status bit"]
+ bufer @ 6,
+ #[doc = "INT Interrupt Flag bit"]
+ int @ 7,
+});
+
+register!(ECON2, 0b1000_0000, u8, {
+ #[doc = "Voltage Regulator Power Save Enable bit"]
+ vrps @ 3,
+ #[doc = "Power Save Enable bit"]
+ pwrsv @ 5,
+ #[doc = "Packet Decrement bit"]
+ pktdec @ 6,
+ #[doc = "Automatic Buffer Pointer Increment Enable bit"]
+ autoinc @ 7,
+});
+
+register!(ECON1, 0, u8, {
+ #[doc = "Bank Select bits"]
+ bsel @ 0..1,
+ #[doc = "Receive Enable bi"]
+ rxen @ 2,
+ #[doc = "Transmit Request to Send bit"]
+ txrts @ 3,
+ #[doc = "DMA Checksum Enable bit"]
+ csumen @ 4,
+ #[doc = "DMA Start and Busy Status bit"]
+ dmast @ 5,
+ #[doc = "Receive Logic Reset bit"]
+ rxrst @ 6,
+ #[doc = "Transmit Logic Reset bit"]
+ txrst @ 7,
+});
diff --git a/embassy-net-enc28j60/src/fmt.rs b/embassy-net-enc28j60/src/fmt.rs
new file mode 100644
index 00000000..06697081
--- /dev/null
+++ b/embassy-net-enc28j60/src/fmt.rs
@@ -0,0 +1,225 @@
+#![macro_use]
+#![allow(unused_macros)]
+
+#[cfg(all(feature = "defmt", feature = "log"))]
+compile_error!("You may not enable both `defmt` and `log` features.");
+
+macro_rules! assert {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::assert!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::assert!($($x)*);
+ }
+ };
+}
+
+macro_rules! assert_eq {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::assert_eq!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::assert_eq!($($x)*);
+ }
+ };
+}
+
+macro_rules! assert_ne {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::assert_ne!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::assert_ne!($($x)*);
+ }
+ };
+}
+
+macro_rules! debug_assert {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::debug_assert!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug_assert!($($x)*);
+ }
+ };
+}
+
+macro_rules! debug_assert_eq {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::debug_assert_eq!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug_assert_eq!($($x)*);
+ }
+ };
+}
+
+macro_rules! debug_assert_ne {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::debug_assert_ne!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug_assert_ne!($($x)*);
+ }
+ };
+}
+
+macro_rules! todo {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::todo!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::todo!($($x)*);
+ }
+ };
+}
+
+macro_rules! unreachable {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::unreachable!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::unreachable!($($x)*);
+ }
+ };
+}
+
+macro_rules! panic {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::panic!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::panic!($($x)*);
+ }
+ };
+}
+
+macro_rules! trace {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::trace!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::trace!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! debug {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::debug!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! info {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::info!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::info!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! warn {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::warn!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::warn!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! error {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::error!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::error!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+#[cfg(feature = "defmt")]
+macro_rules! unwrap {
+ ($($x:tt)*) => {
+ ::defmt::unwrap!($($x)*)
+ };
+}
+
+#[cfg(not(feature = "defmt"))]
+macro_rules! unwrap {
+ ($arg:expr) => {
+ match $crate::fmt::Try::into_result($arg) {
+ ::core::result::Result::Ok(t) => t,
+ ::core::result::Result::Err(e) => {
+ ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
+ }
+ }
+ };
+ ($arg:expr, $($msg:expr),+ $(,)? ) => {
+ match $crate::fmt::Try::into_result($arg) {
+ ::core::result::Result::Ok(t) => t,
+ ::core::result::Result::Err(e) => {
+ ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
+ }
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct NoneError;
+
+pub trait Try {
+ type Ok;
+ type Error;
+ fn into_result(self) -> Result;
+}
+
+impl Try for Option {
+ type Ok = T;
+ type Error = NoneError;
+
+ #[inline]
+ fn into_result(self) -> Result {
+ self.ok_or(NoneError)
+ }
+}
+
+impl Try for Result {
+ type Ok = T;
+ type Error = E;
+
+ #[inline]
+ fn into_result(self) -> Self {
+ self
+ }
+}
diff --git a/embassy-net-enc28j60/src/header.rs b/embassy-net-enc28j60/src/header.rs
new file mode 100644
index 00000000..c2d4e468
--- /dev/null
+++ b/embassy-net-enc28j60/src/header.rs
@@ -0,0 +1,30 @@
+register!(RxStatus, 0, u32, {
+ #[doc = "Indicates length of the received frame"]
+ byte_count @ 0..15,
+ #[doc = "Indicates a packet over 50,000 bit times occurred or that a packet was dropped since the last receive"]
+ long_event @ 16,
+ #[doc = "Indicates that at some time since the last receive, a carrier event was detected"]
+ carrier_event @ 18,
+ #[doc = "Indicates that frame CRC field value does not match the CRC calculated by the MAC"]
+ crc_error @ 20,
+ #[doc = "Indicates that frame length field value in the packet does not match the actual data byte length and specifies a valid length"]
+ length_check_error @ 21,
+ #[doc = "Indicates that frame type/length field was larger than 1500 bytes (type field)"]
+ length_out_of_range @ 22,
+ #[doc = "Indicates that at the packet had a valid CRC and no symbol errors"]
+ received_ok @ 23,
+ #[doc = "Indicates packet received had a valid Multicast address"]
+ multicast @ 24,
+ #[doc = "Indicates packet received had a valid Broadcast address."]
+ broadcast @ 25,
+ #[doc = "Indicates that after the end of this packet, an additional 1 to 7 bits were received"]
+ dribble_nibble @ 26,
+ #[doc = "Current frame was recognized as a control frame for having a valid type/length designating it as a control frame"]
+ receive_control_frame @ 27,
+ #[doc = "Current frame was recognized as a control frame containing a valid pause frame opcode and a valid destination address"]
+ receive_pause_control_frame @ 28,
+ #[doc = "Current frame was recognized as a control frame but it contained an unknown opcode"]
+ receive_unknown_opcode @ 29,
+ #[doc = "Current frame was recognized as a VLAN tagged frame"]
+ receive_vlan_type_detected @ 30,
+});
diff --git a/embassy-net-enc28j60/src/lib.rs b/embassy-net-enc28j60/src/lib.rs
new file mode 100644
index 00000000..b44cefaf
--- /dev/null
+++ b/embassy-net-enc28j60/src/lib.rs
@@ -0,0 +1,717 @@
+#![no_std]
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
+
+// must go first.
+mod fmt;
+
+#[macro_use]
+mod macros;
+mod bank0;
+mod bank1;
+mod bank2;
+mod bank3;
+mod common;
+mod header;
+mod phy;
+mod traits;
+
+use core::cmp;
+use core::convert::TryInto;
+
+use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium};
+use embassy_time::Duration;
+use embedded_hal::digital::OutputPin;
+use embedded_hal::spi::{Operation, SpiDevice};
+use traits::U16Ext;
+
+// Total buffer size (see section 3.2)
+const BUF_SZ: u16 = 8 * 1024;
+
+// Maximum frame length
+const MAX_FRAME_LENGTH: u16 = 1518; // value recommended in the data sheet
+
+// Size of the Frame check sequence (32-bit CRC)
+const CRC_SZ: u16 = 4;
+
+// define the boundaries of the TX and RX buffers
+// to workaround errata #5 we do the opposite of what section 6.1 of the data sheet
+// says: we place the RX buffer at address 0 and the TX buffer after it
+const RXST: u16 = 0x0000;
+const RXND: u16 = 0x19ff;
+const TXST: u16 = 0x1a00;
+const _TXND: u16 = 0x1fff;
+
+const MTU: usize = 1514; // 1500 IP + 14 ethernet header
+
+/// ENC28J60 embassy-net driver
+pub struct Enc28j60 {
+ mac_addr: [u8; 6],
+
+ spi: S,
+ rst: Option,
+
+ bank: Bank,
+
+ // address of the next packet in buffer memory
+ next_packet: u16,
+}
+
+impl Enc28j60
+where
+ S: SpiDevice,
+ O: OutputPin,
+{
+ /// Create a new ENC28J60 driver instance.
+ ///
+ /// The RST pin is optional. If None, reset will be done with a SPI
+ /// soft reset command, instead of via the RST pin.
+ pub fn new(spi: S, rst: Option, mac_addr: [u8; 6]) -> Self {
+ let mut res = Self {
+ mac_addr,
+ spi,
+ rst,
+
+ bank: Bank::Bank0,
+ next_packet: RXST,
+ };
+ res.init();
+ res
+ }
+
+ fn init(&mut self) {
+ if let Some(rst) = &mut self.rst {
+ rst.set_low().unwrap();
+ embassy_time::block_for(Duration::from_millis(5));
+ rst.set_high().unwrap();
+ embassy_time::block_for(Duration::from_millis(5));
+ } else {
+ embassy_time::block_for(Duration::from_millis(5));
+ self.soft_reset();
+ embassy_time::block_for(Duration::from_millis(5));
+ }
+
+ debug!(
+ "enc28j60: erevid {=u8:x}",
+ self.read_control_register(bank3::Register::EREVID)
+ );
+ debug!("enc28j60: waiting for clk");
+ while common::ESTAT(self.read_control_register(common::Register::ESTAT)).clkrdy() == 0 {}
+ debug!("enc28j60: clk ok");
+
+ if self.read_control_register(bank3::Register::EREVID) == 0 {
+ panic!("ErevidIsZero");
+ }
+
+ // disable CLKOUT output
+ self.write_control_register(bank3::Register::ECOCON, 0);
+
+ self.init_rx();
+
+ // TX start
+ // "It is recommended that an even address be used for ETXST"
+ debug_assert_eq!(TXST % 2, 0);
+ self.write_control_register(bank0::Register::ETXSTL, TXST.low());
+ self.write_control_register(bank0::Register::ETXSTH, TXST.high());
+
+ // TX end is set in `transmit`
+
+ // MAC initialization (see section 6.5)
+ // 1. Set the MARXEN bit in MACON1 to enable the MAC to receive frames.
+ self.write_control_register(
+ bank2::Register::MACON1,
+ bank2::MACON1::default().marxen(1).passall(0).rxpaus(1).txpaus(1).bits(),
+ );
+
+ // 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3.
+ self.write_control_register(
+ bank2::Register::MACON3,
+ bank2::MACON3::default().frmlnen(1).txcrcen(1).padcfg(0b001).bits(),
+ );
+
+ // 4. Program the MAMXFL registers with the maximum frame length to be permitted to be
+ // received or transmitted
+ self.write_control_register(bank2::Register::MAMXFLL, MAX_FRAME_LENGTH.low());
+ self.write_control_register(bank2::Register::MAMXFLH, MAX_FRAME_LENGTH.high());
+
+ // 5. Configure the Back-to-Back Inter-Packet Gap register, MABBIPG.
+ // Use recommended value of 0x12
+ self.write_control_register(bank2::Register::MABBIPG, 0x12);
+
+ // 6. Configure the Non-Back-to-Back Inter-Packet Gap register low byte, MAIPGL.
+ // Use recommended value of 0x12
+ self.write_control_register(bank2::Register::MAIPGL, 0x12);
+ self.write_control_register(bank2::Register::MAIPGH, 0x0c);
+
+ // 9. Program the local MAC address into the MAADR1:MAADR6 registers
+ self.write_control_register(bank3::Register::MAADR1, self.mac_addr[0]);
+ self.write_control_register(bank3::Register::MAADR2, self.mac_addr[1]);
+ self.write_control_register(bank3::Register::MAADR3, self.mac_addr[2]);
+ self.write_control_register(bank3::Register::MAADR4, self.mac_addr[3]);
+ self.write_control_register(bank3::Register::MAADR5, self.mac_addr[4]);
+ self.write_control_register(bank3::Register::MAADR6, self.mac_addr[5]);
+
+ // Set the PHCON2.HDLDIS bit to prevent automatic loopback of the data which is transmitted
+ self.write_phy_register(phy::Register::PHCON2, phy::PHCON2::default().hdldis(1).bits());
+
+ // Globally enable interrupts
+ //self.bit_field_set(common::Register::EIE, common::EIE::mask().intie());
+
+ // Set the per packet control byte; we'll always use the value 0
+ self.write_buffer_memory(Some(TXST), &mut [0]);
+
+ // Enable reception
+ self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen());
+ }
+
+ fn init_rx(&mut self) {
+ // RX start
+ // "It is recommended that the ERXST Pointer be programmed with an even address"
+ self.write_control_register(bank0::Register::ERXSTL, RXST.low());
+ self.write_control_register(bank0::Register::ERXSTH, RXST.high());
+
+ // RX read pointer
+ // NOTE Errata #14 so we are using an *odd* address here instead of ERXST
+ self.write_control_register(bank0::Register::ERXRDPTL, RXND.low());
+ self.write_control_register(bank0::Register::ERXRDPTH, RXND.high());
+
+ // RX end
+ self.write_control_register(bank0::Register::ERXNDL, RXND.low());
+ self.write_control_register(bank0::Register::ERXNDH, RXND.high());
+
+ // decrease the packet count to 0
+ while self.read_control_register(bank1::Register::EPKTCNT) != 0 {
+ self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec());
+ }
+
+ self.next_packet = RXST;
+ }
+
+ fn reset_rx(&mut self) {
+ self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxrst());
+ self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().rxrst());
+ self.init_rx();
+ self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen());
+ }
+
+ /// Flushes the transmit buffer, ensuring all pending transmissions have completed
+ /// NOTE: The returned packet *must* be `read` or `ignore`-d, otherwise this method will always
+ /// return `None` on subsequent invocations
+ pub fn receive<'a>(&mut self, buf: &'a mut [u8]) -> Option<&'a mut [u8]> {
+ if self.pending_packets() == 0 {
+ // Errata #6: we can't rely on PKTIF so we check PKTCNT
+ return None;
+ }
+
+ let curr_packet = self.next_packet;
+
+ // read out the first 6 bytes
+ let mut temp_buf = [0; 6];
+ self.read_buffer_memory(Some(curr_packet), &mut temp_buf);
+
+ // next packet pointer
+ let next_packet = u16::from_parts(temp_buf[0], temp_buf[1]);
+ // status vector
+ let status = header::RxStatus(u32::from_le_bytes(temp_buf[2..].try_into().unwrap()));
+ let len_with_crc = status.byte_count() as u16;
+
+ if len_with_crc < CRC_SZ || len_with_crc > 1600 || next_packet > RXND {
+ warn!("RX buffer corrupted, resetting RX logic to recover...");
+ self.reset_rx();
+ return None;
+ }
+
+ let len = len_with_crc - CRC_SZ;
+ self.read_buffer_memory(None, &mut buf[..len as usize]);
+
+ // update ERXRDPT
+ // due to Errata #14 we must write an odd address to ERXRDPT
+ // we know that ERXST = 0, that ERXND is odd and that next_packet is even
+ let rxrdpt = if self.next_packet < 1 || self.next_packet > RXND + 1 {
+ RXND
+ } else {
+ self.next_packet - 1
+ };
+ // "To move ERXRDPT, the host controller must write to ERXRDPTL first."
+ self.write_control_register(bank0::Register::ERXRDPTL, rxrdpt.low());
+ self.write_control_register(bank0::Register::ERXRDPTH, rxrdpt.high());
+
+ // decrease the packet count
+ self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec());
+
+ self.next_packet = next_packet;
+
+ Some(&mut buf[..len as usize])
+ }
+
+ fn wait_tx_ready(&mut self) {
+ for _ in 0u32..10000 {
+ if common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 0 {
+ return;
+ }
+ }
+
+ // work around errata #12 by resetting the transmit logic before every new
+ // transmission
+ self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrst());
+ self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().txrst());
+ //self.bit_field_clear(common::Register::EIR, {
+ // let mask = common::EIR::mask();
+ // mask.txerif() | mask.txif()
+ //});
+ }
+
+ /// Starts the transmission of `bytes`
+ ///
+ /// It's up to the caller to ensure that `bytes` is a valid Ethernet frame. The interface will
+ /// take care of appending a (4 byte) CRC to the frame and of padding the frame to the minimum
+ /// size allowed by the Ethernet specification (64 bytes, or 46 bytes of payload).
+ ///
+ /// NOTE This method will flush any previous transmission that's in progress
+ ///
+ /// # Panics
+ ///
+ /// If `bytes` length is greater than 1514, the maximum frame length allowed by the interface,
+ /// or greater than the transmit buffer
+ pub fn transmit(&mut self, bytes: &[u8]) {
+ assert!(bytes.len() <= self.mtu() as usize);
+
+ self.wait_tx_ready();
+
+ // NOTE the plus one is to not overwrite the per packet control byte
+ let wrpt = TXST + 1;
+
+ // 1. ETXST was set during initialization
+
+ // 2. write the frame to the IC memory
+ self.write_buffer_memory(Some(wrpt), bytes);
+
+ let txnd = wrpt + bytes.len() as u16 - 1;
+
+ // 3. Set the end address of the transmit buffer
+ self.write_control_register(bank0::Register::ETXNDL, txnd.low());
+ self.write_control_register(bank0::Register::ETXNDH, txnd.high());
+
+ // 4. reset interrupt flag
+ //self.bit_field_clear(common::Register::EIR, common::EIR::mask().txif());
+
+ // 5. start transmission
+ self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrts());
+
+ // Wait until transmission finishes
+ //while common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 1 {}
+
+ /*
+ // read the transmit status vector
+ let mut tx_stat = [0; 7];
+ self.read_buffer_memory(None, &mut tx_stat);
+
+ let stat = common::ESTAT(self.read_control_register(common::Register::ESTAT));
+
+ if stat.txabrt() == 1 {
+ // work around errata #12 by reading the transmit status vector
+ if stat.latecol() == 1 || (tx_stat[2] & (1 << 5)) != 0 {
+ panic!("LateCollision")
+ } else {
+ panic!("TransmitAbort")
+ }
+ }*/
+ }
+
+ /// Get whether the link is up
+ pub fn is_link_up(&mut self) -> bool {
+ let bits = self.read_phy_register(phy::Register::PHSTAT2);
+ phy::PHSTAT2(bits).lstat() == 1
+ }
+
+ /// Returns the interface Maximum Transmission Unit (MTU)
+ ///
+ /// The value returned by this function will never exceed 1514 bytes. The actual value depends
+ /// on the memory assigned to the transmission buffer when initializing the device
+ pub fn mtu(&self) -> u16 {
+ cmp::min(BUF_SZ - RXND - 1, MAX_FRAME_LENGTH - CRC_SZ)
+ }
+
+ /* Miscellaneous */
+ /// Returns the number of packets that have been received but have not been processed yet
+ pub fn pending_packets(&mut self) -> u8 {
+ self.read_control_register(bank1::Register::EPKTCNT)
+ }
+
+ /// Adjusts the receive filter to *accept* these packet types
+ pub fn accept(&mut self, packets: &[Packet]) {
+ let mask = bank1::ERXFCON::mask();
+ let mut val = 0;
+ for packet in packets {
+ match packet {
+ Packet::Broadcast => val |= mask.bcen(),
+ Packet::Multicast => val |= mask.mcen(),
+ Packet::Unicast => val |= mask.ucen(),
+ }
+ }
+
+ self.bit_field_set(bank1::Register::ERXFCON, val)
+ }
+
+ /// Adjusts the receive filter to *ignore* these packet types
+ pub fn ignore(&mut self, packets: &[Packet]) {
+ let mask = bank1::ERXFCON::mask();
+ let mut val = 0;
+ for packet in packets {
+ match packet {
+ Packet::Broadcast => val |= mask.bcen(),
+ Packet::Multicast => val |= mask.mcen(),
+ Packet::Unicast => val |= mask.ucen(),
+ }
+ }
+
+ self.bit_field_clear(bank1::Register::ERXFCON, val)
+ }
+
+ /* Private */
+ /* Read */
+ fn read_control_register(&mut self, register: R) -> u8
+ where
+ R: Into,
+ {
+ self._read_control_register(register.into())
+ }
+
+ fn _read_control_register(&mut self, register: Register) -> u8 {
+ self.change_bank(register);
+
+ if register.is_eth_register() {
+ let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0];
+ self.spi.transfer_in_place(&mut buffer).unwrap();
+ buffer[1]
+ } else {
+ // MAC, MII regs need a dummy byte.
+ let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0, 0];
+ self.spi.transfer_in_place(&mut buffer).unwrap();
+ buffer[2]
+ }
+ }
+
+ fn read_phy_register(&mut self, register: phy::Register) -> u16 {
+ // set PHY register address
+ self.write_control_register(bank2::Register::MIREGADR, register.addr());
+
+ // start read operation
+ self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(1).bits());
+
+ // wait until the read operation finishes
+ while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {}
+
+ self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(0).bits());
+
+ let l = self.read_control_register(bank2::Register::MIRDL);
+ let h = self.read_control_register(bank2::Register::MIRDH);
+ (l as u16) | (h as u16) << 8
+ }
+
+ /* Write */
+ fn _write_control_register(&mut self, register: Register, value: u8) {
+ self.change_bank(register);
+
+ let buffer = [Instruction::WCR.opcode() | register.addr(), value];
+ self.spi.write(&buffer).unwrap();
+ }
+
+ fn write_control_register(&mut self, register: R, value: u8)
+ where
+ R: Into,
+ {
+ self._write_control_register(register.into(), value)
+ }
+
+ fn write_phy_register(&mut self, register: phy::Register, value: u16) {
+ // set PHY register address
+ self.write_control_register(bank2::Register::MIREGADR, register.addr());
+
+ self.write_control_register(bank2::Register::MIWRL, (value & 0xff) as u8);
+ // this starts the write operation
+ self.write_control_register(bank2::Register::MIWRH, (value >> 8) as u8);
+
+ // wait until the write operation finishes
+ while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {}
+ }
+
+ /* RMW */
+ fn modify_control_register(&mut self, register: R, f: F)
+ where
+ F: FnOnce(u8) -> u8,
+ R: Into,
+ {
+ self._modify_control_register(register.into(), f)
+ }
+
+ fn _modify_control_register(&mut self, register: Register, f: F)
+ where
+ F: FnOnce(u8) -> u8,
+ {
+ let r = self._read_control_register(register);
+ self._write_control_register(register, f(r))
+ }
+
+ /* Auxiliary */
+ fn change_bank(&mut self, register: Register) {
+ let bank = register.bank();
+
+ if let Some(bank) = bank {
+ if self.bank == bank {
+ // already on the register bank
+ return;
+ }
+
+ // change bank
+ self.bank = bank;
+ match bank {
+ Bank::Bank0 => self.bit_field_clear(common::Register::ECON1, 0b11),
+ Bank::Bank1 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b01),
+ Bank::Bank2 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b10),
+ Bank::Bank3 => self.bit_field_set(common::Register::ECON1, 0b11),
+ }
+ } else {
+ // common register
+ }
+ }
+
+ /* Primitive operations */
+ fn bit_field_clear(&mut self, register: R, mask: u8)
+ where
+ R: Into,
+ {
+ self._bit_field_clear(register.into(), mask)
+ }
+
+ fn _bit_field_clear(&mut self, register: Register, mask: u8) {
+ debug_assert!(register.is_eth_register());
+
+ self.change_bank(register);
+
+ self.spi
+ .write(&[Instruction::BFC.opcode() | register.addr(), mask])
+ .unwrap();
+ }
+
+ fn bit_field_set(&mut self, register: R, mask: u8)
+ where
+ R: Into,
+ {
+ self._bit_field_set(register.into(), mask)
+ }
+
+ fn _bit_field_set(&mut self, register: Register, mask: u8) {
+ debug_assert!(register.is_eth_register());
+
+ self.change_bank(register);
+
+ self.spi
+ .write(&[Instruction::BFS.opcode() | register.addr(), mask])
+ .unwrap();
+ }
+
+ fn read_buffer_memory(&mut self, addr: Option, buf: &mut [u8]) {
+ if let Some(addr) = addr {
+ self.write_control_register(bank0::Register::ERDPTL, addr.low());
+ self.write_control_register(bank0::Register::ERDPTH, addr.high());
+ }
+
+ self.spi
+ .transaction(&mut [Operation::Write(&[Instruction::RBM.opcode()]), Operation::Read(buf)])
+ .unwrap();
+ }
+
+ fn soft_reset(&mut self) {
+ self.spi.write(&[Instruction::SRC.opcode()]).unwrap();
+ }
+
+ fn write_buffer_memory(&mut self, addr: Option, buffer: &[u8]) {
+ if let Some(addr) = addr {
+ self.write_control_register(bank0::Register::EWRPTL, addr.low());
+ self.write_control_register(bank0::Register::EWRPTH, addr.high());
+ }
+
+ self.spi
+ .transaction(&mut [Operation::Write(&[Instruction::WBM.opcode()]), Operation::Write(buffer)])
+ .unwrap();
+ }
+}
+
+#[derive(Clone, Copy, PartialEq)]
+enum Bank {
+ Bank0,
+ Bank1,
+ Bank2,
+ Bank3,
+}
+
+#[derive(Clone, Copy)]
+enum Instruction {
+ /// Read Control Register
+ RCR = 0b000_00000,
+ /// Read Buffer Memory
+ RBM = 0b001_11010,
+ /// Write Control Register
+ WCR = 0b010_00000,
+ /// Write Buffer Memory
+ WBM = 0b011_11010,
+ /// Bit Field Set
+ BFS = 0b100_00000,
+ /// Bit Field Clear
+ BFC = 0b101_00000,
+ /// System Reset Command
+ SRC = 0b111_11111,
+}
+
+impl Instruction {
+ fn opcode(&self) -> u8 {
+ *self as u8
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Register {
+ Bank0(bank0::Register),
+ Bank1(bank1::Register),
+ Bank2(bank2::Register),
+ Bank3(bank3::Register),
+ Common(common::Register),
+}
+
+impl Register {
+ fn addr(&self) -> u8 {
+ match *self {
+ Register::Bank0(r) => r.addr(),
+ Register::Bank1(r) => r.addr(),
+ Register::Bank2(r) => r.addr(),
+ Register::Bank3(r) => r.addr(),
+ Register::Common(r) => r.addr(),
+ }
+ }
+
+ fn bank(&self) -> Option {
+ Some(match *self {
+ Register::Bank0(_) => Bank::Bank0,
+ Register::Bank1(_) => Bank::Bank1,
+ Register::Bank2(_) => Bank::Bank2,
+ Register::Bank3(_) => Bank::Bank3,
+ Register::Common(_) => return None,
+ })
+ }
+
+ fn is_eth_register(&self) -> bool {
+ match *self {
+ Register::Bank0(r) => r.is_eth_register(),
+ Register::Bank1(r) => r.is_eth_register(),
+ Register::Bank2(r) => r.is_eth_register(),
+ Register::Bank3(r) => r.is_eth_register(),
+ Register::Common(r) => r.is_eth_register(),
+ }
+ }
+}
+
+/// Packet type, used to configure receive filters
+#[non_exhaustive]
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub enum Packet {
+ /// Broadcast packets
+ Broadcast,
+ /// Multicast packets
+ Multicast,
+ /// Unicast packets
+ Unicast,
+}
+
+static mut TX_BUF: [u8; MTU] = [0; MTU];
+static mut RX_BUF: [u8; MTU] = [0; MTU];
+
+impl embassy_net_driver::Driver for Enc28j60
+where
+ S: SpiDevice,
+ O: OutputPin,
+{
+ type RxToken<'a> = RxToken<'a>
+ where
+ Self: 'a;
+
+ type TxToken<'a> = TxToken<'a, S, O>
+ where
+ Self: 'a;
+
+ fn receive(&mut self, cx: &mut core::task::Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let rx_buf = unsafe { &mut RX_BUF };
+ let tx_buf = unsafe { &mut TX_BUF };
+ if let Some(pkt) = self.receive(rx_buf) {
+ let n = pkt.len();
+ Some((RxToken { buf: &mut pkt[..n] }, TxToken { buf: tx_buf, eth: self }))
+ } else {
+ cx.waker().wake_by_ref();
+ None
+ }
+ }
+
+ fn transmit(&mut self, _cx: &mut core::task::Context) -> Option> {
+ let tx_buf = unsafe { &mut TX_BUF };
+ Some(TxToken { buf: tx_buf, eth: self })
+ }
+
+ fn link_state(&mut self, cx: &mut core::task::Context) -> LinkState {
+ cx.waker().wake_by_ref();
+ match self.is_link_up() {
+ true => LinkState::Up,
+ false => LinkState::Down,
+ }
+ }
+
+ fn capabilities(&self) -> Capabilities {
+ let mut caps = Capabilities::default();
+ caps.max_transmission_unit = MTU;
+ caps.medium = Medium::Ethernet;
+ caps
+ }
+
+ fn hardware_address(&self) -> HardwareAddress {
+ HardwareAddress::Ethernet(self.mac_addr)
+ }
+}
+
+/// embassy-net RX token.
+pub struct RxToken<'a> {
+ buf: &'a mut [u8],
+}
+
+impl<'a> embassy_net_driver::RxToken for RxToken<'a> {
+ fn consume(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(self.buf)
+ }
+}
+
+/// embassy-net TX token.
+pub struct TxToken<'a, S, O>
+where
+ S: SpiDevice,
+ O: OutputPin,
+{
+ eth: &'a mut Enc28j60,
+ buf: &'a mut [u8],
+}
+
+impl<'a, S, O> embassy_net_driver::TxToken for TxToken<'a, S, O>
+where
+ S: SpiDevice,
+ O: OutputPin,
+{
+ fn consume(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ assert!(len <= self.buf.len());
+ let r = f(&mut self.buf[..len]);
+ self.eth.transmit(&self.buf[..len]);
+ r
+ }
+}
diff --git a/embassy-net-enc28j60/src/macros.rs b/embassy-net-enc28j60/src/macros.rs
new file mode 100644
index 00000000..8d064957
--- /dev/null
+++ b/embassy-net-enc28j60/src/macros.rs
@@ -0,0 +1,89 @@
+macro_rules! register {
+ ($REGISTER:ident, $reset_value:expr, $uxx:ty, {
+ $(#[$($attr:tt)*] $bitfield:ident @ $range:expr,)+
+ }) => {
+ #[derive(Clone, Copy)]
+ pub(crate) struct $REGISTER {
+ bits: $uxx,
+ _mode: ::core::marker::PhantomData,
+ }
+
+ impl $REGISTER {
+ #[allow(dead_code)]
+ pub(crate) fn mask() -> $REGISTER {
+ $REGISTER { bits: 0, _mode: ::core::marker::PhantomData }
+ }
+
+ $(
+ #[allow(dead_code)]
+ pub(crate) fn $bitfield(&self) -> $uxx {
+ use super::traits::OffsetSize;
+
+ let size = $range.size();
+ let offset = $range.offset();
+ ((1 << size) - 1) << offset
+ }
+ )+
+ }
+
+ impl ::core::default::Default for $REGISTER {
+ fn default() -> Self {
+ $REGISTER { bits: $reset_value, _mode: ::core::marker::PhantomData }
+ }
+ }
+
+ #[allow(non_snake_case)]
+ #[allow(dead_code)]
+ pub(crate) fn $REGISTER(bits: $uxx) -> $REGISTER {
+ $REGISTER { bits, _mode: ::core::marker::PhantomData }
+ }
+
+ impl $REGISTER {
+ #[allow(dead_code)]
+ pub(crate) fn modify(self) -> $REGISTER {
+ $REGISTER { bits: self.bits, _mode: ::core::marker::PhantomData }
+ }
+
+ $(
+ #[$($attr)*]
+ #[allow(dead_code)]
+ pub(crate) fn $bitfield(&self) -> $uxx {
+ use super::traits::OffsetSize;
+
+ let offset = $range.offset();
+ let size = $range.size();
+ let mask = (1 << size) - 1;
+
+ (self.bits >> offset) & mask
+ }
+ )+
+ }
+
+ impl $REGISTER {
+ #[allow(dead_code)]
+ pub(crate) fn bits(self) -> $uxx {
+ self.bits
+ }
+
+ $(
+ #[$($attr)*]
+ #[allow(dead_code)]
+ pub(crate) fn $bitfield(&mut self, mut bits: $uxx) -> &mut Self {
+ use super::traits::OffsetSize;
+
+ let offset = $range.offset();
+ let size = $range.size();
+ let mask = (1 << size) - 1;
+
+ debug_assert!(bits <= mask);
+ bits &= mask;
+
+ self.bits &= !(mask << offset);
+ self.bits |= bits << offset;
+
+ self
+ }
+ )+
+ }
+ }
+}
diff --git a/embassy-net-enc28j60/src/phy.rs b/embassy-net-enc28j60/src/phy.rs
new file mode 100644
index 00000000..89144ada
--- /dev/null
+++ b/embassy-net-enc28j60/src/phy.rs
@@ -0,0 +1,35 @@
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum Register {
+ PHCON1 = 0x00,
+ PHSTAT1 = 0x01,
+ PHID1 = 0x02,
+ PHID2 = 0x03,
+ PHCON2 = 0x10,
+ PHSTAT2 = 0x11,
+ PHIE = 0x12,
+ PHIR = 0x13,
+ PHLCON = 0x14,
+}
+
+impl Register {
+ pub(crate) fn addr(&self) -> u8 {
+ *self as u8
+ }
+}
+
+register!(PHCON2, 0, u16, {
+ #[doc = "PHY Half-Duplex Loopback Disable bit"]
+ hdldis @ 8,
+ #[doc = "Jabber Correction Disable bit"]
+ jabber @ 10,
+ #[doc = "Twisted-Pair Transmitter Disable bit"]
+ txdis @ 13,
+ #[doc = "PHY Force Linkup bit"]
+ frclnk @ 14,
+});
+
+register!(PHSTAT2, 0, u16, {
+ #[doc = "Link Status bit"]
+ lstat @ 10,
+});
diff --git a/embassy-net-enc28j60/src/traits.rs b/embassy-net-enc28j60/src/traits.rs
new file mode 100644
index 00000000..08f94045
--- /dev/null
+++ b/embassy-net-enc28j60/src/traits.rs
@@ -0,0 +1,57 @@
+use core::ops::Range;
+
+pub(crate) trait OffsetSize {
+ fn offset(self) -> u8;
+ fn size(self) -> u8;
+}
+
+impl OffsetSize for u8 {
+ fn offset(self) -> u8 {
+ self
+ }
+
+ fn size(self) -> u8 {
+ 1
+ }
+}
+
+impl OffsetSize for Range {
+ fn offset(self) -> u8 {
+ self.start
+ }
+
+ fn size(self) -> u8 {
+ self.end - self.start
+ }
+}
+
+pub(crate) trait U16Ext {
+ fn from_parts(low: u8, high: u8) -> Self;
+
+ fn low(self) -> u8;
+
+ fn high(self) -> u8;
+}
+
+impl U16Ext for u16 {
+ fn from_parts(low: u8, high: u8) -> u16 {
+ ((high as u16) << 8) + low as u16
+ }
+
+ fn low(self) -> u8 {
+ (self & 0xff) as u8
+ }
+
+ fn high(self) -> u8 {
+ (self >> 8) as u8
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct Mask;
+
+#[derive(Clone, Copy)]
+pub struct R;
+
+#[derive(Clone, Copy)]
+pub struct W;
diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml
index 26f5b40b..d334cf3f 100644
--- a/embassy-net-esp-hosted/Cargo.toml
+++ b/embassy-net-esp-hosted/Cargo.toml
@@ -12,9 +12,15 @@ embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
-embedded-hal = { version = "1.0.0-alpha.11" }
-embedded-hal-async = { version = "=0.2.0-alpha.2" }
+embedded-hal = { version = "1.0.0-rc.1" }
+embedded-hal-async = { version = "=1.0.0-rc.1" }
noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] }
#noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] }
heapless = "0.7.16"
+
+[package.metadata.embassy_docs]
+src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/"
+target = "thumbv7em-none-eabi"
+features = ["defmt"]
\ No newline at end of file
diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs
index 79f8cde7..ce6636a8 100644
--- a/embassy-net-esp-hosted/src/control.rs
+++ b/embassy-net-esp-hosted/src/control.rs
@@ -1,14 +1,16 @@
use ch::driver::LinkState;
-use defmt::Debug2Format;
use embassy_net_driver_channel as ch;
use heapless::String;
use crate::ioctl::Shared;
use crate::proto::{self, CtrlMsg};
-#[derive(Debug)]
-pub struct Error {
- pub status: u32,
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Error {
+ Failed(u32),
+ Timeout,
+ Internal,
}
pub struct Control<'a> {
@@ -24,59 +26,78 @@ enum WifiMode {
ApSta = 3,
}
+macro_rules! ioctl {
+ ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => {
+ let mut msg = proto::CtrlMsg {
+ msg_id: proto::CtrlMsgId::$req_variant as _,
+ msg_type: proto::CtrlMsgType::Req as _,
+ payload: Some(proto::CtrlMsgPayload::$req_variant($req)),
+ };
+ $self.ioctl(&mut msg).await?;
+ let Some(proto::CtrlMsgPayload::$resp_variant($resp)) = msg.payload else {
+ warn!("unexpected response variant");
+ return Err(Error::Internal);
+ };
+ if $resp.resp != 0 {
+ return Err(Error::Failed($resp.resp));
+ }
+ };
+}
+
impl<'a> Control<'a> {
pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self {
Self { state_ch, shared }
}
- pub async fn init(&mut self) {
+ pub async fn init(&mut self) -> Result<(), Error> {
debug!("wait for init event...");
self.shared.init_wait().await;
- debug!("set wifi mode");
- self.set_wifi_mode(WifiMode::Sta as _).await;
+ debug!("set heartbeat");
+ self.set_heartbeat(10).await?;
- let mac_addr = self.get_mac_addr().await;
+ debug!("set wifi mode");
+ self.set_wifi_mode(WifiMode::Sta as _).await?;
+
+ let mac_addr = self.get_mac_addr().await?;
debug!("mac addr: {:02x}", mac_addr);
self.state_ch.set_ethernet_address(mac_addr);
+
+ Ok(())
}
- pub async fn join(&mut self, ssid: &str, password: &str) {
- let req = proto::CtrlMsg {
- msg_id: proto::CtrlMsgId::ReqConnectAp as _,
- msg_type: proto::CtrlMsgType::Req as _,
- payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp {
- ssid: String::from(ssid),
- pwd: String::from(password),
- bssid: String::new(),
- listen_interval: 3,
- is_wpa3_supported: false,
- })),
+ pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> {
+ let req = proto::CtrlMsgReqConnectAp {
+ ssid: String::from(ssid),
+ pwd: String::from(password),
+ bssid: String::new(),
+ listen_interval: 3,
+ is_wpa3_supported: false,
};
- let resp = self.ioctl(req).await;
- let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else {
- panic!("unexpected resp")
- };
- debug!("======= {:?}", Debug2Format(&resp));
- assert_eq!(resp.resp, 0);
+ ioctl!(self, ReqConnectAp, RespConnectAp, req, resp);
self.state_ch.set_link_state(LinkState::Up);
+ Ok(())
}
- async fn get_mac_addr(&mut self) -> [u8; 6] {
- let req = proto::CtrlMsg {
- msg_id: proto::CtrlMsgId::ReqGetMacAddress as _,
- msg_type: proto::CtrlMsgType::Req as _,
- payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress(
- proto::CtrlMsgReqGetMacAddress {
- mode: WifiMode::Sta as _,
- },
- )),
+ pub async fn disconnect(&mut self) -> Result<(), Error> {
+ let req = proto::CtrlMsgReqGetStatus {};
+ ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp);
+ self.state_ch.set_link_state(LinkState::Down);
+ Ok(())
+ }
+
+ /// duration in seconds, clamped to [10, 3600]
+ async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> {
+ let req = proto::CtrlMsgReqConfigHeartbeat { enable: true, duration };
+ ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp);
+ Ok(())
+ }
+
+ async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> {
+ let req = proto::CtrlMsgReqGetMacAddress {
+ mode: WifiMode::Sta as _,
};
- let resp = self.ioctl(req).await;
- let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else {
- panic!("unexpected resp")
- };
- assert_eq!(resp.resp, 0);
+ ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp);
// WHY IS THIS A STRING? WHYYYY
fn nibble_from_hex(b: u8) -> u8 {
@@ -90,32 +111,32 @@ impl<'a> Control<'a> {
let mac = resp.mac.as_bytes();
let mut res = [0; 6];
- assert_eq!(mac.len(), 17);
+ if mac.len() != 17 {
+ warn!("unexpected MAC respnse length");
+ return Err(Error::Internal);
+ }
for (i, b) in res.iter_mut().enumerate() {
*b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1])
}
- res
+ Ok(res)
}
- async fn set_wifi_mode(&mut self, mode: u32) {
- let req = proto::CtrlMsg {
- msg_id: proto::CtrlMsgId::ReqSetWifiMode as _,
- msg_type: proto::CtrlMsgType::Req as _,
- payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })),
- };
- let resp = self.ioctl(req).await;
- let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else {
- panic!("unexpected resp")
- };
- assert_eq!(resp.resp, 0);
+ async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> {
+ let req = proto::CtrlMsgReqSetMode { mode };
+ ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp);
+
+ Ok(())
}
- async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg {
- debug!("ioctl req: {:?}", &req);
+ async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> {
+ debug!("ioctl req: {:?}", &msg);
let mut buf = [0u8; 128];
- let req_len = noproto::write(&req, &mut buf).unwrap();
+ let req_len = noproto::write(msg, &mut buf).map_err(|_| {
+ warn!("failed to serialize control request");
+ Error::Internal
+ })?;
struct CancelOnDrop<'a>(&'a Shared);
@@ -137,9 +158,12 @@ impl<'a> Control<'a> {
ioctl.defuse();
- let res = noproto::read(&buf[..resp_len]).unwrap();
- debug!("ioctl resp: {:?}", &res);
+ *msg = noproto::read(&buf[..resp_len]).map_err(|_| {
+ warn!("failed to serialize control request");
+ Error::Internal
+ })?;
+ debug!("ioctl resp: {:?}", msg);
- res
+ Ok(())
}
}
diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs
index a35adfca..4a318b20 100644
--- a/embassy-net-esp-hosted/src/lib.rs
+++ b/embassy-net-esp-hosted/src/lib.rs
@@ -1,17 +1,15 @@
#![no_std]
-use control::Control;
-use embassy_futures::select::{select3, Either3};
+use embassy_futures::select::{select4, Either4};
use embassy_net_driver_channel as ch;
+use embassy_net_driver_channel::driver::LinkState;
use embassy_time::{Duration, Instant, Timer};
use embedded_hal::digital::{InputPin, OutputPin};
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiDevice;
-use ioctl::Shared;
-use proto::CtrlMsg;
-use crate::ioctl::PendingIoctl;
-use crate::proto::CtrlMsgPayload;
+use crate::ioctl::{PendingIoctl, Shared};
+use crate::proto::{CtrlMsg, CtrlMsgPayload};
mod proto;
@@ -21,6 +19,8 @@ mod fmt;
mod control;
mod ioctl;
+pub use control::*;
+
const MTU: usize = 1514;
macro_rules! impl_bytes {
@@ -95,6 +95,7 @@ enum InterfaceType {
}
const MAX_SPI_BUFFER_SIZE: usize = 1600;
+const HEARTBEAT_MAX_GAP: Duration = Duration::from_secs(20);
pub struct State {
shared: Shared,
@@ -124,17 +125,19 @@ where
IN: InputPin + Wait,
OUT: OutputPin,
{
- let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
+ let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
let state_ch = ch_runner.state_runner();
let mut runner = Runner {
ch: ch_runner,
+ state_ch,
shared: &state.shared,
next_seq: 1,
handshake,
ready,
reset,
spi,
+ heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP,
};
runner.init().await;
@@ -143,9 +146,11 @@ where
pub struct Runner<'a, SPI, IN, OUT> {
ch: ch::Runner<'a, MTU>,
+ state_ch: ch::StateRunner<'a>,
shared: &'a Shared,
next_seq: u16,
+ heartbeat_deadline: Instant,
spi: SPI,
handshake: IN,
@@ -177,9 +182,10 @@ where
let ioctl = self.shared.ioctl_wait_pending();
let tx = self.ch.tx_buf();
let ev = async { self.ready.wait_for_high().await.unwrap() };
+ let hb = Timer::at(self.heartbeat_deadline);
- match select3(ioctl, tx, ev).await {
- Either3::First(PendingIoctl { buf, req_len }) => {
+ match select4(ioctl, tx, ev, hb).await {
+ Either4::First(PendingIoctl { buf, req_len }) => {
tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02");
tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes());
tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]);
@@ -198,7 +204,7 @@ where
header.checksum = checksum(&tx_buf[..26 + req_len]);
tx_buf[0..12].copy_from_slice(&header.to_bytes());
}
- Either3::Second(packet) => {
+ Either4::Second(packet) => {
tx_buf[12..][..packet.len()].copy_from_slice(packet);
let mut header = PayloadHeader {
@@ -217,9 +223,12 @@ where
self.ch.tx_done();
}
- Either3::Third(()) => {
+ Either4::Third(()) => {
tx_buf[..PayloadHeader::SIZE].fill(0);
}
+ Either4::Fourth(()) => {
+ panic!("heartbeat from esp32 stopped")
+ }
}
if tx_buf[0] != 0 {
@@ -308,7 +317,7 @@ where
}
}
- fn handle_event(&self, data: &[u8]) {
+ fn handle_event(&mut self, data: &[u8]) {
let Ok(event) = noproto::read::(data) else {
warn!("failed to parse event");
return;
@@ -323,6 +332,11 @@ where
match payload {
CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(),
+ CtrlMsgPayload::EventHeartbeat(_) => self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP,
+ CtrlMsgPayload::EventStationDisconnectFromAp(e) => {
+ info!("disconnected, code {}", e.resp);
+ self.state_ch.set_link_state(LinkState::Down);
+ }
_ => {}
}
}
diff --git a/embassy-net-tuntap/Cargo.toml b/embassy-net-tuntap/Cargo.toml
new file mode 100644
index 00000000..08d30968
--- /dev/null
+++ b/embassy-net-tuntap/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "embassy-net-tuntap"
+version = "0.1.0"
+description = "embassy-net driver for Linux TUN/TAP interfaces."
+keywords = ["embedded", "tuntap", "embassy-net", "embedded-hal-async", "ethernet", "async"]
+categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
+license = "MIT OR Apache-2.0"
+edition = "2021"
+
+[dependencies]
+embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
+async-io = "1.6.0"
+log = "0.4.14"
+libc = "0.2.101"
+
+[package.metadata.embassy_docs]
+src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-tuntap-v$VERSION/embassy-net-tuntap/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-tuntap/src/"
+target = "thumbv7em-none-eabi"
\ No newline at end of file
diff --git a/embassy-net-tuntap/README.md b/embassy-net-tuntap/README.md
new file mode 100644
index 00000000..c5d9e746
--- /dev/null
+++ b/embassy-net-tuntap/README.md
@@ -0,0 +1,17 @@
+# `embassy-net` integration for Linux TUN/TAP interfaces.
+
+[`embassy-net`](https://crates.io/crates/embassy-net) integration for for Linux TUN (IP medium) and TAP (Ethernet medium) interfaces.
+
+## Interoperability
+
+This crate can run on any executor.
+
+## License
+
+This work is licensed under either of
+
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+ http://www.apache.org/licenses/LICENSE-2.0)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
diff --git a/examples/std/src/tuntap.rs b/embassy-net-tuntap/src/lib.rs
similarity index 96%
rename from examples/std/src/tuntap.rs
rename to embassy-net-tuntap/src/lib.rs
index d918a2e6..75c54c48 100644
--- a/examples/std/src/tuntap.rs
+++ b/embassy-net-tuntap/src/lib.rs
@@ -4,7 +4,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::task::Context;
use async_io::Async;
-use embassy_net_driver::{self, Capabilities, Driver, LinkState};
+use embassy_net_driver::{self, Capabilities, Driver, HardwareAddress, LinkState};
use log::*;
pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
@@ -19,6 +19,7 @@ const ETHERNET_HEADER_LEN: usize = 14;
#[repr(C)]
#[derive(Debug)]
+#[allow(non_camel_case_types)]
struct ifreq {
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
@@ -180,8 +181,8 @@ impl Driver for TunTapDevice {
LinkState::Up
}
- fn ethernet_address(&self) -> [u8; 6] {
- [0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
+ fn hardware_address(&self) -> HardwareAddress {
+ HardwareAddress::Ethernet([0x02, 0x03, 0x04, 0x05, 0x06, 0x07])
}
}
diff --git a/embassy-net-w5500/README.md b/embassy-net-w5500/README.md
deleted file mode 100644
index 9eaf4b70..00000000
--- a/embassy-net-w5500/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# WIZnet W5500 `embassy-net` integration
-
-[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet W5500 SPI ethernet chip, operating in MACRAW mode.
-
-Supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async)
-
-See [`examples`](https://github.com/kalkyl/embassy-net-w5500/tree/main/examples) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module.
\ No newline at end of file
diff --git a/embassy-net-w5500/src/device.rs b/embassy-net-w5500/src/device.rs
deleted file mode 100644
index 9874df0d..00000000
--- a/embassy-net-w5500/src/device.rs
+++ /dev/null
@@ -1,131 +0,0 @@
-use embedded_hal_async::spi::SpiDevice;
-
-use crate::socket;
-use crate::spi::SpiInterface;
-
-pub const MODE: u16 = 0x00;
-pub const MAC: u16 = 0x09;
-pub const SOCKET_INTR: u16 = 0x18;
-pub const PHY_CFG: u16 = 0x2E;
-
-#[repr(u8)]
-pub enum RegisterBlock {
- Common = 0x00,
- Socket0 = 0x01,
- TxBuf = 0x02,
- RxBuf = 0x03,
-}
-
-/// W5500 in MACRAW mode
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct W5500 {
- bus: SpiInterface,
-}
-
-impl W5500 {
- /// Create and initialize the W5500 driver
- pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result, SPI::Error> {
- let mut bus = SpiInterface(spi);
- // Reset device
- bus.write_frame(RegisterBlock::Common, MODE, &[0x80]).await?;
-
- // Enable interrupt pin
- bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]).await?;
- // Enable receive interrupt
- bus.write_frame(
- RegisterBlock::Socket0,
- socket::SOCKET_INTR_MASK,
- &[socket::Interrupt::Receive as u8],
- )
- .await?;
-
- // Set MAC address
- bus.write_frame(RegisterBlock::Common, MAC, &mac_addr).await?;
-
- // Set the raw socket RX/TX buffer sizes to 16KB
- bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16])
- .await?;
- bus.write_frame(RegisterBlock::Socket0, socket::RXBUF_SIZE, &[16])
- .await?;
-
- // MACRAW mode with MAC filtering.
- let mode: u8 = (1 << 2) | (1 << 7);
- bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]).await?;
- socket::command(&mut bus, socket::Command::Open).await?;
-
- Ok(Self { bus })
- }
-
- /// Read bytes from the RX buffer. Returns the number of bytes read.
- async fn read_bytes(&mut self, buffer: &mut [u8], offset: u16) -> Result {
- let rx_size = socket::get_rx_size(&mut self.bus).await? as usize;
-
- let read_buffer = if rx_size > buffer.len() + offset as usize {
- buffer
- } else {
- &mut buffer[..rx_size - offset as usize]
- };
-
- let read_ptr = socket::get_rx_read_ptr(&mut self.bus).await?.wrapping_add(offset);
- self.bus.read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer).await?;
- socket::set_rx_read_ptr(&mut self.bus, read_ptr.wrapping_add(read_buffer.len() as u16)).await?;
-
- Ok(read_buffer.len())
- }
-
- /// Read an ethernet frame from the device. Returns the number of bytes read.
- pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result {
- let rx_size = socket::get_rx_size(&mut self.bus).await? as usize;
- if rx_size == 0 {
- return Ok(0);
- }
-
- socket::reset_interrupt(&mut self.bus, socket::Interrupt::Receive).await?;
-
- // First two bytes gives the size of the received ethernet frame
- let expected_frame_size: usize = {
- let mut frame_bytes = [0u8; 2];
- assert!(self.read_bytes(&mut frame_bytes[..], 0).await? == 2);
- u16::from_be_bytes(frame_bytes) as usize - 2
- };
-
- // Read the ethernet frame
- let read_buffer = if frame.len() > expected_frame_size {
- &mut frame[..expected_frame_size]
- } else {
- frame
- };
-
- let recvd_frame_size = self.read_bytes(read_buffer, 2).await?;
-
- // Register RX as completed
- socket::command(&mut self.bus, socket::Command::Receive).await?;
-
- // If the whole frame wasn't read, drop it
- if recvd_frame_size < expected_frame_size {
- Ok(0)
- } else {
- Ok(recvd_frame_size)
- }
- }
-
- /// Write an ethernet frame to the device. Returns number of bytes written
- pub async fn write_frame(&mut self, frame: &[u8]) -> Result {
- while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {}
- let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?;
- self.bus.write_frame(RegisterBlock::TxBuf, write_ptr, frame).await?;
- socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?;
- socket::command(&mut self.bus, socket::Command::Send).await?;
- Ok(frame.len())
- }
-
- pub async fn is_link_up(&mut self) -> bool {
- let mut link = [0];
- self.bus
- .read_frame(RegisterBlock::Common, PHY_CFG, &mut link)
- .await
- .ok();
- link[0] & 1 == 1
- }
-}
diff --git a/embassy-net-w5500/src/socket.rs b/embassy-net-w5500/src/socket.rs
deleted file mode 100644
index 3d65583c..00000000
--- a/embassy-net-w5500/src/socket.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-use embedded_hal_async::spi::SpiDevice;
-
-use crate::device::RegisterBlock;
-use crate::spi::SpiInterface;
-
-pub const MODE: u16 = 0x00;
-pub const COMMAND: u16 = 0x01;
-pub const RXBUF_SIZE: u16 = 0x1E;
-pub const TXBUF_SIZE: u16 = 0x1F;
-pub const TX_FREE_SIZE: u16 = 0x20;
-pub const TX_DATA_WRITE_PTR: u16 = 0x24;
-pub const RECVD_SIZE: u16 = 0x26;
-pub const RX_DATA_READ_PTR: u16 = 0x28;
-pub const SOCKET_INTR_MASK: u16 = 0x2C;
-
-#[repr(u8)]
-pub enum Command {
- Open = 0x01,
- Send = 0x20,
- Receive = 0x40,
-}
-
-pub const INTR: u16 = 0x02;
-#[repr(u8)]
-pub enum Interrupt {
- Receive = 0b00100_u8,
-}
-
-pub async fn reset_interrupt(bus: &mut SpiInterface, code: Interrupt) -> Result<(), SPI::Error> {
- let data = [code as u8];
- bus.write_frame(RegisterBlock::Socket0, INTR, &data).await
-}
-
-pub async fn get_tx_write_ptr(bus: &mut SpiInterface) -> Result {
- let mut data = [0u8; 2];
- bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data)
- .await?;
- Ok(u16::from_be_bytes(data))
-}
-
-pub async fn set_tx_write_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> {
- let data = ptr.to_be_bytes();
- bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data).await
-}
-
-pub async fn get_rx_read_ptr(bus: &mut SpiInterface) -> Result {
- let mut data = [0u8; 2];
- bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data)
- .await?;
- Ok(u16::from_be_bytes(data))
-}
-
-pub async fn set_rx_read_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> {
- let data = ptr.to_be_bytes();
- bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data).await
-}
-
-pub async fn command(bus: &mut SpiInterface, command: Command) -> Result<(), SPI::Error> {
- let data = [command as u8];
- bus.write_frame(RegisterBlock::Socket0, COMMAND, &data).await
-}
-
-pub async fn get_rx_size(bus: &mut SpiInterface) -> Result {
- loop {
- // Wait until two sequential reads are equal
- let mut res0 = [0u8; 2];
- bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0).await?;
- let mut res1 = [0u8; 2];
- bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1).await?;
- if res0 == res1 {
- break Ok(u16::from_be_bytes(res0));
- }
- }
-}
-
-pub async fn get_tx_free_size(bus: &mut SpiInterface) -> Result {
- let mut data = [0; 2];
- bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data).await?;
- Ok(u16::from_be_bytes(data))
-}
diff --git a/embassy-net-w5500/src/spi.rs b/embassy-net-w5500/src/spi.rs
deleted file mode 100644
index 07749d6b..00000000
--- a/embassy-net-w5500/src/spi.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use embedded_hal_async::spi::{Operation, SpiDevice};
-
-use crate::device::RegisterBlock;
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct SpiInterface(pub SPI);
-
-impl