Merge branch 'main' into can

This commit is contained in:
xoviat 2023-05-30 21:15:26 -05:00 committed by GitHub
commit 35083b262b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
158 changed files with 7609 additions and 2303 deletions

16
.github/ci/build-stable.sh vendored Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
## on push branch~=gh-readonly-queue/main/.*
## on pull_request
set -euo pipefail
export RUSTUP_HOME=/ci/cache/rustup
export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
./ci_stable.sh

19
.github/ci/build.sh vendored Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
## on push branch~=gh-readonly-queue/main/.*
## on pull_request
set -euo pipefail
export RUSTUP_HOME=/ci/cache/rustup
export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
if [ -f /ci/secrets/teleprobe-token.txt ]; then
echo Got teleprobe token!
export TELEPROBE_HOST=https://teleprobe.embassy.dev
export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt)
fi
hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json
./ci.sh

30
.github/ci/test.sh vendored Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
## on push branch~=gh-readonly-queue/main/.*
## on pull_request
set -euo pipefail
export RUSTUP_HOME=/ci/cache/rustup
export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
hashtime restore /ci/cache/filetime.json || true
hashtime save /ci/cache/filetime.json
cargo test --manifest-path ./embassy-sync/Cargo.toml
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
cargo test --manifest-path ./embassy-hal-common/Cargo.toml
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty
cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1,gpiote
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features nightly,time-driver
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti

View File

@ -2,7 +2,7 @@ name: Docs
on:
push:
branches: [master]
branches: [main]
env:
BUILDER_THREADS: '1'

View File

@ -1,80 +0,0 @@
name: Rust
on:
push:
branches: [staging, trying, master]
pull_request:
branches: [master]
env:
CARGO_TERM_COLOR: always
jobs:
all:
runs-on: ubuntu-latest
needs: [build-nightly, build-stable, test]
steps:
- name: Done
run: exit 0
build-nightly:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Cache multiple paths
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target_ci
key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
- name: build
env:
TELEPROBE_TOKEN: ${{ secrets.TELEPROBE_TOKEN }}
run: |
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
chmod +x /usr/local/bin/cargo-batch
./ci.sh
rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
build-stable:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Cache multiple paths
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target_ci_stable
key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
- name: build
run: |
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
chmod +x /usr/local/bin/cargo-batch
./ci_stable.sh
rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Test boot
working-directory: ./embassy-boot/boot
run: cargo test && cargo test --features nightly && cargo test --features "ed25519-dalek,nightly" && cargo test --features "ed25519-salty,nightly"
- name: Test sync
working-directory: ./embassy-sync
run: cargo test

1
.vscode/.gitignore vendored
View File

@ -1,3 +1,4 @@
*.cortex-debug.*.json
launch.json
tasks.json
*.cfg

View File

@ -99,13 +99,6 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer
### Running examples
- Setup git submodules (needed for STM32 examples)
```bash
git submodule init
git submodule update
```
- Install `probe-rs-cli` with defmt support.
```bash
@ -123,7 +116,7 @@ cd examples/nrf52840
For example:
```bash
cargo run --bin blinky
cargo run --release --bin blinky
```
## Developing Embassy with Rust Analyzer based editors

55
ci.sh
View File

@ -2,10 +2,13 @@
set -euo pipefail
export CARGO_TARGET_DIR=$PWD/target_ci
export RUSTFLAGS=-Dwarnings
export DEFMT_LOG=trace
# needed by wifi examples
export WIFI_NETWORK=x
export WIFI_PASSWORD=x
TARGET=$(rustc -vV | sed -n 's|host: ||p')
BUILD_EXTRA=""
@ -13,7 +16,7 @@ if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then
BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std"
fi
find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2018
find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2021
cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \
@ -83,6 +86,13 @@ cargo batch \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \
--- 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 embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
@ -128,45 +138,24 @@ cargo batch \
--- 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/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/bluepill-stm32f103c8 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/nucleo-stm32f429zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/nucleo-stm32g491re \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/nucleo-stm32g071rb \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/nucleo-stm32c031c6 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/nucleo-stm32h755zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/nucleo-stm32h563zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \
--- 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 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
$BUILD_EXTRA
function run_elf {
echo Running target=$1 elf=$2
STATUSCODE=$(
curl \
-sS \
--output /dev/stderr \
--write-out "%{http_code}" \
-H "Authorization: Bearer $TELEPROBE_TOKEN" \
https://teleprobe.embassy.dev/targets/$1/run --data-binary @$2
)
echo
echo HTTP Status code: $STATUSCODE
test "$STATUSCODE" -eq 200
}
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
echo No teleprobe token found, skipping running HIL tests
exit
fi
for board in $(ls out/tests); do
echo Running tests for board: $board
for elf in $(ls out/tests/$board); do
run_elf $board out/tests/$board/$elf
done
done
teleprobe client run -r out/tests

View File

@ -2,12 +2,9 @@
set -euo pipefail
export CARGO_TARGET_DIR=$PWD/target_ci_stable
export RUSTFLAGS=-Dwarnings
export DEFMT_LOG=trace
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
cargo batch \
--- 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 \

BIN
cyw43-firmware/43439A0.bin Executable file

Binary file not shown.

BIN
cyw43-firmware/43439A0_clm.bin Executable file

Binary file not shown.

View File

@ -0,0 +1,49 @@
Permissive Binary License
Version 1.0, July 2019
Redistribution. Redistribution and use in binary form, without
modification, are permitted provided that the following conditions are
met:
1) Redistributions must reproduce the above copyright notice and the
following disclaimer in the documentation and/or other materials
provided with the distribution.
2) Unless to the extent explicitly permitted by law, no reverse
engineering, decompilation, or disassembly of this software is
permitted.
3) Redistribution as part of a software development kit must include the
accompanying file named <20>DEPENDENCIES<45> and any dependencies listed in
that file.
4) Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
Limited patent license. The copyright holders (and contributors) grant a
worldwide, non-exclusive, no-charge, royalty-free patent license to
make, have made, use, offer to sell, sell, import, and otherwise
transfer this software, where such license applies only to those patent
claims licensable by the copyright holders (and contributors) that are
necessarily infringed by this software. This patent license shall not
apply to any combinations that include this software. No hardware is
licensed hereunder.
If you institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the software
itself infringes your patent(s), then your rights granted under this
license shall terminate as of the date such litigation is filed.
DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

5
cyw43-firmware/README.md Normal file
View File

@ -0,0 +1,5 @@
# WiFi firmware
Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)

17
cyw43-pio/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "cyw43-pio"
version = "0.1.0"
edition = "2021"
[features]
# If disabled, SPI runs at 31.25MHz
# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet.
overclock = []
[dependencies]
cyw43 = { version = "0.1.0", path = "../cyw43" }
embassy-rp = { version = "0.1.0", path = "../embassy-rp" }
pio-proc = "0.2"
pio = "0.2.1"
fixed = "1.23.1"
defmt = { version = "0.3", optional = true }

229
cyw43-pio/src/lib.rs Normal file
View File

@ -0,0 +1,229 @@
#![no_std]
#![allow(incomplete_features)]
#![feature(async_fn_in_trait)]
use core::slice;
use cyw43::SpiBusCyw43;
use embassy_rp::dma::Channel;
use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate};
use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef};
use fixed::FixedU32;
use pio_proc::pio_asm;
pub struct PioSpi<'d, CS: Pin, PIO: Instance, const SM: usize, DMA> {
cs: Output<'d, CS>,
sm: StateMachine<'d, PIO, SM>,
irq: Irq<'d, PIO, 0>,
dma: PeripheralRef<'d, DMA>,
wrap_target: u8,
}
impl<'d, CS, PIO, const SM: usize, DMA> PioSpi<'d, CS, PIO, SM, DMA>
where
DMA: Channel,
CS: Pin,
PIO: Instance,
{
pub fn new<DIO, CLK>(
common: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
irq: Irq<'d, PIO, 0>,
cs: Output<'d, CS>,
dio: DIO,
clk: CLK,
dma: impl Peripheral<P = DMA> + 'd,
) -> Self
where
DIO: PioPin,
CLK: PioPin,
{
#[cfg(feature = "overclock")]
let program = pio_asm!(
".side_set 1"
".wrap_target"
// write out x-1 bits
"lp:"
"out pins, 1 side 0"
"jmp x-- lp side 1"
// switch directions
"set pindirs, 0 side 0"
"nop side 1" // necessary for clkdiv=1.
"nop side 0"
// read in y-1 bits
"lp2:"
"in pins, 1 side 1"
"jmp y-- lp2 side 0"
// wait for event and irq host
"wait 1 pin 0 side 0"
"irq 0 side 0"
".wrap"
);
#[cfg(not(feature = "overclock"))]
let program = pio_asm!(
".side_set 1"
".wrap_target"
// write out x-1 bits
"lp:"
"out pins, 1 side 0"
"jmp x-- lp side 1"
// switch directions
"set pindirs, 0 side 0"
"nop side 0"
// read in y-1 bits
"lp2:"
"in pins, 1 side 1"
"jmp y-- lp2 side 0"
// wait for event and irq host
"wait 1 pin 0 side 0"
"irq 0 side 0"
".wrap"
);
let relocated = RelocatedProgram::new(&program.program);
let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);
pin_io.set_pull(Pull::None);
pin_io.set_schmitt(true);
pin_io.set_input_sync_bypass(true);
pin_io.set_drive_strength(Drive::_12mA);
pin_io.set_slew_rate(SlewRate::Fast);
let mut pin_clk = common.make_pio_pin(clk);
pin_clk.set_drive_strength(Drive::_12mA);
pin_clk.set_slew_rate(SlewRate::Fast);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&relocated), &[&pin_clk]);
cfg.set_out_pins(&[&pin_io]);
cfg.set_in_pins(&[&pin_io]);
cfg.set_set_pins(&[&pin_io]);
cfg.shift_out.direction = ShiftDirection::Left;
cfg.shift_out.auto_fill = true;
//cfg.shift_out.threshold = 32;
cfg.shift_in.direction = ShiftDirection::Left;
cfg.shift_in.auto_fill = true;
//cfg.shift_in.threshold = 32;
#[cfg(feature = "overclock")]
{
// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to
// data sheet, but seems to work fine.
cfg.clock_divider = FixedU32::from_bits(0x0100);
}
#[cfg(not(feature = "overclock"))]
{
// same speed as pico-sdk, 62.5Mhz
// This is actually the fastest we can go without overclocking.
// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq.
// However, the PIO uses a fractional divider, which works by introducing jitter when
// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz
// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles
// violate the maximum from the data sheet.
cfg.clock_divider = FixedU32::from_bits(0x0200);
}
sm.set_config(&cfg);
sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]);
sm.set_pins(Level::Low, &[&pin_clk, &pin_io]);
Self {
cs,
sm,
irq,
dma: dma.into_ref(),
wrap_target: relocated.wrap().target,
}
}
pub async fn write(&mut self, write: &[u32]) -> u32 {
self.sm.set_enable(false);
let write_bits = write.len() * 32 - 1;
let read_bits = 31;
#[cfg(feature = "defmt")]
defmt::trace!("write={} read={}", write_bits, read_bits);
unsafe {
pio_instr_util::set_x(&mut self.sm, write_bits as u32);
pio_instr_util::set_y(&mut self.sm, read_bits as u32);
pio_instr_util::set_pindir(&mut self.sm, 0b1);
pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target);
}
self.sm.set_enable(true);
self.sm.tx().dma_push(self.dma.reborrow(), write).await;
let mut status = 0;
self.sm
.rx()
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
.await;
status
}
pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 {
self.sm.set_enable(false);
let write_bits = 31;
let read_bits = read.len() * 32 + 32 - 1;
#[cfg(feature = "defmt")]
defmt::trace!("write={} read={}", write_bits, read_bits);
unsafe {
pio_instr_util::set_y(&mut self.sm, read_bits as u32);
pio_instr_util::set_x(&mut self.sm, write_bits as u32);
pio_instr_util::set_pindir(&mut self.sm, 0b1);
pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target);
}
// self.cs.set_low();
self.sm.set_enable(true);
self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await;
self.sm.rx().dma_pull(self.dma.reborrow(), read).await;
let mut status = 0;
self.sm
.rx()
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
.await;
status
}
}
impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA>
where
CS: Pin,
PIO: Instance,
DMA: Channel,
{
async fn cmd_write(&mut self, write: &[u32]) -> u32 {
self.cs.set_low();
let status = self.write(write).await;
self.cs.set_high();
status
}
async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 {
self.cs.set_low();
let status = self.cmd_read(write, read).await;
self.cs.set_high();
status
}
async fn wait_for_event(&mut self) {
self.irq.wait().await;
}
}

28
cyw43/Cargo.toml Normal file
View File

@ -0,0 +1,28 @@
[package]
name = "cyw43"
version = "0.1.0"
edition = "2021"
[features]
defmt = ["dep:defmt"]
log = ["dep:log"]
# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`.
firmware-logs = []
[dependencies]
embassy-time = { version = "0.1.0", path = "../embassy-time"}
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
atomic-polyfill = "0.1.5"
defmt = { version = "0.3", optional = true }
log = { version = "0.4.17", optional = true }
cortex-m = "0.7.6"
cortex-m-rt = "0.7.0"
futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" }
num_enum = { version = "0.5.7", default-features = false }

57
cyw43/README.md Normal file
View File

@ -0,0 +1,57 @@
# cyw43
WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
## Current status
Working:
- Station mode (joining an AP).
- AP mode (creating an AP)
- Scanning
- Sending and receiving Ethernet frames.
- Using the default MAC address.
- [`embassy-net`](https://embassy.dev) integration.
- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W.
- Using IRQ for device events
- GPIO support (for LED on the Pico W)
TODO:
- Setting a custom MAC address.
- Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?)
## Running the examples
- `cargo install probe-rs-cli`
- `cd examples/rpi-pico-w`
### Example 1: Scan the wifi stations
- `cargo run --release --bin wifi_scan`
### Example 2: Create an access point (IP and credentials in the code)
- `cargo run --release --bin tcp_server_ap`
### Example 3: Connect to an existing network and create a server
- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release`
After a few seconds, you should see that DHCP picks up an IP address like this
```
11.944489 DEBUG Acquired IP configuration:
11.944517 DEBUG IP address: 192.168.0.250/24
11.944620 DEBUG Default gateway: 192.168.0.33
11.944722 DEBUG DNS server 0: 192.168.0.33
```
This example implements a TCP echo server on port 1234. You can try connecting to it with:
```
nc 192.168.0.250 1234
```
Send it some data, you should see it echoed back and printed in the firmware's logs.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

328
cyw43/src/bus.rs Normal file
View File

@ -0,0 +1,328 @@
use embassy_futures::yield_now;
use embassy_time::{Duration, Timer};
use embedded_hal_1::digital::OutputPin;
use futures::FutureExt;
use crate::consts::*;
use crate::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.
pub trait SpiBusCyw43 {
/// Issues a write command on the bus
/// First 32 bits of `word` are expected to be a cmd word
async fn cmd_write(&mut self, write: &[u32]) -> u32;
/// Issues a read command on the bus
/// `write` is expected to be a 32 bit cmd word
/// `read` will contain the response of the device
/// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`.
/// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long.
async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32;
/// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high.
/// The default implementation always reports ready, resulting in active polling of the device.
async fn wait_for_event(&mut self) {
yield_now().await;
}
}
pub(crate) struct Bus<PWR, SPI> {
backplane_window: u32,
pwr: PWR,
spi: SPI,
status: u32,
}
impl<PWR, SPI> Bus<PWR, SPI>
where
PWR: OutputPin,
SPI: SpiBusCyw43,
{
pub(crate) fn new(pwr: PWR, spi: SPI) -> Self {
Self {
backplane_window: 0xAAAA_AAAA,
pwr,
spi,
status: 0,
}
}
pub async fn init(&mut self) {
// Reset
self.pwr.set_low().unwrap();
Timer::after(Duration::from_millis(20)).await;
self.pwr.set_high().unwrap();
Timer::after(Duration::from_millis(250)).await;
while self
.read32_swapped(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!("{:#x}", val);
assert_eq!(val, TEST_PATTERN);
let val = self.read32_swapped(REG_BUS_CTRL).await;
trace!("{:#010b}", (val & 0xff));
// 32-bit word length, little endian (which is the default endianess).
self.write32_swapped(
REG_BUS_CTRL,
WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS,
)
.await;
let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await;
trace!("{:#b}", val);
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await;
trace!("{:#x}", val);
assert_eq!(val, FEEDBEAD);
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await;
trace!("{:#x}", val);
assert_eq!(val, TEST_PATTERN);
}
pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) {
let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8);
let len_in_u32 = (len_in_u8 as usize + 3) / 4;
self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await;
}
pub async fn wlan_write(&mut self, buf: &[u32]) {
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4);
//TODO try to remove copy?
let mut cmd_buf = [0_u32; 513];
cmd_buf[0] = cmd;
cmd_buf[1..][..buf.len()].copy_from_slice(buf);
self.status = self.spi.cmd_write(&cmd_buf).await;
}
#[allow(unused)]
pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) {
// It seems the HW force-aligns the addr
// to 2 if data.len() >= 2
// to 4 if data.len() >= 4
// To simplify, enforce 4-align for now.
assert!(addr % 4 == 0);
// Backplane read buffer has one extra word for the response delay.
let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1];
while !data.is_empty() {
// Ensure transfer doesn't cross a window boundary.
let window_offs = addr & BACKPLANE_ADDRESS_MASK;
let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize;
let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining);
self.backplane_set_window(addr).await;
let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32);
// round `buf` to word boundary, add one extra word for the response delay
self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await;
// when writing out the data, we skip the response-delay byte
data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]);
// Advance ptr.
addr += len as u32;
data = &mut data[len..];
}
}
pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) {
// It seems the HW force-aligns the addr
// to 2 if data.len() >= 2
// to 4 if data.len() >= 4
// To simplify, enforce 4-align for now.
assert!(addr % 4 == 0);
let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1];
while !data.is_empty() {
// Ensure transfer doesn't cross a window boundary.
let window_offs = addr & BACKPLANE_ADDRESS_MASK;
let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize;
let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining);
slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]);
self.backplane_set_window(addr).await;
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32);
buf[0] = cmd;
self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await;
// Advance ptr.
addr += len as u32;
data = &data[len..];
}
}
pub async fn bp_read8(&mut self, addr: u32) -> u8 {
self.backplane_readn(addr, 1).await as u8
}
pub async fn bp_write8(&mut self, addr: u32, val: u8) {
self.backplane_writen(addr, val as u32, 1).await
}
pub async fn bp_read16(&mut self, addr: u32) -> u16 {
self.backplane_readn(addr, 2).await as u16
}
#[allow(unused)]
pub async fn bp_write16(&mut self, addr: u32, val: u16) {
self.backplane_writen(addr, val as u32, 2).await
}
#[allow(unused)]
pub async fn bp_read32(&mut self, addr: u32) -> u32 {
self.backplane_readn(addr, 4).await
}
pub async fn bp_write32(&mut self, addr: u32, val: u32) {
self.backplane_writen(addr, val, 4).await
}
async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 {
self.backplane_set_window(addr).await;
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
if len == 4 {
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG
}
self.readn(FUNC_BACKPLANE, bus_addr, len).await
}
async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) {
self.backplane_set_window(addr).await;
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
if len == 4 {
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG
}
self.writen(FUNC_BACKPLANE, bus_addr, val, len).await
}
async fn backplane_set_window(&mut self, addr: u32) {
let new_window = addr & !BACKPLANE_ADDRESS_MASK;
if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 {
self.write8(
FUNC_BACKPLANE,
REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH,
(new_window >> 24) as u8,
)
.await;
}
if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 {
self.write8(
FUNC_BACKPLANE,
REG_BACKPLANE_BACKPLANE_ADDRESS_MID,
(new_window >> 16) as u8,
)
.await;
}
if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 {
self.write8(
FUNC_BACKPLANE,
REG_BACKPLANE_BACKPLANE_ADDRESS_LOW,
(new_window >> 8) as u8,
)
.await;
}
self.backplane_window = new_window;
}
pub async fn read8(&mut self, func: u32, addr: u32) -> u8 {
self.readn(func, addr, 1).await as u8
}
pub async fn write8(&mut self, func: u32, addr: u32, val: u8) {
self.writen(func, addr, val as u32, 1).await
}
pub async fn read16(&mut self, func: u32, addr: u32) -> u16 {
self.readn(func, addr, 2).await as u16
}
#[allow(unused)]
pub async fn write16(&mut self, func: u32, addr: u32, val: u16) {
self.writen(func, addr, val as u32, 2).await
}
pub async fn read32(&mut self, func: u32, addr: u32) -> u32 {
self.readn(func, addr, 4).await
}
#[allow(unused)]
pub async fn write32(&mut self, func: u32, addr: u32, val: u32) {
self.writen(func, addr, val, 4).await
}
async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 {
let cmd = cmd_word(READ, INC_ADDR, func, addr, len);
let mut buf = [0; 2];
// if we are reading from the backplane, we need an extra word for the response delay
let len = if func == FUNC_BACKPLANE { 2 } else { 1 };
self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await;
// if we read from the backplane, the result is in the second word, after the response delay
if func == FUNC_BACKPLANE {
buf[1]
} else {
buf[0]
}
}
async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) {
let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len);
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);
let cmd = swap16(cmd);
let mut buf = [0; 1];
self.status = self.spi.cmd_read(cmd, &mut buf).await;
swap16(buf[0])
}
async fn write32_swapped(&mut self, addr: u32, val: u32) {
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4);
let buf = [swap16(cmd), swap16(val)];
self.status = self.spi.cmd_write(&buf).await;
}
pub async fn wait_for_event(&mut self) {
self.spi.wait_for_event().await;
}
pub fn status(&self) -> u32 {
self.status
}
}
fn swap16(x: u32) -> u32 {
x.rotate_left(16)
}
fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 {
(write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF)
}

318
cyw43/src/consts.rs Normal file
View File

@ -0,0 +1,318 @@
#![allow(unused)]
pub(crate) const FUNC_BUS: u32 = 0;
pub(crate) const FUNC_BACKPLANE: u32 = 1;
pub(crate) const FUNC_WLAN: u32 = 2;
pub(crate) const FUNC_BT: u32 = 3;
pub(crate) const REG_BUS_CTRL: u32 = 0x0;
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;
pub(crate) const WORD_LENGTH_32: u32 = 0x1;
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;
// SPI_STATUS_REGISTER bits
pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001;
pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002;
pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004;
pub(crate) const STATUS_F2_INTR: u32 = 0x00000008;
pub(crate) const STATUS_F3_INTR: u32 = 0x00000010;
pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020;
pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040;
pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080;
pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100;
pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00;
pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9;
pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000;
pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000;
pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21;
pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005;
pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006;
pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007;
pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008;
pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009;
pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A;
pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B;
pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C;
pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D;
pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E;
pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F;
pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B;
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 BACKPLANE_WINDOW_SIZE: usize = 0x8000;
pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF;
pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000;
pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64;
// Active Low Power (ALP) clock constants
pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08;
pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40;
// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect
// (AI) pub (crate) constants
pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408;
pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002;
pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001;
pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020;
pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800;
pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1;
pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804;
pub(crate) const TEST_PATTERN: u32 = 0x12345678;
pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD;
// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits
pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1"
pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002;
pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004;
pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1
pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1
pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020;
pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040;
pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests
pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100;
pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200;
pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400;
pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800;
pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000;
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_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;
// CYW_SPID command structure constants.
pub(crate) const WRITE: bool = true;
pub(crate) const READ: bool = false;
pub(crate) const INC_ADDR: bool = true;
pub(crate) const FIXED_ADDR: bool = false;
pub(crate) const AES_ENABLED: u32 = 0x0004;
pub(crate) const WPA2_SECURITY: u32 = 0x00400000;
pub(crate) const MIN_PSK_LEN: usize = 8;
pub(crate) const MAX_PSK_LEN: usize = 64;
// Security type (authentication and encryption types are combined using bit mask)
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, PartialEq)]
#[repr(u32)]
pub(crate) enum Security {
OPEN = 0,
WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED,
}
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum EStatus {
/// operation was successful
SUCCESS = 0,
/// operation failed
FAIL = 1,
/// operation timed out
TIMEOUT = 2,
/// failed due to no matching network found
NO_NETWORKS = 3,
/// operation was aborted
ABORT = 4,
/// protocol failure: packet not ack'd
NO_ACK = 5,
/// AUTH or ASSOC packet was unsolicited
UNSOLICITED = 6,
/// attempt to assoc to an auto auth configuration
ATTEMPT = 7,
/// scan results are incomplete
PARTIAL = 8,
/// scan aborted by another scan
NEWSCAN = 9,
/// scan aborted due to assoc in progress
NEWASSOC = 10,
/// 802.11h quiet period started
_11HQUIET = 11,
/// user disabled scanning (WLC_SET_SCANSUPPRESS)
SUPPRESS = 12,
/// no allowable channels to scan
NOCHANS = 13,
/// scan aborted due to CCX fast roam
CCXFASTRM = 14,
/// abort channel select
CS_ABORT = 15,
}
impl PartialEq<EStatus> for u32 {
fn eq(&self, other: &EStatus) -> bool {
*self == *other as Self
}
}
#[allow(dead_code)]
pub(crate) struct FormatStatus(pub u32);
#[cfg(feature = "defmt")]
impl defmt::Format for FormatStatus {
fn format(&self, fmt: defmt::Formatter) {
macro_rules! implm {
($($name:ident),*) => {
$(
if self.0 & $name > 0 {
defmt::write!(fmt, " | {}", &stringify!($name)[7..]);
}
)*
};
}
implm!(
STATUS_DATA_NOT_AVAILABLE,
STATUS_UNDERFLOW,
STATUS_OVERFLOW,
STATUS_F2_INTR,
STATUS_F3_INTR,
STATUS_F2_RX_READY,
STATUS_F3_RX_READY,
STATUS_HOST_CMD_DATA_ERR,
STATUS_F2_PKT_AVAILABLE,
STATUS_F3_PKT_AVAILABLE
);
}
}
#[cfg(feature = "log")]
impl core::fmt::Debug for FormatStatus {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
macro_rules! implm {
($($name:ident),*) => {
$(
if self.0 & $name > 0 {
core::write!(fmt, " | {}", &stringify!($name)[7..])?;
}
)*
};
}
implm!(
STATUS_DATA_NOT_AVAILABLE,
STATUS_UNDERFLOW,
STATUS_OVERFLOW,
STATUS_F2_INTR,
STATUS_F3_INTR,
STATUS_F2_RX_READY,
STATUS_F3_RX_READY,
STATUS_HOST_CMD_DATA_ERR,
STATUS_F2_PKT_AVAILABLE,
STATUS_F3_PKT_AVAILABLE
);
Ok(())
}
}
#[cfg(feature = "log")]
impl core::fmt::Display for FormatStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self, f)
}
}
#[allow(dead_code)]
pub(crate) struct FormatInterrupt(pub u16);
#[cfg(feature = "defmt")]
impl defmt::Format for FormatInterrupt {
fn format(&self, fmt: defmt::Formatter) {
macro_rules! implm {
($($name:ident),*) => {
$(
if self.0 & $name > 0 {
defmt::write!(fmt, " | {}", &stringify!($name)[4..]);
}
)*
};
}
implm!(
IRQ_DATA_UNAVAILABLE,
IRQ_F2_F3_FIFO_RD_UNDERFLOW,
IRQ_F2_F3_FIFO_WR_OVERFLOW,
IRQ_COMMAND_ERROR,
IRQ_DATA_ERROR,
IRQ_F2_PACKET_AVAILABLE,
IRQ_F3_PACKET_AVAILABLE,
IRQ_F1_OVERFLOW,
IRQ_MISC_INTR0,
IRQ_MISC_INTR1,
IRQ_MISC_INTR2,
IRQ_MISC_INTR3,
IRQ_MISC_INTR4,
IRQ_F1_INTR,
IRQ_F2_INTR,
IRQ_F3_INTR
);
}
}
#[cfg(feature = "log")]
impl core::fmt::Debug for FormatInterrupt {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
macro_rules! implm {
($($name:ident),*) => {
$(
if self.0 & $name > 0 {
core::write!(fmt, " | {}", &stringify!($name)[7..])?;
}
)*
};
}
implm!(
IRQ_DATA_UNAVAILABLE,
IRQ_F2_F3_FIFO_RD_UNDERFLOW,
IRQ_F2_F3_FIFO_WR_OVERFLOW,
IRQ_COMMAND_ERROR,
IRQ_DATA_ERROR,
IRQ_F2_PACKET_AVAILABLE,
IRQ_F3_PACKET_AVAILABLE,
IRQ_F1_OVERFLOW,
IRQ_MISC_INTR0,
IRQ_MISC_INTR1,
IRQ_MISC_INTR2,
IRQ_MISC_INTR3,
IRQ_MISC_INTR4,
IRQ_F1_INTR,
IRQ_F2_INTR,
IRQ_F3_INTR
);
Ok(())
}
}
#[cfg(feature = "log")]
impl core::fmt::Display for FormatInterrupt {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self, f)
}
}

