mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-21 14:22:33 +00:00
Merge remote-tracking branch 'origin/master' into feature-i2-slave-r2
This commit is contained in:
commit
aee8b9b1f8
7
.github/ci/test.sh
vendored
7
.github/ci/test.sh
vendored
@ -8,6 +8,10 @@ export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
|
||||
# needed for "dumb HTTP" transport support
|
||||
# used when pointing stm32-metapac to a CI-built one.
|
||||
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
|
||||
cargo test --manifest-path ./embassy-futures/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||
@ -21,7 +25,8 @@ cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-salty
|
||||
|
||||
cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nrf52840,time-driver-rtc1,gpiote
|
||||
|
||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver
|
||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp2040,_test
|
||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp235xa,_test
|
||||
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,exti,time-driver-any,exti
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,exti,time-driver-any,exti
|
||||
|
56
ci.sh
56
ci.sh
@ -2,11 +2,13 @@
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
# check-cfg is stable on rustc 1.79 but not cargo 1.79.
|
||||
# however, our cargo-batch is currently based on cargo 1.80, which does support check-cfg.
|
||||
# so, force build.rs scripts to emit check-cfg commands.
|
||||
# when 1.80 hits stable we can make build.rs unconditionally emit check-cfg and remove all this.
|
||||
export EMBASSY_FORCE_CHECK_CFG=1
|
||||
if ! command -v cargo-batch &> /dev/null; then
|
||||
echo "cargo-batch could not be found. Install it with the following command:"
|
||||
echo ""
|
||||
echo " cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export RUSTFLAGS=-Dwarnings
|
||||
export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
|
||||
@ -45,7 +47,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
@ -80,10 +82,13 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,time \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,defmt \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,log \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,intrinsics \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,defmt,rp2040 \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,log,rp2040 \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,intrinsics,rp2040 \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio,rp2040 \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,defmt,rp235xa \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,log,rp235xa \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,rp235xa,binary-info \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \
|
||||
@ -171,13 +176,17 @@ cargo batch \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \
|
||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \
|
||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs,bluetooth' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs,bluetooth' \
|
||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \
|
||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040,overclock' \
|
||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \
|
||||
--- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \
|
||||
--- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \
|
||||
--- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi --features embassy-rp/rp2040 \
|
||||
--- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
|
||||
--- build --release --manifest-path docs/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
|
||||
@ -191,6 +200,7 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/ns \
|
||||
--- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/nrf51 \
|
||||
--- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \
|
||||
--- build --release --manifest-path examples/rp23/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/rp23 \
|
||||
--- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \
|
||||
--- 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 \
|
||||
@ -205,6 +215,8 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \
|
||||
--- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \
|
||||
--- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \
|
||||
--- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \
|
||||
--- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \
|
||||
--- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \
|
||||
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \
|
||||
--- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \
|
||||
@ -218,6 +230,8 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --out-dir out/examples/boot/nrf9151 \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --out-dir out/examples/boot/nrf9161 \
|
||||
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \
|
||||
@ -230,10 +244,12 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \
|
||||
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h747xi-cm7 \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \
|
||||
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \
|
||||
@ -274,6 +290,10 @@ cargo batch \
|
||||
$BUILD_EXTRA
|
||||
|
||||
|
||||
# temporarily disabled, these boards are dead.
|
||||
rm -rf out/tests/stm32f103c8
|
||||
rm -rf out/tests/nrf52840-dk
|
||||
|
||||
rm out/tests/stm32wb55rg/wpan_mac
|
||||
rm out/tests/stm32wb55rg/wpan_ble
|
||||
|
||||
@ -283,8 +303,10 @@ rm out/tests/stm32f207zg/eth
|
||||
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
|
||||
rm out/tests/stm32u5a5zj/usart
|
||||
|
||||
# flaky, perhaps bad wire
|
||||
# flaky, probably due to bad ringbuffered dma code.
|
||||
rm out/tests/stm32l152re/usart_rx_ringbuffered
|
||||
rm out/tests/stm32f207zg/usart_rx_ringbuffered
|
||||
rm out/tests/stm32wl55jc/usart_rx_ringbuffered
|
||||
|
||||
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
|
||||
echo No teleprobe token found, skipping running HIL tests
|
||||
|
BIN
cyw43-firmware/43439A0.bin
Executable file → Normal file
BIN
cyw43-firmware/43439A0.bin
Executable file → Normal file
Binary file not shown.
BIN
cyw43-firmware/43439A0_btfw.bin
Normal file
BIN
cyw43-firmware/43439A0_btfw.bin
Normal file
Binary file not shown.
BIN
cyw43-firmware/43439A0_clm.bin
Executable file → Normal file
BIN
cyw43-firmware/43439A0_clm.bin
Executable file → Normal file
Binary file not shown.
@ -1,9 +1,14 @@
|
||||
# WiFi firmware
|
||||
# WiFi + Bluetooth firmware blobs
|
||||
|
||||
Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439
|
||||
Firmware obtained from https://github.com/georgerobotics/cyw43-driver/tree/main/firmware
|
||||
|
||||
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
|
||||
* 2023-08-21: synced with `a1dc885` - Update 43439 fw + clm to come from `wb43439A0_7_95_49_00_combined.h` + add Bluetooth firmware
|
||||
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 to 7.95.62
|
||||
|
||||
## Notes
|
||||
|
||||
If you update these files, please update the lengths in the `tests/rp/src/bin/cyw43_perf.rs` test (which relies on these files running from RAM).
|
||||
|
17
cyw43-pio/CHANGELOG.md
Normal file
17
cyw43-pio/CHANGELOG.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.2.0 - 2024-08-05
|
||||
|
||||
- Update to cyw43 0.2.0
|
||||
- Update to embassy-rp 0.2.0
|
||||
|
||||
## 0.1.0 - 2024-01-11
|
||||
|
||||
- First release
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cyw43-pio"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
description = "RP2040 PIO SPI implementation for cyw43"
|
||||
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
|
||||
@ -15,8 +15,8 @@ documentation = "https://docs.embassy.dev/cyw43-pio"
|
||||
overclock = []
|
||||
|
||||
[dependencies]
|
||||
cyw43 = { version = "0.1.0", path = "../cyw43" }
|
||||
embassy-rp = { version = "0.1.0", path = "../embassy-rp" }
|
||||
cyw43 = { version = "0.2.0", path = "../cyw43" }
|
||||
embassy-rp = { version = "0.2.0", path = "../embassy-rp" }
|
||||
pio-proc = "0.2"
|
||||
pio = "0.2.1"
|
||||
fixed = "1.23.1"
|
||||
@ -26,3 +26,4 @@ defmt = { version = "0.3", optional = true }
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/"
|
||||
target = "thumbv6m-none-eabi"
|
||||
features = ["embassy-rp/rp2040"]
|
||||
|
@ -181,7 +181,10 @@ where
|
||||
let read_bits = read.len() * 32 + 32 - 1;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::trace!("write={} read={}", write_bits, read_bits);
|
||||
defmt::trace!("cmd_read write={} read={}", write_bits, read_bits);
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len());
|
||||
|
||||
unsafe {
|
||||
instr::set_y(&mut self.sm, read_bits as u32);
|
||||
@ -201,6 +204,10 @@ where
|
||||
.rx()
|
||||
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
|
||||
.await;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::trace!("cmd_read cmd = {:02x} len = {} read = {:08x}", cmd, read.len(), read);
|
||||
|
||||
status
|
||||
}
|
||||
}
|
||||
|
23
cyw43/CHANGELOG.md
Normal file
23
cyw43/CHANGELOG.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.2.0 - 2024-08-05
|
||||
|
||||
- Update to new versions of embassy-{time,sync}
|
||||
- Add more fields to the BssInfo packet struct #2461
|
||||
- Extend the Scan API #2282
|
||||
- Reuse buf to reduce stack usage #2580
|
||||
- Add MAC address getter to cyw43 controller #2818
|
||||
- Add function to join WPA2 network with precomputed PSK. #2885
|
||||
- Add function to close soft AP. #3042
|
||||
- Fixing missing re-export #3211
|
||||
|
||||
## 0.1.0 - 2024-01-11
|
||||
|
||||
- First release
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cyw43"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W."
|
||||
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
|
||||
@ -10,17 +10,18 @@ repository = "https://github.com/embassy-rs/embassy"
|
||||
documentation = "https://docs.embassy.dev/cyw43"
|
||||
|
||||
[features]
|
||||
defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt"]
|
||||
defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt", "bt-hci?/defmt", "embedded-io-async?/defmt-03"]
|
||||
log = ["dep:log"]
|
||||
bluetooth = ["dep:bt-hci", "dep:embedded-io-async"]
|
||||
|
||||
# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`.
|
||||
firmware-logs = []
|
||||
|
||||
[dependencies]
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time"}
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time"}
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
|
||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.17", optional = true }
|
||||
@ -31,9 +32,12 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
|
||||
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
|
||||
num_enum = { version = "0.5.7", default-features = false }
|
||||
|
||||
heapless = "0.8.0"
|
||||
|
||||
# Bluetooth deps
|
||||
embedded-io-async = { version = "0.6.0", optional = true }
|
||||
bt-hci = { version = "0.1.0", optional = true }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/"
|
||||
|
508
cyw43/src/bluetooth.rs
Normal file
508
cyw43/src/bluetooth.rs
Normal file
@ -0,0 +1,508 @@
|
||||
use core::cell::RefCell;
|
||||
use core::future::Future;
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use bt_hci::transport::WithIndicator;
|
||||
use bt_hci::{ControllerToHostPacket, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci};
|
||||
use embassy_futures::yield_now;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::zerocopy_channel;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
|
||||
use crate::bus::Bus;
|
||||
pub use crate::bus::SpiBusCyw43;
|
||||
use crate::consts::*;
|
||||
use crate::util::round_up;
|
||||
use crate::{util, CHIP};
|
||||
|
||||
pub(crate) struct BtState {
|
||||
rx: [BtPacketBuf; 4],
|
||||
tx: [BtPacketBuf; 4],
|
||||
inner: MaybeUninit<BtStateInnre<'static>>,
|
||||
}
|
||||
|
||||
impl BtState {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
rx: [const { BtPacketBuf::new() }; 4],
|
||||
tx: [const { BtPacketBuf::new() }; 4],
|
||||
inner: MaybeUninit::uninit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BtStateInnre<'d> {
|
||||
rx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>,
|
||||
tx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>,
|
||||
}
|
||||
|
||||
/// Bluetooth driver.
|
||||
pub struct BtDriver<'d> {
|
||||
rx: RefCell<zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>>,
|
||||
tx: RefCell<zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>>,
|
||||
}
|
||||
|
||||
pub(crate) struct BtRunner<'d> {
|
||||
pub(crate) tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>,
|
||||
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>,
|
||||
|
||||
// Bluetooth circular buffers
|
||||
addr: u32,
|
||||
h2b_write_pointer: u32,
|
||||
b2h_read_pointer: u32,
|
||||
}
|
||||
|
||||
const BT_HCI_MTU: usize = 1024;
|
||||
|
||||
/// Represents a packet of size MTU.
|
||||
pub(crate) struct BtPacketBuf {
|
||||
pub(crate) len: usize,
|
||||
pub(crate) buf: [u8; BT_HCI_MTU],
|
||||
}
|
||||
|
||||
impl BtPacketBuf {
|
||||
/// Create a new packet buffer.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
len: 0,
|
||||
buf: [0; BT_HCI_MTU],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new<'d>(state: &'d mut BtState) -> (BtRunner<'d>, BtDriver<'d>) {
|
||||
// safety: this is a self-referential struct, however:
|
||||
// - it can't move while the `'d` borrow is active.
|
||||
// - when the borrow ends, the dangling references inside the MaybeUninit will never be used again.
|
||||
let state_uninit: *mut MaybeUninit<BtStateInnre<'d>> =
|
||||
(&mut state.inner as *mut MaybeUninit<BtStateInnre<'static>>).cast();
|
||||
let state = unsafe { &mut *state_uninit }.write(BtStateInnre {
|
||||
rx: zerocopy_channel::Channel::new(&mut state.rx[..]),
|
||||
tx: zerocopy_channel::Channel::new(&mut state.tx[..]),
|
||||
});
|
||||
|
||||
let (rx_sender, rx_receiver) = state.rx.split();
|
||||
let (tx_sender, tx_receiver) = state.tx.split();
|
||||
|
||||
(
|
||||
BtRunner {
|
||||
tx_chan: tx_receiver,
|
||||
rx_chan: rx_sender,
|
||||
|
||||
addr: 0,
|
||||
h2b_write_pointer: 0,
|
||||
b2h_read_pointer: 0,
|
||||
},
|
||||
BtDriver {
|
||||
rx: RefCell::new(rx_receiver),
|
||||
tx: RefCell::new(tx_sender),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) struct CybtFwCb<'a> {
|
||||
pub p_next_line_start: &'a [u8],
|
||||
}
|
||||
|
||||
pub(crate) struct HexFileData<'a> {
|
||||
pub addr_mode: i32,
|
||||
pub hi_addr: u16,
|
||||
pub dest_addr: u32,
|
||||
pub p_ds: &'a mut [u8],
|
||||
}
|
||||
|
||||
pub(crate) fn read_firmware_patch_line(p_btfw_cb: &mut CybtFwCb, hfd: &mut HexFileData) -> u32 {
|
||||
let mut abs_base_addr32 = 0;
|
||||
|
||||
loop {
|
||||
let num_bytes = p_btfw_cb.p_next_line_start[0];
|
||||
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..];
|
||||
|
||||
let addr = (p_btfw_cb.p_next_line_start[0] as u16) << 8 | p_btfw_cb.p_next_line_start[1] as u16;
|
||||
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[2..];
|
||||
|
||||
let line_type = p_btfw_cb.p_next_line_start[0];
|
||||
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..];
|
||||
|
||||
if num_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
hfd.p_ds[..num_bytes as usize].copy_from_slice(&p_btfw_cb.p_next_line_start[..num_bytes as usize]);
|
||||
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[num_bytes as usize..];
|
||||
|
||||
match line_type {
|
||||
BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS => {
|
||||
hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16;
|
||||
hfd.addr_mode = BTFW_ADDR_MODE_EXTENDED;
|
||||
}
|
||||
BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS => {
|
||||
hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16;
|
||||
hfd.addr_mode = BTFW_ADDR_MODE_SEGMENT;
|
||||
}
|
||||
BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS => {
|
||||
abs_base_addr32 = (hfd.p_ds[0] as u32) << 24
|
||||
| (hfd.p_ds[1] as u32) << 16
|
||||
| (hfd.p_ds[2] as u32) << 8
|
||||
| hfd.p_ds[3] as u32;
|
||||
hfd.addr_mode = BTFW_ADDR_MODE_LINEAR32;
|
||||
}
|
||||
BTFW_HEX_LINE_TYPE_DATA => {
|
||||
hfd.dest_addr = addr as u32;
|
||||
match hfd.addr_mode {
|
||||
BTFW_ADDR_MODE_EXTENDED => hfd.dest_addr += (hfd.hi_addr as u32) << 16,
|
||||
BTFW_ADDR_MODE_SEGMENT => hfd.dest_addr += (hfd.hi_addr as u32) << 4,
|
||||
BTFW_ADDR_MODE_LINEAR32 => hfd.dest_addr += abs_base_addr32,
|
||||
_ => {}
|
||||
}
|
||||
return num_bytes as u32;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
impl<'a> BtRunner<'a> {
|
||||
pub(crate) async fn init_bluetooth(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>, firmware: &[u8]) {
|
||||
trace!("init_bluetooth");
|
||||
bus.bp_write32(CHIP.bluetooth_base_address + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE)
|
||||
.await;
|
||||
Timer::after(Duration::from_millis(2)).await;
|
||||
self.upload_bluetooth_firmware(bus, firmware).await;
|
||||
self.wait_bt_ready(bus).await;
|
||||
self.init_bt_buffers(bus).await;
|
||||
self.wait_bt_awake(bus).await;
|
||||
self.bt_set_host_ready(bus).await;
|
||||
self.bt_toggle_intr(bus).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn upload_bluetooth_firmware(
|
||||
&mut self,
|
||||
bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>,
|
||||
firmware: &[u8],
|
||||
) {
|
||||
// read version
|
||||
let version_length = firmware[0];
|
||||
let _version = &firmware[1..=version_length as usize];
|
||||
// skip version + 1 extra byte as per cybt_shared_bus_driver.c
|
||||
let firmware = &firmware[version_length as usize + 2..];
|
||||
// buffers
|
||||
let mut data_buffer: [u8; 0x100] = [0; 0x100];
|
||||
let mut aligned_data_buffer: [u8; 0x100] = [0; 0x100];
|
||||
// structs
|
||||
let mut btfw_cb = CybtFwCb {
|
||||
p_next_line_start: firmware,
|
||||
};
|
||||
let mut hfd = HexFileData {
|
||||
addr_mode: BTFW_ADDR_MODE_EXTENDED,
|
||||
hi_addr: 0,
|
||||
dest_addr: 0,
|
||||
p_ds: &mut data_buffer,
|
||||
};
|
||||
loop {
|
||||
let num_fw_bytes = read_firmware_patch_line(&mut btfw_cb, &mut hfd);
|
||||
if num_fw_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
let fw_bytes = &hfd.p_ds[0..num_fw_bytes as usize];
|
||||
let mut dest_start_addr = hfd.dest_addr + CHIP.bluetooth_base_address;
|
||||
let mut aligned_data_buffer_index: usize = 0;
|
||||
// pad start
|
||||
if !util::is_aligned(dest_start_addr, 4) {
|
||||
let num_pad_bytes = dest_start_addr % 4;
|
||||
let padded_dest_start_addr = util::round_down(dest_start_addr, 4);
|
||||
let memory_value = bus.bp_read32(padded_dest_start_addr).await;
|
||||
let memory_value_bytes = memory_value.to_le_bytes();
|
||||
// Copy the previous memory value's bytes to the start
|
||||
for i in 0..num_pad_bytes as usize {
|
||||
aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i];
|
||||
aligned_data_buffer_index += 1;
|
||||
}
|
||||
// Copy the firmware bytes after the padding bytes
|
||||
for i in 0..num_fw_bytes as usize {
|
||||
aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i];
|
||||
aligned_data_buffer_index += 1;
|
||||
}
|
||||
dest_start_addr = padded_dest_start_addr;
|
||||
} else {
|
||||
// Directly copy fw_bytes into aligned_data_buffer if no start padding is required
|
||||
for i in 0..num_fw_bytes as usize {
|
||||
aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i];
|
||||
aligned_data_buffer_index += 1;
|
||||
}
|
||||
}
|
||||
// pad end
|
||||
let mut dest_end_addr = dest_start_addr + aligned_data_buffer_index as u32;
|
||||
if !util::is_aligned(dest_end_addr, 4) {
|
||||
let offset = dest_end_addr % 4;
|
||||
let num_pad_bytes_end = 4 - offset;
|
||||
let padded_dest_end_addr = util::round_down(dest_end_addr, 4);
|
||||
let memory_value = bus.bp_read32(padded_dest_end_addr).await;
|
||||
let memory_value_bytes = memory_value.to_le_bytes();
|
||||
// Append the necessary memory bytes to pad the end of aligned_data_buffer
|
||||
for i in offset..4 {
|
||||
aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i as usize];
|
||||
aligned_data_buffer_index += 1;
|
||||
}
|
||||
dest_end_addr += num_pad_bytes_end;
|
||||
} else {
|
||||
// pad end alignment not needed
|
||||
}
|
||||
let buffer_to_write = &aligned_data_buffer[0..aligned_data_buffer_index as usize];
|
||||
assert!(dest_start_addr % 4 == 0);
|
||||
assert!(dest_end_addr % 4 == 0);
|
||||
assert!(aligned_data_buffer_index % 4 == 0);
|
||||
bus.bp_write(dest_start_addr, buffer_to_write).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_bt_ready(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
trace!("wait_bt_ready");
|
||||
let mut success = false;
|
||||
for _ in 0..300 {
|
||||
let val = bus.bp_read32(BT_CTRL_REG_ADDR).await;
|
||||
trace!("BT_CTRL_REG_ADDR = {:08x}", val);
|
||||
if val & BTSDIO_REG_FW_RDY_BITMASK != 0 {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(1)).await;
|
||||
}
|
||||
assert!(success == true);
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_bt_awake(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
trace!("wait_bt_awake");
|
||||
let mut success = false;
|
||||
for _ in 0..300 {
|
||||
let val = bus.bp_read32(BT_CTRL_REG_ADDR).await;
|
||||
trace!("BT_CTRL_REG_ADDR = {:08x}", val);
|
||||
if val & BTSDIO_REG_BT_AWAKE_BITMASK != 0 {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(1)).await;
|
||||
}
|
||||
assert!(success == true);
|
||||
}
|
||||
|
||||
pub(crate) async fn bt_set_host_ready(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
trace!("bt_set_host_ready");
|
||||
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
|
||||
// TODO: do we need to swap endianness on this read?
|
||||
let new_val = old_val | BTSDIO_REG_SW_RDY_BITMASK;
|
||||
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
|
||||
}
|
||||
|
||||
// TODO: use this
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn bt_set_awake(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>, awake: bool) {
|
||||
trace!("bt_set_awake");
|
||||
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
|
||||
// TODO: do we need to swap endianness on this read?
|
||||
let new_val = if awake {
|
||||
old_val | BTSDIO_REG_WAKE_BT_BITMASK
|
||||
} else {
|
||||
old_val & !BTSDIO_REG_WAKE_BT_BITMASK
|
||||
};
|
||||
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn bt_toggle_intr(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
trace!("bt_toggle_intr");
|
||||
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
|
||||
// TODO: do we need to swap endianness on this read?
|
||||
let new_val = old_val ^ BTSDIO_REG_DATA_VALID_BITMASK;
|
||||
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
|
||||
}
|
||||
|
||||
// TODO: use this
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn bt_set_intr(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
trace!("bt_set_intr");
|
||||
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
|
||||
let new_val = old_val | BTSDIO_REG_DATA_VALID_BITMASK;
|
||||
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn init_bt_buffers(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
trace!("init_bt_buffers");
|
||||
self.addr = bus.bp_read32(WLAN_RAM_BASE_REG_ADDR).await;
|
||||
assert!(self.addr != 0);
|
||||
trace!("wlan_ram_base_addr = {:08x}", self.addr);
|
||||
bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, 0).await;
|
||||
bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT, 0).await;
|
||||
bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_IN, 0).await;
|
||||
bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, 0).await;
|
||||
}
|
||||
|
||||
async fn bt_bus_request(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
// TODO: CYW43_THREAD_ENTER mutex?
|
||||
self.bt_set_awake(bus, true).await;
|
||||
self.wait_bt_awake(bus).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn hci_write(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
self.bt_bus_request(bus).await;
|
||||
|
||||
// NOTE(unwrap): we only call this when we do have a packet in the queue.
|
||||
let buf = self.tx_chan.try_receive().unwrap();
|
||||
debug!("HCI tx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len]));
|
||||
|
||||
let len = buf.len as u32 - 1; // len doesn't include hci type byte
|
||||
let rounded_len = round_up(len, 4);
|
||||
let total_len = 4 + rounded_len;
|
||||
|
||||
let read_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT).await;
|
||||
let available = read_pointer.wrapping_sub(self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE;
|
||||
if available < total_len {
|
||||
warn!(
|
||||
"bluetooth tx queue full, retrying. len {} available {}",
|
||||
total_len, available
|
||||
);
|
||||
yield_now().await;
|
||||
return;
|
||||
}
|
||||
|
||||
// Build header
|
||||
let mut header = [0u8; 4];
|
||||
header[0] = len as u8;
|
||||
header[1] = (len >> 8) as u8;
|
||||
header[2] = (len >> 16) as u8;
|
||||
header[3] = buf.buf[0]; // HCI type byte
|
||||
|
||||
// Write header
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer;
|
||||
bus.bp_write(addr, &header).await;
|
||||
self.h2b_write_pointer = (self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE;
|
||||
|
||||
// Write payload.
|
||||
let payload = &buf.buf[1..][..rounded_len as usize];
|
||||
if self.h2b_write_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize {
|
||||
// wraparound
|
||||
let n = BTSDIO_FWBUF_SIZE - self.h2b_write_pointer;
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer;
|
||||
bus.bp_write(addr, &payload[..n as usize]).await;
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF;
|
||||
bus.bp_write(addr, &payload[n as usize..]).await;
|
||||
} else {
|
||||
// no wraparound
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer;
|
||||
bus.bp_write(addr, payload).await;
|
||||
}
|
||||
self.h2b_write_pointer = (self.h2b_write_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE;
|
||||
|
||||
// Update pointer.
|
||||
bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, self.h2b_write_pointer)
|
||||
.await;
|
||||
|
||||
self.bt_toggle_intr(bus).await;
|
||||
|
||||
self.tx_chan.receive_done();
|
||||
}
|
||||
|
||||
async fn bt_has_work(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) -> bool {
|
||||
let int_status = bus.bp_read32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS).await;
|
||||
if int_status & I_HMB_FC_CHANGE != 0 {
|
||||
bus.bp_write32(
|
||||
CHIP.sdiod_core_base_address + SDIO_INT_STATUS,
|
||||
int_status & I_HMB_FC_CHANGE,
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_irq(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
|
||||
if self.bt_has_work(bus).await {
|
||||
loop {
|
||||
// Check if we have data.
|
||||
let write_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_BT2HOST_IN).await;
|
||||
let available = write_pointer.wrapping_sub(self.b2h_read_pointer) % BTSDIO_FWBUF_SIZE;
|
||||
if available == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// read header
|
||||
let mut header = [0u8; 4];
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer;
|
||||
bus.bp_read(addr, &mut header).await;
|
||||
|
||||
// calc length
|
||||
let len = header[0] as u32 | ((header[1]) as u32) << 8 | ((header[2]) as u32) << 16;
|
||||
let rounded_len = round_up(len, 4);
|
||||
if available < 4 + rounded_len {
|
||||
warn!("ringbuf data not enough for a full packet?");
|
||||
break;
|
||||
}
|
||||
self.b2h_read_pointer = (self.b2h_read_pointer + 4) % BTSDIO_FWBUF_SIZE;
|
||||
|
||||
// Obtain a buf from the channel.
|
||||
let buf = self.rx_chan.send().await;
|
||||
|
||||
buf.buf[0] = header[3]; // hci packet type
|
||||
let payload = &mut buf.buf[1..][..rounded_len as usize];
|
||||
if self.b2h_read_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize {
|
||||
// wraparound
|
||||
let n = BTSDIO_FWBUF_SIZE - self.b2h_read_pointer;
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer;
|
||||
bus.bp_read(addr, &mut payload[..n as usize]).await;
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF;
|
||||
bus.bp_read(addr, &mut payload[n as usize..]).await;
|
||||
} else {
|
||||
// no wraparound
|
||||
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer;
|
||||
bus.bp_read(addr, payload).await;
|
||||
}
|
||||
self.b2h_read_pointer = (self.b2h_read_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE;
|
||||
bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, self.b2h_read_pointer)
|
||||
.await;
|
||||
|
||||
buf.len = 1 + len as usize;
|
||||
debug!("HCI rx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len]));
|
||||
|
||||
self.rx_chan.send_done();
|
||||
|
||||
self.bt_toggle_intr(bus).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> embedded_io_async::ErrorType for BtDriver<'d> {
|
||||
type Error = core::convert::Infallible;
|
||||
}
|
||||
|
||||
impl<'d> bt_hci::transport::Transport for BtDriver<'d> {
|
||||
fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future<Output = Result<ControllerToHostPacket<'a>, Self::Error>> {
|
||||
async {
|
||||
let ch = &mut *self.rx.borrow_mut();
|
||||
let buf = ch.receive().await;
|
||||
let n = buf.len;
|
||||
assert!(n < rx.len());
|
||||
rx[..n].copy_from_slice(&buf.buf[..n]);
|
||||
ch.receive_done();
|
||||
|
||||
let kind = PacketKind::from_hci_bytes_complete(&rx[..1]).unwrap();
|
||||
let (res, _) = ControllerToHostPacket::from_hci_bytes_with_kind(kind, &rx[1..n]).unwrap();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a complete HCI packet from the tx buffer
|
||||
fn write<T: HostToControllerPacket>(&self, val: &T) -> impl Future<Output = Result<(), Self::Error>> {
|
||||
async {
|
||||
let ch = &mut *self.tx.borrow_mut();
|
||||
let buf = ch.send().await;
|
||||
let buf_len = buf.buf.len();
|
||||
let mut slice = &mut buf.buf[..];
|
||||
WithIndicator::new(val).write_hci(&mut slice).unwrap();
|
||||
buf.len = buf_len - slice.len();
|
||||
ch.send_done();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ use embedded_hal_1::digital::OutputPin;
|
||||
use futures::FutureExt;
|
||||
|
||||
use crate::consts::*;
|
||||
use crate::slice8_mut;
|
||||
use crate::util::slice8_mut;
|
||||
|
||||
/// Custom Spi Trait that _only_ supports the bus operation of the cyw43
|
||||
/// Implementors are expected to hold the CS pin low during an operation.
|
||||
@ -48,44 +48,91 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init(&mut self) {
|
||||
pub async fn init(&mut self, bluetooth_enabled: bool) {
|
||||
// Reset
|
||||
trace!("WL_REG off/on");
|
||||
self.pwr.set_low().unwrap();
|
||||
Timer::after_millis(20).await;
|
||||
self.pwr.set_high().unwrap();
|
||||
Timer::after_millis(250).await;
|
||||
|
||||
trace!("read REG_BUS_TEST_RO");
|
||||
while self
|
||||
.read32_swapped(REG_BUS_TEST_RO)
|
||||
.read32_swapped(FUNC_BUS, REG_BUS_TEST_RO)
|
||||
.inspect(|v| trace!("{:#x}", v))
|
||||
.await
|
||||
!= FEEDBEAD
|
||||
{}
|
||||
|
||||
self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await;
|
||||
let val = self.read32_swapped(REG_BUS_TEST_RW).await;
|
||||
trace!("write REG_BUS_TEST_RW");
|
||||
self.write32_swapped(FUNC_BUS, REG_BUS_TEST_RW, TEST_PATTERN).await;
|
||||
let val = self.read32_swapped(FUNC_BUS, REG_BUS_TEST_RW).await;
|
||||
trace!("{:#x}", val);
|
||||
assert_eq!(val, TEST_PATTERN);
|
||||
|
||||
let val = self.read32_swapped(REG_BUS_CTRL).await;
|
||||
trace!("read REG_BUS_CTRL");
|
||||
let val = self.read32_swapped(FUNC_BUS, REG_BUS_CTRL).await;
|
||||
trace!("{:#010b}", (val & 0xff));
|
||||
|
||||
// 32-bit word length, little endian (which is the default endianess).
|
||||
// TODO: C library is uint32_t val = WORD_LENGTH_32 | HIGH_SPEED_MODE| ENDIAN_BIG | INTERRUPT_POLARITY_HIGH | WAKE_UP | 0x4 << (8 * SPI_RESPONSE_DELAY) | INTR_WITH_STATUS << (8 * SPI_STATUS_ENABLE);
|
||||
trace!("write REG_BUS_CTRL");
|
||||
self.write32_swapped(
|
||||
FUNC_BUS,
|
||||
REG_BUS_CTRL,
|
||||
WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS,
|
||||
WORD_LENGTH_32
|
||||
| HIGH_SPEED
|
||||
| INTERRUPT_POLARITY_HIGH
|
||||
| WAKE_UP
|
||||
| 0x4 << (8 * REG_BUS_RESPONSE_DELAY)
|
||||
| STATUS_ENABLE << (8 * REG_BUS_STATUS_ENABLE)
|
||||
| INTR_WITH_STATUS << (8 * REG_BUS_STATUS_ENABLE),
|
||||
)
|
||||
.await;
|
||||
|
||||
trace!("read REG_BUS_CTRL");
|
||||
let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await;
|
||||
trace!("{:#b}", val);
|
||||
|
||||
// TODO: C doesn't do this? i doubt it messes anything up
|
||||
trace!("read REG_BUS_TEST_RO");
|
||||
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await;
|
||||
trace!("{:#x}", val);
|
||||
assert_eq!(val, FEEDBEAD);
|
||||
|
||||
// TODO: C doesn't do this? i doubt it messes anything up
|
||||
trace!("read REG_BUS_TEST_RW");
|
||||
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await;
|
||||
trace!("{:#x}", val);
|
||||
assert_eq!(val, TEST_PATTERN);
|
||||
|
||||
trace!("write SPI_RESP_DELAY_F1 CYW43_BACKPLANE_READ_PAD_LEN_BYTES");
|
||||
self.write8(FUNC_BUS, SPI_RESP_DELAY_F1, WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE)
|
||||
.await;
|
||||
|
||||
// TODO: Make sure error interrupt bits are clear?
|
||||
// cyw43_write_reg_u8(self, BUS_FUNCTION, SPI_INTERRUPT_REGISTER, DATA_UNAVAILABLE | COMMAND_ERROR | DATA_ERROR | F1_OVERFLOW) != 0)
|
||||
trace!("Make sure error interrupt bits are clear");
|
||||
self.write8(
|
||||
FUNC_BUS,
|
||||
REG_BUS_INTERRUPT,
|
||||
(IRQ_DATA_UNAVAILABLE | IRQ_COMMAND_ERROR | IRQ_DATA_ERROR | IRQ_F1_OVERFLOW) as u8,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Enable a selection of interrupts
|
||||
// TODO: why not all of these F2_F3_FIFO_RD_UNDERFLOW | F2_F3_FIFO_WR_OVERFLOW | COMMAND_ERROR | DATA_ERROR | F2_PACKET_AVAILABLE | F1_OVERFLOW | F1_INTR
|
||||
trace!("enable a selection of interrupts");
|
||||
let mut val = IRQ_F2_F3_FIFO_RD_UNDERFLOW
|
||||
| IRQ_F2_F3_FIFO_WR_OVERFLOW
|
||||
| IRQ_COMMAND_ERROR
|
||||
| IRQ_DATA_ERROR
|
||||
| IRQ_F2_PACKET_AVAILABLE
|
||||
| IRQ_F1_OVERFLOW;
|
||||
if bluetooth_enabled {
|
||||
val = val | IRQ_F1_INTR;
|
||||
}
|
||||
self.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, val).await;
|
||||
}
|
||||
|
||||
pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) {
|
||||
@ -107,6 +154,8 @@ where
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) {
|
||||
trace!("bp_read addr = {:08x}", addr);
|
||||
|
||||
// It seems the HW force-aligns the addr
|
||||
// to 2 if data.len() >= 2
|
||||
// to 4 if data.len() >= 4
|
||||
@ -140,6 +189,8 @@ where
|
||||
}
|
||||
|
||||
pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) {
|
||||
trace!("bp_write addr = {:08x}", addr);
|
||||
|
||||
// It seems the HW force-aligns the addr
|
||||
// to 2 if data.len() >= 2
|
||||
// to 4 if data.len() >= 4
|
||||
@ -196,23 +247,32 @@ where
|
||||
}
|
||||
|
||||
async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 {
|
||||
trace!("backplane_readn addr = {:08x} len = {}", addr, len);
|
||||
|
||||
self.backplane_set_window(addr).await;
|
||||
|
||||
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
|
||||
if len == 4 {
|
||||
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG
|
||||
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG;
|
||||
}
|
||||
self.readn(FUNC_BACKPLANE, bus_addr, len).await
|
||||
|
||||
let val = self.readn(FUNC_BACKPLANE, bus_addr, len).await;
|
||||
|
||||
trace!("backplane_readn addr = {:08x} len = {} val = {:08x}", addr, len, val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) {
|
||||
trace!("backplane_writen addr = {:08x} len = {} val = {:08x}", addr, len, val);
|
||||
|
||||
self.backplane_set_window(addr).await;
|
||||
|
||||
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
|
||||
if len == 4 {
|
||||
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG
|
||||
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG;
|
||||
}
|
||||
self.writen(FUNC_BACKPLANE, bus_addr, val, len).await
|
||||
self.writen(FUNC_BACKPLANE, bus_addr, val, len).await;
|
||||
}
|
||||
|
||||
async fn backplane_set_window(&mut self, addr: u32) {
|
||||
@ -293,8 +353,8 @@ where
|
||||
self.status = self.spi.cmd_write(&[cmd, val]).await;
|
||||
}
|
||||
|
||||
async fn read32_swapped(&mut self, addr: u32) -> u32 {
|
||||
let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4);
|
||||
async fn read32_swapped(&mut self, func: u32, addr: u32) -> u32 {
|
||||
let cmd = cmd_word(READ, INC_ADDR, func, addr, 4);
|
||||
let cmd = swap16(cmd);
|
||||
let mut buf = [0; 1];
|
||||
|
||||
@ -303,8 +363,8 @@ where
|
||||
swap16(buf[0])
|
||||
}
|
||||
|
||||
async fn write32_swapped(&mut self, addr: u32, val: u32) {
|
||||
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4);
|
||||
async fn write32_swapped(&mut self, func: u32, addr: u32, val: u32) {
|
||||
let cmd = cmd_word(WRITE, INC_ADDR, func, addr, 4);
|
||||
let buf = [swap16(cmd), swap16(val)];
|
||||
|
||||
self.status = self.spi.cmd_write(&buf).await;
|
||||
|
@ -5,19 +5,33 @@ pub(crate) const FUNC_BACKPLANE: u32 = 1;
|
||||
pub(crate) const FUNC_WLAN: u32 = 2;
|
||||
pub(crate) const FUNC_BT: u32 = 3;
|
||||
|
||||
// Register addresses
|
||||
pub(crate) const REG_BUS_CTRL: u32 = 0x0;
|
||||
pub(crate) const REG_BUS_RESPONSE_DELAY: u32 = 0x1;
|
||||
pub(crate) const REG_BUS_STATUS_ENABLE: u32 = 0x2;
|
||||
pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status
|
||||
pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask
|
||||
pub(crate) const REG_BUS_STATUS: u32 = 0x8;
|
||||
pub(crate) const REG_BUS_TEST_RO: u32 = 0x14;
|
||||
pub(crate) const REG_BUS_TEST_RW: u32 = 0x18;
|
||||
pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c;
|
||||
|
||||
// SPI_BUS_CONTROL Bits
|
||||
pub(crate) const WORD_LENGTH_32: u32 = 0x1;
|
||||
pub(crate) const ENDIAN_BIG: u32 = 0x2;
|
||||
pub(crate) const CLOCK_PHASE: u32 = 0x4;
|
||||
pub(crate) const CLOCK_POLARITY: u32 = 0x8;
|
||||
pub(crate) const HIGH_SPEED: u32 = 0x10;
|
||||
pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5;
|
||||
pub(crate) const WAKE_UP: u32 = 1 << 7;
|
||||
pub(crate) const STATUS_ENABLE: u32 = 1 << 16;
|
||||
pub(crate) const INTERRUPT_WITH_STATUS: u32 = 1 << 17;
|
||||
pub(crate) const INTERRUPT_POLARITY_HIGH: u32 = 0x20;
|
||||
pub(crate) const WAKE_UP: u32 = 0x80;
|
||||
|
||||
// SPI_STATUS_ENABLE bits
|
||||
pub(crate) const STATUS_ENABLE: u32 = 0x01;
|
||||
pub(crate) const INTR_WITH_STATUS: u32 = 0x02;
|
||||
pub(crate) const RESP_DELAY_ALL: u32 = 0x04;
|
||||
pub(crate) const DWORD_PKT_LEN_EN: u32 = 0x08;
|
||||
pub(crate) const CMD_ERR_CHK_EN: u32 = 0x20;
|
||||
pub(crate) const DATA_ERR_CHK_EN: u32 = 0x40;
|
||||
|
||||
// SPI_STATUS_REGISTER bits
|
||||
pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001;
|
||||
@ -51,6 +65,13 @@ pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C;
|
||||
pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E;
|
||||
pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F;
|
||||
|
||||
pub(crate) const I_HMB_SW_MASK: u32 = 0x000000f0;
|
||||
pub(crate) const I_HMB_FC_CHANGE: u32 = 1 << 5;
|
||||
pub(crate) const SDIO_INT_STATUS: u32 = 0x20;
|
||||
pub(crate) const SDIO_INT_HOST_MASK: u32 = 0x24;
|
||||
|
||||
pub(crate) const SPI_F2_WATERMARK: u8 = 0x20;
|
||||
|
||||
pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000;
|
||||
pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF;
|
||||
pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000;
|
||||
@ -92,17 +113,6 @@ pub(crate) const IRQ_F1_INTR: u16 = 0x2000;
|
||||
pub(crate) const IRQ_F2_INTR: u16 = 0x4000;
|
||||
pub(crate) const IRQ_F3_INTR: u16 = 0x8000;
|
||||
|
||||
pub(crate) const IOCTL_CMD_UP: u32 = 2;
|
||||
pub(crate) const IOCTL_CMD_DOWN: u32 = 3;
|
||||
pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26;
|
||||
pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30;
|
||||
pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52;
|
||||
pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64;
|
||||
pub(crate) const IOCTL_CMD_SET_AP: u32 = 118;
|
||||
pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263;
|
||||
pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262;
|
||||
pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268;
|
||||
|
||||
pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0;
|
||||
pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1;
|
||||
pub(crate) const CHANNEL_TYPE_DATA: u8 = 2;
|
||||
@ -119,6 +129,44 @@ pub(crate) const WPA2_SECURITY: u32 = 0x00400000;
|
||||
pub(crate) const MIN_PSK_LEN: usize = 8;
|
||||
pub(crate) const MAX_PSK_LEN: usize = 64;
|
||||
|
||||
// Bluetooth firmware extraction constants.
|
||||
pub(crate) const BTFW_ADDR_MODE_UNKNOWN: i32 = 0;
|
||||
pub(crate) const BTFW_ADDR_MODE_EXTENDED: i32 = 1;
|
||||
pub(crate) const BTFW_ADDR_MODE_SEGMENT: i32 = 2;
|
||||
pub(crate) const BTFW_ADDR_MODE_LINEAR32: i32 = 3;
|
||||
|
||||
pub(crate) const BTFW_HEX_LINE_TYPE_DATA: u8 = 0;
|
||||
pub(crate) const BTFW_HEX_LINE_TYPE_END_OF_DATA: u8 = 1;
|
||||
pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS: u8 = 2;
|
||||
pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS: u8 = 4;
|
||||
pub(crate) const BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS: u8 = 5;
|
||||
|
||||
// Bluetooth constants.
|
||||
pub(crate) const SPI_RESP_DELAY_F1: u32 = 0x001d;
|
||||
pub(crate) const WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE: u8 = 4;
|
||||
|
||||
pub(crate) const BT2WLAN_PWRUP_WAKE: u32 = 3;
|
||||
pub(crate) const BT2WLAN_PWRUP_ADDR: u32 = 0x640894;
|
||||
|
||||
pub(crate) const BT_CTRL_REG_ADDR: u32 = 0x18000c7c;
|
||||
pub(crate) const HOST_CTRL_REG_ADDR: u32 = 0x18000d6c;
|
||||
pub(crate) const WLAN_RAM_BASE_REG_ADDR: u32 = 0x18000d68;
|
||||
|
||||
pub(crate) const BTSDIO_REG_DATA_VALID_BITMASK: u32 = 1 << 1;
|
||||
pub(crate) const BTSDIO_REG_BT_AWAKE_BITMASK: u32 = 1 << 8;
|
||||
pub(crate) const BTSDIO_REG_WAKE_BT_BITMASK: u32 = 1 << 17;
|
||||
pub(crate) const BTSDIO_REG_SW_RDY_BITMASK: u32 = 1 << 24;
|
||||
pub(crate) const BTSDIO_REG_FW_RDY_BITMASK: u32 = 1 << 24;
|
||||
|
||||
pub(crate) const BTSDIO_FWBUF_SIZE: u32 = 0x1000;
|
||||
pub(crate) const BTSDIO_OFFSET_HOST_WRITE_BUF: u32 = 0;
|
||||
pub(crate) const BTSDIO_OFFSET_HOST_READ_BUF: u32 = BTSDIO_FWBUF_SIZE;
|
||||
|
||||
pub(crate) const BTSDIO_OFFSET_HOST2BT_IN: u32 = 0x00002000;
|
||||
pub(crate) const BTSDIO_OFFSET_HOST2BT_OUT: u32 = 0x00002004;
|
||||
pub(crate) const BTSDIO_OFFSET_BT2HOST_IN: u32 = 0x00002008;
|
||||
pub(crate) const BTSDIO_OFFSET_BT2HOST_OUT: u32 = 0x0000200C;
|
||||
|
||||
// Security type (authentication and encryption types are combined using bit mask)
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
@ -317,3 +365,306 @@ impl core::fmt::Display for FormatInterrupt {
|
||||
core::fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u32)]
|
||||
pub(crate) enum Ioctl {
|
||||
GetMagic = 0,
|
||||
GetVersion = 1,
|
||||
Up = 2,
|
||||
Down = 3,
|
||||
GetLoop = 4,
|
||||
SetLoop = 5,
|
||||
Dump = 6,
|
||||
GetMsglevel = 7,
|
||||
SetMsglevel = 8,
|
||||
GetPromisc = 9,
|
||||
SetPromisc = 10,
|
||||
GetRate = 12,
|
||||
GetInstance = 14,
|
||||
GetInfra = 19,
|
||||
SetInfra = 20,
|
||||
GetAuth = 21,
|
||||
SetAuth = 22,
|
||||
GetBssid = 23,
|
||||
SetBssid = 24,
|
||||
GetSsid = 25,
|
||||
SetSsid = 26,
|
||||
Restart = 27,
|
||||
GetChannel = 29,
|
||||
SetChannel = 30,
|
||||
GetSrl = 31,
|
||||
SetSrl = 32,
|
||||
GetLrl = 33,
|
||||
SetLrl = 34,
|
||||
GetPlcphdr = 35,
|
||||
SetPlcphdr = 36,
|
||||
GetRadio = 37,
|
||||
SetRadio = 38,
|
||||
GetPhytype = 39,
|
||||
DumpRate = 40,
|
||||
SetRateParams = 41,
|
||||
GetKey = 44,
|
||||
SetKey = 45,
|
||||
GetRegulatory = 46,
|
||||
SetRegulatory = 47,
|
||||
GetPassiveScan = 48,
|
||||
SetPassiveScan = 49,
|
||||
Scan = 50,
|
||||
ScanResults = 51,
|
||||
Disassoc = 52,
|
||||
Reassoc = 53,
|
||||
GetRoamTrigger = 54,
|
||||
SetRoamTrigger = 55,
|
||||
GetRoamDelta = 56,
|
||||
SetRoamDelta = 57,
|
||||
GetRoamScanPeriod = 58,
|
||||
SetRoamScanPeriod = 59,
|
||||
Evm = 60,
|
||||
GetTxant = 61,
|
||||
SetTxant = 62,
|
||||
GetAntdiv = 63,
|
||||
SetAntdiv = 64,
|
||||
GetClosed = 67,
|
||||
SetClosed = 68,
|
||||
GetMaclist = 69,
|
||||
SetMaclist = 70,
|
||||
GetRateset = 71,
|
||||
SetRateset = 72,
|
||||
Longtrain = 74,
|
||||
GetBcnprd = 75,
|
||||
SetBcnprd = 76,
|
||||
GetDtimprd = 77,
|
||||
SetDtimprd = 78,
|
||||
GetSrom = 79,
|
||||
SetSrom = 80,
|
||||
GetWepRestrict = 81,
|
||||
SetWepRestrict = 82,
|
||||
GetCountry = 83,
|
||||
SetCountry = 84,
|
||||
GetPm = 85,
|
||||
SetPm = 86,
|
||||
GetWake = 87,
|
||||
SetWake = 88,
|
||||
GetForcelink = 90,
|
||||
SetForcelink = 91,
|
||||
FreqAccuracy = 92,
|
||||
CarrierSuppress = 93,
|
||||
GetPhyreg = 94,
|
||||
SetPhyreg = 95,
|
||||
GetRadioreg = 96,
|
||||
SetRadioreg = 97,
|
||||
GetRevinfo = 98,
|
||||
GetUcantdiv = 99,
|
||||
SetUcantdiv = 100,
|
||||
RReg = 101,
|
||||
WReg = 102,
|
||||
GetMacmode = 105,
|
||||
SetMacmode = 106,
|
||||
GetMonitor = 107,
|
||||
SetMonitor = 108,
|
||||
GetGmode = 109,
|
||||
SetGmode = 110,
|
||||
GetLegacyErp = 111,
|
||||
SetLegacyErp = 112,
|
||||
GetRxAnt = 113,
|
||||
GetCurrRateset = 114,
|
||||
GetScansuppress = 115,
|
||||
SetScansuppress = 116,
|
||||
GetAp = 117,
|
||||
SetAp = 118,
|
||||
GetEapRestrict = 119,
|
||||
SetEapRestrict = 120,
|
||||
ScbAuthorize = 121,
|
||||
ScbDeauthorize = 122,
|
||||
GetWdslist = 123,
|
||||
SetWdslist = 124,
|
||||
GetAtim = 125,
|
||||
SetAtim = 126,
|
||||
GetRssi = 127,
|
||||
GetPhyantdiv = 128,
|
||||
SetPhyantdiv = 129,
|
||||
ApRxOnly = 130,
|
||||
GetTxPathPwr = 131,
|
||||
SetTxPathPwr = 132,
|
||||
GetWsec = 133,
|
||||
SetWsec = 134,
|
||||
GetPhyNoise = 135,
|
||||
GetBssInfo = 136,
|
||||
GetPktcnts = 137,
|
||||
GetLazywds = 138,
|
||||
SetLazywds = 139,
|
||||
GetBandlist = 140,
|
||||
GetBand = 141,
|
||||
SetBand = 142,
|
||||
ScbDeauthenticate = 143,
|
||||
GetShortslot = 144,
|
||||
GetShortslotOverride = 145,
|
||||
SetShortslotOverride = 146,
|
||||
GetShortslotRestrict = 147,
|
||||
SetShortslotRestrict = 148,
|
||||
GetGmodeProtection = 149,
|
||||
GetGmodeProtectionOverride = 150,
|
||||
SetGmodeProtectionOverride = 151,
|
||||
Upgrade = 152,
|
||||
GetIgnoreBcns = 155,
|
||||
SetIgnoreBcns = 156,
|
||||
GetScbTimeout = 157,
|
||||
SetScbTimeout = 158,
|
||||
GetAssoclist = 159,
|
||||
GetClk = 160,
|
||||
SetClk = 161,
|
||||
GetUp = 162,
|
||||
Out = 163,
|
||||
GetWpaAuth = 164,
|
||||
SetWpaAuth = 165,
|
||||
GetUcflags = 166,
|
||||
SetUcflags = 167,
|
||||
GetPwridx = 168,
|
||||
SetPwridx = 169,
|
||||
GetTssi = 170,
|
||||
GetSupRatesetOverride = 171,
|
||||
SetSupRatesetOverride = 172,
|
||||
GetProtectionControl = 178,
|
||||
SetProtectionControl = 179,
|
||||
GetPhylist = 180,
|
||||
EncryptStrength = 181,
|
||||
DecryptStatus = 182,
|
||||
GetKeySeq = 183,
|
||||
GetScanChannelTime = 184,
|
||||
SetScanChannelTime = 185,
|
||||
GetScanUnassocTime = 186,
|
||||
SetScanUnassocTime = 187,
|
||||
GetScanHomeTime = 188,
|
||||
SetScanHomeTime = 189,
|
||||
GetScanNprobes = 190,
|
||||
SetScanNprobes = 191,
|
||||
GetPrbRespTimeout = 192,
|
||||
SetPrbRespTimeout = 193,
|
||||
GetAtten = 194,
|
||||
SetAtten = 195,
|
||||
GetShmem = 196,
|
||||
SetShmem = 197,
|
||||
SetWsecTest = 200,
|
||||
ScbDeauthenticateForReason = 201,
|
||||
TkipCountermeasures = 202,
|
||||
GetPiomode = 203,
|
||||
SetPiomode = 204,
|
||||
SetAssocPrefer = 205,
|
||||
GetAssocPrefer = 206,
|
||||
SetRoamPrefer = 207,
|
||||
GetRoamPrefer = 208,
|
||||
SetLed = 209,
|
||||
GetLed = 210,
|
||||
GetInterferenceMode = 211,
|
||||
SetInterferenceMode = 212,
|
||||
GetChannelQa = 213,
|
||||
StartChannelQa = 214,
|
||||
GetChannelSel = 215,
|
||||
StartChannelSel = 216,
|
||||
GetValidChannels = 217,
|
||||
GetFakefrag = 218,
|
||||
SetFakefrag = 219,
|
||||
GetPwroutPercentage = 220,
|
||||
SetPwroutPercentage = 221,
|
||||
SetBadFramePreempt = 222,
|
||||
GetBadFramePreempt = 223,
|
||||
SetLeapList = 224,
|
||||
GetLeapList = 225,
|
||||
GetCwmin = 226,
|
||||
SetCwmin = 227,
|
||||
GetCwmax = 228,
|
||||
SetCwmax = 229,
|
||||
GetWet = 230,
|
||||
SetWet = 231,
|
||||
GetPub = 232,
|
||||
GetKeyPrimary = 235,
|
||||
SetKeyPrimary = 236,
|
||||
GetAciArgs = 238,
|
||||
SetAciArgs = 239,
|
||||
UnsetCallback = 240,
|
||||
SetCallback = 241,
|
||||
GetRadar = 242,
|
||||
SetRadar = 243,
|
||||
SetSpectManagment = 244,
|
||||
GetSpectManagment = 245,
|
||||
WdsGetRemoteHwaddr = 246,
|
||||
WdsGetWpaSup = 247,
|
||||
SetCsScanTimer = 248,
|
||||
GetCsScanTimer = 249,
|
||||
MeasureRequest = 250,
|
||||
Init = 251,
|
||||
SendQuiet = 252,
|
||||
Keepalive = 253,
|
||||
SendPwrConstraint = 254,
|
||||
UpgradeStatus = 255,
|
||||
CurrentPwr = 256,
|
||||
GetScanPassiveTime = 257,
|
||||
SetScanPassiveTime = 258,
|
||||
LegacyLinkBehavior = 259,
|
||||
GetChannelsInCountry = 260,
|
||||
GetCountryList = 261,
|
||||
GetVar = 262,
|
||||
SetVar = 263,
|
||||
NvramGet = 264,
|
||||
NvramSet = 265,
|
||||
NvramDump = 266,
|
||||
Reboot = 267,
|
||||
SetWsecPmk = 268,
|
||||
GetAuthMode = 269,
|
||||
SetAuthMode = 270,
|
||||
GetWakeentry = 271,
|
||||
SetWakeentry = 272,
|
||||
NdconfigItem = 273,
|
||||
Nvotpw = 274,
|
||||
Otpw = 275,
|
||||
IovBlockGet = 276,
|
||||
IovModulesGet = 277,
|
||||
SoftReset = 278,
|
||||
GetAllowMode = 279,
|
||||
SetAllowMode = 280,
|
||||
GetDesiredBssid = 281,
|
||||
SetDesiredBssid = 282,
|
||||
DisassocMyap = 283,
|
||||
GetNbands = 284,
|
||||
GetBandstates = 285,
|
||||
GetWlcBssInfo = 286,
|
||||
GetAssocInfo = 287,
|
||||
GetOidPhy = 288,
|
||||
SetOidPhy = 289,
|
||||
SetAssocTime = 290,
|
||||
GetDesiredSsid = 291,
|
||||
GetChanspec = 292,
|
||||
GetAssocState = 293,
|
||||
SetPhyState = 294,
|
||||
GetScanPending = 295,
|
||||
GetScanreqPending = 296,
|
||||
GetPrevRoamReason = 297,
|
||||
SetPrevRoamReason = 298,
|
||||
GetBandstatesPi = 299,
|
||||
GetPhyState = 300,
|
||||
GetBssWpaRsn = 301,
|
||||
GetBssWpa2Rsn = 302,
|
||||
GetBssBcnTs = 303,
|
||||
GetIntDisassoc = 304,
|
||||
SetNumPeers = 305,
|
||||
GetNumBss = 306,
|
||||
GetWsecPmk = 318,
|
||||
GetRandomBytes = 319,
|
||||
}
|
||||
|
||||
pub(crate) const WSEC_TKIP: u32 = 0x02;
|
||||
pub(crate) const WSEC_AES: u32 = 0x04;
|
||||
|
||||
pub(crate) const AUTH_OPEN: u32 = 0x00;
|
||||
pub(crate) const AUTH_SAE: u32 = 0x03;
|
||||
|
||||
pub(crate) const MFP_NONE: u32 = 0;
|
||||
pub(crate) const MFP_CAPABLE: u32 = 1;
|
||||
pub(crate) const MFP_REQUIRED: u32 = 2;
|
||||
|
||||
pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000;
|
||||
pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004;
|
||||
pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080;
|
||||
pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000;
|
||||
|
@ -35,16 +35,19 @@ pub struct Control<'a> {
|
||||
ioctl_state: &'a IoctlState,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ScanType {
|
||||
Active,
|
||||
Passive,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Scan options.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct ScanOptions {
|
||||
/// SSID to scan for.
|
||||
pub ssid: Option<heapless::String<32>>,
|
||||
/// If set to `None`, all APs will be returned. If set to `Some`, only APs
|
||||
/// with the specified BSSID will be returned.
|
||||
@ -72,6 +75,79 @@ impl Default for ScanOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Authentication type, used in [`JoinOptions::auth`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum JoinAuth {
|
||||
/// Open network
|
||||
Open,
|
||||
/// WPA only
|
||||
Wpa,
|
||||
/// WPA2 only
|
||||
Wpa2,
|
||||
/// WPA3 only
|
||||
Wpa3,
|
||||
/// WPA2 + WPA3
|
||||
Wpa2Wpa3,
|
||||
}
|
||||
|
||||
/// Options for [`Control::join`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct JoinOptions<'a> {
|
||||
/// Authentication type. Default `Wpa2Wpa3`.
|
||||
pub auth: JoinAuth,
|
||||
/// Enable TKIP encryption. Default false.
|
||||
pub cipher_tkip: bool,
|
||||
/// Enable AES encryption. Default true.
|
||||
pub cipher_aes: bool,
|
||||
/// Passphrase. Default empty.
|
||||
pub passphrase: &'a [u8],
|
||||
/// If false, `passphrase` is the human-readable passphrase string.
|
||||
/// If true, `passphrase` is the result of applying the PBKDF2 hash to the
|
||||
/// passphrase string. This makes it possible to avoid storing unhashed passwords.
|
||||
///
|
||||
/// This is not compatible with WPA3.
|
||||
/// Default false.
|
||||
pub passphrase_is_prehashed: bool,
|
||||
}
|
||||
|
||||
impl<'a> JoinOptions<'a> {
|
||||
/// Create a new `JoinOptions` for joining open networks.
|
||||
pub fn new_open() -> Self {
|
||||
Self {
|
||||
auth: JoinAuth::Open,
|
||||
cipher_tkip: false,
|
||||
cipher_aes: false,
|
||||
passphrase: &[],
|
||||
passphrase_is_prehashed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `JoinOptions` for joining encrypted networks.
|
||||
///
|
||||
/// Defaults to supporting WPA2+WPA3 with AES only, you may edit
|
||||
/// the returned options to change this.
|
||||
pub fn new(passphrase: &'a [u8]) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.passphrase = passphrase;
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for JoinOptions<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
auth: JoinAuth::Wpa2Wpa3,
|
||||
cipher_tkip: false,
|
||||
cipher_aes: true,
|
||||
passphrase: &[],
|
||||
passphrase_is_prehashed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Control<'a> {
|
||||
pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self {
|
||||
Self {
|
||||
@ -81,8 +157,7 @@ impl<'a> Control<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize WiFi controller.
|
||||
pub async fn init(&mut self, clm: &[u8]) {
|
||||
async fn load_clm(&mut self, clm: &[u8]) {
|
||||
const CHUNK_SIZE: usize = 1024;
|
||||
|
||||
debug!("Downloading CLM...");
|
||||
@ -108,12 +183,17 @@ impl<'a> Control<'a> {
|
||||
buf[0..8].copy_from_slice(b"clmload\x00");
|
||||
buf[8..20].copy_from_slice(&header.to_bytes());
|
||||
buf[20..][..chunk.len()].copy_from_slice(&chunk);
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()])
|
||||
self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()])
|
||||
.await;
|
||||
}
|
||||
|
||||
// check clmload ok
|
||||
assert_eq!(self.get_iovar_u32("clmload_status").await, 0);
|
||||
}
|
||||
|
||||
/// Initialize WiFi controller.
|
||||
pub async fn init(&mut self, clm: &[u8]) {
|
||||
self.load_clm(&clm).await;
|
||||
|
||||
debug!("Configuring misc stuff...");
|
||||
|
||||
@ -139,7 +219,7 @@ impl<'a> Control<'a> {
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
// Set antenna to chip antenna
|
||||
self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await;
|
||||
self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await;
|
||||
|
||||
self.set_iovar_u32("bus:txglom", 0).await;
|
||||
Timer::after_millis(100).await;
|
||||
@ -177,24 +257,24 @@ impl<'a> Control<'a> {
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto
|
||||
self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any
|
||||
self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto
|
||||
self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
|
||||
|
||||
debug!("INIT DONE");
|
||||
debug!("cyw43 control init done");
|
||||
}
|
||||
|
||||
/// Set the WiFi interface up.
|
||||
async fn up(&mut self) {
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
|
||||
self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await;
|
||||
}
|
||||
|
||||
/// Set the interface down.
|
||||
async fn down(&mut self) {
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
|
||||
self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await;
|
||||
}
|
||||
|
||||
/// Set power management mode.
|
||||
@ -207,17 +287,74 @@ impl<'a> Control<'a> {
|
||||
self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await;
|
||||
self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await;
|
||||
}
|
||||
self.ioctl_set_u32(86, 0, mode_num).await;
|
||||
self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await;
|
||||
}
|
||||
|
||||
/// Join an unprotected network with the provided ssid.
|
||||
pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> {
|
||||
pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> {
|
||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||
|
||||
self.ioctl_set_u32(134, 0, 0).await; // wsec = open
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
|
||||
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
|
||||
self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0)
|
||||
if options.auth == JoinAuth::Open {
|
||||
self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
|
||||
self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await;
|
||||
self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await;
|
||||
self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await;
|
||||
} else {
|
||||
let mut wsec = 0;
|
||||
if options.cipher_aes {
|
||||
wsec |= WSEC_AES;
|
||||
}
|
||||
if options.cipher_tkip {
|
||||
wsec |= WSEC_TKIP;
|
||||
}
|
||||
self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await;
|
||||
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth {
|
||||
JoinAuth::Open => unreachable!(),
|
||||
JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK),
|
||||
JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK),
|
||||
JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK),
|
||||
JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK),
|
||||
};
|
||||
|
||||
if wpa12 {
|
||||
let mut flags = 0;
|
||||
if !options.passphrase_is_prehashed {
|
||||
flags |= 1;
|
||||
}
|
||||
let mut pfi = PassphraseInfo {
|
||||
len: options.passphrase.len() as _,
|
||||
flags,
|
||||
passphrase: [0; 64],
|
||||
};
|
||||
pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase);
|
||||
Timer::after_millis(3).await;
|
||||
self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes())
|
||||
.await;
|
||||
}
|
||||
|
||||
if wpa3 {
|
||||
let mut pfi = SaePassphraseInfo {
|
||||
len: options.passphrase.len() as _,
|
||||
passphrase: [0; 128],
|
||||
};
|
||||
pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase);
|
||||
Timer::after_millis(3).await;
|
||||
self.set_iovar("sae_password", &pfi.to_bytes()).await;
|
||||
}
|
||||
|
||||
self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await;
|
||||
self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await;
|
||||
self.set_iovar_u32("mfp", mfp).await;
|
||||
self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await;
|
||||
}
|
||||
|
||||
let mut i = SsidInfo {
|
||||
len: ssid.len() as _,
|
||||
@ -228,69 +365,13 @@ impl<'a> Control<'a> {
|
||||
self.wait_for_join(i).await
|
||||
}
|
||||
|
||||
/// Join a protected network with the provided ssid and [`PassphraseInfo`].
|
||||
async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> {
|
||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||
|
||||
self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
self.ioctl(
|
||||
IoctlType::Set,
|
||||
IOCTL_CMD_SET_PASSPHRASE,
|
||||
0,
|
||||
&mut passphrase_info.to_bytes(),
|
||||
)
|
||||
.await; // WLC_SET_WSEC_PMK
|
||||
|
||||
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
|
||||
self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open)
|
||||
self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth
|
||||
|
||||
let mut i = SsidInfo {
|
||||
len: ssid.len() as _,
|
||||
ssid: [0; 32],
|
||||
};
|
||||
i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
|
||||
|
||||
self.wait_for_join(i).await
|
||||
}
|
||||
|
||||
/// Join a protected network with the provided ssid and passphrase.
|
||||
pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> {
|
||||
let mut pfi = PassphraseInfo {
|
||||
len: passphrase.len() as _,
|
||||
flags: 1,
|
||||
passphrase: [0; 64],
|
||||
};
|
||||
pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes());
|
||||
self.join_wpa2_passphrase_info(ssid, &pfi).await
|
||||
}
|
||||
|
||||
/// Join a protected network with the provided ssid and precomputed PSK.
|
||||
pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> {
|
||||
let mut pfi = PassphraseInfo {
|
||||
len: psk.len() as _,
|
||||
flags: 0,
|
||||
passphrase: [0; 64],
|
||||
};
|
||||
pfi.passphrase[..psk.len()].copy_from_slice(psk);
|
||||
self.join_wpa2_passphrase_info(ssid, &pfi).await
|
||||
}
|
||||
|
||||
async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> {
|
||||
self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]);
|
||||
let mut subscriber = self.events.queue.subscriber().unwrap();
|
||||
// the actual join operation starts here
|
||||
// we make sure to enable events before so we don't miss any
|
||||
|
||||
// set_ssid
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes())
|
||||
.await;
|
||||
self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await;
|
||||
|
||||
// to complete the join, we wait for a SET_SSID event
|
||||
// we also save the AUTH status for the user, it may be interesting
|
||||
@ -351,7 +432,7 @@ impl<'a> Control<'a> {
|
||||
self.up().await;
|
||||
|
||||
// Turn on AP mode
|
||||
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await;
|
||||
self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await;
|
||||
|
||||
// Set SSID
|
||||
let mut i = SsidInfoWithIndex {
|
||||
@ -365,7 +446,7 @@ impl<'a> Control<'a> {
|
||||
self.set_iovar("bsscfg:ssid", &i.to_bytes()).await;
|
||||
|
||||
// Set channel number
|
||||
self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await;
|
||||
self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await;
|
||||
|
||||
// Set security
|
||||
self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await;
|
||||
@ -382,7 +463,7 @@ impl<'a> Control<'a> {
|
||||
passphrase: [0; 64],
|
||||
};
|
||||
pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes());
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes())
|
||||
self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes())
|
||||
.await;
|
||||
}
|
||||
|
||||
@ -399,7 +480,7 @@ impl<'a> Control<'a> {
|
||||
self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN
|
||||
|
||||
// Turn off AP mode
|
||||
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 0).await;
|
||||
self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await;
|
||||
|
||||
// Temporarily set wifi down
|
||||
self.down().await;
|
||||
@ -478,11 +559,11 @@ impl<'a> Control<'a> {
|
||||
}
|
||||
|
||||
async fn set_iovar(&mut self, name: &str, val: &[u8]) {
|
||||
self.set_iovar_v::<64>(name, val).await
|
||||
self.set_iovar_v::<196>(name, val).await
|
||||
}
|
||||
|
||||
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
|
||||
debug!("set {} = {:02x}", name, Bytes(val));
|
||||
debug!("iovar set {} = {:02x}", name, Bytes(val));
|
||||
|
||||
let mut buf = [0; BUFSIZE];
|
||||
buf[..name.len()].copy_from_slice(name.as_bytes());
|
||||
@ -490,13 +571,13 @@ impl<'a> Control<'a> {
|
||||
buf[name.len() + 1..][..val.len()].copy_from_slice(val);
|
||||
|
||||
let total_len = name.len() + 1 + val.len();
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len])
|
||||
self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len])
|
||||
.await;
|
||||
}
|
||||
|
||||
// TODO this is not really working, it always returns all zeros.
|
||||
async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize {
|
||||
debug!("get {}", name);
|
||||
debug!("iovar get {}", name);
|
||||
|
||||
let mut buf = [0; 64];
|
||||
buf[..name.len()].copy_from_slice(name.as_bytes());
|
||||
@ -504,7 +585,7 @@ impl<'a> Control<'a> {
|
||||
|
||||
let total_len = max(name.len() + 1, res.len());
|
||||
let res_len = self
|
||||
.ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len])
|
||||
.ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len])
|
||||
.await;
|
||||
|
||||
let out_len = min(res.len(), res_len);
|
||||
@ -512,12 +593,20 @@ impl<'a> Control<'a> {
|
||||
out_len
|
||||
}
|
||||
|
||||
async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) {
|
||||
async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) {
|
||||
let mut buf = val.to_le_bytes();
|
||||
self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await;
|
||||
}
|
||||
|
||||
async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
|
||||
async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
|
||||
if kind == IoctlType::Set {
|
||||
debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf));
|
||||
}
|
||||
let n = self.ioctl_inner(kind, cmd, iface, buf).await;
|
||||
n
|
||||
}
|
||||
|
||||
async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
|
||||
struct CancelOnDrop<'a>(&'a IoctlState);
|
||||
|
||||
impl CancelOnDrop<'_> {
|
||||
@ -609,7 +698,7 @@ impl<'a> Control<'a> {
|
||||
}
|
||||
/// Leave the wifi, with which we are currently associated.
|
||||
pub async fn leave(&mut self) {
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await;
|
||||
self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await;
|
||||
info!("Disassociated")
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,10 @@ use core::task::{Poll, Waker};
|
||||
|
||||
use embassy_sync::waitqueue::WakerRegistration;
|
||||
|
||||
use crate::consts::Ioctl;
|
||||
use crate::fmt::Bytes;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IoctlType {
|
||||
Get = 0,
|
||||
Set = 2,
|
||||
@ -16,7 +17,7 @@ pub enum IoctlType {
|
||||
pub struct PendingIoctl {
|
||||
pub buf: *mut [u8],
|
||||
pub kind: IoctlType,
|
||||
pub cmd: u32,
|
||||
pub cmd: Ioctl,
|
||||
pub iface: u32,
|
||||
}
|
||||
|
||||
@ -101,7 +102,7 @@ impl IoctlState {
|
||||
self.state.set(IoctlStateInner::Done { resp_len: 0 });
|
||||
}
|
||||
|
||||
pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
|
||||
pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
|
||||
self.state
|
||||
.set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface }));
|
||||
self.wake_runner();
|
||||
|
@ -8,18 +8,18 @@
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
#[cfg(feature = "bluetooth")]
|
||||
mod bluetooth;
|
||||
mod bus;
|
||||
mod consts;
|
||||
mod control;
|
||||
mod countries;
|
||||
mod events;
|
||||
mod ioctl;
|
||||
mod structs;
|
||||
|
||||
mod control;
|
||||
mod nvram;
|
||||
mod runner;
|
||||
|
||||
use core::slice;
|
||||
mod structs;
|
||||
mod util;
|
||||
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
@ -28,7 +28,9 @@ use ioctl::IoctlState;
|
||||
|
||||
use crate::bus::Bus;
|
||||
pub use crate::bus::SpiBusCyw43;
|
||||
pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, Scanner};
|
||||
pub use crate::control::{
|
||||
AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, Scanner,
|
||||
};
|
||||
pub use crate::runner::Runner;
|
||||
pub use crate::structs::BssInfo;
|
||||
|
||||
@ -56,6 +58,7 @@ impl Core {
|
||||
struct Chip {
|
||||
arm_core_base_address: u32,
|
||||
socsram_base_address: u32,
|
||||
bluetooth_base_address: u32,
|
||||
socsram_wrapper_base_address: u32,
|
||||
sdiod_core_base_address: u32,
|
||||
pmu_base_address: u32,
|
||||
@ -83,6 +86,7 @@ const WRAPPER_REGISTER_OFFSET: u32 = 0x100000;
|
||||
const CHIP: Chip = Chip {
|
||||
arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET,
|
||||
socsram_base_address: 0x18004000,
|
||||
bluetooth_base_address: 0x19000000,
|
||||
socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET,
|
||||
sdiod_core_base_address: 0x18002000,
|
||||
pmu_base_address: 0x18000000,
|
||||
@ -107,6 +111,12 @@ const CHIP: Chip = Chip {
|
||||
/// Driver state.
|
||||
pub struct State {
|
||||
ioctl_state: IoctlState,
|
||||
net: NetState,
|
||||
#[cfg(feature = "bluetooth")]
|
||||
bt: bluetooth::BtState,
|
||||
}
|
||||
|
||||
struct NetState {
|
||||
ch: ch::State<MTU, 4, 4>,
|
||||
events: Events,
|
||||
}
|
||||
@ -116,8 +126,12 @@ impl State {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ioctl_state: IoctlState::new(),
|
||||
ch: ch::State::new(),
|
||||
events: Events::new(),
|
||||
net: NetState {
|
||||
ch: ch::State::new(),
|
||||
events: Events::new(),
|
||||
},
|
||||
#[cfg(feature = "bluetooth")]
|
||||
bt: bluetooth::BtState::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,21 +239,60 @@ where
|
||||
PWR: OutputPin,
|
||||
SPI: SpiBusCyw43,
|
||||
{
|
||||
let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
|
||||
let (ch_runner, device) = ch::new(&mut state.net.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);
|
||||
let mut runner = Runner::new(
|
||||
ch_runner,
|
||||
Bus::new(pwr, spi),
|
||||
&state.ioctl_state,
|
||||
&state.net.events,
|
||||
#[cfg(feature = "bluetooth")]
|
||||
None,
|
||||
);
|
||||
|
||||
runner.init(firmware).await;
|
||||
runner.init(firmware, None).await;
|
||||
let control = Control::new(state_ch, &state.net.events, &state.ioctl_state);
|
||||
|
||||
(
|
||||
device,
|
||||
Control::new(state_ch, &state.events, &state.ioctl_state),
|
||||
runner,
|
||||
)
|
||||
(device, control, runner)
|
||||
}
|
||||
|
||||
fn slice8_mut(x: &mut [u32]) -> &mut [u8] {
|
||||
let len = x.len() * 4;
|
||||
unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) }
|
||||
/// Create a new instance of the CYW43 driver.
|
||||
///
|
||||
/// Returns a handle to the network device, control handle and a runner for driving the low level
|
||||
/// stack.
|
||||
#[cfg(feature = "bluetooth")]
|
||||
pub async fn new_with_bluetooth<'a, PWR, SPI>(
|
||||
state: &'a mut State,
|
||||
pwr: PWR,
|
||||
spi: SPI,
|
||||
wifi_firmware: &[u8],
|
||||
bluetooth_firmware: &[u8],
|
||||
) -> (
|
||||
NetDriver<'a>,
|
||||
bluetooth::BtDriver<'a>,
|
||||
Control<'a>,
|
||||
Runner<'a, PWR, SPI>,
|
||||
)
|
||||
where
|
||||
PWR: OutputPin,
|
||||
SPI: SpiBusCyw43,
|
||||
{
|
||||
let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
|
||||
let state_ch = ch_runner.state_runner();
|
||||
|
||||
let (bt_runner, bt_driver) = bluetooth::new(&mut state.bt);
|
||||
let mut runner = Runner::new(
|
||||
ch_runner,
|
||||
Bus::new(pwr, spi),
|
||||
&state.ioctl_state,
|
||||
&state.net.events,
|
||||
#[cfg(feature = "bluetooth")]
|
||||
Some(bt_runner),
|
||||
);
|
||||
|
||||
runner.init(wifi_firmware, Some(bluetooth_firmware)).await;
|
||||
let control = Control::new(state_ch, &state.net.events, &state.ioctl_state);
|
||||
|
||||
(device, bt_driver, control, runner)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use embassy_futures::select::{select3, Either3};
|
||||
use embassy_futures::select::{select4, Either4};
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_time::{block_for, Duration, Timer};
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
@ -11,7 +11,8 @@ use crate::fmt::Bytes;
|
||||
use crate::ioctl::{IoctlState, IoctlType, PendingIoctl};
|
||||
use crate::nvram::NVRAM;
|
||||
use crate::structs::*;
|
||||
use crate::{events, slice8_mut, Core, CHIP, MTU};
|
||||
use crate::util::slice8_mut;
|
||||
use crate::{events, Core, CHIP, MTU};
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
struct LogState {
|
||||
@ -36,7 +37,7 @@ impl Default for LogState {
|
||||
/// Driver communicating with the WiFi chip.
|
||||
pub struct Runner<'a, PWR, SPI> {
|
||||
ch: ch::Runner<'a, MTU>,
|
||||
bus: Bus<PWR, SPI>,
|
||||
pub(crate) bus: Bus<PWR, SPI>,
|
||||
|
||||
ioctl_state: &'a IoctlState,
|
||||
ioctl_id: u16,
|
||||
@ -47,6 +48,9 @@ pub struct Runner<'a, PWR, SPI> {
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
log: LogState,
|
||||
|
||||
#[cfg(feature = "bluetooth")]
|
||||
pub(crate) bt: Option<crate::bluetooth::BtRunner<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, PWR, SPI> Runner<'a, PWR, SPI>
|
||||
@ -59,6 +63,7 @@ where
|
||||
bus: Bus<PWR, SPI>,
|
||||
ioctl_state: &'a IoctlState,
|
||||
events: &'a Events,
|
||||
#[cfg(feature = "bluetooth")] bt: Option<crate::bluetooth::BtRunner<'a>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ch,
|
||||
@ -70,33 +75,52 @@ where
|
||||
events,
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
log: LogState::default(),
|
||||
#[cfg(feature = "bluetooth")]
|
||||
bt,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn init(&mut self, firmware: &[u8]) {
|
||||
self.bus.init().await;
|
||||
pub(crate) async fn init(&mut self, wifi_fw: &[u8], bt_fw: Option<&[u8]>) {
|
||||
self.bus.init(bt_fw.is_some()).await;
|
||||
|
||||
// Init ALP (Active Low Power) clock
|
||||
debug!("init alp");
|
||||
self.bus
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ)
|
||||
.await;
|
||||
|
||||
debug!("set f2 watermark");
|
||||
self.bus
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10)
|
||||
.await;
|
||||
let watermark = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK).await;
|
||||
debug!("watermark = {:02x}", watermark);
|
||||
assert!(watermark == 0x10);
|
||||
|
||||
debug!("waiting for clock...");
|
||||
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {}
|
||||
debug!("clock ok");
|
||||
|
||||
// clear request for ALP
|
||||
debug!("clear request for ALP");
|
||||
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0).await;
|
||||
|
||||
let chip_id = self.bus.bp_read16(0x1800_0000).await;
|
||||
debug!("chip ID: {}", chip_id);
|
||||
|
||||
// Upload firmware.
|
||||
self.core_disable(Core::WLAN).await;
|
||||
self.core_disable(Core::SOCSRAM).await; // TODO: is this needed if we reset right after?
|
||||
self.core_reset(Core::SOCSRAM).await;
|
||||
|
||||
// this is 4343x specific stuff: Disable remap for SRAM_3
|
||||
self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await;
|
||||
self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await;
|
||||
|
||||
let ram_addr = CHIP.atcm_ram_base_address;
|
||||
|
||||
debug!("loading fw");
|
||||
self.bus.bp_write(ram_addr, firmware).await;
|
||||
self.bus.bp_write(ram_addr, wifi_fw).await;
|
||||
|
||||
debug!("loading nvram");
|
||||
// Round up to 4 bytes.
|
||||
@ -116,10 +140,23 @@ where
|
||||
self.core_reset(Core::WLAN).await;
|
||||
assert!(self.core_is_up(Core::WLAN).await);
|
||||
|
||||
// wait until HT clock is available; takes about 29ms
|
||||
debug!("wait for HT clock");
|
||||
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
|
||||
|
||||
// "Set up the interrupt mask and enable interrupts"
|
||||
// self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await;
|
||||
debug!("setup interrupt mask");
|
||||
self.bus
|
||||
.bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_SW_MASK)
|
||||
.await;
|
||||
|
||||
// Set up the interrupt mask and enable interrupts
|
||||
if bt_fw.is_some() {
|
||||
debug!("bluetooth setup interrupt mask");
|
||||
self.bus
|
||||
.bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_FC_CHANGE)
|
||||
.await;
|
||||
}
|
||||
|
||||
self.bus
|
||||
.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE)
|
||||
@ -128,11 +165,11 @@ where
|
||||
// "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped."
|
||||
// Sounds scary...
|
||||
self.bus
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32)
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, SPI_F2_WATERMARK)
|
||||
.await;
|
||||
|
||||
// wait for wifi startup
|
||||
debug!("waiting for wifi init...");
|
||||
// wait for F2 to be ready
|
||||
debug!("waiting for F2 to be ready...");
|
||||
while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {}
|
||||
|
||||
// Some random configs related to sleep.
|
||||
@ -153,19 +190,27 @@ where
|
||||
*/
|
||||
|
||||
// clear pulls
|
||||
debug!("clear pad pulls");
|
||||
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await;
|
||||
let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await;
|
||||
|
||||
// start HT clock
|
||||
//self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await;
|
||||
//debug!("waiting for HT clock...");
|
||||
//while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
|
||||
//debug!("clock ok");
|
||||
self.bus
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10)
|
||||
.await; // SBSDIO_HT_AVAIL_REQ
|
||||
debug!("waiting for HT clock...");
|
||||
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
|
||||
debug!("clock ok");
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
self.log_init().await;
|
||||
|
||||
debug!("wifi init done");
|
||||
#[cfg(feature = "bluetooth")]
|
||||
if let Some(bt_fw) = bt_fw {
|
||||
self.bt.as_mut().unwrap().init_bluetooth(&mut self.bus, bt_fw).await;
|
||||
}
|
||||
|
||||
debug!("cyw43 runner init done");
|
||||
}
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
@ -222,7 +267,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the
|
||||
/// Run the CYW43 event handling loop.
|
||||
pub async fn run(mut self) -> ! {
|
||||
let mut buf = [0; 512];
|
||||
loop {
|
||||
@ -231,11 +276,27 @@ where
|
||||
|
||||
if self.has_credit() {
|
||||
let ioctl = self.ioctl_state.wait_pending();
|
||||
let tx = self.ch.tx_buf();
|
||||
let wifi_tx = self.ch.tx_buf();
|
||||
#[cfg(feature = "bluetooth")]
|
||||
let bt_tx = async {
|
||||
match &mut self.bt {
|
||||
Some(bt) => bt.tx_chan.receive().await,
|
||||
None => core::future::pending().await,
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "bluetooth"))]
|
||||
let bt_tx = core::future::pending::<()>();
|
||||
|
||||
// interrupts aren't working yet for bluetooth. Do busy-polling instead.
|
||||
// Note for this to work `ev` has to go last in the `select()`. It prefers
|
||||
// first futures if they're ready, so other select branches don't get starved.`
|
||||
#[cfg(feature = "bluetooth")]
|
||||
let ev = core::future::ready(());
|
||||
#[cfg(not(feature = "bluetooth"))]
|
||||
let ev = self.bus.wait_for_event();
|
||||
|
||||
match select3(ioctl, tx, ev).await {
|
||||
Either3::First(PendingIoctl {
|
||||
match select4(ioctl, wifi_tx, bt_tx, ev).await {
|
||||
Either4::First(PendingIoctl {
|
||||
buf: iobuf,
|
||||
kind,
|
||||
cmd,
|
||||
@ -244,7 +305,7 @@ where
|
||||
self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }, &mut buf).await;
|
||||
self.check_status(&mut buf).await;
|
||||
}
|
||||
Either3::Second(packet) => {
|
||||
Either4::Second(packet) => {
|
||||
trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
|
||||
|
||||
let buf8 = slice8_mut(&mut buf);
|
||||
@ -298,8 +359,19 @@ where
|
||||
self.ch.tx_done();
|
||||
self.check_status(&mut buf).await;
|
||||
}
|
||||
Either3::Third(()) => {
|
||||
Either4::Third(_) => {
|
||||
#[cfg(feature = "bluetooth")]
|
||||
self.bt.as_mut().unwrap().hci_write(&mut self.bus).await;
|
||||
}
|
||||
Either4::Fourth(()) => {
|
||||
self.handle_irq(&mut buf).await;
|
||||
|
||||
// If we do busy-polling, make sure to yield.
|
||||
// `handle_irq` will only do a 32bit read if there's no work to do, which is really fast.
|
||||
// Depending on optimization level, it is possible that the 32-bit read finishes on
|
||||
// first poll, so it never yields and we starve all other tasks.
|
||||
#[cfg(feature = "bluetooth")]
|
||||
embassy_futures::yield_now().await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -314,17 +386,24 @@ where
|
||||
async fn handle_irq(&mut self, buf: &mut [u32; 512]) {
|
||||
// Receive stuff
|
||||
let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await;
|
||||
trace!("irq{}", FormatInterrupt(irq));
|
||||
if irq != 0 {
|
||||
trace!("irq{}", FormatInterrupt(irq));
|
||||
}
|
||||
|
||||
if irq & IRQ_F2_PACKET_AVAILABLE != 0 {
|
||||
self.check_status(buf).await;
|
||||
}
|
||||
|
||||
if irq & IRQ_DATA_UNAVAILABLE != 0 {
|
||||
// TODO what should we do here?
|
||||
warn!("IRQ DATA_UNAVAILABLE, clearing...");
|
||||
// this seems to be ignorable with no ill effects.
|
||||
trace!("IRQ DATA_UNAVAILABLE, clearing...");
|
||||
self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bluetooth")]
|
||||
if let Some(bt) = &mut self.bt {
|
||||
bt.handle_irq(&mut self.bus).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle F2 events while status register is set
|
||||
@ -481,7 +560,7 @@ where
|
||||
self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0
|
||||
}
|
||||
|
||||
async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8], buf: &mut [u32; 512]) {
|
||||
async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) {
|
||||
let buf8 = slice8_mut(buf);
|
||||
|
||||
let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len();
|
||||
@ -503,7 +582,7 @@ where
|
||||
};
|
||||
|
||||
let cdc_header = CdcHeader {
|
||||
cmd: cmd,
|
||||
cmd: cmd as u32,
|
||||
len: data.len() as _,
|
||||
flags: kind as u16 | (iface as u16) << 12,
|
||||
id: self.ioctl_id,
|
||||
|
@ -394,6 +394,15 @@ pub struct PassphraseInfo {
|
||||
}
|
||||
impl_bytes!(PassphraseInfo);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct SaePassphraseInfo {
|
||||
pub len: u16,
|
||||
pub passphrase: [u8; 128],
|
||||
}
|
||||
impl_bytes!(SaePassphraseInfo);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
|
20
cyw43/src/util.rs
Normal file
20
cyw43/src/util.rs
Normal file
@ -0,0 +1,20 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use core::slice;
|
||||
|
||||
pub(crate) fn slice8_mut(x: &mut [u32]) -> &mut [u8] {
|
||||
let len = x.len() * 4;
|
||||
unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) }
|
||||
}
|
||||
|
||||
pub(crate) fn is_aligned(a: u32, x: u32) -> bool {
|
||||
(a & (x - 1)) == 0
|
||||
}
|
||||
|
||||
pub(crate) fn round_down(x: u32, a: u32) -> u32 {
|
||||
x & !(a - 1)
|
||||
}
|
||||
|
||||
pub(crate) fn round_up(x: u32, a: u32) -> u32 {
|
||||
((x + a - 1) / a) * a
|
||||
}
|
@ -6,9 +6,9 @@ version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
embassy-executor = { version = "0.5.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-time = { version = "0.3.1", path = "../../../embassy-time", features = ["defmt"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
|
||||
embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] }
|
||||
embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
|
||||
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.3"
|
||||
|
@ -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.5.0", features = ["arch-cortex-m", "executor-thread"] }
|
||||
embassy-executor = { version = "0.6.0", features = ["arch-cortex-m", "executor-thread"] }
|
||||
|
||||
defmt = "0.3.0"
|
||||
defmt-rtt = "0.3.0"
|
||||
|
@ -4,7 +4,7 @@ So you've got one of the examples running, but what now? Let's go through a simp
|
||||
|
||||
== Main
|
||||
|
||||
The full example can be found link:https://github.com/embassy-rs/embassy/tree/master/docs/examples/basic[here].
|
||||
The full example can be found link:https://github.com/embassy-rs/embassy/tree/main/docs/examples/basic[here].
|
||||
|
||||
NOTE: If you’re using VS Code and rust-analyzer to view and edit the examples, you may need to make some changes to `.vscode/settings.json` to tell it which project we’re working on. Follow the instructions commented in that file to get rust-analyzer working correctly.
|
||||
|
||||
|
@ -19,6 +19,8 @@ The bootloader supports
|
||||
|
||||
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
|
||||
|
||||
STM32L0x1 devices require the `flash-erase-zero` feature to be enabled.
|
||||
|
||||
== Design
|
||||
|
||||
image::bootloader_flash.png[Bootloader flash layout]
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]!
|
||||
|
||||
* link:https://github.com/1-rafael-1/pi-pico-alarmclock-rust[A Raspberry Pi Pico W Alarmclock]
|
||||
** A hobbyist project building an alarm clock around a Pi Pico W complete with code, components list and enclosure design files.
|
||||
* link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware]
|
||||
** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more!
|
||||
** Targets STM32, RP2040, nRF52 and ESP32 MCUs
|
||||
|
@ -361,4 +361,14 @@ Practically, there's not a LOT of difference either way - so go with what makes
|
||||
|
||||
== splitting peripherals resources between tasks
|
||||
|
||||
There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example]
|
||||
There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example]
|
||||
|
||||
== My code/driver works in debug mode, but not release mode (or with LTO)
|
||||
|
||||
Issues like these while implementing drivers often fall into one of the following general causes, which are a good list of common errors to check for:
|
||||
|
||||
1. Some kind of race condition - the faster code means you miss an interrupt or something
|
||||
2. Some kind of UB, if you have unsafe code, or something like DMA with fences missing
|
||||
3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds
|
||||
4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary
|
||||
5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time
|
||||
|
@ -1,6 +1,6 @@
|
||||
= Starting a new project
|
||||
|
||||
Once you’ve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project.
|
||||
Once you’ve successfully xref:#_getting_started[run some example projects], the next step is to make a standalone Embassy project.
|
||||
|
||||
== Tools for generating Embassy projects
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
= Embassy nRF HAL
|
||||
|
||||
The link:https://github.com/embassy-rs/embassy/tree/master/embassy-nrf[Embassy nRF HAL] is based on the PACs (Peripheral Access Crate) from link:https://github.com/nrf-rs/[nrf-rs].
|
||||
The link:https://github.com/embassy-rs/embassy/tree/main/embassy-nrf[Embassy nRF HAL] is based on the PACs (Peripheral Access Crate) from link:https://github.com/nrf-rs/[nrf-rs].
|
||||
|
||||
== Timer driver
|
||||
|
||||
|
@ -48,7 +48,7 @@ link:https://github.com/lora-rs/lora-rs[lora-rs] supports LoRa networking on a w
|
||||
link:https://docs.embassy.dev/embassy-usb/[embassy-usb] implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
|
||||
=== Bootloader and DFU
|
||||
link:https://github.com/embassy-rs/embassy/tree/master/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
||||
link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
||||
|
||||
== What is DMA?
|
||||
|
||||
@ -77,3 +77,7 @@ For more reading material on async Rust and Embassy:
|
||||
* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy]
|
||||
* link:https://dev.to/apollolabsbin/series/20707[Tutorials]
|
||||
* link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy]
|
||||
|
||||
Videos:
|
||||
|
||||
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]
|
@ -8,7 +8,7 @@ The following examples shows different ways to use the on-board LED on a Raspber
|
||||
|
||||
Using mutual exclusion is the simplest way to share a peripheral.
|
||||
|
||||
TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here].
|
||||
TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here].
|
||||
[,rust]
|
||||
----
|
||||
use defmt::*;
|
||||
@ -78,7 +78,7 @@ To indicate that the pin will be set to an Output. The `AnyPin` could have been
|
||||
|
||||
A channel is another way to ensure exclusive access to a resource. Using a channel is great in the cases where the access can happen at a later point in time, allowing you to enqueue operations and do other things.
|
||||
|
||||
TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here].
|
||||
TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here].
|
||||
[,rust]
|
||||
----
|
||||
use defmt::*;
|
||||
|
@ -1,6 +1,6 @@
|
||||
= Embassy STM32 HAL
|
||||
|
||||
The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy STM32 HAL] is based on the `stm32-metapac` project.
|
||||
The link:https://github.com/embassy-rs/embassy/tree/main/embassy-stm32[Embassy STM32 HAL] is based on the `stm32-metapac` project.
|
||||
|
||||
== The infinite variant problem
|
||||
|
||||
|
@ -16,7 +16,7 @@ The `embassy::time::Timer` type provides two timing methods.
|
||||
|
||||
An example of a delay is provided as follows:
|
||||
|
||||
TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here].
|
||||
TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here].
|
||||
[,rust]
|
||||
----
|
||||
use embassy::executor::{task, Executor};
|
||||
@ -41,7 +41,7 @@ that expect a generic delay implementation to be provided.
|
||||
|
||||
An example of how this can be used:
|
||||
|
||||
TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here].
|
||||
TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here].
|
||||
[,rust]
|
||||
----
|
||||
use embassy::executor::{task, Executor};
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "embassy-boot-nrf"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Bootloader lib for nRF chips"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
@ -25,8 +25,8 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.17", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embassy-nrf = { version = "0.1.0", path = "../embassy-nrf", default-features = false }
|
||||
embassy-boot = { version = "0.2.0", path = "../embassy-boot" }
|
||||
embassy-nrf = { version = "0.2.0", path = "../embassy-nrf", default-features = false }
|
||||
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.1"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "embassy-boot-rp"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Bootloader lib for RP2040 chips"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
@ -16,6 +16,7 @@ categories = [
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-rp/src/"
|
||||
target = "thumbv6m-none-eabi"
|
||||
features = ["embassy-rp/rp2040"]
|
||||
|
||||
[lib]
|
||||
|
||||
@ -24,9 +25,9 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embassy-rp = { version = "0.1.0", path = "../embassy-rp", default-features = false }
|
||||
embassy-boot = { version = "0.2.0", path = "../embassy-boot" }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-rp = { version = "0.2.0", path = "../embassy-rp", default-features = false }
|
||||
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
|
@ -26,7 +26,7 @@ log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false }
|
||||
embassy-boot = { version = "0.2.0", path = "../embassy-boot" }
|
||||
embassy-boot = { version = "0.3.0", path = "../embassy-boot" }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.1"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "embassy-boot"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
@ -27,8 +27,8 @@ features = ["defmt"]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
digest = "0.10"
|
||||
log = { version = "0.4", optional = true }
|
||||
ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true }
|
||||
embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }
|
||||
ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true }
|
||||
embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
@ -42,11 +42,12 @@ rand = "0.8"
|
||||
futures = { version = "0.3", features = ["executor"] }
|
||||
sha1 = "0.10.5"
|
||||
critical-section = { version = "1.1.1", features = ["std"] }
|
||||
ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] }
|
||||
ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] }
|
||||
|
||||
[features]
|
||||
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
|
||||
ed25519-salty = ["dep:salty", "_verify"]
|
||||
flash-erase-zero = []
|
||||
|
||||
#Internal features
|
||||
_verify = []
|
||||
|
@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
||||
|
||||
use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||
use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||
|
||||
/// Errors returned by bootloader
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
@ -276,7 +276,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
||||
self.state.erase(0, self.state.capacity() as u32)?;
|
||||
|
||||
// Set magic
|
||||
state_word.fill(BOOT_MAGIC);
|
||||
state_word.fill(REVERT_MAGIC);
|
||||
self.state.write(0, state_word)?;
|
||||
}
|
||||
}
|
||||
@ -411,6 +411,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
||||
Ok(State::Swap)
|
||||
} else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||
Ok(State::DfuDetach)
|
||||
} else if !state_word.iter().any(|&b| b != REVERT_MAGIC) {
|
||||
Ok(State::Revert)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
|
@ -107,7 +107,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?;
|
||||
return self.state.mark_updated().await;
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
||||
message,
|
||||
r.is_ok()
|
||||
);
|
||||
r.map_err(into_signature_error)?
|
||||
r.map_err(into_signature_error)?;
|
||||
return self.state.mark_updated().await;
|
||||
}
|
||||
#[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
|
||||
{
|
||||
Err(FirmwareUpdaterError::Signature(signature::Error::new()))
|
||||
}
|
||||
|
||||
self.state.mark_updated().await
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
@ -285,7 +289,8 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
|
||||
|
||||
// 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 {
|
||||
let state = self.get_state().await?;
|
||||
if state == State::Boot || state == State::DfuDetach || state == State::Revert {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
@ -299,12 +304,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
|
||||
/// `mark_booted`.
|
||||
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned).await?;
|
||||
|
||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
Ok(State::from(&self.aligned))
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
|
@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?;
|
||||
return self.state.mark_updated();
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
|
||||
message,
|
||||
r.is_ok()
|
||||
);
|
||||
r.map_err(into_signature_error)?
|
||||
r.map_err(into_signature_error)?;
|
||||
return self.state.mark_updated();
|
||||
}
|
||||
#[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
|
||||
{
|
||||
Err(FirmwareUpdaterError::Signature(signature::Error::new()))
|
||||
}
|
||||
|
||||
self.state.mark_updated()
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
@ -320,7 +324,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
|
||||
|
||||
// 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 || self.get_state()? == State::DfuDetach {
|
||||
let state = self.get_state()?;
|
||||
if state == State::Boot || state == State::DfuDetach || state == State::Revert {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
@ -334,14 +339,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
|
||||
/// `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 if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||
Ok(State::DfuDetach)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
Ok(State::from(&self.aligned))
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
|
@ -14,13 +14,18 @@ mod test_flash;
|
||||
|
||||
// The expected value of the flash after an erase
|
||||
// TODO: Use the value provided by NorFlash when available
|
||||
#[cfg(not(feature = "flash-erase-zero"))]
|
||||
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||
#[cfg(feature = "flash-erase-zero")]
|
||||
pub(crate) const STATE_ERASE_VALUE: u8 = 0x00;
|
||||
|
||||
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
|
||||
pub use firmware_updater::{
|
||||
BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
|
||||
FirmwareUpdaterError,
|
||||
};
|
||||
|
||||
pub(crate) const REVERT_MAGIC: u8 = 0xC0;
|
||||
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
|
||||
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
|
||||
pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
|
||||
@ -33,10 +38,30 @@ pub enum State {
|
||||
Boot,
|
||||
/// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
|
||||
Swap,
|
||||
/// Bootloader has reverted the active partition with the dfu partition and will attempt boot.
|
||||
Revert,
|
||||
/// Application has received a request to reboot into DFU mode to apply an update.
|
||||
DfuDetach,
|
||||
}
|
||||
|
||||
impl<T> From<T> for State
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn from(magic: T) -> State {
|
||||
let magic = magic.as_ref();
|
||||
if !magic.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
State::Swap
|
||||
} else if !magic.iter().any(|&b| b != REVERT_MAGIC) {
|
||||
State::Revert
|
||||
} else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||
State::DfuDetach
|
||||
} else {
|
||||
State::Boot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
|
||||
#[repr(align(32))]
|
||||
pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
|
||||
@ -153,6 +178,9 @@ mod tests {
|
||||
// Running again should cause a revert
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
// Next time we know it was reverted
|
||||
assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
|
21
embassy-embedded-hal/CHANGELOG.md
Normal file
21
embassy-embedded-hal/CHANGELOG.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Changelog for embassy-embedded-hal
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.2.0 - 2024-08-05
|
||||
|
||||
- Add Clone derive to flash Partition in embassy-embedded-hal
|
||||
- Add support for all word sizes to async shared spi
|
||||
- Add Copy and 'static constraint to Word type in SPI structs
|
||||
- Improve flexibility by introducing SPI word size as a generic parameter
|
||||
- Allow changing Spi/I2cDeviceWithConfig's config at runtime
|
||||
- Impl `MultiwriteNorFlash` for `BlockingAsync`
|
||||
|
||||
## 0.1.0 - 2024-01-10
|
||||
|
||||
- First release
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-embedded-hal"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy."
|
||||
@ -29,7 +29,7 @@ default = ["time"]
|
||||
[dependencies]
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time", optional = true }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||
"unproven",
|
||||
] }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-executor-macros"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "macros for creating the entry point and tasks for embassy-executor"
|
||||
|
@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.6.0 - 2024-08-05
|
||||
|
||||
- Add collapse_debuginfo to fmt.rs macros.
|
||||
- initial support for avr
|
||||
- use nightly waker_getters APIs
|
||||
|
||||
## 0.5.0 - 2024-01-11
|
||||
|
||||
- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-executor"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "async/await executor designed for embedded usage"
|
||||
@ -33,7 +33,7 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
rtos-trace = { version = "0.1.2", optional = true }
|
||||
|
||||
embassy-executor-macros = { version = "0.4.0", path = "../embassy-executor-macros" }
|
||||
embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" }
|
||||
embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true }
|
||||
embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true }
|
||||
critical-section = "1.1"
|
||||
@ -79,7 +79,7 @@ arch-cortex-m = ["_arch", "dep:cortex-m"]
|
||||
## RISC-V 32
|
||||
arch-riscv32 = ["_arch"]
|
||||
## WASM
|
||||
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
|
||||
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"]
|
||||
## AVR
|
||||
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
|
||||
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::process::Command;
|
||||
|
||||
/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring
|
||||
/// them (`cargo:rust-check-cfg=cfg(X)`).
|
||||
@ -17,7 +15,6 @@ use std::process::Command;
|
||||
pub struct CfgSet {
|
||||
enabled: HashSet<String>,
|
||||
declared: HashSet<String>,
|
||||
emit_declared: bool,
|
||||
}
|
||||
|
||||
impl CfgSet {
|
||||
@ -25,7 +22,6 @@ impl CfgSet {
|
||||
Self {
|
||||
enabled: HashSet::new(),
|
||||
declared: HashSet::new(),
|
||||
emit_declared: is_rustc_nightly(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +45,7 @@ impl CfgSet {
|
||||
///
|
||||
/// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid.
|
||||
pub fn declare(&mut self, cfg: impl AsRef<str>) {
|
||||
if self.declared.insert(cfg.as_ref().to_owned()) && self.emit_declared {
|
||||
if self.declared.insert(cfg.as_ref().to_owned()) {
|
||||
println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref());
|
||||
}
|
||||
}
|
||||
@ -69,21 +65,6 @@ impl CfgSet {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_rustc_nightly() -> bool {
|
||||
if env::var_os("EMBASSY_FORCE_CHECK_CFG").is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
|
||||
|
||||
let output = Command::new(rustc)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect("failed to run `rustc --version`");
|
||||
|
||||
String::from_utf8_lossy(&output.stdout).contains("nightly")
|
||||
}
|
||||
|
||||
/// Sets configs that describe the target platform.
|
||||
pub fn set_target_cfgs(cfgs: &mut CfgSet) {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
||||
#![cfg_attr(feature = "nightly", feature(waker_getters))]
|
||||
#![allow(clippy::new_without_default)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
@ -50,8 +50,7 @@ pub fn task_from_waker(waker: &Waker) -> TaskRef {
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
{
|
||||
let raw_waker = waker.as_raw();
|
||||
(raw_waker.vtable(), raw_waker.data())
|
||||
(waker.vtable(), waker.data())
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -237,7 +237,7 @@ impl<Fut: Future, const N: usize> Future for SelectArray<Fut, N> {
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct SelectSlice<'a, Fut> {
|
||||
inner: &'a mut [Fut],
|
||||
inner: Pin<&'a mut [Fut]>,
|
||||
}
|
||||
|
||||
/// Creates a new future which will select over a slice of futures.
|
||||
@ -247,31 +247,26 @@ pub struct SelectSlice<'a, Fut> {
|
||||
/// future that was ready.
|
||||
///
|
||||
/// If the slice is empty, the resulting future will be Pending forever.
|
||||
pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> {
|
||||
pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> {
|
||||
SelectSlice { inner: slice }
|
||||
}
|
||||
|
||||
impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> {
|
||||
type Output = (Fut::Output, usize);
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move,
|
||||
// its elements also cannot move. Therefore it is safe to access `inner` and pin
|
||||
// references to the contained futures.
|
||||
let item = unsafe {
|
||||
self.get_unchecked_mut()
|
||||
.inner
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) {
|
||||
Poll::Pending => None,
|
||||
Poll::Ready(e) => Some((i, e)),
|
||||
})
|
||||
};
|
||||
|
||||
match item {
|
||||
Some((idx, res)) => Poll::Ready((res, idx)),
|
||||
None => Poll::Pending,
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// Safety: refer to
|
||||
// https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2
|
||||
#[inline(always)]
|
||||
fn pin_iter<T>(slice: Pin<&mut [T]>) -> impl Iterator<Item = Pin<&mut T>> {
|
||||
unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) }
|
||||
}
|
||||
for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() {
|
||||
if let Poll::Ready(res) = fut.poll(cx) {
|
||||
return Poll::Ready((res, i));
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-hal-internal"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY."
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::process::Command;
|
||||
|
||||
/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring
|
||||
/// them (`cargo:rust-check-cfg=cfg(X)`).
|
||||
@ -17,7 +15,6 @@ use std::process::Command;
|
||||
pub struct CfgSet {
|
||||
enabled: HashSet<String>,
|
||||
declared: HashSet<String>,
|
||||
emit_declared: bool,
|
||||
}
|
||||
|
||||
impl CfgSet {
|
||||
@ -25,7 +22,6 @@ impl CfgSet {
|
||||
Self {
|
||||
enabled: HashSet::new(),
|
||||
declared: HashSet::new(),
|
||||
emit_declared: is_rustc_nightly(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +45,7 @@ impl CfgSet {
|
||||
///
|
||||
/// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid.
|
||||
pub fn declare(&mut self, cfg: impl AsRef<str>) {
|
||||
if self.declared.insert(cfg.as_ref().to_owned()) && self.emit_declared {
|
||||
if self.declared.insert(cfg.as_ref().to_owned()) {
|
||||
println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref());
|
||||
}
|
||||
}
|
||||
@ -69,21 +65,6 @@ impl CfgSet {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_rustc_nightly() -> bool {
|
||||
if env::var_os("EMBASSY_FORCE_CHECK_CFG").is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
|
||||
|
||||
let output = Command::new(rustc)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect("failed to run `rustc --version`");
|
||||
|
||||
String::from_utf8_lossy(&output.stdout).contains("nightly")
|
||||
}
|
||||
|
||||
/// Sets configs that describe the target platform.
|
||||
pub fn set_target_cfgs(cfgs: &mut CfgSet) {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
@ -16,8 +16,8 @@ log = { version = "0.4", default-features = false, optional = true }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
|
||||
embedded-hal-async = { version = "1.0" }
|
||||
embedded-hal-bus = { version = "0.1", features = ["async"] }
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
bitfield = "0.14.0"
|
||||
|
||||
|
@ -21,7 +21,7 @@ APL can be used in [`intrinsic safety applications/explosion hazardous areas`](h
|
||||
|
||||
## Supported SPI modes
|
||||
|
||||
`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf)
|
||||
`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/wp-content/uploads/2023/12/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf)
|
||||
|
||||
Both modes support with and without additional CRC.
|
||||
Currently only `Generic` SPI with or without CRC is supported.
|
||||
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.3.0 - 2024-08-05
|
||||
|
||||
- Add collapse_debuginfo to fmt.rs macros.
|
||||
- Update embassy-sync version
|
||||
|
||||
## 0.2.0 - 2023-10-18
|
||||
|
||||
- Update `embassy-net-driver` to v0.2
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-net-driver-channel"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack."
|
||||
|
@ -13,7 +13,7 @@ documentation = "https://docs.embassy.dev/embassy-net-enc28j60"
|
||||
embedded-hal = { version = "1.0" }
|
||||
embedded-hal-async = { version = "1.0" }
|
||||
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
@ -17,10 +17,10 @@ log = [ "dep:log" ]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
|
||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
embedded-hal = { version = "1.0" }
|
||||
embedded-hal-async = { version = "1.0" }
|
||||
|
@ -120,7 +120,7 @@ impl<'a> Control<'a> {
|
||||
pwd: unwrap!(String::try_from(password)),
|
||||
bssid: String::new(),
|
||||
listen_interval: 3,
|
||||
is_wpa3_supported: false,
|
||||
is_wpa3_supported: true,
|
||||
};
|
||||
ioctl!(self, ReqConnectAp, RespConnectAp, req, resp);
|
||||
self.state_ch.set_link_state(LinkState::Up);
|
||||
|
@ -137,7 +137,7 @@ where
|
||||
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 {
|
||||
let runner = Runner {
|
||||
ch: ch_runner,
|
||||
state_ch,
|
||||
shared: &state.shared,
|
||||
@ -148,7 +148,6 @@ where
|
||||
spi,
|
||||
heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP,
|
||||
};
|
||||
runner.init().await;
|
||||
|
||||
(device, Control::new(state_ch, &state.shared), runner)
|
||||
}
|
||||
@ -174,8 +173,6 @@ where
|
||||
IN: InputPin + Wait,
|
||||
OUT: OutputPin,
|
||||
{
|
||||
async fn init(&mut self) {}
|
||||
|
||||
/// Run the packet processing.
|
||||
pub async fn run(mut self) -> ! {
|
||||
debug!("resetting...");
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use heapless::{String, Vec};
|
||||
|
||||
/// internal supporting structures for CtrlMsg
|
||||
|
38
embassy-net-nrf91/Cargo.toml
Normal file
38
embassy-net-nrf91/Cargo.toml
Normal file
@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "embassy-net-nrf91"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "embassy-net driver for Nordic nRF91-series cellular modems"
|
||||
keywords = ["embedded", "nrf91", "embassy-net", "cellular"]
|
||||
categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
documentation = "https://docs.embassy.dev/embassy-net-nrf91"
|
||||
|
||||
[features]
|
||||
defmt = [ "dep:defmt", "heapless/defmt-03" ]
|
||||
log = [ "dep:log" ]
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
nrf9160-pac = { version = "0.12.0" }
|
||||
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
heapless = "0.8"
|
||||
embedded-io = "0.6.1"
|
||||
at-commands = "0.5.4"
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/"
|
||||
target = "thumbv7em-none-eabi"
|
||||
features = ["defmt"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["defmt"]
|
9
embassy-net-nrf91/README.md
Normal file
9
embassy-net-nrf91/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# nRF91 `embassy-net` integration
|
||||
|
||||
[`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems.
|
||||
|
||||
See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160.
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
350
embassy-net-nrf91/src/context.rs
Normal file
350
embassy-net-nrf91/src/context.rs
Normal file
@ -0,0 +1,350 @@
|
||||
//! Helper utility to configure a specific modem context.
|
||||
use core::net::IpAddr;
|
||||
use core::str::FromStr;
|
||||
|
||||
use at_commands::builder::CommandBuilder;
|
||||
use at_commands::parser::CommandParser;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use heapless::Vec;
|
||||
|
||||
/// Provides a higher level API for controlling a given context.
|
||||
pub struct Control<'a> {
|
||||
control: crate::Control<'a>,
|
||||
cid: u8,
|
||||
}
|
||||
|
||||
/// Configuration for a given context
|
||||
pub struct Config<'a> {
|
||||
/// Desired APN address.
|
||||
pub apn: &'a [u8],
|
||||
/// Desired authentication protocol.
|
||||
pub auth_prot: AuthProt,
|
||||
/// Credentials.
|
||||
pub auth: Option<(&'a [u8], &'a [u8])>,
|
||||
}
|
||||
|
||||
/// Authentication protocol.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum AuthProt {
|
||||
/// No authentication.
|
||||
None = 0,
|
||||
/// PAP authentication.
|
||||
Pap = 1,
|
||||
/// CHAP authentication.
|
||||
Chap = 2,
|
||||
}
|
||||
|
||||
/// Error returned by control.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
/// Not enough space for command.
|
||||
BufferTooSmall,
|
||||
/// Error parsing response from modem.
|
||||
AtParseError,
|
||||
/// Error parsing IP addresses.
|
||||
AddrParseError,
|
||||
}
|
||||
|
||||
impl From<at_commands::parser::ParseError> for Error {
|
||||
fn from(_: at_commands::parser::ParseError) -> Self {
|
||||
Self::AtParseError
|
||||
}
|
||||
}
|
||||
|
||||
/// Status of a given context.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Status {
|
||||
/// Attached to APN or not.
|
||||
pub attached: bool,
|
||||
/// IP if assigned.
|
||||
pub ip: Option<IpAddr>,
|
||||
/// Gateway if assigned.
|
||||
pub gateway: Option<IpAddr>,
|
||||
/// DNS servers if assigned.
|
||||
pub dns: Vec<IpAddr, 2>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Status {
|
||||
fn format(&self, f: defmt::Formatter<'_>) {
|
||||
defmt::write!(f, "attached: {}", self.attached);
|
||||
if let Some(ip) = &self.ip {
|
||||
defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Control<'a> {
|
||||
/// Create a new instance of a control handle for a given context.
|
||||
///
|
||||
/// Will wait for the modem to be initialized if not.
|
||||
pub async fn new(control: crate::Control<'a>, cid: u8) -> Self {
|
||||
control.wait_init().await;
|
||||
Self { control, cid }
|
||||
}
|
||||
|
||||
/// Perform a raw AT command
|
||||
pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize {
|
||||
self.control.at_command(req, resp).await
|
||||
}
|
||||
|
||||
/// Configures the modem with the provided config.
|
||||
///
|
||||
/// NOTE: This will disconnect the modem from any current APN and should not
|
||||
/// be called if the configuration has not been changed.
|
||||
///
|
||||
/// After configuring, invoke [`enable()`] to activate the configuration.
|
||||
pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CFUN")
|
||||
.with_int_parameter(0)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CGDCONT")
|
||||
.with_int_parameter(self.cid)
|
||||
.with_string_parameter("IP")
|
||||
.with_string_parameter(config.apn)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
// info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
|
||||
let mut op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CGAUTH")
|
||||
.with_int_parameter(self.cid)
|
||||
.with_int_parameter(config.auth_prot as u8);
|
||||
if let Some((username, password)) = config.auth {
|
||||
op = op.with_string_parameter(username).with_string_parameter(password);
|
||||
}
|
||||
let op = op.finish().map_err(|_| Error::BufferTooSmall)?;
|
||||
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
// info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attach to the PDN
|
||||
pub async fn attach(&self) -> Result<(), Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CGATT")
|
||||
.with_int_parameter(1)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read current connectivity status for modem.
|
||||
pub async fn detach(&self) -> Result<(), Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CGATT")
|
||||
.with_int_parameter(0)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn attached(&self) -> Result<bool, Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
|
||||
let op = CommandBuilder::create_query(&mut cmd, true)
|
||||
.named("+CGATT")
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
let (res,) = CommandParser::parse(&buf[..n])
|
||||
.expect_identifier(b"+CGATT: ")
|
||||
.expect_int_parameter()
|
||||
.expect_identifier(b"\r\nOK")
|
||||
.finish()?;
|
||||
Ok(res == 1)
|
||||
}
|
||||
|
||||
/// Read current connectivity status for modem.
|
||||
pub async fn status(&self) -> Result<Status, Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
|
||||
let op = CommandBuilder::create_query(&mut cmd, true)
|
||||
.named("+CGATT")
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
let (res,) = CommandParser::parse(&buf[..n])
|
||||
.expect_identifier(b"+CGATT: ")
|
||||
.expect_int_parameter()
|
||||
.expect_identifier(b"\r\nOK")
|
||||
.finish()?;
|
||||
let attached = res == 1;
|
||||
if !attached {
|
||||
return Ok(Status {
|
||||
attached,
|
||||
ip: None,
|
||||
gateway: None,
|
||||
dns: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CGPADDR")
|
||||
.with_int_parameter(self.cid)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
let (_, ip1, _ip2) = CommandParser::parse(&buf[..n])
|
||||
.expect_identifier(b"+CGPADDR: ")
|
||||
.expect_int_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_identifier(b"\r\nOK")
|
||||
.finish()?;
|
||||
|
||||
let ip = if let Some(ip) = ip1 {
|
||||
let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
|
||||
Some(ip)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CGCONTRDP")
|
||||
.with_int_parameter(self.cid)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n])
|
||||
.expect_identifier(b"+CGCONTRDP: ")
|
||||
.expect_int_parameter()
|
||||
.expect_optional_int_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_optional_string_parameter()
|
||||
.expect_optional_int_parameter()
|
||||
.expect_optional_int_parameter()
|
||||
.expect_optional_int_parameter()
|
||||
.expect_optional_int_parameter()
|
||||
.expect_optional_int_parameter()
|
||||
.expect_identifier(b"\r\nOK")
|
||||
.finish()?;
|
||||
|
||||
let gateway = if let Some(ip) = gateway {
|
||||
if ip.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut dns = Vec::new();
|
||||
if let Some(ip) = dns1 {
|
||||
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if let Some(ip) = dns2 {
|
||||
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(Status {
|
||||
attached,
|
||||
ip,
|
||||
gateway,
|
||||
dns,
|
||||
})
|
||||
}
|
||||
|
||||
async fn wait_attached(&self) -> Result<Status, Error> {
|
||||
while !self.attached().await? {
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
}
|
||||
let status = self.status().await?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Disable modem
|
||||
pub async fn disable(&self) -> Result<(), Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CFUN")
|
||||
.with_int_parameter(0)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable modem
|
||||
pub async fn enable(&self) -> Result<(), Error> {
|
||||
let mut cmd: [u8; 256] = [0; 256];
|
||||
let mut buf: [u8; 256] = [0; 256];
|
||||
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("+CFUN")
|
||||
.with_int_parameter(1)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
|
||||
// Make modem survive PDN detaches
|
||||
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||
.named("%XPDNCFG")
|
||||
.with_int_parameter(1)
|
||||
.finish()
|
||||
.map_err(|_| Error::BufferTooSmall)?;
|
||||
let n = self.control.at_command(op, &mut buf).await;
|
||||
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a control loop for this context, ensuring that reaattach is handled.
|
||||
pub async fn run<F: Fn(&Status)>(&self, reattach: F) -> Result<(), Error> {
|
||||
self.enable().await?;
|
||||
let status = self.wait_attached().await?;
|
||||
let mut fd = self.control.open_raw_socket().await;
|
||||
reattach(&status);
|
||||
|
||||
loop {
|
||||
if !self.attached().await? {
|
||||
trace!("detached");
|
||||
|
||||
self.control.close_raw_socket(fd).await;
|
||||
let status = self.wait_attached().await?;
|
||||
trace!("attached");
|
||||
fd = self.control.open_raw_socket().await;
|
||||
reattach(&status);
|
||||
}
|
||||
Timer::after(Duration::from_secs(10)).await;
|
||||
}
|
||||
}
|
||||
}
|
274
embassy-net-nrf91/src/fmt.rs
Normal file
274
embassy-net-nrf91/src/fmt.rs
Normal file
@ -0,0 +1,274 @@
|
||||
#![macro_use]
|
||||
#![allow(unused)]
|
||||
|
||||
use core::fmt::{Debug, Display, LowerHex};
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! todo {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::todo!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::todo!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::core::unreachable!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unreachable!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! panic {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::panic!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::panic!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! trace {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::trace!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::debug!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! info {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::info!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! warn {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::warn!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! error {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::error!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unwrap!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($arg:expr) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
pub trait Try {
|
||||
type Ok;
|
||||
type Error;
|
||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
1046
embassy-net-nrf91/src/lib.rs
Normal file
1046
embassy-net-nrf91/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embedded-io-async = { version = "0.6.1" }
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
ppproto = { version = "0.1.2"}
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
|
@ -12,8 +12,8 @@ documentation = "https://docs.embassy.dev/embassy-net-wiznet"
|
||||
[dependencies]
|
||||
embedded-hal = { version = "1.0" }
|
||||
embedded-hal-async = { version = "1.0" }
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
|
@ -16,11 +16,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 = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"]
|
||||
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"]
|
||||
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@ -44,6 +44,8 @@ raw = ["smoltcp/socket-raw"]
|
||||
tcp = ["smoltcp/socket-tcp"]
|
||||
## Enable DNS support
|
||||
dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"]
|
||||
## Enable mDNS support
|
||||
mdns = ["dns", "smoltcp/socket-mdns"]
|
||||
## Enable DHCPv4 support
|
||||
dhcpv4 = ["proto-ipv4", "medium-ethernet", "smoltcp/socket-dhcpv4"]
|
||||
## Enable DHCPv4 support with hostname
|
||||
@ -58,21 +60,21 @@ medium-ethernet = ["smoltcp/medium-ethernet"]
|
||||
medium-ip = ["smoltcp/medium-ip"]
|
||||
## Enable the IEEE 802.15.4 medium
|
||||
medium-ieee802154 = ["smoltcp/medium-ieee802154"]
|
||||
## Enable IGMP support
|
||||
igmp = ["smoltcp/proto-igmp"]
|
||||
## Enable multicast support (for both ipv4 and/or ipv6 if enabled)
|
||||
multicast = ["smoltcp/multicast"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
smoltcp = { version = "0.11.0", default-features = false, features = [
|
||||
smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [
|
||||
"socket",
|
||||
"async",
|
||||
] }
|
||||
|
||||
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embedded-io-async = { version = "0.6.1" }
|
||||
|
||||
|
@ -10,8 +10,9 @@ memory management designed to work well for embedded systems, aiming for a more
|
||||
|
||||
- IPv4, IPv6
|
||||
- Ethernet and bare-IP mediums.
|
||||
- TCP, UDP, DNS, DHCPv4, IGMPv4
|
||||
- TCP, UDP, DNS, DHCPv4
|
||||
- TCP sockets implement the `embedded-io` async traits.
|
||||
- Multicast
|
||||
|
||||
See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and
|
||||
unimplemented features of the network protocols.
|
||||
|
@ -9,7 +9,7 @@ pub use smoltcp::socket::dns::{DnsQuery, Socket};
|
||||
pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError};
|
||||
pub use smoltcp::wire::{DnsQueryType, IpAddress};
|
||||
|
||||
use crate::{Driver, Stack};
|
||||
use crate::Stack;
|
||||
|
||||
/// Errors returned by DnsSocket.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
@ -44,21 +44,15 @@ impl From<StartQueryError> for Error {
|
||||
/// This exists only for compatibility with crates that use `embedded-nal-async`.
|
||||
/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're
|
||||
/// not using `embedded-nal-async`.
|
||||
pub struct DnsSocket<'a, D>
|
||||
where
|
||||
D: Driver + 'static,
|
||||
{
|
||||
stack: &'a Stack<D>,
|
||||
pub struct DnsSocket<'a> {
|
||||
stack: Stack<'a>,
|
||||
}
|
||||
|
||||
impl<'a, D> DnsSocket<'a, D>
|
||||
where
|
||||
D: Driver + 'static,
|
||||
{
|
||||
impl<'a> DnsSocket<'a> {
|
||||
/// Create a new DNS socket using the provided stack.
|
||||
///
|
||||
/// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated.
|
||||
pub fn new(stack: &'a Stack<D>) -> Self {
|
||||
pub fn new(stack: Stack<'a>) -> Self {
|
||||
Self { stack }
|
||||
}
|
||||
|
||||
@ -72,10 +66,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
|
||||
where
|
||||
D: Driver + 'static,
|
||||
{
|
||||
impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
|
||||
type Error = Error;
|
||||
|
||||
async fn get_host_by_name(
|
||||
@ -124,3 +115,7 @@ where
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> {
|
||||
x
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ where
|
||||
{
|
||||
fn consume<R, F>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> R,
|
||||
F: FnOnce(&[u8]) -> R,
|
||||
{
|
||||
self.0.consume(|buf| {
|
||||
#[cfg(feature = "packet-trace")]
|
@ -12,9 +12,9 @@ compile_error!("You must enable at least one of the following features: proto-ip
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
mod device;
|
||||
#[cfg(feature = "dns")]
|
||||
pub mod dns;
|
||||
mod driver_util;
|
||||
#[cfg(feature = "raw")]
|
||||
pub mod raw;
|
||||
#[cfg(feature = "tcp")]
|
||||
@ -25,6 +25,7 @@ pub mod udp;
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::mem::MaybeUninit;
|
||||
use core::pin::pin;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
@ -34,7 +35,9 @@ use embassy_sync::waitqueue::WakerRegistration;
|
||||
use embassy_time::{Instant, Timer};
|
||||
#[allow(unused_imports)]
|
||||
use heapless::Vec;
|
||||
#[cfg(feature = "igmp")]
|
||||
#[cfg(feature = "dns")]
|
||||
pub use smoltcp::config::DNS_MAX_SERVER_COUNT;
|
||||
#[cfg(feature = "multicast")]
|
||||
pub use smoltcp::iface::MulticastError;
|
||||
#[allow(unused_imports)]
|
||||
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
|
||||
@ -55,7 +58,7 @@ pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
|
||||
|
||||
use crate::device::DriverAdapter;
|
||||
use crate::driver_util::DriverAdapter;
|
||||
use crate::time::{instant_from_smoltcp, instant_to_smoltcp};
|
||||
|
||||
const LOCAL_PORT_MIN: u16 = 1025;
|
||||
@ -67,33 +70,33 @@ const MAX_HOSTNAME_LEN: usize = 32;
|
||||
|
||||
/// Memory resources needed for a network stack.
|
||||
pub struct StackResources<const SOCK: usize> {
|
||||
sockets: [SocketStorage<'static>; SOCK],
|
||||
sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>,
|
||||
inner: MaybeUninit<RefCell<Inner>>,
|
||||
#[cfg(feature = "dns")]
|
||||
queries: [Option<dns::DnsQuery>; MAX_QUERIES],
|
||||
queries: MaybeUninit<[Option<dns::DnsQuery>; MAX_QUERIES]>,
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
hostname: core::cell::UnsafeCell<HostnameResources>,
|
||||
hostname: HostnameResources,
|
||||
}
|
||||
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
struct HostnameResources {
|
||||
option: smoltcp::wire::DhcpOption<'static>,
|
||||
data: [u8; MAX_HOSTNAME_LEN],
|
||||
option: MaybeUninit<smoltcp::wire::DhcpOption<'static>>,
|
||||
data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>,
|
||||
}
|
||||
|
||||
impl<const SOCK: usize> StackResources<SOCK> {
|
||||
/// Create a new set of stack resources.
|
||||
pub const fn new() -> Self {
|
||||
#[cfg(feature = "dns")]
|
||||
const INIT: Option<dns::DnsQuery> = None;
|
||||
Self {
|
||||
sockets: [SocketStorage::EMPTY; SOCK],
|
||||
sockets: MaybeUninit::uninit(),
|
||||
inner: MaybeUninit::uninit(),
|
||||
#[cfg(feature = "dns")]
|
||||
queries: [INIT; MAX_QUERIES],
|
||||
queries: MaybeUninit::uninit(),
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
hostname: core::cell::UnsafeCell::new(HostnameResources {
|
||||
option: smoltcp::wire::DhcpOption { kind: 0, data: &[] },
|
||||
data: [0; MAX_HOSTNAME_LEN],
|
||||
}),
|
||||
hostname: HostnameResources {
|
||||
option: MaybeUninit::uninit(),
|
||||
data: MaybeUninit::uninit(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,16 +240,32 @@ pub enum ConfigV6 {
|
||||
Static(StaticConfigV6),
|
||||
}
|
||||
|
||||
/// A network stack.
|
||||
/// Network stack runner.
|
||||
///
|
||||
/// This is the main entry point for the network stack.
|
||||
pub struct Stack<D: Driver> {
|
||||
pub(crate) socket: RefCell<SocketStack>,
|
||||
inner: RefCell<Inner<D>>,
|
||||
/// You must call [`Runner::run()`] in a background task for the network stack to work.
|
||||
pub struct Runner<'d, D: Driver> {
|
||||
driver: D,
|
||||
stack: Stack<'d>,
|
||||
}
|
||||
|
||||
struct Inner<D: Driver> {
|
||||
device: D,
|
||||
/// Network stack handle
|
||||
///
|
||||
/// Use this to create sockets. It's `Copy`, so you can pass
|
||||
/// it by value instead of by reference.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Stack<'d> {
|
||||
inner: &'d RefCell<Inner>,
|
||||
}
|
||||
|
||||
pub(crate) struct Inner {
|
||||
pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased.
|
||||
pub(crate) iface: Interface,
|
||||
/// Waker used for triggering polls.
|
||||
pub(crate) waker: WakerRegistration,
|
||||
/// Waker used for waiting for link up or config up.
|
||||
state_waker: WakerRegistration,
|
||||
hardware_address: HardwareAddress,
|
||||
next_local_port: u16,
|
||||
link_up: bool,
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static_v4: Option<StaticConfigV4>,
|
||||
@ -254,20 +273,88 @@ struct Inner<D: Driver> {
|
||||
static_v6: Option<StaticConfigV6>,
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
dhcp_socket: Option<SocketHandle>,
|
||||
config_waker: WakerRegistration,
|
||||
#[cfg(feature = "dns")]
|
||||
dns_socket: SocketHandle,
|
||||
#[cfg(feature = "dns")]
|
||||
dns_waker: WakerRegistration,
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
hostname: &'static mut core::cell::UnsafeCell<HostnameResources>,
|
||||
hostname: *mut HostnameResources,
|
||||
}
|
||||
|
||||
pub(crate) struct SocketStack {
|
||||
pub(crate) sockets: SocketSet<'static>,
|
||||
pub(crate) iface: Interface,
|
||||
pub(crate) waker: WakerRegistration,
|
||||
next_local_port: u16,
|
||||
fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> {
|
||||
x
|
||||
}
|
||||
|
||||
/// Create a new network stack.
|
||||
pub fn new<'d, D: Driver, const SOCK: usize>(
|
||||
mut driver: D,
|
||||
config: Config,
|
||||
resources: &'d mut StackResources<SOCK>,
|
||||
random_seed: u64,
|
||||
) -> (Stack<'d>, Runner<'d, D>) {
|
||||
let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address());
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(hardware_address);
|
||||
iface_cfg.random_seed = random_seed;
|
||||
|
||||
let iface = Interface::new(
|
||||
iface_cfg,
|
||||
&mut DriverAdapter {
|
||||
inner: &mut driver,
|
||||
cx: None,
|
||||
medium,
|
||||
},
|
||||
instant_to_smoltcp(Instant::now()),
|
||||
);
|
||||
|
||||
unsafe fn transmute_slice<T>(x: &mut [T]) -> &'static mut [T] {
|
||||
core::mem::transmute(x)
|
||||
}
|
||||
|
||||
let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]);
|
||||
#[allow(unused_mut)]
|
||||
let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) });
|
||||
|
||||
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
|
||||
|
||||
#[cfg(feature = "dns")]
|
||||
let dns_socket = sockets.add(dns::Socket::new(
|
||||
&[],
|
||||
managed::ManagedSlice::Borrowed(unsafe {
|
||||
transmute_slice(resources.queries.write([const { None }; MAX_QUERIES]))
|
||||
}),
|
||||
));
|
||||
|
||||
let mut inner = Inner {
|
||||
sockets,
|
||||
iface,
|
||||
waker: WakerRegistration::new(),
|
||||
state_waker: WakerRegistration::new(),
|
||||
next_local_port,
|
||||
hardware_address,
|
||||
link_up: false,
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static_v4: None,
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
static_v6: None,
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
dhcp_socket: None,
|
||||
#[cfg(feature = "dns")]
|
||||
dns_socket,
|
||||
#[cfg(feature = "dns")]
|
||||
dns_waker: WakerRegistration::new(),
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
hostname: &mut resources.hostname,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
inner.set_config_v4(config.ipv4);
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
inner.set_config_v6(config.ipv6);
|
||||
inner.apply_static_config();
|
||||
|
||||
let inner = &*resources.inner.write(RefCell::new(inner));
|
||||
let stack = Stack { inner };
|
||||
(stack, Runner { driver, stack })
|
||||
}
|
||||
|
||||
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
|
||||
@ -290,89 +377,23 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddres
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Driver> Stack<D> {
|
||||
/// Create a new network stack.
|
||||
pub fn new<const SOCK: usize>(
|
||||
mut device: D,
|
||||
config: Config,
|
||||
resources: &'static mut StackResources<SOCK>,
|
||||
random_seed: u64,
|
||||
) -> Self {
|
||||
let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address());
|
||||
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
|
||||
iface_cfg.random_seed = random_seed;
|
||||
|
||||
let iface = Interface::new(
|
||||
iface_cfg,
|
||||
&mut DriverAdapter {
|
||||
inner: &mut device,
|
||||
cx: None,
|
||||
medium,
|
||||
},
|
||||
instant_to_smoltcp(Instant::now()),
|
||||
);
|
||||
|
||||
let sockets = SocketSet::new(&mut resources.sockets[..]);
|
||||
|
||||
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,
|
||||
waker: WakerRegistration::new(),
|
||||
next_local_port,
|
||||
};
|
||||
|
||||
let mut inner = Inner {
|
||||
device,
|
||||
link_up: false,
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
static_v4: None,
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
static_v6: None,
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
dhcp_socket: None,
|
||||
config_waker: WakerRegistration::new(),
|
||||
#[cfg(feature = "dns")]
|
||||
dns_socket: socket.sockets.add(dns::Socket::new(
|
||||
&[],
|
||||
managed::ManagedSlice::Borrowed(&mut resources.queries),
|
||||
)),
|
||||
#[cfg(feature = "dns")]
|
||||
dns_waker: WakerRegistration::new(),
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
hostname: &mut resources.hostname,
|
||||
};
|
||||
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
inner.set_config_v4(&mut socket, config.ipv4);
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
inner.set_config_v6(&mut socket, config.ipv6);
|
||||
inner.apply_static_config(&mut socket);
|
||||
|
||||
Self {
|
||||
socket: RefCell::new(socket),
|
||||
inner: RefCell::new(inner),
|
||||
}
|
||||
impl<'d> Stack<'d> {
|
||||
fn with<R>(&self, f: impl FnOnce(&Inner) -> R) -> R {
|
||||
f(&*self.inner.borrow())
|
||||
}
|
||||
|
||||
fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
|
||||
f(&*self.socket.borrow(), &*self.inner.borrow())
|
||||
}
|
||||
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
|
||||
f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut Inner) -> R) -> R {
|
||||
f(&mut *self.inner.borrow_mut())
|
||||
}
|
||||
|
||||
/// 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()).0)
|
||||
self.with(|i| i.hardware_address)
|
||||
}
|
||||
|
||||
/// Get whether the link is up.
|
||||
pub fn is_link_up(&self) -> bool {
|
||||
self.with(|_s, i| i.link_up)
|
||||
self.with(|i| i.link_up)
|
||||
}
|
||||
|
||||
/// Get whether the network stack has a valid IP configuration.
|
||||
@ -402,10 +423,20 @@ impl<D: Driver> Stack<D> {
|
||||
v4_up || v6_up
|
||||
}
|
||||
|
||||
/// Wait for the network device to obtain a link signal.
|
||||
pub async fn wait_link_up(&self) {
|
||||
self.wait(|| self.is_link_up()).await
|
||||
}
|
||||
|
||||
/// Wait for the network device to lose link signal.
|
||||
pub async fn wait_link_down(&self) {
|
||||
self.wait(|| !self.is_link_up()).await
|
||||
}
|
||||
|
||||
/// Wait for the network stack to obtain a valid IP configuration.
|
||||
///
|
||||
/// ## Notes:
|
||||
/// - Ensure [`Stack::run`] has been called before using this function.
|
||||
/// - Ensure [`Runner::run`] has been started before using this function.
|
||||
///
|
||||
/// - This function may never return (e.g. if no configuration is obtained through DHCP).
|
||||
/// The caller is supposed to handle a timeout for this case.
|
||||
@ -413,44 +444,49 @@ impl<D: Driver> Stack<D> {
|
||||
/// ## Example
|
||||
/// ```ignore
|
||||
/// let config = embassy_net::Config::dhcpv4(Default::default());
|
||||
///// Init network stack
|
||||
/// static RESOURCES: StaticCell<embassy_net::StackResources<2>> = StaticCell::new();
|
||||
/// static STACK: StaticCell<embassy_net::Stack> = StaticCell::new();
|
||||
/// let stack = &*STACK.init(embassy_net::Stack::new(
|
||||
/// device,
|
||||
/// // Init network stack
|
||||
/// // NOTE: DHCP and DNS need one socket slot if enabled. This is why we're
|
||||
/// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP).
|
||||
/// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it.
|
||||
/// static RESOURCES: StaticCell<embassy_net::StackResources<3>> = StaticCell::new();
|
||||
/// let (stack, runner) = embassy_net::new(
|
||||
/// driver,
|
||||
/// config,
|
||||
/// RESOURCES.init(embassy_net::StackResources::new()),
|
||||
/// seed
|
||||
/// ));
|
||||
/// // Launch network task that runs `stack.run().await`
|
||||
/// spawner.spawn(net_task(stack)).unwrap();
|
||||
/// );
|
||||
/// // Launch network task that runs `runner.run().await`
|
||||
/// spawner.spawn(net_task(runner)).unwrap();
|
||||
/// // Wait for DHCP config
|
||||
/// stack.wait_config_up().await;
|
||||
/// // use the network stack
|
||||
/// // ...
|
||||
/// ```
|
||||
pub async fn wait_config_up(&self) {
|
||||
// If the config is up already, we can return immediately.
|
||||
if self.is_config_up() {
|
||||
return;
|
||||
}
|
||||
self.wait(|| self.is_config_up()).await
|
||||
}
|
||||
|
||||
poll_fn(|cx| {
|
||||
if self.is_config_up() {
|
||||
/// Wait for the network stack to lose a valid IP configuration.
|
||||
pub async fn wait_config_down(&self) {
|
||||
self.wait(|| !self.is_config_up()).await
|
||||
}
|
||||
|
||||
fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future<Output = ()> + 'a {
|
||||
poll_fn(move |cx| {
|
||||
if predicate() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
// If the config is not up, we register a waker that is woken up
|
||||
// when a config is applied (static or DHCP).
|
||||
trace!("Waiting for config up");
|
||||
|
||||
self.with_mut(|_, i| {
|
||||
i.config_waker.register(cx.waker());
|
||||
self.with_mut(|i| {
|
||||
i.state_waker.register(cx.waker());
|
||||
});
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Get the current IPv4 configuration.
|
||||
@ -459,45 +495,33 @@ impl<D: Driver> Stack<D> {
|
||||
/// acquire an IP address, or Some if it has.
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub fn config_v4(&self) -> Option<StaticConfigV4> {
|
||||
self.with(|_, i| i.static_v4.clone())
|
||||
self.with(|i| i.static_v4.clone())
|
||||
}
|
||||
|
||||
/// Get the current IPv6 configuration.
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub fn config_v6(&self) -> Option<StaticConfigV6> {
|
||||
self.with(|_, i| i.static_v6.clone())
|
||||
self.with(|i| i.static_v6.clone())
|
||||
}
|
||||
|
||||
/// Set the IPv4 configuration.
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub fn set_config_v4(&self, config: ConfigV4) {
|
||||
self.with_mut(|s, i| {
|
||||
i.set_config_v4(s, config);
|
||||
i.apply_static_config(s);
|
||||
self.with_mut(|i| {
|
||||
i.set_config_v4(config);
|
||||
i.apply_static_config();
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the IPv6 configuration.
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub fn set_config_v6(&self, config: ConfigV6) {
|
||||
self.with_mut(|s, i| {
|
||||
i.set_config_v6(s, config);
|
||||
i.apply_static_config(s);
|
||||
self.with_mut(|i| {
|
||||
i.set_config_v6(config);
|
||||
i.apply_static_config();
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the network stack.
|
||||
///
|
||||
/// You must call this in a background task, to process network events.
|
||||
pub async fn run(&self) -> ! {
|
||||
poll_fn(|cx| {
|
||||
self.with_mut(|s, i| i.poll(cx, s));
|
||||
Poll::<()>::Pending
|
||||
})
|
||||
.await;
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Make a query for a given name and return the corresponding IP addresses.
|
||||
#[cfg(feature = "dns")]
|
||||
pub async fn dns_query(
|
||||
@ -523,11 +547,11 @@ impl<D: Driver> Stack<D> {
|
||||
}
|
||||
|
||||
let query = poll_fn(|cx| {
|
||||
self.with_mut(|s, i| {
|
||||
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
match socket.start_query(s.iface.context(), name, qtype) {
|
||||
self.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
match socket.start_query(i.iface.context(), name, qtype) {
|
||||
Ok(handle) => {
|
||||
s.waker.wake();
|
||||
i.waker.wake();
|
||||
Poll::Ready(Ok(handle))
|
||||
}
|
||||
Err(dns::StartQueryError::NoFreeSlot) => {
|
||||
@ -564,17 +588,17 @@ impl<D: Driver> Stack<D> {
|
||||
}
|
||||
|
||||
let drop = OnDrop::new(|| {
|
||||
self.with_mut(|s, i| {
|
||||
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
self.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
socket.cancel_query(query);
|
||||
s.waker.wake();
|
||||
i.waker.wake();
|
||||
i.dns_waker.wake();
|
||||
})
|
||||
});
|
||||
|
||||
let res = poll_fn(|cx| {
|
||||
self.with_mut(|s, i| {
|
||||
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
self.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
|
||||
match socket.get_query_result(query) {
|
||||
Ok(addrs) => {
|
||||
i.dns_waker.wake();
|
||||
@ -599,104 +623,34 @@ impl<D: Driver> Stack<D> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "igmp")]
|
||||
impl<D: Driver> Stack<D> {
|
||||
#[cfg(feature = "multicast")]
|
||||
impl<'d> Stack<'d> {
|
||||
/// Join a multicast group.
|
||||
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| {
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
medium,
|
||||
};
|
||||
|
||||
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)),
|
||||
}
|
||||
})
|
||||
pub fn join_multicast_group(&self, addr: impl Into<IpAddress>) -> Result<(), MulticastError> {
|
||||
self.with_mut(|i| i.iface.join_multicast_group(addr))
|
||||
}
|
||||
|
||||
/// Leave a multicast group.
|
||||
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| {
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut i.device,
|
||||
medium,
|
||||
};
|
||||
|
||||
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)),
|
||||
}
|
||||
})
|
||||
pub fn leave_multicast_group(&self, addr: impl Into<IpAddress>) -> Result<(), MulticastError> {
|
||||
self.with_mut(|i| i.iface.leave_multicast_group(addr))
|
||||
}
|
||||
|
||||
/// Get whether the network stack has joined the given multicast group.
|
||||
pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
|
||||
self.socket.borrow().iface.has_multicast_group(addr)
|
||||
pub fn has_multicast_group(&self, addr: impl Into<IpAddress>) -> bool {
|
||||
self.with(|i| i.iface.has_multicast_group(addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl SocketStack {
|
||||
impl Inner {
|
||||
#[allow(clippy::absurd_extreme_comparisons, dead_code)]
|
||||
pub fn get_local_port(&mut self) -> u16 {
|
||||
let res = self.next_local_port;
|
||||
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Driver> Inner<D> {
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) {
|
||||
pub fn set_config_v4(&mut self, config: ConfigV4) {
|
||||
// Handle static config.
|
||||
self.static_v4 = match config.clone() {
|
||||
ConfigV4::None => None,
|
||||
@ -712,12 +666,12 @@ impl<D: Driver> Inner<D> {
|
||||
// Create the socket if it doesn't exist.
|
||||
if self.dhcp_socket.is_none() {
|
||||
let socket = smoltcp::socket::dhcpv4::Socket::new();
|
||||
let handle = _s.sockets.add(socket);
|
||||
let handle = self.sockets.add(socket);
|
||||
self.dhcp_socket = Some(handle);
|
||||
}
|
||||
|
||||
// Configure it
|
||||
let socket = _s.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
|
||||
let socket = self.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
|
||||
socket.set_ignore_naks(c.ignore_naks);
|
||||
socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp));
|
||||
socket.set_ports(c.server_port, c.client_port);
|
||||
@ -726,19 +680,20 @@ impl<D: Driver> Inner<D> {
|
||||
socket.set_outgoing_options(&[]);
|
||||
#[cfg(feature = "dhcpv4-hostname")]
|
||||
if let Some(h) = c.hostname {
|
||||
// safety: we just did set_outgoing_options([]) so we know the socket is no longer holding a reference.
|
||||
let hostname = unsafe { &mut *self.hostname.get() };
|
||||
// safety:
|
||||
// - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference.
|
||||
// - we know this pointer lives for as long as the stack exists, because `new()` borrows
|
||||
// the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp.
|
||||
let hostname = unsafe { &mut *self.hostname };
|
||||
|
||||
// create data
|
||||
// safety: we know the buffer lives forever, new borrows the StackResources for 'static.
|
||||
// also we won't modify it until next call to this function.
|
||||
hostname.data[..h.len()].copy_from_slice(h.as_bytes());
|
||||
let data: &[u8] = &hostname.data[..h.len()];
|
||||
let data: &'static [u8] = unsafe { core::mem::transmute(data) };
|
||||
let data = hostname.data.write([0; MAX_HOSTNAME_LEN]);
|
||||
data[..h.len()].copy_from_slice(h.as_bytes());
|
||||
let data: &[u8] = &data[..h.len()];
|
||||
|
||||
// set the option.
|
||||
hostname.option = smoltcp::wire::DhcpOption { data, kind: 12 };
|
||||
socket.set_outgoing_options(core::slice::from_ref(&hostname.option));
|
||||
let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 });
|
||||
socket.set_outgoing_options(core::slice::from_ref(option));
|
||||
}
|
||||
|
||||
socket.reset();
|
||||
@ -746,7 +701,7 @@ impl<D: Driver> Inner<D> {
|
||||
_ => {
|
||||
// Remove DHCP socket if any.
|
||||
if let Some(socket) = self.dhcp_socket {
|
||||
_s.sockets.remove(socket);
|
||||
self.sockets.remove(socket);
|
||||
self.dhcp_socket = None;
|
||||
}
|
||||
}
|
||||
@ -754,14 +709,14 @@ impl<D: Driver> Inner<D> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) {
|
||||
pub fn set_config_v6(&mut self, config: ConfigV6) {
|
||||
self.static_v6 = match config {
|
||||
ConfigV6::None => None,
|
||||
ConfigV6::Static(c) => Some(c),
|
||||
};
|
||||
}
|
||||
|
||||
fn apply_static_config(&mut self, s: &mut SocketStack) {
|
||||
fn apply_static_config(&mut self) {
|
||||
let mut addrs = Vec::new();
|
||||
#[cfg(feature = "dns")]
|
||||
let mut dns_servers: Vec<_, 6> = Vec::new();
|
||||
@ -805,35 +760,43 @@ impl<D: Driver> Inner<D> {
|
||||
}
|
||||
|
||||
// Apply addresses
|
||||
s.iface.update_ip_addrs(|a| *a = addrs);
|
||||
self.iface.update_ip_addrs(|a| *a = addrs);
|
||||
|
||||
// Apply gateways
|
||||
#[cfg(feature = "proto-ipv4")]
|
||||
if let Some(gateway) = gateway_v4 {
|
||||
unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway));
|
||||
unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway));
|
||||
} else {
|
||||
s.iface.routes_mut().remove_default_ipv4_route();
|
||||
self.iface.routes_mut().remove_default_ipv4_route();
|
||||
}
|
||||
#[cfg(feature = "proto-ipv6")]
|
||||
if let Some(gateway) = gateway_v6 {
|
||||
unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway));
|
||||
unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway));
|
||||
} else {
|
||||
s.iface.routes_mut().remove_default_ipv6_route();
|
||||
self.iface.routes_mut().remove_default_ipv6_route();
|
||||
}
|
||||
|
||||
// Apply DNS servers
|
||||
#[cfg(feature = "dns")]
|
||||
s.sockets
|
||||
.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket)
|
||||
.update_servers(&dns_servers[..]);
|
||||
if !dns_servers.is_empty() {
|
||||
let count = if dns_servers.len() > DNS_MAX_SERVER_COUNT {
|
||||
warn!("Number of DNS servers exceeds DNS_MAX_SERVER_COUNT, truncating list.");
|
||||
DNS_MAX_SERVER_COUNT
|
||||
} else {
|
||||
dns_servers.len()
|
||||
};
|
||||
self.sockets
|
||||
.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket)
|
||||
.update_servers(&dns_servers[..count]);
|
||||
}
|
||||
|
||||
self.config_waker.wake();
|
||||
self.state_waker.wake();
|
||||
}
|
||||
|
||||
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
|
||||
s.waker.register(cx.waker());
|
||||
fn poll<D: Driver>(&mut self, cx: &mut Context<'_>, driver: &mut D) {
|
||||
self.waker.register(cx.waker());
|
||||
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address());
|
||||
let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address());
|
||||
|
||||
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
|
||||
{
|
||||
@ -846,25 +809,26 @@ impl<D: Driver> Inner<D> {
|
||||
_ => false,
|
||||
};
|
||||
if do_set {
|
||||
s.iface.set_hardware_addr(_hardware_addr);
|
||||
self.iface.set_hardware_addr(_hardware_addr);
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp = instant_to_smoltcp(Instant::now());
|
||||
let mut smoldev = DriverAdapter {
|
||||
cx: Some(cx),
|
||||
inner: &mut self.device,
|
||||
inner: driver,
|
||||
medium,
|
||||
};
|
||||
s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
|
||||
self.iface.poll(timestamp, &mut smoldev, &mut self.sockets);
|
||||
|
||||
// Update link up
|
||||
let old_link_up = self.link_up;
|
||||
self.link_up = self.device.link_state(cx) == LinkState::Up;
|
||||
self.link_up = driver.link_state(cx) == LinkState::Up;
|
||||
|
||||
// Print when changed
|
||||
if old_link_up != self.link_up {
|
||||
info!("link_up = {:?}", self.link_up);
|
||||
self.state_waker.wake();
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
@ -872,7 +836,7 @@ impl<D: Driver> Inner<D> {
|
||||
|
||||
#[cfg(feature = "dhcpv4")]
|
||||
if let Some(dhcp_handle) = self.dhcp_socket {
|
||||
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
|
||||
let socket = self.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
|
||||
|
||||
if self.link_up {
|
||||
if old_link_up != self.link_up {
|
||||
@ -901,10 +865,10 @@ impl<D: Driver> Inner<D> {
|
||||
}
|
||||
|
||||
if apply_config {
|
||||
self.apply_static_config(s);
|
||||
self.apply_static_config();
|
||||
}
|
||||
|
||||
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
|
||||
if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) {
|
||||
let t = pin!(Timer::at(instant_from_smoltcp(poll_at)));
|
||||
if t.poll(cx).is_ready() {
|
||||
cx.waker().wake_by_ref();
|
||||
@ -912,3 +876,17 @@ impl<D: Driver> Inner<D> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, D: Driver> Runner<'d, D> {
|
||||
/// Run the network stack.
|
||||
///
|
||||
/// You must call this in a background task, to process network events.
|
||||
pub async fn run(&mut self) -> ! {
|
||||
poll_fn(|cx| {
|
||||
self.stack.with_mut(|i| i.poll(cx, &mut self.driver));
|
||||
Poll::<()>::Pending
|
||||
})
|
||||
.await;
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Raw sockets.
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::future::poll_fn;
|
||||
use core::mem;
|
||||
use core::task::{Context, Poll};
|
||||
@ -11,7 +10,7 @@ use smoltcp::socket::raw;
|
||||
pub use smoltcp::socket::raw::PacketMetadata;
|
||||
use smoltcp::wire::{IpProtocol, IpVersion};
|
||||
|
||||
use crate::{SocketStack, Stack};
|
||||
use crate::Stack;
|
||||
|
||||
/// Error returned by [`RawSocket::recv`] and [`RawSocket::send`].
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
@ -23,14 +22,14 @@ pub enum RecvError {
|
||||
|
||||
/// An Raw socket.
|
||||
pub struct RawSocket<'a> {
|
||||
stack: &'a RefCell<SocketStack>,
|
||||
stack: Stack<'a>,
|
||||
handle: SocketHandle,
|
||||
}
|
||||
|
||||
impl<'a> RawSocket<'a> {
|
||||
/// Create a new Raw socket using the provided stack and buffers.
|
||||
pub fn new<D: Driver>(
|
||||
stack: &'a Stack<D>,
|
||||
stack: Stack<'a>,
|
||||
ip_version: IpVersion,
|
||||
ip_protocol: IpProtocol,
|
||||
rx_meta: &'a mut [PacketMetadata],
|
||||
@ -38,31 +37,29 @@ impl<'a> RawSocket<'a> {
|
||||
tx_meta: &'a mut [PacketMetadata],
|
||||
tx_buffer: &'a mut [u8],
|
||||
) -> Self {
|
||||
let s = &mut *stack.socket.borrow_mut();
|
||||
let handle = stack.with_mut(|i| {
|
||||
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
|
||||
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
|
||||
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||
i.sockets.add(raw::Socket::new(
|
||||
ip_version,
|
||||
ip_protocol,
|
||||
raw::PacketBuffer::new(rx_meta, rx_buffer),
|
||||
raw::PacketBuffer::new(tx_meta, tx_buffer),
|
||||
))
|
||||
});
|
||||
|
||||
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
|
||||
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
|
||||
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||
let handle = s.sockets.add(raw::Socket::new(
|
||||
ip_version,
|
||||
ip_protocol,
|
||||
raw::PacketBuffer::new(rx_meta, rx_buffer),
|
||||
raw::PacketBuffer::new(tx_meta, tx_buffer),
|
||||
));
|
||||
|
||||
Self {
|
||||
stack: &stack.socket,
|
||||
handle,
|
||||
}
|
||||
Self { stack, handle }
|
||||
}
|
||||
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R {
|
||||
let s = &mut *self.stack.borrow_mut();
|
||||
let socket = s.sockets.get_mut::<raw::Socket>(self.handle);
|
||||
let res = f(socket, &mut s.iface);
|
||||
s.waker.wake();
|
||||
res
|
||||
self.stack.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<raw::Socket>(self.handle);
|
||||
let res = f(socket, &mut i.iface);
|
||||
i.waker.wake();
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Receive a datagram.
|
||||
@ -115,6 +112,10 @@ impl<'a> RawSocket<'a> {
|
||||
|
||||
impl Drop for RawSocket<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.stack.borrow_mut().sockets.remove(self.handle);
|
||||
self.stack.with_mut(|i| i.sockets.remove(self.handle));
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> {
|
||||
x
|
||||
}
|
||||
|
@ -8,12 +8,10 @@
|
||||
//! Incoming connections when no socket is listening are rejected. To accept many incoming
|
||||
//! connections, create many sockets and put them all into listening mode.
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::future::poll_fn;
|
||||
use core::mem;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_net_driver::Driver;
|
||||
use embassy_time::Duration;
|
||||
use smoltcp::iface::{Interface, SocketHandle};
|
||||
use smoltcp::socket::tcp;
|
||||
@ -21,7 +19,7 @@ pub use smoltcp::socket::tcp::State;
|
||||
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
|
||||
|
||||
use crate::time::duration_to_smoltcp;
|
||||
use crate::{SocketStack, Stack};
|
||||
use crate::Stack;
|
||||
|
||||
/// Error returned by TcpSocket read/write functions.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
@ -79,6 +77,15 @@ impl<'a> TcpReader<'a> {
|
||||
///
|
||||
/// Returns how many bytes were read, or an error. If no data is available, it waits
|
||||
/// until there is at least one byte available.
|
||||
///
|
||||
/// # Note
|
||||
/// A return value of Ok(0) means that we have read all data and the remote
|
||||
/// side has closed our receive half of the socket. The remote can no longer
|
||||
/// send bytes.
|
||||
///
|
||||
/// The send half of the socket is still open. If you want to reconnect using
|
||||
/// the socket you split this reader off the send half needs to be closed using
|
||||
/// [`abort()`](TcpSocket::abort).
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
@ -148,20 +155,18 @@ impl<'a> TcpWriter<'a> {
|
||||
|
||||
impl<'a> TcpSocket<'a> {
|
||||
/// Create a new TCP socket on the given stack, with the given buffers.
|
||||
pub fn new<D: Driver>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
|
||||
let s = &mut *stack.socket.borrow_mut();
|
||||
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||
let handle = s.sockets.add(tcp::Socket::new(
|
||||
tcp::SocketBuffer::new(rx_buffer),
|
||||
tcp::SocketBuffer::new(tx_buffer),
|
||||
));
|
||||
pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
|
||||
let handle = stack.with_mut(|i| {
|
||||
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||
i.sockets.add(tcp::Socket::new(
|
||||
tcp::SocketBuffer::new(rx_buffer),
|
||||
tcp::SocketBuffer::new(tx_buffer),
|
||||
))
|
||||
});
|
||||
|
||||
Self {
|
||||
io: TcpIo {
|
||||
stack: &stack.socket,
|
||||
handle,
|
||||
},
|
||||
io: TcpIo { stack: stack, handle },
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +224,7 @@ impl<'a> TcpSocket<'a> {
|
||||
where
|
||||
T: Into<IpEndpoint>,
|
||||
{
|
||||
let local_port = self.io.stack.borrow_mut().get_local_port();
|
||||
let local_port = self.io.stack.with_mut(|i| i.get_local_port());
|
||||
|
||||
match {
|
||||
self.io
|
||||
@ -273,6 +278,9 @@ impl<'a> TcpSocket<'a> {
|
||||
///
|
||||
/// Returns how many bytes were read, or an error. If no data is available, it waits
|
||||
/// until there is at least one byte available.
|
||||
///
|
||||
/// A return value of Ok(0) means that the socket was closed and is longer
|
||||
/// able to receive any data.
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.io.read(buf).await
|
||||
}
|
||||
@ -297,6 +305,10 @@ impl<'a> TcpSocket<'a> {
|
||||
///
|
||||
/// If the timeout is set, the socket will be closed if no data is received for the
|
||||
/// specified duration.
|
||||
///
|
||||
/// # Note:
|
||||
/// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when
|
||||
/// the remote could still respond.
|
||||
pub fn set_timeout(&mut self, duration: Option<Duration>) {
|
||||
self.io
|
||||
.with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp)))
|
||||
@ -308,6 +320,9 @@ impl<'a> TcpSocket<'a> {
|
||||
/// the specified duration of inactivity.
|
||||
///
|
||||
/// If not set, the socket will not send keep-alive packets.
|
||||
///
|
||||
/// By setting a [`timeout`](Self::timeout) larger then the keep alive you
|
||||
/// can detect a remote endpoint that no longer answers.
|
||||
pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
|
||||
self.io
|
||||
.with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp)))
|
||||
@ -382,31 +397,43 @@ impl<'a> TcpSocket<'a> {
|
||||
|
||||
impl<'a> Drop for TcpSocket<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.io.stack.borrow_mut().sockets.remove(self.io.handle);
|
||||
self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle));
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> {
|
||||
x
|
||||
}
|
||||
fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> {
|
||||
x
|
||||
}
|
||||
fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> {
|
||||
x
|
||||
}
|
||||
|
||||
// =======================
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct TcpIo<'a> {
|
||||
stack: &'a RefCell<SocketStack>,
|
||||
stack: Stack<'a>,
|
||||
handle: SocketHandle,
|
||||
}
|
||||
|
||||
impl<'d> TcpIo<'d> {
|
||||
fn with<R>(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R {
|
||||
let s = &*self.stack.borrow();
|
||||
let socket = s.sockets.get::<tcp::Socket>(self.handle);
|
||||
f(socket, &s.iface)
|
||||
self.stack.with(|i| {
|
||||
let socket = i.sockets.get::<tcp::Socket>(self.handle);
|
||||
f(socket, &i.iface)
|
||||
})
|
||||
}
|
||||
|
||||
fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
|
||||
let s = &mut *self.stack.borrow_mut();
|
||||
let socket = s.sockets.get_mut::<tcp::Socket>(self.handle);
|
||||
let res = f(socket, &mut s.iface);
|
||||
s.waker.wake();
|
||||
res
|
||||
self.stack.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<tcp::Socket>(self.handle);
|
||||
let res = f(socket, &mut i.iface);
|
||||
i.waker.wake();
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
@ -515,7 +542,7 @@ impl<'d> TcpIo<'d> {
|
||||
async fn flush(&mut self) -> Result<(), Error> {
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| {
|
||||
let data_pending = s.send_queue() > 0;
|
||||
let data_pending = (s.send_queue() > 0) && s.state() != tcp::State::Closed;
|
||||
let fin_pending = matches!(
|
||||
s.state(),
|
||||
tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck
|
||||
@ -657,15 +684,15 @@ pub mod client {
|
||||
/// TCP client connection pool compatible with `embedded-nal-async` traits.
|
||||
///
|
||||
/// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ.
|
||||
pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
|
||||
stack: &'d Stack<D>,
|
||||
pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
|
||||
stack: Stack<'d>,
|
||||
state: &'d TcpClientState<N, TX_SZ, RX_SZ>,
|
||||
socket_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> {
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> {
|
||||
/// Create a new `TcpClient`.
|
||||
pub fn new(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self {
|
||||
pub fn new(stack: Stack<'d>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self {
|
||||
Self {
|
||||
stack,
|
||||
state,
|
||||
@ -682,8 +709,8 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
|
||||
for TcpClient<'d, D, N, TX_SZ, RX_SZ>
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
|
||||
for TcpClient<'d, N, TX_SZ, RX_SZ>
|
||||
{
|
||||
type Error = Error;
|
||||
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
|
||||
@ -703,7 +730,7 @@ pub mod client {
|
||||
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
|
||||
};
|
||||
let remote_endpoint = (addr, remote.port());
|
||||
let mut socket = TcpConnection::new(&self.stack, self.state)?;
|
||||
let mut socket = TcpConnection::new(self.stack, self.state)?;
|
||||
socket.socket.set_timeout(self.socket_timeout.clone());
|
||||
socket
|
||||
.socket
|
||||
@ -722,7 +749,7 @@ pub mod client {
|
||||
}
|
||||
|
||||
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> {
|
||||
fn new<D: Driver>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
|
||||
fn new(stack: Stack<'d>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
|
||||
let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?;
|
||||
Ok(Self {
|
||||
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) },
|
||||
|
@ -1,17 +1,15 @@
|
||||
//! UDP sockets.
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::future::poll_fn;
|
||||
use core::mem;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_net_driver::Driver;
|
||||
use smoltcp::iface::{Interface, SocketHandle};
|
||||
use smoltcp::socket::udp;
|
||||
pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata};
|
||||
use smoltcp::wire::IpListenEndpoint;
|
||||
|
||||
use crate::{SocketStack, Stack};
|
||||
use crate::Stack;
|
||||
|
||||
/// Error returned by [`UdpSocket::bind`].
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
@ -43,34 +41,31 @@ pub enum RecvError {
|
||||
|
||||
/// An UDP socket.
|
||||
pub struct UdpSocket<'a> {
|
||||
stack: &'a RefCell<SocketStack>,
|
||||
stack: Stack<'a>,
|
||||
handle: SocketHandle,
|
||||
}
|
||||
|
||||
impl<'a> UdpSocket<'a> {
|
||||
/// Create a new UDP socket using the provided stack and buffers.
|
||||
pub fn new<D: Driver>(
|
||||
stack: &'a Stack<D>,
|
||||
pub fn new(
|
||||
stack: Stack<'a>,
|
||||
rx_meta: &'a mut [PacketMetadata],
|
||||
rx_buffer: &'a mut [u8],
|
||||
tx_meta: &'a mut [PacketMetadata],
|
||||
tx_buffer: &'a mut [u8],
|
||||
) -> Self {
|
||||
let s = &mut *stack.socket.borrow_mut();
|
||||
let handle = stack.with_mut(|i| {
|
||||
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
|
||||
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
|
||||
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||
i.sockets.add(udp::Socket::new(
|
||||
udp::PacketBuffer::new(rx_meta, rx_buffer),
|
||||
udp::PacketBuffer::new(tx_meta, tx_buffer),
|
||||
))
|
||||
});
|
||||
|
||||
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
|
||||
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
|
||||
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
|
||||
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
|
||||
let handle = s.sockets.add(udp::Socket::new(
|
||||
udp::PacketBuffer::new(rx_meta, rx_buffer),
|
||||
udp::PacketBuffer::new(tx_meta, tx_buffer),
|
||||
));
|
||||
|
||||
Self {
|
||||
stack: &stack.socket,
|
||||
handle,
|
||||
}
|
||||
Self { stack, handle }
|
||||
}
|
||||
|
||||
/// Bind the socket to a local endpoint.
|
||||
@ -82,7 +77,7 @@ impl<'a> UdpSocket<'a> {
|
||||
|
||||
if endpoint.port == 0 {
|
||||
// If user didn't specify port allocate a dynamic port.
|
||||
endpoint.port = self.stack.borrow_mut().get_local_port();
|
||||
endpoint.port = self.stack.with_mut(|i| i.get_local_port());
|
||||
}
|
||||
|
||||
match self.with_mut(|s, _| s.bind(endpoint)) {
|
||||
@ -93,17 +88,19 @@ impl<'a> UdpSocket<'a> {
|
||||
}
|
||||
|
||||
fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R {
|
||||
let s = &*self.stack.borrow();
|
||||
let socket = s.sockets.get::<udp::Socket>(self.handle);
|
||||
f(socket, &s.iface)
|
||||
self.stack.with(|i| {
|
||||
let socket = i.sockets.get::<udp::Socket>(self.handle);
|
||||
f(socket, &i.iface)
|
||||
})
|
||||
}
|
||||
|
||||
fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R {
|
||||
let s = &mut *self.stack.borrow_mut();
|
||||
let socket = s.sockets.get_mut::<udp::Socket>(self.handle);
|
||||
let res = f(socket, &mut s.iface);
|
||||
s.waker.wake();
|
||||
res
|
||||
self.stack.with_mut(|i| {
|
||||
let socket = i.sockets.get_mut::<udp::Socket>(self.handle);
|
||||
let res = f(socket, &mut i.iface);
|
||||
i.waker.wake();
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Receive a datagram.
|
||||
@ -138,6 +135,35 @@ impl<'a> UdpSocket<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Receive a datagram with a zero-copy function.
|
||||
///
|
||||
/// When no datagram is available, this method will return `Poll::Pending` and
|
||||
/// register the current task to be notified when a datagram is received.
|
||||
///
|
||||
/// When a datagram is received, this method will call the provided function
|
||||
/// with the number of bytes received and the remote endpoint and return
|
||||
/// `Poll::Ready` with the function's returned value.
|
||||
pub async fn recv_from_with<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&[u8], UdpMetadata) -> R,
|
||||
{
|
||||
let mut f = Some(f);
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| {
|
||||
match s.recv() {
|
||||
Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)),
|
||||
Err(udp::RecvError::Truncated) => unreachable!(),
|
||||
Err(udp::RecvError::Exhausted) => {
|
||||
// socket buffer is empty wait until at least one byte has arrived
|
||||
s.register_recv_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send a datagram to the specified remote endpoint.
|
||||
///
|
||||
/// This method will wait until the datagram has been sent.
|
||||
@ -181,6 +207,40 @@ impl<'a> UdpSocket<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a datagram to the specified remote endpoint with a zero-copy function.
|
||||
///
|
||||
/// This method will wait until the buffer can fit the requested size before
|
||||
/// calling the function to fill its contents.
|
||||
///
|
||||
/// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)`
|
||||
pub async fn send_to_with<T, F, R>(&mut self, size: usize, remote_endpoint: T, f: F) -> Result<R, SendError>
|
||||
where
|
||||
T: Into<UdpMetadata> + Copy,
|
||||
F: FnOnce(&mut [u8]) -> R,
|
||||
{
|
||||
let mut f = Some(f);
|
||||
poll_fn(move |cx| {
|
||||
self.with_mut(|s, _| {
|
||||
match s.send(size, remote_endpoint) {
|
||||
Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))),
|
||||
Err(udp::SendError::BufferFull) => {
|
||||
s.register_send_waker(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
Err(udp::SendError::Unaddressable) => {
|
||||
// If no sender/outgoing port is specified, there is not really "no route"
|
||||
if s.endpoint().port == 0 {
|
||||
Poll::Ready(Err(SendError::SocketNotBound))
|
||||
} else {
|
||||
Poll::Ready(Err(SendError::NoRoute))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns the local endpoint of the socket.
|
||||
pub fn endpoint(&self) -> IpListenEndpoint {
|
||||
self.with(|s, _| s.endpoint())
|
||||
@ -235,6 +295,10 @@ impl<'a> UdpSocket<'a> {
|
||||
|
||||
impl Drop for UdpSocket<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.stack.borrow_mut().sockets.remove(self.handle);
|
||||
self.stack.with_mut(|i| i.sockets.remove(self.handle));
|
||||
}
|
||||
}
|
||||
|
||||
fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> {
|
||||
x
|
||||
}
|
||||
|
@ -7,24 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Drop `sealed` mod
|
||||
- nrf52840: Add dcdc voltage parameter to configure REG0 regulator
|
||||
- radio: Add support for IEEE 802.15.4 and BLE via radio peripheral
|
||||
- spim: Reduce trace-level messages ("Copying SPIM tx buffer..")
|
||||
- uart: Add support for rx- or tx-only BufferedUart
|
||||
- uart: Implement splitting Rx/Tx
|
||||
- spi: Allow specifying OutputDrive for SPI spins
|
||||
- pdm: Fix gain register value derivation
|
||||
- spim: Implement chunked DMA transfers
|
||||
- spi: Add bounds checks for EasyDMA buffer size
|
||||
- uarte: Add support for handling RX errors
|
||||
- nrf51: Implement support for nrf51 chip
|
||||
- pwm: Expose `duty` method
|
||||
- pwm: Fix infinite loop
|
||||
- spi: Add support for configuring bit order for bus
|
||||
- pwm: Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method
|
||||
- gpio: Drop GPIO Pin generics (API break)
|
||||
- pwm: Allow specifying OutputDrive for PWM channels
|
||||
## 0.2.0 - 2024-08-05
|
||||
|
||||
- Support for NRF chips:
|
||||
- nrf51
|
||||
- nrf9151
|
||||
- Support for new peripherals:
|
||||
- EGU
|
||||
- radio - low-level support for IEEE 802.15.4 and BLE via radio peripheral
|
||||
- Peripheral changes:
|
||||
- gpio: Drop GPIO Pin generics (API break)
|
||||
- pdm: Fix gain register value derivation
|
||||
- pwm:
|
||||
- Expose `duty` method
|
||||
- Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method
|
||||
- Allow specifying OutputDrive for PWM channels
|
||||
- Fix infinite loop
|
||||
- spim:
|
||||
- Reduce trace-level messages ("Copying SPIM tx buffer..")
|
||||
- Support configuring bit order for bus
|
||||
- Allow specifying OutputDrive for SPI pins
|
||||
- Add bounds checks for EasyDMA buffer size
|
||||
- Implement chunked DMA transfers
|
||||
- uart:
|
||||
- Add support for rx- or tx-only BufferedUart
|
||||
- Implement splitting Rx/Tx
|
||||
- Add support for handling RX errors
|
||||
- Miscellaneous changes:
|
||||
- Add `collapse_debuginfo` to fmt.rs macros.
|
||||
- Drop `sealed` mod
|
||||
- nrf52840: Add dcdc voltage parameter to configure REG0 regulator
|
||||
|
||||
## 0.1.0 - 2024-01-12
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-nrf"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Embassy Hardware Abstraction Layer (HAL) for nRF series microcontrollers"
|
||||
@ -103,12 +103,12 @@ nrf9160-ns = ["_nrf9160", "_ns", "_nrf91"]
|
||||
## The nRF9120 is the internal part number for the nRF9161 and nRF9151.
|
||||
## nRF9120 in Secure mode
|
||||
nrf9120-s = ["_nrf9120", "_s", "_nrf91"]
|
||||
nrf9151-s = ["_nrf9120", "_s", "_nrf91"]
|
||||
nrf9161-s = ["_nrf9120", "_s", "_nrf91"]
|
||||
nrf9151-s = ["nrf9120-s"]
|
||||
nrf9161-s = ["nrf9120-s"]
|
||||
## nRF9120 in Non-Secure mode
|
||||
nrf9120-ns = ["_nrf9120", "_ns", "_nrf91"]
|
||||
nrf9151-ns = ["_nrf9120", "_ns", "_nrf91"]
|
||||
nrf9161-ns = ["_nrf9120", "_ns", "_nrf91"]
|
||||
nrf9151-ns = ["nrf9120-ns"]
|
||||
nrf9161-ns = ["nrf9120-ns"]
|
||||
|
||||
# Features starting with `_` are for internal use only. They're not intended
|
||||
# to be enabled by other crates, and are not covered by semver guarantees.
|
||||
@ -137,10 +137,10 @@ _nrf52832_anomaly_109 = []
|
||||
|
||||
[dependencies]
|
||||
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time", optional = true }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
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-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
|
||||
embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" }
|
||||
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" }
|
||||
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
||||
|
@ -219,6 +219,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `rx_buffer.len()` is odd.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
uarte: impl Peripheral<P = U> + 'd,
|
||||
timer: impl Peripheral<P = T> + 'd,
|
||||
@ -254,6 +255,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `rx_buffer.len()` is odd.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_with_rtscts(
|
||||
uarte: impl Peripheral<P = U> + 'd,
|
||||
timer: impl Peripheral<P = T> + 'd,
|
||||
@ -286,6 +288,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_inner(
|
||||
peri: PeripheralRef<'d, U>,
|
||||
timer: PeripheralRef<'d, T>,
|
||||
@ -355,6 +358,11 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
|
||||
self.tx.write(buf).await
|
||||
}
|
||||
|
||||
/// Try writing a buffer without waiting, returning how many bytes were written.
|
||||
pub fn try_write(&mut self, buf: &[u8]) -> Result<usize, Error> {
|
||||
self.tx.try_write(buf)
|
||||
}
|
||||
|
||||
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
|
||||
pub async fn flush(&mut self) -> Result<(), Error> {
|
||||
self.tx.flush().await
|
||||
@ -479,6 +487,29 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Try writing a buffer without waiting, returning how many bytes were written.
|
||||
pub fn try_write(&mut self, buf: &[u8]) -> Result<usize, Error> {
|
||||
//trace!("poll_write: {:?}", buf.len());
|
||||
let s = U::buffered_state();
|
||||
let mut tx = unsafe { s.tx_buf.writer() };
|
||||
|
||||
let tx_buf = tx.push_slice();
|
||||
if tx_buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let n = min(tx_buf.len(), buf.len());
|
||||
tx_buf[..n].copy_from_slice(&buf[..n]);
|
||||
tx.push_done(n);
|
||||
|
||||
//trace!("poll_write: queued {:?}", n);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
U::Interrupt::pend();
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
|
||||
pub async fn flush(&mut self) -> Result<(), Error> {
|
||||
poll_fn(move |cx| {
|
||||
@ -534,6 +565,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `rx_buffer.len()` is odd.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
uarte: impl Peripheral<P = U> + 'd,
|
||||
timer: impl Peripheral<P = T> + 'd,
|
||||
@ -564,6 +596,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `rx_buffer.len()` is odd.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_with_rts(
|
||||
uarte: impl Peripheral<P = U> + 'd,
|
||||
timer: impl Peripheral<P = T> + 'd,
|
||||
@ -590,6 +623,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_inner(
|
||||
peri: PeripheralRef<'d, U>,
|
||||
timer: PeripheralRef<'d, T>,
|
||||
@ -614,6 +648,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> {
|
||||
this
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_innerer(
|
||||
peri: PeripheralRef<'d, U>,
|
||||
timer: PeripheralRef<'d, T>,
|
||||
|
@ -534,11 +534,13 @@ mod eh02 {
|
||||
type Error = Infallible;
|
||||
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_high())
|
||||
self.set_high();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_low())
|
||||
self.set_low();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,11 +582,13 @@ mod eh02 {
|
||||
type Error = Infallible;
|
||||
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_high())
|
||||
self.set_high();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_low())
|
||||
self.set_low();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,11 +632,13 @@ impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> {
|
||||
|
||||
impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> {
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_high())
|
||||
self.set_high();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_low())
|
||||
self.set_low();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,11 +671,13 @@ impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> {
|
||||
|
||||
impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> {
|
||||
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_high())
|
||||
self.set_high();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(self.set_low())
|
||||
self.set_low();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,8 @@ impl Default for Config {
|
||||
|
||||
/// Used to configure an individual SAADC peripheral channel.
|
||||
///
|
||||
/// See the `Default` impl for suitable default values.
|
||||
/// Construct using the `single_ended` or `differential` methods. These provide sensible defaults
|
||||
/// for the public fields, which can be overridden if required.
|
||||
#[non_exhaustive]
|
||||
pub struct ChannelConfig<'d> {
|
||||
/// Reference voltage of the SAADC input.
|
||||
|
@ -184,8 +184,9 @@ impl WatchdogHandle {
|
||||
|
||||
/// Steal a watchdog handle by index.
|
||||
///
|
||||
/// Safety: watchdog must be initialized, index must be between 0 and N-1 where
|
||||
/// N is the handle count when initializing.
|
||||
/// # Safety
|
||||
/// Watchdog must be initialized and `index` must be between `0` and `N-1`
|
||||
/// where `N` is the handle count when initializing.
|
||||
pub unsafe fn steal(index: u8) -> Self {
|
||||
Self { index }
|
||||
}
|
||||
|
32
embassy-rp/CHANGELOG.md
Normal file
32
embassy-rp/CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Changelog for embassy-rp
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.2.0 - 2024-08-05
|
||||
|
||||
- Add read_to_break_with_count
|
||||
- add option to provide your own boot2
|
||||
- Add multichannel ADC
|
||||
- Add collapse_debuginfo to fmt.rs macros.
|
||||
- Use raw slices .len() method instead of unsafe hacks.
|
||||
- Add missing word "pin" in rp pwm documentation
|
||||
- Add Clone and Copy to Error types
|
||||
- fix spinlocks staying locked after reset.
|
||||
- wait until read matches for PSM accesses.
|
||||
- Remove generics
|
||||
- fix drop implementation of BufferedUartRx and BufferedUartTx
|
||||
- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash`
|
||||
- rp usb: wake ep-wakers after stalling
|
||||
- rp usb: add stall implementation
|
||||
- Add parameter for enabling pull-up and pull-down in RP PWM input mode
|
||||
- rp: remove mod sealed.
|
||||
- rename pins data type and the macro
|
||||
- rename pwm channels to pwm slices, including in documentation
|
||||
- rename the Channel trait to Slice and the PwmPin to PwmChannel
|
||||
- i2c: Fix race condition that appears on fast repeated transfers.
|
||||
- Add a basic "read to break" function
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-rp"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller"
|
||||
@ -14,7 +14,9 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/emba
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/"
|
||||
features = ["defmt", "unstable-pac", "time-driver"]
|
||||
flavors = [
|
||||
{ name = "rp2040", target = "thumbv6m-none-eabi" },
|
||||
{ name = "rp2040", target = "thumbv6m-none-eabi", features = ["rp2040"] },
|
||||
{ name = "rp235xa", target = "thumbv8m.main-none-eabihf", features = ["rp235xa"] },
|
||||
{ name = "rp235xb", target = "thumbv8m.main-none-eabihf", features = ["rp235xb"] },
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
@ -22,13 +24,13 @@ features = ["defmt", "unstable-pac", "time-driver"]
|
||||
|
||||
[features]
|
||||
default = [ "rt" ]
|
||||
## Enable the rt feature of [`rp-pac`](https://docs.rs/crates/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization.
|
||||
## Enable the rt feature of [`rp-pac`](https://docs.rs/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization.
|
||||
rt = [ "rp-pac/rt" ]
|
||||
|
||||
## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers.
|
||||
defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"]
|
||||
|
||||
## Configure the critical section crate to use an implementation that is safe for multicore use on rp2040.
|
||||
## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040.
|
||||
critical-section-impl = ["critical-section/restore-state-u8"]
|
||||
|
||||
## Reexport the PAC for the currently enabled chip at `embassy_rp::pac`.
|
||||
@ -88,13 +90,30 @@ boot2-w25x10cl = []
|
||||
## ```
|
||||
boot2-none = []
|
||||
|
||||
## Configure the hal for use with the rp2040
|
||||
rp2040 = ["rp-pac/rp2040"]
|
||||
_rp235x = ["rp-pac/rp235x"]
|
||||
## Configure the hal for use with the rp235xA
|
||||
rp235xa = ["_rp235x"]
|
||||
## Configure the hal for use with the rp235xB
|
||||
rp235xb = ["_rp235x"]
|
||||
|
||||
# hack around cortex-m peripherals being wrong when running tests.
|
||||
_test = []
|
||||
|
||||
## Add a binary-info header block containing picotool-compatible metadata.
|
||||
##
|
||||
## Takes up a little flash space, but picotool can then report the name of your
|
||||
## program and other details.
|
||||
binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"]
|
||||
|
||||
[dependencies]
|
||||
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
|
||||
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
|
||||
embassy-time = { version = "0.3.1", path = "../embassy-time" }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
|
||||
embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
|
||||
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
|
||||
embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" }
|
||||
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" }
|
||||
atomic-polyfill = "1.0.1"
|
||||
defmt = { version = "0.3", optional = true }
|
||||
@ -112,7 +131,7 @@ embedded-storage-async = { version = "0.4.1" }
|
||||
rand_core = "0.6.4"
|
||||
fixed = "1.23.1"
|
||||
|
||||
rp-pac = { version = "6" }
|
||||
rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] }
|
||||
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
|
||||
@ -123,7 +142,9 @@ pio-proc = {version= "0.2" }
|
||||
pio = {version= "0.2.1" }
|
||||
rp2040-boot2 = "0.3"
|
||||
document-features = "0.2.7"
|
||||
sha2-const-stable = "0.1"
|
||||
rp-binary-info = { version = "0.1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
embassy-executor = { version = "0.5.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
|
||||
embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
|
||||
static_cell = { version = "2" }
|
||||
|
202
embassy-rp/LICENSE-APACHE
Normal file
202
embassy-rp/LICENSE-APACHE
Normal file
@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (c) Embassy project contributors
|
||||
Portions copyright (c) rp-rs organization
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
26
embassy-rp/LICENSE-MIT
Normal file
26
embassy-rp/LICENSE-MIT
Normal file
@ -0,0 +1,26 @@
|
||||
Copyright (c) Embassy project contributors
|
||||
Portions copyright (c) rp-rs organization
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
@ -23,5 +23,5 @@ The `embassy-rp` HAL implements the traits from [embedded-hal](https://crates.io
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time` feature. If you enable it,
|
||||
Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time-driver` feature. If you enable it,
|
||||
you must link an `embassy-time` driver in your project.
|
||||
|
@ -4,14 +4,16 @@ use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put the linker script somewhere the linker can find it
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let link_x = include_bytes!("link-rp.x.in");
|
||||
let mut f = File::create(out.join("link-rp.x")).unwrap();
|
||||
f.write_all(link_x).unwrap();
|
||||
if env::var("CARGO_FEATURE_RP2040").is_ok() {
|
||||
// Put the linker script somewhere the linker can find it
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let link_x = include_bytes!("link-rp.x.in");
|
||||
let mut f = File::create(out.join("link-rp.x")).unwrap();
|
||||
f.write_all(link_x).unwrap();
|
||||
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=link-rp.x.in");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=link-rp.x.in");
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,4 @@ SECTIONS {
|
||||
{
|
||||
KEEP(*(.boot2));
|
||||
} > BOOT2
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ use embassy_sync::waitqueue::AtomicWaker;
|
||||
use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin};
|
||||
use crate::interrupt::typelevel::Binding;
|
||||
use crate::interrupt::InterruptExt;
|
||||
use crate::pac::dma::vals::TreqSel;
|
||||
use crate::peripherals::{ADC, ADC_TEMP_SENSOR};
|
||||
use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt};
|
||||
|
||||
@ -34,6 +35,8 @@ impl<'p> Channel<'p> {
|
||||
pub fn new_pin(pin: impl Peripheral<P = impl AdcPin + 'p> + 'p, pull: Pull) -> Self {
|
||||
into_ref!(pin);
|
||||
pin.pad_ctrl().modify(|w| {
|
||||
#[cfg(feature = "_rp235x")]
|
||||
w.set_iso(false);
|
||||
// manual says:
|
||||
//
|
||||
// > When using an ADC input shared with a GPIO pin, the pin’s
|
||||
@ -229,7 +232,10 @@ impl<'d> Adc<'d, Async> {
|
||||
div: u16,
|
||||
dma: impl Peripheral<P = impl dma::Channel>,
|
||||
) -> Result<(), Error> {
|
||||
#[cfg(feature = "rp2040")]
|
||||
let mut rrobin = 0_u8;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
let mut rrobin = 0_u16;
|
||||
for c in channels {
|
||||
rrobin |= 1 << c;
|
||||
}
|
||||
@ -278,7 +284,7 @@ impl<'d> Adc<'d, Async> {
|
||||
}
|
||||
let auto_reset = ResetDmaConfig;
|
||||
|
||||
let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], 36) };
|
||||
let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], TreqSel::ADC) };
|
||||
// start conversions and wait for dma to finish. we can't report errors early
|
||||
// because there's no interrupt to signal them, and inspecting every element
|
||||
// of the fifo is too costly to do here.
|
||||
@ -423,10 +429,31 @@ macro_rules! impl_pin {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
|
||||
impl_pin!(PIN_26, 0);
|
||||
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
|
||||
impl_pin!(PIN_27, 1);
|
||||
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
|
||||
impl_pin!(PIN_28, 2);
|
||||
#[cfg(any(feature = "rp235xa", feature = "rp2040"))]
|
||||
impl_pin!(PIN_29, 3);
|
||||
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_40, 0);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_41, 1);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_42, 2);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_43, 3);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_44, 4);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_45, 5);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_46, 6);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_47, 7);
|
||||
|
||||
impl SealedAdcChannel for peripherals::ADC_TEMP_SENSOR {}
|
||||
impl AdcChannel for peripherals::ADC_TEMP_SENSOR {}
|
||||
|
1079
embassy-rp/src/block.rs
Normal file
1079
embassy-rp/src/block.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,17 @@
|
||||
//! Clock configuration for the RP2040
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
use core::arch::asm;
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{AtomicU16, AtomicU32, Ordering};
|
||||
#[cfg(feature = "rp2040")]
|
||||
use core::sync::atomic::AtomicU16;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
||||
use pac::clocks::vals::*;
|
||||
|
||||
use crate::gpio::{AnyPin, SealedPin};
|
||||
#[cfg(feature = "rp2040")]
|
||||
use crate::pac::common::{Reg, RW};
|
||||
use crate::{pac, reset, Peripheral};
|
||||
|
||||
@ -26,6 +31,7 @@ struct Clocks {
|
||||
// gpin1: AtomicU32,
|
||||
rosc: AtomicU32,
|
||||
peri: AtomicU32,
|
||||
#[cfg(feature = "rp2040")]
|
||||
rtc: AtomicU16,
|
||||
}
|
||||
|
||||
@ -41,6 +47,7 @@ static CLOCKS: Clocks = Clocks {
|
||||
// gpin1: AtomicU32::new(0),
|
||||
rosc: AtomicU32::new(0),
|
||||
peri: AtomicU32::new(0),
|
||||
#[cfg(feature = "rp2040")]
|
||||
rtc: AtomicU16::new(0),
|
||||
};
|
||||
|
||||
@ -81,6 +88,7 @@ pub struct ClockConfig {
|
||||
/// ADC clock configuration.
|
||||
pub adc_clk: Option<AdcClkConfig>,
|
||||
/// RTC clock configuration.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub rtc_clk: Option<RtcClkConfig>,
|
||||
// gpin0: Option<(u32, Gpin<'static, AnyPin>)>,
|
||||
// gpin1: Option<(u32, Gpin<'static, AnyPin>)>,
|
||||
@ -135,6 +143,7 @@ impl ClockConfig {
|
||||
phase: 0,
|
||||
}),
|
||||
// CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz
|
||||
#[cfg(feature = "rp2040")]
|
||||
rtc_clk: Some(RtcClkConfig {
|
||||
src: RtcClkSrc::PllUsb,
|
||||
div_int: 1024,
|
||||
@ -174,6 +183,7 @@ impl ClockConfig {
|
||||
phase: 0,
|
||||
}),
|
||||
// CLK RTC = ROSC (140MHz) / 2986.667969 ≅ 46875Hz
|
||||
#[cfg(feature = "rp2040")]
|
||||
rtc_clk: Some(RtcClkConfig {
|
||||
src: RtcClkSrc::Rosc,
|
||||
div_int: 2986,
|
||||
@ -295,9 +305,17 @@ pub struct SysClkConfig {
|
||||
/// SYS clock source.
|
||||
pub src: SysClkSrc,
|
||||
/// SYS clock divider.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub div_int: u32,
|
||||
/// SYS clock fraction.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub div_frac: u8,
|
||||
/// SYS clock divider.
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub div_int: u16,
|
||||
/// SYS clock fraction.
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub div_frac: u16,
|
||||
}
|
||||
|
||||
/// USB clock source.
|
||||
@ -358,6 +376,7 @@ pub struct AdcClkConfig {
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub enum RtcClkSrc {
|
||||
/// PLL USB.
|
||||
PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _,
|
||||
@ -372,6 +391,7 @@ pub enum RtcClkSrc {
|
||||
}
|
||||
|
||||
/// RTC clock config.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub struct RtcClkConfig {
|
||||
/// RTC clock source.
|
||||
pub src: RtcClkSrc,
|
||||
@ -396,10 +416,9 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
peris.set_pads_qspi(false);
|
||||
peris.set_pll_sys(false);
|
||||
peris.set_pll_usb(false);
|
||||
// TODO investigate if usb should be unreset here
|
||||
peris.set_usbctrl(false);
|
||||
peris.set_syscfg(false);
|
||||
peris.set_rtc(false);
|
||||
//peris.set_rtc(false);
|
||||
reset::reset(peris);
|
||||
|
||||
// Disable resus that may be enabled from previous software
|
||||
@ -409,9 +428,15 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
|
||||
// Before we touch PLLs, switch sys and ref cleanly away from their aux sources.
|
||||
c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF));
|
||||
#[cfg(feature = "rp2040")]
|
||||
while c.clk_sys_selected().read() != 1 {}
|
||||
#[cfg(feature = "_rp235x")]
|
||||
while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1) {}
|
||||
c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH));
|
||||
#[cfg(feature = "rp2040")]
|
||||
while c.clk_ref_selected().read() != 1 {}
|
||||
#[cfg(feature = "_rp235x")]
|
||||
while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {}
|
||||
|
||||
// Reset the PLLs
|
||||
let mut peris = reset::Peripherals(0);
|
||||
@ -479,15 +504,26 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
w.set_src(ref_src);
|
||||
w.set_auxsrc(ref_aux);
|
||||
});
|
||||
while c.clk_ref_selected().read() != 1 << ref_src as u32 {}
|
||||
#[cfg(feature = "rp2040")]
|
||||
while c.clk_ref_selected().read() != (1 << ref_src as u32) {}
|
||||
#[cfg(feature = "_rp235x")]
|
||||
while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {}
|
||||
c.clk_ref_div().write(|w| {
|
||||
w.set_int(config.ref_clk.div);
|
||||
});
|
||||
|
||||
// Configure tick generation on the 2040.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pac::WATCHDOG.tick().write(|w| {
|
||||
w.set_cycles((clk_ref_freq / 1_000_000) as u16);
|
||||
w.set_enable(true);
|
||||
});
|
||||
// Configure tick generator on the 2350
|
||||
#[cfg(feature = "_rp235x")]
|
||||
{
|
||||
pac::TICKS.timer0_cycles().write(|w| w.0 = clk_ref_freq / 1_000_000);
|
||||
pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true));
|
||||
}
|
||||
|
||||
let (sys_src, sys_aux, clk_sys_freq) = {
|
||||
use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src};
|
||||
@ -500,7 +536,6 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
// SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq),
|
||||
// SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq),
|
||||
};
|
||||
assert!(config.sys_clk.div_int <= 0x1000000);
|
||||
let div = config.sys_clk.div_int as u64 * 256 + config.sys_clk.div_frac as u64;
|
||||
(src, aux, ((freq as u64 * 256) / div) as u32)
|
||||
};
|
||||
@ -508,13 +543,21 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed);
|
||||
if sys_src != ClkSysCtrlSrc::CLK_REF {
|
||||
c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF));
|
||||
while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF as u32 {}
|
||||
#[cfg(feature = "rp2040")]
|
||||
while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {}
|
||||
#[cfg(feature = "_rp235x")]
|
||||
while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {}
|
||||
}
|
||||
c.clk_sys_ctrl().write(|w| {
|
||||
w.set_auxsrc(sys_aux);
|
||||
w.set_src(sys_src);
|
||||
});
|
||||
while c.clk_sys_selected().read() != 1 << sys_src as u32 {}
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
while c.clk_sys_selected().read() != (1 << sys_src as u32) {}
|
||||
#[cfg(feature = "_rp235x")]
|
||||
while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {}
|
||||
|
||||
c.clk_sys_div().write(|w| {
|
||||
w.set_int(config.sys_clk.div_int);
|
||||
w.set_frac(config.sys_clk.div_frac);
|
||||
@ -592,6 +635,8 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
CLOCKS.adc.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// rp2040 specific clocks
|
||||
#[cfg(feature = "rp2040")]
|
||||
if let Some(conf) = config.rtc_clk {
|
||||
c.clk_rtc_div().write(|w| {
|
||||
w.set_int(conf.div_int);
|
||||
@ -621,6 +666,13 @@ pub(crate) unsafe fn init(config: ClockConfig) {
|
||||
CLOCKS.rtc.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// rp235x specific clocks
|
||||
#[cfg(feature = "_rp235x")]
|
||||
{
|
||||
// TODO hstx clock
|
||||
peris.set_hstx(false);
|
||||
}
|
||||
|
||||
// Peripheral clocks should now all be running
|
||||
reset::unreset_wait(peris);
|
||||
}
|
||||
@ -709,6 +761,7 @@ pub fn clk_adc_freq() -> u32 {
|
||||
}
|
||||
|
||||
/// RTC clock frequency.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub fn clk_rtc_freq() -> u16 {
|
||||
CLOCKS.rtc.load(Ordering::Relaxed)
|
||||
}
|
||||
@ -794,6 +847,10 @@ impl<'d, T: GpinPin> Gpin<'d, T> {
|
||||
into_ref!(gpin);
|
||||
|
||||
gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08));
|
||||
#[cfg(feature = "_rp235x")]
|
||||
gpin.pad_ctrl().write(|w| {
|
||||
w.set_iso(false);
|
||||
});
|
||||
|
||||
Gpin {
|
||||
gpin: gpin.map_into(),
|
||||
@ -808,6 +865,7 @@ impl<'d, T: GpinPin> Gpin<'d, T> {
|
||||
|
||||
impl<'d, T: GpinPin> Drop for Gpin<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
self.gpin.pad_ctrl().write(|_| {});
|
||||
self.gpin
|
||||
.gpio()
|
||||
.ctrl()
|
||||
@ -856,6 +914,7 @@ pub enum GpoutSrc {
|
||||
/// ADC.
|
||||
Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _,
|
||||
/// RTC.
|
||||
#[cfg(feature = "rp2040")]
|
||||
Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _,
|
||||
/// REF.
|
||||
Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _,
|
||||
@ -867,16 +926,21 @@ pub struct Gpout<'d, T: GpoutPin> {
|
||||
}
|
||||
|
||||
impl<'d, T: GpoutPin> Gpout<'d, T> {
|
||||
/// Create new general purpose cloud output.
|
||||
/// Create new general purpose clock output.
|
||||
pub fn new(gpout: impl Peripheral<P = T> + 'd) -> Self {
|
||||
into_ref!(gpout);
|
||||
|
||||
gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08));
|
||||
#[cfg(feature = "_rp235x")]
|
||||
gpout.pad_ctrl().write(|w| {
|
||||
w.set_iso(false);
|
||||
});
|
||||
|
||||
Self { gpout }
|
||||
}
|
||||
|
||||
/// Set clock divider.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub fn set_div(&self, int: u32, frac: u8) {
|
||||
let c = pac::CLOCKS;
|
||||
c.clk_gpout_div(self.gpout.number()).write(|w| {
|
||||
@ -885,6 +949,16 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Set clock divider.
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub fn set_div(&self, int: u16, frac: u16) {
|
||||
let c = pac::CLOCKS;
|
||||
c.clk_gpout_div(self.gpout.number()).write(|w| {
|
||||
w.set_int(int);
|
||||
w.set_frac(frac);
|
||||
});
|
||||
}
|
||||
|
||||
/// Set clock source.
|
||||
pub fn set_src(&self, src: GpoutSrc) {
|
||||
let c = pac::CLOCKS;
|
||||
@ -924,13 +998,13 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
|
||||
ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(),
|
||||
ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(),
|
||||
ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(),
|
||||
ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _,
|
||||
//ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _,
|
||||
ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let div = c.clk_gpout_div(self.gpout.number()).read();
|
||||
let int = if div.int() == 0 { 65536 } else { div.int() } as u64;
|
||||
let int = if div.int() == 0 { 0xFFFF } else { div.int() } as u64;
|
||||
let frac = div.frac() as u64;
|
||||
|
||||
((base as u64 * 256) / (int * 256 + frac)) as u32
|
||||
@ -940,6 +1014,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
|
||||
impl<'d, T: GpoutPin> Drop for Gpout<'d, T> {
|
||||
fn drop(&mut self) {
|
||||
self.disable();
|
||||
self.gpout.pad_ctrl().write(|_| {});
|
||||
self.gpout
|
||||
.gpio()
|
||||
.ctrl()
|
||||
@ -987,7 +1062,7 @@ impl rand_core::RngCore for RoscRng {
|
||||
/// and can only be exited through resets, dormant-wake GPIO interrupts,
|
||||
/// and RTC interrupts. If RTC is clocked from an internal clock source
|
||||
/// it will be stopped and not function as a wakeup source.
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[cfg(all(target_arch = "arm", feature = "rp2040"))]
|
||||
pub fn dormant_sleep() {
|
||||
struct Set<T: Copy, F: Fn()>(Reg<T, RW>, T, F);
|
||||
|
||||
@ -1107,7 +1182,7 @@ pub fn dormant_sleep() {
|
||||
coma = in (reg) 0x636f6d61,
|
||||
);
|
||||
} else {
|
||||
pac::ROSC.dormant().write_value(0x636f6d61);
|
||||
pac::ROSC.dormant().write_value(rp_pac::rosc::regs::Dormant(0x636f6d61));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use crate::{interrupt, pac, peripherals};
|
||||
#[cfg(feature = "rt")]
|
||||
#[interrupt]
|
||||
fn DMA_IRQ_0() {
|
||||
let ints0 = pac::DMA.ints0().read().ints0();
|
||||
let ints0 = pac::DMA.ints(0).read();
|
||||
for channel in 0..CHANNEL_COUNT {
|
||||
let ctrl_trig = pac::DMA.ch(channel).ctrl_trig().read();
|
||||
if ctrl_trig.ahb_error() {
|
||||
@ -26,14 +26,14 @@ fn DMA_IRQ_0() {
|
||||
CHANNEL_WAKERS[channel].wake();
|
||||
}
|
||||
}
|
||||
pac::DMA.ints0().write(|w| w.set_ints0(ints0));
|
||||
pac::DMA.ints(0).write_value(ints0);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init() {
|
||||
interrupt::DMA_IRQ_0.disable();
|
||||
interrupt::DMA_IRQ_0.set_priority(interrupt::Priority::P3);
|
||||
|
||||
pac::DMA.inte0().write(|w| w.set_inte0(0xFFFF));
|
||||
pac::DMA.inte(0).write_value(0xFFFF);
|
||||
|
||||
interrupt::DMA_IRQ_0.enable();
|
||||
}
|
||||
@ -45,7 +45,7 @@ pub unsafe fn read<'a, C: Channel, W: Word>(
|
||||
ch: impl Peripheral<P = C> + 'a,
|
||||
from: *const W,
|
||||
to: *mut [W],
|
||||
dreq: u8,
|
||||
dreq: vals::TreqSel,
|
||||
) -> Transfer<'a, C> {
|
||||
copy_inner(
|
||||
ch,
|
||||
@ -66,7 +66,7 @@ pub unsafe fn write<'a, C: Channel, W: Word>(
|
||||
ch: impl Peripheral<P = C> + 'a,
|
||||
from: *const [W],
|
||||
to: *mut W,
|
||||
dreq: u8,
|
||||
dreq: vals::TreqSel,
|
||||
) -> Transfer<'a, C> {
|
||||
copy_inner(
|
||||
ch,
|
||||
@ -90,7 +90,7 @@ pub unsafe fn write_repeated<'a, C: Channel, W: Word>(
|
||||
ch: impl Peripheral<P = C> + 'a,
|
||||
to: *mut W,
|
||||
len: usize,
|
||||
dreq: u8,
|
||||
dreq: vals::TreqSel,
|
||||
) -> Transfer<'a, C> {
|
||||
copy_inner(
|
||||
ch,
|
||||
@ -123,7 +123,7 @@ pub unsafe fn copy<'a, C: Channel, W: Word>(
|
||||
W::size(),
|
||||
true,
|
||||
true,
|
||||
vals::TreqSel::PERMANENT.0,
|
||||
vals::TreqSel::PERMANENT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ fn copy_inner<'a, C: Channel>(
|
||||
data_size: DataSize,
|
||||
incr_read: bool,
|
||||
incr_write: bool,
|
||||
dreq: u8,
|
||||
dreq: vals::TreqSel,
|
||||
) -> Transfer<'a, C> {
|
||||
into_ref!(ch);
|
||||
|
||||
@ -143,14 +143,20 @@ fn copy_inner<'a, C: Channel>(
|
||||
|
||||
p.read_addr().write_value(from as u32);
|
||||
p.write_addr().write_value(to as u32);
|
||||
p.trans_count().write_value(len as u32);
|
||||
#[cfg(feature = "rp2040")]
|
||||
p.trans_count().write(|w| {
|
||||
*w = len as u32;
|
||||
});
|
||||
#[cfg(feature = "_rp235x")]
|
||||
p.trans_count().write(|w| {
|
||||
w.set_mode(0.into());
|
||||
w.set_count(len as u32);
|
||||
});
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
p.ctrl_trig().write(|w| {
|
||||
// TODO: Add all DREQ options to pac vals::TreqSel, and use
|
||||
// `set_treq:sel`
|
||||
w.0 = ((dreq as u32) & 0x3f) << 15usize;
|
||||
w.set_treq_sel(dreq);
|
||||
w.set_data_size(data_size);
|
||||
w.set_incr_read(incr_read);
|
||||
w.set_incr_write(incr_write);
|
||||
@ -202,7 +208,10 @@ impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub(crate) const CHANNEL_COUNT: usize = 12;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub(crate) const CHANNEL_COUNT: usize = 16;
|
||||
const NEW_AW: AtomicWaker = AtomicWaker::new();
|
||||
static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT];
|
||||
|
||||
@ -297,3 +306,11 @@ channel!(DMA_CH8, 8);
|
||||
channel!(DMA_CH9, 9);
|
||||
channel!(DMA_CH10, 10);
|
||||
channel!(DMA_CH11, 11);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
channel!(DMA_CH12, 12);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
channel!(DMA_CH13, 13);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
channel!(DMA_CH14, 14);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
channel!(DMA_CH15, 15);
|
||||
|
@ -17,9 +17,13 @@ use crate::peripherals::FLASH;
|
||||
/// Flash base address.
|
||||
pub const FLASH_BASE: *const u32 = 0x10000000 as _;
|
||||
|
||||
/// Address for xip setup function set up by the 235x bootrom.
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub const BOOTROM_BASE: *const u32 = 0x400e0000 as _;
|
||||
|
||||
/// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead.
|
||||
// TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance.
|
||||
pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram");
|
||||
pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x");
|
||||
|
||||
// **NOTE**:
|
||||
//
|
||||
@ -97,7 +101,10 @@ impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, '
|
||||
// Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in
|
||||
// flight that might effect an address written to start a new transfer. This stalls
|
||||
// until after any transfer is complete, so the address will not change anymore.
|
||||
#[cfg(feature = "rp2040")]
|
||||
const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _;
|
||||
unsafe {
|
||||
core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE);
|
||||
}
|
||||
@ -225,12 +232,14 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
|
||||
}
|
||||
|
||||
/// Read SPI flash unique ID
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> {
|
||||
unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read SPI flash JEDEC ID
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> {
|
||||
let mut jedec = None;
|
||||
unsafe {
|
||||
@ -301,8 +310,18 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> {
|
||||
// Use the XIP AUX bus port, rather than the FIFO register access (e.x.
|
||||
// pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on
|
||||
// general XIP access.
|
||||
#[cfg(feature = "rp2040")]
|
||||
const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _;
|
||||
let transfer = unsafe { crate::dma::read(self.dma.as_mut().unwrap(), XIP_AUX_BASE, data, 37) };
|
||||
#[cfg(feature = "_rp235x")]
|
||||
const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _;
|
||||
let transfer = unsafe {
|
||||
crate::dma::read(
|
||||
self.dma.as_mut().unwrap(),
|
||||
XIP_AUX_BASE,
|
||||
data,
|
||||
pac::dma::vals::TreqSel::XIP_STREAM,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(BackgroundRead {
|
||||
flash: PhantomData,
|
||||
@ -505,7 +524,10 @@ mod ram_helpers {
|
||||
pub unsafe fn flash_range_erase(addr: u32, len: u32) {
|
||||
let mut boot2 = [0u32; 256 / 4];
|
||||
let ptrs = if USE_BOOT2 {
|
||||
#[cfg(feature = "rp2040")]
|
||||
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256);
|
||||
flash_function_pointers_with_boot2(true, false, &boot2)
|
||||
} else {
|
||||
flash_function_pointers(true, false)
|
||||
@ -535,7 +557,10 @@ mod ram_helpers {
|
||||
pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) {
|
||||
let mut boot2 = [0u32; 256 / 4];
|
||||
let ptrs = if USE_BOOT2 {
|
||||
#[cfg(feature = "rp2040")]
|
||||
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256);
|
||||
flash_function_pointers_with_boot2(true, true, &boot2)
|
||||
} else {
|
||||
flash_function_pointers(true, true)
|
||||
@ -570,7 +595,10 @@ mod ram_helpers {
|
||||
pub unsafe fn flash_range_program(addr: u32, data: &[u8]) {
|
||||
let mut boot2 = [0u32; 256 / 4];
|
||||
let ptrs = if USE_BOOT2 {
|
||||
#[cfg(feature = "rp2040")]
|
||||
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256);
|
||||
flash_function_pointers_with_boot2(false, true, &boot2)
|
||||
} else {
|
||||
flash_function_pointers(false, true)
|
||||
@ -597,16 +625,8 @@ mod ram_helpers {
|
||||
/// addr must be aligned to 4096
|
||||
#[inline(never)]
|
||||
#[link_section = ".data.ram_func"]
|
||||
#[cfg(feature = "rp2040")]
|
||||
unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) {
|
||||
/*
|
||||
Should be equivalent to:
|
||||
rom_data::connect_internal_flash();
|
||||
rom_data::flash_exit_xip();
|
||||
rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected
|
||||
rom_data::flash_range_program(addr, data as *const _, len); // if selected
|
||||
rom_data::flash_flush_cache();
|
||||
rom_data::flash_enter_cmd_xip();
|
||||
*/
|
||||
#[cfg(target_arch = "arm")]
|
||||
core::arch::asm!(
|
||||
"mov r8, r0",
|
||||
@ -659,6 +679,32 @@ mod ram_helpers {
|
||||
);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Nothing must access flash while this is running.
|
||||
/// Usually this means:
|
||||
/// - interrupts must be disabled
|
||||
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
|
||||
/// - DMA must not access flash memory
|
||||
/// Length of data must be a multiple of 4096
|
||||
/// addr must be aligned to 4096
|
||||
#[inline(never)]
|
||||
#[link_section = ".data.ram_func"]
|
||||
#[cfg(feature = "_rp235x")]
|
||||
unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) {
|
||||
let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null());
|
||||
((*ptrs).connect_internal_flash)();
|
||||
((*ptrs).flash_exit_xip)();
|
||||
if (*ptrs).flash_range_erase.is_some() {
|
||||
((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0);
|
||||
}
|
||||
if (*ptrs).flash_range_program.is_some() {
|
||||
((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize);
|
||||
}
|
||||
((*ptrs).flash_flush_cache)();
|
||||
((*ptrs).flash_enter_cmd_xip)();
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct FlashCommand {
|
||||
cmd_addr: *const u8,
|
||||
@ -692,6 +738,7 @@ mod ram_helpers {
|
||||
/// - DMA must not access flash memory
|
||||
///
|
||||
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub unsafe fn flash_unique_id(out: &mut [u8]) {
|
||||
let mut boot2 = [0u32; 256 / 4];
|
||||
let ptrs = if USE_BOOT2 {
|
||||
@ -700,6 +747,7 @@ mod ram_helpers {
|
||||
} else {
|
||||
flash_function_pointers(false, false)
|
||||
};
|
||||
|
||||
// 4B - read unique ID
|
||||
let cmd = [0x4B];
|
||||
read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers);
|
||||
@ -720,6 +768,7 @@ mod ram_helpers {
|
||||
/// - DMA must not access flash memory
|
||||
///
|
||||
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub unsafe fn flash_jedec_id() -> u32 {
|
||||
let mut boot2 = [0u32; 256 / 4];
|
||||
let ptrs = if USE_BOOT2 {
|
||||
@ -728,6 +777,7 @@ mod ram_helpers {
|
||||
} else {
|
||||
flash_function_pointers(false, false)
|
||||
};
|
||||
|
||||
let mut id = [0u8; 4];
|
||||
// 9F - read JEDEC ID
|
||||
let cmd = [0x9F];
|
||||
@ -735,6 +785,7 @@ mod ram_helpers {
|
||||
u32::from_be_bytes(id)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) {
|
||||
read_flash_inner(
|
||||
FlashCommand {
|
||||
@ -758,6 +809,7 @@ mod ram_helpers {
|
||||
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
|
||||
#[inline(never)]
|
||||
#[link_section = ".data.ram_func"]
|
||||
#[cfg(feature = "rp2040")]
|
||||
unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) {
|
||||
#[cfg(target_arch = "arm")]
|
||||
core::arch::asm!(
|
||||
|
@ -14,7 +14,12 @@ use crate::pac::SIO;
|
||||
use crate::{interrupt, pac, peripherals, Peripheral, RegExt};
|
||||
|
||||
const NEW_AW: AtomicWaker = AtomicWaker::new();
|
||||
const BANK0_PIN_COUNT: usize = 30;
|
||||
|
||||
#[cfg(any(feature = "rp2040", feature = "rp235xa"))]
|
||||
pub(crate) const BANK0_PIN_COUNT: usize = 30;
|
||||
#[cfg(feature = "rp235xb")]
|
||||
pub(crate) const BANK0_PIN_COUNT: usize = 48;
|
||||
|
||||
static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [NEW_AW; BANK0_PIN_COUNT];
|
||||
#[cfg(feature = "qspi-as-gpio")]
|
||||
const QSPI_PIN_COUNT: usize = 6;
|
||||
@ -178,6 +183,13 @@ impl<'d> Input<'d> {
|
||||
pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> {
|
||||
self.pin.dormant_wake(cfg)
|
||||
}
|
||||
|
||||
/// Set the pin's pad isolation
|
||||
#[cfg(feature = "_rp235x")]
|
||||
#[inline]
|
||||
pub fn set_pad_isolation(&mut self, isolate: bool) {
|
||||
self.pin.set_pad_isolation(isolate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupt trigger levels.
|
||||
@ -413,6 +425,13 @@ impl<'d> Output<'d> {
|
||||
pub fn toggle(&mut self) {
|
||||
self.pin.toggle()
|
||||
}
|
||||
|
||||
/// Set the pin's pad isolation
|
||||
#[cfg(feature = "_rp235x")]
|
||||
#[inline]
|
||||
pub fn set_pad_isolation(&mut self, isolate: bool) {
|
||||
self.pin.set_pad_isolation(isolate)
|
||||
}
|
||||
}
|
||||
|
||||
/// GPIO output open-drain.
|
||||
@ -539,6 +558,13 @@ impl<'d> OutputOpenDrain<'d> {
|
||||
pub async fn wait_for_any_edge(&mut self) {
|
||||
self.pin.wait_for_any_edge().await;
|
||||
}
|
||||
|
||||
/// Set the pin's pad isolation
|
||||
#[cfg(feature = "_rp235x")]
|
||||
#[inline]
|
||||
pub fn set_pad_isolation(&mut self, isolate: bool) {
|
||||
self.pin.set_pad_isolation(isolate)
|
||||
}
|
||||
}
|
||||
|
||||
/// GPIO flexible pin.
|
||||
@ -560,11 +586,16 @@ impl<'d> Flex<'d> {
|
||||
into_ref!(pin);
|
||||
|
||||
pin.pad_ctrl().write(|w| {
|
||||
#[cfg(feature = "_rp235x")]
|
||||
w.set_iso(false);
|
||||
w.set_ie(true);
|
||||
});
|
||||
|
||||
pin.gpio().ctrl().write(|w| {
|
||||
#[cfg(feature = "rp2040")]
|
||||
w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIOB_PROC_0 as _);
|
||||
});
|
||||
|
||||
Self { pin: pin.map_into() }
|
||||
@ -760,6 +791,15 @@ impl<'d> Flex<'d> {
|
||||
cfg,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the pin's pad isolation
|
||||
#[cfg(feature = "_rp235x")]
|
||||
#[inline]
|
||||
pub fn set_pad_isolation(&mut self, isolate: bool) {
|
||||
self.pin.pad_ctrl().modify(|w| {
|
||||
w.set_iso(isolate);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Drop for Flex<'d> {
|
||||
@ -806,12 +846,12 @@ pub(crate) trait SealedPin: Sized {
|
||||
|
||||
#[inline]
|
||||
fn _pin(&self) -> u8 {
|
||||
self.pin_bank() & 0x1f
|
||||
self.pin_bank() & 0x7f
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn _bank(&self) -> Bank {
|
||||
match self.pin_bank() >> 5 {
|
||||
match self.pin_bank() >> 7 {
|
||||
#[cfg(feature = "qspi-as-gpio")]
|
||||
1 => Bank::Qspi,
|
||||
_ => Bank::Bank0,
|
||||
@ -840,15 +880,27 @@ pub(crate) trait SealedPin: Sized {
|
||||
}
|
||||
|
||||
fn sio_out(&self) -> pac::sio::Gpio {
|
||||
SIO.gpio_out(self._bank() as _)
|
||||
if cfg!(feature = "rp2040") {
|
||||
SIO.gpio_out(self._bank() as _)
|
||||
} else {
|
||||
SIO.gpio_out((self._pin() / 32) as _)
|
||||
}
|
||||
}
|
||||
|
||||
fn sio_oe(&self) -> pac::sio::Gpio {
|
||||
SIO.gpio_oe(self._bank() as _)
|
||||
if cfg!(feature = "rp2040") {
|
||||
SIO.gpio_oe(self._bank() as _)
|
||||
} else {
|
||||
SIO.gpio_oe((self._pin() / 32) as _)
|
||||
}
|
||||
}
|
||||
|
||||
fn sio_in(&self) -> Reg<u32, RW> {
|
||||
SIO.gpio_in(self._bank() as _)
|
||||
if cfg!(feature = "rp2040") {
|
||||
SIO.gpio_in(self._bank() as _)
|
||||
} else {
|
||||
SIO.gpio_in((self._pin() / 32) as _)
|
||||
}
|
||||
}
|
||||
|
||||
fn int_proc(&self) -> pac::io::Int {
|
||||
@ -913,7 +965,7 @@ macro_rules! impl_pin {
|
||||
impl SealedPin for peripherals::$name {
|
||||
#[inline]
|
||||
fn pin_bank(&self) -> u8 {
|
||||
($bank as u8) * 32 + $pin_num
|
||||
($bank as u8) * 128 + $pin_num
|
||||
}
|
||||
}
|
||||
|
||||
@ -956,6 +1008,44 @@ impl_pin!(PIN_27, Bank::Bank0, 27);
|
||||
impl_pin!(PIN_28, Bank::Bank0, 28);
|
||||
impl_pin!(PIN_29, Bank::Bank0, 29);
|
||||
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_30, Bank::Bank0, 30);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_31, Bank::Bank0, 31);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_32, Bank::Bank0, 32);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_33, Bank::Bank0, 33);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_34, Bank::Bank0, 34);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_35, Bank::Bank0, 35);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_36, Bank::Bank0, 36);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_37, Bank::Bank0, 37);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_38, Bank::Bank0, 38);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_39, Bank::Bank0, 39);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_40, Bank::Bank0, 40);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_41, Bank::Bank0, 41);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_42, Bank::Bank0, 42);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_43, Bank::Bank0, 43);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_44, Bank::Bank0, 44);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_45, Bank::Bank0, 45);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_46, Bank::Bank0, 46);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_47, Bank::Bank0, 47);
|
||||
|
||||
// TODO rp235x bank1 as gpio support
|
||||
#[cfg(feature = "qspi-as-gpio")]
|
||||
impl_pin!(PIN_QSPI_SCLK, Bank::Qspi, 0);
|
||||
#[cfg(feature = "qspi-as-gpio")]
|
||||
|
@ -312,13 +312,13 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from address into buffer using DMA.
|
||||
/// Read from address into buffer asynchronously.
|
||||
pub async fn read_async(&mut self, addr: impl Into<u16>, buffer: &mut [u8]) -> Result<(), Error> {
|
||||
Self::setup(addr.into())?;
|
||||
self.read_async_internal(buffer, true, true).await
|
||||
}
|
||||
|
||||
/// Write to address from buffer using DMA.
|
||||
/// Write to address from buffer asynchronously.
|
||||
pub async fn write_async(
|
||||
&mut self,
|
||||
addr: impl Into<u16>,
|
||||
@ -328,7 +328,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
|
||||
self.write_async_internal(bytes, true).await
|
||||
}
|
||||
|
||||
/// Write to address from bytes and read from address into buffer using DMA.
|
||||
/// Write to address from bytes and read from address into buffer asynchronously.
|
||||
pub async fn write_read_async(
|
||||
&mut self,
|
||||
addr: impl Into<u16>,
|
||||
@ -363,6 +363,8 @@ where
|
||||
{
|
||||
pin.gpio().ctrl().write(|w| w.set_funcsel(3));
|
||||
pin.pad_ctrl().write(|w| {
|
||||
#[cfg(feature = "_rp235x")]
|
||||
w.set_iso(false);
|
||||
w.set_schmitt(true);
|
||||
w.set_slewfast(false);
|
||||
w.set_ie(true);
|
||||
@ -779,9 +781,6 @@ pub fn i2c_reserved_addr(addr: u16) -> bool {
|
||||
}
|
||||
|
||||
pub(crate) trait SealedInstance {
|
||||
const TX_DREQ: u8;
|
||||
const RX_DREQ: u8;
|
||||
|
||||
fn regs() -> crate::pac::i2c::I2c;
|
||||
fn reset() -> crate::pac::resets::regs::Peripherals;
|
||||
fn waker() -> &'static AtomicWaker;
|
||||
@ -816,11 +815,8 @@ pub trait Instance: SealedInstance {
|
||||
}
|
||||
|
||||
macro_rules! impl_instance {
|
||||
($type:ident, $irq:ident, $reset:ident, $tx_dreq:expr, $rx_dreq:expr) => {
|
||||
($type:ident, $irq:ident, $reset:ident) => {
|
||||
impl SealedInstance for peripherals::$type {
|
||||
const TX_DREQ: u8 = $tx_dreq;
|
||||
const RX_DREQ: u8 = $rx_dreq;
|
||||
|
||||
#[inline]
|
||||
fn regs() -> pac::i2c::I2c {
|
||||
pac::$type
|
||||
@ -846,8 +842,8 @@ macro_rules! impl_instance {
|
||||
};
|
||||
}
|
||||
|
||||
impl_instance!(I2C0, I2C0_IRQ, set_i2c0, 32, 33);
|
||||
impl_instance!(I2C1, I2C1_IRQ, set_i2c1, 34, 35);
|
||||
impl_instance!(I2C0, I2C0_IRQ, set_i2c0);
|
||||
impl_instance!(I2C1, I2C1_IRQ, set_i2c1);
|
||||
|
||||
/// SDA pin.
|
||||
pub trait SdaPin<T: Instance>: crate::gpio::Pin {}
|
||||
@ -890,3 +886,39 @@ impl_pin!(PIN_26, I2C1, SdaPin);
|
||||
impl_pin!(PIN_27, I2C1, SclPin);
|
||||
impl_pin!(PIN_28, I2C0, SdaPin);
|
||||
impl_pin!(PIN_29, I2C0, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_30, I2C1, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_31, I2C1, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_32, I2C0, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_33, I2C0, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_34, I2C1, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_35, I2C1, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_36, I2C0, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_37, I2C0, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_38, I2C1, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_39, I2C1, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_40, I2C0, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_41, I2C0, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_42, I2C1, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_43, I2C1, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_44, I2C0, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_45, I2C0, SclPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_46, I2C1, SdaPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_47, I2C1, SclPin);
|
||||
|
@ -9,28 +9,41 @@
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
#[cfg(feature = "binary-info")]
|
||||
pub use rp_binary_info as binary_info;
|
||||
|
||||
#[cfg(feature = "critical-section-impl")]
|
||||
mod critical_section_impl;
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
mod intrinsics;
|
||||
|
||||
pub mod adc;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub mod block;
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub mod bootsel;
|
||||
pub mod clocks;
|
||||
pub mod dma;
|
||||
pub mod flash;
|
||||
#[cfg(feature = "rp2040")]
|
||||
mod float;
|
||||
pub mod gpio;
|
||||
pub mod i2c;
|
||||
pub mod i2c_slave;
|
||||
pub mod multicore;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub mod otp;
|
||||
pub mod pwm;
|
||||
mod reset;
|
||||
pub mod rom_data;
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub mod rtc;
|
||||
pub mod spi;
|
||||
#[cfg(feature = "time-driver")]
|
||||
pub mod time_driver;
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub mod trng;
|
||||
pub mod uart;
|
||||
pub mod usb;
|
||||
pub mod watchdog;
|
||||
@ -49,6 +62,7 @@ pub(crate) use rp_pac as pac;
|
||||
#[cfg(feature = "rt")]
|
||||
pub use crate::pac::NVIC_PRIO_BITS;
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
TIMER_IRQ_0,
|
||||
TIMER_IRQ_1,
|
||||
@ -84,6 +98,54 @@ embassy_hal_internal::interrupt_mod!(
|
||||
SWI_IRQ_5,
|
||||
);
|
||||
|
||||
#[cfg(feature = "_rp235x")]
|
||||
embassy_hal_internal::interrupt_mod!(
|
||||
TIMER0_IRQ_0,
|
||||
TIMER0_IRQ_1,
|
||||
TIMER0_IRQ_2,
|
||||
TIMER0_IRQ_3,
|
||||
TIMER1_IRQ_0,
|
||||
TIMER1_IRQ_1,
|
||||
TIMER1_IRQ_2,
|
||||
TIMER1_IRQ_3,
|
||||
PWM_IRQ_WRAP_0,
|
||||
PWM_IRQ_WRAP_1,
|
||||
DMA_IRQ_0,
|
||||
DMA_IRQ_1,
|
||||
USBCTRL_IRQ,
|
||||
PIO0_IRQ_0,
|
||||
PIO0_IRQ_1,
|
||||
PIO1_IRQ_0,
|
||||
PIO1_IRQ_1,
|
||||
PIO2_IRQ_0,
|
||||
PIO2_IRQ_1,
|
||||
IO_IRQ_BANK0,
|
||||
IO_IRQ_BANK0_NS,
|
||||
IO_IRQ_QSPI,
|
||||
IO_IRQ_QSPI_NS,
|
||||
SIO_IRQ_FIFO,
|
||||
SIO_IRQ_BELL,
|
||||
SIO_IRQ_FIFO_NS,
|
||||
SIO_IRQ_BELL_NS,
|
||||
CLOCKS_IRQ,
|
||||
SPI0_IRQ,
|
||||
SPI1_IRQ,
|
||||
UART0_IRQ,
|
||||
UART1_IRQ,
|
||||
ADC_IRQ_FIFO,
|
||||
I2C0_IRQ,
|
||||
I2C1_IRQ,
|
||||
TRNG_IRQ,
|
||||
PLL_SYS_IRQ,
|
||||
PLL_USB_IRQ,
|
||||
SWI_IRQ_0,
|
||||
SWI_IRQ_1,
|
||||
SWI_IRQ_2,
|
||||
SWI_IRQ_3,
|
||||
SWI_IRQ_4,
|
||||
SWI_IRQ_5,
|
||||
);
|
||||
|
||||
/// Macro to bind interrupts to handlers.
|
||||
///
|
||||
/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`)
|
||||
@ -123,6 +185,7 @@ macro_rules! bind_interrupts {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
embassy_hal_internal::peripherals! {
|
||||
PIN_0,
|
||||
PIN_1,
|
||||
@ -210,7 +273,142 @@ embassy_hal_internal::peripherals! {
|
||||
BOOTSEL,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "boot2-none"))]
|
||||
#[cfg(feature = "_rp235x")]
|
||||
embassy_hal_internal::peripherals! {
|
||||
PIN_0,
|
||||
PIN_1,
|
||||
PIN_2,
|
||||
PIN_3,
|
||||
PIN_4,
|
||||
PIN_5,
|
||||
PIN_6,
|
||||
PIN_7,
|
||||
PIN_8,
|
||||
PIN_9,
|
||||
PIN_10,
|
||||
PIN_11,
|
||||
PIN_12,
|
||||
PIN_13,
|
||||
PIN_14,
|
||||
PIN_15,
|
||||
PIN_16,
|
||||
PIN_17,
|
||||
PIN_18,
|
||||
PIN_19,
|
||||
PIN_20,
|
||||
PIN_21,
|
||||
PIN_22,
|
||||
PIN_23,
|
||||
PIN_24,
|
||||
PIN_25,
|
||||
PIN_26,
|
||||
PIN_27,
|
||||
PIN_28,
|
||||
PIN_29,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_30,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_31,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_32,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_33,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_34,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_35,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_36,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_37,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_38,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_39,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_40,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_41,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_42,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_43,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_44,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_45,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_46,
|
||||
#[cfg(feature = "rp235xb")]
|
||||
PIN_47,
|
||||
PIN_QSPI_SCLK,
|
||||
PIN_QSPI_SS,
|
||||
PIN_QSPI_SD0,
|
||||
PIN_QSPI_SD1,
|
||||
PIN_QSPI_SD2,
|
||||
PIN_QSPI_SD3,
|
||||
|
||||
UART0,
|
||||
UART1,
|
||||
|
||||
SPI0,
|
||||
SPI1,
|
||||
|
||||
I2C0,
|
||||
I2C1,
|
||||
|
||||
DMA_CH0,
|
||||
DMA_CH1,
|
||||
DMA_CH2,
|
||||
DMA_CH3,
|
||||
DMA_CH4,
|
||||
DMA_CH5,
|
||||
DMA_CH6,
|
||||
DMA_CH7,
|
||||
DMA_CH8,
|
||||
DMA_CH9,
|
||||
DMA_CH10,
|
||||
DMA_CH11,
|
||||
DMA_CH12,
|
||||
DMA_CH13,
|
||||
DMA_CH14,
|
||||
DMA_CH15,
|
||||
|
||||
PWM_SLICE0,
|
||||
PWM_SLICE1,
|
||||
PWM_SLICE2,
|
||||
PWM_SLICE3,
|
||||
PWM_SLICE4,
|
||||
PWM_SLICE5,
|
||||
PWM_SLICE6,
|
||||
PWM_SLICE7,
|
||||
PWM_SLICE8,
|
||||
PWM_SLICE9,
|
||||
PWM_SLICE10,
|
||||
PWM_SLICE11,
|
||||
|
||||
USB,
|
||||
|
||||
RTC,
|
||||
|
||||
FLASH,
|
||||
|
||||
ADC,
|
||||
ADC_TEMP_SENSOR,
|
||||
|
||||
CORE1,
|
||||
|
||||
PIO0,
|
||||
PIO1,
|
||||
PIO2,
|
||||
|
||||
WATCHDOG,
|
||||
BOOTSEL,
|
||||
|
||||
TRNG
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
|
||||
macro_rules! select_bootloader {
|
||||
( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => {
|
||||
$(
|
||||
@ -227,7 +425,7 @@ macro_rules! select_bootloader {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "boot2-none"))]
|
||||
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
|
||||
select_bootloader! {
|
||||
"boot2-at25sf128a" => BOOT_LOADER_AT25SF128A,
|
||||
"boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS,
|
||||
@ -279,6 +477,7 @@ pub fn install_core0_stack_guard() -> Result<(), ()> {
|
||||
unsafe { install_stack_guard(core::ptr::addr_of_mut!(_stack_end)) }
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "rp2040", not(feature = "_test")))]
|
||||
#[inline(always)]
|
||||
fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
|
||||
let core = unsafe { cortex_m::Peripherals::steal() };
|
||||
@ -306,6 +505,32 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "_rp235x", not(feature = "_test")))]
|
||||
#[inline(always)]
|
||||
fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> {
|
||||
let core = unsafe { cortex_m::Peripherals::steal() };
|
||||
|
||||
// Fail if MPU is already configured
|
||||
if core.MPU.ctrl.read() != 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
core.MPU.ctrl.write(5); // enable mpu with background default map
|
||||
core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address
|
||||
core.MPU.rlar.write(1); // enable region
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is to hack around cortex_m defaulting to ARMv7 when building tests,
|
||||
// so the compile fails when we try to use ARMv8 peripherals.
|
||||
#[cfg(feature = "_test")]
|
||||
#[inline(always)]
|
||||
fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// HAL configuration for RP.
|
||||
pub mod config {
|
||||
use crate::clocks::ClockConfig;
|
||||
@ -354,7 +579,7 @@ pub fn init(config: config::Config) -> Peripherals {
|
||||
peripherals
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg(all(feature = "rt", feature = "rp2040"))]
|
||||
#[cortex_m_rt::pre_init]
|
||||
unsafe fn pre_init() {
|
||||
// SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD.
|
||||
|
@ -84,7 +84,7 @@ impl<const SIZE: usize> Stack<SIZE> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg(all(feature = "rt", feature = "rp2040"))]
|
||||
#[interrupt]
|
||||
#[link_section = ".data.ram_func"]
|
||||
unsafe fn SIO_IRQ_PROC1() {
|
||||
@ -109,6 +109,31 @@ unsafe fn SIO_IRQ_PROC1() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "rt", feature = "_rp235x"))]
|
||||
#[interrupt]
|
||||
#[link_section = ".data.ram_func"]
|
||||
unsafe fn SIO_IRQ_FIFO() {
|
||||
let sio = pac::SIO;
|
||||
// Clear IRQ
|
||||
sio.fifo().st().write(|w| w.set_wof(false));
|
||||
|
||||
while sio.fifo().st().read().vld() {
|
||||
// Pause CORE1 execution and disable interrupts
|
||||
if fifo_read_wfe() == PAUSE_TOKEN {
|
||||
cortex_m::interrupt::disable();
|
||||
// Signal to CORE0 that execution is paused
|
||||
fifo_write(PAUSE_TOKEN);
|
||||
// Wait for `resume` signal from CORE0
|
||||
while fifo_read_wfe() != RESUME_TOKEN {
|
||||
cortex_m::asm::nop();
|
||||
}
|
||||
cortex_m::interrupt::enable();
|
||||
// Signal to CORE0 that execution is resumed
|
||||
fifo_write(RESUME_TOKEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a function on this core
|
||||
pub fn spawn_core1<F, const SIZE: usize>(_core1: CORE1, stack: &'static mut Stack<SIZE>, entry: F)
|
||||
where
|
||||
@ -135,7 +160,14 @@ where
|
||||
|
||||
IS_CORE1_INIT.store(true, Ordering::Release);
|
||||
// Enable fifo interrupt on CORE1 for `pause` functionality.
|
||||
unsafe { interrupt::SIO_IRQ_PROC1.enable() };
|
||||
#[cfg(feature = "rp2040")]
|
||||
unsafe {
|
||||
interrupt::SIO_IRQ_PROC1.enable()
|
||||
};
|
||||
#[cfg(feature = "_rp235x")]
|
||||
unsafe {
|
||||
interrupt::SIO_IRQ_FIFO.enable()
|
||||
};
|
||||
|
||||
entry()
|
||||
}
|
||||
|
108
embassy-rp/src/otp.rs
Normal file
108
embassy-rp/src/otp.rs
Normal file
@ -0,0 +1,108 @@
|
||||
//! Interface to the RP2350's One Time Programmable Memory
|
||||
|
||||
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
|
||||
// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs
|
||||
|
||||
/// The ways in which we can fail to read OTP
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
/// The user passed an invalid index to a function.
|
||||
InvalidIndex,
|
||||
/// The hardware refused to let us read this word, probably due to
|
||||
/// read lock set earlier in the boot process.
|
||||
InvalidPermissions,
|
||||
}
|
||||
|
||||
/// OTP read address, using automatic Error Correction.
|
||||
///
|
||||
/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or
|
||||
/// all-ones on permission failure. Only the first 8 KiB is populated.
|
||||
pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32;
|
||||
|
||||
/// OTP read address, without using any automatic Error Correction.
|
||||
///
|
||||
/// A 32-bit read returns 24-bits of raw data from the OTP word.
|
||||
pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32;
|
||||
|
||||
/// How many pages in OTP (post error-correction)
|
||||
pub const NUM_PAGES: usize = 64;
|
||||
|
||||
/// How many rows in one page in OTP (post error-correction)
|
||||
pub const NUM_ROWS_PER_PAGE: usize = 64;
|
||||
|
||||
/// How many rows in OTP (post error-correction)
|
||||
pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE;
|
||||
|
||||
/// Read one ECC protected word from the OTP
|
||||
pub fn read_ecc_word(row: usize) -> Result<u16, Error> {
|
||||
if row >= NUM_ROWS {
|
||||
return Err(Error::InvalidIndex);
|
||||
}
|
||||
// First do a raw read to check permissions
|
||||
let _ = read_raw_word(row)?;
|
||||
// One 32-bit read gets us two rows
|
||||
let offset = row >> 1;
|
||||
// # Safety
|
||||
//
|
||||
// We checked this offset was in range already.
|
||||
let value = unsafe { OTP_DATA_BASE.add(offset).read() };
|
||||
if (row & 1) == 0 {
|
||||
Ok(value as u16)
|
||||
} else {
|
||||
Ok((value >> 16) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read one raw word from the OTP
|
||||
///
|
||||
/// You get the 24-bit raw value in the lower part of the 32-bit result.
|
||||
pub fn read_raw_word(row: usize) -> Result<u32, Error> {
|
||||
if row >= NUM_ROWS {
|
||||
return Err(Error::InvalidIndex);
|
||||
}
|
||||
// One 32-bit read gets us one row
|
||||
// # Safety
|
||||
//
|
||||
// We checked this offset was in range already.
|
||||
let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() };
|
||||
if value == 0xFFFF_FFFF {
|
||||
Err(Error::InvalidPermissions)
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the random 64bit chipid from rows 0x0-0x3.
|
||||
pub fn get_chipid() -> Result<u64, Error> {
|
||||
let w0 = read_ecc_word(0x000)?.to_be_bytes();
|
||||
let w1 = read_ecc_word(0x001)?.to_be_bytes();
|
||||
let w2 = read_ecc_word(0x002)?.to_be_bytes();
|
||||
let w3 = read_ecc_word(0x003)?.to_be_bytes();
|
||||
|
||||
Ok(u64::from_be_bytes([
|
||||
w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1],
|
||||
]))
|
||||
}
|
||||
|
||||
/// Get the 128bit private random number from rows 0x4-0xb.
|
||||
///
|
||||
/// This ID is not exposed through the USB PICOBOOT GET_INFO command
|
||||
/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP
|
||||
/// access point can read the entirety of page 0, so this value is not
|
||||
/// meaningfully private unless the USB PICOBOOT interface is disabled via the
|
||||
//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0
|
||||
pub fn get_private_random_number() -> Result<u128, Error> {
|
||||
let w0 = read_ecc_word(0x004)?.to_be_bytes();
|
||||
let w1 = read_ecc_word(0x005)?.to_be_bytes();
|
||||
let w2 = read_ecc_word(0x006)?.to_be_bytes();
|
||||
let w3 = read_ecc_word(0x007)?.to_be_bytes();
|
||||
let w4 = read_ecc_word(0x008)?.to_be_bytes();
|
||||
let w5 = read_ecc_word(0x009)?.to_be_bytes();
|
||||
let w6 = read_ecc_word(0x00a)?.to_be_bytes();
|
||||
let w7 = read_ecc_word(0x00b)?.to_be_bytes();
|
||||
|
||||
Ok(u128::from_be_bytes([
|
||||
w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1],
|
||||
]))
|
||||
}
|
@ -5,19 +5,16 @@ use core::pin::Pin as FuturePin;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use atomic_polyfill::{AtomicU32, AtomicU8};
|
||||
use atomic_polyfill::{AtomicU64, AtomicU8};
|
||||
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use fixed::types::extra::U8;
|
||||
use fixed::FixedU32;
|
||||
use pac::io::vals::Gpio0ctrlFuncsel;
|
||||
use pac::pio::vals::SmExecctrlStatusSel;
|
||||
use pio::{Program, SideSet, Wrap};
|
||||
|
||||
use crate::dma::{Channel, Transfer, Word};
|
||||
use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate};
|
||||
use crate::interrupt::typelevel::{Binding, Handler, Interrupt};
|
||||
use crate::pac::dma::vals::TreqSel;
|
||||
use crate::relocate::RelocatedProgram;
|
||||
use crate::{pac, peripherals, RegExt};
|
||||
|
||||
@ -355,11 +352,14 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> {
|
||||
let p = ch.regs();
|
||||
p.write_addr().write_value(data.as_ptr() as u32);
|
||||
p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32);
|
||||
p.trans_count().write_value(data.len() as u32);
|
||||
#[cfg(feature = "rp2040")]
|
||||
p.trans_count().write(|w| *w = data.len() as u32);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
p.trans_count().write(|w| w.set_count(data.len() as u32));
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
p.ctrl_trig().write(|w| {
|
||||
// Set RX DREQ for this statemachine
|
||||
w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8 + 4));
|
||||
w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8 + 4));
|
||||
w.set_data_size(W::size());
|
||||
w.set_chain_to(ch.number());
|
||||
w.set_incr_read(false);
|
||||
@ -437,11 +437,14 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
|
||||
let p = ch.regs();
|
||||
p.read_addr().write_value(data.as_ptr() as u32);
|
||||
p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32);
|
||||
p.trans_count().write_value(data.len() as u32);
|
||||
#[cfg(feature = "rp2040")]
|
||||
p.trans_count().write(|w| *w = data.len() as u32);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
p.trans_count().write(|w| w.set_count(data.len() as u32));
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
p.ctrl_trig().write(|w| {
|
||||
// Set TX DREQ for this statemachine
|
||||
w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8));
|
||||
w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8));
|
||||
w.set_data_size(W::size());
|
||||
w.set_chain_to(ch.number());
|
||||
w.set_incr_read(true);
|
||||
@ -523,6 +526,39 @@ pub struct PinConfig {
|
||||
pub out_base: u8,
|
||||
}
|
||||
|
||||
/// Comparison level or IRQ index for the MOV x, STATUS instruction.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pub enum StatusN {
|
||||
/// IRQ flag in this PIO block
|
||||
This(u8),
|
||||
/// IRQ flag in the next lower PIO block
|
||||
Lower(u8),
|
||||
/// IRQ flag in the next higher PIO block
|
||||
Higher(u8),
|
||||
}
|
||||
|
||||
#[cfg(feature = "_rp235x")]
|
||||
impl Default for StatusN {
|
||||
fn default() -> Self {
|
||||
Self::This(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "_rp235x")]
|
||||
impl Into<crate::pac::pio::vals::ExecctrlStatusN> for StatusN {
|
||||
fn into(self) -> crate::pac::pio::vals::ExecctrlStatusN {
|
||||
let x = match self {
|
||||
StatusN::This(n) => n,
|
||||
StatusN::Lower(n) => n + 0x08,
|
||||
StatusN::Higher(n) => n + 0x10,
|
||||
};
|
||||
|
||||
crate::pac::pio::vals::ExecctrlStatusN(x)
|
||||
}
|
||||
}
|
||||
|
||||
/// PIO config.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Config<'d, PIO: Instance> {
|
||||
@ -537,7 +573,12 @@ pub struct Config<'d, PIO: Instance> {
|
||||
/// Which source to use for checking status.
|
||||
pub status_sel: StatusSource,
|
||||
/// Status comparison level.
|
||||
#[cfg(feature = "rp2040")]
|
||||
pub status_n: u8,
|
||||
// This cfg probably shouldn't be required, but the SVD for the 2040 doesn't have the enum
|
||||
#[cfg(feature = "_rp235x")]
|
||||
/// Status comparison level.
|
||||
pub status_n: StatusN,
|
||||
exec: ExecConfig,
|
||||
origin: Option<u8>,
|
||||
/// Configure FIFO allocation.
|
||||
@ -653,7 +694,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
|
||||
assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536");
|
||||
assert!(config.clock_divider >= 1, "clkdiv must be >= 1");
|
||||
assert!(config.out_en_sel < 32, "out_en_sel must be < 32");
|
||||
assert!(config.status_n < 32, "status_n must be < 32");
|
||||
//assert!(config.status_n < 32, "status_n must be < 32");
|
||||
// sm expects 0 for 32, truncation makes that happen
|
||||
assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32");
|
||||
assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32");
|
||||
@ -668,11 +709,17 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
|
||||
w.set_out_sticky(config.out_sticky);
|
||||
w.set_wrap_top(config.exec.wrap_top);
|
||||
w.set_wrap_bottom(config.exec.wrap_bottom);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
w.set_status_sel(match config.status_sel {
|
||||
StatusSource::TxFifoLevel => SmExecctrlStatusSel::TXLEVEL,
|
||||
StatusSource::RxFifoLevel => SmExecctrlStatusSel::RXLEVEL,
|
||||
StatusSource::TxFifoLevel => pac::pio::vals::ExecctrlStatusSel::TXLEVEL,
|
||||
StatusSource::RxFifoLevel => pac::pio::vals::ExecctrlStatusSel::RXLEVEL,
|
||||
});
|
||||
w.set_status_n(config.status_n);
|
||||
#[cfg(feature = "rp2040")]
|
||||
w.set_status_sel(match config.status_sel {
|
||||
StatusSource::TxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::TXLEVEL,
|
||||
StatusSource::RxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::RXLEVEL,
|
||||
});
|
||||
w.set_status_n(config.status_n.into());
|
||||
});
|
||||
sm.shiftctrl().write(|w| {
|
||||
w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly);
|
||||
@ -684,6 +731,8 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
|
||||
w.set_autopull(config.shift_out.auto_fill);
|
||||
w.set_autopush(config.shift_in.auto_fill);
|
||||
});
|
||||
|
||||
#[cfg(feature = "rp2040")]
|
||||
sm.pinctrl().write(|w| {
|
||||
w.set_sideset_count(config.pins.sideset_count);
|
||||
w.set_set_count(config.pins.set_count);
|
||||
@ -693,6 +742,52 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
|
||||
w.set_set_base(config.pins.set_base);
|
||||
w.set_out_base(config.pins.out_base);
|
||||
});
|
||||
|
||||
#[cfg(feature = "_rp235x")]
|
||||
{
|
||||
let mut low_ok = true;
|
||||
let mut high_ok = true;
|
||||
|
||||
let in_pins = config.pins.in_base..config.pins.in_base + config.in_count;
|
||||
let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count;
|
||||
let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count;
|
||||
let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count;
|
||||
|
||||
for pin_range in [in_pins, side_pins, set_pins, out_pins] {
|
||||
for pin in pin_range {
|
||||
low_ok &= pin < 32;
|
||||
high_ok &= pin >= 16;
|
||||
}
|
||||
}
|
||||
|
||||
if !low_ok && !high_ok {
|
||||
panic!(
|
||||
"All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}",
|
||||
config.pins.in_base,
|
||||
config.pins.in_base + config.in_count - 1,
|
||||
config.pins.sideset_base,
|
||||
config.pins.sideset_base + config.pins.sideset_count - 1,
|
||||
config.pins.set_base,
|
||||
config.pins.set_base + config.pins.set_count - 1,
|
||||
config.pins.out_base,
|
||||
config.pins.out_base + config.pins.out_count - 1,
|
||||
)
|
||||
}
|
||||
let shift = if low_ok { 0 } else { 16 };
|
||||
|
||||
sm.pinctrl().write(|w| {
|
||||
w.set_sideset_count(config.pins.sideset_count);
|
||||
w.set_set_count(config.pins.set_count);
|
||||
w.set_out_count(config.pins.out_count);
|
||||
w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default());
|
||||
w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default());
|
||||
w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default());
|
||||
w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default());
|
||||
});
|
||||
|
||||
PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16));
|
||||
}
|
||||
|
||||
if let Some(origin) = config.origin {
|
||||
unsafe { instr::exec_jmp(self, origin) }
|
||||
}
|
||||
@ -959,6 +1054,10 @@ impl<'d, PIO: Instance> Common<'d, PIO> {
|
||||
pub fn make_pio_pin(&mut self, pin: impl Peripheral<P = impl PioPin + 'd> + 'd) -> Pin<'d, PIO> {
|
||||
into_ref!(pin);
|
||||
pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _));
|
||||
#[cfg(feature = "_rp235x")]
|
||||
pin.pad_ctrl().modify(|w| {
|
||||
w.set_iso(false);
|
||||
});
|
||||
// we can be relaxed about this because we're &mut here and nothing is cached
|
||||
PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed);
|
||||
Pin {
|
||||
@ -1140,16 +1239,15 @@ impl<'d, PIO: Instance> Pio<'d, PIO> {
|
||||
// other way.
|
||||
pub struct State {
|
||||
users: AtomicU8,
|
||||
used_pins: AtomicU32,
|
||||
used_pins: AtomicU64,
|
||||
}
|
||||
|
||||
fn on_pio_drop<PIO: Instance>() {
|
||||
let state = PIO::state();
|
||||
if state.users.fetch_sub(1, Ordering::AcqRel) == 1 {
|
||||
let used_pins = state.used_pins.load(Ordering::Relaxed);
|
||||
let null = Gpio0ctrlFuncsel::NULL as _;
|
||||
// we only have 30 pins. don't test the other two since gpio() asserts.
|
||||
for i in 0..30 {
|
||||
let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _;
|
||||
for i in 0..crate::gpio::BANK0_PIN_COUNT {
|
||||
if used_pins & (1 << i) != 0 {
|
||||
pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null));
|
||||
}
|
||||
@ -1174,7 +1272,7 @@ trait SealedInstance {
|
||||
fn state() -> &'static State {
|
||||
static STATE: State = State {
|
||||
users: AtomicU8::new(0),
|
||||
used_pins: AtomicU32::new(0),
|
||||
used_pins: AtomicU64::new(0),
|
||||
};
|
||||
|
||||
&STATE
|
||||
@ -1203,6 +1301,8 @@ macro_rules! impl_pio {
|
||||
|
||||
impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0);
|
||||
impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
impl_pio!(PIO2, 2, PIO2, PIO2_0, PIO2_IRQ_0);
|
||||
|
||||
/// PIO pin.
|
||||
pub trait PioPin: gpio::Pin {}
|
||||
@ -1247,3 +1347,25 @@ impl_pio_pin! {
|
||||
PIN_28,
|
||||
PIN_29,
|
||||
}
|
||||
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pio_pin! {
|
||||
PIN_30,
|
||||
PIN_31,
|
||||
PIN_32,
|
||||
PIN_33,
|
||||
PIN_34,
|
||||
PIN_35,
|
||||
PIN_36,
|
||||
PIN_37,
|
||||
PIN_38,
|
||||
PIN_39,
|
||||
PIN_40,
|
||||
PIN_41,
|
||||
PIN_42,
|
||||
PIN_43,
|
||||
PIN_44,
|
||||
PIN_45,
|
||||
PIN_46,
|
||||
PIN_47,
|
||||
}
|
||||
|
@ -110,6 +110,8 @@ impl<'d> Pwm<'d> {
|
||||
if let Some(pin) = &b {
|
||||
pin.gpio().ctrl().write(|w| w.set_funcsel(4));
|
||||
pin.pad_ctrl().modify(|w| {
|
||||
#[cfg(feature = "_rp235x")]
|
||||
w.set_iso(false);
|
||||
w.set_pue(b_pull == Pull::Up);
|
||||
w.set_pde(b_pull == Pull::Down);
|
||||
});
|
||||
@ -363,6 +365,15 @@ slice!(PWM_SLICE5, 5);
|
||||
slice!(PWM_SLICE6, 6);
|
||||
slice!(PWM_SLICE7, 7);
|
||||
|
||||
#[cfg(feature = "_rp235x")]
|
||||
slice!(PWM_SLICE8, 8);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
slice!(PWM_SLICE9, 9);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
slice!(PWM_SLICE10, 10);
|
||||
#[cfg(feature = "_rp235x")]
|
||||
slice!(PWM_SLICE11, 11);
|
||||
|
||||
/// PWM Channel A.
|
||||
pub trait ChannelAPin<T: Slice>: GpioPin {}
|
||||
/// PWM Channel B.
|
||||
@ -404,3 +415,39 @@ impl_pin!(PIN_26, PWM_SLICE5, ChannelAPin);
|
||||
impl_pin!(PIN_27, PWM_SLICE5, ChannelBPin);
|
||||
impl_pin!(PIN_28, PWM_SLICE6, ChannelAPin);
|
||||
impl_pin!(PIN_29, PWM_SLICE6, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_30, PWM_SLICE7, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_31, PWM_SLICE7, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_32, PWM_SLICE8, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_33, PWM_SLICE8, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_34, PWM_SLICE9, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_35, PWM_SLICE9, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_36, PWM_SLICE10, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_37, PWM_SLICE10, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_38, PWM_SLICE11, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_39, PWM_SLICE11, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_40, PWM_SLICE8, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_41, PWM_SLICE8, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_42, PWM_SLICE9, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_43, PWM_SLICE9, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_44, PWM_SLICE10, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_45, PWM_SLICE10, ChannelBPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_46, PWM_SLICE11, ChannelAPin);
|
||||
#[cfg(feature = "rp235xb")]
|
||||
impl_pin!(PIN_47, PWM_SLICE11, ChannelBPin);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user