commit
f033089625
41
.gitattributes
vendored
Normal file
41
.gitattributes
vendored
Normal file
@ -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
|
17
.github/ci/crlf.sh
vendored
Executable file
17
.github/ci/crlf.sh
vendored
Executable file
@ -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
|
13
.github/ci/doc.sh
vendored
13
.github/ci/doc.sh
vendored
@ -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
|
||||
|
||||
# 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
|
||||
|
2
.github/ci/test.sh
vendored
2
.github/ci/test.sh
vendored
@ -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
|
||||
|
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
@ -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",
|
||||
|
11
README.md
11
README.md
@ -33,6 +33,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
|
||||
|
||||
- **Bluetooth** -
|
||||
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
The <a href="https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan">embassy-stm32-wpan</a> crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers.
|
||||
|
||||
- **LoRa** -
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> 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
|
||||
|
19
ci.sh
19
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 \
|
||||
|
@ -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 \
|
||||
|
Binary file not shown.
Binary file not shown.
@ -3,3 +3,7 @@
|
||||
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)
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62
|
||||
|
@ -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<PIO> = 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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)]
|
||||
|
@ -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);
|
||||
|
@ -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"] }
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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<DFU: NorFlash, STATE: NorFlash> {
|
||||
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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
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<DFU, STATE>) -> Self {
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, 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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, 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, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
|
||||
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<State, FirmwareUpdaterError> {
|
||||
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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// 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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
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::<Sha512>(_update_len, _aligned, &mut message).await?;
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
@ -143,7 +121,8 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
@ -156,7 +135,7 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
}
|
||||
|
||||
/// 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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
/// # 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<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// 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<State, FirmwareUpdaterError> {
|
||||
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::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
|
||||
|
@ -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<DFU: NorFlash, STATE: NorFlash> {
|
||||
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<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
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<DFU, STATE>) -> 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<DFU, STATE>, 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<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, 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, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
|
||||
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<State, FirmwareUpdaterError> {
|
||||
self.state.get_state()
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
@ -94,23 +83,16 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// 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<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
@ -145,7 +128,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
@ -158,7 +142,7 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
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<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
}
|
||||
|
||||
/// 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<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
/// # 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<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
///
|
||||
/// 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<State, FirmwareUpdaterError> {
|
||||
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::<NoopRawMutex, _>::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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
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();
|
||||
},
|
||||
&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 {
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.mark_booted(&mut aligned)).unwrap();
|
||||
},
|
||||
&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 {
|
||||
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();
|
||||
},
|
||||
&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 {
|
||||
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();
|
||||
},
|
||||
&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 {
|
||||
let mut aligned = [0; 4];
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
|
||||
let mut aligned = [0; 4];
|
||||
},
|
||||
&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());
|
||||
}
|
||||
|
@ -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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = PAGE_SIZE> {
|
||||
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE>;
|
||||
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
|
||||
{
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> 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<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware.
|
||||
pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
|
||||
config: BootLoaderConfig<ACTIVE, DFU, STATE>,
|
||||
) -> 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
/// 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
///
|
||||
/// 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: FLASH,
|
||||
wdt: wdt::WatchdogHandle,
|
||||
}
|
||||
|
||||
impl<'d> WatchdogFlash<'d> {
|
||||
impl<FLASH> WatchdogFlash<FLASH> {
|
||||
/// 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 = <Nvmc<'d> as ErrorType>::Error;
|
||||
impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> {
|
||||
type Error = FLASH::Error;
|
||||
}
|
||||
|
||||
impl<'d> NorFlash for WatchdogFlash<'d> {
|
||||
const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE;
|
||||
impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> {
|
||||
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 = <Nvmc<'d> as ReadNorFlash>::READ_SIZE;
|
||||
impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> {
|
||||
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)
|
||||
|
@ -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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = ERASE_SIZE> {
|
||||
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>;
|
||||
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
|
||||
{
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> 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<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
|
||||
pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
|
||||
config: BootLoaderConfig<ACTIVE, DFU, STATE>,
|
||||
) -> 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
///
|
||||
/// 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
|
||||
/// 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 = <Flash<'d, FLASH, SIZE> as ErrorType>::Error;
|
||||
type Error = <Flash<'d, FLASH, Blocking, SIZE> as ErrorType>::Error;
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
|
||||
const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_SIZE;
|
||||
const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> 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 = <Flash<'d, FLASH, SIZE> as ReadNorFlash>::READ_SIZE;
|
||||
const READ_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> 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()
|
||||
|
@ -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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> {
|
||||
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
pub struct BootLoader;
|
||||
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
|
||||
{
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>(
|
||||
config: BootLoaderConfig<ACTIVE, DFU, STATE>,
|
||||
) -> 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<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
///
|
||||
/// 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();
|
||||
|
@ -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"
|
||||
|
@ -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<T, E> embedded_hal_1::serial::ErrorType for BlockingAsync<T>
|
||||
where
|
||||
T: serial::Read<u8, Error = E>,
|
||||
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};
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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() };
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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<Closure<dyn FnMut(JsValue)>>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -147,10 +147,7 @@ impl<F: Future + 'static> TaskStorage<F> {
|
||||
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
|
||||
let task = AvailableTask::claim(self);
|
||||
match task {
|
||||
Some(task) => {
|
||||
let task = task.initialize(future);
|
||||
unsafe { SpawnToken::<F>::new(task) }
|
||||
}
|
||||
Some(task) => task.initialize(future),
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
@ -186,12 +183,16 @@ impl<F: Future + 'static> TaskStorage<F> {
|
||||
}
|
||||
}
|
||||
|
||||
struct AvailableTask<F: Future + 'static> {
|
||||
/// An uninitialized [`TaskStorage`].
|
||||
pub struct AvailableTask<F: Future + 'static> {
|
||||
task: &'static TaskStorage<F>,
|
||||
}
|
||||
|
||||
impl<F: Future + 'static> AvailableTask<F> {
|
||||
fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
|
||||
/// Try to claim a [`TaskStorage`].
|
||||
///
|
||||
/// This function returns `None` if a task has already been spawned and has not finished running.
|
||||
pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
|
||||
task.raw
|
||||
.state
|
||||
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
|
||||
@ -199,61 +200,30 @@ impl<F: Future + 'static> AvailableTask<F> {
|
||||
.map(|_| Self { task })
|
||||
}
|
||||
|
||||
fn initialize(self, future: impl FnOnce() -> F) -> TaskRef {
|
||||
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
|
||||
unsafe {
|
||||
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
|
||||
self.task.future.write(future());
|
||||
}
|
||||
TaskRef::new(self.task)
|
||||
|
||||
let task = TaskRef::new(self.task);
|
||||
|
||||
SpawnToken::new(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage that can hold up to N tasks of the same type.
|
||||
///
|
||||
/// This is essentially a `[TaskStorage<F>; N]`.
|
||||
pub struct TaskPool<F: Future + 'static, const N: usize> {
|
||||
pool: [TaskStorage<F>; N],
|
||||
/// Initialize the [`TaskStorage`] to run the given future.
|
||||
pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken<F> {
|
||||
self.initialize_impl::<F>(future)
|
||||
}
|
||||
|
||||
impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
|
||||
/// Create a new TaskPool, with all tasks in non-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pool: [TaskStorage::NEW; N],
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to spawn a task in the pool.
|
||||
/// Initialize the [`TaskStorage`] to run the given future.
|
||||
///
|
||||
/// See [`TaskStorage::spawn()`] for details.
|
||||
/// # Safety
|
||||
///
|
||||
/// This will loop over the pool and spawn the task in the first storage that
|
||||
/// is currently free. If none is free, a "poisoned" SpawnToken is returned,
|
||||
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
|
||||
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
|
||||
let task = self.pool.iter().find_map(AvailableTask::claim);
|
||||
match task {
|
||||
Some(task) => {
|
||||
let task = task.initialize(future);
|
||||
unsafe { SpawnToken::<F>::new(task) }
|
||||
}
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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`
|
||||
/// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
|
||||
/// is an `async fn`, NOT a hand-written `Future`.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized>
|
||||
where
|
||||
FutFn: FnOnce() -> F,
|
||||
{
|
||||
pub unsafe fn __initialize_async_fn<FutFn>(self, future: impl FnOnce() -> F) -> SpawnToken<FutFn> {
|
||||
// When send-spawning a task, we construct the future in this thread, and effectively
|
||||
// "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory,
|
||||
// send-spawning should require the future `F` to be `Send`.
|
||||
@ -279,66 +249,73 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
|
||||
//
|
||||
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
|
||||
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`.
|
||||
|
||||
let task = self.pool.iter().find_map(AvailableTask::claim);
|
||||
match task {
|
||||
Some(task) => {
|
||||
let task = task.initialize(future);
|
||||
unsafe { SpawnToken::<FutFn>::new(task) }
|
||||
self.initialize_impl::<FutFn>(future)
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage that can hold up to N tasks of the same type.
|
||||
///
|
||||
/// This is essentially a `[TaskStorage<F>; N]`.
|
||||
pub struct TaskPool<F: Future + 'static, const N: usize> {
|
||||
pool: [TaskStorage<F>; N],
|
||||
}
|
||||
|
||||
impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
|
||||
/// Create a new TaskPool, with all tasks in non-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pool: [TaskStorage::NEW; N],
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> SpawnToken<T> {
|
||||
match self.pool.iter().find_map(AvailableTask::claim) {
|
||||
Some(task) => task.initialize_impl::<T>(future),
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to spawn a task in the pool.
|
||||
///
|
||||
/// See [`TaskStorage::spawn()`] for details.
|
||||
///
|
||||
/// This will loop over the pool and spawn the task in the first storage that
|
||||
/// is currently free. If none is free, a "poisoned" SpawnToken is returned,
|
||||
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
|
||||
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
|
||||
self.spawn_impl::<F>(future)
|
||||
}
|
||||
|
||||
/// Like spawn(), but allows the task to be send-spawned if the args are Send even if
|
||||
/// the future is !Send.
|
||||
///
|
||||
/// Not covered by semver guarantees. DO NOT call this directly. Intended to be used
|
||||
/// by the Embassy macros ONLY.
|
||||
///
|
||||
/// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
|
||||
/// is an `async fn`, NOT a hand-written `Future`.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized>
|
||||
where
|
||||
FutFn: FnOnce() -> F,
|
||||
{
|
||||
// See the comment in AvailableTask::__initialize_async_fn for explanation.
|
||||
self.spawn_impl::<FutFn>(future)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) 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;
|
||||
|
||||
|
@ -33,7 +33,8 @@ impl<S> SpawnToken<S> {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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"
|
16
embassy-hal-internal/README.md
Normal file
16
embassy-hal-internal/README.md
Normal file
@ -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
|
||||
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||
|
||||
at your option.
|
@ -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;
|
@ -116,6 +116,7 @@ macro_rules! impl_peripheral {
|
||||
|
||||
#[inline]
|
||||
unsafe fn clone_unchecked(&self) -> Self::P {
|
||||
#[allow(clippy::needless_update)]
|
||||
$type { ..*self }
|
||||
}
|
||||
}
|
@ -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"}
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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<MTU, N_RX, N_TX>,
|
||||
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 {
|
||||
|
@ -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<T: ?Sized + Driver> Driver for &mut T {
|
||||
@ -75,8 +87,8 @@ impl<T: ?Sized + Driver> 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 {
|
||||
|
23
embassy-net-enc28j60/Cargo.toml
Normal file
23
embassy-net-enc28j60/Cargo.toml
Normal file
@ -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"
|
19
embassy-net-enc28j60/README.md
Normal file
19
embassy-net-enc28j60/README.md
Normal file
@ -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.
|
69
embassy-net-enc28j60/src/bank0.rs
Normal file
69
embassy-net-enc28j60/src/bank0.rs
Normal file
@ -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<super::Register> for Register {
|
||||
fn into(self) -> super::Register {
|
||||
super::Register::Bank0(self)
|
||||
}
|
||||
}
|
84
embassy-net-enc28j60/src/bank1.rs
Normal file
84
embassy-net-enc28j60/src/bank1.rs
Normal file
@ -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<super::Register> 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,
|
||||
});
|
86
embassy-net-enc28j60/src/bank2.rs
Normal file
86
embassy-net-enc28j60/src/bank2.rs
Normal file
@ -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<super::Register> 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,
|
||||
});
|
53
embassy-net-enc28j60/src/bank3.rs
Normal file
53
embassy-net-enc28j60/src/bank3.rs
Normal file
@ -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<super::Register> for Register {
|
||||
fn into(self) -> super::Register {
|
||||
super::Register::Bank3(self)
|
||||
}
|
||||
}
|
106
embassy-net-enc28j60/src/common.rs
Normal file
106
embassy-net-enc28j60/src/common.rs
Normal file
@ -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<super::Register> 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,
|
||||
});
|
225
embassy-net-enc28j60/src/fmt.rs
Normal file
225
embassy-net-enc28j60/src/fmt.rs
Normal file
@ -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<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
30
embassy-net-enc28j60/src/header.rs
Normal file
30
embassy-net-enc28j60/src/header.rs
Normal file
@ -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,
|
||||
});
|
717
embassy-net-enc28j60/src/lib.rs
Normal file
717
embassy-net-enc28j60/src/lib.rs
Normal file
@ -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<S, O> {
|
||||
mac_addr: [u8; 6],
|
||||
|
||||
spi: S,
|
||||
rst: Option<O>,
|
||||
|
||||
bank: Bank,
|
||||
|
||||
// address of the next packet in buffer memory
|
||||
next_packet: u16,
|
||||
}
|
||||
|
||||
impl<S, O> Enc28j60<S, O>
|
||||
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<O>, 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<R>(&mut self, register: R) -> u8
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
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<R>(&mut self, register: R, value: u8)
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
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<R, F>(&mut self, register: R, f: F)
|
||||
where
|
||||
F: FnOnce(u8) -> u8,
|
||||
R: Into<Register>,
|
||||
{
|
||||
self._modify_control_register(register.into(), f)
|
||||
}
|
||||
|
||||
fn _modify_control_register<F>(&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<R>(&mut self, register: R, mask: u8)
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
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<R>(&mut self, register: R, mask: u8)
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
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<u16>, 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<u16>, 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<Bank> {
|
||||
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<S, O> embassy_net_driver::Driver for Enc28j60<S, O>
|
||||
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<Self::TxToken<'_>> {
|
||||
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<R, F>(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<S, O>,
|
||||
buf: &'a mut [u8],
|
||||
}
|
||||
|
||||
impl<'a, S, O> embassy_net_driver::TxToken for TxToken<'a, S, O>
|
||||
where
|
||||
S: SpiDevice,
|
||||
O: OutputPin,
|
||||
{
|
||||
fn consume<R, F>(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
|
||||
}
|
||||
}
|
89
embassy-net-enc28j60/src/macros.rs
Normal file
89
embassy-net-enc28j60/src/macros.rs
Normal file
@ -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<MODE> {
|
||||
bits: $uxx,
|
||||
_mode: ::core::marker::PhantomData<MODE>,
|
||||
}
|
||||
|
||||
impl $REGISTER<super::traits::Mask> {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn mask() -> $REGISTER<super::traits::Mask> {
|
||||
$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<super::traits::W> {
|
||||
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<super::traits::R> {
|
||||
$REGISTER { bits, _mode: ::core::marker::PhantomData }
|
||||
}
|
||||
|
||||
impl $REGISTER<super::traits::R> {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn modify(self) -> $REGISTER<super::traits::W> {
|
||||
$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<super::traits::W> {
|
||||
#[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
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
35
embassy-net-enc28j60/src/phy.rs
Normal file
35
embassy-net-enc28j60/src/phy.rs
Normal file
@ -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,
|
||||
});
|
57
embassy-net-enc28j60/src/traits.rs
Normal file
57
embassy-net-enc28j60/src/traits.rs
Normal file
@ -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<u8> {
|
||||
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;
|
@ -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"]
|
@ -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 {
|
||||
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 {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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::<CtrlMsg>(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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
19
embassy-net-tuntap/Cargo.toml
Normal file
19
embassy-net-tuntap/Cargo.toml
Normal file
@ -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"
|
17
embassy-net-tuntap/README.md
Normal file
17
embassy-net-tuntap/README.md
Normal file
@ -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.
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
@ -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<SPI> {
|
||||
bus: SpiInterface<SPI>,
|
||||
}
|
||||
|
||||
impl<SPI: SpiDevice> W5500<SPI> {
|
||||
/// Create and initialize the W5500 driver
|
||||
pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result<W5500<SPI>, 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<usize, SPI::Error> {
|
||||
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<usize, SPI::Error> {
|
||||
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<usize, SPI::Error> {
|
||||
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
|
||||
}
|
||||
}
|
@ -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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, 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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
|
||||
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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, 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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
|
||||
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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, 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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, command: Command) -> Result<(), SPI::Error> {
|
||||
let data = [command as u8];
|
||||
bus.write_frame(RegisterBlock::Socket0, COMMAND, &data).await
|
||||
}
|
||||
|
||||
pub async fn get_rx_size<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
|
||||
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<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
|
||||
let mut data = [0; 2];
|
||||
bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data).await?;
|
||||
Ok(u16::from_be_bytes(data))
|
||||
}
|
@ -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<SPI>(pub SPI);
|
||||
|
||||
impl<SPI: SpiDevice> SpiInterface<SPI> {
|
||||
pub async fn read_frame(&mut self, block: RegisterBlock, address: u16, data: &mut [u8]) -> Result<(), SPI::Error> {
|
||||
let address_phase = address.to_be_bytes();
|
||||
let control_phase = [(block as u8) << 3];
|
||||
let operations = &mut [
|
||||
Operation::Write(&address_phase),
|
||||
Operation::Write(&control_phase),
|
||||
Operation::TransferInPlace(data),
|
||||
];
|
||||
self.0.transaction(operations).await
|
||||
}
|
||||
|
||||
pub async fn write_frame(&mut self, block: RegisterBlock, address: u16, data: &[u8]) -> Result<(), SPI::Error> {
|
||||
let address_phase = address.to_be_bytes();
|
||||
let control_phase = [(block as u8) << 3 | 0b0000_0100];
|
||||
let data_phase = data;
|
||||
let operations = &mut [
|
||||
Operation::Write(&address_phase[..]),
|
||||
Operation::Write(&control_phase),
|
||||
Operation::Write(&data_phase),
|
||||
];
|
||||
self.0.transaction(operations).await
|
||||
}
|
||||
}
|
@ -1,21 +1,22 @@
|
||||
[package]
|
||||
name = "embassy-net-w5500"
|
||||
name = "embassy-net-wiznet"
|
||||
version = "0.1.0"
|
||||
description = "embassy-net driver for the W5500 ethernet chip"
|
||||
keywords = ["embedded", "w5500", "embassy-net", "embedded-hal-async", "ethernet", "async"]
|
||||
description = "embassy-net driver for WIZnet SPI Ethernet chips"
|
||||
keywords = ["embedded", "wiznet", "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-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" }
|
||||
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-w5500-v$VERSION/embassy-net-w5500/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-w5500/src/"
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-wiznet/src/"
|
||||
target = "thumbv7em-none-eabi"
|
||||
features = ["defmt"]
|
27
embassy-net-wiznet/README.md
Normal file
27
embassy-net-wiznet/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# WIZnet `embassy-net` integration
|
||||
|
||||
[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet SPI ethernet chips, operating in MACRAW mode.
|
||||
|
||||
See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module.
|
||||
|
||||
## Supported chips
|
||||
|
||||
- W5500
|
||||
- W5100S
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async).
|
||||
|
||||
|
||||
## 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.
|
48
embassy-net-wiznet/src/chip/mod.rs
Normal file
48
embassy-net-wiznet/src/chip/mod.rs
Normal file
@ -0,0 +1,48 @@
|
||||
mod w5500;
|
||||
pub use w5500::W5500;
|
||||
mod w5100s;
|
||||
pub use w5100s::W5100S;
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
pub trait Chip {
|
||||
type Address;
|
||||
|
||||
const COMMON_MODE: Self::Address;
|
||||
const COMMON_MAC: Self::Address;
|
||||
const COMMON_SOCKET_INTR: Self::Address;
|
||||
const COMMON_PHY_CFG: Self::Address;
|
||||
const SOCKET_MODE: Self::Address;
|
||||
const SOCKET_COMMAND: Self::Address;
|
||||
const SOCKET_RXBUF_SIZE: Self::Address;
|
||||
const SOCKET_TXBUF_SIZE: Self::Address;
|
||||
const SOCKET_TX_FREE_SIZE: Self::Address;
|
||||
const SOCKET_TX_DATA_WRITE_PTR: Self::Address;
|
||||
const SOCKET_RECVD_SIZE: Self::Address;
|
||||
const SOCKET_RX_DATA_READ_PTR: Self::Address;
|
||||
const SOCKET_INTR_MASK: Self::Address;
|
||||
const SOCKET_INTR: Self::Address;
|
||||
|
||||
const SOCKET_MODE_VALUE: u8;
|
||||
|
||||
const BUF_SIZE: u16;
|
||||
const AUTO_WRAP: bool;
|
||||
|
||||
fn rx_addr(addr: u16) -> Self::Address;
|
||||
fn tx_addr(addr: u16) -> Self::Address;
|
||||
|
||||
async fn bus_read<SPI: SpiDevice>(
|
||||
spi: &mut SPI,
|
||||
address: Self::Address,
|
||||
data: &mut [u8],
|
||||
) -> Result<(), SPI::Error>;
|
||||
async fn bus_write<SPI: SpiDevice>(
|
||||
spi: &mut SPI,
|
||||
address: Self::Address,
|
||||
data: &[u8],
|
||||
) -> Result<(), SPI::Error>;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Chip: sealed::Chip {}
|
61
embassy-net-wiznet/src/chip/w5100s.rs
Normal file
61
embassy-net-wiznet/src/chip/w5100s.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use embedded_hal_async::spi::{Operation, SpiDevice};
|
||||
|
||||
const SOCKET_BASE: u16 = 0x400;
|
||||
const TX_BASE: u16 = 0x4000;
|
||||
const RX_BASE: u16 = 0x6000;
|
||||
|
||||
pub enum W5100S {}
|
||||
|
||||
impl super::Chip for W5100S {}
|
||||
impl super::sealed::Chip for W5100S {
|
||||
type Address = u16;
|
||||
|
||||
const COMMON_MODE: Self::Address = 0x00;
|
||||
const COMMON_MAC: Self::Address = 0x09;
|
||||
const COMMON_SOCKET_INTR: Self::Address = 0x16;
|
||||
const COMMON_PHY_CFG: Self::Address = 0x3c;
|
||||
|
||||
const SOCKET_MODE: Self::Address = SOCKET_BASE + 0x00;
|
||||
const SOCKET_COMMAND: Self::Address = SOCKET_BASE + 0x01;
|
||||
const SOCKET_RXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1E;
|
||||
const SOCKET_TXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1F;
|
||||
const SOCKET_TX_FREE_SIZE: Self::Address = SOCKET_BASE + 0x20;
|
||||
const SOCKET_TX_DATA_WRITE_PTR: Self::Address = SOCKET_BASE + 0x24;
|
||||
const SOCKET_RECVD_SIZE: Self::Address = SOCKET_BASE + 0x26;
|
||||
const SOCKET_RX_DATA_READ_PTR: Self::Address = SOCKET_BASE + 0x28;
|
||||
const SOCKET_INTR_MASK: Self::Address = SOCKET_BASE + 0x2C;
|
||||
const SOCKET_INTR: Self::Address = SOCKET_BASE + 0x02;
|
||||
|
||||
const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 6);
|
||||
|
||||
const BUF_SIZE: u16 = 0x2000;
|
||||
const AUTO_WRAP: bool = false;
|
||||
|
||||
fn rx_addr(addr: u16) -> Self::Address {
|
||||
RX_BASE + addr
|
||||
}
|
||||
|
||||
fn tx_addr(addr: u16) -> Self::Address {
|
||||
TX_BASE + addr
|
||||
}
|
||||
|
||||
async fn bus_read<SPI: SpiDevice>(
|
||||
spi: &mut SPI,
|
||||
address: Self::Address,
|
||||
data: &mut [u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
spi.transaction(&mut [
|
||||
Operation::Write(&[0x0F, (address >> 8) as u8, address as u8]),
|
||||
Operation::Read(data),
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn bus_write<SPI: SpiDevice>(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> {
|
||||
spi.transaction(&mut [
|
||||
Operation::Write(&[0xF0, (address >> 8) as u8, address as u8]),
|
||||
Operation::Write(data),
|
||||
])
|
||||
.await
|
||||
}
|
||||
}
|
72
embassy-net-wiznet/src/chip/w5500.rs
Normal file
72
embassy-net-wiznet/src/chip/w5500.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use embedded_hal_async::spi::{Operation, SpiDevice};
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum RegisterBlock {
|
||||
Common = 0x00,
|
||||
Socket0 = 0x01,
|
||||
TxBuf = 0x02,
|
||||
RxBuf = 0x03,
|
||||
}
|
||||
|
||||
pub enum W5500 {}
|
||||
|
||||
impl super::Chip for W5500 {}
|
||||
impl super::sealed::Chip for W5500 {
|
||||
type Address = (RegisterBlock, u16);
|
||||
|
||||
const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x00);
|
||||
const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x09);
|
||||
const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x18);
|
||||
const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x2E);
|
||||
|
||||
const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x00);
|
||||
const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x01);
|
||||
const SOCKET_RXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1E);
|
||||
const SOCKET_TXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1F);
|
||||
const SOCKET_TX_FREE_SIZE: Self::Address = (RegisterBlock::Socket0, 0x20);
|
||||
const SOCKET_TX_DATA_WRITE_PTR: Self::Address = (RegisterBlock::Socket0, 0x24);
|
||||
const SOCKET_RECVD_SIZE: Self::Address = (RegisterBlock::Socket0, 0x26);
|
||||
const SOCKET_RX_DATA_READ_PTR: Self::Address = (RegisterBlock::Socket0, 0x28);
|
||||
const SOCKET_INTR_MASK: Self::Address = (RegisterBlock::Socket0, 0x2C);
|
||||
const SOCKET_INTR: Self::Address = (RegisterBlock::Socket0, 0x02);
|
||||
|
||||
const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 7);
|
||||
|
||||
const BUF_SIZE: u16 = 0x4000;
|
||||
const AUTO_WRAP: bool = true;
|
||||
|
||||
fn rx_addr(addr: u16) -> Self::Address {
|
||||
(RegisterBlock::RxBuf, addr)
|
||||
}
|
||||
|
||||
fn tx_addr(addr: u16) -> Self::Address {
|
||||
(RegisterBlock::TxBuf, addr)
|
||||
}
|
||||
|
||||
async fn bus_read<SPI: SpiDevice>(
|
||||
spi: &mut SPI,
|
||||
address: Self::Address,
|
||||
data: &mut [u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
let address_phase = address.1.to_be_bytes();
|
||||
let control_phase = [(address.0 as u8) << 3];
|
||||
let operations = &mut [
|
||||
Operation::Write(&address_phase),
|
||||
Operation::Write(&control_phase),
|
||||
Operation::TransferInPlace(data),
|
||||
];
|
||||
spi.transaction(operations).await
|
||||
}
|
||||
|
||||
async fn bus_write<SPI: SpiDevice>(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> {
|
||||
let address_phase = address.1.to_be_bytes();
|
||||
let control_phase = [(address.0 as u8) << 3 | 0b0000_0100];
|
||||
let data_phase = data;
|
||||
let operations = &mut [
|
||||
Operation::Write(&address_phase[..]),
|
||||
Operation::Write(&control_phase),
|
||||
Operation::Write(&data_phase),
|
||||
];
|
||||
spi.transaction(operations).await
|
||||
}
|
||||
}
|
195
embassy-net-wiznet/src/device.rs
Normal file
195
embassy-net-wiznet/src/device.rs
Normal file
@ -0,0 +1,195 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
use crate::chip::Chip;
|
||||
|
||||
#[repr(u8)]
|
||||
enum Command {
|
||||
Open = 0x01,
|
||||
Send = 0x20,
|
||||
Receive = 0x40,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
enum Interrupt {
|
||||
Receive = 0b00100_u8,
|
||||
}
|
||||
|
||||
/// Wiznet chip in MACRAW mode
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub(crate) struct WiznetDevice<C, SPI> {
|
||||
spi: SPI,
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C: Chip, SPI: SpiDevice> WiznetDevice<C, SPI> {
|
||||
/// Create and initialize the driver
|
||||
pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result<Self, SPI::Error> {
|
||||
let mut this = Self {
|
||||
spi,
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
|
||||
// Reset device
|
||||
this.bus_write(C::COMMON_MODE, &[0x80]).await?;
|
||||
|
||||
// Enable interrupt pin
|
||||
this.bus_write(C::COMMON_SOCKET_INTR, &[0x01]).await?;
|
||||
// Enable receive interrupt
|
||||
this.bus_write(C::SOCKET_INTR_MASK, &[Interrupt::Receive as u8]).await?;
|
||||
|
||||
// Set MAC address
|
||||
this.bus_write(C::COMMON_MAC, &mac_addr).await?;
|
||||
|
||||
// Set the raw socket RX/TX buffer sizes.
|
||||
let buf_kbs = (C::BUF_SIZE / 1024) as u8;
|
||||
this.bus_write(C::SOCKET_TXBUF_SIZE, &[buf_kbs]).await?;
|
||||
this.bus_write(C::SOCKET_RXBUF_SIZE, &[buf_kbs]).await?;
|
||||
|
||||
// MACRAW mode with MAC filtering.
|
||||
this.bus_write(C::SOCKET_MODE, &[C::SOCKET_MODE_VALUE]).await?;
|
||||
this.command(Command::Open).await?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
async fn bus_read(&mut self, address: C::Address, data: &mut [u8]) -> Result<(), SPI::Error> {
|
||||
C::bus_read(&mut self.spi, address, data).await
|
||||
}
|
||||
|
||||
async fn bus_write(&mut self, address: C::Address, data: &[u8]) -> Result<(), SPI::Error> {
|
||||
C::bus_write(&mut self.spi, address, data).await
|
||||
}
|
||||
|
||||
async fn reset_interrupt(&mut self, code: Interrupt) -> Result<(), SPI::Error> {
|
||||
let data = [code as u8];
|
||||
self.bus_write(C::SOCKET_INTR, &data).await
|
||||
}
|
||||
|
||||
async fn get_tx_write_ptr(&mut self) -> Result<u16, SPI::Error> {
|
||||
let mut data = [0u8; 2];
|
||||
self.bus_read(C::SOCKET_TX_DATA_WRITE_PTR, &mut data).await?;
|
||||
Ok(u16::from_be_bytes(data))
|
||||
}
|
||||
|
||||
async fn set_tx_write_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> {
|
||||
let data = ptr.to_be_bytes();
|
||||
self.bus_write(C::SOCKET_TX_DATA_WRITE_PTR, &data).await
|
||||
}
|
||||
|
||||
async fn get_rx_read_ptr(&mut self) -> Result<u16, SPI::Error> {
|
||||
let mut data = [0u8; 2];
|
||||
self.bus_read(C::SOCKET_RX_DATA_READ_PTR, &mut data).await?;
|
||||
Ok(u16::from_be_bytes(data))
|
||||
}
|
||||
|
||||
async fn set_rx_read_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> {
|
||||
let data = ptr.to_be_bytes();
|
||||
self.bus_write(C::SOCKET_RX_DATA_READ_PTR, &data).await
|
||||
}
|
||||
|
||||
async fn command(&mut self, command: Command) -> Result<(), SPI::Error> {
|
||||
let data = [command as u8];
|
||||
self.bus_write(C::SOCKET_COMMAND, &data).await
|
||||
}
|
||||
|
||||
async fn get_rx_size(&mut self) -> Result<u16, SPI::Error> {
|
||||
loop {
|
||||
// Wait until two sequential reads are equal
|
||||
let mut res0 = [0u8; 2];
|
||||
self.bus_read(C::SOCKET_RECVD_SIZE, &mut res0).await?;
|
||||
let mut res1 = [0u8; 2];
|
||||
self.bus_read(C::SOCKET_RECVD_SIZE, &mut res1).await?;
|
||||
if res0 == res1 {
|
||||
break Ok(u16::from_be_bytes(res0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_tx_free_size(&mut self) -> Result<u16, SPI::Error> {
|
||||
let mut data = [0; 2];
|
||||
self.bus_read(C::SOCKET_TX_FREE_SIZE, &mut data).await?;
|
||||
Ok(u16::from_be_bytes(data))
|
||||
}
|
||||
|
||||
/// Read bytes from the RX buffer.
|
||||
async fn read_bytes(&mut self, read_ptr: &mut u16, buffer: &mut [u8]) -> Result<(), SPI::Error> {
|
||||
if C::AUTO_WRAP {
|
||||
self.bus_read(C::rx_addr(*read_ptr), buffer).await?;
|
||||
} else {
|
||||
let addr = *read_ptr % C::BUF_SIZE;
|
||||
if addr as usize + buffer.len() <= C::BUF_SIZE as usize {
|
||||
self.bus_read(C::rx_addr(addr), buffer).await?;
|
||||
} else {
|
||||
let n = C::BUF_SIZE - addr;
|
||||
self.bus_read(C::rx_addr(addr), &mut buffer[..n as usize]).await?;
|
||||
self.bus_read(C::rx_addr(0), &mut buffer[n as usize..]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
*read_ptr = (*read_ptr).wrapping_add(buffer.len() as u16);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read an ethernet frame from the device. Returns the number of bytes read.
|
||||
pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result<usize, SPI::Error> {
|
||||
let rx_size = self.get_rx_size().await? as usize;
|
||||
if rx_size == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
self.reset_interrupt(Interrupt::Receive).await?;
|
||||
|
||||
let mut read_ptr = self.get_rx_read_ptr().await?;
|
||||
|
||||
// First two bytes gives the size of the received ethernet frame
|
||||
let expected_frame_size: usize = {
|
||||
let mut frame_bytes = [0u8; 2];
|
||||
self.read_bytes(&mut read_ptr, &mut frame_bytes).await?;
|
||||
u16::from_be_bytes(frame_bytes) as usize - 2
|
||||
};
|
||||
|
||||
// Read the ethernet frame
|
||||
self.read_bytes(&mut read_ptr, &mut frame[..expected_frame_size])
|
||||
.await?;
|
||||
|
||||
// Register RX as completed
|
||||
self.set_rx_read_ptr(read_ptr).await?;
|
||||
self.command(Command::Receive).await?;
|
||||
|
||||
Ok(expected_frame_size)
|
||||
}
|
||||
|
||||
/// Write an ethernet frame to the device. Returns number of bytes written
|
||||
pub async fn write_frame(&mut self, frame: &[u8]) -> Result<usize, SPI::Error> {
|
||||
while self.get_tx_free_size().await? < frame.len() as u16 {}
|
||||
let write_ptr = self.get_tx_write_ptr().await?;
|
||||
|
||||
if C::AUTO_WRAP {
|
||||
self.bus_write(C::tx_addr(write_ptr), frame).await?;
|
||||
} else {
|
||||
let addr = write_ptr % C::BUF_SIZE;
|
||||
if addr as usize + frame.len() <= C::BUF_SIZE as usize {
|
||||
self.bus_write(C::tx_addr(addr), frame).await?;
|
||||
} else {
|
||||
let n = C::BUF_SIZE - addr;
|
||||
self.bus_write(C::tx_addr(addr), &frame[..n as usize]).await?;
|
||||
self.bus_write(C::tx_addr(0), &frame[n as usize..]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
self.set_tx_write_ptr(write_ptr.wrapping_add(frame.len() as u16))
|
||||
.await?;
|
||||
self.command(Command::Send).await?;
|
||||
Ok(frame.len())
|
||||
}
|
||||
|
||||
pub async fn is_link_up(&mut self) -> bool {
|
||||
let mut link = [0];
|
||||
self.bus_read(C::COMMON_PHY_CFG, &mut link).await.ok();
|
||||
link[0] & 1 == 1
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip.
|
||||
//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for WIZnet ethernet chips.
|
||||
#![no_std]
|
||||
#![feature(async_fn_in_trait)]
|
||||
|
||||
pub mod chip;
|
||||
mod device;
|
||||
mod socket;
|
||||
mod spi;
|
||||
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_net_driver_channel as ch;
|
||||
@ -13,10 +13,12 @@ use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
use crate::device::W5500;
|
||||
use crate::chip::Chip;
|
||||
use crate::device::WiznetDevice;
|
||||
|
||||
const MTU: usize = 1514;
|
||||
|
||||
/// Type alias for the embassy-net driver for W5500
|
||||
/// Type alias for the embassy-net driver.
|
||||
pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
|
||||
|
||||
/// Internal state for the embassy-net integration.
|
||||
@ -33,18 +35,18 @@ impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Background runner for the W5500.
|
||||
/// Background runner for the driver.
|
||||
///
|
||||
/// You must call `.run()` in a background task for the W5500 to operate.
|
||||
pub struct Runner<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> {
|
||||
mac: W5500<SPI>,
|
||||
/// You must call `.run()` in a background task for the driver to operate.
|
||||
pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> {
|
||||
mac: WiznetDevice<C, SPI>,
|
||||
ch: ch::Runner<'d, MTU>,
|
||||
int: INT,
|
||||
_reset: RST,
|
||||
}
|
||||
|
||||
/// You must call this in a background task for the W5500 to operate.
|
||||
impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> {
|
||||
/// You must call this in a background task for the driver to operate.
|
||||
impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> {
|
||||
pub async fn run(mut self) -> ! {
|
||||
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
|
||||
loop {
|
||||
@ -78,25 +80,31 @@ impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a driver for using the W5500 with [`embassy-net`](https://crates.io/crates/embassy-net).
|
||||
pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>(
|
||||
/// Create a Wiznet ethernet chip driver for [`embassy-net`](https://crates.io/crates/embassy-net).
|
||||
///
|
||||
/// This returns two structs:
|
||||
/// - a `Device` that you must pass to the `embassy-net` stack.
|
||||
/// - a `Runner`. You must call `.run()` on it in a background task.
|
||||
pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin>(
|
||||
mac_addr: [u8; 6],
|
||||
state: &'a mut State<N_RX, N_TX>,
|
||||
spi_dev: SPI,
|
||||
int: INT,
|
||||
mut reset: RST,
|
||||
) -> (Device<'a>, Runner<'a, SPI, INT, RST>) {
|
||||
// Reset the W5500.
|
||||
) -> (Device<'a>, Runner<'a, C, SPI, INT, RST>) {
|
||||
// Reset the chip.
|
||||
reset.set_low().ok();
|
||||
// Ensure the reset is registered.
|
||||
Timer::after(Duration::from_millis(1)).await;
|
||||
reset.set_high().ok();
|
||||
// Wait for the W5500 to achieve PLL lock.
|
||||
Timer::after(Duration::from_millis(2)).await;
|
||||
|
||||
let mac = W5500::new(spi_dev, mac_addr).await.unwrap();
|
||||
// Wait for PLL lock. Some chips are slower than others.
|
||||
// Slowest is w5100s which is 100ms, so let's just wait that.
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
let (runner, device) = ch::new(&mut state.ch_state, mac_addr);
|
||||
let mac = WiznetDevice::new(spi_dev, mac_addr).await.unwrap();
|
||||
|
||||
let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr));
|
||||
(
|
||||
device,
|
||||
Runner {
|
@ -14,11 +14,11 @@ categories = [
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/"
|
||||
features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"]
|
||||
features = ["nightly", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"]
|
||||
features = ["nightly", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@ -26,8 +26,7 @@ std = []
|
||||
|
||||
defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"]
|
||||
|
||||
nightly = ["dep:embedded-io", "embedded-io?/async", "dep:embedded-nal-async"]
|
||||
unstable-traits = []
|
||||
nightly = ["dep:embedded-io-async", "dep:embedded-nal-async"]
|
||||
|
||||
udp = ["smoltcp/socket-udp"]
|
||||
tcp = ["smoltcp/socket-tcp"]
|
||||
@ -37,6 +36,7 @@ proto-ipv4 = ["smoltcp/proto-ipv4"]
|
||||
proto-ipv6 = ["smoltcp/proto-ipv6"]
|
||||
medium-ethernet = ["smoltcp/medium-ethernet"]
|
||||
medium-ip = ["smoltcp/medium-ip"]
|
||||
medium-ieee802154 = ["smoltcp/medium-ieee802154"]
|
||||
igmp = ["smoltcp/proto-igmp"]
|
||||
|
||||
[dependencies]
|
||||
@ -52,7 +52,7 @@ smoltcp = { version = "0.10.0", default-features = false, features = [
|
||||
embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time" }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embedded-io = { version = "0.4.0", optional = true }
|
||||
embedded-io-async = { version = "0.5.0", optional = true }
|
||||
|
||||
managed = { version = "0.8.0", default-features = false, features = [ "map" ] }
|
||||
heapless = { version = "0.7.5", default-features = false }
|
||||
@ -61,5 +61,5 @@ generic-array = { version = "0.14.4", default-features = false }
|
||||
stable_deref_trait = { version = "1.2.0", default-features = false }
|
||||
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
|
||||
atomic-pool = "1.0"
|
||||
embedded-nal-async = { version = "0.4.0", optional = true }
|
||||
embedded-nal-async = { version = "0.5.0", optional = true }
|
||||
atomic-polyfill = { version = "1.0" }
|
||||
|
@ -22,7 +22,7 @@ unimplemented features of the network protocols.
|
||||
- [`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-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5).
|
||||
- [`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 (W5100S, W5500)
|
||||
- [`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.
|
||||
|
||||
## Examples
|
||||
@ -49,7 +49,7 @@ trait has not had breaking changes.
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
[`embassy-time`](https://crates.io/crates/embassy-net-driver) is used for timekeeping and timeouts. You must
|
||||
[`embassy-time`](https://crates.io/crates/embassy-time) is used for timekeeping and timeouts. You must
|
||||
link an `embassy-time` driver in your project to use this crate.
|
||||
|
||||
## License
|
||||
|
@ -51,6 +51,8 @@ where
|
||||
Medium::Ethernet => phy::Medium::Ethernet,
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => phy::Medium::Ip,
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
Medium::Ieee802154 => phy::Medium::Ieee802154,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!(
|
||||
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
|
||||
|
@ -68,7 +68,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
|
||||
where
|
||||
D: Driver + 'static,
|
||||
|
@ -24,16 +24,22 @@ use embassy_net_driver::{Driver, LinkState, Medium};
|
||||
use embassy_sync::waitqueue::WakerRegistration;
|
||||
use embassy_time::{Instant, Timer};
|
||||
use futures::pin_mut;
|
||||
#[allow(unused_imports)]
|
||||
use heapless::Vec;
|
||||
#[cfg(feature = "igmp")]
|
||||
pub use smoltcp::iface::MulticastError;
|
||||
#[allow(unused_imports)]
|
||||
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
use smoltcp::socket::dhcpv4::{self, RetryConfig};
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
pub use smoltcp::wire::EthernetAddress;
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))]
|
||||
pub use smoltcp::wire::HardwareAddress;
|
||||
#[cfg(feature = "udp")]
|
||||
pub use smoltcp::wire::IpListenEndpoint;
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
pub use smoltcp::wire::{EthernetAddress, HardwareAddress};
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame};
|
||||
pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint};
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
|
||||
@ -224,6 +230,20 @@ pub(crate) struct SocketStack {
|
||||
next_local_port: u16,
|
||||
}
|
||||
|
||||
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress {
|
||||
match addr {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
driver::HardwareAddress::Ethernet(eth) => HardwareAddress::Ethernet(EthernetAddress(eth)),
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
driver::HardwareAddress::Ieee802154(ieee) => HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
|
||||
#[cfg(feature = "medium-ip")]
|
||||
driver::HardwareAddress::Ip => HardwareAddress::Ip,
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("Unsupported address {:?}. Make sure to enable medium-ethernet or medium-ieee802154 in embassy-net's Cargo features.", addr),
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Driver + 'static> Stack<D> {
|
||||
/// Create a new network stack.
|
||||
pub fn new<const SOCK: usize>(
|
||||
@ -232,21 +252,7 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
resources: &'static mut StackResources<SOCK>,
|
||||
random_seed: u64,
|
||||
) -> Self {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = device.capabilities().medium;
|
||||
|
||||
let hardware_addr = match medium {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())),
|
||||
#[cfg(feature = "medium-ip")]
|
||||
Medium::Ip => HardwareAddress::Ip,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!(
|
||||
"Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
|
||||
medium
|
||||
),
|
||||
};
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(to_smoltcp_hardware_address(device.hardware_address()));
|
||||
iface_cfg.random_seed = random_seed;
|
||||
|
||||
let iface = Interface::new(
|
||||
@ -262,6 +268,7 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
|
||||
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
|
||||
|
||||
#[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
|
||||
let mut socket = SocketStack {
|
||||
sockets,
|
||||
iface,
|
||||
@ -269,6 +276,7 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
next_local_port,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
|
||||
let mut inner = Inner {
|
||||
device,
|
||||
link_up: false,
|
||||
@ -287,6 +295,9 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
dns_waker: WakerRegistration::new(),
|
||||
};
|
||||
|
||||
#[cfg(feature = "medium-ieee802154")]
|
||||
let _ = config;
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
match config.ipv4 {
|
||||
ConfigV4::Static(config) => {
|
||||
@ -323,9 +334,9 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
|
||||
}
|
||||
|
||||
/// Get the MAC address of the network interface.
|
||||
pub fn ethernet_address(&self) -> [u8; 6] {
|
||||
self.with(|_s, i| i.device.ethernet_address())
|
||||
/// Get the hardware address of the network interface.
|
||||
pub fn hardware_address(&self) -> HardwareAddress {
|
||||
self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()))
|
||||
}
|
||||
|
||||
/// Get whether the link is up.
|
||||
@ -479,30 +490,78 @@ impl<D: Driver + 'static> Stack<D> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "igmp")]
|
||||
impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> {
|
||||
impl<D: Driver + 'static> Stack<D> {
|
||||
/// Join a multicast group.
|
||||
pub fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await
|
||||
}
|
||||
|
||||
/// Join a multicast group.
|
||||
///
|
||||
/// When the send queue is full, this method will return `Poll::Pending`
|
||||
/// and register the current task to be notified when the queue has space available.
|
||||
pub fn poll_join_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
self.with_mut(|s, i| {
|
||||
s.iface
|
||||
.join_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now()))
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
};
|
||||
|
||||
match s
|
||||
.iface
|
||||
.join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
|
||||
{
|
||||
Ok(announce_sent) => Poll::Ready(Ok(announce_sent)),
|
||||
Err(MulticastError::Exhausted) => Poll::Pending,
|
||||
Err(other) => Poll::Ready(Err(other)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Leave a multicast group.
|
||||
pub fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
pub async fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await
|
||||
}
|
||||
|
||||
/// Leave a multicast group.
|
||||
///
|
||||
/// When the send queue is full, this method will return `Poll::Pending`
|
||||
/// and register the current task to be notified when the queue has space available.
|
||||
pub fn poll_leave_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
|
||||
where
|
||||
T: Into<IpAddress>,
|
||||
{
|
||||
let addr = addr.into();
|
||||
|
||||
self.with_mut(|s, i| {
|
||||
s.iface
|
||||
.leave_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now()))
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
};
|
||||
|
||||
match s
|
||||
.iface
|
||||
.leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
|
||||
{
|
||||
Ok(leave_sent) => Poll::Ready(Ok(leave_sent)),
|
||||
Err(MulticastError::Exhausted) => Poll::Pending,
|
||||
Err(other) => Poll::Ready(Err(other)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -524,22 +583,26 @@ impl SocketStack {
|
||||
impl<D: Driver + 'static> Inner<D> {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
fn apply_config_v4(&mut self, s: &mut SocketStack, config: StaticConfigV4) {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = self.device.capabilities().medium;
|
||||
|
||||
debug!("Acquired IP configuration:");
|
||||
|
||||
debug!(" IP address: {}", config.address);
|
||||
s.iface.update_ip_addrs(|addrs| {
|
||||
if addrs.is_empty() {
|
||||
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
|
||||
} else {
|
||||
addrs[0] = IpCidr::Ipv4(config.address);
|
||||
if let Some((index, _)) = addrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
|
||||
{
|
||||
addrs.remove(index);
|
||||
}
|
||||
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
|
||||
});
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if medium == Medium::Ethernet {
|
||||
#[cfg(feature = "medium-ip")]
|
||||
let skip_gateway = self.device.capabilities().medium != Medium::Ip;
|
||||
#[cfg(not(feature = "medium-ip"))]
|
||||
let skip_gateway = false;
|
||||
|
||||
if !skip_gateway {
|
||||
if let Some(gateway) = config.gateway {
|
||||
debug!(" Default gateway: {}", gateway);
|
||||
s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap();
|
||||
@ -570,11 +633,14 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
|
||||
debug!(" IP address: {}", config.address);
|
||||
s.iface.update_ip_addrs(|addrs| {
|
||||
if addrs.is_empty() {
|
||||
addrs.push(IpCidr::Ipv6(config.address)).unwrap();
|
||||
} else {
|
||||
addrs[0] = IpCidr::Ipv6(config.address);
|
||||
if let Some((index, _)) = addrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv6(_)))
|
||||
{
|
||||
addrs.remove(index);
|
||||
}
|
||||
addrs.push(IpCidr::Ipv6(config.address)).unwrap();
|
||||
});
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
@ -642,13 +708,21 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
socket.set_retry_config(config.retry_config);
|
||||
}
|
||||
|
||||
#[allow(unused)] // used only with dhcp
|
||||
fn unapply_config(&mut self, s: &mut SocketStack) {
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
fn unapply_config_v4(&mut self, s: &mut SocketStack) {
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
let medium = self.device.capabilities().medium;
|
||||
|
||||
debug!("Lost IP configuration");
|
||||
s.iface.update_ip_addrs(|ip_addrs| ip_addrs.clear());
|
||||
s.iface.update_ip_addrs(|ip_addrs| {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
if let Some((index, _)) = ip_addrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
|
||||
{
|
||||
ip_addrs.remove(index);
|
||||
}
|
||||
});
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if medium == Medium::Ethernet {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
@ -665,11 +739,12 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
|
||||
s.waker.register(cx.waker());
|
||||
|
||||
#[cfg(feature = "medium-ethernet")]
|
||||
if self.device.capabilities().medium == Medium::Ethernet {
|
||||
s.iface.set_hardware_addr(HardwareAddress::Ethernet(EthernetAddress(
|
||||
self.device.ethernet_address(),
|
||||
)));
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
if self.device.capabilities().medium == Medium::Ethernet
|
||||
|| self.device.capabilities().medium == Medium::Ieee802154
|
||||
{
|
||||
s.iface
|
||||
.set_hardware_addr(to_smoltcp_hardware_address(self.device.hardware_address()));
|
||||
}
|
||||
|
||||
let timestamp = instant_to_smoltcp(Instant::now());
|
||||
@ -695,7 +770,7 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
if self.link_up {
|
||||
match socket.poll() {
|
||||
None => {}
|
||||
Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
|
||||
Some(dhcpv4::Event::Deconfigured) => self.unapply_config_v4(s),
|
||||
Some(dhcpv4::Event::Configured(config)) => {
|
||||
let config = StaticConfigV4 {
|
||||
address: config.address,
|
||||
@ -707,7 +782,7 @@ impl<D: Driver + 'static> Inner<D> {
|
||||
}
|
||||
} else if old_link_up {
|
||||
socket.reset();
|
||||
self.unapply_config(s);
|
||||
self.unapply_config_v4(s);
|
||||
}
|
||||
}
|
||||
//if old_link_up || self.link_up {
|
||||
|
@ -82,6 +82,22 @@ impl<'a> TcpReader<'a> {
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
|
||||
/// Call `f` with the largest contiguous slice of octets in the receive buffer,
|
||||
/// and dequeue the amount of elements returned by `f`.
|
||||
///
|
||||
/// If no data is available, it waits until there is at least one byte available.
|
||||
pub async fn read_with<F, R>(&mut self, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> (usize, R),
|
||||
{
|
||||
self.io.read_with(f).await
|
||||
}
|
||||
|
||||
/// Return the maximum number of bytes inside the transmit buffer.
|
||||
pub fn recv_capacity(&self) -> usize {
|
||||
self.io.recv_capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TcpWriter<'a> {
|
||||
@ -100,6 +116,22 @@ impl<'a> TcpWriter<'a> {
|
||||
pub async fn flush(&mut self) -> Result<(), Error> {
|
||||
self.io.flush().await
|
||||
}
|
||||
|
||||
/// Call `f` with the largest contiguous slice of octets in the transmit buffer,
|
||||
/// and enqueue the amount of elements returned by `f`.
|
||||
///
|
||||
/// If the socket is not ready to accept data, it waits until it is.
|
||||
pub async fn write_with<F, R>(&mut self, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> (usize, R),
|
||||
{
|
||||
self.io.write_with(f).await
|
||||
}
|
||||
|
||||
/// Return the maximum number of bytes inside the transmit buffer.
|
||||
pub fn send_capacity(&self) -> usize {
|
||||
self.io.send_capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TcpSocket<'a> {
|
||||
@ -121,6 +153,38 @@ impl<'a> TcpSocket<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the maximum number of bytes inside the recv buffer.
|
||||
pub fn recv_capacity(&self) -> usize {
|
||||
self.io.recv_capacity()
|
||||
}
|
||||
|
||||
/// Return the maximum number of bytes inside the transmit buffer.
|
||||
pub fn send_capacity(&self) -> usize {
|
||||
self.io.send_capacity()
|
||||
}
|
||||
|
||||
/// Call `f` with the largest contiguous slice of octets in the transmit buffer,
|
||||
/// and enqueue the amount of elements returned by `f`.
|
||||
///
|
||||
/// If the socket is not ready to accept data, it waits until it is.
|
||||
pub async fn write_with<F, R>(&mut self, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> (usize, R),
|
||||
{
|
||||
self.io.write_with(f).await
|
||||
}
|
||||
|
||||
/// Call `f` with the largest contiguous slice of octets in the receive buffer,
|
||||
/// and dequeue the amount of elements returned by `f`.
|
||||
///
|
||||
/// If no data is available, it waits until there is at least one byte available.
|
||||
pub async fn read_with<F, R>(&mut self, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> (usize, R),
|
||||
{
|
||||
self.io.read_with(f).await
|
||||
}
|
||||
|
||||
/// Split the socket into reader and a writer halves.
|
||||
pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) {
|
||||
(TcpReader { io: self.io }, TcpWriter { io: self.io })
|
||||
@ -359,6 +423,64 @@ impl<'d> TcpIo<'d> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn write_with<F, R>(&mut self, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> (usize, R),
|
||||
{
|
||||
let mut f = Some(f);
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| {
|
||||
if !s.can_send() {
|
||||
if s.may_send() {
|
||||
// socket buffer is full wait until it has atleast one byte free
|
||||
s.register_send_waker(cx.waker());
|
||||
Poll::Pending
|
||||
} else {
|
||||
// if we can't transmit because the transmit half of the duplex connection is closed then return an error
|
||||
Poll::Ready(Err(Error::ConnectionReset))
|
||||
}
|
||||
} else {
|
||||
Poll::Ready(match s.send(f.take().unwrap()) {
|
||||
// Connection reset. TODO: this can also be timeouts etc, investigate.
|
||||
Err(tcp::SendError::InvalidState) => Err(Error::ConnectionReset),
|
||||
Ok(r) => Ok(r),
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn read_with<F, R>(&mut self, f: F) -> Result<R, Error>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> (usize, R),
|
||||
{
|
||||
let mut f = Some(f);
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| {
|
||||
if !s.can_recv() {
|
||||
if s.may_recv() {
|
||||
// socket buffer is empty wait until it has atleast one byte has arrived
|
||||
s.register_recv_waker(cx.waker());
|
||||
Poll::Pending
|
||||
} else {
|
||||
// if we can't receive because the recieve half of the duplex connection is closed then return an error
|
||||
Poll::Ready(Err(Error::ConnectionReset))
|
||||
}
|
||||
} else {
|
||||
Poll::Ready(match s.recv(f.take().unwrap()) {
|
||||
// Connection reset. TODO: this can also be timeouts etc, investigate.
|
||||
Err(tcp::RecvError::Finished) | Err(tcp::RecvError::InvalidState) => {
|
||||
Err(Error::ConnectionReset)
|
||||
}
|
||||
Ok(r) => Ok(r),
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn flush(&mut self) -> Result<(), Error> {
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| {
|
||||
@ -376,35 +498,50 @@ impl<'d> TcpIo<'d> {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn recv_capacity(&self) -> usize {
|
||||
self.with(|s, _| s.recv_capacity())
|
||||
}
|
||||
|
||||
fn send_capacity(&self) -> usize {
|
||||
self.with(|s, _| s.send_capacity())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
mod embedded_io_impls {
|
||||
use super::*;
|
||||
|
||||
impl embedded_io::Error for ConnectError {
|
||||
fn kind(&self) -> embedded_io::ErrorKind {
|
||||
embedded_io::ErrorKind::Other
|
||||
impl embedded_io_async::Error for ConnectError {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
match self {
|
||||
ConnectError::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset,
|
||||
ConnectError::TimedOut => embedded_io_async::ErrorKind::TimedOut,
|
||||
ConnectError::NoRoute => embedded_io_async::ErrorKind::NotConnected,
|
||||
ConnectError::InvalidState => embedded_io_async::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_io::Error for Error {
|
||||
fn kind(&self) -> embedded_io::ErrorKind {
|
||||
embedded_io::ErrorKind::Other
|
||||
impl embedded_io_async::Error for Error {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
match self {
|
||||
Error::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::Io for TcpSocket<'d> {
|
||||
impl<'d> embedded_io_async::ErrorType for TcpSocket<'d> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Read for TcpSocket<'d> {
|
||||
impl<'d> embedded_io_async::Read for TcpSocket<'d> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Write for TcpSocket<'d> {
|
||||
impl<'d> embedded_io_async::Write for TcpSocket<'d> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.io.write(buf).await
|
||||
}
|
||||
@ -414,21 +551,21 @@ mod embedded_io_impls {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::Io for TcpReader<'d> {
|
||||
impl<'d> embedded_io_async::ErrorType for TcpReader<'d> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Read for TcpReader<'d> {
|
||||
impl<'d> embedded_io_async::Read for TcpReader<'d> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::Io for TcpWriter<'d> {
|
||||
impl<'d> embedded_io_async::ErrorType for TcpWriter<'d> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d> embedded_io::asynch::Write for TcpWriter<'d> {
|
||||
impl<'d> embedded_io_async::Write for TcpWriter<'d> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.io.write(buf).await
|
||||
}
|
||||
@ -440,7 +577,7 @@ mod embedded_io_impls {
|
||||
}
|
||||
|
||||
/// TCP client compatible with `embedded-nal-async` traits.
|
||||
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod client {
|
||||
use core::cell::UnsafeCell;
|
||||
use core::mem::MaybeUninit;
|
||||
@ -527,13 +664,13 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::Io
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType
|
||||
for TcpConnection<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Read
|
||||
for TcpConnection<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
@ -541,7 +678,7 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Write
|
||||
for TcpConnection<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
|
@ -184,6 +184,26 @@ impl<'a> UdpSocket<'a> {
|
||||
pub fn may_recv(&self) -> bool {
|
||||
self.with(|s, _| s.can_recv())
|
||||
}
|
||||
|
||||
/// Return the maximum number packets the socket can receive.
|
||||
pub fn packet_recv_capacity(&self) -> usize {
|
||||
self.with(|s, _| s.packet_recv_capacity())
|
||||
}
|
||||
|
||||
/// Return the maximum number packets the socket can receive.
|
||||
pub fn packet_send_capacity(&self) -> usize {
|
||||
self.with(|s, _| s.packet_send_capacity())
|
||||
}
|
||||
|
||||
/// Return the maximum number of bytes inside the recv buffer.
|
||||
pub fn payload_recv_capacity(&self) -> usize {
|
||||
self.with(|s, _| s.payload_recv_capacity())
|
||||
}
|
||||
|
||||
/// Return the maximum number of bytes inside the transmit buffer.
|
||||
pub fn payload_send_capacity(&self) -> usize {
|
||||
self.with(|s, _| s.payload_send_capacity())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UdpSocket<'_> {
|
||||
|
@ -32,10 +32,10 @@ rt = [
|
||||
|
||||
time = ["dep:embassy-time"]
|
||||
|
||||
defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embedded-io?/defmt", "embassy-embedded-hal/defmt"]
|
||||
defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embassy-embedded-hal/defmt"]
|
||||
|
||||
# Enable nightly-only features
|
||||
nightly = ["embedded-hal-1", "embedded-hal-async", "dep:embassy-usb-driver", "embedded-storage-async", "dep:embedded-io", "embassy-embedded-hal/nightly"]
|
||||
nightly = ["embedded-hal-1", "embedded-hal-async", "dep:embassy-usb-driver", "embedded-storage-async", "dep:embedded-io-async", "embassy-embedded-hal/nightly"]
|
||||
|
||||
# Reexport the PAC for the currently enabled chip at `embassy_nrf::pac`.
|
||||
# This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version.
|
||||
@ -93,14 +93,15 @@ _gpio-p1 = []
|
||||
[dependencies]
|
||||
embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-3"] }
|
||||
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
|
||||
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
|
||||
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", 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", optional = true}
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true}
|
||||
embedded-io = { version = "0.4.0", features = ["async"], optional = true }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1", optional = true}
|
||||
embedded-hal-async = { version = "=1.0.0-rc.1", optional = true}
|
||||
embedded-io = { version = "0.5.0" }
|
||||
embedded-io-async = { version = "0.5.0", optional = true }
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
@ -15,8 +15,8 @@ use core::slice;
|
||||
use core::sync::atomic::{compiler_fence, AtomicU8, AtomicUsize, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::atomic_ring_buffer::RingBuffer;
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_internal::atomic_ring_buffer::RingBuffer;
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
// Re-export SVD variants to allow user to directly set values
|
||||
pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
|
||||
@ -572,37 +572,37 @@ impl<'u, 'd, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'u, 'd, U, T> {
|
||||
mod _embedded_io {
|
||||
use super::*;
|
||||
|
||||
impl embedded_io::Error for Error {
|
||||
fn kind(&self) -> embedded_io::ErrorKind {
|
||||
impl embedded_io_async::Error for Error {
|
||||
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarte<'d, U, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> {
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteRx<'u, 'd, U, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> {
|
||||
impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteTx<'u, 'd, U, T> {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarte<'d, U, T> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.inner_read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> {
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarteRx<'u, 'd, U, T> {
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.inner.inner_read(buf).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarte<'d, U, T> {
|
||||
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
||||
self.inner_fill_buf().await
|
||||
}
|
||||
@ -612,7 +612,7 @@ mod _embedded_io {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> {
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarteRx<'u, 'd, U, T> {
|
||||
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
||||
self.inner.inner_fill_buf().await
|
||||
}
|
||||
@ -622,7 +622,7 @@ mod _embedded_io {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> {
|
||||
impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarte<'d, U, T> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.inner_write(buf).await
|
||||
}
|
||||
@ -632,7 +632,7 @@ mod _embedded_io {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> {
|
||||
impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarteTx<'u, 'd, U, T> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
self.inner.inner_write(buf).await
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 192 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -208,7 +208,7 @@ impl_ppi_channel!(PPI_CH31, 31 => static);
|
||||
impl_saadc_input!(P0_04, ANALOG_INPUT2);
|
||||
impl_saadc_input!(P0_05, ANALOG_INPUT3);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 192 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -234,7 +234,7 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_30, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 192 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -236,7 +236,7 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_30, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 256 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 18;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -224,7 +224,7 @@ impl_ppi_channel!(PPI_CH29, 29 => static);
|
||||
impl_ppi_channel!(PPI_CH30, 30 => static);
|
||||
impl_ppi_channel!(PPI_CH31, 31 => static);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -12,7 +12,7 @@ pub const FLASH_SIZE: usize = 512 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 21;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -263,7 +263,7 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
impl_i2s!(I2S, I2S, I2S);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 512 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 18;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -306,7 +306,7 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
impl_i2s!(I2S, I2S, I2S);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -8,7 +8,7 @@ pub const FLASH_SIZE: usize = 1024 * 1024;
|
||||
|
||||
pub const RESET_PIN: u32 = 18;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -311,7 +311,7 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7);
|
||||
|
||||
impl_i2s!(I2S, I2S, I2S);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
POWER_CLOCK,
|
||||
RADIO,
|
||||
UARTE0_UART0,
|
||||
|
@ -218,7 +218,7 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub const FLASH_SIZE: usize = 1024 * 1024;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// USB
|
||||
USBD,
|
||||
|
||||
@ -506,7 +506,7 @@ impl_saadc_input!(P0_18, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_19, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_20, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
FPU,
|
||||
CACHE,
|
||||
SPU,
|
||||
|
@ -109,7 +109,7 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub const FLASH_SIZE: usize = 256 * 1024;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -342,7 +342,7 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable);
|
||||
impl_ppi_channel!(PPI_CH30, 30 => configurable);
|
||||
impl_ppi_channel!(PPI_CH31, 31 => configurable);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
CLOCK_POWER,
|
||||
RADIO,
|
||||
RNG,
|
||||
|
@ -169,7 +169,7 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
pub const FLASH_SIZE: usize = 1024 * 1024;
|
||||
|
||||
embassy_hal_common::peripherals! {
|
||||
embassy_hal_internal::peripherals! {
|
||||
// RTC
|
||||
RTC0,
|
||||
RTC1,
|
||||
@ -368,7 +368,7 @@ impl_saadc_input!(P0_18, ANALOG_INPUT5);
|
||||
impl_saadc_input!(P0_19, ANALOG_INPUT6);
|
||||
impl_saadc_input!(P0_20, ANALOG_INPUT7);
|
||||
|
||||
embassy_hal_common::interrupt_mod!(
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
SPU,
|
||||
CLOCK_POWER,
|
||||
UARTE0_SPIM0_SPIS0_TWIM0_TWIS0,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user