457
cyw43/src/control.rs Normal file
View File

@ -0,0 +1,457 @@
use core::cmp::{max, min};
use ch::driver::LinkState;
use embassy_net_driver_channel as ch;
use embassy_time::{Duration, Timer};
pub use crate::bus::SpiBusCyw43;
use crate::consts::*;
use crate::events::{Event, EventSubscriber, Events};
use crate::fmt::Bytes;
use crate::ioctl::{IoctlState, IoctlType};
use crate::structs::*;
use crate::{countries, events, PowerManagementMode};
#[derive(Debug)]
pub struct Error {
pub status: u32,
}
pub struct Control<'a> {
state_ch: ch::StateRunner<'a>,
events: &'a Events,
ioctl_state: &'a IoctlState,
}
impl<'a> Control<'a> {
pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self {
Self {
state_ch,
events: event_sub,
ioctl_state,
}
}
pub async fn init(&mut self, clm: &[u8]) {
const CHUNK_SIZE: usize = 1024;
debug!("Downloading CLM...");
let mut offs = 0;
for chunk in clm.chunks(CHUNK_SIZE) {
let mut flag = DOWNLOAD_FLAG_HANDLER_VER;
if offs == 0 {
flag |= DOWNLOAD_FLAG_BEGIN;
}
offs += chunk.len();
if offs == clm.len() {
flag |= DOWNLOAD_FLAG_END;
}
let header = DownloadHeader {
flag,
dload_type: DOWNLOAD_TYPE_CLM,
len: chunk.len() as _,
crc: 0,
};
let mut buf = [0; 8 + 12 + CHUNK_SIZE];
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()])
.await;
}
// check clmload ok
assert_eq!(self.get_iovar_u32("clmload_status").await, 0);
debug!("Configuring misc stuff...");
// Disable tx gloming which transfers multiple packets in one request.
// 'glom' is short for "conglomerate" which means "gather together into
// a compact mass".
self.set_iovar_u32("bus:txglom", 0).await;
self.set_iovar_u32("apsta", 1).await;
// read MAC addr.
let mut mac_addr = [0; 6];
assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6);
debug!("mac addr: {:02x}", Bytes(&mac_addr));
let country = countries::WORLD_WIDE_XX;
let country_info = CountryInfo {
country_abbrev: [country.code[0], country.code[1], 0, 0],
country_code: [country.code[0], country.code[1], 0, 0],
rev: if country.rev == 0 { -1 } else { country.rev as _ },
};
self.set_iovar("country", &country_info.to_bytes()).await;
// set country takes some time, next ioctls fail if we don't wait.
Timer::after(Duration::from_millis(100)).await;
// Set antenna to chip antenna
self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await;
self.set_iovar_u32("bus:txglom", 0).await;
Timer::after(Duration::from_millis(100)).await;
//self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...??
//Timer::after(Duration::from_millis(100)).await;
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
Timer::after(Duration::from_millis(100)).await;
self.set_iovar_u32("ampdu_mpdu", 4).await;
Timer::after(Duration::from_millis(100)).await;
//self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes
//Timer::after(Duration::from_millis(100)).await;
// evts
let mut evts = EventMask {
iface: 0,
events: [0xFF; 24],
};
// Disable spammy uninteresting events.
evts.unset(Event::RADIO);
evts.unset(Event::IF);
evts.unset(Event::PROBREQ_MSG);
evts.unset(Event::PROBREQ_MSG_RX);
evts.unset(Event::PROBRESP_MSG);
evts.unset(Event::PROBRESP_MSG);
evts.unset(Event::ROAM);
self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await;
Timer::after(Duration::from_millis(100)).await;
// set wifi up
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
Timer::after(Duration::from_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
Timer::after(Duration::from_millis(100)).await;
self.state_ch.set_ethernet_address(mac_addr);
debug!("INIT DONE");
}
pub async fn set_power_management(&mut self, mode: PowerManagementMode) {
// power save mode
let mode_num = mode.mode();
if mode_num == 2 {
self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await;
self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await;
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;
}
pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> {
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
self.ioctl_set_u32(134, 0, 0).await; // wsec = open
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0)
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
}
pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> 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(Duration::from_millis(100)).await;
let mut pfi = PassphraseInfo {
len: passphrase.len() as _,
flags: 1,
passphrase: [0; 64],
};
pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes());
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes())
.await; // WLC_SET_WSEC_PMK
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open)
self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth
let mut i = SsidInfo {
len: ssid.len() as _,
ssid: [0; 32],
};
i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
self.wait_for_join(i).await
}
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;
// to complete the join, we wait for a SET_SSID event
// we also save the AUTH status for the user, it may be interesting
let mut auth_status = 0;
let status = loop {
let msg = subscriber.next_message_pure().await;
if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS {
auth_status = msg.header.status;
} else if msg.header.event_type == Event::SET_SSID {
// join operation ends with SET_SSID event
break msg.header.status;
}
};
self.events.mask.disable_all();
if status == EStatus::SUCCESS {
// successful join
self.state_ch.set_link_state(LinkState::Up);
debug!("JOINED");
Ok(())
} else {
warn!("JOIN failed with status={} auth={}", status, auth_status);
Err(Error { status })
}
}
pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) {
assert!(gpio_n < 3);
self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 })
.await
}
pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) {
self.start_ap(ssid, "", Security::OPEN, channel).await;
}
pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) {
self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await;
}
async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) {
if security != Security::OPEN
&& (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN)
{
panic!("Passphrase is too short or too long");
}
// Temporarily set wifi down
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
// Turn off APSTA mode
self.set_iovar_u32("apsta", 0).await;
// Set wifi up again
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
// Turn on AP mode
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await;
// Set SSID
let mut i = SsidInfoWithIndex {
index: 0,
ssid_info: SsidInfo {
len: ssid.as_bytes().len() as _,
ssid: [0; 32],
},
};
i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes());
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;
// Set security
self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await;
if security != Security::OPEN {
self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK
Timer::after(Duration::from_millis(100)).await;
// Set passphrase
let mut pfi = PassphraseInfo {
len: passphrase.as_bytes().len() as _,
flags: 1, // WSEC_PASSPHRASE
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())
.await;
}
// Change mutlicast rate from 1 Mbps to 11 Mbps
self.set_iovar_u32("2g_mrate", 11000000 / 500000).await;
// Start AP
self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP
}
async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) {
let mut buf = [0; 8];
buf[0..4].copy_from_slice(&val1.to_le_bytes());
buf[4..8].copy_from_slice(&val2.to_le_bytes());
self.set_iovar(name, &buf).await
}
async fn set_iovar_u32(&mut self, name: &str, val: u32) {
self.set_iovar(name, &val.to_le_bytes()).await
}
async fn get_iovar_u32(&mut self, name: &str) -> u32 {
let mut buf = [0; 4];
let len = self.get_iovar(name, &mut buf).await;
assert_eq!(len, 4);
u32::from_le_bytes(buf)
}
async fn set_iovar(&mut self, name: &str, val: &[u8]) {
self.set_iovar_v::<64>(name, val).await
}
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
debug!("set {} = {:02x}", name, Bytes(val));
let mut buf = [0; BUFSIZE];
buf[..name.len()].copy_from_slice(name.as_bytes());
buf[name.len()] = 0;
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])
.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);
let mut buf = [0; 64];
buf[..name.len()].copy_from_slice(name.as_bytes());
buf[name.len()] = 0;
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])
.await;
let out_len = min(res.len(), res_len);
res[..out_len].copy_from_slice(&buf[..out_len]);
out_len
}
async fn ioctl_set_u32(&mut self, cmd: u32, 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 {
struct CancelOnDrop<'a>(&'a IoctlState);
impl CancelOnDrop<'_> {
fn defuse(self) {
core::mem::forget(self);
}
}
impl Drop for CancelOnDrop<'_> {
fn drop(&mut self) {
self.0.cancel_ioctl();
}
}
let ioctl = CancelOnDrop(self.ioctl_state);
ioctl.0.do_ioctl(kind, cmd, iface, buf).await;
let resp_len = ioctl.0.wait_complete().await;
ioctl.defuse();
resp_len
}
/// Start a wifi scan
///
/// Returns a `Stream` of networks found by the device
///
/// # Note
/// Device events are currently implemented using a bounded queue.
/// To not miss any events, you should make sure to always await the stream.
pub async fn scan(&mut self) -> Scanner<'_> {
const SCANTYPE_PASSIVE: u8 = 1;
let scan_params = ScanParams {
version: 1,
action: 1,
sync_id: 1,
ssid_len: 0,
ssid: [0; 32],
bssid: [0xff; 6],
bss_type: 2,
scan_type: SCANTYPE_PASSIVE,
nprobes: !0,
active_time: !0,
passive_time: !0,
home_time: !0,
channel_num: 0,
channel_list: [0; 1],
};
self.events.mask.enable(&[Event::ESCAN_RESULT]);
let subscriber = self.events.queue.subscriber().unwrap();
self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await;
Scanner {
subscriber,
events: &self.events,
}
}
}
pub struct Scanner<'a> {
subscriber: EventSubscriber<'a>,
events: &'a Events,
}
impl Scanner<'_> {
/// wait for the next found network
pub async fn next(&mut self) -> Option<BssInfo> {
let event = self.subscriber.next_message_pure().await;
if event.header.status != EStatus::PARTIAL {
self.events.mask.disable_all();
return None;
}
if let events::Payload::BssInfo(bss) = event.payload {
Some(bss)
} else {
None
}
}
}
impl Drop for Scanner<'_> {
fn drop(&mut self) {
self.events.mask.disable_all();
}
}

481
cyw43/src/countries.rs Normal file
View File

@ -0,0 +1,481 @@
#![allow(unused)]
pub struct Country {
pub code: [u8; 2],
pub rev: u16,
}
/// AF Afghanistan
pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 };
/// AL Albania
pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 };
/// DZ Algeria
pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 };
/// AS American_Samoa
pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 };
/// AO Angola
pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 };
/// AI Anguilla
pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 };
/// AG Antigua_and_Barbuda
pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 };
/// AR Argentina
pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 };
/// AM Armenia
pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 };
/// AW Aruba
pub const ARUBA: Country = Country { code: *b"AW", rev: 0 };
/// AU Australia
pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 };
/// AT Austria
pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 };
/// AZ Azerbaijan
pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 };
/// BS Bahamas
pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 };
/// BH Bahrain
pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 };
/// 0B Baker_Island
pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 };
/// BD Bangladesh
pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 };
/// BB Barbados
pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 };
/// BY Belarus
pub const BELARUS: Country = Country { code: *b"BY", rev: 0 };
/// BE Belgium
pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 };
/// BZ Belize
pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 };
/// BJ Benin
pub const BENIN: Country = Country { code: *b"BJ", rev: 0 };
/// BM Bermuda
pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 };
/// BT Bhutan
pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 };
/// BO Bolivia
pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 };
/// BA Bosnia_and_Herzegovina
pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 };
/// BW Botswana
pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 };
/// BR Brazil
pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 };
/// IO British_Indian_Ocean_Territory
pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 };
/// BN Brunei_Darussalam
pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 };
/// BG Bulgaria
pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 };
/// BF Burkina_Faso
pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 };
/// BI Burundi
pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 };
/// KH Cambodia
pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 };
/// CM Cameroon
pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 };
/// CA Canada
pub const CANADA: Country = Country { code: *b"CA", rev: 0 };
/// CA Canada Revision 950
pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 };
/// CV Cape_Verde
pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 };
/// KY Cayman_Islands
pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 };
/// CF Central_African_Republic
pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 };
/// TD Chad
pub const CHAD: Country = Country { code: *b"TD", rev: 0 };
/// CL Chile
pub const CHILE: Country = Country { code: *b"CL", rev: 0 };
/// CN China
pub const CHINA: Country = Country { code: *b"CN", rev: 0 };
/// CX Christmas_Island
pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 };
/// CO Colombia
pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 };
/// KM Comoros
pub const COMOROS: Country = Country { code: *b"KM", rev: 0 };
/// CG Congo
pub const CONGO: Country = Country { code: *b"CG", rev: 0 };
/// CD Congo,_The_Democratic_Republic_Of_The
pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 };
/// CR Costa_Rica
pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 };
/// CI Cote_D'ivoire
pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 };
/// HR Croatia
pub const CROATIA: Country = Country { code: *b"HR", rev: 0 };
/// CU Cuba
pub const CUBA: Country = Country { code: *b"CU", rev: 0 };
/// CY Cyprus
pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 };
/// CZ Czech_Republic
pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 };
/// DK Denmark
pub const DENMARK: Country = Country { code: *b"DK", rev: 0 };
/// DJ Djibouti
pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 };
/// DM Dominica
pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 };
/// DO Dominican_Republic
pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 };
/// AU G'Day mate!
pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 };
/// EC Ecuador
pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 };
/// EG Egypt
pub const EGYPT: Country = Country { code: *b"EG", rev: 0 };
/// SV El_Salvador
pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 };
/// GQ Equatorial_Guinea
pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 };
/// ER Eritrea
pub const ERITREA: Country = Country { code: *b"ER", rev: 0 };
/// EE Estonia
pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 };
/// ET Ethiopia
pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 };
/// FK Falkland_Islands_(Malvinas)
pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 };
/// FO Faroe_Islands
pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 };
/// FJ Fiji
pub const FIJI: Country = Country { code: *b"FJ", rev: 0 };
/// FI Finland
pub const FINLAND: Country = Country { code: *b"FI", rev: 0 };
/// FR France
pub const FRANCE: Country = Country { code: *b"FR", rev: 0 };
/// GF French_Guina
pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 };
/// PF French_Polynesia
pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 };
/// TF French_Southern_Territories
pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 };
/// GA Gabon
pub const GABON: Country = Country { code: *b"GA", rev: 0 };
/// GM Gambia
pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 };
/// GE Georgia
pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 };
/// DE Germany
pub const GERMANY: Country = Country { code: *b"DE", rev: 0 };
/// E0 European_Wide Revision 895
pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 };
/// GH Ghana
pub const GHANA: Country = Country { code: *b"GH", rev: 0 };
/// GI Gibraltar
pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 };
/// GR Greece
pub const GREECE: Country = Country { code: *b"GR", rev: 0 };
/// GD Grenada
pub const GRENADA: Country = Country { code: *b"GD", rev: 0 };
/// GP Guadeloupe
pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 };
/// GU Guam
pub const GUAM: Country = Country { code: *b"GU", rev: 0 };
/// GT Guatemala
pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 };
/// GG Guernsey
pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 };
/// GN Guinea
pub const GUINEA: Country = Country { code: *b"GN", rev: 0 };
/// GW Guinea-bissau
pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 };
/// GY Guyana
pub const GUYANA: Country = Country { code: *b"GY", rev: 0 };
/// HT Haiti
pub const HAITI: Country = Country { code: *b"HT", rev: 0 };
/// VA Holy_See_(Vatican_City_State)
pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 };
/// HN Honduras
pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 };
/// HK Hong_Kong
pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 };
/// HU Hungary
pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 };
/// IS Iceland
pub const ICELAND: Country = Country { code: *b"IS", rev: 0 };
/// IN India
pub const INDIA: Country = Country { code: *b"IN", rev: 0 };
/// ID Indonesia
pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 };
/// IR Iran,_Islamic_Republic_Of
pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 };
/// IQ Iraq
pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 };
/// IE Ireland
pub const IRELAND: Country = Country { code: *b"IE", rev: 0 };
/// IL Israel
pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 };
/// IT Italy
pub const ITALY: Country = Country { code: *b"IT", rev: 0 };
/// JM Jamaica
pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 };
/// JP Japan
pub const JAPAN: Country = Country { code: *b"JP", rev: 0 };
/// JE Jersey
pub const JERSEY: Country = Country { code: *b"JE", rev: 0 };
/// JO Jordan
pub const JORDAN: Country = Country { code: *b"JO", rev: 0 };
/// KZ Kazakhstan
pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 };
/// KE Kenya
pub const KENYA: Country = Country { code: *b"KE", rev: 0 };
/// KI Kiribati
pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 };
/// KR Korea,_Republic_Of
pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 };
/// 0A Kosovo
pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 };
/// KW Kuwait
pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 };
/// KG Kyrgyzstan
pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 };
/// LA Lao_People's_Democratic_Repubic
pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 };
/// LV Latvia
pub const LATVIA: Country = Country { code: *b"LV", rev: 0 };
/// LB Lebanon
pub const LEBANON: Country = Country { code: *b"LB", rev: 0 };
/// LS Lesotho
pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 };
/// LR Liberia
pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 };
/// LY Libyan_Arab_Jamahiriya
pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 };
/// LI Liechtenstein
pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 };
/// LT Lithuania
pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 };
/// LU Luxembourg
pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 };
/// MO Macao
pub const MACAO: Country = Country { code: *b"MO", rev: 0 };
/// MK Macedonia,_Former_Yugoslav_Republic_Of
pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 };
/// MG Madagascar
pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 };
/// MW Malawi
pub const MALAWI: Country = Country { code: *b"MW", rev: 0 };
/// MY Malaysia
pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 };
/// MV Maldives
pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 };
/// ML Mali
pub const MALI: Country = Country { code: *b"ML", rev: 0 };
/// MT Malta
pub const MALTA: Country = Country { code: *b"MT", rev: 0 };
/// IM Man,_Isle_Of
pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 };
/// MQ Martinique
pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 };
/// MR Mauritania
pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 };
/// MU Mauritius
pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 };
/// YT Mayotte
pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 };
/// MX Mexico
pub const MEXICO: Country = Country { code: *b"MX", rev: 0 };
/// FM Micronesia,_Federated_States_Of
pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 };
/// MD Moldova,_Republic_Of
pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 };
/// MC Monaco
pub const MONACO: Country = Country { code: *b"MC", rev: 0 };
/// MN Mongolia
pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 };
/// ME Montenegro
pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 };
/// MS Montserrat
pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 };
/// MA Morocco
pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 };
/// MZ Mozambique
pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 };
/// MM Myanmar
pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 };
/// NA Namibia
pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 };
/// NR Nauru
pub const NAURU: Country = Country { code: *b"NR", rev: 0 };
/// NP Nepal
pub const NEPAL: Country = Country { code: *b"NP", rev: 0 };
/// NL Netherlands
pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 };
/// AN Netherlands_Antilles
pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 };
/// NC New_Caledonia
pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 };
/// NZ New_Zealand
pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 };
/// NI Nicaragua
pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 };
/// NE Niger
pub const NIGER: Country = Country { code: *b"NE", rev: 0 };
/// NG Nigeria
pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 };
/// NF Norfolk_Island
pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 };
/// MP Northern_Mariana_Islands
pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 };
/// NO Norway
pub const NORWAY: Country = Country { code: *b"NO", rev: 0 };
/// OM Oman
pub const OMAN: Country = Country { code: *b"OM", rev: 0 };
/// PK Pakistan
pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 };
/// PW Palau
pub const PALAU: Country = Country { code: *b"PW", rev: 0 };
/// PA Panama
pub const PANAMA: Country = Country { code: *b"PA", rev: 0 };
/// PG Papua_New_Guinea
pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 };
/// PY Paraguay
pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 };
/// PE Peru
pub const PERU: Country = Country { code: *b"PE", rev: 0 };
/// PH Philippines
pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 };
/// PL Poland
pub const POLAND: Country = Country { code: *b"PL", rev: 0 };
/// PT Portugal
pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 };
/// PR Pueto_Rico
pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 };
/// QA Qatar
pub const QATAR: Country = Country { code: *b"QA", rev: 0 };
/// RE Reunion
pub const REUNION: Country = Country { code: *b"RE", rev: 0 };
/// RO Romania
pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 };
/// RU Russian_Federation
pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 };
/// RW Rwanda
pub const RWANDA: Country = Country { code: *b"RW", rev: 0 };
/// KN Saint_Kitts_and_Nevis
pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 };
/// LC Saint_Lucia
pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 };
/// PM Saint_Pierre_and_Miquelon
pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 };
/// VC Saint_Vincent_and_The_Grenadines
pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 };
/// WS Samoa
pub const SAMOA: Country = Country { code: *b"WS", rev: 0 };
/// MF Sanit_Martin_/_Sint_Marteen
pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 };
/// ST Sao_Tome_and_Principe
pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 };
/// SA Saudi_Arabia
pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 };
/// SN Senegal
pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 };
/// RS Serbia
pub const SERBIA: Country = Country { code: *b"RS", rev: 0 };
/// SC Seychelles
pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 };
/// SL Sierra_Leone
pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 };
/// SG Singapore
pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 };
/// SK Slovakia
pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 };
/// SI Slovenia
pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 };
/// SB Solomon_Islands
pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 };
/// SO Somalia
pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 };
/// ZA South_Africa
pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 };
/// ES Spain
pub const SPAIN: Country = Country { code: *b"ES", rev: 0 };
/// LK Sri_Lanka
pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 };
/// SR Suriname
pub const SURINAME: Country = Country { code: *b"SR", rev: 0 };
/// SZ Swaziland
pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 };
/// SE Sweden
pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 };
/// CH Switzerland
pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 };
/// SY Syrian_Arab_Republic
pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 };
/// TW Taiwan,_Province_Of_China
pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 };
/// TJ Tajikistan
pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 };
/// TZ Tanzania,_United_Republic_Of
pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 };
/// TH Thailand
pub const THAILAND: Country = Country { code: *b"TH", rev: 0 };
/// TG Togo
pub const TOGO: Country = Country { code: *b"TG", rev: 0 };
/// TO Tonga
pub const TONGA: Country = Country { code: *b"TO", rev: 0 };
/// TT Trinidad_and_Tobago
pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 };
/// TN Tunisia
pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 };
/// TR Turkey
pub const TURKEY: Country = Country { code: *b"TR", rev: 0 };
/// TM Turkmenistan
pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 };
/// TC Turks_and_Caicos_Islands
pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 };
/// TV Tuvalu
pub const TUVALU: Country = Country { code: *b"TV", rev: 0 };
/// UG Uganda
pub const UGANDA: Country = Country { code: *b"UG", rev: 0 };
/// UA Ukraine
pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 };
/// AE United_Arab_Emirates
pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 };
/// GB United_Kingdom
pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 };
/// US United_States
pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 };
/// US United_States Revision 4
pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 };
/// Q1 United_States Revision 931
pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 };
/// Q2 United_States_(No_DFS)
pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 };
/// UM United_States_Minor_Outlying_Islands
pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 };
/// UY Uruguay
pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 };
/// UZ Uzbekistan
pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 };
/// VU Vanuatu
pub const VANUATU: Country = Country { code: *b"VU", rev: 0 };
/// VE Venezuela
pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 };
/// VN Viet_Nam
pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 };
/// VG Virgin_Islands,_British
pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 };
/// VI Virgin_Islands,_U.S.
pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 };
/// WF Wallis_and_Futuna
pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 };
/// 0C West_Bank
pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 };
/// EH Western_Sahara
pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 };
/// Worldwide Locale Revision 983
pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 };
/// Worldwide Locale (passive Ch12-14)
pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 };
/// Worldwide Locale (passive Ch12-14) Revision 17
pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 };
/// YE Yemen
pub const YEMEN: Country = Country { code: *b"YE", rev: 0 };
/// ZM Zambia
pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 };
/// ZW Zimbabwe
pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 };

400
cyw43/src/events.rs Normal file
View File

