Merge remote-tracking branch 'origin/master' into feature-i2-slave-r2

This commit is contained in:
anton smeenk 2024-09-19 11:26:45 +02:00
commit aee8b9b1f8
378 changed files with 16865 additions and 1878 deletions

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

@ -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
View File

@ -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

Binary file not shown.

Binary file not shown.

BIN
cyw43-firmware/43439A0_clm.bin Executable file → Normal file

Binary file not shown.

View File

@ -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
View 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

View File

@ -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"]

View File

@ -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
View 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

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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,49 +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
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(20, 0, 1).await; // set_infra = 1
self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0)
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
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;
/// 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
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),
};
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
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 _,
@ -260,37 +365,13 @@ impl<'a> Control<'a> {
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")
}

View File

@ -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();

View File

@ -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(),
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)
}
/// 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();
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) }
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)
}

View File

@ -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;
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,

View File

@ -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
View 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
}

View File

@ -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"

View File

@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
cortex-m = "0.7"
cortex-m-rt = "0.7"
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] }
embassy-executor = { version = "0.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"

View File

@ -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 youre 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 were working on. Follow the instructions commented in that file to get rust-analyzer working correctly.

View File

@ -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]

View File

@ -2,6 +2,8 @@
Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]!
* link:https://github.com/1-rafael-1/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

View File

@ -362,3 +362,13 @@ 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]
== 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

View File

@ -1,6 +1,6 @@
= Starting a new project
Once youve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project.
Once youve successfully xref:#_getting_started[run some example projects], the next step is to make a standalone Embassy project.
== Tools for generating Embassy projects

View File

@ -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

View File

@ -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]

View File

@ -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::*;

View File

@ -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

View File

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

View File

@ -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"

View File

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

View File

@ -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"

View File

@ -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 = []

View File

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

View File

@ -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.

View File

@ -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.

View File

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

View 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

View File

@ -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",
] }

View File

@ -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"

View File

@ -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.

View File

@ -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"]

View File

@ -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();

View File

@ -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)]

View File

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

View File

@ -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)),
})
};
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));
}
}
match item {
Some((idx, res)) => Poll::Ready((res, idx)),
None => Poll::Pending,
}
Poll::Pending
}
}

View File

@ -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."

View File

@ -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();

View File

@ -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"

View File

@ -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.

View File

@ -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

View File

@ -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."

View File

@ -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 }

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
#![allow(unused)]
use heapless::{String, Vec};
/// internal supporting structures for CtrlMsg

View 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"]

View 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.

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

View 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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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 }

View File

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

View File

@ -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.

View File

@ -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
}

View File

@ -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")]

View File

@ -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.
@ -414,43 +445,48 @@ impl<D: Driver> Stack<D> {
/// ```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,
/// // 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
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[..]);
self.config_waker.wake();
.update_servers(&dns_servers[..count]);
}
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
s.waker.register(cx.waker());
self.state_waker.wake();
}
let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address());
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(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!()
}
}

View File

@ -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) };
let handle = s.sockets.add(raw::Socket::new(
i.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();
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
}

View File

@ -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();
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) };
let handle = s.sockets.add(tcp::Socket::new(
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();
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) },

View File

@ -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) };
let handle = s.sockets.add(udp::Socket::new(
i.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();
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
}

View File

@ -7,24 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
## 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
- 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.1.0 - 2024-01-12

View File

@ -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"] }

View File

@ -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>,

View File

@ -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(())
}
}

View File

@ -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.

View File

@ -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
View 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

View File

@ -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
View 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
View 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.

View File

@ -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.

View File

@ -4,6 +4,7 @@ use std::io::Write;
use std::path::PathBuf;
fn main() {
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");
@ -15,3 +16,4 @@ fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=link-rp.x.in");
}
}

View File

@ -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 pins
@ -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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -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!(

View File

@ -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 {
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 {
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> {
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")]

View File

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

View File

@ -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.

View File

@ -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
View 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],
]))
}

View File

@ -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,
}

View File

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

View File

@ -2,7 +2,7 @@ pub use pac::resets::regs::Peripherals;
use crate::pac;
pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ffffff);
pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ff_ffff);
pub(crate) fn reset(peris: Peripherals) {
pac::RESETS.reset().write_value(peris);

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