@ -0,0 +1,400 @@
#![allow(dead_code)]
#![allow(non_camel_case_types)]
use core::cell::RefCell;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::pubsub::{PubSubChannel, Subscriber};
use crate::structs::BssInfo;
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Event {
#[num_enum(default)]
Unknown = 0xFF,
/// indicates status of set SSID
SET_SSID = 0,
/// differentiates join IBSS from found (START) IBSS
JOIN = 1,
/// STA founded an IBSS or AP started a BSS
START = 2,
/// 802.11 AUTH request
AUTH = 3,
/// 802.11 AUTH indication
AUTH_IND = 4,
/// 802.11 DEAUTH request
DEAUTH = 5,
/// 802.11 DEAUTH indication
DEAUTH_IND = 6,
/// 802.11 ASSOC request
ASSOC = 7,
/// 802.11 ASSOC indication
ASSOC_IND = 8,
/// 802.11 REASSOC request
REASSOC = 9,
/// 802.11 REASSOC indication
REASSOC_IND = 10,
/// 802.11 DISASSOC request
DISASSOC = 11,
/// 802.11 DISASSOC indication
DISASSOC_IND = 12,
/// 802.11h Quiet period started
QUIET_START = 13,
/// 802.11h Quiet period ended
QUIET_END = 14,
/// BEACONS received/lost indication
BEACON_RX = 15,
/// generic link indication
LINK = 16,
/// TKIP MIC error occurred
MIC_ERROR = 17,
/// NDIS style link indication
NDIS_LINK = 18,
/// roam attempt occurred: indicate status & reason
ROAM = 19,
/// change in dot11FailedCount (txfail)
TXFAIL = 20,
/// WPA2 pmkid cache indication
PMKID_CACHE = 21,
/// current AP's TSF value went backward
RETROGRADE_TSF = 22,
/// AP was pruned from join list for reason
PRUNE = 23,
/// report AutoAuth table entry match for join attempt
AUTOAUTH = 24,
/// Event encapsulating an EAPOL message
EAPOL_MSG = 25,
/// Scan results are ready or scan was aborted
SCAN_COMPLETE = 26,
/// indicate to host addts fail/success
ADDTS_IND = 27,
/// indicate to host delts fail/success
DELTS_IND = 28,
/// indicate to host of beacon transmit
BCNSENT_IND = 29,
/// Send the received beacon up to the host
BCNRX_MSG = 30,
/// indicate to host loss of beacon
BCNLOST_MSG = 31,
/// before attempting to roam
ROAM_PREP = 32,
/// PFN network found event
PFN_NET_FOUND = 33,
/// PFN network lost event
PFN_NET_LOST = 34,
RESET_COMPLETE = 35,
JOIN_START = 36,
ROAM_START = 37,
ASSOC_START = 38,
IBSS_ASSOC = 39,
RADIO = 40,
/// PSM microcode watchdog fired
PSM_WATCHDOG = 41,
/// CCX association start
CCX_ASSOC_START = 42,
/// CCX association abort
CCX_ASSOC_ABORT = 43,
/// probe request received
PROBREQ_MSG = 44,
SCAN_CONFIRM_IND = 45,
/// WPA Handshake
PSK_SUP = 46,
COUNTRY_CODE_CHANGED = 47,
/// WMMAC excedded medium time
EXCEEDED_MEDIUM_TIME = 48,
/// WEP ICV error occurred
ICV_ERROR = 49,
/// Unsupported unicast encrypted frame
UNICAST_DECODE_ERROR = 50,
/// Unsupported multicast encrypted frame
MULTICAST_DECODE_ERROR = 51,
TRACE = 52,
/// BT-AMP HCI event
BTA_HCI_EVENT = 53,
/// I/F change (for wlan host notification)
IF = 54,
/// P2P Discovery listen state expires
P2P_DISC_LISTEN_COMPLETE = 55,
/// indicate RSSI change based on configured levels
RSSI = 56,
/// PFN best network batching event
PFN_BEST_BATCHING = 57,
EXTLOG_MSG = 58,
/// Action frame reception
ACTION_FRAME = 59,
/// Action frame Tx complete
ACTION_FRAME_COMPLETE = 60,
/// assoc request received
PRE_ASSOC_IND = 61,
/// re-assoc request received
PRE_REASSOC_IND = 62,
/// channel adopted (xxx: obsoleted)
CHANNEL_ADOPTED = 63,
/// AP started
AP_STARTED = 64,
/// AP stopped due to DFS
DFS_AP_STOP = 65,
/// AP resumed due to DFS
DFS_AP_RESUME = 66,
/// WAI stations event
WAI_STA_EVENT = 67,
/// event encapsulating an WAI message
WAI_MSG = 68,
/// escan result event
ESCAN_RESULT = 69,
/// action frame off channel complete
ACTION_FRAME_OFF_CHAN_COMPLETE = 70,
/// probe response received
PROBRESP_MSG = 71,
/// P2P Probe request received
P2P_PROBREQ_MSG = 72,
DCS_REQUEST = 73,
/// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM]
FIFO_CREDIT_MAP = 74,
/// Received action frame event WITH wl_event_rx_frame_data_t header
ACTION_FRAME_RX = 75,
/// Wake Event timer fired, used for wake WLAN test mode
WAKE_EVENT = 76,
/// Radio measurement complete
RM_COMPLETE = 77,
/// Synchronize TSF with the host
HTSFSYNC = 78,
/// request an overlay IOCTL/iovar from the host
OVERLAY_REQ = 79,
CSA_COMPLETE_IND = 80,
/// excess PM Wake Event to inform host
EXCESS_PM_WAKE_EVENT = 81,
/// no PFN networks around
PFN_SCAN_NONE = 82,
/// last found PFN network gets lost
PFN_SCAN_ALLGONE = 83,
GTK_PLUMBED = 84,
/// 802.11 ASSOC indication for NDIS only
ASSOC_IND_NDIS = 85,
/// 802.11 REASSOC indication for NDIS only
REASSOC_IND_NDIS = 86,
ASSOC_REQ_IE = 87,
ASSOC_RESP_IE = 88,
/// association recreated on resume
ASSOC_RECREATED = 89,
/// rx action frame event for NDIS only
ACTION_FRAME_RX_NDIS = 90,
/// authentication request received
AUTH_REQ = 91,
/// fast assoc recreation failed
SPEEDY_RECREATE_FAIL = 93,
/// port-specific event and payload (e.g. NDIS)
NATIVE = 94,
/// event for tx pkt delay suddently jump
PKTDELAY_IND = 95,
/// AWDL AW period starts
AWDL_AW = 96,
/// AWDL Master/Slave/NE master role event
AWDL_ROLE = 97,
/// Generic AWDL event
AWDL_EVENT = 98,
/// NIC AF txstatus
NIC_AF_TXS = 99,
/// NAN event
NAN = 100,
BEACON_FRAME_RX = 101,
/// desired service found
SERVICE_FOUND = 102,
/// GAS fragment received
GAS_FRAGMENT_RX = 103,
/// GAS sessions all complete
GAS_COMPLETE = 104,
/// New device found by p2p offload
P2PO_ADD_DEVICE = 105,
/// device has been removed by p2p offload
P2PO_DEL_DEVICE = 106,
/// WNM event to notify STA enter sleep mode
WNM_STA_SLEEP = 107,
/// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s)
TXFAIL_THRESH = 108,
/// Proximity Detection event
PROXD = 109,
/// AWDL RX Probe response
AWDL_RX_PRB_RESP = 111,
/// AWDL RX Action Frames
AWDL_RX_ACT_FRAME = 112,
/// AWDL Wowl nulls
AWDL_WOWL_NULLPKT = 113,
/// AWDL Phycal status
AWDL_PHYCAL_STATUS = 114,
/// AWDL OOB AF status
AWDL_OOB_AF_STATUS = 115,
/// Interleaved Scan status
AWDL_SCAN_STATUS = 116,
/// AWDL AW Start
AWDL_AW_START = 117,
/// AWDL AW End
AWDL_AW_END = 118,
/// AWDL AW Extensions
AWDL_AW_EXT = 119,
AWDL_PEER_CACHE_CONTROL = 120,
CSA_START_IND = 121,
CSA_DONE_IND = 122,
CSA_FAILURE_IND = 123,
/// CCA based channel quality report
CCA_CHAN_QUAL = 124,
/// to report change in BSSID while roaming
BSSID = 125,
/// tx error indication
TX_STAT_ERROR = 126,
/// credit check for BCMC supported
BCMC_CREDIT_SUPPORT = 127,
/// psta primary interface indication
PSTA_PRIMARY_INTF_IND = 128,
/// Handover Request Initiated
BT_WIFI_HANDOVER_REQ = 130,
/// Southpaw TxInhibit notification
SPW_TXINHIBIT = 131,
/// FBT Authentication Request Indication
FBT_AUTH_REQ_IND = 132,
/// Enhancement addition for RSSI
RSSI_LQM = 133,
/// Full probe/beacon (IEs etc) results
PFN_GSCAN_FULL_RESULT = 134,
/// Significant change in rssi of bssids being tracked
PFN_SWC = 135,
/// a STA been authroized for traffic
AUTHORIZED = 136,
/// probe req with wl_event_rx_frame_data_t header
PROBREQ_MSG_RX = 137,
/// PFN completed scan of network list
PFN_SCAN_COMPLETE = 138,
/// RMC Event
RMC_EVENT = 139,
/// DPSTA interface indication
DPSTA_INTF_IND = 140,
/// RRM Event
RRM = 141,
/// ULP entry event
ULP = 146,
/// TCP Keep Alive Offload Event
TKO = 151,
/// authentication request received
EXT_AUTH_REQ = 187,
/// authentication request received
EXT_AUTH_FRAME_RX = 188,
/// mgmt frame Tx complete
MGMT_FRAME_TXSTATUS = 189,
/// highest val + 1 for range checking
LAST = 190,
}
// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient.
pub type EventQueue = PubSubChannel<NoopRawMutex, Message, 2, 1, 1>;
pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>;
pub struct Events {
pub queue: EventQueue,
pub mask: SharedEventMask,
}
impl Events {
pub fn new() -> Self {
Self {
queue: EventQueue::new(),
mask: SharedEventMask::default(),
}
}
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Status {
pub event_type: Event,
pub status: u32,
}
#[derive(Clone, Copy)]
pub enum Payload {
None,
BssInfo(BssInfo),
}
#[derive(Clone, Copy)]
pub struct Message {
pub header: Status,
pub payload: Payload,
}
impl Message {
pub fn new(status: Status, payload: Payload) -> Self {
Self {
header: status,
payload,
}
}
}
#[derive(Default)]
struct EventMask {
mask: [u32; Self::WORD_COUNT],
}
impl EventMask {
const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize;
fn enable(&mut self, event: Event) {
let n = event as u32;
let word = n / u32::BITS;
let bit = n % u32::BITS;
self.mask[word as usize] |= 1 << bit;
}
fn disable(&mut self, event: Event) {
let n = event as u32;
let word = n / u32::BITS;
let bit = n % u32::BITS;
self.mask[word as usize] &= !(1 << bit);
}
fn is_enabled(&self, event: Event) -> bool {
let n = event as u32;
let word = n / u32::BITS;
let bit = n % u32::BITS;
self.mask[word as usize] & (1 << bit) > 0
}
}
#[derive(Default)]
pub struct SharedEventMask {
mask: RefCell<EventMask>,
}
impl SharedEventMask {
pub fn enable(&self, events: &[Event]) {
let mut mask = self.mask.borrow_mut();
for event in events {
mask.enable(*event);
}
}
#[allow(dead_code)]
pub fn disable(&self, events: &[Event]) {
let mut mask = self.mask.borrow_mut();
for event in events {
mask.disable(*event);
}
}
pub fn disable_all(&self) {
let mut mask = self.mask.borrow_mut();
mask.mask = Default::default();
}
pub fn is_enabled(&self, event: Event) -> bool {
let mask = self.mask.borrow();
mask.is_enabled(event)
}
}

257
cyw43/src/fmt.rs Normal file
View File

@ -0,0 +1,257 @@
#![macro_use]
#![allow(unused_macros)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[cfg(feature = "defmt-timestamp-uptime")]
defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() }
#[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 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)
}
}

126
cyw43/src/ioctl.rs Normal file
View File

@ -0,0 +1,126 @@
use core::cell::{Cell, RefCell};
use core::future::poll_fn;
use core::task::{Poll, Waker};
use embassy_sync::waitqueue::WakerRegistration;
use crate::fmt::Bytes;
#[derive(Clone, Copy)]
pub enum IoctlType {
Get = 0,
Set = 2,
}
#[derive(Clone, Copy)]
pub struct PendingIoctl {
pub buf: *mut [u8],
pub kind: IoctlType,
pub cmd: u32,
pub iface: u32,
}
#[derive(Clone, Copy)]
enum IoctlStateInner {
Pending(PendingIoctl),
Sent { buf: *mut [u8] },
Done { resp_len: usize },
}
struct Wakers {
control: WakerRegistration,
runner: WakerRegistration,
}
impl Default for Wakers {
fn default() -> Self {
Self {
control: WakerRegistration::new(),
runner: WakerRegistration::new(),
}
}
}
pub struct IoctlState {
state: Cell<IoctlStateInner>,
wakers: RefCell<Wakers>,
}
impl IoctlState {
pub fn new() -> Self {
Self {
state: Cell::new(IoctlStateInner::Done { resp_len: 0 }),
wakers: Default::default(),
}
}
fn wake_control(&self) {
self.wakers.borrow_mut().control.wake();
}
fn register_control(&self, waker: &Waker) {
self.wakers.borrow_mut().control.register(waker);
}
fn wake_runner(&self) {
self.wakers.borrow_mut().runner.wake();
}
fn register_runner(&self, waker: &Waker) {
self.wakers.borrow_mut().runner.register(waker);
}
pub async fn wait_complete(&self) -> usize {
poll_fn(|cx| {
if let IoctlStateInner::Done { resp_len } = self.state.get() {
Poll::Ready(resp_len)
} else {
self.register_control(cx.waker());
Poll::Pending
}
})
.await
}
pub async fn wait_pending(&self) -> PendingIoctl {
let pending = poll_fn(|cx| {
if let IoctlStateInner::Pending(pending) = self.state.get() {
Poll::Ready(pending)
} else {
self.register_runner(cx.waker());
Poll::Pending
}
})
.await;
self.state.set(IoctlStateInner::Sent { buf: pending.buf });
pending
}
pub fn cancel_ioctl(&self) {
self.state.set(IoctlStateInner::Done { resp_len: 0 });
}
pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
self.state
.set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface }));
self.wake_runner();
self.wait_complete().await
}
pub fn ioctl_done(&self, response: &[u8]) {
if let IoctlStateInner::Sent { buf } = self.state.get() {
trace!("IOCTL Response: {:02x}", Bytes(response));
// TODO fix this
(unsafe { &mut *buf }[..response.len()]).copy_from_slice(response);
self.state.set(IoctlStateInner::Done {
resp_len: response.len(),
});
self.wake_control();
} else {
warn!("IOCTL Response but no pending Ioctl");
}
}
}

236
cyw43/src/lib.rs Normal file
View File

@ -0,0 +1,236 @@
#![no_std]
#![no_main]
#![allow(incomplete_features)]
#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)]
#![deny(unused_must_use)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
mod bus;
mod consts;
mod countries;
mod events;
mod ioctl;
mod structs;
mod control;
mod nvram;
mod runner;
use core::slice;
use embassy_net_driver_channel as ch;
use embedded_hal_1::digital::OutputPin;
use events::Events;
use ioctl::IoctlState;
use crate::bus::Bus;
pub use crate::bus::SpiBusCyw43;
pub use crate::control::{Control, Error as ControlError};
pub use crate::runner::Runner;
pub use crate::structs::BssInfo;
const MTU: usize = 1514;
#[allow(unused)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum Core {
WLAN = 0,
SOCSRAM = 1,
SDIOD = 2,
}
impl Core {
fn base_addr(&self) -> u32 {
match self {
Self::WLAN => CHIP.arm_core_base_address,
Self::SOCSRAM => CHIP.socsram_wrapper_base_address,
Self::SDIOD => CHIP.sdiod_core_base_address,
}
}
}
#[allow(unused)]
struct Chip {
arm_core_base_address: u32,
socsram_base_address: u32,
socsram_wrapper_base_address: u32,
sdiod_core_base_address: u32,
pmu_base_address: u32,
chip_ram_size: u32,
atcm_ram_base_address: u32,
socram_srmem_size: u32,
chanspec_band_mask: u32,
chanspec_band_2g: u32,
chanspec_band_5g: u32,
chanspec_band_shift: u32,
chanspec_bw_10: u32,
chanspec_bw_20: u32,
chanspec_bw_40: u32,
chanspec_bw_mask: u32,
chanspec_bw_shift: u32,
chanspec_ctl_sb_lower: u32,
chanspec_ctl_sb_upper: u32,
chanspec_ctl_sb_none: u32,
chanspec_ctl_sb_mask: u32,
}
const WRAPPER_REGISTER_OFFSET: u32 = 0x100000;
// Data for CYW43439
const CHIP: Chip = Chip {
arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET,
socsram_base_address: 0x18004000,
socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET,
sdiod_core_base_address: 0x18002000,
pmu_base_address: 0x18000000,
chip_ram_size: 512 * 1024,
atcm_ram_base_address: 0,
socram_srmem_size: 64 * 1024,
chanspec_band_mask: 0xc000,
chanspec_band_2g: 0x0000,
chanspec_band_5g: 0xc000,
chanspec_band_shift: 14,
chanspec_bw_10: 0x0800,
chanspec_bw_20: 0x1000,
chanspec_bw_40: 0x1800,
chanspec_bw_mask: 0x3800,
chanspec_bw_shift: 11,
chanspec_ctl_sb_lower: 0x0000,
chanspec_ctl_sb_upper: 0x0100,
chanspec_ctl_sb_none: 0x0000,
chanspec_ctl_sb_mask: 0x0700,
};
pub struct State {
ioctl_state: IoctlState,
ch: ch::State<MTU, 4, 4>,
events: Events,
}
impl State {
pub fn new() -> Self {
Self {
ioctl_state: IoctlState::new(),
ch: ch::State::new(),
events: Events::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerManagementMode {
/// Custom, officially unsupported mode. Use at your own risk.
/// All power-saving features set to their max at only a marginal decrease in power consumption
/// as oppposed to `Aggressive`.
SuperSave,
/// Aggressive power saving mode.
Aggressive,
/// The default mode.
PowerSave,
/// Performance is prefered over power consumption but still some power is conserved as opposed to
/// `None`.
Performance,
/// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of
/// a much lower throughput.
ThroughputThrottling,
/// No power management is configured. This consumes the most power.
None,
}
impl Default for PowerManagementMode {
fn default() -> Self {
Self::PowerSave
}
}
impl PowerManagementMode {
fn sleep_ret_ms(&self) -> u16 {
match self {
PowerManagementMode::SuperSave => 2000,
PowerManagementMode::Aggressive => 2000,
PowerManagementMode::PowerSave => 200,
PowerManagementMode::Performance => 20,
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
PowerManagementMode::None => 0, // value doesn't matter
}
}
fn beacon_period(&self) -> u8 {
match self {
PowerManagementMode::SuperSave => 255,
PowerManagementMode::Aggressive => 1,
PowerManagementMode::PowerSave => 1,
PowerManagementMode::Performance => 1,
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
PowerManagementMode::None => 0, // value doesn't matter
}
}
fn dtim_period(&self) -> u8 {
match self {
PowerManagementMode::SuperSave => 255,
PowerManagementMode::Aggressive => 1,
PowerManagementMode::PowerSave => 1,
PowerManagementMode::Performance => 1,
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
PowerManagementMode::None => 0, // value doesn't matter
}
}
fn assoc(&self) -> u8 {
match self {
PowerManagementMode::SuperSave => 255,
PowerManagementMode::Aggressive => 10,
PowerManagementMode::PowerSave => 10,
PowerManagementMode::Performance => 1,
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
PowerManagementMode::None => 0, // value doesn't matter
}
}
fn mode(&self) -> u32 {
match self {
PowerManagementMode::ThroughputThrottling => 1,
PowerManagementMode::None => 0,
_ => 2,
}
}
}
pub type NetDriver<'a> = ch::Device<'a, MTU>;
pub async fn new<'a, PWR, SPI>(
state: &'a mut State,
pwr: PWR,
spi: SPI,
firmware: &[u8],
) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>)
where
PWR: OutputPin,
SPI: SpiBusCyw43,
{
let (ch_runner, device) = ch::new(&mut state.ch, [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);
runner.init(firmware).await;
(
device,
Control::new(state_ch, &state.events, &state.ioctl_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) }
}

54
cyw43/src/nvram.rs Normal file
View File

@ -0,0 +1,54 @@
macro_rules! nvram {
($($s:literal,)*) => {
concat_bytes!($($s, b"\x00",)* b"\x00\x00")
};
}
pub static NVRAM: &'static [u8] = &*nvram!(
b"NVRAMRev=$Rev$",
b"manfid=0x2d0",
b"prodid=0x0727",
b"vendid=0x14e4",
b"devid=0x43e2",
b"boardtype=0x0887",
b"boardrev=0x1100",
b"boardnum=22",
b"macaddr=00:A0:50:b5:59:5e",
b"sromrev=11",
b"boardflags=0x00404001",
b"boardflags3=0x04000000",
b"xtalfreq=37400",
b"nocrc=1",
b"ag0=255",
b"aa2g=1",
b"ccode=ALL",
b"pa0itssit=0x20",
b"extpagain2g=0",
b"pa2ga0=-168,6649,-778",
b"AvVmid_c0=0x0,0xc8",
b"cckpwroffset0=5",
b"maxp2ga0=84",
b"txpwrbckof=6",
b"cckbw202gpo=0",
b"legofdmbw202gpo=0x66111111",
b"mcsbw202gpo=0x77711111",
b"propbw202gpo=0xdd",
b"ofdmdigfilttype=18",
b"ofdmdigfilttypebe=18",
b"papdmode=1",
b"papdvalidtest=1",
b"pacalidx2g=45",
b"papdepsoffset=-30",
b"papdendidx=58",
b"ltecxmux=0",
b"ltecxpadnum=0x0102",
b"ltecxfnsel=0x44",
b"ltecxgcigpio=0x01",
b"il0macaddr=00:90:4c:c5:12:38",
b"wl0id=0x431b",
b"deadman_to=0xffffffff",
b"muxenab=0x100",
b"spurconfig=0x3",
b"glitch_based_crsmin=1",
b"btc_mode=1",
);

575
cyw43/src/runner.rs Normal file
View File

@ -0,0 +1,575 @@
use embassy_futures::select::{select3, Either3};
use embassy_net_driver_channel as ch;
use embassy_sync::pubsub::PubSubBehavior;
use embassy_time::{block_for, Duration, Timer};
use embedded_hal_1::digital::OutputPin;
use crate::bus::Bus;
pub use crate::bus::SpiBusCyw43;
use crate::consts::*;
use crate::events::{Event, Events, Status};
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};
#[cfg(feature = "firmware-logs")]
struct LogState {
addr: u32,
last_idx: usize,
buf: [u8; 256],
buf_count: usize,
}
#[cfg(feature = "firmware-logs")]
impl Default for LogState {
fn default() -> Self {
Self {
addr: Default::default(),
last_idx: Default::default(),
buf: [0; 256],
buf_count: Default::default(),
}
}
}
pub struct Runner<'a, PWR, SPI> {
ch: ch::Runner<'a, MTU>,
bus: Bus<PWR, SPI>,
ioctl_state: &'a IoctlState,
ioctl_id: u16,
sdpcm_seq: u8,
sdpcm_seq_max: u8,
events: &'a Events,
#[cfg(feature = "firmware-logs")]
log: LogState,
}
impl<'a, PWR, SPI> Runner<'a, PWR, SPI>
where
PWR: OutputPin,
SPI: SpiBusCyw43,
{
pub(crate) fn new(
ch: ch::Runner<'a, MTU>,
bus: Bus<PWR, SPI>,
ioctl_state: &'a IoctlState,
events: &'a Events,
) -> Self {
Self {
ch,
bus,
ioctl_state,
ioctl_id: 0,
sdpcm_seq: 0,
sdpcm_seq_max: 1,
events,
#[cfg(feature = "firmware-logs")]
log: LogState::default(),
}
}
pub(crate) async fn init(&mut self, firmware: &[u8]) {
self.bus.init().await;
// Init ALP (Active Low Power) clock
self.bus
.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ)
.await;
debug!("waiting for clock...");
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {}
debug!("clock ok");
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_reset(Core::SOCSRAM).await;
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;
debug!("loading nvram");
// Round up to 4 bytes.
let nvram_len = (NVRAM.len() + 3) / 4 * 4;
self.bus
.bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM)
.await;
let nvram_len_words = nvram_len as u32 / 4;
let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words;
self.bus
.bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic)
.await;
// Start core!
debug!("starting up core...");
self.core_reset(Core::WLAN).await;
assert!(self.core_is_up(Core::WLAN).await);
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;
self.bus
.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE)
.await;
// "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)
.await;
// wait for wifi startup
debug!("waiting for wifi init...");
while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {}
// Some random configs related to sleep.
// These aren't needed if we don't want to sleep the bus.
// TODO do we need to sleep the bus to read the irq line, due to
// being on the same pin as MOSI/MISO?
/*
let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await;
val |= 0x02; // WAKE_TILL_HT_AVAIL
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await;
self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT
let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await;
val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await;
*/
// clear 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");
#[cfg(feature = "firmware-logs")]
self.log_init().await;
debug!("wifi init done");
}
#[cfg(feature = "firmware-logs")]
async fn log_init(&mut self) {
// Initialize shared memory for logging.
let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size;
let shared_addr = self.bus.bp_read32(addr).await;
debug!("shared_addr {:08x}", shared_addr);
let mut shared = [0; SharedMemData::SIZE];
self.bus.bp_read(shared_addr, &mut shared).await;
let shared = SharedMemData::from_bytes(&shared);
self.log.addr = shared.console_addr + 8;
}
#[cfg(feature = "firmware-logs")]
async fn log_read(&mut self) {
// Read log struct
let mut log = [0; SharedMemLog::SIZE];
self.bus.bp_read(self.log.addr, &mut log).await;
let log = SharedMemLog::from_bytes(&log);
let idx = log.idx as usize;
// If pointer hasn't moved, no need to do anything.
if idx == self.log.last_idx {
return;
}
// Read entire buf for now. We could read only what we need, but then we
// run into annoying alignment issues in `bp_read`.
let mut buf = [0; 0x400];
self.bus.bp_read(log.buf, &mut buf).await;
while self.log.last_idx != idx as usize {
let b = buf[self.log.last_idx];
if b == b'\r' || b == b'\n' {
if self.log.buf_count != 0 {
let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) };
debug!("LOGS: {}", s);
self.log.buf_count = 0;
}
} else if self.log.buf_count < self.log.buf.len() {
self.log.buf[self.log.buf_count] = b;
self.log.buf_count += 1;
}
self.log.last_idx += 1;
if self.log.last_idx == 0x400 {
self.log.last_idx = 0;
}
}
}
pub async fn run(mut self) -> ! {
let mut buf = [0; 512];
loop {
#[cfg(feature = "firmware-logs")]
self.log_read().await;
if self.has_credit() {
let ioctl = self.ioctl_state.wait_pending();
let tx = self.ch.tx_buf();
let ev = self.bus.wait_for_event();
match select3(ioctl, tx, ev).await {
Either3::First(PendingIoctl {
buf: iobuf,
kind,
cmd,
iface,
}) => {
self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await;
self.check_status(&mut buf).await;
}
Either3::Second(packet) => {
trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
let mut buf = [0; 512];
let buf8 = slice8_mut(&mut buf);
// There MUST be 2 bytes of padding between the SDPCM and BDC headers.
// And ONLY for data packets!
// No idea why, but the firmware will append two zero bytes to the tx'd packets
// otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it
// be oversized and get dropped.
// WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90
// and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597
// ¯\_(ツ)_/¯
const PADDING_SIZE: usize = 2;
let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len();
let seq = self.sdpcm_seq;
self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1);
let sdpcm_header = SdpcmHeader {
len: total_len as u16, // TODO does this len need to be rounded up to u32?
len_inv: !total_len as u16,
sequence: seq,
channel_and_flags: CHANNEL_TYPE_DATA,
next_length: 0,
header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _,
wireless_flow_control: 0,
bus_data_credit: 0,
reserved: [0, 0],
};
let bdc_header = BdcHeader {
flags: BDC_VERSION << BDC_VERSION_SHIFT,
priority: 0,
flags2: 0,
data_offset: 0,
};
trace!("tx {:?}", sdpcm_header);
trace!(" {:?}", bdc_header);
buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes());
buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE]
.copy_from_slice(&bdc_header.to_bytes());
buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()]
.copy_from_slice(packet);
let total_len = (total_len + 3) & !3; // round up to 4byte
trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)]));
self.bus.wlan_write(&buf[..(total_len / 4)]).await;
self.ch.tx_done();
self.check_status(&mut buf).await;
}
Either3::Third(()) => {
self.handle_irq(&mut buf).await;
}
}
} else {
warn!("TX stalled");
self.bus.wait_for_event().await;
self.handle_irq(&mut buf).await;
}
}
}
/// Wait for IRQ on F2 packet available
async fn handle_irq(&mut self, buf: &mut [u32; 512]) {
// Receive stuff
let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await;
trace!("irq{}", FormatInterrupt(irq));
if irq & 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...");
self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await;
}
}
/// Handle F2 events while status register is set
async fn check_status(&mut self, buf: &mut [u32; 512]) {
loop {
let status = self.bus.status();
trace!("check status{}", FormatStatus(status));
if status & STATUS_F2_PKT_AVAILABLE != 0 {
let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT;
self.bus.wlan_read(buf, len).await;
trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)]));
self.rx(&mut slice8_mut(buf)[..len as usize]);
} else {
break;
}
}
}
fn rx(&mut self, packet: &mut [u8]) {
let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return };
self.update_credit(&sdpcm_header);
let channel = sdpcm_header.channel_and_flags & 0x0f;
match channel {
CHANNEL_TYPE_CONTROL => {
let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; };
trace!(" {:?}", cdc_header);
if cdc_header.id == self.ioctl_id {
if cdc_header.status != 0 {
// TODO: propagate error instead
panic!("IOCTL error {}", cdc_header.status as i32);
}
self.ioctl_state.ioctl_done(response);
}
}
CHANNEL_TYPE_EVENT => {
let Some((_, bdc_packet)) = BdcHeader::parse(payload) else {
warn!("BDC event, incomplete header");
return;
};
let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else {
warn!("BDC event, incomplete data");
return;
};
const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h
if event_packet.eth.ether_type != ETH_P_LINK_CTL {
warn!(
"unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}",
event_packet.eth.ether_type, ETH_P_LINK_CTL
);
return;
}
const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18];
if event_packet.hdr.oui != BROADCOM_OUI {
warn!(
"unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}",
Bytes(&event_packet.hdr.oui),
Bytes(BROADCOM_OUI)
);
return;
}
const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769;
if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG {
warn!("unexpected subtype {}", event_packet.hdr.subtype);
return;
}
const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1;
if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT {
warn!("unexpected user_subtype {}", event_packet.hdr.subtype);
return;
}
let evt_type = events::Event::from(event_packet.msg.event_type as u8);
debug!(
"=== EVENT {:?}: {:?} {:02x}",
evt_type,
event_packet.msg,
Bytes(evt_data)
);
if self.events.mask.is_enabled(evt_type) {
let status = event_packet.msg.status;
let event_payload = match evt_type {
Event::ESCAN_RESULT if status == EStatus::PARTIAL => {
let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return };
let Some(bss_info) = BssInfo::parse(bss_info) else { return };
events::Payload::BssInfo(*bss_info)
}
Event::ESCAN_RESULT => events::Payload::None,
_ => events::Payload::None,
};
// this intentionally uses the non-blocking publish immediate
// publish() is a deadlock risk in the current design as awaiting here prevents ioctls
// The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event
// (if they are actively awaiting the queue)
self.events.queue.publish_immediate(events::Message::new(
Status {
event_type: evt_type,
status,
},
event_payload,
));
}
}
CHANNEL_TYPE_DATA => {
let Some((_, packet)) = BdcHeader::parse(payload) else { return };
trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
match self.ch.try_rx_buf() {
Some(buf) => {
buf[..packet.len()].copy_from_slice(packet);
self.ch.rx_done(packet.len())
}
None => warn!("failed to push rxd packet to the channel."),
}
}
_ => {}
}
}
fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) {
if sdpcm_header.channel_and_flags & 0xf < 3 {
let mut sdpcm_seq_max = sdpcm_header.bus_data_credit;
if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 {
sdpcm_seq_max = self.sdpcm_seq + 2;
}
self.sdpcm_seq_max = sdpcm_seq_max;
}
}
fn has_credit(&self) -> bool {
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]) {
let mut buf = [0; 512];
let buf8 = slice8_mut(&mut buf);
let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len();
let sdpcm_seq = self.sdpcm_seq;
self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1);
self.ioctl_id = self.ioctl_id.wrapping_add(1);
let sdpcm_header = SdpcmHeader {
len: total_len as u16, // TODO does this len need to be rounded up to u32?
len_inv: !total_len as u16,
sequence: sdpcm_seq,
channel_and_flags: CHANNEL_TYPE_CONTROL,
next_length: 0,
header_length: SdpcmHeader::SIZE as _,
wireless_flow_control: 0,
bus_data_credit: 0,
reserved: [0, 0],
};
let cdc_header = CdcHeader {
cmd: cmd,
len: data.len() as _,
flags: kind as u16 | (iface as u16) << 12,
id: self.ioctl_id,
status: 0,
};
trace!("tx {:?}", sdpcm_header);
trace!(" {:?}", cdc_header);
buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes());
buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes());
buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data);
let total_len = (total_len + 3) & !3; // round up to 4byte
trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)]));
self.bus.wlan_write(&buf[..total_len / 4]).await;
}
async fn core_disable(&mut self, core: Core) {
let base = core.base_addr();
// Dummy read?
let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
// Check it isn't already reset
let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
if r & AI_RESETCTRL_BIT_RESET != 0 {
return;
}
self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await;
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
block_for(Duration::from_millis(1));
self.bus
.bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET)
.await;
let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
}
async fn core_reset(&mut self, core: Core) {
self.core_disable(core).await;
let base = core.base_addr();
self.bus
.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN)
.await;
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await;
Timer::after(Duration::from_millis(1)).await;
self.bus
.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN)
.await;
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
Timer::after(Duration::from_millis(1)).await;
}
async fn core_is_up(&mut self, core: Core) -> bool {
let base = core.base_addr();
let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN {
debug!("core_is_up: returning false due to bad ioctrl {:02x}", io);
return false;
}
let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
if r & (AI_RESETCTRL_BIT_RESET) != 0 {
debug!("core_is_up: returning false due to bad resetctrl {:02x}", r);
return false;
}
true
}
}

496
cyw43/src/structs.rs Normal file
View File

@ -0,0 +1,496 @@
use crate::events::Event;
use crate::fmt::Bytes;
macro_rules! impl_bytes {
($t:ident) => {
impl $t {
pub const SIZE: usize = core::mem::size_of::<Self>();
#[allow(unused)]
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
unsafe { core::mem::transmute(*self) }
}
#[allow(unused)]
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self {
let alignment = core::mem::align_of::<Self>();
assert_eq!(
bytes.as_ptr().align_offset(alignment),
0,
"{} is not aligned",
core::any::type_name::<Self>()
);
unsafe { core::mem::transmute(bytes) }
}
#[allow(unused)]
pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self {
let alignment = core::mem::align_of::<Self>();
assert_eq!(
bytes.as_ptr().align_offset(alignment),
0,
"{} is not aligned",
core::any::type_name::<Self>()
);
unsafe { core::mem::transmute(bytes) }
}
}
};
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct SharedMemData {
pub flags: u32,
pub trap_addr: u32,
pub assert_exp_addr: u32,
pub assert_file_addr: u32,
pub assert_line: u32,
pub console_addr: u32,
pub msgtrace_addr: u32,
pub fwid: u32,
}
impl_bytes!(SharedMemData);
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct SharedMemLog {
pub buf: u32,
pub buf_size: u32,
pub idx: u32,
pub out_idx: u32,
}
impl_bytes!(SharedMemLog);
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct SdpcmHeader {
pub len: u16,
pub len_inv: u16,
/// Rx/Tx sequence number
pub sequence: u8,
/// 4 MSB Channel number, 4 LSB arbitrary flag
pub channel_and_flags: u8,
/// Length of next data frame, reserved for Tx
pub next_length: u8,
/// Data offset
pub header_length: u8,
/// Flow control bits, reserved for Tx
pub wireless_flow_control: u8,
/// Maximum Sequence number allowed by firmware for Tx
pub bus_data_credit: u8,
/// Reserved
pub reserved: [u8; 2],
}
impl_bytes!(SdpcmHeader);
impl SdpcmHeader {
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
let packet_len = packet.len();
if packet_len < Self::SIZE {
warn!("packet too short, len={}", packet.len());
return None;
}
let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE);
let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap());
trace!("rx {:?}", sdpcm_header);
if sdpcm_header.len != !sdpcm_header.len_inv {
warn!("len inv mismatch");
return None;
}
if sdpcm_header.len as usize != packet_len {
warn!("len from header doesn't match len from spi");
return None;
}
let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..];
Some((sdpcm_header, sdpcm_packet))
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C, packed(2))]
pub struct CdcHeader {
pub cmd: u32,
pub len: u32,
pub flags: u16,
pub id: u16,
pub status: u32,
}
impl_bytes!(CdcHeader);
#[cfg(feature = "defmt")]
impl defmt::Format for CdcHeader {
fn format(&self, fmt: defmt::Formatter) {
fn copy<T: Copy>(t: T) -> T {
t
}
defmt::write!(
fmt,
"CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}",
copy(self.cmd),
copy(self.len),
copy(self.flags),
copy(self.id),
copy(self.status),
)
}
}
impl CdcHeader {
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
if packet.len() < Self::SIZE {
warn!("payload too short, len={}", packet.len());
return None;
}
let (cdc_header, payload) = packet.split_at_mut(Self::SIZE);
let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap());
let payload = &mut payload[..cdc_header.len as usize];
Some((cdc_header, payload))
}
}
pub const BDC_VERSION: u8 = 2;
pub const BDC_VERSION_SHIFT: u8 = 4;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct BdcHeader {
pub flags: u8,
/// 802.1d Priority (low 3 bits)
pub priority: u8,
pub flags2: u8,
/// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers.
pub data_offset: u8,
}
impl_bytes!(BdcHeader);
impl BdcHeader {
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
if packet.len() < Self::SIZE {
return None;
}
let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE);
let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap());
trace!(" {:?}", bdc_header);
let packet_start = 4 * bdc_header.data_offset as usize;
let bdc_packet = bdc_packet.get_mut(packet_start..)?;
trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)]));
Some((bdc_header, bdc_packet))
}
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct EthernetHeader {
pub destination_mac: [u8; 6],
pub source_mac: [u8; 6],
pub ether_type: u16,
}
impl EthernetHeader {
pub fn byteswap(&mut self) {
self.ether_type = self.ether_type.to_be();
}
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct EventHeader {
pub subtype: u16,
pub length: u16,
pub version: u8,
pub oui: [u8; 3],
pub user_subtype: u16,
}
impl EventHeader {
pub fn byteswap(&mut self) {
self.subtype = self.subtype.to_be();
self.length = self.length.to_be();
self.user_subtype = self.user_subtype.to_be();
}
}
#[derive(Debug, Clone, Copy)]
// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C, packed(2))]
pub struct EventMessage {
/// version
pub version: u16,
/// see flags below
pub flags: u16,
/// Message (see below)
pub event_type: u32,
/// Status code (see below)
pub status: u32,
/// Reason code (if applicable)
pub reason: u32,
/// WLC_E_AUTH
pub auth_type: u32,
/// data buf
pub datalen: u32,
/// Station address (if applicable)
pub addr: [u8; 6],
/// name of the incoming packet interface
pub ifname: [u8; 16],
/// destination OS i/f index
pub ifidx: u8,
/// source bsscfg index
pub bsscfgidx: u8,
}
impl_bytes!(EventMessage);
#[cfg(feature = "defmt")]
impl defmt::Format for EventMessage {
fn format(&self, fmt: defmt::Formatter) {
let event_type = self.event_type;
let status = self.status;
let reason = self.reason;
let auth_type = self.auth_type;
let datalen = self.datalen;
defmt::write!(
fmt,
"EventMessage {{ \
version: {=u16}, \
flags: {=u16}, \
event_type: {=u32}, \
status: {=u32}, \
reason: {=u32}, \
auth_type: {=u32}, \
datalen: {=u32}, \
addr: {=[u8; 6]:x}, \
ifname: {=[u8; 16]:x}, \
ifidx: {=u8}, \
bsscfgidx: {=u8}, \
}} ",
self.version,
self.flags,
event_type,
status,
reason,
auth_type,
datalen,
self.addr,
self.ifname,
self.ifidx,
self.bsscfgidx
);
}
}
impl EventMessage {
pub fn byteswap(&mut self) {
self.version = self.version.to_be();
self.flags = self.flags.to_be();
self.event_type = self.event_type.to_be();
self.status = self.status.to_be();
self.reason = self.reason.to_be();
self.auth_type = self.auth_type.to_be();
self.datalen = self.datalen.to_be();
}
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C, packed(2))]
pub struct EventPacket {
pub eth: EthernetHeader,
pub hdr: EventHeader,
pub msg: EventMessage,
}
impl_bytes!(EventPacket);
impl EventPacket {
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
if packet.len() < Self::SIZE {
return None;
}
let (event_header, event_packet) = packet.split_at_mut(Self::SIZE);
let event_header = Self::from_bytes_mut(event_header.try_into().unwrap());
// warn!("event_header {:x}", event_header as *const _);
event_header.byteswap();
let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?;
Some((event_header, event_packet))
}
pub fn byteswap(&mut self) {
self.eth.byteswap();
self.hdr.byteswap();
self.msg.byteswap();
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct DownloadHeader {
pub flag: u16, //
pub dload_type: u16,
pub len: u32,
pub crc: u32,
}
impl_bytes!(DownloadHeader);
#[allow(unused)]
pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001;
pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002;
pub const DOWNLOAD_FLAG_END: u16 = 0x0004;
pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000;
// Country Locale Matrix (CLM)
pub const DOWNLOAD_TYPE_CLM: u16 = 2;
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct CountryInfo {
pub country_abbrev: [u8; 4],
pub rev: i32,
pub country_code: [u8; 4],
}
impl_bytes!(CountryInfo);
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct SsidInfo {
pub len: u32,
pub ssid: [u8; 32],
}
impl_bytes!(SsidInfo);
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct PassphraseInfo {
pub len: u16,
pub flags: u16,
pub passphrase: [u8; 64],
}
impl_bytes!(PassphraseInfo);
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct SsidInfoWithIndex {
pub index: u32,
pub ssid_info: SsidInfo,
}
impl_bytes!(SsidInfoWithIndex);
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct EventMask {
pub iface: u32,
pub events: [u8; 24],
}
impl_bytes!(EventMask);
impl EventMask {
pub fn unset(&mut self, evt: Event) {
let evt = evt as u8 as usize;
self.events[evt / 8] &= !(1 << (evt % 8));
}
}
/// Parameters for a wifi scan
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct ScanParams {
pub version: u32,
pub action: u16,
pub sync_id: u16,
pub ssid_len: u32,
pub ssid: [u8; 32],
pub bssid: [u8; 6],
pub bss_type: u8,
pub scan_type: u8,
pub nprobes: u32,
pub active_time: u32,
pub passive_time: u32,
pub home_time: u32,
pub channel_num: u32,
pub channel_list: [u16; 1],
}
impl_bytes!(ScanParams);
/// Wifi Scan Results Header, followed by `bss_count` `BssInfo`
#[derive(Clone, Copy)]
// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C, packed(2))]
pub struct ScanResults {
pub buflen: u32,
pub version: u32,
pub sync_id: u16,
pub bss_count: u16,
}
impl_bytes!(ScanResults);
impl ScanResults {
pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> {
if packet.len() < ScanResults::SIZE {
return None;
}
let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE);
let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap());
if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE {
warn!("Scan result, incomplete BssInfo");
return None;
}
Some((scan_results, bssinfo))
}
}
/// Wifi Scan Result
#[derive(Clone, Copy)]
// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C, packed(2))]
#[non_exhaustive]
pub struct BssInfo {
pub version: u32,
pub length: u32,
pub bssid: [u8; 6],
pub beacon_period: u16,
pub capability: u16,
pub ssid_len: u8,
pub ssid: [u8; 32],
// there will be more stuff here
}
impl_bytes!(BssInfo);
impl BssInfo {
pub fn parse(packet: &mut [u8]) -> Option<&mut Self> {
if packet.len() < BssInfo::SIZE {
return None;
}
Some(BssInfo::from_bytes_mut(
packet[..BssInfo::SIZE].as_mut().try_into().unwrap(),
))
}
}

View File

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

View File

@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"], default-features = false }
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] }
defmt = "0.3.0"
defmt-rtt = "0.3.0"

View File

@ -27,9 +27,10 @@ defmt = { version = "0.3", optional = true }
digest = "0.10"
log = { version = "0.4", optional = true }
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true}
embedded-storage-async = { version = "0.4.0", optional = true }
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
signature = { version = "1.6.4", default-features = false }
@ -39,6 +40,7 @@ env_logger = "0.9"
rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
futures = { version = "0.3", features = ["executor"] }
sha1 = "0.10.5"
critical-section = { version = "1.1.1", features = ["std"] }
[dev-dependencies.ed25519-dalek]
default_features = false
@ -48,7 +50,7 @@ features = ["rand", "std", "u32_backend"]
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
ed25519-salty = ["dep:salty", "_verify"]
nightly = ["dep:embedded-storage-async"]
nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"]
#Internal features
_verify = []

View File

@ -1,6 +1,11 @@
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
use core::cell::RefCell;
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
use embassy_embedded_hal::flash::partition::BlockingPartition;
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, STATE_ERASE_VALUE, SWAP_MAGIC};
/// Errors returned by bootloader
#[derive(PartialEq, Eq, Debug)]
@ -30,63 +35,96 @@ where
}
}
/// Trait defining the flash handles used for active and DFU partition.
pub trait FlashConfig {
/// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
const STATE_ERASE_VALUE: u8 = 0xFF;
/// Bootloader flash configuration holding the three flashes used by the bootloader
///
/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use.
/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition
/// the provided flash according to symbols defined in the linkerfile.
pub struct BootLoaderConfig<ACTIVE, DFU, STATE> {
/// Flash type used for the active partition - the partition which will be booted from.
pub active: ACTIVE,
/// Flash type used for the dfu partition - the partition which will be swapped in when requested.
pub dfu: DFU,
/// Flash type used for the state partition.
type STATE: NorFlash;
/// Flash type used for the active partition.
type ACTIVE: NorFlash;
/// Flash type used for the dfu partition.
type DFU: NorFlash;
/// Return flash instance used to write/read to/from active partition.
fn active(&mut self) -> &mut Self::ACTIVE;
/// Return flash instance used to write/read to/from dfu partition.
fn dfu(&mut self) -> &mut Self::DFU;
/// Return flash instance used to write/read to/from bootloader state.
fn state(&mut self) -> &mut Self::STATE;
pub state: STATE,
}
trait FlashConfigEx {
fn page_size() -> u32;
}
impl<'a, FLASH: NorFlash>
BootLoaderConfig<
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, FLASH>,
>
{
/// Create a bootloader config from the flash and address symbols defined in the linkerfile
// #[cfg(target_os = "none")]
pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
impl<T: FlashConfig> FlashConfigEx for T {
/// Get the page size which is the "unit of operation" within the bootloader.
fn page_size() -> u32 {
core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32
let active = unsafe {
let start = &__bootloader_active_start as *const u32 as u32;
let end = &__bootloader_active_end as *const u32 as u32;
trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
Self { active, dfu, state }
}
}
/// BootLoader works with any flash implementing embedded_storage.
pub struct BootLoader {
// Page with current state of bootloader. The state partition has the following format:
// All ranges are in multiples of WRITE_SIZE bytes.
// | Range | Description |
// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
// | 2..2 + N | Progress index used while swapping or reverting |
state: Partition,
// Location of the partition which will be booted from
active: Partition,
// Location of the partition which will be swapped in when requested
dfu: Partition,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> {
active: ACTIVE,
dfu: DFU,
/// The state partition has the following format:
/// All ranges are in multiples of WRITE_SIZE bytes.
/// | Range | Description |
/// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
/// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
/// | 2..2 + N | Progress index used while swapping or reverting
state: STATE,
}
impl BootLoader {
/// Create a new instance of a bootloader with the given partitions.
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> {
/// Get the page size which is the "unit of operation" within the bootloader.
const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE {
ACTIVE::ERASE_SIZE as u32
} else {
DFU::ERASE_SIZE as u32
};
/// Create a new instance of a bootloader with the flash partitions.
///
/// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
/// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self { active, dfu, state }
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
active: config.active,
dfu: config.dfu,
state: config.state,
}
/// Return the offset of the active partition into the active flash.
pub fn boot_address(&self) -> usize {
self.active.from as usize
}
/// Perform necessary boot preparations like swapping images.
@ -175,195 +213,174 @@ impl BootLoader {
/// | DFU | 3 | 3 | 2 | 1 | 3 |
/// +-----------+--------------+--------+--------+--------+--------+
///
pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
// Ensure we have enough progress pages to store copy progress
assert_eq!(0, P::page_size() % aligned_buf.len() as u32);
assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32);
assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32);
assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32);
assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32);
assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32);
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32);
assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32);
assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32);
assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
// Copy contents from partition N to active
let state = self.read_state(p, aligned_buf)?;
let state = self.read_state(aligned_buf)?;
if state == State::Swap {
//
// Check if we already swapped. If we're in the swap state, this means we should revert
// since the app has failed to mark boot as successful
//
if !self.is_swapped(p, aligned_buf)? {
if !self.is_swapped(aligned_buf)? {
trace!("Swapping");
self.swap(p, aligned_buf)?;
self.swap(aligned_buf)?;
trace!("Swapping done");
} else {
trace!("Reverting");
self.revert(p, aligned_buf)?;
self.revert(aligned_buf)?;
let state_flash = p.state();
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
// Invalidate progress
state_word.fill(!P::STATE_ERASE_VALUE);
self.state
.write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
state_word.fill(!STATE_ERASE_VALUE);
self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
// Clear magic and progress
self.state.wipe_blocking(state_flash)?;
self.state.erase(0, self.state.capacity() as u32)?;
// Set magic
state_word.fill(BOOT_MAGIC);
self.state.write_blocking(state_flash, 0, state_word)?;
self.state.write(0, state_word)?;
}
}
Ok(state)
}
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
let page_count = (self.active.size() / P::page_size()) as usize;
let progress = self.current_progress(p, aligned_buf)?;
fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
let page_count = self.active.capacity() / Self::PAGE_SIZE as usize;
let progress = self.current_progress(aligned_buf)?;
Ok(progress >= page_count * 2)
}
fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
let write_size = P::STATE::WRITE_SIZE as u32;
let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize;
let state_flash = config.state();
fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
let write_size = STATE::WRITE_SIZE as u32;
let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2;
let state_word = &mut aligned_buf[..write_size as usize];
self.state.read_blocking(state_flash, write_size, state_word)?;
if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
self.state.read(write_size, state_word)?;
if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) {
// Progress is invalid
return Ok(max_index);
}
for index in 0..max_index {
self.state
.read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
self.state.read((2 + index) as u32 * write_size, state_word)?;
if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
return Ok(index);
}
}
Ok(max_index)
}
fn update_progress<P: FlashConfig>(
&mut self,
progress_index: usize,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
state_word.fill(!P::STATE_ERASE_VALUE);
self.state.write_blocking(
p.state(),
(2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32,
state_word,
)?;
fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
state_word.fill(!STATE_ERASE_VALUE);
self.state
.write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?;
Ok(())
}
fn copy_page_once_to_active<P: FlashConfig>(
fn copy_page_once_to_active(
&mut self,
progress_index: usize,
from_offset: u32,
to_offset: u32,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= progress_index {
let page_size = P::page_size() as u32;
if self.current_progress(aligned_buf)? <= progress_index {
let page_size = Self::PAGE_SIZE as u32;
self.active
.erase_blocking(p.active(), to_offset, to_offset + page_size)?;
self.active.erase(to_offset, to_offset + page_size)?;
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
self.dfu
.read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
self.active
.write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?;
self.active.write(to_offset + offset_in_page as u32, aligned_buf)?;
}
self.update_progress(progress_index, p, aligned_buf)?;
self.update_progress(progress_index, aligned_buf)?;
}
Ok(())
}
fn copy_page_once_to_dfu<P: FlashConfig>(
fn copy_page_once_to_dfu(
&mut self,
progress_index: usize,
from_offset: u32,
to_offset: u32,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= progress_index {
let page_size = P::page_size() as u32;
if self.current_progress(aligned_buf)? <= progress_index {
let page_size = Self::PAGE_SIZE as u32;
self.dfu
.erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
self.dfu.erase(to_offset as u32, to_offset + page_size)?;
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
self.active
.read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
self.dfu
.write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
self.active.read(from_offset + offset_in_page as u32, aligned_buf)?;
self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?;
}
self.update_progress(progress_index, p, aligned_buf)?;
self.update_progress(progress_index, aligned_buf)?;
}
Ok(())
}
fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_size = P::page_size();
let page_count = self.active.size() / page_size;
fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
for page_num in 0..page_count {
let progress_index = (page_num * 2) as usize;
// Copy active page to the 'next' DFU page.
let active_from_offset = (page_count - 1 - page_num) * page_size;
let dfu_to_offset = (page_count - page_num) * page_size;
let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE;
//trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
// Copy DFU page to the active page
let active_to_offset = (page_count - 1 - page_num) * page_size;
let dfu_from_offset = (page_count - 1 - page_num) * page_size;
let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
//trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
}
Ok(())
}
fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_size = P::page_size();
let page_count = self.active.size() / page_size;
fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
for page_num in 0..page_count {
let progress_index = (page_count * 2 + page_num * 2) as usize;
// Copy the bad active page to the DFU page
let active_from_offset = page_num * page_size;
let dfu_to_offset = page_num * page_size;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
let active_from_offset = page_num * Self::PAGE_SIZE;
let dfu_to_offset = page_num * Self::PAGE_SIZE;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
// Copy the DFU page back to the active page
let active_to_offset = page_num * page_size;
let dfu_from_offset = (page_num + 1) * page_size;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
let active_to_offset = page_num * Self::PAGE_SIZE;
let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
}
Ok(())
}
fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
self.state.read_blocking(config.state(), 0, state_word)?;
fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
self.state.read(0, state_word)?;
if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
@ -373,161 +390,32 @@ impl BootLoader {
}
}
fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) {
assert_eq!(active.size() % page_size, 0);
assert_eq!(dfu.size() % page_size, 0);
assert!(dfu.size() - active.size() >= page_size);
assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32);
}
/// A flash wrapper implementing the Flash and embedded_storage traits.
pub struct BootFlash<F>
where
F: NorFlash,
{
flash: F,
}
impl<F> BootFlash<F>
where
F: NorFlash,
{
/// Create a new instance of a bootable flash
pub fn new(flash: F) -> Self {
Self { flash }
}
}
impl<F> ErrorType for BootFlash<F>
where
F: NorFlash,
{
type Error = F::Error;
}
impl<F> NorFlash for BootFlash<F>
where
F: NorFlash,
{
const WRITE_SIZE: usize = F::WRITE_SIZE;
const ERASE_SIZE: usize = F::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
F::erase(&mut self.flash, from, to)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
F::write(&mut self.flash, offset, bytes)
}
}
impl<F> ReadNorFlash for BootFlash<F>
where
F: NorFlash,
{
const READ_SIZE: usize = F::READ_SIZE;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
F::read(&mut self.flash, offset, bytes)
}
fn capacity(&self) -> usize {
F::capacity(&self.flash)
}
}
/// Convenience provider that uses a single flash for all partitions.
pub struct SingleFlashConfig<'a, F>
where
F: NorFlash,
{
flash: &'a mut F,
}
impl<'a, F> SingleFlashConfig<'a, F>
where
F: NorFlash,
{
/// Create a provider for a single flash.
pub fn new(flash: &'a mut F) -> Self {
Self { flash }
}
}
impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
where
F: NorFlash,
{
type STATE = F;
type ACTIVE = F;
type DFU = F;
fn active(&mut self) -> &mut Self::STATE {
self.flash
}
fn dfu(&mut self) -> &mut Self::ACTIVE {
self.flash
}
fn state(&mut self) -> &mut Self::DFU {
self.flash
}
}
/// Convenience flash provider that uses separate flash instances for each partition.
pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
active: &'a mut ACTIVE,
state: &'a mut STATE,
dfu: &'a mut DFU,
}
impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
/// Create a new flash provider with separate configuration for all three partitions.
pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
Self { active, state, dfu }
}
}
impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
type STATE = STATE;
type ACTIVE = ACTIVE;
type DFU = DFU;
fn active(&mut self) -> &mut Self::ACTIVE {
self.active
}
fn dfu(&mut self) -> &mut Self::DFU {
self.dfu
}
fn state(&mut self) -> &mut Self::STATE {
self.state
}
fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
active: &ACTIVE,
dfu: &DFU,
state: &STATE,
page_size: u32,
) {
assert_eq!(active.capacity() as u32 % page_size, 0);
assert_eq!(dfu.capacity() as u32 % page_size, 0);
assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size);
assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mem_flash::MemFlash;
#[test]
#[should_panic]
fn test_range_asserts() {
const ACTIVE: Partition = Partition::new(4096, 4194304);
const DFU: Partition = Partition::new(4194304, 2 * 4194304);
const STATE: Partition = Partition::new(0, 4096);
assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
const ACTIVE_SIZE: usize = 4194304 - 4096;
const DFU_SIZE: usize = 4194304;
const STATE_SIZE: usize = 4096;
static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF);
static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF);
static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF);
assert_partitions(&ACTIVE, &DFU, &STATE, 4096);
}
}

View File

@ -1,543 +0,0 @@
use digest::Digest;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
/// Errors returned by FirmwareUpdater
#[derive(Debug)]
pub enum FirmwareUpdaterError {
/// Error from flash.
Flash(NorFlashErrorKind),
/// Signature errors.
Signature(signature::Error),
}
#[cfg(feature = "defmt")]
impl defmt::Format for FirmwareUpdaterError {
fn format(&self, fmt: defmt::Formatter) {
match self {
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
}
}
}
impl<E> From<E> for FirmwareUpdaterError
where
E: NorFlashError,
{
fn from(error: E) -> Self {
FirmwareUpdaterError::Flash(error.kind())
}
}
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct FirmwareUpdater {
state: Partition,
dfu: Partition,
}
#[cfg(target_os = "none")]
impl Default for FirmwareUpdater {
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
FirmwareUpdater::new(dfu, state)
}
}
impl FirmwareUpdater {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
pub const fn new(dfu: Partition, state: Partition) -> Self {
Self { dfu, state }
}
/// Obtain the current state.
///
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
#[cfg(feature = "nightly")]
pub async fn get_state<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
self.state.read(state_flash, 0, aligned).await?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(all(feature = "_verify", feature = "nightly"))]
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
&mut self,
_state_and_dfu_flash: &mut F,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(_aligned.len(), F::WRITE_SIZE);
assert!(_update_len <= self.dfu.size());
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
use crate::digest_adapters::ed25519_dalek::Sha512;
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
.await?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Signature};
use crate::digest_adapters::salty::Sha512;
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
.await?;
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
}
/// Verify the update in DFU with any digest.
#[cfg(feature = "nightly")]
pub async fn hash<F: AsyncNorFlash, D: Digest>(
&mut self,
dfu_flash: &mut F,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read(dfu_flash, offset, chunk_buf).await?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
output.copy_from_slice(digest.finalize().as_slice());
Ok(())
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
pub async fn mark_updated<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC, state_flash).await
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(feature = "nightly")]
pub async fn mark_booted<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
}
#[cfg(feature = "nightly")]
async fn set_magic<F: AsyncNorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
state_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
self.state.read(state_flash, 0, aligned).await?;
if aligned.iter().any(|&b| b != magic) {
// Read progress validity
self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
// FIXME: Do not make this assumption.
const STATE_ERASE_VALUE: u8 = 0xFF;
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
// The current progress validity marker is invalid
} else {
// Invalidate progress
aligned.fill(!STATE_ERASE_VALUE);
self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
}
// Clear magic and progress
self.state.wipe(state_flash).await?;
// Set magic
aligned.fill(magic);
self.state.write(state_flash, 0, aligned).await?;
}
Ok(())
}
/// Write data to a flash page.
///
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
///
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
#[cfg(feature = "nightly")]
pub async fn write_firmware<F: AsyncNorFlash>(
&mut self,
offset: usize,
data: &[u8],
dfu_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
self.dfu
.erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
.await?;
self.dfu.write(dfu_flash, offset as u32, data).await?;
Ok(())
}
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
#[cfg(feature = "nightly")]
pub async fn prepare_update<F: AsyncNorFlash>(
&mut self,
dfu_flash: &mut F,
) -> Result<Partition, FirmwareUpdaterError> {
self.dfu.wipe(dfu_flash).await?;
Ok(self.dfu)
}
//
// Blocking API
//
/// Obtain the current state.
///
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
pub fn get_state_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
self.state.read_blocking(state_flash, 0, aligned)?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(feature = "_verify")]
pub fn verify_and_mark_updated_blocking<F: NorFlash>(
&mut self,
_state_and_dfu_flash: &mut F,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(_aligned.len(), F::WRITE_SIZE);
assert!(_update_len <= self.dfu.size());
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
use crate::digest_adapters::ed25519_dalek::Sha512;
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Signature};
use crate::digest_adapters::salty::Sha512;
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
}
/// Verify the update in DFU with any digest.
pub fn hash_blocking<F: NorFlash, D: Digest>(
&mut self,
dfu_flash: &mut F,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
output.copy_from_slice(digest.finalize().as_slice());
Ok(())
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
pub fn mark_updated_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub fn mark_booted_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
}
fn set_magic_blocking<F: NorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
state_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
self.state.read_blocking(state_flash, 0, aligned)?;
if aligned.iter().any(|&b| b != magic) {
// Read progress validity
self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
// FIXME: Do not make this assumption.
const STATE_ERASE_VALUE: u8 = 0xFF;
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
// The current progress validity marker is invalid
} else {
// Invalidate progress
aligned.fill(!STATE_ERASE_VALUE);
self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
}
// Clear magic and progress
self.state.wipe_blocking(state_flash)?;
// Set magic
aligned.fill(magic);
self.state.write_blocking(state_flash, 0, aligned)?;
}
Ok(())
}
/// Write data to a flash page.
///
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
///
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
pub fn write_firmware_blocking<F: NorFlash>(
&mut self,
offset: usize,
data: &[u8],
dfu_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
self.dfu
.erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
Ok(())
}
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware_blocking` allows for an optimized
/// API in exchange for added complexity.
pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
self.dfu.wipe_blocking(flash)?;
Ok(self.dfu)
}
}
#[cfg(test)]
mod tests {
use futures::executor::block_on;
use sha1::{Digest, Sha1};
use super::*;
use crate::mem_flash::MemFlash;
#[test]
#[cfg(feature = "nightly")]
fn can_verify_sha1() {
const STATE: Partition = Partition::new(0, 4096);
const DFU: Partition = Partition::new(65536, 131072);
let mut flash = MemFlash::<131072, 4096, 8>::default();
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let mut to_write = [0; 4096];
to_write[..7].copy_from_slice(update.as_slice());
let mut updater = FirmwareUpdater::new(DFU, STATE);
block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
assert_eq!(Sha1::digest(update).as_slice(), hash);
}
}

View File

@ -0,0 +1,271 @@
use digest::Digest;
#[cfg(target_os = "none")]
use embassy_embedded_hal::flash::partition::Partition;
#[cfg(target_os = "none")]
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embedded_storage_async::nor_flash::NorFlash;
use super::FirmwareUpdaterConfig;
use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
dfu: DFU,
state: STATE,
}
#[cfg(target_os = "none")]
impl<'a, FLASH: NorFlash>
FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>>
{
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
Partition::new(flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
Partition::new(flash, start, end - start)
};
Self { dfu, state }
}
}
impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
Self {
dfu: config.dfu,
state: config.state,
}
}
/// Obtain the current state.
///
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, aligned).await?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(feature = "_verify")]
pub async fn verify_and_mark_updated(
&mut self,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
assert!(_update_len <= self.dfu.capacity() as u32);
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
use crate::digest_adapters::ed25519_dalek::Sha512;
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Signature};
use crate::digest_adapters::salty::Sha512;
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic(_aligned, SWAP_MAGIC).await
}
/// Verify the update in DFU with any digest.
pub async fn hash<D: Digest>(
&mut self,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read(offset, chunk_buf).await?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
output.copy_from_slice(digest.finalize().as_slice());
Ok(())
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC).await
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC).await
}
async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
self.state.read(0, aligned).await?;
if aligned.iter().any(|&b| b != magic) {
// Read progress validity
self.state.read(STATE::WRITE_SIZE as u32, aligned).await?;
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
// The current progress validity marker is invalid
} else {
// Invalidate progress
aligned.fill(!STATE_ERASE_VALUE);
self.state.write(STATE::WRITE_SIZE as u32, aligned).await?;
}
// Clear magic and progress
self.state.erase(0, self.state.capacity() as u32).await?;
// Set magic
aligned.fill(magic);
self.state.write(0, aligned).await?;
}
Ok(())
}
/// Write data to a flash page.
///
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
///
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= DFU::ERASE_SIZE);
self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
self.dfu.write(offset as u32, data).await?;
Ok(())
}
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
self.dfu.erase(0, self.dfu.capacity() as u32).await?;
Ok(&mut self.dfu)
}
}
#[cfg(test)]
mod tests {
use embassy_embedded_hal::flash::partition::Partition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::mutex::Mutex;
use futures::executor::block_on;
use sha1::{Digest, Sha1};
use super::*;
use crate::mem_flash::MemFlash;
#[test]
fn can_verify_sha1() {
let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
let state = Partition::new(&flash, 0, 4096);
let dfu = Partition::new(&flash, 65536, 65536);
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let mut to_write = [0; 4096];
to_write[..7].copy_from_slice(update.as_slice());
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
block_on(updater.write_firmware(0, to_write.as_slice())).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
assert_eq!(Sha1::digest(update).as_slice(), hash);
}
}

View File

@ -0,0 +1,276 @@
use digest::Digest;
#[cfg(target_os = "none")]
use embassy_embedded_hal::flash::partition::BlockingPartition;
#[cfg(target_os = "none")]
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embedded_storage::nor_flash::NorFlash;
use super::FirmwareUpdaterConfig;
use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
dfu: DFU,
state: STATE,
}
#[cfg(target_os = "none")]
impl<'a, FLASH: NorFlash>
FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>>
{
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
pub fn from_linkerfile_blocking(
flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>,
) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
Self { dfu, state }
}
}
impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
Self {
dfu: config.dfu,
state: config.state,
}
}
/// Obtain the current state.
///
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, aligned)?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
/// Verify the DFU given a public key. If there is an error then DO NOT
/// proceed with updating the firmware as it must be signed with a
/// corresponding private key (otherwise it could be malicious firmware).
///
/// Mark to trigger firmware swap on next boot if verify suceeds.
///
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
/// been generated from a SHA-512 digest of the firmware bytes.
///
/// If no signature feature is set then this method will always return a
/// signature error.
///
/// # Safety
///
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(feature = "_verify")]
pub fn verify_and_mark_updated(
&mut self,
_public_key: &[u8],
_signature: &[u8],
_update_len: u32,
_aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
assert!(_update_len <= self.dfu.capacity() as u32);
#[cfg(feature = "ed25519-dalek")]
{
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
use crate::digest_adapters::ed25519_dalek::Sha512;
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
#[cfg(feature = "ed25519-salty")]
{
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
use salty::{PublicKey, Signature};
use crate::digest_adapters::salty::Sha512;
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
FirmwareUpdaterError::Signature(signature::Error::default())
}
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
let mut message = [0; 64];
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
let r = public_key.verify(&message, &signature);
trace!(
"Verifying with public key {}, signature {} and message {} yields ok: {}",
public_key.to_bytes(),
signature.to_bytes(),
message,
r.is_ok()
);
r.map_err(into_signature_error)?
}
self.set_magic(_aligned, SWAP_MAGIC)
}
/// Verify the update in DFU with any digest.
pub fn hash<D: Digest>(
&mut self,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read(offset, chunk_buf)?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
output.copy_from_slice(digest.finalize().as_slice());
Ok(())
}
/// Mark to trigger firmware swap on next boot.
///
/// # Safety
///
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC)
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC)
}
fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
self.state.read(0, aligned)?;
if aligned.iter().any(|&b| b != magic) {
// Read progress validity
self.state.read(STATE::WRITE_SIZE as u32, aligned)?;
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
// The current progress validity marker is invalid
} else {
// Invalidate progress
aligned.fill(!STATE_ERASE_VALUE);
self.state.write(STATE::WRITE_SIZE as u32, aligned)?;
}
// Clear magic and progress
self.state.erase(0, self.state.capacity() as u32)?;
// Set magic
aligned.fill(magic);
self.state.write(0, aligned)?;
}
Ok(())
}
/// Write data to a flash page.
///
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
///
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= DFU::ERASE_SIZE);
self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
self.dfu.write(offset as u32, data)?;
Ok(())
}
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
self.dfu.erase(0, self.dfu.capacity() as u32)?;
Ok(&mut self.dfu)
}
}
#[cfg(test)]
mod tests {
use core::cell::RefCell;
use embassy_embedded_hal::flash::partition::BlockingPartition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use sha1::{Digest, Sha1};
use super::*;
use crate::mem_flash::MemFlash;
#[test]
fn can_verify_sha1() {
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
let state = BlockingPartition::new(&flash, 0, 4096);
let dfu = BlockingPartition::new(&flash, 65536, 65536);
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let mut to_write = [0; 4096];
to_write[..7].copy_from_slice(update.as_slice());
let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
updater.write_firmware(0, to_write.as_slice()).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
updater
.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
.unwrap();
assert_eq!(Sha1::digest(update).as_slice(), hash);
}
}

View File

@ -0,0 +1,48 @@
#[cfg(feature = "nightly")]
mod asynch;
mod blocking;
#[cfg(feature = "nightly")]
pub use asynch::FirmwareUpdater;
pub use blocking::BlockingFirmwareUpdater;
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
/// Firmware updater flash configuration holding the two flashes used by the updater
///
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
/// the provided flash according to symbols defined in the linkerfile.
pub struct FirmwareUpdaterConfig<DFU, STATE> {
/// The dfu flash partition
pub dfu: DFU,
/// The state flash partition
pub state: STATE,
}
/// Errors returned by FirmwareUpdater
#[derive(Debug)]
pub enum FirmwareUpdaterError {
/// Error from flash.
Flash(NorFlashErrorKind),
/// Signature errors.
Signature(signature::Error),
}
#[cfg(feature = "defmt")]
impl defmt::Format for FirmwareUpdaterError {
fn format(&self, fmt: defmt::Formatter) {
match self {
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
}
}
}
impl<E> From<E> for FirmwareUpdaterError
where
E: NorFlashError,
{
fn from(error: E) -> Self {
FirmwareUpdaterError::Flash(error.kind())
}
}

View File

@ -7,12 +7,18 @@ mod fmt;
mod boot_loader;
mod digest_adapters;
mod firmware_updater;
#[cfg(test)]
mod mem_flash;
mod partition;
#[cfg(test)]
mod test_flash;
pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig};
pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError};
pub use partition::Partition;
// The expected value of the flash after an erase
// TODO: Use the value provided by NorFlash when available
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
#[cfg(feature = "nightly")]
pub use firmware_updater::FirmwareUpdater;
pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
@ -45,10 +51,18 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
#[cfg(test)]
mod tests {
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
use futures::executor::block_on;
use super::*;
use crate::boot_loader::BootLoaderConfig;
use crate::firmware_updater::FirmwareUpdaterConfig;
use crate::mem_flash::MemFlash;
#[cfg(feature = "nightly")]
use crate::test_flash::AsyncTestFlash;
use crate::test_flash::BlockingTestFlash;
/*
#[test]
@ -67,147 +81,173 @@ mod tests {
#[test]
fn test_boot_state() {
const STATE: Partition = Partition::new(0, 4096);
const ACTIVE: Partition = Partition::new(4096, 61440);
const DFU: Partition = Partition::new(61440, 122880);
let flash = BlockingTestFlash::new(BootLoaderConfig {
active: MemFlash::<57344, 4096, 4>::default(),
dfu: MemFlash::<61440, 4096, 4>::default(),
state: MemFlash::<4096, 4096, 4>::default(),
});
let mut flash = MemFlash::<131072, 4096, 4>::default();
flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
let mut flash = SingleFlashConfig::new(&mut flash);
flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
let mut page = [0; 4096];
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap());
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
}
#[test]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_swap_state() {
const STATE: Partition = Partition::new(0, 4096);
const ACTIVE: Partition = Partition::new(4096, 61440);
const DFU: Partition = Partition::new(61440, 122880);
let mut flash = MemFlash::<131072, 4096, 4>::random();
const FIRMWARE_SIZE: usize = 57344;
let flash = AsyncTestFlash::new(BootLoaderConfig {
active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(),
dfu: MemFlash::<61440, 4096, 4>::default(),
state: MemFlash::<4096, 4096, 4>::default(),
});
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
let mut aligned = [0; 4];
flash.program(ACTIVE.from, &original).unwrap();
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut updater = FirmwareUpdater::new(DFU, STATE);
block_on(updater.write_firmware(0, &update, &mut flash)).unwrap();
block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
block_on(updater.mark_updated(&mut aligned)).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
let mut page = [0; 1024];
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
.unwrap()
);
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
flash.assert_eq(ACTIVE.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// First DFU page is untouched
flash.assert_eq(DFU.from + 4096, &original);
flash.dfu().read(4096, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
// Running again should cause a revert
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
.unwrap()
);
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
flash.assert_eq(ACTIVE.from, &original);
// Last page is untouched
flash.assert_eq(DFU.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
// Last DFU page is untouched
flash.dfu().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// Mark as booted
block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap();
assert_eq!(
State::Boot,
bootloader
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
.unwrap()
);
let flash = flash.into_async();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.mark_booted(&mut aligned)).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
}
#[test]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_separate_flash_active_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
const DFU: Partition = Partition::new(0, 16384);
fn test_swap_state_active_page_biggest() {
const FIRMWARE_SIZE: usize = 12288;
let flash = AsyncTestFlash::new(BootLoaderConfig {
active: MemFlash::<12288, 4096, 8>::random(),
dfu: MemFlash::<16384, 2048, 8>::random(),
state: MemFlash::<2048, 128, 4>::random(),
});
let mut active = MemFlash::<16384, 4096, 8>::random();
let mut dfu = MemFlash::<16384, 2048, 8>::random();
let mut state = MemFlash::<4096, 128, 4>::random();
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
let mut aligned = [0; 4];
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
active.program(ACTIVE.from, &original).unwrap();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
block_on(updater.mark_updated(&mut aligned)).unwrap();
let mut updater = FirmwareUpdater::new(DFU, STATE);
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut page = [0; 4096];
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page)
.unwrap()
);
active.assert_eq(ACTIVE.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// First DFU page is untouched
dfu.assert_eq(DFU.from + 4096, &original);
flash.dfu().read(4096, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
}
#[test]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_separate_flash_dfu_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
const DFU: Partition = Partition::new(0, 16384);
fn test_swap_state_dfu_page_biggest() {
const FIRMWARE_SIZE: usize = 12288;
let flash = AsyncTestFlash::new(BootLoaderConfig {
active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(),
dfu: MemFlash::<16384, 4096, 8>::random(),
state: MemFlash::<2048, 128, 4>::random(),
});
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
let mut aligned = [0; 4];
let mut active = MemFlash::<16384, 2048, 4>::random();
let mut dfu = MemFlash::<16384, 4096, 8>::random();
let mut state = MemFlash::<4096, 128, 4>::random();
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
active.program(ACTIVE.from, &original).unwrap();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
block_on(updater.mark_updated(&mut aligned)).unwrap();
let mut updater = FirmwareUpdater::new(DFU, STATE);
block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
let mut page = [0; 4096];
assert_eq!(
State::Swap,
bootloader
.prepare_boot(
&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,),
&mut page
)
.unwrap()
);
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
active.assert_eq(ACTIVE.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// First DFU page is untouched
dfu.assert_eq(DFU.from + 4096, &original);
flash.dfu().read(4096, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
}
#[test]
@ -233,25 +273,28 @@ mod tests {
let public_key: PublicKey = keypair.public;
// Setup flash
const STATE: Partition = Partition::new(0, 4096);
const DFU: Partition = Partition::new(4096, 8192);
let mut flash = MemFlash::<8192, 4096, 4>::default();
let flash = BlockingTestFlash::new(BootLoaderConfig {
active: MemFlash::<0, 0, 0>::default(),
dfu: MemFlash::<4096, 4096, 4>::default(),
state: MemFlash::<4096, 4096, 4>::default(),
});
let firmware_len = firmware.len();
let mut write_buf = [0; 4096];
write_buf[0..firmware_len].copy_from_slice(firmware);
DFU.write_blocking(&mut flash, 0, &write_buf).unwrap();
flash.dfu().write(0, &write_buf).unwrap();
// On with the test
let mut updater = FirmwareUpdater::new(DFU, STATE);
let flash = flash.into_async();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
let mut aligned = [0; 4];
assert!(block_on(updater.verify_and_mark_updated(
&mut flash,
&public_key.to_bytes(),
&signature.to_bytes(),
firmware_len as u32,

View File

@ -34,6 +34,52 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
}
}
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> {
let len = bytes.len();
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
Ok(())
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
assert!(offset % WRITE_SIZE == 0);
assert!(offset + bytes.len() <= SIZE);
if let Some(pending_successes) = self.pending_write_successes {
if pending_successes > 0 {
self.pending_write_successes = Some(pending_successes - 1);
} else {
return Err(MemFlashError);
}
}
for ((offset, mem_byte), new_byte) in self
.mem
.iter_mut()
.enumerate()
.skip(offset)
.take(bytes.len())
.zip(bytes)
{
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
*mem_byte = *new_byte;
}
Ok(())
}
fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> {
let from = from as usize;
let to = to as usize;
assert!(from % ERASE_SIZE == 0);
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
for i in from..to {
self.mem[i] = 0xFF;
}
Ok(())
}
pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
@ -44,12 +90,6 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
Ok(())
}
pub fn assert_eq(&self, offset: u32, expectation: &[u8]) {
for i in 0..expectation.len() {
assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i);
}
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
@ -78,9 +118,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNo
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let len = bytes.len();
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
Ok(())
self.read(offset, bytes)
}
fn capacity(&self) -> usize {
@ -94,44 +132,12 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
let from = from as usize;
let to = to as usize;
assert!(from % ERASE_SIZE == 0);
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
for i in from..to {
self.mem[i] = 0xFF;
}
Ok(())
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
assert!(offset % WRITE_SIZE == 0);
assert!(offset + bytes.len() <= SIZE);
if let Some(pending_successes) = self.pending_write_successes {
if pending_successes > 0 {
self.pending_write_successes = Some(pending_successes - 1);
} else {
return Err(MemFlashError);
}
self.write(offset, bytes)
}
for ((offset, mem_byte), new_byte) in self
.mem
.iter_mut()
.enumerate()
.skip(offset)
.take(bytes.len())
.zip(bytes)
{
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
*mem_byte = *new_byte;
}
Ok(())
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.erase(from, to)
}
}
@ -142,11 +148,11 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncR
const READ_SIZE: usize = 1;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
<Self as ReadNorFlash>::read(self, offset, bytes)
self.read(offset, bytes)
}
fn capacity(&self) -> usize {
<Self as ReadNorFlash>::capacity(self)
SIZE
}
}
@ -157,11 +163,11 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncN
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
<Self as NorFlash>::erase(self, from, to)
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.write(offset, bytes)
}
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
<Self as NorFlash>::write(self, offset, bytes)
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.erase(from, to)
}
}

View File

@ -1,144 +0,0 @@
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
/// A region in flash used by the bootloader.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Partition {
/// The offset into the flash where the partition starts.
pub from: u32,
/// The offset into the flash where the partition ends.
pub to: u32,
}
impl Partition {
/// Create a new partition with the provided range
pub const fn new(from: u32, to: u32) -> Self {
Self { from, to }
}
/// Return the size of the partition
pub const fn size(&self) -> u32 {
self.to - self.from
}
/// Read from the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn read<F: AsyncReadNorFlash>(
&self,
flash: &mut F,
offset: u32,
bytes: &mut [u8],
) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.read(offset, bytes).await
}
/// Write to the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.write(offset, bytes).await?;
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
Ok(())
}
/// Erase part of the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
let from = self.from as u32 + from;
let to = self.from as u32 + to;
flash.erase(from, to).await?;
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Erase the entire partition
#[cfg(feature = "nightly")]
pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
let from = self.from as u32;
let to = self.to as u32;
flash.erase(from, to).await?;
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Read from the partition on the provided flash
pub fn read_blocking<F: ReadNorFlash>(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.read(offset, bytes)
}
/// Write to the partition on the provided flash
pub fn write_blocking<F: NorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.write(offset, bytes)?;
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
Ok(())
}
/// Erase part of the partition on the provided flash
pub fn erase_blocking<F: NorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
let from = self.from as u32 + from;
let to = self.from as u32 + to;
flash.erase(from, to)?;
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Erase the entire partition
pub(crate) fn wipe_blocking<F: NorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
let from = self.from as u32;
let to = self.to as u32;
flash.erase(from, to)?;
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::mem_flash::MemFlash;
use crate::Partition;
#[test]
fn can_erase() {
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
let partition = Partition::new(256, 512);
partition.erase_blocking(&mut flash, 64, 192).unwrap();
for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) {
assert_eq!(0x00, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) {
assert_eq!(0xFF, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) {
assert_eq!(0x00, byte, "Index {}", index);
}
}
#[test]
fn can_wipe() {
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
let partition = Partition::new(256, 512);
partition.wipe_blocking(&mut flash).unwrap();
for (index, byte) in flash.mem.iter().copied().enumerate().take(256) {
assert_eq!(0x00, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) {
assert_eq!(0xFF, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) {
assert_eq!(0x00, byte, "Index {}", index);
}
}
}

View File

@ -0,0 +1,64 @@
use embassy_embedded_hal::flash::partition::Partition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::mutex::Mutex;
use embedded_storage_async::nor_flash::NorFlash;
use crate::BootLoaderConfig;
pub struct AsyncTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
active: Mutex<NoopRawMutex, ACTIVE>,
dfu: Mutex<NoopRawMutex, DFU>,
state: Mutex<NoopRawMutex, STATE>,
}
impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
active: Mutex::new(config.active),
dfu: Mutex::new(config.dfu),
state: Mutex::new(config.state),
}
}
pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> {
Self::create_partition(&self.active)
}
pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> {
Self::create_partition(&self.dfu)
}
pub fn state(&self) -> Partition<NoopRawMutex, STATE> {
Self::create_partition(&self.state)
}
fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> {
Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32)
}
}
impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash,
DFU: NorFlash + embedded_storage::nor_flash::NorFlash,
STATE: NorFlash + embedded_storage::nor_flash::NorFlash,
{
pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> {
let config = BootLoaderConfig {
active: self.active.into_inner(),
dfu: self.dfu.into_inner(),
state: self.state.into_inner(),
};
super::BlockingTestFlash::new(config)
}
}

View File

@ -0,0 +1,69 @@
use core::cell::RefCell;
use embassy_embedded_hal::flash::partition::BlockingPartition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_storage::nor_flash::NorFlash;
use crate::BootLoaderConfig;
pub struct BlockingTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
active: Mutex<NoopRawMutex, RefCell<ACTIVE>>,
dfu: Mutex<NoopRawMutex, RefCell<DFU>>,
state: Mutex<NoopRawMutex, RefCell<STATE>>,
}
impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
active: Mutex::new(RefCell::new(config.active)),
dfu: Mutex::new(RefCell::new(config.dfu)),
state: Mutex::new(RefCell::new(config.state)),
}
}
pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> {
Self::create_partition(&self.active)
}
pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> {
Self::create_partition(&self.dfu)
}
pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> {
Self::create_partition(&self.state)
}
pub fn create_partition<T: NorFlash>(
mutex: &Mutex<NoopRawMutex, RefCell<T>>,
) -> BlockingPartition<NoopRawMutex, T> {
BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32)
}
}
#[cfg(feature = "nightly")]
impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash,
STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
{
pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> {
let config = BootLoaderConfig {
active: self.active.into_inner().into_inner(),
dfu: self.dfu.into_inner().into_inner(),
state: self.state.into_inner().into_inner(),
};
super::AsyncTestFlash::new(config)
}
}

View File

@ -0,0 +1,7 @@
#[cfg(feature = "nightly")]
mod asynch;
mod blocking;
#[cfg(feature = "nightly")]
pub(crate) use asynch::AsyncTestFlash;
pub(crate) use blocking::BlockingTestFlash;

View File

@ -17,7 +17,7 @@ target = "thumbv7em-none-eabi"
defmt = { version = "0.3", optional = true }
embassy-sync = { path = "../../embassy-sync" }
embassy-nrf = { path = "../../embassy-nrf", default-features = false }
embassy-nrf = { path = "../../embassy-nrf" }
embassy-boot = { path = "../boot", default-features = false }
cortex-m = { version = "0.7.6" }
cortex-m-rt = { version = "0.7" }

View File

@ -3,74 +3,37 @@
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
#[cfg(feature = "nightly")]
pub use embassy_boot::FirmwareUpdater;
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig};
use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE};
use embassy_nrf::peripherals::WDT;
use embassy_nrf::wdt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for nRF devices.
pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> {
boot: embassy_boot::BootLoader,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = PAGE_SIZE> {
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
#[cfg(target_os = "none")]
impl Default for BootLoader<PAGE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
{
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
boot: embassy_boot::BootLoader::new(config),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) {
Ok(_) => self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
pub fn prepare(&mut self) {
self.boot
.prepare_boot(&mut self.aligned_buf.0)
.expect("Boot prepare error");
}
/// Boots the application without softdevice mechanisms.
@ -79,10 +42,12 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(not(feature = "softdevice"))]
pub unsafe fn load(&mut self, start: usize) -> ! {
pub unsafe fn load(self, start: u32) -> ! {
core::mem::drop(self.boot);
let mut p = cortex_m::Peripherals::steal();
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
@ -92,7 +57,7 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(feature = "softdevice")]
pub unsafe fn load(&mut self, _app: usize) -> ! {
pub unsafe fn load(&mut self, _app: u32) -> ! {
use nrf_softdevice_mbr as mbr;
const NRF_SUCCESS: u32 = 0;

View File

@ -3,7 +3,9 @@
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
#[cfg(feature = "nightly")]
pub use embassy_boot::FirmwareUpdater;
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
use embassy_rp::flash::{Flash, ERASE_SIZE};
use embassy_rp::peripherals::{FLASH, WATCHDOG};
use embassy_rp::watchdog::Watchdog;
@ -11,27 +13,28 @@ use embassy_time::Duration;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for RP2040 devices.
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
boot: embassy_boot::BootLoader,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = ERASE_SIZE> {
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
{
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
boot: embassy_boot::BootLoader::new(config),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
pub fn prepare(&mut self) {
self.boot
.prepare_boot(self.aligned_buf.as_mut())
.expect("Boot prepare error");
}
/// Boots the application.
@ -39,58 +42,20 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! {
pub unsafe fn load(self, start: u32) -> ! {
core::mem::drop(self.boot);
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
#[cfg(not(armv6m))]
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
}
#[cfg(target_os = "none")]
impl Default for BootLoader<ERASE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}
/// A flash implementation that will feed a watchdog when touching flash.
pub struct WatchdogFlash<'d, const SIZE: usize> {
flash: Flash<'d, FLASH, SIZE>,

View File

@ -3,30 +3,34 @@
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
#[cfg(feature = "nightly")]
pub use embassy_boot::FirmwareUpdater;
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
use embedded_storage::nor_flash::NorFlash;
/// A bootloader for STM32 devices.
pub struct BootLoader<const BUFFER_SIZE: usize> {
boot: embassy_boot::BootLoader,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> {
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
{
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
boot: embassy_boot::BootLoader::new(config),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
pub fn prepare(&mut self) {
self.boot
.prepare_boot(self.aligned_buf.as_mut())
.expect("Boot prepare error");
}
/// Boots the application.
@ -34,54 +38,16 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! {
pub unsafe fn load(self, start: u32) -> ! {
core::mem::drop(self.boot);
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
#[cfg(not(armv6m))]
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
}
#[cfg(target_os = "none")]
impl<const BUFFER_SIZE: usize> Default for BootLoader<BUFFER_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}

View File

@ -30,6 +30,16 @@ impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> {
}
Self { flash, offset, size }
}
/// Get the partition offset within the flash
pub const fn offset(&self) -> u32 {
self.offset
}
/// Get the partition size
pub const fn size(&self) -> u32 {
self.size
}
}
impl<M: RawMutex, T: NorFlash> ErrorType for Partition<'_, M, T> {

View File

@ -31,6 +31,16 @@ impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> {
}
Self { flash, offset, size }
}
/// Get the partition offset within the flash
pub const fn offset(&self) -> u32 {
self.offset
}
/// Get the partition size
pub const fn size(&self) -> u32 {
self.size
}
}
impl<M: RawMutex, T: NorFlash> ErrorType for BlockingPartition<'_, M, T> {

View File

@ -2,13 +2,12 @@
//!
//! # Example (nrf52)
//!
//! ```rust
//! ```rust,ignore
//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
//!
//! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new();
//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default());
//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default());
//! let i2c_bus = NoopMutex::new(RefCell::new(i2c));
//! let i2c_bus = I2C_BUS.init(i2c_bus);
//!

View File

@ -2,13 +2,12 @@
//!
//! # Example (nrf52)
//!
//! ```rust
//! ```rust,ignore
//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
//!
//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
//! let irq = interrupt::take!(SPIM3);
//! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default());
//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default());
//! let spi_bus = NoopMutex::new(RefCell::new(spi));
//! let spi_bus = SPI_BUS.init(spi_bus);
//!

View File

@ -458,8 +458,6 @@ mod tests {
#[test]
fn push_slices() {
init();
let mut b = [0; 4];
let rb = RingBuffer::new();
unsafe {

View File

@ -0,0 +1,16 @@
[package]
name = "embassy-net-w5500"
version = "0.1.0"
description = "embassy-net driver for the W5500 ethernet chip"
keywords = ["embedded", "w5500", "embassy-net", "embedded-hal-async", "ethernet", "async"]
categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
license = "MIT OR Apache-2.0"
edition = "2021"
[dependencies]
embedded-hal = { version = "1.0.0-alpha.10" }
embedded-hal-async = { version = "=0.2.0-alpha.1" }
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
embassy-time = { version = "0.1.0" }
embassy-futures = { version = "0.1.0" }
defmt = { version = "0.3", optional = true }

View File

@ -0,0 +1,7 @@
# WIZnet W5500 `embassy-net` integration
[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet W5500 SPI ethernet chip, operating in MACRAW mode.
Supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async)
See [`examples`](https://github.com/kalkyl/embassy-net-w5500/tree/main/examples) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module.

View File

@ -0,0 +1,131 @@
use embedded_hal_async::spi::SpiDevice;
use crate::socket;
use crate::spi::SpiInterface;
pub const MODE: u16 = 0x00;
pub const MAC: u16 = 0x09;
pub const SOCKET_INTR: u16 = 0x18;
pub const PHY_CFG: u16 = 0x2E;
#[repr(u8)]
pub enum RegisterBlock {
Common = 0x00,
Socket0 = 0x01,
TxBuf = 0x02,
RxBuf = 0x03,
}
/// W5500 in MACRAW mode
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct W5500<SPI> {
bus: SpiInterface<SPI>,
}
impl<SPI: SpiDevice> W5500<SPI> {
/// Create and initialize the W5500 driver
pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result<W5500<SPI>, SPI::Error> {
let mut bus = SpiInterface(spi);
// Reset device
bus.write_frame(RegisterBlock::Common, MODE, &[0x80]).await?;
// Enable interrupt pin
bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]).await?;
// Enable receive interrupt
bus.write_frame(
RegisterBlock::Socket0,
socket::SOCKET_INTR_MASK,
&[socket::Interrupt::Receive as u8],
)
.await?;
// Set MAC address
bus.write_frame(RegisterBlock::Common, MAC, &mac_addr).await?;
// Set the raw socket RX/TX buffer sizes to 16KB
bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16])
.await?;
bus.write_frame(RegisterBlock::Socket0, socket::RXBUF_SIZE, &[16])
.await?;
// MACRAW mode with MAC filtering.
let mode: u8 = (1 << 2) | (1 << 7);
bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]).await?;
socket::command(&mut bus, socket::Command::Open).await?;
Ok(Self { bus })
}
/// Read bytes from the RX buffer. Returns the number of bytes read.
async fn read_bytes(&mut self, buffer: &mut [u8], offset: u16) -> Result<usize, SPI::Error> {
let rx_size = socket::get_rx_size(&mut self.bus).await? as usize;
let read_buffer = if rx_size > buffer.len() + offset as usize {
buffer
} else {
&mut buffer[..rx_size - offset as usize]
};
let read_ptr = socket::get_rx_read_ptr(&mut self.bus).await?.wrapping_add(offset);
self.bus.read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer).await?;
socket::set_rx_read_ptr(&mut self.bus, read_ptr.wrapping_add(read_buffer.len() as u16)).await?;
Ok(read_buffer.len())
}
/// Read an ethernet frame from the device. Returns the number of bytes read.
pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result<usize, SPI::Error> {
let rx_size = socket::get_rx_size(&mut self.bus).await? as usize;
if rx_size == 0 {
return Ok(0);
}
socket::reset_interrupt(&mut self.bus, socket::Interrupt::Receive).await?;
// First two bytes gives the size of the received ethernet frame
let expected_frame_size: usize = {
let mut frame_bytes = [0u8; 2];
assert!(self.read_bytes(&mut frame_bytes[..], 0).await? == 2);
u16::from_be_bytes(frame_bytes) as usize - 2
};
// Read the ethernet frame
let read_buffer = if frame.len() > expected_frame_size {
&mut frame[..expected_frame_size]
} else {
frame
};
let recvd_frame_size = self.read_bytes(read_buffer, 2).await?;
// Register RX as completed
socket::command(&mut self.bus, socket::Command::Receive).await?;
// If the whole frame wasn't read, drop it
if recvd_frame_size < expected_frame_size {
Ok(0)
} else {
Ok(recvd_frame_size)
}
}
/// Write an ethernet frame to the device. Returns number of bytes written
pub async fn write_frame(&mut self, frame: &[u8]) -> Result<usize, SPI::Error> {
while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {}
let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?;
self.bus.write_frame(RegisterBlock::TxBuf, write_ptr, frame).await?;
socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?;
socket::command(&mut self.bus, socket::Command::Send).await?;
Ok(frame.len())
}
pub async fn is_link_up(&mut self) -> bool {
let mut link = [0];
self.bus
.read_frame(RegisterBlock::Common, PHY_CFG, &mut link)
.await
.ok();
link[0] & 1 == 1
}
}

View File

@ -0,0 +1,108 @@
#![no_std]
/// [`embassy-net`](crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip.
mod device;
mod socket;
mod spi;
use embassy_futures::select::{select, Either};
use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::LinkState;
use embassy_time::{Duration, Timer};
use embedded_hal::digital::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiDevice;
use crate::device::W5500;
const MTU: usize = 1514;
/// Type alias for the embassy-net driver for W5500
pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>;
/// Internal state for the embassy-net integration.
pub struct State<const N_RX: usize, const N_TX: usize> {
ch_state: ch::State<MTU, N_RX, N_TX>,
}
impl<const N_RX: usize, const N_TX: usize> State<N_RX, N_TX> {
/// Create a new `State`.
pub const fn new() -> Self {
Self {
ch_state: ch::State::new(),
}
}
}
/// Background runner for the W5500.
///
/// You must call `.run()` in a background task for the W5500 to operate.
pub struct Runner<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> {
mac: W5500<SPI>,
ch: ch::Runner<'d, MTU>,
int: INT,
_reset: RST,
}
/// You must call this in a background task for the W5500 to operate.
impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> {
pub async fn run(mut self) -> ! {
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
loop {
if self.mac.is_link_up().await {
state_chan.set_link_state(LinkState::Up);
loop {
match select(
async {
self.int.wait_for_low().await.ok();
rx_chan.rx_buf().await
},
tx_chan.tx_buf(),
)
.await
{
Either::First(p) => {
if let Ok(n) = self.mac.read_frame(p).await {
rx_chan.rx_done(n);
}
}
Either::Second(p) => {
self.mac.write_frame(p).await.ok();
tx_chan.tx_done();
}
}
}
} else {
state_chan.set_link_state(LinkState::Down);
}
}
}
}
/// Obtain a driver for using the W5500 with [`embassy-net`](crates.io/crates/embassy-net).
pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>(
mac_addr: [u8; 6],
state: &'a mut State<N_RX, N_TX>,
spi_dev: SPI,
int: INT,
mut reset: RST,
) -> (Device<'a>, Runner<'a, SPI, INT, RST>) {
// Reset the W5500.
reset.set_low().ok();
// Ensure the reset is registered.
Timer::after(Duration::from_millis(1)).await;
reset.set_high().ok();
// Wait for the W5500 to achieve PLL lock.
Timer::after(Duration::from_millis(2)).await;
let mac = W5500::new(spi_dev, mac_addr).await.unwrap();
let (runner, device) = ch::new(&mut state.ch_state, mac_addr);
(
device,
Runner {
ch: runner,
mac,
int,
_reset: reset,
},
)
}

View File

@ -0,0 +1,80 @@
use embedded_hal_async::spi::SpiDevice;
use crate::device::RegisterBlock;
use crate::spi::SpiInterface;
pub const MODE: u16 = 0x00;
pub const COMMAND: u16 = 0x01;
pub const RXBUF_SIZE: u16 = 0x1E;
pub const TXBUF_SIZE: u16 = 0x1F;
pub const TX_FREE_SIZE: u16 = 0x20;
pub const TX_DATA_WRITE_PTR: u16 = 0x24;
pub const RECVD_SIZE: u16 = 0x26;
pub const RX_DATA_READ_PTR: u16 = 0x28;
pub const SOCKET_INTR_MASK: u16 = 0x2C;
#[repr(u8)]
pub enum Command {
Open = 0x01,
Send = 0x20,
Receive = 0x40,
}
pub const INTR: u16 = 0x02;
#[repr(u8)]
pub enum Interrupt {
Receive = 0b00100_u8,
}
pub async fn reset_interrupt<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, code: Interrupt) -> Result<(), SPI::Error> {
let data = [code as u8];
bus.write_frame(RegisterBlock::Socket0, INTR, &data).await
}
pub async fn get_tx_write_ptr<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
let mut data = [0u8; 2];
bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data)
.await?;
Ok(u16::from_be_bytes(data))
}
pub async fn set_tx_write_ptr<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, ptr: u16) -> Result<(), SPI::Error> {
let data = ptr.to_be_bytes();
bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data).await
}
pub async fn get_rx_read_ptr<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
let mut data = [0u8; 2];
bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data)
.await?;
Ok(u16::from_be_bytes(data))
}
pub async fn set_rx_read_ptr<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, ptr: u16) -> Result<(), SPI::Error> {
let data = ptr.to_be_bytes();
bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data).await
}
pub async fn command<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>, command: Command) -> Result<(), SPI::Error> {
let data = [command as u8];
bus.write_frame(RegisterBlock::Socket0, COMMAND, &data).await
}
pub async fn get_rx_size<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
loop {
// Wait until two sequential reads are equal
let mut res0 = [0u8; 2];
bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0).await?;
let mut res1 = [0u8; 2];
bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1).await?;
if res0 == res1 {
break Ok(u16::from_be_bytes(res0));
}
}
}
pub async fn get_tx_free_size<SPI: SpiDevice>(bus: &mut SpiInterface<SPI>) -> Result<u16, SPI::Error> {
let mut data = [0; 2];
bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data).await?;
Ok(u16::from_be_bytes(data))
}

View File

@ -0,0 +1,28 @@
use embedded_hal_async::spi::{Operation, SpiDevice};
use crate::device::RegisterBlock;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpiInterface<SPI>(pub SPI);
impl<SPI: SpiDevice> SpiInterface<SPI> {
pub async fn read_frame(&mut self, block: RegisterBlock, address: u16, data: &mut [u8]) -> Result<(), SPI::Error> {
let address_phase = address.to_be_bytes();
let control_phase = [(block as u8) << 3];
let operations = &mut [
Operation::Write(&address_phase),
Operation::Write(&control_phase),
Operation::TransferInPlace(data),
];
self.0.transaction(operations).await
}
pub async fn write_frame(&mut self, block: RegisterBlock, address: u16, data: &[u8]) -> Result<(), SPI::Error> {
let address_phase = address.to_be_bytes();
let control_phase = [(block as u8) << 3 | 0b0000_0100];
let data_phase = data;
let operations = &[&address_phase[..], &control_phase, &data_phase];
self.0.write_transaction(operations).await
}
}

View File

@ -16,6 +16,18 @@ flavors = [
]
[features]
default = [
"nrf52805-pac?/rt",
"nrf52810-pac?/rt",
"nrf52811-pac?/rt",
"nrf52820-pac?/rt",
"nrf52832-pac?/rt",
"nrf52833-pac?/rt",
"nrf52840-pac?/rt",
"nrf5340-app-pac?/rt",
"nrf5340-net-pac?/rt",
"nrf9160-pac?/rt",
]
time = ["dep:embassy-time"]
@ -103,13 +115,14 @@ embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true }
cfg-if = "1.0.0"
nrf52805-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52810-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52811-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52820-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52832-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52833-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52840-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf5340-app-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf5340-net-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf9160-pac = { version = "0.12.0", optional = true, features = [ "rt" ] }
nrf52805-pac = { version = "0.12.0", optional = true }
nrf52810-pac = { version = "0.12.0", optional = true }
nrf52811-pac = { version = "0.12.0", optional = true }
nrf52820-pac = { version = "0.12.0", optional = true }
nrf52832-pac = { version = "0.12.0", optional = true }
nrf52833-pac = { version = "0.12.0", optional = true }
nrf52840-pac = { version = "0.12.0", optional = true }
nrf5340-app-pac = { version = "0.12.0", optional = true }
nrf5340-net-pac = { version = "0.12.0", optional = true }
nrf9160-pac = { version = "0.12.0", optional = true }

View File

@ -13,7 +13,7 @@ with peripherals. It takes care of sending/receiving data over a variety of bus
However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust
slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen:
```no_run
```rust,ignore
// As we pass a slice to the function whose contents will not ever change,
// the compiler writes it into the flash and thus the pointer to it will
// reference static memory. Since EasyDMA requires slices to reside in RAM,

View File

@ -154,10 +154,19 @@ impl<'d, T: Instance> Qdec<'d, T> {
/// # Example
///
/// ```no_run
/// let irq = interrupt::take!(QDEC);
/// use embassy_nrf::qdec::{self, Qdec};
/// use embassy_nrf::{bind_interrupts, peripherals};
///
/// bind_interrupts!(struct Irqs {
/// QDEC => qdec::InterruptHandler<peripherals::QDEC>;
/// });
///
/// # async {
/// # let p: embassy_nrf::Peripherals = todo!();
/// let config = qdec::Config::default();
/// let mut q = Qdec::new(p.QDEC, p.P0_31, p.P0_30, config);
/// let mut q = Qdec::new(p.QDEC, Irqs, p.P0_31, p.P0_30, config);
/// let delta = q.read().await;
/// # };
/// ```
pub async fn read(&mut self) -> i16 {
let t = T::regs();

View File

@ -56,8 +56,19 @@ impl<'d> Temp<'d> {
/// # Example
///
/// ```no_run
/// let mut t = Temp::new(p.TEMP, interrupt::take!(TEMP));
/// use embassy_nrf::{bind_interrupts, temp};
/// use embassy_nrf::temp::Temp;
/// use embassy_time::{Duration, Timer};
///
/// bind_interrupts!(struct Irqs {
/// TEMP => temp::InterruptHandler;
/// });
///
/// # async {
/// # let p: embassy_nrf::Peripherals = todo!();
/// let mut t = Temp::new(p.TEMP, Irqs);
/// let v: u16 = t.read().await.to_num::<u16>();
/// # };
/// ```
pub async fn read(&mut self) -> I30F2 {
// In case the future is dropped, stop the task and reset events.

View File

@ -67,7 +67,7 @@ fn compare_n(n: usize) -> u32 {
1 << (n + 16)
}
#[cfg(tests)]
#[cfg(test)]
mod test {
use super::*;

View File

@ -13,6 +13,8 @@ flavors = [
]
[features]
default = [ "rp-pac/rt" ]
defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"]
# critical section that is safe for multicore use
@ -70,7 +72,7 @@ embedded-storage = { version = "0.3" }
rand_core = "0.6.4"
fixed = "1.23.1"
rp-pac = { version = "4", features = ["rt"] }
rp-pac = { version = "4" }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
@ -81,3 +83,7 @@ paste = "1.0"
pio-proc = {version= "0.2" }
pio = {version= "0.2.1" }
rp2040-boot2 = "0.3"
[dev-dependencies]
embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
static_cell = "1.0"

View File

@ -575,6 +575,7 @@ mod ram_helpers {
#[inline(never)]
#[link_section = ".data.ram_func"]
unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) {
#[cfg(target_arch = "arm")]
core::arch::asm!(
"mov r10, r0", // cmd
"mov r5, r1", // ptrs

View File

@ -61,16 +61,17 @@ macro_rules! intrinsics_aliases {
/// Like the compiler-builtins macro, it accepts a series of functions that
/// looks like normal Rust code:
///
/// ```rust,ignore
/// intrinsics! {
/// extern "C" fn foo(a: i32) -> u32 {
/// // ...
/// }
///
/// #[nonstandard_attribute]
/// extern "C" fn bar(a: i32) -> u32 {
/// // ...
/// }
/// }
/// ```
///
/// Each function can also be decorated with nonstandard attributes to control
/// additional behaviour:

View File

@ -9,22 +9,41 @@
//! the `embassy-sync` primitives and `CriticalSectionRawMutex`.
//!
//! # Usage
//!
//! ```no_run
//! # #![feature(type_alias_impl_trait)]
//! use embassy_rp::multicore::Stack;
//! use static_cell::StaticCell;
//! use embassy_executor::Executor;
//!
//! static mut CORE1_STACK: Stack<4096> = Stack::new();
//! static EXECUTOR0: StaticCell<Executor> = StaticCell::new();
//! static EXECUTOR1: StaticCell<Executor> = StaticCell::new();
//!
//! # // workaround weird error: `main` function not found in crate `rust_out`
//! # let _ = ();
//!
//! #[embassy_executor::task]
//! async fn core0_task() {
//! // ...
//! }
//!
//! #[embassy_executor::task]
//! async fn core1_task() {
//! // ...
//! }
//!
//! #[cortex_m_rt::entry]
//! fn main() -> ! {
//! let p = embassy_rp::init(Default::default());
//!
//! spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
//! embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
//! let executor1 = EXECUTOR1.init(Executor::new());
//! executor1.run(|spawner| unwrap!(spawner.spawn(core1_task())));
//! executor1.run(|spawner| spawner.spawn(core1_task()).unwrap());
//! });
//!
//! let executor0 = EXECUTOR0.init(Executor::new());
//! executor0.run(|spawner| unwrap!(spawner.spawn(core0_task())));
//! executor0.run(|spawner| spawner.spawn(core0_task()).unwrap())
//! }
//! ```

View File

@ -121,7 +121,7 @@ impl<'d, T: Instance> RealTimeClock<'d, T> {
/// # #[cfg(not(feature = "chrono"))]
/// # fn main() {
/// # use embassy_rp::rtc::{RealTimeClock, DateTimeFilter};
/// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() };
/// # let mut real_time_clock: RealTimeClock<embassy_rp::peripherals::RTC> = unsafe { core::mem::zeroed() };
/// let now = real_time_clock.now().unwrap();
/// real_time_clock.schedule_alarm(
/// DateTimeFilter::default()

View File

@ -912,6 +912,16 @@ fn main() {
println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
}
// ========
// stm32wb tl_mbox link sections
if chip_name.starts_with("stm32wb") {
let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string();
fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap();
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rerun-if-changed=tl_mbox.x.in");
}
// =======
// Features for targeting groups of chips

View File

@ -111,24 +111,18 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
}
let mut wake = false;
if isr.htif(channel_num) && cr.read().htie() {
// Acknowledge half transfer complete interrupt
dma.ifcr().write(|w| w.set_htif(channel_num, true));
wake = true;
}
if isr.tcif(channel_num) && cr.read().tcie() {
} else if isr.tcif(channel_num) && cr.read().tcie() {
// Acknowledge transfer complete interrupt
dma.ifcr().write(|w| w.set_tcif(channel_num, true));
STATE.complete_count[index].fetch_add(1, Ordering::Release);
wake = true;
} else {
return;
}
if wake {
STATE.ch_wakers[index].wake();
}
}
#[cfg(any(bdma_v2, dmamux))]
@ -371,7 +365,7 @@ impl<'a, C: Channel> Future for Transfer<'a, C> {
struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>);
impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> {
fn ndtr(&self) -> usize {
fn get_remaining_transfers(&self) -> usize {
let ch = self.0.regs().ch(self.0.num());
unsafe { ch.ndtr().read() }.ndt() as usize
}
@ -457,21 +451,17 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
}
/// Read bytes from the ring buffer
/// Return a tuple of the length read and the length remaining in the buffer
/// If not all of the bytes were read, then there will be some bytes in the buffer remaining
/// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, buf: &mut [W]) -> Result<usize, OverrunError> {
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf)
}
pub fn is_empty(&self) -> bool {
self.ringbuf.is_empty()
}
pub fn len(&self) -> usize {
self.ringbuf.len()
}
pub fn capacity(&self) -> usize {
self.ringbuf.dma_buf.len()
/// The capacity of the ringbuffer
pub fn cap(&self) -> usize {
self.ringbuf.cap()
}
pub fn set_waker(&mut self, waker: &Waker) {
@ -506,12 +496,6 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
let ch = self.channel.regs().ch(self.channel.num());
unsafe { ch.cr().read() }.en()
}
/// Synchronize the position of the ring buffer to the actual DMA controller position
pub fn reload_position(&mut self) {
let ch = self.channel.regs().ch(self.channel.num());
self.ringbuf.ndtr = unsafe { ch.ndtr().read() }.ndt() as usize;
}
}
impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> {

View File

@ -187,24 +187,18 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: usize, index:
panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num);
}
let mut wake = false;
if isr.htif(channel_num % 4) && cr.read().htie() {
// Acknowledge half transfer complete interrupt
dma.ifcr(channel_num / 4).write(|w| w.set_htif(channel_num % 4, true));
wake = true;
}
if isr.tcif(channel_num % 4) && cr.read().tcie() {
} else if isr.tcif(channel_num % 4) && cr.read().tcie() {
// Acknowledge transfer complete interrupt
dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true));
STATE.complete_count[index].fetch_add(1, Ordering::Release);
wake = true;
} else {
return;
}
if wake {
STATE.ch_wakers[index].wake();
}
}
#[cfg(any(dma_v2, dmamux))]
@ -612,7 +606,7 @@ impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> {
struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>);
impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> {
fn ndtr(&self) -> usize {
fn get_remaining_transfers(&self) -> usize {
let ch = self.0.regs().st(self.0.num());
unsafe { ch.ndtr().read() }.ndt() as usize
}
@ -713,21 +707,17 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
}
/// Read bytes from the ring buffer
/// Return a tuple of the length read and the length remaining in the buffer
/// If not all of the bytes were read, then there will be some bytes in the buffer remaining
/// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, buf: &mut [W]) -> Result<usize, OverrunError> {
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf)
}
pub fn is_empty(&self) -> bool {
self.ringbuf.is_empty()
}
pub fn len(&self) -> usize {
self.ringbuf.len()
}
pub fn capacity(&self) -> usize {
self.ringbuf.dma_buf.len()
// The capacity of the ringbuffer
pub fn cap(&self) -> usize {
self.ringbuf.cap()
}
pub fn set_waker(&mut self, waker: &Waker) {
@ -766,12 +756,6 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> {
let ch = self.channel.regs().st(self.channel.num());
unsafe { ch.cr().read() }.en()
}
/// Synchronize the position of the ring buffer to the actual DMA controller position
pub fn reload_position(&mut self) {
let ch = self.channel.regs().st(self.channel.num());
self.ringbuf.ndtr = unsafe { ch.ndtr().read() }.ndt() as usize;
}
}
impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> {

View File

@ -25,14 +25,13 @@ use super::word::Word;
/// +-----------------------------------------+ +-----------------------------------------+
/// ^ ^ ^ ^ ^ ^
/// | | | | | |
/// +- first --+ | +- end ------+ |
/// +- start --+ | +- end ------+ |
/// | | | |
/// +- end --------------------+ +- first ----------------+
/// +- end --------------------+ +- start ----------------+
/// ```
pub struct DmaRingBuffer<'a, W: Word> {
pub(crate) dma_buf: &'a mut [W],
first: usize,
pub ndtr: usize,
start: usize,
}
#[derive(Debug, PartialEq)]
@ -41,7 +40,7 @@ pub struct OverrunError;
pub trait DmaCtrl {
/// Get the NDTR register value, i.e. the space left in the underlying
/// buffer until the dma writer wraps.
fn ndtr(&self) -> usize;
fn get_remaining_transfers(&self) -> usize;
/// Get the transfer completed counter.
/// This counter is incremented by the dma controller when NDTR is reloaded,
@ -54,151 +53,131 @@ pub trait DmaCtrl {
impl<'a, W: Word> DmaRingBuffer<'a, W> {
pub fn new(dma_buf: &'a mut [W]) -> Self {
let ndtr = dma_buf.len();
Self {
dma_buf,
first: 0,
ndtr,
}
Self { dma_buf, start: 0 }
}
/// Reset the ring buffer to its initial state
pub fn clear(&mut self, mut dma: impl DmaCtrl) {
self.first = 0;
self.ndtr = self.dma_buf.len();
self.start = 0;
dma.reset_complete_count();
}
/// The buffer end position
fn end(&self) -> usize {
self.dma_buf.len() - self.ndtr
/// The capacity of the ringbuffer
pub const fn cap(&self) -> usize {
self.dma_buf.len()
}
/// Returns whether the buffer is empty
pub fn is_empty(&self) -> bool {
self.first == self.end()
}
/// The current number of bytes in the buffer
/// This may change at any time if dma is currently active
pub fn len(&self) -> usize {
// Read out a stable end (the dma periheral can change it at anytime)
let end = self.end();
if self.first <= end {
// No wrap
end - self.first
} else {
self.dma_buf.len() - self.first + end
}
/// The current position of the ringbuffer
fn pos(&self, remaining_transfers: usize) -> usize {
self.cap() - remaining_transfers
}
/// Read bytes from the ring buffer
/// Return a tuple of the length read and the length remaining in the buffer
/// If not all of the bytes were read, then there will be some bytes in the buffer remaining
/// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<usize, OverrunError> {
let end = self.end();
pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
/*
This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check
after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed
to fire in the same clock cycle that a register is read, so checking get_complete_count early does
not yield relevant information.
compiler_fence(Ordering::SeqCst);
Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full
buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error
conditions.
if self.first == end {
// The buffer is currently empty
if dma.get_complete_count() > 0 {
// The DMA has written such that the ring buffer wraps at least once
self.ndtr = dma.ndtr();
if self.end() > self.first || dma.get_complete_count() > 1 {
return Err(OverrunError);
}
}
Ok(0)
} else if self.first < end {
After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that
the dma has not overrun within the data we could have copied. We check the data we could have copied
rather than the data we actually copied because it costs nothing and confirms an error condition
earlier.
*/
let end = self.pos(dma.get_remaining_transfers());
if self.start == end && dma.get_complete_count() == 0 {
// No bytes are available in the buffer
Ok((0, self.cap()))
} else if self.start < end {
// The available, unread portion in the ring buffer DOES NOT wrap
if dma.get_complete_count() > 1 {
return Err(OverrunError);
}
// Copy out the bytes from the dma buffer
let len = self.copy_to(buf, self.first..end);
let len = self.copy_to(buf, self.start..end);
compiler_fence(Ordering::SeqCst);
match dma.get_complete_count() {
0 => {
// The DMA writer has not wrapped before nor after the copy
}
1 => {
// The DMA writer has written such that the ring buffer now wraps
self.ndtr = dma.ndtr();
if self.end() > self.first || dma.get_complete_count() > 1 {
// The bytes that we have copied out have overflowed
// as the writer has now both wrapped and is currently writing
// within the region that we have just copied out
return Err(OverrunError);
}
}
_ => {
return Err(OverrunError);
}
}
/*
first, check if the dma has wrapped at all if it's after end
or more than once if it's before start
self.first = (self.first + len) % self.dma_buf.len();
Ok(len)
this is in a critical section to try to reduce mushy behavior.
it's not ideal but it's the best we can do
then, get the current position of of the dma write and check
if it's inside data we could have copied
*/
let (pos, complete_count) =
critical_section::with(|_| (self.pos(dma.get_remaining_transfers()), dma.get_complete_count()));
if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 {
Err(OverrunError)
} else {
self.start = (self.start + len) % self.cap();
Ok((len, self.cap() - self.start))
}
} else if self.start + buf.len() < self.cap() {
// The available, unread portion in the ring buffer DOES wrap
// The DMA writer has wrapped since we last read and is currently
// writing (or the next byte added will be) in the beginning of the ring buffer.
// The provided read buffer is not large enough to include all bytes from the tail of the dma buffer.
// Copy out from the dma buffer
let len = self.copy_to(buf, self.start..self.cap());
compiler_fence(Ordering::SeqCst);
/*
first, check if the dma has wrapped around more than once
then, get the current position of of the dma write and check
if it's inside data we could have copied
*/
let pos = self.pos(dma.get_remaining_transfers());
if pos > self.start || pos < end || dma.get_complete_count() > 1 {
Err(OverrunError)
} else {
self.start = (self.start + len) % self.cap();
Ok((len, self.start + end))
}
} else {
// The available, unread portion in the ring buffer DOES wrap
// The DMA writer has wrapped since we last read and is currently
// writing (or the next byte added will be) in the beginning of the ring buffer.
let complete_count = dma.get_complete_count();
if complete_count > 1 {
return Err(OverrunError);
}
// If the unread portion wraps then the writer must also have wrapped
assert!(complete_count == 1);
if self.first + buf.len() < self.dma_buf.len() {
// The provided read buffer is not large enough to include all bytes from the tail of the dma buffer.
// Copy out from the dma buffer
let len = self.copy_to(buf, self.first..self.dma_buf.len());
compiler_fence(Ordering::SeqCst);
// We have now copied out the data from dma_buf
// Make sure that the just read part was not overwritten during the copy
self.ndtr = dma.ndtr();
if self.end() > self.first || dma.get_complete_count() > 1 {
// The writer has entered the data that we have just read since we read out `end` in the beginning and until now.
return Err(OverrunError);
}
self.first = (self.first + len) % self.dma_buf.len();
Ok(len)
} else {
// The provided read buffer is large enough to include all bytes from the tail of the dma buffer,
// so the next read will not have any unread tail bytes in the ring buffer.
// Copy out from the dma buffer
let tail = self.copy_to(buf, self.first..self.dma_buf.len());
let tail = self.copy_to(buf, self.start..self.cap());
let head = self.copy_to(&mut buf[tail..], 0..end);
compiler_fence(Ordering::SeqCst);
// We have now copied out the data from dma_buf
// Reset complete counter and make sure that the just read part was not overwritten during the copy
self.ndtr = dma.ndtr();
let complete_count = dma.reset_complete_count();
if self.end() > self.first || complete_count > 1 {
return Err(OverrunError);
}
/*
first, check if the dma has wrapped around more than once
self.first = head;
Ok(tail + head)
then, get the current position of of the dma write and check
if it's inside data we could have copied
*/
let pos = self.pos(dma.get_remaining_transfers());
if pos > self.start || pos < end || dma.reset_complete_count() > 1 {
Err(OverrunError)
} else {
self.start = head;
Ok((tail + head, self.cap() - self.start))
}
}
}
/// Copy from the dma buffer at `data_range` into `buf`
fn copy_to(&mut self, buf: &mut [W], data_range: Range<usize>) -> usize {
// Limit the number of bytes that can be copied
@ -218,203 +197,289 @@ impl<'a, W: Word> DmaRingBuffer<'a, W> {
length
}
}
#[cfg(test)]
mod tests {
use core::array;
use core::cell::RefCell;
use std::{cell, vec};
use super::*;
struct TestCtrl {
next_ndtr: RefCell<Option<usize>>,
complete_count: usize,
#[allow(dead_code)]
#[derive(PartialEq, Debug)]
enum TestCircularTransferRequest {
GetCompleteCount(usize),
ResetCompleteCount(usize),
PositionRequest(usize),
}
impl TestCtrl {
pub const fn new() -> Self {
Self {
next_ndtr: RefCell::new(None),
complete_count: 0,
}
struct TestCircularTransfer {
len: usize,
requests: cell::RefCell<vec::Vec<TestCircularTransferRequest>>,
}
pub fn set_next_ndtr(&mut self, ndtr: usize) {
self.next_ndtr.borrow_mut().replace(ndtr);
}
}
impl DmaCtrl for &mut TestCircularTransfer {
fn get_remaining_transfers(&self) -> usize {
match self.requests.borrow_mut().pop().unwrap() {
TestCircularTransferRequest::PositionRequest(pos) => {
let len = self.len;
impl DmaCtrl for &mut TestCtrl {
fn ndtr(&self) -> usize {
self.next_ndtr.borrow_mut().unwrap()
assert!(len >= pos);
len - pos
}
_ => unreachable!(),
}
}
fn get_complete_count(&self) -> usize {
self.complete_count
match self.requests.borrow_mut().pop().unwrap() {
TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count,
_ => unreachable!(),
}
}
fn reset_complete_count(&mut self) -> usize {
let old = self.complete_count;
self.complete_count = 0;
old
match self.requests.get_mut().pop().unwrap() {
TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count,
_ => unreachable!(),
}
}
}
impl TestCircularTransfer {
pub fn new(len: usize) -> Self {
Self {
requests: cell::RefCell::new(vec![]),
len: len,
}
}
pub fn setup(&self, mut requests: vec::Vec<TestCircularTransferRequest>) {
requests.reverse();
self.requests.replace(requests);
}
}
#[test]
fn empty() {
fn empty_and_read_not_started() {
let mut dma_buf = [0u8; 16];
let ringbuf = DmaRingBuffer::new(&mut dma_buf);
assert!(ringbuf.is_empty());
assert_eq!(0, ringbuf.len());
assert_eq!(0, ringbuf.start);
}
#[test]
fn can_read() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.ndtr = 6;
assert!(!ringbuf.is_empty());
assert_eq!(10, ringbuf.len());
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::PositionRequest(10),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!([0, 1], buf);
assert_eq!(8, ringbuf.len());
assert_eq!(2, ringbuf.start);
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(10),
TestCircularTransferRequest::PositionRequest(12),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!([2, 3], buf);
assert_eq!(6, ringbuf.len());
assert_eq!(4, ringbuf.start);
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(12),
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 8];
assert_eq!(6, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]);
assert_eq!(0, ringbuf.len());
let mut buf = [0; 2];
assert_eq!(0, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!(12, ringbuf.start);
}
#[test]
fn can_read_with_wrap() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 12;
ringbuf.ndtr = 10;
// The dma controller has written 4 + 6 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(10);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
assert!(!ringbuf.is_empty());
assert_eq!(6 + 4, ringbuf.len());
/*
Read to close to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::PositionRequest(16),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 14];
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(14, ringbuf.start);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([12, 13], buf);
assert_eq!(6 + 2, ringbuf.len());
let mut buf = [0; 4];
assert_eq!(4, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([14, 15, 0, 1], buf);
assert_eq!(4, ringbuf.len());
/*
Now, read around the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::ResetCompleteCount(1),
]);
let mut buf = [0; 6];
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(4, ringbuf.start);
}
#[test]
fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 2;
ringbuf.ndtr = 6;
// The dma controller has written 6 + 2 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(14);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
/*
Read to close to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::PositionRequest(16),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 14];
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(14, ringbuf.start);
/*
Now, read to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::ResetCompleteCount(1),
]);
let mut buf = [0; 2];
assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([2, 3], buf);
assert_eq!(1, ctrl.complete_count); // The interrupt flag IS NOT cleared
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(0, ringbuf.start);
}
#[test]
fn can_read_when_dma_writer_is_wrapped_and_read_wraps() {
fn can_read_when_dma_writer_wraps_once_with_same_ndtr() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 12;
ringbuf.ndtr = 10;
// The dma controller has written 6 + 2 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(14);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
let mut buf = [0; 10];
assert_eq!(10, ringbuf.read(&mut ctrl, &mut buf).unwrap());
assert_eq!([12, 13, 14, 15, 0, 1, 2, 3, 4, 5], buf);
/*
Read to about the middle of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 6];
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(6, ringbuf.start);
assert_eq!(0, ctrl.complete_count); // The interrupt flag IS cleared
}
#[test]
fn cannot_read_when_dma_writer_wraps_with_same_ndtr() {
let mut dma_buf = [0u8; 16];
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 6;
ringbuf.ndtr = 10;
ctrl.set_next_ndtr(9);
assert!(ringbuf.is_empty()); // The ring buffer thinks that it is empty
// The dma controller has written exactly 16 bytes
ctrl.complete_count = 1;
let mut buf = [0; 2];
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
/*
Now, wrap the DMA controller around
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(1),
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(1),
]);
let mut buf = [0; 6];
assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(12, ringbuf.start);
}
#[test]
fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 2;
ringbuf.ndtr = 6;
// The dma controller has written 6 + 3 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(13);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
let mut buf = [0; 2];
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
/*
Read a few bytes
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(2),
TestCircularTransferRequest::PositionRequest(2),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 6];
assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(2, ringbuf.start);
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
/*
Now, overtake the reader
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(4),
TestCircularTransferRequest::PositionRequest(6),
TestCircularTransferRequest::GetCompleteCount(1),
]);
let mut buf = [0; 6];
assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err());
}
#[test]
fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() {
let mut dma = TestCircularTransfer::new(16);
let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15
let mut ctrl = TestCtrl::new();
let mut ringbuf = DmaRingBuffer::new(&mut dma_buf);
ringbuf.first = 12;
ringbuf.ndtr = 10;
// The dma controller has written 6 + 13 bytes and has reloaded NDTR
ctrl.complete_count = 1;
ctrl.set_next_ndtr(3);
assert_eq!(0, ringbuf.start);
assert_eq!(16, ringbuf.cap());
let mut buf = [0; 2];
assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf));
/*
Read to close to the end of the buffer
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(14),
TestCircularTransferRequest::PositionRequest(16),
TestCircularTransferRequest::GetCompleteCount(0),
]);
let mut buf = [0; 14];
assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0);
assert_eq!(14, ringbuf.start);
assert_eq!(1, ctrl.complete_count); // The complete counter is not reset
/*
Now, overtake the reader
*/
dma.setup(vec![
TestCircularTransferRequest::PositionRequest(8),
TestCircularTransferRequest::PositionRequest(10),
TestCircularTransferRequest::ResetCompleteCount(2),
]);
let mut buf = [0; 6];
assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err());
}
}

View File

@ -163,7 +163,7 @@ pub(super) fn get_sector(address: u32, regions: &[&FlashRegion]) -> FlashSector
bank_offset = 0;
}
if address < region.end() {
if address >= region.base && address < region.end() {
let index_in_region = (address - region.base) / region.erase_size;
return FlashSector {
bank: region.bank,

View File

@ -1,4 +1,4 @@
#![no_std]
#![cfg_attr(not(test), no_std)]
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))]
// This must go FIRST so that all the other modules see its macros.
@ -41,8 +41,6 @@ pub mod crc;
pub mod flash;
#[cfg(all(spi_v1, rcc_f4))]
pub mod i2s;
#[cfg(stm32wb)]
pub mod ipcc;
pub mod pwm;
#[cfg(quadspi)]
pub mod qspi;

View File

@ -209,39 +209,39 @@ mod tests {
#[test]
fn test_compute_dead_time_value() {
struct test_run {
struct TestRun {
value: u16,
ckd: Ckd,
bits: u8,
}
let fn_results = [
test_run {
TestRun {
value: 1,
ckd: Ckd::DIV1,
bits: 1,
},
test_run {
TestRun {
value: 125,
ckd: Ckd::DIV1,
bits: 125,
},
test_run {
TestRun {
value: 245,
ckd: Ckd::DIV1,
bits: 64 + 245 / 2,
},
test_run {
TestRun {
value: 255,
ckd: Ckd::DIV2,
bits: 127,
},
test_run {
TestRun {
value: 400,
ckd: Ckd::DIV1,
bits: 32 + (400u16 / 8) as u8,
},
test_run {
TestRun {
value: 600,
ckd: Ckd::DIV4,
bits: 64 + (600u16 / 8) as u8,

View File

@ -1,5 +1,3 @@
use core::mem::MaybeUninit;
use embassy_futures::block_on;
use super::cmd::CmdSerial;
@ -10,17 +8,17 @@ use super::{
channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE, TL_CHANNEL,
TL_REF_TABLE,
};
use crate::ipcc::Ipcc;
use crate::tl_mbox::cmd::CmdPacket;
use crate::tl_mbox::ipcc::Ipcc;
pub struct Ble;
impl Ble {
pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
pub fn enable() {
unsafe {
LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr());
TL_BLE_TABLE = MaybeUninit::new(BleTable {
TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable {
pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(),
pcs_buffer: CS_BUFFER.as_mut_ptr().cast(),
pevt_queue: EVT_QUEUE.as_ptr().cast(),
@ -28,12 +26,10 @@ impl Ble {
});
}
ipcc.c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true);
Ble
Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true);
}
pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
pub fn evt_handler() {
unsafe {
let mut node_ptr = core::ptr::null_mut();
let node_ptr_ptr: *mut _ = &mut node_ptr;
@ -48,10 +44,10 @@ impl Ble {
}
}
ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
}
pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
pub fn send_cmd(buf: &[u8]) {
unsafe {
let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer;
let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial;
@ -63,6 +59,6 @@ impl Ble {
cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8;
}
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL);
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL);
}
}

View File

@ -50,7 +50,7 @@
//!
pub mod cpu1 {
use crate::ipcc::IpccChannel;
use crate::tl_mbox::ipcc::IpccChannel;
// Not used currently but reserved
pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1;
@ -75,7 +75,7 @@ pub mod cpu1 {
}
pub mod cpu2 {
use crate::ipcc::IpccChannel;
use crate::tl_mbox::ipcc::IpccChannel;
pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1;
pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2;

View File

@ -3,7 +3,7 @@ use core::mem::MaybeUninit;
use super::cmd::{AclDataPacket, AclDataSerial};
use super::consts::TlPacketType;
use super::{PacketHeader, TL_EVT_HEADER_SIZE};
use crate::tl_mbox::mm;
use crate::tl_mbox::mm::MemoryManager;
/// the payload of [`Evt`] for a command status event
#[derive(Copy, Clone)]
@ -131,9 +131,6 @@ impl EvtBox {
impl Drop for EvtBox {
fn drop(&mut self) {
use crate::ipcc::Ipcc;
let mut ipcc = Ipcc::new_inner(unsafe { crate::Peripherals::steal() }.IPCC);
mm::MemoryManager::evt_drop(self.ptr, &mut ipcc);
MemoryManager::evt_drop(self.ptr);
}
}

View File

@ -1,6 +1,4 @@
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use crate::ipcc::sealed::Instance;
use self::sealed::Instance;
use crate::peripherals::IPCC;
use crate::rcc::sealed::RccPeripheral;
@ -22,29 +20,17 @@ pub enum IpccChannel {
Channel6 = 5,
}
pub(crate) mod sealed {
pub mod sealed {
pub trait Instance: crate::rcc::RccPeripheral {
fn regs() -> crate::pac::ipcc::Ipcc;
fn set_cpu2(enabled: bool);
}
}
pub struct Ipcc<'d> {
_peri: PeripheralRef<'d, IPCC>,
}
pub struct Ipcc;
impl<'d> Ipcc<'d> {
pub fn new(peri: impl Peripheral<P = IPCC> + 'd, _config: Config) -> Self {
Self::new_inner(peri)
}
pub(crate) fn new_inner(peri: impl Peripheral<P = IPCC> + 'd) -> Self {
into_ref!(peri);
Self { _peri: peri }
}
pub fn init(&mut self) {
impl Ipcc {
pub fn enable(_config: Config) {
IPCC::enable();
IPCC::reset();
IPCC::set_cpu2(true);
@ -61,56 +47,60 @@ impl<'d> Ipcc<'d> {
}
}
pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
pub fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
}
pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool {
pub fn c1_get_rx_channel(channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
}
pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
#[allow(dead_code)]
pub fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
}
pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool {
#[allow(dead_code)]
pub fn c2_get_rx_channel(channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
}
pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
pub fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
}
pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool {
pub fn c1_get_tx_channel(channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
}
pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
#[allow(dead_code)]
pub fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
}
pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool {
#[allow(dead_code)]
pub fn c2_get_tx_channel(channel: IpccChannel) -> bool {
let regs = IPCC::regs();
// If bit is set to 1 then interrupt is disabled
@ -118,53 +108,51 @@ impl<'d> Ipcc<'d> {
}
/// clears IPCC receive channel status for CPU1
pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) {
pub fn c1_clear_flag_channel(channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
}
#[allow(dead_code)]
/// clears IPCC receive channel status for CPU2
pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) {
pub fn c2_clear_flag_channel(channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
}
pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) {
pub fn c1_set_flag_channel(channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
}
pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) {
#[allow(dead_code)]
pub fn c2_set_flag_channel(channel: IpccChannel) {
let regs = IPCC::regs();
unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
}
pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool {
pub fn c1_is_active_flag(channel: IpccChannel) -> bool {
let regs = IPCC::regs();
unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
}
pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool {
pub fn c2_is_active_flag(channel: IpccChannel) -> bool {
let regs = IPCC::regs();
unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
}
pub fn is_tx_pending(&self, channel: IpccChannel) -> bool {
!self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel)
pub fn is_tx_pending(channel: IpccChannel) -> bool {
!Self::c1_is_active_flag(channel) && Self::c1_get_tx_channel(channel)
}
pub fn is_rx_pending(&self, channel: IpccChannel) -> bool {
self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel)
}
pub fn as_mut_ptr(&self) -> *mut Self {
unsafe { &mut core::ptr::read(self) as *mut _ }
pub fn is_rx_pending(channel: IpccChannel) -> bool {
Self::c2_is_active_flag(channel) && Self::c1_get_rx_channel(channel)
}
}

View File

@ -1,22 +1,20 @@
use core::mem::MaybeUninit;
use super::evt::EvtPacket;
use super::unsafe_linked_list::LinkedListNode;
use super::{
channels, MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE,
SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, TL_REF_TABLE,
};
use crate::ipcc::Ipcc;
use crate::tl_mbox::ipcc::Ipcc;
pub struct MemoryManager;
impl MemoryManager {
pub fn new() -> Self {
pub fn enable() {
unsafe {
LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr());
LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr());
TL_MEM_MANAGER_TABLE = MaybeUninit::new(MemManagerTable {
TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable {
spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(),
spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(),
ble_pool: EVT_POOL.as_ptr().cast(),
@ -26,31 +24,29 @@ impl MemoryManager {
traces_pool_size: 0,
});
}
MemoryManager
}
pub fn evt_handler(ipcc: &mut Ipcc) {
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false);
pub fn evt_handler() {
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false);
Self::send_free_buf();
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
}
pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) {
pub fn evt_drop(evt: *mut EvtPacket) {
unsafe {
let list_node = evt.cast();
LinkedListNode::remove_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node);
}
let channel_is_busy = ipcc.c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
let channel_is_busy = Ipcc::c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
// postpone event buffer freeing to IPCC interrupt handler
if channel_is_busy {
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true);
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true);
} else {
Self::send_free_buf();
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
}
}

View File

@ -1,6 +1,9 @@
use core::mem::MaybeUninit;
use atomic_polyfill::{compiler_fence, Ordering};
use bit_field::BitField;
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel;
@ -12,13 +15,16 @@ use self::shci::{shci_ble_init, ShciBleInitCmdParam};
use self::sys::Sys;
use self::unsafe_linked_list::LinkedListNode;
use crate::interrupt;
use crate::ipcc::Ipcc;
use crate::peripherals::IPCC;
pub use crate::tl_mbox::ipcc::Config;
use crate::tl_mbox::ipcc::Ipcc;
mod ble;
mod channels;
mod cmd;
mod consts;
mod evt;
mod ipcc;
mod mm;
mod shci;
mod sys;
@ -58,13 +64,34 @@ pub struct FusInfoTable {
pub struct ReceiveInterruptHandler {}
impl interrupt::Handler<interrupt::IPCC_C1_RX> for ReceiveInterruptHandler {
unsafe fn on_interrupt() {}
unsafe fn on_interrupt() {
// info!("ipcc rx interrupt");
if Ipcc::is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
sys::Sys::evt_handler();
} else if Ipcc::is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) {
ble::Ble::evt_handler();
} else {
todo!()
}
}
}
pub struct TransmitInterruptHandler {}
impl interrupt::Handler<interrupt::IPCC_C1_TX> for TransmitInterruptHandler {
unsafe fn on_interrupt() {}
unsafe fn on_interrupt() {
// info!("ipcc tx interrupt");
if Ipcc::is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
// TODO: handle this case
let _ = sys::Sys::cmd_evt_handler();
} else if Ipcc::is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) {
mm::MemoryManager::evt_handler();
} else {
todo!()
}
}
}
/// # Version
@ -289,21 +316,24 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251
// TODO: get a better size, this is a placeholder
pub(crate) static TL_CHANNEL: Channel<CriticalSectionRawMutex, EvtBox, 5> = Channel::new();
pub struct TlMbox {
_sys: Sys,
_ble: Ble,
_mm: MemoryManager,
pub struct TlMbox<'d> {
_ipcc: PeripheralRef<'d, IPCC>,
}
impl TlMbox {
impl<'d> TlMbox<'d> {
/// initializes low-level transport between CPU1 and BLE stack on CPU2
pub fn init(
ipcc: &mut Ipcc,
pub fn new(
ipcc: impl Peripheral<P = IPCC> + 'd,
_irqs: impl interrupt::Binding<interrupt::IPCC_C1_RX, ReceiveInterruptHandler>
+ interrupt::Binding<interrupt::IPCC_C1_TX, TransmitInterruptHandler>,
) -> TlMbox {
config: Config,
) -> Self {
into_ref!(ipcc);
unsafe {
TL_REF_TABLE = MaybeUninit::new(RefTable {
compiler_fence(Ordering::AcqRel);
TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable {
device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(),
ble_table: TL_BLE_TABLE.as_ptr(),
thread_table: TL_THREAD_TABLE.as_ptr(),
@ -316,6 +346,10 @@ impl TlMbox {
ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(),
});
// info!("TL_REF_TABLE addr: {:x}", TL_REF_TABLE.as_ptr() as usize);
compiler_fence(Ordering::AcqRel);
TL_SYS_TABLE = MaybeUninit::zeroed();
TL_DEVICE_INFO_TABLE = MaybeUninit::zeroed();
TL_BLE_TABLE = MaybeUninit::zeroed();
@ -334,33 +368,24 @@ impl TlMbox {
CS_BUFFER = MaybeUninit::zeroed();
BLE_CMD_BUFFER = MaybeUninit::zeroed();
HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed();
compiler_fence(Ordering::AcqRel);
}
ipcc.init();
Ipcc::enable(config);
let _sys = Sys::new(ipcc);
let _ble = Ble::new(ipcc);
let _mm = MemoryManager::new();
Sys::enable();
Ble::enable();
MemoryManager::enable();
// rx_irq.disable();
// tx_irq.disable();
//
// rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
// tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
//
// rx_irq.set_handler(|ipcc| {
// let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
// Self::interrupt_ipcc_rx_handler(ipcc);
// });
// tx_irq.set_handler(|ipcc| {
// let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
// Self::interrupt_ipcc_tx_handler(ipcc);
// });
//
// rx_irq.enable();
// tx_irq.enable();
// enable interrupts
unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend();
unsafe { crate::interrupt::IPCC_C1_TX::steal() }.unpend();
TlMbox { _sys, _ble, _mm }
unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable();
unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable();
Self { _ipcc: ipcc }
}
pub fn wireless_fw_info(&self) -> Option<WirelessFwInfoTable> {
@ -374,42 +399,19 @@ impl TlMbox {
}
}
pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
shci_ble_init(ipcc, param);
pub fn shci_ble_init(&self, param: ShciBleInitCmdParam) {
shci_ble_init(param);
}
pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
ble::Ble::send_cmd(ipcc, buf);
pub fn send_ble_cmd(&self, buf: &[u8]) {
ble::Ble::send_cmd(buf);
}
// pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
// sys::Sys::send_cmd(ipcc, buf);
// pub fn send_sys_cmd(&self, buf: &[u8]) {
// sys::Sys::send_cmd(buf);
// }
pub async fn read(&self) -> EvtBox {
TL_CHANNEL.recv().await
}
#[allow(dead_code)]
fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) {
if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
sys::Sys::evt_handler(ipcc);
} else if ipcc.is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) {
ble::Ble::evt_handler(ipcc);
} else {
todo!()
}
}
#[allow(dead_code)]
fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) {
if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
// TODO: handle this case
let _ = sys::Sys::cmd_evt_handler(ipcc);
} else if ipcc.is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) {
mm::MemoryManager::evt_handler(ipcc);
} else {
todo!()
}
}
}

View File

@ -3,7 +3,7 @@
use super::cmd::CmdPacket;
use super::consts::TlPacketType;
use super::{channels, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE, TL_SYS_TABLE};
use crate::ipcc::Ipcc;
use crate::tl_mbox::ipcc::Ipcc;
const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66;
pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE;
@ -76,7 +76,7 @@ pub struct ShciBleInitCmdPacket {
param: ShciBleInitCmdParam,
}
pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
pub fn shci_ble_init(param: ShciBleInitCmdParam) {
let mut packet = ShciBleInitCmdPacket {
header: ShciHeader::default(),
param,
@ -95,7 +95,7 @@ pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8;
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
}
}

View File

@ -1,5 +1,3 @@
use core::mem::MaybeUninit;
use embassy_futures::block_on;
use super::cmd::{CmdPacket, CmdSerial};
@ -7,27 +5,25 @@ use super::consts::TlPacketType;
use super::evt::{CcEvt, EvtBox, EvtSerial};
use super::unsafe_linked_list::LinkedListNode;
use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE};
use crate::ipcc::Ipcc;
use crate::tl_mbox::ipcc::Ipcc;
pub struct Sys;
impl Sys {
pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
pub fn enable() {
unsafe {
LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr());
TL_SYS_TABLE = MaybeUninit::new(SysTable {
TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable {
pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(),
sys_queue: SYSTEM_EVT_QUEUE.as_ptr(),
});
}
ipcc.c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true);
Sys
Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true);
}
pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
pub fn evt_handler() {
unsafe {
let mut node_ptr = core::ptr::null_mut();
let node_ptr_ptr: *mut _ = &mut node_ptr;
@ -43,11 +39,11 @@ impl Sys {
}
}
ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL);
Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL);
}
pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) -> CcEvt {
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false);
pub fn cmd_evt_handler() -> CcEvt {
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false);
// ST's command response data structure is really convoluted.
//
@ -68,11 +64,11 @@ impl Sys {
}
#[allow(dead_code)]
pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
pub fn send_cmd(buf: &[u8]) {
unsafe {
// TODO: check this
let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
let cmd_serial: *mut CmdSerial = &mut (*cmd_buffer).cmd_serial;
let cmd_serial: *mut CmdSerial = &mut cmd_buffer.cmd_serial;
let cmd_serial_buf = cmd_serial.cast();
core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len());
@ -80,8 +76,8 @@ impl Sys {
let cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8;
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
}
}
}

View File

@ -13,6 +13,12 @@ use futures::future::{select, Either};
use crate::dma::{NoDma, Transfer};
use crate::gpio::sealed::AFType;
#[cfg(not(any(usart_v1, usart_v2)))]
#[allow(unused_imports)]
use crate::pac::usart::regs::Isr as Sr;
#[cfg(any(usart_v1, usart_v2))]
#[allow(unused_imports)]
use crate::pac::usart::regs::Sr;
#[cfg(not(any(usart_v1, usart_v2)))]
use crate::pac::usart::Lpuart as Regs;
#[cfg(any(usart_v1, usart_v2))]
use crate::pac::usart::Usart as Regs;
@ -32,7 +38,6 @@ impl<T: BasicInstance> interrupt::Handler<T::Interrupt> for InterruptHandler<T>
let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) };
let mut wake = false;
let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie());
if has_errors {
// clear all interrupts and DMA Rx Request
@ -52,10 +57,7 @@ impl<T: BasicInstance> interrupt::Handler<T::Interrupt> for InterruptHandler<T>
w.set_dmar(false);
});
}
wake = true;
} else {
if cr1.idleie() && sr.idle() {
} else if cr1.idleie() && sr.idle() {
// IDLE detected: no more data will come
unsafe {
r.cr1().modify(|w| {
@ -63,25 +65,17 @@ impl<T: BasicInstance> interrupt::Handler<T::Interrupt> for InterruptHandler<T>
w.set_idleie(false);
});
}
wake = true;
}
if cr1.rxneie() {
} else if cr1.rxneie() {
// We cannot check the RXNE flag as it is auto-cleared by the DMA controller
// It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection
wake = true;
}
} else {
return;
}
if wake {
compiler_fence(Ordering::SeqCst);
s.rx_waker.wake();
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -1109,9 +1103,9 @@ pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler;
mod buffered;
#[cfg(not(gpdma))]
mod rx_ringbuffered;
mod ringbuffered;
#[cfg(not(gpdma))]
pub use rx_ringbuffered::RingBufferedUartRx;
pub use ringbuffered::RingBufferedUartRx;
use self::sealed::Kind;

View File

@ -2,13 +2,12 @@ use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::PeripheralRef;
use futures::future::{select, Either};
use super::{clear_interrupt_flags, rdr, sr, BasicInstance, Error, UartRx};
use crate::dma::ringbuffer::OverrunError;
use crate::dma::RingBuffer;
use crate::usart::{Regs, Sr};
pub struct RingBufferedUartRx<'d, T: BasicInstance, RxDma: super::RxDma<T>> {
_peri: PeripheralRef<'d, T>,
@ -24,7 +23,9 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> UartRx<'d, T, RxDma> {
let request = self.rx_dma.request();
let opts = Default::default();
let ring_buf = unsafe { RingBuffer::new_read(self.rx_dma, request, rdr(T::regs()), dma_buf, opts) };
RingBufferedUartRx {
_peri: self._peri,
ring_buf,
@ -42,11 +43,18 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
Ok(())
}
fn stop(&mut self, err: Error) -> Result<usize, Error> {
self.teardown_uart();
Err(err)
}
/// Start uart background receive
fn setup_uart(&mut self) {
// fence before starting DMA.
compiler_fence(Ordering::SeqCst);
// start the dma controller
self.ring_buf.start();
let r = T::regs();
@ -58,8 +66,8 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
w.set_rxneie(false);
// enable parity interrupt if not ParityNone
w.set_peie(w.pce());
// disable idle line interrupt
w.set_idleie(false);
// enable idle line interrupt
w.set_idleie(true);
});
r.cr3().modify(|w| {
// enable Error Interrupt: (Frame error, Noise error, Overrun error)
@ -72,6 +80,8 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
/// Stop uart background receive
fn teardown_uart(&mut self) {
self.ring_buf.request_stop();
let r = T::regs();
// clear all interrupts and DMA Rx Request
// SAFETY: only clears Rx related flags
@ -93,9 +103,6 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
}
compiler_fence(Ordering::SeqCst);
self.ring_buf.request_stop();
while self.ring_buf.is_running() {}
}
/// Read bytes that are readily available in the ring buffer.
@ -111,96 +118,49 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
// Start background receive if it was not already started
// SAFETY: read only
let is_started = unsafe { r.cr3().read().dmar() };
if !is_started {
self.start()?;
}
match unsafe { r.cr3().read().dmar() } {
false => self.start()?,
_ => {}
};
// SAFETY: read only and we only use Rx related flags
let s = unsafe { sr(r).read() };
let has_errors = s.pe() || s.fe() || s.ne() || s.ore();
if has_errors {
self.teardown_uart();
if s.pe() {
return Err(Error::Parity);
} else if s.fe() {
return Err(Error::Framing);
} else if s.ne() {
return Err(Error::Noise);
} else {
return Err(Error::Overrun);
}
}
self.ring_buf.reload_position();
match self.ring_buf.read(buf) {
Ok(len) if len == 0 => {}
Ok(len) => {
assert!(len > 0);
return Ok(len);
}
Err(OverrunError) => {
// Stop any transfer from now on
// The user must re-start to receive any more data
self.teardown_uart();
return Err(Error::Overrun);
}
}
check_for_errors(clear_idle_flag(T::regs()))?;
loop {
self.wait_for_data_or_idle().await?;
self.ring_buf.reload_position();
if !self.ring_buf.is_empty() {
break;
match self.ring_buf.read(buf) {
Ok((0, _)) => {}
Ok((len, _)) => {
return Ok(len);
}
Err(_) => {
return self.stop(Error::Overrun);
}
}
let len = self.ring_buf.read(buf).map_err(|_err| Error::Overrun)?;
assert!(len > 0);
Ok(len)
match self.wait_for_data_or_idle().await {
Ok(_) => {}
Err(err) => {
return self.stop(err);
}
}
}
}
/// Wait for uart idle or dma half-full or full
async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> {
let r = T::regs();
// make sure USART state is restored to neutral state
let _on_drop = OnDrop::new(move || {
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable idle line interrupt
w.set_idleie(false);
});
}
});
// SAFETY: only sets Rx related flags
unsafe {
r.cr1().modify(|w| {
// enable idle line interrupt
w.set_idleie(true);
});
}
compiler_fence(Ordering::SeqCst);
let mut dma_init = false;
// Future which completes when there is dma is half full or full
let dma = poll_fn(|cx| {
self.ring_buf.set_waker(cx.waker());
compiler_fence(Ordering::SeqCst);
let status = match dma_init {
false => Poll::Pending,
true => Poll::Ready(()),
};
self.ring_buf.reload_position();
if !self.ring_buf.is_empty() {
// Some data is now available
Poll::Ready(())
} else {
Poll::Pending
}
dma_init = true;
status
});
// Future which completes when idle line is detected
@ -210,28 +170,11 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
compiler_fence(Ordering::SeqCst);
// SAFETY: read only and we only use Rx related flags
let sr = unsafe { sr(r).read() };
// Critical section is needed so that IDLE isn't set after
// our read but before we clear it.
let sr = critical_section::with(|_| clear_idle_flag(T::regs()));
// SAFETY: only clears Rx related flags
unsafe {
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
}
let has_errors = sr.pe() || sr.fe() || sr.ne() || sr.ore();
if has_errors {
if sr.pe() {
return Poll::Ready(Err(Error::Parity));
} else if sr.fe() {
return Poll::Ready(Err(Error::Framing));
} else if sr.ne() {
return Poll::Ready(Err(Error::Noise));
} else {
return Poll::Ready(Err(Error::Overrun));
}
}
check_for_errors(sr)?;
if sr.idle() {
// Idle line is detected
@ -243,11 +186,7 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD
match select(dma, uart).await {
Either::Left(((), _)) => Ok(()),
Either::Right((Ok(()), _)) => Ok(()),
Either::Right((Err(e), _)) => {
self.teardown_uart();
Err(e)
}
Either::Right((result, _)) => result,
}
}
}
@ -257,6 +196,37 @@ impl<T: BasicInstance, RxDma: super::RxDma<T>> Drop for RingBufferedUartRx<'_, T
self.teardown_uart();
}
}
/// Return an error result if the Sr register has errors
fn check_for_errors(s: Sr) -> Result<(), Error> {
if s.pe() {
Err(Error::Parity)
} else if s.fe() {
Err(Error::Framing)
} else if s.ne() {
Err(Error::Noise)
} else if s.ore() {
Err(Error::Overrun)
} else {
Ok(())
}
}
/// Clear IDLE and return the Sr register
fn clear_idle_flag(r: Regs) -> Sr {
unsafe {
// SAFETY: read only and we only use Rx related flags
let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
r.cr1().modify(|w| w.set_idleie(true));
sr
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
mod eio {

View File

@ -1,21 +1,13 @@
/*
Memory size for STM32WB55xG with 512K FLASH
*/
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8
RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K
}
/* Place stack at the end of SRAM1 */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
/*
* Scatter the mailbox interface memory sections in shared memory
*/
SECTIONS {
SECTIONS
{
TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED
MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED

View File

@ -169,4 +169,4 @@ wasm-timer = { version = "0.2.5", optional = true }
[dev-dependencies]
serial_test = "0.9"
critical-section = { version = "1.1", features = ["std"] }
embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly"] }

View File

@ -49,7 +49,7 @@
//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
//! todo!()
//! }
//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
//! todo!()
//! }
//! }

View File

@ -183,7 +183,6 @@ mod tests {
use serial_test::serial;
use super::InnerQueue;
use crate::driver::{AlarmHandle, Driver};
use crate::queue_generic::QUEUE;
use crate::Instant;
@ -317,14 +316,18 @@ mod tests {
fn setup() {
DRIVER.reset();
QUEUE.inner.lock(|inner| {
*inner.borrow_mut() = InnerQueue::new();
});
critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None);
}
fn queue_len() -> usize {
QUEUE.inner.lock(|inner| inner.borrow().queue.iter().count())
critical_section::with(|cs| {
QUEUE
.inner
.borrow_ref(cs)
.as_ref()
.map(|inner| inner.queue.iter().count())
.unwrap_or(0)
})
}
#[test]

View File

@ -109,7 +109,6 @@ impl Future for Timer {
/// # #![feature(type_alias_impl_trait)]
/// #
/// use embassy_time::{Duration, Ticker};
/// use futures::StreamExt;
/// # fn foo(){}
///
/// #[embassy_executor::task]

View File

@ -0,0 +1,3 @@
[profile.release]
# Allows defmt to display log locations even in release
debug = true

View File

@ -3,12 +3,13 @@
#![macro_use]
#![feature(type_alias_impl_trait)]
use embassy_boot_nrf::FirmwareUpdater;
use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
use embassy_nrf::nvmc::Nvmc;
use embassy_nrf::wdt::{self, Watchdog};
use embassy_sync::mutex::Mutex;
use panic_reset as _;
static APP_B: &[u8] = include_bytes!("../../b.bin");
@ -45,9 +46,10 @@ async fn main(_spawner: Spawner) {
};
let nvmc = Nvmc::new(p.NVMC);
let mut nvmc = BlockingAsync::new(nvmc);
let nvmc = Mutex::new(BlockingAsync::new(nvmc));
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc);
let mut updater = FirmwareUpdater::new(config);
loop {
led.set_low();
button.wait_for_any_edge().await;
@ -56,11 +58,11 @@ async fn main(_spawner: Spawner) {
for chunk in APP_B.chunks(4096) {
let mut buf: [u8; 4096] = [0; 4096];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = [0; 4];
updater.mark_updated(&mut nvmc, &mut magic).await.unwrap();
updater.mark_updated(&mut magic).await.unwrap();
led.set_high();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -20,6 +20,7 @@ embedded-hal = { version = "0.2.6" }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"
embedded-storage = "0.3.0"
[features]
default = ["panic-reset"]

View File

@ -2,13 +2,17 @@
#![no_main]
#![feature(type_alias_impl_trait)]
use core::cell::RefCell;
use defmt_rtt as _;
use embassy_boot_rp::*;
use embassy_executor::Spawner;
use embassy_rp::flash::Flash;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::watchdog::Watchdog;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::{Duration, Timer};
use embedded_storage::nor_flash::NorFlash;
#[cfg(feature = "panic-probe")]
use panic_probe as _;
#[cfg(feature = "panic-reset")]
@ -26,9 +30,11 @@ async fn main(_s: Spawner) {
let mut watchdog = Watchdog::new(p.WATCHDOG);
watchdog.start(Duration::from_secs(8));
let mut flash: Flash<_, FLASH_SIZE> = Flash::new_blocking(p.FLASH);
let flash: Flash<_, FLASH_SIZE> = Flash::new(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut updater = BlockingFirmwareUpdater::new(config);
Timer::after(Duration::from_secs(5)).await;
watchdog.feed();
@ -36,22 +42,20 @@ async fn main(_s: Spawner) {
let mut offset = 0;
let mut buf: AlignedBuffer<4096> = AlignedBuffer([0; 4096]);
defmt::info!("preparing update");
let mut writer = updater
.prepare_update_blocking(&mut flash)
let writer = updater
.prepare_update()
.map_err(|e| defmt::warn!("E: {:?}", defmt::Debug2Format(&e)))
.unwrap();
defmt::info!("writer created, starting write");
for chunk in APP_B.chunks(4096) {
buf.0[..chunk.len()].copy_from_slice(chunk);
defmt::info!("writing block at offset {}", offset);
writer
.write_block_blocking(offset, &buf.0[..], &mut flash, 256)
.unwrap();
offset += chunk.len();
writer.write(offset, &buf.0[..]).unwrap();
offset += chunk.len() as u32;
}
watchdog.feed();
defmt::info!("firmware written, marking update");
updater.mark_updated_blocking(&mut flash, &mut buf.0[..1]).unwrap();
updater.mark_updated(&mut buf.0[..1]).unwrap();
Timer::after(Duration::from_secs(2)).await;
led.set_low();
defmt::info!("update marked, resetting");

View File

@ -4,12 +4,13 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use embassy_sync::mutex::Mutex;
use panic_reset as _;
static APP_B: &[u8] = include_bytes!("../../b.bin");
@ -18,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PC13, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -26,17 +27,18 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PA5, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
let mut offset = 0;
for chunk in APP_B.chunks(2048) {
let mut buf: [u8; 2048] = [0; 2048];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
updater.mark_updated(magic.as_mut()).await.unwrap();
led.set_low();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig};
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::{Flash, WRITE_SIZE};
@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let mut flash = Flash::new_blocking(p.FLASH);
let flash = Flash::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let button = Input::new(p.PC13, Pull::Down);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -24,20 +25,19 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB7, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let mut writer = updater.prepare_update_blocking(&mut flash).unwrap();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut updater = BlockingFirmwareUpdater::new(config);
let mut writer = updater.prepare_update().unwrap();
button.wait_for_rising_edge().await;
let mut offset = 0;
let mut buf = AlignedBuffer([0; 4096]);
for chunk in APP_B.chunks(4096) {
buf.as_mut()[..chunk.len()].copy_from_slice(chunk);
writer
.write_block_blocking(offset, buf.as_ref(), &mut flash, chunk.len())
.unwrap();
writer.write(offset, buf.as_ref()).unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap();
updater.mark_updated(magic.as_mut()).unwrap();
led.set_low();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig};
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::{Flash, WRITE_SIZE};
@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let mut flash = Flash::new_blocking(p.FLASH);
let flash = Flash::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let button = Input::new(p.PC13, Pull::Down);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -24,21 +25,19 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let mut writer = updater.prepare_update_blocking(&mut flash).unwrap();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut updater = BlockingFirmwareUpdater::new(config);
let mut writer = updater.prepare_update().unwrap();
button.wait_for_rising_edge().await;
let mut offset = 0;
let mut buf = AlignedBuffer([0; 4096]);
for chunk in APP_B.chunks(4096) {
buf.as_mut()[..chunk.len()].copy_from_slice(chunk);
writer
.write_block_blocking(offset, buf.as_ref(), &mut flash, 4096)
.unwrap();
writer.write(offset, buf.as_ref()).unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap();
updater.mark_updated(magic.as_mut()).unwrap();
led.set_low();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
@ -19,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PB2, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI2);
@ -28,18 +28,19 @@ async fn main(_spawner: Spawner) {
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
let mut offset = 0;
for chunk in APP_B.chunks(128) {
let mut buf: [u8; 128] = [0; 128];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 128).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
updater.mark_updated(magic.as_mut()).await.unwrap();
led.set_low();
Timer::after(Duration::from_secs(1)).await;
cortex_m::peripheral::SCB::sys_reset();

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PC13, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -26,13 +26,14 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
let mut offset = 0;
for chunk in APP_B.chunks(2048) {
let mut buf: [u8; 2048] = [0; 2048];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);

View File

@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let mut flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PA0, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI0);
@ -26,7 +26,8 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB9, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
//defmt::info!("Starting update");
let mut offset = 0;
@ -34,11 +35,11 @@ async fn main(_spawner: Spawner) {
let mut buf: [u8; 2048] = [0; 2048];
buf[..chunk.len()].copy_from_slice(chunk);
// defmt::info!("Writing chunk at 0x{:x}", offset);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
updater.mark_updated(magic.as_mut()).await.unwrap();
//defmt::info!("Marked as updated");
led.set_low();
cortex_m::peripheral::SCB::sys_reset();

View File

@ -9,9 +9,10 @@ license = "MIT OR Apache-2.0"
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
embassy-nrf = { path = "../../../../embassy-nrf", default-features = false, features = ["nightly"] }
embassy-boot-nrf = { path = "../../../../embassy-boot/nrf", default-features = false }
embassy-nrf = { path = "../../../../embassy-nrf", features = ["nightly"] }
embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
embassy-sync = { path = "../../../../embassy-sync" }
cortex-m-rt = { version = "0.7" }
cfg-if = "1.0.0"

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