Compare commits
2 Commits
main
...
stm32-ring
Author | SHA1 | Date | |
---|---|---|---|
|
b49bc449d7 | ||
|
a21a2901ac |
41
.gitattributes
vendored
41
.gitattributes
vendored
@ -1,41 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
*.adoc text
|
||||
*.html text
|
||||
*.in text
|
||||
*.json text
|
||||
*.md text
|
||||
*.proto text
|
||||
*.py text
|
||||
*.rs text
|
||||
*.service text
|
||||
*.sh text
|
||||
*.toml text
|
||||
*.txt text
|
||||
*.x text
|
||||
*.yml text
|
||||
|
||||
*.raw binary
|
||||
*.bin binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.flv binary
|
||||
*.fla binary
|
||||
*.swf binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.pdf binary
|
||||
*.ez binary
|
||||
*.bz2 binary
|
||||
*.swp binary
|
4
.github/bors.toml
vendored
Normal file
4
.github/bors.toml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
status = [
|
||||
"all",
|
||||
]
|
||||
delete_merged_branches = true
|
29
.github/ci/build-nightly.sh
vendored
29
.github/ci/build-nightly.sh
vendored
@ -1,29 +0,0 @@
|
||||
#!/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
|
||||
mv rust-toolchain-nightly.toml rust-toolchain.toml
|
||||
|
||||
# needed for "dumb HTTP" transport support
|
||||
# used when pointing stm32-metapac to a CI-built one.
|
||||
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
|
||||
# Restore lockfiles
|
||||
if [ -f /ci/cache/lockfiles.tar ]; then
|
||||
echo Restoring lockfiles...
|
||||
tar xf /ci/cache/lockfiles.tar
|
||||
fi
|
||||
|
||||
hashtime restore /ci/cache/filetime.json || true
|
||||
hashtime save /ci/cache/filetime.json
|
||||
|
||||
./ci-nightly.sh
|
||||
|
||||
# Save lockfiles
|
||||
echo Saving lockfiles...
|
||||
find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+
|
34
.github/ci/build.sh
vendored
34
.github/ci/build.sh
vendored
@ -1,34 +0,0 @@
|
||||
#!/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)
|
||||
export TELEPROBE_CACHE=/ci/cache/teleprobe_cache.json
|
||||
fi
|
||||
|
||||
# needed for "dumb HTTP" transport support
|
||||
# used when pointing stm32-metapac to a CI-built one.
|
||||
export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
||||
|
||||
# Restore lockfiles
|
||||
if [ -f /ci/cache/lockfiles.tar ]; then
|
||||
echo Restoring lockfiles...
|
||||
tar xf /ci/cache/lockfiles.tar
|
||||
fi
|
||||
|
||||
hashtime restore /ci/cache/filetime.json || true
|
||||
hashtime save /ci/cache/filetime.json
|
||||
|
||||
./ci.sh
|
||||
|
||||
# Save lockfiles
|
||||
echo Saving lockfiles...
|
||||
find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+
|
17
.github/ci/crlf.sh
vendored
17
.github/ci/crlf.sh
vendored
@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
## on push branch~=gh-readonly-queue/main/.*
|
||||
## on pull_request
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FILES_WITH_CRLF=$(find ! -path "./.git/*" -not -type d | xargs file -N | (grep " CRLF " || true))
|
||||
|
||||
if [ -z "$FILES_WITH_CRLF" ]; then
|
||||
echo -e "No files with CRLF endings found."
|
||||
exit 0
|
||||
else
|
||||
NR_FILES=$(echo "$FILES_WITH_CRLF" | wc -l)
|
||||
echo -e "ERROR: Found ${NR_FILES} files with CRLF endings."
|
||||
echo "$FILES_WITH_CRLF"
|
||||
exit "$NR_FILES"
|
||||
fi
|
54
.github/ci/doc.sh
vendored
54
.github/ci/doc.sh
vendored
@ -1,54 +0,0 @@
|
||||
#!/bin/bash
|
||||
## on push branch=main
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
export BUILDER_THREADS=4
|
||||
export BUILDER_COMPRESS=true
|
||||
|
||||
# force rustup to download the toolchain before starting building.
|
||||
# Otherwise, the docs builder is running multiple instances of cargo rustdoc concurrently.
|
||||
# They all see the toolchain is not installed and try to install it in parallel
|
||||
# which makes rustup very sad
|
||||
rustc --version > /dev/null
|
||||
|
||||
docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup
|
||||
docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup
|
||||
docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup
|
||||
docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup
|
||||
docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup
|
||||
docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup
|
||||
docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup
|
||||
docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup
|
||||
docserver-builder -i ./embassy-net-driver -o webroot/crates/embassy-net-driver/git.zup
|
||||
docserver-builder -i ./embassy-net-driver-channel -o webroot/crates/embassy-net-driver-channel/git.zup
|
||||
docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup
|
||||
docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup
|
||||
docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup
|
||||
docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup
|
||||
docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup
|
||||
docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup
|
||||
docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup
|
||||
docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup
|
||||
docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup
|
||||
docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/git.zup
|
||||
docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup
|
||||
docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup
|
||||
docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static
|
||||
docserver-builder -i ./embassy-net-adin1110 -o webroot/crates/embassy-net-adin1110/git.zup
|
||||
|
||||
export KUBECONFIG=/ci/secrets/kubeconfig.yml
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp webroot/crates $POD:/data
|
||||
kubectl cp webroot/static $POD:/data
|
||||
|
||||
# build and upload stm32 last
|
||||
# so that it doesn't prevent other crates from getting docs updates when it breaks.
|
||||
|
||||
rm -rf webroot
|
||||
docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp webroot/crates $POD:/data
|
12
.github/ci/rustfmt.sh
vendored
12
.github/ci/rustfmt.sh
vendored
@ -1,12 +0,0 @@
|
||||
#!/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
|
||||
mv rust-toolchain-nightly.toml rust-toolchain.toml
|
||||
|
||||
find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2021
|
13
.github/ci/test-nightly.sh
vendored
13
.github/ci/test-nightly.sh
vendored
@ -1,13 +0,0 @@
|
||||
#!/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
|
||||
mv rust-toolchain-nightly.toml rust-toolchain.toml
|
||||
|
||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
|
||||
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
28
.github/ci/test.sh
vendored
28
.github/ci/test.sh
vendored
@ -1,28 +0,0 @@
|
||||
#!/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
|
||||
|
||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue
|
||||
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-dalek
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-salty
|
||||
|
||||
cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nrf52840,time-driver-rtc1,gpiote
|
||||
|
||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver
|
||||
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,exti,time-driver-any,exti
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,exti,time-driver-any,exti
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,exti,time-driver-any,exti
|
||||
|
||||
cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml
|
87
.github/workflows/doc.yml
vendored
Normal file
87
.github/workflows/doc.yml
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
BUILDER_THREADS: '1'
|
||||
|
||||
jobs:
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Since stm32 crates take SO LONG to build, we split them
|
||||
# into a separate job. This way it doesn't slow down updating
|
||||
# the rest.
|
||||
strategy:
|
||||
matrix:
|
||||
crates:
|
||||
#- stm32 # runs out of disk space...
|
||||
- rest
|
||||
|
||||
# This will ensure at most one doc build job is running at a time
|
||||
# (for stm32 and non-stm32 independently).
|
||||
# If another job is already running, the new job will wait.
|
||||
# If another job is already waiting, it'll be canceled.
|
||||
# This means some commits will be skipped, but that's fine because
|
||||
# we only care that the latest gets built.
|
||||
concurrency: doc-${{ matrix.crates }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Rust targets
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
rustup target add wasm32-unknown-unknown
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv7em-none-eabi
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
rustup target add thumbv8m.base-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabihf
|
||||
|
||||
- name: Install docserver
|
||||
run: |
|
||||
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder"
|
||||
chmod +x /usr/local/bin/builder
|
||||
|
||||
- name: build-stm32
|
||||
if: ${{ matrix.crates=='stm32' }}
|
||||
run: |
|
||||
mkdir crates
|
||||
builder ./embassy-stm32 crates/embassy-stm32/git.zup
|
||||
|
||||
- name: build-rest
|
||||
if: ${{ matrix.crates=='rest' }}
|
||||
run: |
|
||||
mkdir crates
|
||||
builder ./embassy-boot/boot crates/embassy-boot/git.zup
|
||||
builder ./embassy-boot/nrf crates/embassy-boot-nrf/git.zup
|
||||
builder ./embassy-boot/rp crates/embassy-boot-rp/git.zup
|
||||
builder ./embassy-boot/stm32 crates/embassy-boot-stm32/git.zup
|
||||
builder ./embassy-cortex-m crates/embassy-cortex-m/git.zup
|
||||
builder ./embassy-embedded-hal crates/embassy-embedded-hal/git.zup
|
||||
builder ./embassy-executor crates/embassy-executor/git.zup
|
||||
builder ./embassy-futures crates/embassy-futures/git.zup
|
||||
builder ./embassy-lora crates/embassy-lora/git.zup
|
||||
builder ./embassy-net crates/embassy-net/git.zup
|
||||
builder ./embassy-net-driver crates/embassy-net-driver/git.zup
|
||||
builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup
|
||||
builder ./embassy-nrf crates/embassy-nrf/git.zup
|
||||
builder ./embassy-rp crates/embassy-rp/git.zup
|
||||
builder ./embassy-sync crates/embassy-sync/git.zup
|
||||
builder ./embassy-time crates/embassy-time/git.zup
|
||||
builder ./embassy-usb crates/embassy-usb/git.zup
|
||||
builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup
|
||||
builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup
|
||||
|
||||
- name: upload
|
||||
run: |
|
||||
mkdir -p ~/.kube
|
||||
echo "${{secrets.KUBECONFIG}}" > ~/.kube/config
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp crates $POD:/data
|
78
.github/workflows/rust.yml
vendored
Normal file
78
.github/workflows/rust.yml
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
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@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
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
|
||||
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@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
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@v2
|
||||
|
||||
- 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
|
4
.vscode/.gitignore
vendored
4
.vscode/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
*.cortex-debug.*.json
|
||||
launch.json
|
||||
tasks.json
|
||||
*.cfg
|
21
.vscode/settings.json
vendored
21
.vscode/settings.json
vendored
@ -3,23 +3,19 @@
|
||||
"[toml]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"rust-analyzer.check.allTargets": false,
|
||||
"rust-analyzer.check.noDefaultFeatures": true,
|
||||
"rust-analyzer.cargo.noDefaultFeatures": true,
|
||||
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||
// uncomment the target of your chip.
|
||||
//"rust-analyzer.cargo.target": "thumbv6m-none-eabi",
|
||||
//"rust-analyzer.cargo.target": "thumbv7m-none-eabi",
|
||||
"rust-analyzer.cargo.target": "thumbv7em-none-eabi",
|
||||
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
|
||||
"rust-analyzer.cargo.features": [
|
||||
"nightly",
|
||||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
// Uncomment ONE line for the chip you want to work on.
|
||||
// This makes rust-analyzer work on the example crate and all its dependencies.
|
||||
"examples/stm32l4/Cargo.toml",
|
||||
// "examples/nrf52840-rtic/Cargo.toml",
|
||||
// Declare for the target you wish to develop
|
||||
// "embassy-executor/Cargo.toml",
|
||||
// "embassy-sync/Cargo.toml",
|
||||
"examples/nrf52840/Cargo.toml",
|
||||
// "examples/nrf5340/Cargo.toml",
|
||||
// "examples/nrf-rtos-trace/Cargo.toml",
|
||||
// "examples/rp/Cargo.toml",
|
||||
@ -29,7 +25,6 @@
|
||||
// "examples/stm32f1/Cargo.toml",
|
||||
// "examples/stm32f2/Cargo.toml",
|
||||
// "examples/stm32f3/Cargo.toml",
|
||||
// "examples/stm32f334/Cargo.toml",
|
||||
// "examples/stm32f4/Cargo.toml",
|
||||
// "examples/stm32f7/Cargo.toml",
|
||||
// "examples/stm32g0/Cargo.toml",
|
||||
@ -45,4 +40,4 @@
|
||||
// "examples/stm32wl/Cargo.toml",
|
||||
// "examples/wasm/Cargo.toml",
|
||||
],
|
||||
}
|
||||
}
|
30
README.md
30
README.md
@ -33,10 +33,9 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
|
||||
|
||||
- **Bluetooth** -
|
||||
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
The <a href="https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan">embassy-stm32-wpan</a> crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers.
|
||||
|
||||
- **LoRa** -
|
||||
<a href="hthttps://github.com/lora-rs/lora-rs">The lora-rs project</a> provides an async LoRa and LoRaWAN stack that works well on Embassy.
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
|
||||
|
||||
- **USB** -
|
||||
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
@ -62,9 +61,9 @@ async fn blink(pin: AnyPin) {
|
||||
loop {
|
||||
// Timekeeping is globally available, no need to mess with hardware timers.
|
||||
led.set_high();
|
||||
Timer::after_millis(150).await;
|
||||
Timer::after(Duration::from_millis(150)).await;
|
||||
led.set_low();
|
||||
Timer::after_millis(150).await;
|
||||
Timer::after(Duration::from_millis(150)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,10 +99,17 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer
|
||||
|
||||
### Running examples
|
||||
|
||||
- Install `probe-rs`.
|
||||
- Setup git submodules (needed for STM32 examples)
|
||||
|
||||
```bash
|
||||
cargo install probe-rs --features cli
|
||||
git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
- Install `probe-rs-cli` with defmt support.
|
||||
|
||||
```bash
|
||||
cargo install probe-rs-cli
|
||||
```
|
||||
|
||||
- Change directory to the sample's base directory. For example:
|
||||
@ -112,22 +118,14 @@ cargo install probe-rs --features cli
|
||||
cd examples/nrf52840
|
||||
```
|
||||
|
||||
- Ensure `Cargo.toml` sets the right feature for the name of the chip you are programming.
|
||||
If this name is incorrect, the example may fail to run or immediately crash
|
||||
after being programmed.
|
||||
|
||||
- Ensure `.cargo/config.toml` contains the name of the chip you are programming.
|
||||
|
||||
- Run the example
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
cargo run --release --bin blinky
|
||||
cargo run --bin blinky
|
||||
```
|
||||
|
||||
For more help getting started, see [Getting Started][1] and [Running the Examples][2].
|
||||
|
||||
## Developing Embassy with Rust Analyzer based editors
|
||||
|
||||
The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/)
|
||||
@ -160,5 +158,3 @@ This work is licensed under either of
|
||||
|
||||
at your option.
|
||||
|
||||
[1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started
|
||||
[2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples
|
||||
|
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
export RUSTFLAGS=-Dwarnings
|
||||
export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
|
||||
if [[ -z "${CARGO_TARGET_DIR}" ]]; then
|
||||
export CARGO_TARGET_DIR=target_ci
|
||||
fi
|
||||
|
||||
cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread,integrated-timers \
|
||||
--- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \
|
||||
|
278
ci.sh
278
ci.sh
@ -1,12 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
export CARGO_TARGET_DIR=$PWD/target_ci
|
||||
export RUSTFLAGS=-Dwarnings
|
||||
export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
|
||||
if [[ -z "${CARGO_TARGET_DIR}" ]]; then
|
||||
export CARGO_TARGET_DIR=target_ci
|
||||
fi
|
||||
export DEFMT_LOG=trace
|
||||
|
||||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||||
|
||||
@ -15,123 +13,66 @@ 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
|
||||
|
||||
cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,integrated-timers \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \
|
||||
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \
|
||||
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52820,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832,gpiote,time-driver-rtc1,reset-pin-as-gpio \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833,gpiote,time-driver-rtc1,nfc-pins-as-gpio \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-s,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-ns,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,log,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,defmt,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features log \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features intrinsics \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features qspi-as-gpio \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f401ve,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f405zg,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f407zg,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f401ve,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f405zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f407zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f412zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f413vh,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f415zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f417zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f423zh,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f427zi,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f437zi,log,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f439zi,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446ze,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f469zi,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f479zi,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f730i8,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h735zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h725re,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l422cb,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb15cc,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l041f6,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073cz,defmt,exti,time-driver-any,low-power,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f398ve,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f378cc,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g0c1ve,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32wl54jc-cm0p,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wle5jb,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g474pe,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f107vc,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103re,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f100c4,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32h503rb,defmt,exti,time-driver-any,time \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32h562ag,defmt,exti,time-driver-any,time \
|
||||
--- 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 \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1,reset-pin-as-gpio \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits,nfc-pins-as-gpio \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-ns,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-net,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52840,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52840,log,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52840,defmt,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,log \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f730i8,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wb15cc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l072cz,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32g0c1ve,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- 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 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 \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4,nightly \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
|
||||
@ -144,7 +85,6 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \
|
||||
--- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \
|
||||
--- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \
|
||||
--- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f334 \
|
||||
--- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \
|
||||
--- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \
|
||||
--- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \
|
||||
@ -158,70 +98,64 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32l5 \
|
||||
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \
|
||||
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \
|
||||
--- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \
|
||||
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \
|
||||
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \
|
||||
--- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wb-dfu \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/boot/nrf --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns --out-dir out/examples/boot/nrf --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/rp --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/stm32l0 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/boot/stm32l1 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf \
|
||||
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --out-dir out/tests/stm32f446re \
|
||||
--- 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 stm32h753zi --out-dir out/tests/stm32h753zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --out-dir out/tests/stm32h7a3zi \
|
||||
--- 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/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wba52cg --out-dir out/tests/stm32wba52cg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --out-dir out/tests/stm32l4r5zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --out-dir out/tests/stm32l552ze \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f767zi --out-dir out/tests/stm32f767zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --out-dir out/tests/stm32f207zg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --out-dir out/tests/stm32f303ze \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --out-dir out/tests/stm32l496zg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --out-dir out/tests/stm32wl55jc \
|
||||
--- 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/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
|
||||
|
||||
|
||||
rm out/tests/stm32wb55rg/wpan_mac
|
||||
rm out/tests/stm32wb55rg/wpan_ble
|
||||
|
||||
# not in CI yet.
|
||||
rm -rf out/tests/stm32f446re
|
||||
|
||||
# unstable, I think it's running out of RAM?
|
||||
rm out/tests/stm32f207zg/eth
|
||||
|
||||
# doesn't work, gives "noise error", no idea why. usart_dma does pass.
|
||||
rm out/tests/stm32u5a5zj/usart
|
||||
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
|
||||
if [[ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN-}" ]]; then
|
||||
echo No teleprobe token found, skipping running HIL tests
|
||||
exit
|
||||
fi
|
||||
|
||||
export TELEPROBE_TOKEN=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value')
|
||||
fi
|
||||
|
||||
teleprobe client run -r out/tests
|
||||
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
|
||||
|
74
ci_stable.sh
Executable file
74
ci_stable.sh
Executable file
@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
|
||||
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 \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52820,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-s,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,log,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,defmt,gpiote,time-driver-rtc1,unstable-traits \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,defmt \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,log \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g473cc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55vy,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55cc-cm4,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r9zi,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303vc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf52840 --bin raw_spawn \
|
||||
--- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --no-default-features --out-dir out/examples/stm32l0 --bin raw_spawn \
|
Binary file not shown.
Binary file not shown.
@ -1,49 +0,0 @@
|
||||
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.
|
@ -1,9 +0,0 @@
|
||||
# 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)
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62
|
@ -1,22 +0,0 @@
|
||||
[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 }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/"
|
||||
target = "thumbv6m-none-eabi"
|
@ -1,17 +0,0 @@
|
||||
# cyw43-pio
|
||||
|
||||
RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. The PIO driver offloads SPI communication with the WiFi chip and improves throughput.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||
|
||||
at your option.
|
@ -1,232 +0,0 @@
|
||||
#![no_std]
|
||||
#![allow(async_fn_in_trait)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
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::{instr, Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
|
||||
use embassy_rp::{Peripheral, PeripheralRef};
|
||||
use fixed::FixedU32;
|
||||
use pio_proc::pio_asm;
|
||||
|
||||
/// SPI comms driven by PIO.
|
||||
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,
|
||||
{
|
||||
/// Create a new instance of PioSpi.
|
||||
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 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();
|
||||
let loaded_program = common.load_program(&program.program);
|
||||
cfg.use_program(&loaded_program, &[&pin_clk]);
|
||||
cfg.set_out_pins(&[&pin_io]);
|
||||
cfg.set_in_pins(&[&pin_io]);
|
||||
cfg.set_set_pins(&[&pin_io]);
|
||||
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: loaded_program.wrap.target,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write data to peripheral and return status.
|
||||
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 {
|
||||
instr::set_x(&mut self.sm, write_bits as u32);
|
||||
instr::set_y(&mut self.sm, read_bits as u32);
|
||||
instr::set_pindir(&mut self.sm, 0b1);
|
||||
instr::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
|
||||
}
|
||||
|
||||
/// Send command and read response into buffer.
|
||||
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 {
|
||||
instr::set_y(&mut self.sm, read_bits as u32);
|
||||
instr::set_x(&mut self.sm, write_bits as u32);
|
||||
instr::set_pindir(&mut self.sm, 0b1);
|
||||
instr::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;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
[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.2", path = "../embassy-time"}
|
||||
embassy-sync = { version = "0.5.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"}
|
||||
|
||||
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-rc.3" }
|
||||
num_enum = { version = "0.5.7", default-features = false }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/"
|
||||
target = "thumbv6m-none-eabi"
|
||||
features = ["defmt", "firmware-logs"]
|
@ -1,61 +0,0 @@
|
||||
# cyw43
|
||||
|
||||
Rust 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 (for power consumption optimization)
|
||||
|
||||
## Running the examples
|
||||
|
||||
- `cargo install probe-rs --features cli`
|
||||
- `cd examples/rp`
|
||||
### 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 wifi_ap_tcp_server`
|
||||
### Example 3: Connect to an existing network and create a server
|
||||
- `cargo run --release --bin wifi_tcp_server`
|
||||
|
||||
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.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## 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
328
cyw43/src/bus.rs
@ -1,328 +0,0 @@
|
||||
use embassy_futures::yield_now;
|
||||
use embassy_time::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_millis(20).await;
|
||||
self.pwr.set_high().unwrap();
|
||||
Timer::after_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[..buf.len() + 1]).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)
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
#![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_DISASSOC: u32 = 52;
|
||||
pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64;
|
||||
pub(crate) const IOCTL_CMD_SET_AP: u32 = 118;
|
||||
pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263;
|
||||
pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262;
|
||||
pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268;
|
||||
|
||||
pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0;
|
||||
pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1;
|
||||
pub(crate) const CHANNEL_TYPE_DATA: u8 = 2;
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
@ -1,537 +0,0 @@
|
||||
use core::cmp::{max, min};
|
||||
use core::iter::zip;
|
||||
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
|
||||
use embassy_time::Timer;
|
||||
|
||||
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};
|
||||
|
||||
/// Control errors.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// Status code.
|
||||
pub status: u32,
|
||||
}
|
||||
|
||||
/// Multicast errors.
|
||||
#[derive(Debug)]
|
||||
pub enum AddMulticastAddressError {
|
||||
/// Not a multicast address.
|
||||
NotMulticast,
|
||||
/// No free address slots.
|
||||
NoFreeSlots,
|
||||
}
|
||||
|
||||
/// Control driver.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize WiFi controller.
|
||||
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_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_millis(100).await;
|
||||
//self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...??
|
||||
//Timer::after_millis(100).await;
|
||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||
Timer::after_millis(100).await;
|
||||
self.set_iovar_u32("ampdu_mpdu", 4).await;
|
||||
Timer::after_millis(100).await;
|
||||
//self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes
|
||||
|
||||
//Timer::after_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_millis(100).await;
|
||||
|
||||
// set wifi up
|
||||
self.up().await;
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto
|
||||
self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any
|
||||
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
|
||||
|
||||
debug!("INIT DONE");
|
||||
}
|
||||
|
||||
/// Set the WiFi interface up.
|
||||
async fn up(&mut self) {
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
|
||||
}
|
||||
|
||||
/// Set the interface down.
|
||||
async fn down(&mut self) {
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
|
||||
}
|
||||
|
||||
/// Set power management mode.
|
||||
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;
|
||||
}
|
||||
|
||||
/// Join an unprotected network with the provided ssid.
|
||||
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
|
||||
}
|
||||
|
||||
/// Join an protected network with the provided ssid and passphrase.
|
||||
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_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 })
|
||||
}
|
||||
}
|
||||
|
||||
/// Set GPIO pin on WiFi chip.
|
||||
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
|
||||
}
|
||||
|
||||
/// Start open access point.
|
||||
pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) {
|
||||
self.start_ap(ssid, "", Security::OPEN, channel).await;
|
||||
}
|
||||
|
||||
/// Start WPA2 protected access point.
|
||||
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.down().await;
|
||||
|
||||
// Turn off APSTA mode
|
||||
self.set_iovar_u32("apsta", 0).await;
|
||||
|
||||
// Set wifi up again
|
||||
self.up().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_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
|
||||
}
|
||||
|
||||
/// Add specified address to the list of hardware addresses the device
|
||||
/// listens on. The address must be a Group address (I/G bit set). Up
|
||||
/// to 10 addresses are supported by the firmware. Returns the number of
|
||||
/// address slots filled after adding, or an error.
|
||||
pub async fn add_multicast_address(&mut self, address: [u8; 6]) -> Result<usize, AddMulticastAddressError> {
|
||||
// The firmware seems to ignore non-multicast addresses, so let's
|
||||
// prevent the user from adding them and wasting space.
|
||||
if address[0] & 0x01 != 1 {
|
||||
return Err(AddMulticastAddressError::NotMulticast);
|
||||
}
|
||||
|
||||
let mut buf = [0; 64];
|
||||
self.get_iovar("mcast_list", &mut buf).await;
|
||||
|
||||
let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize;
|
||||
let (used, free) = buf[4..].split_at_mut(n * 6);
|
||||
|
||||
if used.chunks(6).any(|a| a == address) {
|
||||
return Ok(n);
|
||||
}
|
||||
|
||||
if free.len() < 6 {
|
||||
return Err(AddMulticastAddressError::NoFreeSlots);
|
||||
}
|
||||
|
||||
free[..6].copy_from_slice(&address);
|
||||
let n = n + 1;
|
||||
buf[..4].copy_from_slice(&(n as u32).to_le_bytes());
|
||||
|
||||
self.set_iovar_v::<80>("mcast_list", &buf).await;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// Retrieve the list of configured multicast hardware addresses.
|
||||
pub async fn list_mulistcast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize {
|
||||
let mut buf = [0; 64];
|
||||
self.get_iovar("mcast_list", &mut buf).await;
|
||||
|
||||
let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize;
|
||||
let used = &buf[4..][..n * 6];
|
||||
|
||||
for (addr, output) in zip(used.chunks(6), result.iter_mut()) {
|
||||
output.copy_from_slice(addr)
|
||||
}
|
||||
|
||||
n
|
||||
}
|
||||
|
||||
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);
|
||||
let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).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,
|
||||
}
|
||||
}
|
||||
/// Leave the wifi, with which we are currently associated.
|
||||
pub async fn leave(&mut self) {
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await;
|
||||
info!("Disassociated")
|
||||
}
|
||||
}
|
||||
|
||||
/// WiFi network scanner.
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,481 +0,0 @@
|
||||
#![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 };
|
@ -1,400 +0,0 @@
|
||||
#![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)
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
245
cyw43/src/lib.rs
245
cyw43/src/lib.rs
@ -1,245 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![allow(async_fn_in_trait)]
|
||||
#![deny(unused_must_use)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
// 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::{AddMulticastAddressError, Control, Error as ControlError, Scanner};
|
||||
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,
|
||||
};
|
||||
|
||||
/// Driver state.
|
||||
pub struct State {
|
||||
ioctl_state: IoctlState,
|
||||
ch: ch::State<MTU, 4, 4>,
|
||||
events: Events,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Create new driver state holder.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ioctl_state: IoctlState::new(),
|
||||
ch: ch::State::new(),
|
||||
events: Events::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Power management modes.
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Embassy-net driver.
|
||||
pub type NetDriver<'a> = ch::Device<'a, MTU>;
|
||||
|
||||
/// Create a new instance of the CYW43 driver.
|
||||
///
|
||||
/// Returns a handle to the network device, control handle and a runner for driving the low level
|
||||
/// stack.
|
||||
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, ch::driver::HardwareAddress::Ethernet([0; 6]));
|
||||
let state_ch = ch_runner.state_runner();
|
||||
|
||||
let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events);
|
||||
|
||||
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) }
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
pub static NVRAM: &'static [u8] = b"
|
||||
NVRAMRev=$Rev$\x00\
|
||||
manfid=0x2d0\x00\
|
||||
prodid=0x0727\x00\
|
||||
vendid=0x14e4\x00\
|
||||
devid=0x43e2\x00\
|
||||
boardtype=0x0887\x00\
|
||||
boardrev=0x1100\x00\
|
||||
boardnum=22\x00\
|
||||
macaddr=00:A0:50:b5:59:5e\x00\
|
||||
sromrev=11\x00\
|
||||
boardflags=0x00404001\x00\
|
||||
boardflags3=0x04000000\x00\
|
||||
xtalfreq=37400\x00\
|
||||
nocrc=1\x00\
|
||||
ag0=255\x00\
|
||||
aa2g=1\x00\
|
||||
ccode=ALL\x00\
|
||||
pa0itssit=0x20\x00\
|
||||
extpagain2g=0\x00\
|
||||
pa2ga0=-168,6649,-778\x00\
|
||||
AvVmid_c0=0x0,0xc8\x00\
|
||||
cckpwroffset0=5\x00\
|
||||
maxp2ga0=84\x00\
|
||||
txpwrbckof=6\x00\
|
||||
cckbw202gpo=0\x00\
|
||||
legofdmbw202gpo=0x66111111\x00\
|
||||
mcsbw202gpo=0x77711111\x00\
|
||||
propbw202gpo=0xdd\x00\
|
||||
ofdmdigfilttype=18\x00\
|
||||
ofdmdigfilttypebe=18\x00\
|
||||
papdmode=1\x00\
|
||||
papdvalidtest=1\x00\
|
||||
pacalidx2g=45\x00\
|
||||
papdepsoffset=-30\x00\
|
||||
papdendidx=58\x00\
|
||||
ltecxmux=0\x00\
|
||||
ltecxpadnum=0x0102\x00\
|
||||
ltecxfnsel=0x44\x00\
|
||||
ltecxgcigpio=0x01\x00\
|
||||
il0macaddr=00:90:4c:c5:12:38\x00\
|
||||
wl0id=0x431b\x00\
|
||||
deadman_to=0xffffffff\x00\
|
||||
muxenab=0x100\x00\
|
||||
spurconfig=0x3\x00\
|
||||
glitch_based_crsmin=1\x00\
|
||||
btc_mode=1\x00\
|
||||
\x00";
|
@ -1,587 +0,0 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Driver communicating with the WiFi chip.
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the
|
||||
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_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_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
|
||||
}
|
||||
}
|
@ -1,508 +0,0 @@
|
||||
use crate::events::Event;
|
||||
use crate::fmt::Bytes;
|
||||
|
||||
macro_rules! impl_bytes {
|
||||
($t:ident) => {
|
||||
impl $t {
|
||||
/// Bytes consumed by this type.
|
||||
pub const SIZE: usize = core::mem::size_of::<Self>();
|
||||
|
||||
/// Convert to byte array.
|
||||
#[allow(unused)]
|
||||
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
|
||||
unsafe { core::mem::transmute(*self) }
|
||||
}
|
||||
|
||||
/// Create from byte array.
|
||||
#[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) }
|
||||
}
|
||||
|
||||
/// Create from mutable byte array.
|
||||
#[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 {
|
||||
/// Swap endianness.
|
||||
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 {
|
||||
/// Version.
|
||||
pub version: u32,
|
||||
/// Length.
|
||||
pub length: u32,
|
||||
/// BSSID.
|
||||
pub bssid: [u8; 6],
|
||||
/// Beacon period.
|
||||
pub beacon_period: u16,
|
||||
/// Capability.
|
||||
pub capability: u16,
|
||||
/// SSID length.
|
||||
pub ssid_len: u8,
|
||||
/// SSID.
|
||||
pub ssid: [u8; 32],
|
||||
// there will be more stuff here
|
||||
}
|
||||
impl_bytes!(BssInfo);
|
||||
|
||||
impl BssInfo {
|
||||
pub(crate) 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(),
|
||||
))
|
||||
}
|
||||
}
|
@ -6,9 +6,9 @@ version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
embassy-executor = { version = "0.4.0", path = "../../../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-time = { version = "0.2.0", path = "../../../../../embassy-time", features = ["defmt"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
|
||||
embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] }
|
||||
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.3"
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use defmt::*;
|
||||
use embassy_executor::Spawner;
|
||||
|
@ -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"] }
|
||||
embassy-executor = { version = "0.4.0", features = ["arch-cortex-m", "executor-thread"] }
|
||||
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"] }
|
||||
|
||||
defmt = "0.3.0"
|
||||
defmt-rtt = "0.3.0"
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::exti::ExtiInput;
|
||||
|
@ -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"] }
|
||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"], default-features = false }
|
||||
|
||||
defmt = "0.3.0"
|
||||
defmt-rtt = "0.3.0"
|
||||
|
@ -9,7 +9,7 @@ use {defmt_rtt as _, panic_probe as _};
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
let mut led = Output::new(p.PB14, Level::High, Speed::VeryHigh);
|
||||
let mut button = Input::new(p.PC13, Pull::Up);
|
||||
let button = Input::new(p.PC13, Pull::Up);
|
||||
|
||||
loop {
|
||||
if button.is_low() {
|
||||
|
@ -20,13 +20,13 @@ fn main() -> ! {
|
||||
let led = Output::new(p.PB14, Level::Low, Speed::Low);
|
||||
let mut button = Input::new(p.PC13, Pull::Up);
|
||||
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
cortex_m::interrupt::free(|cs| unsafe {
|
||||
enable_interrupt(&mut button);
|
||||
|
||||
LED.borrow(cs).borrow_mut().replace(led);
|
||||
BUTTON.borrow(cs).borrow_mut().replace(button);
|
||||
|
||||
unsafe { NVIC::unmask(pac::Interrupt::EXTI15_10) };
|
||||
NVIC::unmask(pac::Interrupt::EXTI15_10);
|
||||
});
|
||||
|
||||
loop {
|
||||
@ -64,21 +64,25 @@ const PORT: u8 = 2;
|
||||
const PIN: usize = 13;
|
||||
fn check_interrupt<P: Pin>(_pin: &mut Input<'static, P>) -> bool {
|
||||
let exti = pac::EXTI;
|
||||
let pin = PIN;
|
||||
let lines = exti.pr(0).read();
|
||||
lines.line(pin)
|
||||
unsafe {
|
||||
let pin = PIN;
|
||||
let lines = exti.pr(0).read();
|
||||
lines.line(pin)
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_interrupt<P: Pin>(_pin: &mut Input<'static, P>) {
|
||||
let exti = pac::EXTI;
|
||||
let pin = PIN;
|
||||
let mut lines = exti.pr(0).read();
|
||||
lines.set_line(pin, true);
|
||||
exti.pr(0).write_value(lines);
|
||||
unsafe {
|
||||
let pin = PIN;
|
||||
let mut lines = exti.pr(0).read();
|
||||
lines.set_line(pin, true);
|
||||
exti.pr(0).write_value(lines);
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_interrupt<P: Pin>(_pin: &mut Input<'static, P>) {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
cortex_m::interrupt::free(|_| unsafe {
|
||||
let rcc = pac::RCC;
|
||||
rcc.apb2enr().modify(|w| w.set_syscfgen(true));
|
||||
|
||||
|
@ -1,18 +1,10 @@
|
||||
* xref:getting_started.adoc[Getting started]
|
||||
** xref:basic_application.adoc[Basic application]
|
||||
** xref:project_structure.adoc[Project Structure]
|
||||
** xref:new_project.adoc[Starting a new Embassy project]
|
||||
** xref:best_practices.adoc[Best Practices]
|
||||
* xref:layer_by_layer.adoc[Bare metal to async]
|
||||
** xref:layer_by_layer.adoc[Layer by Layer]
|
||||
* xref:runtime.adoc[Executor]
|
||||
* xref:delaying_a_task.adoc[Delaying a Task]
|
||||
* xref:hal.adoc[HAL]
|
||||
** xref:nrf.adoc[nRF]
|
||||
** xref:stm32.adoc[STM32]
|
||||
* xref:bootloader.adoc[Bootloader]
|
||||
|
||||
* xref:examples.adoc[Examples]
|
||||
* xref:developer.adoc[Developer Docs]
|
||||
** xref:developer_stm32.adoc[Developer Docs: STM32]
|
||||
* xref:embassy_in_the_wild.adoc[Embassy in the wild]
|
||||
* xref:faq.adoc[Frequently Asked Questions]
|
||||
|
@ -6,24 +6,13 @@ So you've got one of the xref:examples.adoc[examples] running, but what now? Let
|
||||
|
||||
The full example can be found link:https://github.com/embassy-rs/embassy/tree/master/docs/modules/ROOT/examples/basic[here].
|
||||
|
||||
NOTE: If you’re using VS Code and rust-analyzer to view and edit the examples, you may need to make some changes to `.vscode/settings.json` to tell it which project we’re working on. Follow the instructions commented in that file to get rust-analyzer working correctly.
|
||||
|
||||
=== Bare metal
|
||||
|
||||
The first thing you’ll notice are two attributes at the top of the file. These tells the compiler that program has no access to std, and that there is no main function (because it is not run by an OS).
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
include::example$basic/src/main.rs[lines="1..2"]
|
||||
----
|
||||
|
||||
=== Rust Nightly
|
||||
|
||||
The next declaration is a Rust Unstable feature, which means that Embassy requires Rust Nightly:
|
||||
The first thing you'll notice is a few declarations stating that Embassy requires some nightly features:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
include::example$basic/src/main.rs[lines="3"]
|
||||
include::example$basic/src/main.rs[lines="1..3"]
|
||||
----
|
||||
|
||||
=== Dealing with errors
|
||||
@ -50,18 +39,19 @@ NOTE: Notice that there is no busy waiting going on in this task. It is using th
|
||||
|
||||
=== Main
|
||||
|
||||
The main entry point of an Embassy application is defined using the `#[embassy_executor::main]` macro. The entry point is passed a `Spawner`, which it can use to spawn other tasks.
|
||||
The main entry point of an Embassy application is defined using the `#[embassy_executor::main]` macro. The entry point is also required to take a `Spawner` and a `Peripherals` argument.
|
||||
|
||||
We then initialize the HAL with a default config, which gives us a `Peripherals` struct we can use to access the MCU’s various peripherals. In this case, we want to configure one of the pins as a GPIO output driving the LED:
|
||||
The `Spawner` is the way the main application spawns other tasks. The `Peripherals` type comes from the HAL and holds all peripherals that the application may use. In this case, we want to configure one of the pins as a GPIO output driving the LED:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
include::example$basic/src/main.rs[lines="22..-1"]
|
||||
----
|
||||
|
||||
What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy_executor::main]` macro. The macro does the following:
|
||||
What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
|
||||
|
||||
. Creates an Embassy Executor
|
||||
. Initializes the microcontroller HAL to get the `Peripherals`
|
||||
. Defines a main task for the entry point
|
||||
. Runs the executor spawning the main task
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
= Best Practices
|
||||
|
||||
Over time, a couple of best practices have emerged. The following list should serve as a guideline for developers writing embedded software in _Rust_, especially in the context of the _Embassy_ framework.
|
||||
|
||||
== Passing Buffers by Reference
|
||||
It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't want to spend ressources on an allocator and end up placing buffers on the stack.
|
||||
This, however, can easily blow up your stack if you are not careful.
|
||||
|
||||
Consider the following example:
|
||||
[,rust]
|
||||
----
|
||||
fn process_buffer(mut buf: [u8; 1024]) -> [u8; 1024] {
|
||||
// do stuff and return new buffer
|
||||
for elem in buf.iter_mut() {
|
||||
*elem = 0;
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn main() -> () {
|
||||
let buf = [1u8; 1024];
|
||||
let buf_new = process_buffer(buf);
|
||||
// do stuff with buf_new
|
||||
()
|
||||
}
|
||||
----
|
||||
When calling `process_buffer` in your program, a copy of the buffer you pass to the function will be created,
|
||||
consuming another 1024 bytes.
|
||||
After the processing, another 1024 byte buffer will be placed on the stack to be returned to the caller.
|
||||
(You can check the assembly, there will be two memcopy operations, e.g., `bl __aeabi_memcpy` when compiling for a Cortex-M processor.)
|
||||
|
||||
*Possible Solution:*
|
||||
|
||||
Pass the data by reference and not by value on both, the way in and the way out.
|
||||
For example, you could return a slice of the input buffer as the output.
|
||||
Requiring the lifetime of the input slice and the output slice to be the same, the memory safetly of this procedure will be enforced by the compiler.
|
||||
|
||||
[,rust]
|
||||
----
|
||||
fn process_buffer<'a>(buf: &'a mut [u8]) -> &'a mut[u8] {
|
||||
for elem in buf.iter_mut() {
|
||||
*elem = 0;
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn main() -> () {
|
||||
let mut buf = [1u8; 1024];
|
||||
let buf_new = process_buffer(&mut buf);
|
||||
// do stuff with buf_new
|
||||
()
|
||||
}
|
||||
----
|
@ -45,8 +45,6 @@ The BOOTLOADER_STATE partition must be big enough to store one word per page in
|
||||
|
||||
The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
|
||||
|
||||
NOTE: The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application.
|
||||
|
||||
=== FirmwareUpdater
|
||||
|
||||
The `FirmwareUpdater` is an object for conveniently flashing firmware to the DFU partition and subsequently marking it as being ready for swapping with the active partition on the next reset. Its principle methods are `write_firmware`, which is called once per the size of the flash "write block" (typically 4KiB), and `mark_updated`, which is the final call.
|
||||
@ -93,4 +91,4 @@ cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
|
||||
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed
|
||||
----
|
||||
|
||||
Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware.
|
||||
Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware.
|
@ -1,28 +0,0 @@
|
||||
= Delaying a Task
|
||||
|
||||
In an embedded program, delaying a task is one of the most common actions taken. In an event loop, delays will need to be inserted to ensure
|
||||
that other tasks have a chance to run before the next iteration of the loop is called, if no other I/O is performed. Embassy provides an abstraction
|
||||
to delay the current task for a specified interval of time.
|
||||
|
||||
Timing is serviced by the `embassy::time::Timer` struct, which provides two timing methods.
|
||||
|
||||
`Timer::at` creates a future that completes at the specified `Instant`, relative to the system boot time.
|
||||
`Timer::after` creates a future that completes after the specified `Duration`, relative to when the future was created.
|
||||
|
||||
An example of a delay is provided as follows:
|
||||
|
||||
[,rust]
|
||||
----
|
||||
use embassy::executor::{task, Executor};
|
||||
use embassy::time::{Duration, Timer};
|
||||
|
||||
#[task]
|
||||
/// Task that ticks periodically
|
||||
async fn tick_periodic() -> ! {
|
||||
loop {
|
||||
rprintln!("tick!");
|
||||
// async sleep primitive, suspends the task for 500ms.
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
----
|
@ -1 +0,0 @@
|
||||
= Developer Documentation
|
@ -1,79 +0,0 @@
|
||||
= Developer Documentation: STM32
|
||||
|
||||
== Understanding metapac
|
||||
|
||||
When a project that imports `embassy-stm32` is compiled, that project selects the feature corresponding to the chip that project is using. Based on that feature, `embassy-stm32` selects supported link:https://anysilicon.com/ip-intellectual-property-core-semiconductors/[IP] for the chip, and enables the corresponding HAL implementations. But how does `embassy-stm32` know what IP the chip contains, out of the hundreds of chips that we support? It's a long story that starts with `stm32-data-sources`.
|
||||
|
||||
== `stm32-data-sources`
|
||||
|
||||
link:https://github.com/embassy-rs/stm32-data-sources[`stm32-data-sources`] is as mostly barren repository. It has no README, no documentation, and few watchers. But it's the core of what makes `embassy-stm32` possible. The data for every chip that we support is taken in part from a corresponding XML file like link:https://github.com/embassy-rs/stm32-data-sources/blob/b8b85202e22a954d6c59d4a43d9795d34cff05cf/cubedb/mcu/STM32F051K4Ux.xml[`STM32F051K4Ux.xml`]. In that file, you'll see lines like the following:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<IP InstanceName="I2C1" Name="I2C" Version="i2c2_v1_1_Cube"/>
|
||||
<!-- snip -->
|
||||
<IP ConfigFile="TIM-STM32F0xx" InstanceName="TIM1" Name="TIM1_8F0" Version="gptimer2_v2_x_Cube"/>
|
||||
----
|
||||
|
||||
These lines indicate that this chip has an i2c, and that it's version is "v1_1". It also indicates that it has a general purpose timer that with a version of "v2_x". From this data, it's possible to determine which implementations should be included in `embassy-stm32`. But actually doing that is another matter.
|
||||
|
||||
|
||||
== `stm32-data`
|
||||
|
||||
While all users of this project are familiar with `embassy-stm32`, fewer are familiar with the project that powers it: `stm32-data`. This project doesn't just aim to generate data for `embassy-stm32`, but for machine consumption in general. To acheive this, information from multiple files from the `stm32-data-sources` project are combined and parsed to assign register block implementations for each supported IP. The core of this matching resides in `chips.rs`:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
(".*:I2C:i2c2_v1_1", ("i2c", "v2", "I2C")),
|
||||
// snip
|
||||
(r".*TIM\d.*:gptimer.*", ("timer", "v1", "TIM_GP16")),
|
||||
----
|
||||
|
||||
In this case, the i2c version corresponds to our "v2" and the general purpose timer version corresponds to our "v1". Therefore, the `i2c_v2.yaml` and `timer_v1.yaml` register block implementations are assigned to those IP, respectively. The result is that these lines arr generated in `STM32F051K4.json`:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"name": "I2C1",
|
||||
"address": 1073763328,
|
||||
"registers": {
|
||||
"kind": "i2c",
|
||||
"version": "v2",
|
||||
"block": "I2C"
|
||||
},
|
||||
// snip
|
||||
}
|
||||
// snip
|
||||
{
|
||||
"name": "TIM1",
|
||||
"address": 1073818624,
|
||||
"registers": {
|
||||
"kind": "timer",
|
||||
"version": "v1",
|
||||
"block": "TIM_ADV"
|
||||
},
|
||||
// snip
|
||||
}
|
||||
----
|
||||
|
||||
In addition to register blocks, data for pin and RCC mapping is also generated and consumed by `embassy-stm32`. `stm32-metapac-gen` is used to package and publish the data as a crate.
|
||||
|
||||
|
||||
== `embassy-stm32`
|
||||
|
||||
In the `lib.rs` file located in the root of `embassy-stm32`, you'll see this line:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
#[cfg(i2c)]
|
||||
pub mod i2c;
|
||||
----
|
||||
|
||||
And in the `mod.rs` of the i2c mod, you'll see this:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
#[cfg_attr(i2c_v2, path = "v2.rs")]
|
||||
----
|
||||
|
||||
Because i2c is supported for STM32F051K4 and its version corresponds to our "v2", the `i2c` and `i2c_v2`, configuration directives will be present, and `embassy-stm32` will include these files, respectively. This and other configuration directives and tables are generated from the data for chip, allowing `embassy-stm32` to expressively and clearly adapt logic and implementations to what is required for each chip. Compared to other projects across the embedded ecosystem, `embassy-stm32` is the only project that can re-use code across the entire stm32 lineup and remove difficult-to-implement unsafe logic to the HAL.
|
@ -1,9 +0,0 @@
|
||||
= Embassy in the wild!
|
||||
|
||||
Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/modules/ROOT/pages/embassy_in_the_wild.adoc[add more]!
|
||||
|
||||
* link:https://github.com/cbruiz/printhor/[Printhor: The highly reliable but not necessarily functional 3D printer firmware]
|
||||
** Targets some STM32 MCUs
|
||||
* link:https://github.com/card-io-ecg/card-io-fw[Card/IO firmware] - firmware for an open source ECG device
|
||||
** Targets the ESP32-S3 or ESP32-C6 MCU
|
||||
* The link:https://github.com/lora-rs/lora-rs[lora-rs] project includes link:https://github.com/lora-rs/lora-rs/tree/main/examples/stm32l0/src/bin[various standalone examples] for NRF52840, RP2040, STM32L0 and STM32WL
|
@ -1,155 +0,0 @@
|
||||
= Frequently Asked Questions
|
||||
|
||||
These are a list of unsorted, commonly asked questions and answers.
|
||||
|
||||
Please feel free to add items to link:https://github.com/embassy-rs/embassy/edit/main/docs/modules/ROOT/pages/faq.adoc[this page], especially if someone in the chat answered a question for you!
|
||||
|
||||
== How to deploy to RP2040 without a debugging probe.
|
||||
|
||||
Install link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] for converting the generated elf binary into a uf2 file.
|
||||
|
||||
Configure the runner to use this tool, add this to `.cargo/config.toml`:
|
||||
[source,toml]
|
||||
----
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
runner = "elf2uf2-rs --deploy --serial --verbose"
|
||||
----
|
||||
|
||||
The command-line parameters `--deploy` will detect your device and upload the binary, `--serial` starts a serial connection. See the documentation for more info.
|
||||
|
||||
== Missing main macro
|
||||
|
||||
If you see an error like this:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
#[embassy_executor::main]
|
||||
| ^^^^ could not find `main` in `embassy_executor`
|
||||
----
|
||||
|
||||
You are likely missing some features of the `embassy-executor` crate.
|
||||
|
||||
For Cortex-M targets, consider making sure that ALL of the following features are active in your `Cargo.toml` for the `embassy-executor` crate:
|
||||
|
||||
* `arch-cortex-m`
|
||||
* `executor-thread`
|
||||
* `nightly`
|
||||
|
||||
For ESP32, consider using the executors and `#[main]` macro provided by your appropriate link:https://crates.io/crates/esp-hal-common[HAL crate].
|
||||
|
||||
== Why is my binary so big?
|
||||
|
||||
The first step to managing your binary size is to set up your link:https://doc.rust-lang.org/cargo/reference/profiles.html[profiles].
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[profile.release]
|
||||
debug = false
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
----
|
||||
|
||||
All of these flags are elaborated on in the Rust Book page linked above.
|
||||
|
||||
=== My binary is still big... filled with `std::fmt` stuff!
|
||||
|
||||
This means your code is sufficiently complex that `panic!` invocation's formatting requirements could not be optimized out, despite your usage of `panic-halt` or `panic-reset`.
|
||||
|
||||
You can remedy this by adding the following to your `.cargo/config.toml`:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
build-std-features = ["panic_immediate_abort"]
|
||||
----
|
||||
|
||||
This replaces all panics with a `UDF` (undefined) instruction.
|
||||
|
||||
Depending on your chipset, this will exhibit different behavior.
|
||||
|
||||
Refer to the spec for your chipset, but for `thumbv6m`, it results in a hardfault. Which can be configured like so:
|
||||
|
||||
[source,rust]
|
||||
----
|
||||
#[exception]
|
||||
unsafe fn HardFault(_frame: &ExceptionFrame) -> ! {
|
||||
SCB::sys_reset() // <- you could do something other than reset
|
||||
}
|
||||
----
|
||||
|
||||
Refer to cortex-m's link:https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.exception.html[exception handling] for more info.
|
||||
|
||||
== `embassy-time` throws linker errors
|
||||
|
||||
If you see linker error like this:
|
||||
|
||||
[source,text]
|
||||
----
|
||||
= note: rust-lld: error: undefined symbol: _embassy_time_now
|
||||
>>> referenced by driver.rs:127 (src/driver.rs:127)
|
||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::now::hefb1f99d6e069842) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
||||
|
||||
rust-lld: error: undefined symbol: _embassy_time_allocate_alarm
|
||||
>>> referenced by driver.rs:134 (src/driver.rs:134)
|
||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::allocate_alarm::hf5145b6bd46706b2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
||||
|
||||
rust-lld: error: undefined symbol: _embassy_time_set_alarm_callback
|
||||
>>> referenced by driver.rs:139 (src/driver.rs:139)
|
||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm_callback::h24f92388d96eafd2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
||||
|
||||
rust-lld: error: undefined symbol: _embassy_time_set_alarm
|
||||
>>> referenced by driver.rs:144 (src/driver.rs:144)
|
||||
>>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib
|
||||
----
|
||||
|
||||
You probably need to enable a time driver for your HAL (not in `embassy-time`!). For example with `embassy-stm32`, you might need to enable `time-driver-any`:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[dependencies.embassy-stm32]
|
||||
version = "0.1.0"
|
||||
features = [
|
||||
# ...
|
||||
"time-driver-any", # Add this line!
|
||||
# ...
|
||||
]
|
||||
----
|
||||
|
||||
== Error: `Only one package in the dependency graph may specify the same links value.`
|
||||
|
||||
You have multiple versions of the same crate in your dependency tree. This means that some of your
|
||||
embassy crates are coming from crates.io, and some from git, each of them pulling in a different set
|
||||
of dependencies.
|
||||
|
||||
To resolve this issue, make sure to only use a single source for all your embassy crates! To do this,
|
||||
you should patch your dependencies to use git sources using `[patch.crates.io]` and maybe `[patch.'https://github.com/embassy-rs/embassy.git']`.
|
||||
|
||||
Example:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[patch.crates-io]
|
||||
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
|
||||
----
|
||||
|
||||
Note that the git revision should match any other embassy patches or git dependencies that you are using!
|
||||
|
||||
== How can I optimize the speed of my embassy-stm32 program?
|
||||
|
||||
* Make sure RCC is set up to go as fast as possible
|
||||
* Make sure link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html[flash cache] is enabled
|
||||
* build with `--release`
|
||||
* Set the following keys for the release profile in your `Cargo.toml`:
|
||||
** `opt-level = "s"`
|
||||
** `lto = "fat"`
|
||||
* Set the following keys in the `[unstable]` section of your `.cargo/config.toml`
|
||||
** `build-std = ["core"]`
|
||||
** `build-std-features = ["panic_immediate_abort"]`
|
||||
* Enable feature `embassy-time/generic-queue`, disable feature `embassy-executor/integrated-timers`
|
||||
* When using `InterruptExecutor`:
|
||||
** disable `executor-thread`
|
||||
** make `main`` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }`
|
||||
** *Note:* If you need 2 priority levels, using 2 interrupt executors is better than 1 thread executor + 1 interrupt executor.
|
@ -3,15 +3,13 @@
|
||||
So you want to try Embassy, great! To get started, there are a few tools you need to install:
|
||||
|
||||
* link:https://rustup.rs/[rustup] - the Rust toolchain is needed to compile Rust code.
|
||||
* link:https://crates.io/crates/probe-rs[probe-rs] - to flash the firmware on your device. If you already have other tools like `OpenOCD` setup, you can use that as well.
|
||||
* link:https://crates.io/crates/probe-run[probe-run] - to flash the firmware on your device. If you already have other tools like `OpenOCD` setup, you can use that as well.
|
||||
|
||||
If you don't have any supported board, don't worry: you can also run embassy on your PC using the `std` examples.
|
||||
|
||||
== Getting a board with examples
|
||||
|
||||
Embassy supports many microcontroller families, but the quickest way to get started is by using a board which Embassy has existing example code for.
|
||||
|
||||
This list is non-exhaustive. If your board isn’t included here, check the link:https://github.com/embassy-rs/embassy/tree/main/examples[examples folder] to see if example code has been written for it.
|
||||
Embassy supports many microcontroller families, but the easiest ways to get started is if you have one of the more common development kits.
|
||||
|
||||
=== nRF kits
|
||||
|
||||
@ -32,94 +30,28 @@ This list is non-exhaustive. If your board isn’t included here, check the link
|
||||
|
||||
* link:https://www.raspberrypi.com/products/raspberry-pi-pico/[Raspberry Pi Pico]
|
||||
|
||||
=== ESP32
|
||||
|
||||
* link:https://github.com/esp-rs/esp-rust-board[ESP32C3]
|
||||
|
||||
== Running an example
|
||||
|
||||
First you need to clone the link:https://github.com/embassy-rs/embassy[github repository];
|
||||
First you need to clone the [github repository];
|
||||
|
||||
[source, bash]
|
||||
----
|
||||
git clone https://github.com/embassy-rs/embassy.git
|
||||
cd embassy
|
||||
git submodule update --init
|
||||
----
|
||||
|
||||
Once you have a copy of the repository, find examples folder for your board and, and build an example program. `blinky` is a good choice as all it does is blink an LED – the embedded world’s equivalent of “Hello World”.
|
||||
You can run an example by opening a terminal and entering the following commands:
|
||||
|
||||
[source, bash]
|
||||
----
|
||||
cd examples/nrf52840
|
||||
cargo build --bin blinky --release
|
||||
----
|
||||
|
||||
Once you’ve confirmed you can build the example, connect your computer to your board with a debug probe and run it on hardware:
|
||||
|
||||
[source, bash]
|
||||
----
|
||||
cargo run --bin blinky --release
|
||||
----
|
||||
|
||||
If everything worked correctly, you should see a blinking LED on your board, and debug output similar to this on your computer:
|
||||
== Whats next?
|
||||
|
||||
[source]
|
||||
----
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1m 56s
|
||||
Running `probe-run --chip STM32F407VGTx target/thumbv7em-none-eabi/debug/blinky`
|
||||
(HOST) INFO flashing program (71.36 KiB)
|
||||
(HOST) INFO success!
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
0 INFO Hello World!
|
||||
└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:18
|
||||
1 INFO high
|
||||
└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:23
|
||||
2 INFO low
|
||||
└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:27
|
||||
3 INFO high
|
||||
└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:23
|
||||
4 INFO low
|
||||
└─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:27
|
||||
----
|
||||
|
||||
NOTE: How does the `cargo run` command know how to connect to our board and program it? In each `examples` folder, there’s a `.cargo/config.toml` file which tells cargo to use link:https://probe.rs/[probe-rs] as the runner for ARM binaries in that folder. probe-rs handles communication with the debug probe and MCU. In order for this to work, probe-rs needs to know which chip it’s programming, so you’ll have to edit this file if you want to run examples on other chips.
|
||||
|
||||
=== It didn’t work!
|
||||
|
||||
If you hare having issues when running `cargo run --release`, please check the following:
|
||||
|
||||
* You are specifying the correct `--chip on the command line``, OR
|
||||
* You have set `.cargo/config.toml`'s run line to the correct chip, AND
|
||||
* You have changed `examples/Cargo.toml`'s HAL (e.g. embassy-stm32) dependency's feature to use the correct chip (replace the existing stm32xxxx feature)
|
||||
|
||||
At this point the project should run. If you do not see a blinky LED for blinky, for example, be sure to check the code is toggling your board's LED pin.
|
||||
|
||||
If you are trying to run an example with `cargo run --release` and you see the following output:
|
||||
[source]
|
||||
----
|
||||
0.000000 INFO Hello World!
|
||||
└─ <invalid location: defmt frame-index: 14>
|
||||
0.000000 DEBUG rcc: Clocks { sys: Hertz(80000000), apb1: Hertz(80000000), apb1_tim: Hertz(80000000), apb2: Hertz(80000000), apb2_tim: Hertz(80000000), ahb1: Hertz(80000000), ahb2: Hertz(80000000), ahb3: Hertz(80000000) }
|
||||
└─ <invalid location: defmt frame-index: 124>
|
||||
0.000061 TRACE allocating type=Interrupt mps=8 interval_ms=255, dir=In
|
||||
└─ <invalid location: defmt frame-index: 68>
|
||||
0.000091 TRACE index=1
|
||||
└─ <invalid location: defmt frame-index: 72>
|
||||
----
|
||||
|
||||
To get rid of the frame-index error add the following to your `Cargo.toml`:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[profile.release]
|
||||
debug = 2
|
||||
----
|
||||
|
||||
If you’re still having problems, check the link:https://embassy.dev/book/dev/faq.html[FAQ], or ask for help in the link:https://matrix.to/#/#embassy-rs:matrix.org[Embassy Chat Room].
|
||||
|
||||
== What's next?
|
||||
|
||||
Congratulations, you have your first Embassy application running! Here are some suggestions for where to go from here:
|
||||
Congratulations, you have your first Embassy application running! Here are some alternatives on where to go from here:
|
||||
|
||||
* Read more about the xref:runtime.adoc[executor].
|
||||
* Read more about the xref:hal.adoc[HAL].
|
||||
|
@ -7,6 +7,4 @@ Embassy provides HALs for several microcontroller families:
|
||||
* `embassy-rp` for the Raspberry Pi RP2040 microcontrollers
|
||||
|
||||
These HALs implement async/await functionality for most peripherals while also implementing the
|
||||
async traits in `embedded-hal` and `embedded-hal-async`. You can also use these HALs with another executor.
|
||||
|
||||
For the ESP32 series, there is an link:https://github.com/esp-rs/esp-hal[esp-hal] which you can use.
|
||||
async traits in `embedded-hal-async`. You can also use these HALs with another executor.
|
||||
|
@ -4,61 +4,31 @@ Embassy is a project to make async/await a first-class option for embedded devel
|
||||
|
||||
== What is async?
|
||||
|
||||
When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task (known as a “thread”) can be executed if available, or the CPU can be put to sleep until another task is ready.
|
||||
Software written without async may block on I/O operations. In an std environment, such as a PC, software can handle this either by using threads or non-blocking operations.
|
||||
|
||||
Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread.
|
||||
With threads, one thread blocks on an I/O operation, another is able to take its place. However, even on a PC, threads are relatively heavy, and therefore some programming languages, such as Go, have implemented a concept called coroutines or 'goroutines' that are much lighter and less-intensive than threads.
|
||||
|
||||
In other programming languages, these lightweight tasks are known as “coroutines” or ”goroutines”. In Rust, they are implemented with async. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute.
|
||||
The other way to handle blocking I/O operations is to support polling the state of the underlying peripherals to check whether it is available to perform the requested operation. In programming languages without builtin async support,
|
||||
this requires building a complex loop checking for events.
|
||||
|
||||
Compared to alternatives such as an RTOS, async can yield better performance and lower power consumption because the executor doesn't have to guess when a future is ready to execute. However, program size may be higher than other alternatives, which may be a problem for certain space-constrained devices with very low memory. On the devices Embassy supports, such as stm32 and nrf, memory is generally large enough to accommodate the modestly-increased program size.
|
||||
In Rust, non-blocking operations can be implemented using async-await. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute. Compared to alternatives such as an RTOS, async can yield better performance and lower power consumption because the executor doesn't have to guess when a future is ready to execute. However, program size may be higher than other alternatives, which may be a problem for certain space-constrained devices with very low memory. On the devices Embassy supports, such as stm32 and nrf, memory is generally large enough to accommodate the modestly-increased program size.
|
||||
|
||||
== What is Embassy?
|
||||
|
||||
The Embassy project consists of several crates that you can use together or independently:
|
||||
|
||||
=== Executor
|
||||
The link:https://docs.embassy.dev/embassy-executor/[embassy-executor] is an async/await executor that generally executes a fixed number of tasks, allocated at startup, though more can be added later. The executor may also provide a system timer that you can use for both async and blocking delays. For less than one microsecond, blocking delays should be used because the cost of context-switching is too high and the executor will be unable to provide accurate timing.
|
||||
* **Executor** - The link:https://docs.embassy.dev/embassy-executor/[embassy-executor] is an async/await executor that generally executes a fixed number of tasks, allocated at startup, though more can be added later. The HAL is an API that you can use to access peripherals, such as USART, UART, I2C, SPI, CAN, and USB. Embassy provides implementations of both async and blocking APIs where it makes sense. DMA (Direct Memory Access) is an example where async is a good fit, whereas GPIO states are a better fit for a blocking API. The executor may also provide a system timer that you can use for both async and blocking delays. For less than one microsecond, blocking delays should be used because the cost of context-switching is too high and the executor will be unable to provide accurate timing.
|
||||
|
||||
=== Hardware Abstraction Layers
|
||||
HALs implement safe Rust API which let you use peripherals such as USART, UART, I2C, SPI, CAN, and USB without having to directly manipulate registers.
|
||||
* **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy.
|
||||
** link:https://docs.embassy.dev/embassy-stm32/[embassy-stm32], for all STM32 microcontroller families.
|
||||
** link:https://docs.embassy.dev/embassy-nrf/[embassy-nrf], for the Nordic Semiconductor nRF52, nRF53, nRF91 series.
|
||||
|
||||
Embassy provides implementations of both async and blocking APIs where it makes sense. DMA (Direct Memory Access) is an example where async is a good fit, whereas GPIO states are a better fit for a blocking API.
|
||||
* **Networking** - The link:https://docs.embassy.dev/embassy-net/[embassy-net] network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently.
|
||||
|
||||
The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy.
|
||||
* **Bluetooth** - The link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
|
||||
* link:https://docs.embassy.dev/embassy-stm32/[embassy-stm32], for all STM32 microcontroller families.
|
||||
* link:https://docs.embassy.dev/embassy-nrf/[embassy-nrf], for the Nordic Semiconductor nRF52, nRF53, nRF91 series.
|
||||
* link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 microcontroller.
|
||||
* link:https://github.com/esp-rs[esp-rs], for the Espressif Systems ESP32 series of chips.
|
||||
* **LoRa** - link:https://docs.embassy.dev/embassy-lora/[embassy-lora] supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX127x transceivers.
|
||||
|
||||
NOTE: A common question is if one can use the Embassy HALs standalone. Yes, it is possible! There are no dependency on the executor within the HALs. You can even use them without async,
|
||||
as they implement both the link:https://github.com/rust-embedded/embedded-hal[Embedded HAL] blocking and async traits.
|
||||
* **USB** - link:https://docs.embassy.dev/embassy-usb/[embassy-usb] implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
|
||||
=== Networking
|
||||
The link:https://docs.embassy.dev/embassy-net/[embassy-net] network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently. Several drivers for WiFi and Ethernet chips can be found.
|
||||
|
||||
=== Bluetooth
|
||||
The link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
|
||||
|
||||
=== LoRa
|
||||
link:https://github.com/embassy-rs/lora-phy[lora-phy] and link:https://docs.embassy.dev/embassy-lora/[embassy-lora] supports LoRa networking on a wide range of LoRa radios, fully integrated with a Rust link:https://github.com/ivajloip/rust-lorawan[LoRaWAN] implementation.
|
||||
|
||||
=== USB
|
||||
link:https://docs.embassy.dev/embassy-usb/[embassy-usb] implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
|
||||
=== Bootloader and DFU
|
||||
link:https://github.com/embassy-rs/embassy/tree/master/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
||||
|
||||
== What is DMA?
|
||||
|
||||
For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bits at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller.
|
||||
|
||||
The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them.
|
||||
|
||||
== Resources
|
||||
|
||||
For more reading material on async Rust and Embassy:
|
||||
|
||||
* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy]
|
||||
* link:https://dev.to/apollolabsbin/series/20707[Tutorials]
|
||||
* link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy]
|
||||
* **Bootloader and DFU** - link:https://github.com/embassy-rs/embassy/tree/master/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
|
||||
|
@ -1,4 +1,4 @@
|
||||
= From bare metal to async Rust
|
||||
= Embassy layer by layer
|
||||
|
||||
If you're new to Embassy, it can be overwhelming to grasp all the terminology and concepts. This guide aims to clarify the different layers in Embassy, which problem each layer solves for the application writer.
|
||||
|
||||
@ -8,7 +8,8 @@ The application we'll write is a simple 'push button, blink led' application, wh
|
||||
|
||||
== PAC version
|
||||
|
||||
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
|
||||
The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types
|
||||
to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
|
||||
|
||||
Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use.
|
||||
|
||||
|
@ -1,178 +0,0 @@
|
||||
= Starting a new Embassy project
|
||||
|
||||
Once you’ve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project. The easiest way to do this is to adapt an example for a similar chip to the one you’re targeting.
|
||||
|
||||
As an example, let’s create a new embassy project from scratch for a STM32G474. The same instructions are applicable for any supported chip with some minor changes.
|
||||
|
||||
Run:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
cargo new stm32g474-example
|
||||
cd stm32g474-example
|
||||
----
|
||||
|
||||
to create an empty rust project:
|
||||
|
||||
[source]
|
||||
----
|
||||
stm32g474-example
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
----
|
||||
|
||||
Looking in link:https://github.com/embassy-rs/embassy/tree/main/examples[the Embassy examples], we can see there’s a `stm32g4` folder. Find `src/blinky.rs` and copy its contents into our `src/main.rs`.
|
||||
|
||||
== .cargo/config.toml
|
||||
|
||||
Currently, we’d need to provide cargo with a target triple every time we run `cargo build` or `cargo run`. Let’s spare ourselves that work by copying `.cargo/config.toml` from `examples/stm32g4` into our project.
|
||||
|
||||
[source]
|
||||
----
|
||||
stm32g474-example
|
||||
├── .cargo
|
||||
│ └── config.toml
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
----
|
||||
|
||||
In addition to a target triple, `.cargo/config.toml` contains a `runner` key which allows us to conveniently run our project on hardware with `cargo run` via probe-rs. In order for this to work, we need to provide the correct chip ID. We can do this by checking `probe-rs chip list`:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ probe-rs chip list | grep -i stm32g474re
|
||||
STM32G474RETx
|
||||
----
|
||||
|
||||
and copying `STM32G474RETx` into `.cargo/config.toml` as so:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# replace STM32G071C8Rx with your chip as listed in `probe-rs chip list`
|
||||
runner = "probe-rs run --chip STM32G474RETx"
|
||||
----
|
||||
|
||||
== Cargo.toml
|
||||
|
||||
Now that cargo knows what target to compile for (and probe-rs knows what chip to run it on), we’re ready to add some dependencies.
|
||||
|
||||
Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`.
|
||||
|
||||
At the time of writing, the latest version of embassy isn‘t available on crates.io, so we need to install it straight from the git repository. The recommended way of doing so is as follows:
|
||||
|
||||
* Copy the required `embassy-*` lines from the example `Cargo.toml`
|
||||
* Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32`
|
||||
* Remove the `path = ""` keys in the `embassy-*` entries
|
||||
* Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit we’re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD`
|
||||
|
||||
NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. Hopefully this will no longer be necessary once embassy is released on crates.io!
|
||||
|
||||
At the time of writing, this method produces the following results:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[dependencies]
|
||||
embassy-stm32 = {version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"]}
|
||||
embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
|
||||
embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
||||
|
||||
[patch.crates-io]
|
||||
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" }
|
||||
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" }
|
||||
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" }
|
||||
----
|
||||
|
||||
There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the the `[dependencies]` section in the new `Cargo.toml`:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
defmt = "0.3.5"
|
||||
defmt-rtt = "0.4.0"
|
||||
cortex-m = {version = "0.7.7", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.3"
|
||||
panic-probe = "0.3.1"
|
||||
----
|
||||
|
||||
These are the bare minimum dependencies required to run `blinky.rs`, but it’s worth taking a look at the other dependencies specified in the example `Cargo.toml`, and noting what features are required for use with embassy – for example `futures = { version = "0.3.17", default-features = false, features = ["async-await"] }`.
|
||||
|
||||
Finally, copy the `[profile.release]` section from the example `Cargo.toml` into ours.
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[profile.release]
|
||||
debug = 2
|
||||
----
|
||||
|
||||
== rust-toolchain.toml
|
||||
|
||||
Before we can build our project, we need to add an additional file to tell cargo to use the nightly toolchain. Copy the `rust-toolchain.toml` from the embassy repo to ours, and trim the list of targets down to only the target triple relevent for our project — in this case, `thumbv7em-none-eabi`:
|
||||
|
||||
[source]
|
||||
----
|
||||
stm32g474-example
|
||||
├── .cargo
|
||||
│ └── config.toml
|
||||
├── Cargo.toml
|
||||
├── rust-toolchain.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
----
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
# Before upgrading check that everything is available on all tier1 targets here:
|
||||
# https://rust-lang.github.io/rustup-components-history
|
||||
[toolchain]
|
||||
channel = "nightly-2023-11-01"
|
||||
components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ]
|
||||
targets = ["thumbv7em-none-eabi"]
|
||||
----
|
||||
|
||||
== build.rs
|
||||
|
||||
In order to produce a working binary for our target, cargo requires a custom build script. Copy `build.rs` from the example to our project:
|
||||
|
||||
[source]
|
||||
----
|
||||
stm32g474-example
|
||||
├── build.rs
|
||||
├── .cargo
|
||||
│ └── config.toml
|
||||
├── Cargo.toml
|
||||
├── rust-toolchain.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
----
|
||||
|
||||
== Building and running
|
||||
|
||||
At this point, we‘re finally ready to build and run our project! Connect your board via a debug probe and run:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
cargo run --release
|
||||
----
|
||||
|
||||
should result in a blinking LED (if there’s one attached to the pin in `src/main.rs` – change it if not!) and the following output:
|
||||
|
||||
[source]
|
||||
----
|
||||
Compiling stm32g474-example v0.1.0 (/home/you/stm32g474-example)
|
||||
Finished release [optimized + debuginfo] target(s) in 0.22s
|
||||
Running `probe-rs run --chip STM32G474RETx target/thumbv7em-none-eabi/release/stm32g474-example`
|
||||
Erasing sectors ✔ [00:00:00] [#########################################################] 18.00 KiB/18.00 KiB @ 54.09 KiB/s (eta 0s )
|
||||
Programming pages ✔ [00:00:00] [#########################################################] 17.00 KiB/17.00 KiB @ 35.91 KiB/s (eta 0s ) Finished in 0.817s
|
||||
0.000000 TRACE BDCR configured: 00008200
|
||||
└─ embassy_stm32::rcc::bd::{impl#3}::init::{closure#4} @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:117
|
||||
0.000000 DEBUG rcc: Clocks { sys: Hertz(16000000), pclk1: Hertz(16000000), pclk1_tim: Hertz(16000000), pclk2: Hertz(16000000), pclk2_tim: Hertz(16000000), hclk1: Hertz(16000000), hclk2: Hertz(16000000), pll1_p: None, adc: None, adc34: None, rtc: Some(Hertz(32000)) }
|
||||
└─ embassy_stm32::rcc::set_freqs @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:130
|
||||
0.000000 INFO Hello World!
|
||||
└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:14
|
||||
0.000091 INFO high
|
||||
└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:19
|
||||
0.300201 INFO low
|
||||
└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:23
|
||||
----
|
@ -8,7 +8,7 @@ The nRF timer driver operates at 32768 Hz by default.
|
||||
|
||||
== Peripherals
|
||||
|
||||
The following peripherals have a HAL implementation at present
|
||||
The following peripherals have a HAL implementation at present:
|
||||
|
||||
* PWM
|
||||
* SPIM
|
||||
@ -23,7 +23,3 @@ The following peripherals have a HAL implementation at present
|
||||
* UARTE
|
||||
* TWIM
|
||||
* SAADC
|
||||
|
||||
== Bluetooth
|
||||
|
||||
For bluetooth, you can use the link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] crate.
|
||||
|
@ -1,80 +0,0 @@
|
||||
= Project Structure
|
||||
|
||||
There are many ways to configure embassy and its components for your exact application. The link:https://github.com/embassy-rs/embassy/tree/main/examples[examples] directory for each chipset demonstrates how your project structure should look. Let's break it down:
|
||||
|
||||
The toplevel file structure of your project should look like this:
|
||||
[source,plain]
|
||||
----
|
||||
{} = Maybe
|
||||
|
||||
my-project
|
||||
|- .cargo
|
||||
| |- config.toml
|
||||
|- src
|
||||
| |- main.rs
|
||||
|- build.rs
|
||||
|- Cargo.toml
|
||||
|- {memory.x}
|
||||
|- rust-toolchain.toml
|
||||
----
|
||||
|
||||
=== .cargo/config.toml
|
||||
|
||||
This directory/file describes what platform you're on, and configures link:https://github.com/probe-rs/probe-rs[probe-rs] to deploy to your device.
|
||||
|
||||
Here is a minimal example:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[target.thumbv6m-none-eabi] # <-change for your platform
|
||||
runner = 'probe-rs run --chip STM32F031K6Tx' # <- change for your chip
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi" # <-change for your platform
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "trace" # <- can change to info, warn, or error
|
||||
----
|
||||
|
||||
=== build.rs
|
||||
|
||||
This is the build script for your project. It links defmt (what is defmt?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example].
|
||||
|
||||
=== Cargo.toml
|
||||
|
||||
This is your manifest file, where you can configure all of the embassy components to use the features you need.
|
||||
|
||||
TODO: someone should exhaustively describe every feature for every component!
|
||||
|
||||
=== memory.x
|
||||
|
||||
This file outlines the flash/ram usage of your program. It is especially useful when using link:https://github.com/embassy-rs/nrf-softdevice[nrf-softdevice] on an nRF5x.
|
||||
|
||||
Here is an example for using S140 with an nRF52840:
|
||||
|
||||
[source,x]
|
||||
----
|
||||
MEMORY
|
||||
{
|
||||
/* NOTE 1 K = 1 KiBi = 1024 bytes */
|
||||
/* These values correspond to the NRF52840 with Softdevices S140 7.0.1 */
|
||||
FLASH : ORIGIN = 0x00027000, LENGTH = 868K
|
||||
RAM : ORIGIN = 0x20020000, LENGTH = 128K
|
||||
}
|
||||
----
|
||||
|
||||
=== rust-toolchain.toml
|
||||
|
||||
This file configures the rust version and configuration to use.
|
||||
|
||||
A minimal example:
|
||||
|
||||
[source,toml]
|
||||
----
|
||||
[toolchain]
|
||||
channel = "nightly-2023-08-19" # <- as of writing, this is the exact rust version embassy uses
|
||||
components = [ "rust-src", "rustfmt" ] # <- optionally add "llvm-tools-preview" for some extra features like "cargo size"
|
||||
targets = [
|
||||
"thumbv6m-none-eabi" # <-change for your platform
|
||||
]
|
||||
----
|
@ -6,11 +6,11 @@ The Embassy executor is an async/await executor designed for embedded usage alon
|
||||
|
||||
* No `alloc`, no heap needed. Task are statically allocated.
|
||||
* No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
|
||||
* Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
|
||||
* Integrated timer queue: sleeping is easy, just do `Timer::after(Duration::from_secs(1)).await;`.
|
||||
* No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
|
||||
* Efficient polling: a wake will only poll the woken task, not all of them.
|
||||
* Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.
|
||||
* Creating multiple executor instances is supported, to run tasks at different priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
|
||||
* Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
|
||||
|
||||
== Executor
|
||||
|
||||
@ -27,7 +27,7 @@ If you use the `#[embassy_executor::main]` macro in your application, it creates
|
||||
|
||||
Interrupts are a common way for peripherals to signal completion of some operation and fits well with the async execution model. The following diagram describes a typical application flow where (1) a task is polled and is attempting to make progress. The task then (2) instructs the peripheral to perform some operation, and awaits. After some time has passed, (3) an interrupt is raised, marking the completion of the operation.
|
||||
|
||||
The peripheral HAL then (4) ensures that interrupt signals are routed to the peripheral and updating the peripheral state with the results of the operation. The executor is then (5) notified that the task should be polled, which it will do.
|
||||
The peripheral HAL then (4) ensures that interrupt signals are routed to to the peripheral and updating the peripheral state with the results of the operation. The executor is then (5) notified that the task should be polled, which it will do.
|
||||
|
||||
image::embassy_irq.png[Interrupt handling]
|
||||
|
||||
|
@ -25,27 +25,30 @@ features = ["defmt"]
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
digest = "0.10"
|
||||
log = { version = "0.4", optional = true }
|
||||
ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true }
|
||||
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
|
||||
embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
salty = { version = "0.3", optional = true }
|
||||
signature = { version = "2.0", default-features = false }
|
||||
log = { version = "0.4", optional = true }
|
||||
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
|
||||
embedded-storage = "0.3.0"
|
||||
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 }
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4"
|
||||
env_logger = "0.9"
|
||||
rand = "0.8"
|
||||
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"] }
|
||||
ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] }
|
||||
|
||||
[dev-dependencies.ed25519-dalek]
|
||||
default_features = false
|
||||
features = ["rand", "std", "u32_backend"]
|
||||
|
||||
[features]
|
||||
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
|
||||
ed25519-salty = ["dep:salty", "_verify"]
|
||||
|
||||
nightly = ["dep:embedded-storage-async"]
|
||||
|
||||
#Internal features
|
||||
_verify = []
|
||||
|
@ -8,24 +8,6 @@ The bootloader can be used either as a library or be flashed directly with the d
|
||||
|
||||
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
|
||||
|
||||
## Overview
|
||||
|
||||
The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts:
|
||||
|
||||
* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs.
|
||||
* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application.
|
||||
* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition.
|
||||
* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped.
|
||||
|
||||
For any partition, the following preconditions are required:
|
||||
|
||||
* Partitions must be aligned on the page size.
|
||||
* Partitions must be a multiple of the page size.
|
||||
|
||||
The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application.
|
||||
|
||||
For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html).
|
||||
|
||||
## Hardware support
|
||||
|
||||
The bootloader supports different hardware in separate crates:
|
||||
@ -34,7 +16,6 @@ The bootloader supports different hardware in separate crates:
|
||||
* `embassy-boot-rp` - for the RP2040 microcontrollers.
|
||||
* `embassy-boot-stm32` - for the STM32 microcontrollers.
|
||||
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
@ -1,11 +1,6 @@
|
||||
use core::cell::RefCell;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
|
||||
|
||||
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, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
||||
|
||||
/// Errors returned by bootloader
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
@ -35,96 +30,63 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// 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;
|
||||
/// Flash type used for the state partition.
|
||||
pub state: STATE,
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
trait FlashConfigEx {
|
||||
fn page_size() -> 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 }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// BootLoader works with any flash implementing embedded_storage.
|
||||
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,
|
||||
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,
|
||||
}
|
||||
|
||||
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.
|
||||
impl BootLoader {
|
||||
/// Create a new instance of a bootloader with the given 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(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
active: config.active,
|
||||
dfu: config.dfu,
|
||||
state: config.state,
|
||||
}
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
Self { active, dfu, 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.
|
||||
@ -135,44 +97,51 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
||||
/// The provided aligned_buf argument must satisfy any alignment requirements
|
||||
/// given by the partition flashes. All flash operations will use this buffer.
|
||||
///
|
||||
/// ## SWAPPING
|
||||
/// SWAPPING
|
||||
///
|
||||
/// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
|
||||
/// The swap index contains the copy progress, as to allow continuation of the copy process on
|
||||
/// power failure. The index counter is represented within 1 or more pages (depending on total
|
||||
/// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`)
|
||||
/// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
|
||||
/// contains a zero value. This ensures that index updates can be performed atomically and
|
||||
/// avoid a situation where the wrong index value is set (page write size is "atomic").
|
||||
///
|
||||
///
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|------------|--------|--------|--------|--------|
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Active | 0 | 1 | 2 | 3 | - |
|
||||
/// | DFU | 0 | 3 | 2 | 1 | X |
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
///
|
||||
/// The algorithm starts by copying 'backwards', and after the first step, the layout is
|
||||
/// as follows:
|
||||
///
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|------------|--------|--------|--------|--------|
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Active | 1 | 1 | 2 | 1 | - |
|
||||
/// | DFU | 1 | 3 | 2 | 1 | 3 |
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
///
|
||||
/// The next iteration performs the same steps
|
||||
///
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|------------|--------|--------|--------|--------|
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Active | 2 | 1 | 2 | 1 | - |
|
||||
/// | DFU | 2 | 3 | 2 | 2 | 3 |
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
///
|
||||
/// And again until we're done
|
||||
///
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|------------|--------|--------|--------|--------|
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
/// | Active | 3 | 3 | 2 | 1 | - |
|
||||
/// | DFU | 3 | 3 | 1 | 2 | 3 |
|
||||
/// +-----------+------------+--------+--------+--------+--------+
|
||||
///
|
||||
/// ## REVERTING
|
||||
/// REVERTING
|
||||
///
|
||||
/// The reverting algorithm uses the swap index to discover that images were swapped, but that
|
||||
/// the application failed to mark the boot successful. In this case, the revert algorithm will
|
||||
@ -183,229 +152,382 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
|
||||
///
|
||||
/// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
|
||||
///
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
/// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|--------------|--------|--------|--------|--------|
|
||||
//*/
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
/// | Active | 3 | 1 | 2 | 1 | - |
|
||||
/// | DFU | 3 | 3 | 1 | 2 | 3 |
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
///
|
||||
///
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
/// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|--------------|--------|--------|--------|--------|
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
/// | Active | 3 | 1 | 2 | 1 | - |
|
||||
/// | DFU | 3 | 3 | 2 | 2 | 3 |
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
///
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
/// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
|
||||
/// |-----------|--------------|--------|--------|--------|--------|
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
/// | Active | 3 | 1 | 2 | 3 | - |
|
||||
/// | DFU | 3 | 3 | 2 | 1 | 3 |
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
///
|
||||
pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
// Ensure we have enough progress pages to store copy progress
|
||||
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);
|
||||
|
||||
// Ensure our partitions are able to handle boot operations
|
||||
assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
|
||||
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);
|
||||
|
||||
// Copy contents from partition N to active
|
||||
let state = self.read_state(aligned_buf)?;
|
||||
let state = self.read_state(p, 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(aligned_buf)? {
|
||||
if !self.is_swapped(p, aligned_buf)? {
|
||||
trace!("Swapping");
|
||||
self.swap(aligned_buf)?;
|
||||
self.swap(p, aligned_buf)?;
|
||||
trace!("Swapping done");
|
||||
} else {
|
||||
trace!("Reverting");
|
||||
self.revert(aligned_buf)?;
|
||||
self.revert(p, aligned_buf)?;
|
||||
|
||||
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
|
||||
let state_flash = p.state();
|
||||
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
|
||||
|
||||
// Invalidate progress
|
||||
state_word.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
|
||||
state_word.fill(!P::STATE_ERASE_VALUE);
|
||||
self.state
|
||||
.write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32)?;
|
||||
self.state.wipe_blocking(state_flash)?;
|
||||
|
||||
// Set magic
|
||||
state_word.fill(BOOT_MAGIC);
|
||||
self.state.write(0, state_word)?;
|
||||
self.state.write_blocking(state_flash, 0, state_word)?;
|
||||
}
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
|
||||
Ok(progress >= page_count * 2)
|
||||
}
|
||||
|
||||
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;
|
||||
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();
|
||||
let state_word = &mut aligned_buf[..write_size as usize];
|
||||
|
||||
self.state.read(write_size, state_word)?;
|
||||
if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
self.state.read_blocking(state_flash, write_size, state_word)?;
|
||||
if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
|
||||
// Progress is invalid
|
||||
return Ok(max_index);
|
||||
}
|
||||
|
||||
for index in 0..max_index {
|
||||
self.state.read((2 + index) as u32 * write_size, state_word)?;
|
||||
self.state
|
||||
.read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
|
||||
|
||||
if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
|
||||
if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
|
||||
return Ok(index);
|
||||
}
|
||||
}
|
||||
Ok(max_index)
|
||||
}
|
||||
|
||||
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)?;
|
||||
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,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_page_once_to_active(
|
||||
fn copy_page_once_to_active<P: FlashConfig>(
|
||||
&mut self,
|
||||
progress_index: usize,
|
||||
from_offset: u32,
|
||||
to_offset: u32,
|
||||
p: &mut P,
|
||||
aligned_buf: &mut [u8],
|
||||
) -> Result<(), BootError> {
|
||||
if self.current_progress(aligned_buf)? <= progress_index {
|
||||
let page_size = Self::PAGE_SIZE as u32;
|
||||
if self.current_progress(p, aligned_buf)? <= progress_index {
|
||||
let page_size = P::page_size() as u32;
|
||||
|
||||
self.active.erase(to_offset, to_offset + page_size)?;
|
||||
self.active
|
||||
.erase_blocking(p.active(), to_offset, to_offset + page_size)?;
|
||||
|
||||
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
|
||||
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.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.update_progress(progress_index, aligned_buf)?;
|
||||
self.update_progress(progress_index, p, aligned_buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_page_once_to_dfu(
|
||||
fn copy_page_once_to_dfu<P: FlashConfig>(
|
||||
&mut self,
|
||||
progress_index: usize,
|
||||
from_offset: u32,
|
||||
to_offset: u32,
|
||||
p: &mut P,
|
||||
aligned_buf: &mut [u8],
|
||||
) -> Result<(), BootError> {
|
||||
if self.current_progress(aligned_buf)? <= progress_index {
|
||||
let page_size = Self::PAGE_SIZE as u32;
|
||||
if self.current_progress(p, aligned_buf)? <= progress_index {
|
||||
let page_size = P::page_size() as u32;
|
||||
|
||||
self.dfu.erase(to_offset as u32, to_offset + page_size)?;
|
||||
self.dfu
|
||||
.erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
|
||||
|
||||
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
|
||||
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.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.update_progress(progress_index, aligned_buf)?;
|
||||
self.update_progress(progress_index, p, aligned_buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
|
||||
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;
|
||||
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) * Self::PAGE_SIZE;
|
||||
let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE;
|
||||
let active_from_offset = (page_count - 1 - page_num) * page_size;
|
||||
let dfu_to_offset = (page_count - page_num) * 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, aligned_buf)?;
|
||||
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
|
||||
|
||||
// Copy DFU page to the active page
|
||||
let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
|
||||
let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
|
||||
let active_to_offset = (page_count - 1 - page_num) * page_size;
|
||||
let dfu_from_offset = (page_count - 1 - page_num) * 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, aligned_buf)?;
|
||||
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
|
||||
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;
|
||||
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 * 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)?;
|
||||
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)?;
|
||||
|
||||
// Copy the DFU page back to the active page
|
||||
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)?;
|
||||
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)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
|
||||
if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||
Ok(State::DfuDetach)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// DFU partition has to be bigger than ACTIVE partition to handle swap algorithm
|
||||
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);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_range_asserts() {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use digest::typenum::U64;
|
||||
use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
|
||||
use ed25519_dalek::Digest;
|
||||
use ed25519_dalek::Digest as _;
|
||||
|
||||
pub struct Sha512(ed25519_dalek::Sha512);
|
||||
|
||||
@ -12,7 +12,7 @@ impl Default for Sha512 {
|
||||
|
||||
impl Update for Sha512 {
|
||||
fn update(&mut self, data: &[u8]) {
|
||||
Digest::update(&mut self.0, data)
|
||||
self.0.update(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
542
embassy-boot/boot/src/firmware_updater.rs
Normal file
542
embassy-boot/boot/src/firmware_updater.rs
Normal file
@ -0,0 +1,542 @@
|
||||
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,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,329 +0,0 @@
|
||||
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, DFU_DETACH_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<'d, DFU: NorFlash, STATE: NorFlash> {
|
||||
dfu: DFU,
|
||||
state: FirmwareState<'d, 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<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
|
||||
Self {
|
||||
dfu: config.dfu,
|
||||
state: FirmwareState::new(config.state, aligned),
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.get_state().await
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub async fn verify_and_mark_updated(
|
||||
&mut self,
|
||||
_public_key: &[u8; 32],
|
||||
_signature: &[u8; 64],
|
||||
_update_len: u32,
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(_update_len <= self.dfu.capacity() as u32);
|
||||
|
||||
self.state.verify_booted().await?;
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey};
|
||||
|
||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
||||
|
||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
||||
|
||||
let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature);
|
||||
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
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 = PublicKey::try_from(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::try_from(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
"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.state.mark_updated().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.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_updated().await
|
||||
}
|
||||
|
||||
/// Mark to trigger USB DFU on next boot.
|
||||
pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.verify_booted().await?;
|
||||
self.state.mark_dfu().await
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_booted().await
|
||||
}
|
||||
|
||||
/// 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.state.verify_booted().await?;
|
||||
|
||||
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.state.verify_booted().await?;
|
||||
self.dfu.erase(0, self.dfu.capacity() as u32).await?;
|
||||
|
||||
Ok(&mut self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the state partition of the firmware update.
|
||||
///
|
||||
/// Can be used standalone for more fine grained control, or as part of the updater.
|
||||
pub struct FirmwareState<'d, STATE> {
|
||||
state: STATE,
|
||||
aligned: &'d mut [u8],
|
||||
}
|
||||
|
||||
impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
|
||||
/// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
|
||||
Self::new(config.state, aligned)
|
||||
}
|
||||
|
||||
/// Create a firmware state instance with a buffer for magic content and state partition.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
Self { state, aligned }
|
||||
}
|
||||
|
||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||
async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
if self.get_state().await? == State::Boot {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned).await?;
|
||||
|
||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(SWAP_MAGIC).await
|
||||
}
|
||||
|
||||
/// Mark to trigger USB DFU on next boot.
|
||||
pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(DFU_DETACH_MAGIC).await
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(BOOT_MAGIC).await
|
||||
}
|
||||
|
||||
async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned).await?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
self.aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32).await?;
|
||||
|
||||
// Set magic
|
||||
self.aligned.fill(magic);
|
||||
self.state.write(0, &self.aligned).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_embedded_hal::flash::partition::Partition;
|
||||
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 mut aligned = [0; 8];
|
||||
|
||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
|
||||
block_on(updater.write_firmware(0, to_write.as_slice())).unwrap();
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut hash = [0; 20];
|
||||
block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
|
||||
|
||||
assert_eq!(Sha1::digest(update).as_slice(), hash);
|
||||
}
|
||||
}
|
@ -1,342 +0,0 @@
|
||||
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, DFU_DETACH_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<'d, DFU: NorFlash, STATE: NorFlash> {
|
||||
dfu: DFU,
|
||||
state: BlockingFirmwareState<'d, 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<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
|
||||
Self {
|
||||
dfu: config.dfu,
|
||||
state: BlockingFirmwareState::new(config.state, aligned),
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.get_state()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub fn verify_and_mark_updated(
|
||||
&mut self,
|
||||
_public_key: &[u8; 32],
|
||||
_signature: &[u8; 64],
|
||||
_update_len: u32,
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(_update_len <= self.dfu.capacity() as u32);
|
||||
|
||||
self.state.verify_booted()?;
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey};
|
||||
|
||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
||||
|
||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
||||
|
||||
let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature);
|
||||
|
||||
let mut message = [0; 64];
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
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 = PublicKey::try_from(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::try_from(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
let mut chunk_buf = [0; 2];
|
||||
self.hash::<Sha512>(_update_len, &mut chunk_buf, &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.state.mark_updated()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_updated()
|
||||
}
|
||||
|
||||
/// Mark to trigger USB DFU device on next boot.
|
||||
pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.verify_booted()?;
|
||||
self.state.mark_dfu()
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.mark_booted()
|
||||
}
|
||||
|
||||
/// 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.state.verify_booted()?;
|
||||
|
||||
if offset == 0 {
|
||||
self.dfu.erase(0, self.dfu.capacity() 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.state.verify_booted()?;
|
||||
self.dfu.erase(0, self.dfu.capacity() as u32)?;
|
||||
|
||||
Ok(&mut self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the state partition of the firmware update.
|
||||
///
|
||||
/// Can be used standalone for more fine grained control, or as part of the updater.
|
||||
pub struct BlockingFirmwareState<'d, STATE> {
|
||||
state: STATE,
|
||||
aligned: &'d mut [u8],
|
||||
}
|
||||
|
||||
impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
|
||||
/// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
|
||||
Self::new(config.state, aligned)
|
||||
}
|
||||
|
||||
/// Create a firmware state instance with a buffer for magic content and state partition.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
Self { state, aligned }
|
||||
}
|
||||
|
||||
// Make sure we are running a booted firmware to avoid reverting to a bad state.
|
||||
fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FirmwareUpdaterError::BadState)
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned)?;
|
||||
|
||||
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
|
||||
Ok(State::DfuDetach)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(SWAP_MAGIC)
|
||||
}
|
||||
|
||||
/// Mark to trigger USB DFU on next boot.
|
||||
pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(DFU_DETACH_MAGIC)
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
|
||||
self.set_magic(BOOT_MAGIC)
|
||||
}
|
||||
|
||||
fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(0, &mut self.aligned)?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?;
|
||||
|
||||
if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
self.aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.erase(0, self.state.capacity() as u32)?;
|
||||
|
||||
// Set magic
|
||||
self.aligned.fill(magic);
|
||||
self.state.write(0, &self.aligned)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::cell::RefCell;
|
||||
|
||||
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 mut aligned = [0; 8];
|
||||
|
||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
mod asynch;
|
||||
mod blocking;
|
||||
|
||||
pub use asynch::{FirmwareState, FirmwareUpdater};
|
||||
pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater};
|
||||
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||
|
||||
/// Firmware updater flash configuration holding the two flashes used by the updater
|
||||
///
|
||||
/// 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),
|
||||
/// Bad state.
|
||||
BadState,
|
||||
}
|
||||
|
||||
#[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(_)"),
|
||||
FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for FirmwareUpdaterError
|
||||
where
|
||||
E: NorFlashError,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
FirmwareUpdaterError::Flash(error.kind())
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
#![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.");
|
||||
|
||||
@ -83,17 +81,14 @@ macro_rules! todo {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::core::unreachable!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unreachable!($($x)*)
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -228,31 +223,3 @@ impl<T, E> Try for Result<T, E> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
|
||||
#![allow(incomplete_features)]
|
||||
#![no_std]
|
||||
#![allow(async_fn_in_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
@ -7,23 +8,15 @@ mod fmt;
|
||||
mod boot_loader;
|
||||
mod digest_adapters;
|
||||
mod firmware_updater;
|
||||
#[cfg(test)]
|
||||
mod mem_flash;
|
||||
#[cfg(test)]
|
||||
mod test_flash;
|
||||
mod 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};
|
||||
pub use firmware_updater::{
|
||||
BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
|
||||
FirmwareUpdaterError,
|
||||
};
|
||||
pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig};
|
||||
pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError};
|
||||
pub use partition::Partition;
|
||||
|
||||
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
|
||||
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
|
||||
pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
|
||||
|
||||
/// The state of the bootloader after running prepare.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
@ -33,8 +26,6 @@ pub enum State {
|
||||
Boot,
|
||||
/// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
|
||||
Swap,
|
||||
/// Application has received a request to reboot into DFU mode to apply an update.
|
||||
DfuDetach,
|
||||
}
|
||||
|
||||
/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
|
||||
@ -55,17 +46,10 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
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;
|
||||
use crate::test_flash::{AsyncTestFlash, BlockingTestFlash};
|
||||
|
||||
/*
|
||||
#[test]
|
||||
@ -84,239 +68,195 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_boot_state() {
|
||||
let flash = BlockingTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<57344, 4096, 4>::default(),
|
||||
dfu: MemFlash::<61440, 4096, 4>::default(),
|
||||
state: MemFlash::<4096, 4096, 4>::default(),
|
||||
});
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 61440);
|
||||
const DFU: Partition = Partition::new(61440, 122880);
|
||||
|
||||
flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
|
||||
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);
|
||||
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
|
||||
|
||||
let mut page = [0; 4096];
|
||||
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
|
||||
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_swap_state() {
|
||||
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(),
|
||||
});
|
||||
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 ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
|
||||
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
|
||||
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let mut aligned = [0; 4];
|
||||
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
flash.program(ACTIVE.from, &original).unwrap();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated()).unwrap();
|
||||
|
||||
// Writing after marking updated is not allowed until marked as booted.
|
||||
let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE));
|
||||
assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
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 page = [0; 1024];
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
assert_eq!(
|
||||
State::Swap,
|
||||
bootloader
|
||||
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
flash.assert_eq(ACTIVE.from, &update);
|
||||
// First DFU page is untouched
|
||||
flash.dfu().read(4096, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
flash.assert_eq(DFU.from + 4096, &original);
|
||||
|
||||
// Running again should cause a revert
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
assert_eq!(
|
||||
State::Swap,
|
||||
bootloader
|
||||
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
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);
|
||||
flash.assert_eq(ACTIVE.from, &original);
|
||||
// Last page is untouched
|
||||
flash.assert_eq(DFU.from, &update);
|
||||
|
||||
// Mark as booted
|
||||
let flash = flash.into_async();
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
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()
|
||||
);
|
||||
block_on(updater.mark_booted()).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(not(feature = "_verify"))]
|
||||
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(),
|
||||
});
|
||||
#[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);
|
||||
|
||||
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
|
||||
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
|
||||
let mut active = MemFlash::<16384, 4096, 8>::random();
|
||||
let mut dfu = MemFlash::<16384, 2048, 8>::random();
|
||||
let mut state = MemFlash::<4096, 128, 4>::random();
|
||||
let mut aligned = [0; 4];
|
||||
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated()).unwrap();
|
||||
active.program(ACTIVE.from, &original).unwrap();
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
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 mut page = [0; 4096];
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
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);
|
||||
// First DFU page is untouched
|
||||
flash.dfu().read(4096, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
dfu.assert_eq(DFU.from + 4096, &original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
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(),
|
||||
});
|
||||
#[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);
|
||||
|
||||
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();
|
||||
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated()).unwrap();
|
||||
active.program(ACTIVE.from, &original).unwrap();
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
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 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()
|
||||
);
|
||||
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
active.assert_eq(ACTIVE.from, &update);
|
||||
// First DFU page is untouched
|
||||
flash.dfu().read(4096, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
dfu.assert_eq(DFU.from + 4096, &original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "_verify")]
|
||||
#[cfg(all(feature = "nightly", feature = "_verify"))]
|
||||
fn test_verify() {
|
||||
// The following key setup is based on:
|
||||
// https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
|
||||
|
||||
use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey};
|
||||
use ed25519_dalek::Keypair;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
let mut csprng = OsRng {};
|
||||
let keypair = SigningKey::generate(&mut csprng);
|
||||
let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||
|
||||
use ed25519_dalek::{Digest, Sha512, Signature, Signer};
|
||||
let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU.";
|
||||
let mut digest = Sha512::new();
|
||||
digest.update(&firmware);
|
||||
let message = digest.finalize();
|
||||
let signature: Signature = keypair.sign(&message);
|
||||
|
||||
let public_key = keypair.verifying_key();
|
||||
use ed25519_dalek::PublicKey;
|
||||
let public_key: PublicKey = keypair.public;
|
||||
|
||||
// Setup flash
|
||||
let flash = BlockingTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<0, 0, 0>::default(),
|
||||
dfu: MemFlash::<4096, 4096, 4>::default(),
|
||||
state: MemFlash::<4096, 4096, 4>::default(),
|
||||
});
|
||||
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const DFU: Partition = Partition::new(4096, 8192);
|
||||
let mut flash = MemFlash::<8192, 4096, 4>::default();
|
||||
|
||||
let firmware_len = firmware.len();
|
||||
|
||||
let mut write_buf = [0; 4096];
|
||||
write_buf[0..firmware_len].copy_from_slice(firmware);
|
||||
flash.dfu().write(0, &write_buf).unwrap();
|
||||
DFU.write_blocking(&mut flash, 0, &write_buf).unwrap();
|
||||
|
||||
// On with the test
|
||||
let flash = flash.into_async();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||
|
||||
let mut aligned = [0; 4];
|
||||
let mut updater = FirmwareUpdater::new(
|
||||
FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
},
|
||||
&mut aligned,
|
||||
);
|
||||
|
||||
assert!(block_on(updater.verify_and_mark_updated(
|
||||
&mut flash,
|
||||
&public_key.to_bytes(),
|
||||
&signature.to_bytes(),
|
||||
firmware_len as u32,
|
||||
&mut aligned,
|
||||
))
|
||||
.is_ok());
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
use core::ops::{Bound, Range, RangeBounds};
|
||||
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
|
||||
@ -33,52 +34,6 @@ 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);
|
||||
@ -89,6 +44,12 @@ 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
|
||||
@ -117,7 +78,9 @@ 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> {
|
||||
self.read(offset, bytes)
|
||||
let len = bytes.len();
|
||||
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
@ -131,40 +94,74 @@ 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 write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes)
|
||||
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 erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes)
|
||||
<Self as ReadNorFlash>::read(self, offset, bytes)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
<Self as ReadNorFlash>::capacity(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes)
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
<Self as NorFlash>::erase(self, from, to)
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to)
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
<Self as NorFlash>::write(self, offset, bytes)
|
||||
}
|
||||
}
|
||||
|
144
embassy-boot/boot/src/partition.rs
Normal file
144
embassy-boot/boot/src/partition.rs
Normal file
@ -0,0 +1,144 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
mod asynch;
|
||||
mod blocking;
|
||||
|
||||
pub(crate) use asynch::AsyncTestFlash;
|
||||
pub(crate) use blocking::BlockingTestFlash;
|
@ -4,12 +4,6 @@ name = "embassy-boot-nrf"
|
||||
version = "0.1.0"
|
||||
description = "Bootloader lib for nRF chips"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/"
|
||||
@ -22,16 +16,16 @@ target = "thumbv7em-none-eabi"
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
|
||||
embassy-nrf = { path = "../../embassy-nrf" }
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-nrf = { path = "../../embassy-nrf", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
nrf-softdevice-mbr = { version = "0.2.0", optional = true }
|
||||
nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true }
|
||||
|
||||
[features]
|
||||
defmt = [
|
||||
@ -42,3 +36,8 @@ defmt = [
|
||||
softdevice = [
|
||||
"nrf-softdevice-mbr",
|
||||
]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-nrf/nightly"
|
||||
]
|
||||
|
@ -6,7 +6,7 @@ An adaptation of `embassy-boot` for nRF.
|
||||
|
||||
## Features
|
||||
|
||||
* Load applications with or without the softdevice.
|
||||
* Load applications with our without the softdevice.
|
||||
* Configure bootloader partitions based on linker script.
|
||||
* Using watchdog timer to detect application failure.
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
#![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.");
|
||||
|
||||
@ -83,17 +81,14 @@ macro_rules! todo {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::core::unreachable!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unreachable!($($x)*)
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -228,31 +223,3 @@ impl<T, E> Try for Result<T, E> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
|
@ -3,27 +3,73 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{
|
||||
AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater,
|
||||
FirmwareUpdaterConfig,
|
||||
};
|
||||
use embassy_nrf::nvmc::PAGE_SIZE;
|
||||
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
|
||||
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>;
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> {
|
||||
boot: embassy_boot::BootLoader,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
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> {
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware.
|
||||
pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
|
||||
config: BootLoaderConfig<ACTIVE, DFU, STATE>,
|
||||
) -> Self {
|
||||
let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
|
||||
let mut boot = embassy_boot::BootLoader::new(config);
|
||||
boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error");
|
||||
Self
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
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!"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Boots the application without softdevice mechanisms.
|
||||
@ -32,10 +78,10 @@ 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(self, start: u32) -> ! {
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
let mut p = cortex_m::Peripherals::steal();
|
||||
p.SCB.invalidate_icache();
|
||||
p.SCB.vtor.write(start);
|
||||
p.SCB.vtor.write(start as u32);
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
|
||||
@ -45,7 +91,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(self, _app: u32) -> ! {
|
||||
pub unsafe fn load(&mut self, _app: usize) -> ! {
|
||||
use nrf_softdevice_mbr as mbr;
|
||||
const NRF_SUCCESS: u32 = 0;
|
||||
|
||||
@ -92,15 +138,15 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A flash implementation that wraps any flash and will pet a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<FLASH> {
|
||||
flash: FLASH,
|
||||
/// A flash implementation that wraps NVMC and will pet a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<'d> {
|
||||
flash: Nvmc<'d>,
|
||||
wdt: wdt::WatchdogHandle,
|
||||
}
|
||||
|
||||
impl<FLASH> WatchdogFlash<FLASH> {
|
||||
impl<'d> WatchdogFlash<'d> {
|
||||
/// Start a new watchdog with a given flash and WDT peripheral and a timeout
|
||||
pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self {
|
||||
pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self {
|
||||
let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
@ -115,13 +161,13 @@ impl<FLASH> WatchdogFlash<FLASH> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> {
|
||||
type Error = FLASH::Error;
|
||||
impl<'d> ErrorType for WatchdogFlash<'d> {
|
||||
type Error = <Nvmc<'d> as ErrorType>::Error;
|
||||
}
|
||||
|
||||
impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> {
|
||||
const WRITE_SIZE: usize = FLASH::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = FLASH::ERASE_SIZE;
|
||||
impl<'d> NorFlash for WatchdogFlash<'d> {
|
||||
const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
@ -133,8 +179,8 @@ impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> {
|
||||
const READ_SIZE: usize = FLASH::READ_SIZE;
|
||||
impl<'d> ReadNorFlash for WatchdogFlash<'d> {
|
||||
const READ_SIZE: usize = <Nvmc<'d> as ReadNorFlash>::READ_SIZE;
|
||||
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wdt.pet();
|
||||
self.flash.read(offset, data)
|
||||
|
@ -4,12 +4,6 @@ name = "embassy-boot-rp"
|
||||
version = "0.1.0"
|
||||
description = "Bootloader lib for RP2040 chips"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/"
|
||||
@ -23,15 +17,15 @@ defmt = { version = "0.3", optional = true }
|
||||
defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-rp = { path = "../../embassy-rp", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
embassy-time = { path = "../../embassy-time" }
|
||||
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[features]
|
||||
@ -46,6 +40,12 @@ log = [
|
||||
"embassy-rp/log",
|
||||
]
|
||||
debug = ["defmt-rtt"]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-rp/nightly",
|
||||
"embassy-time/nightly"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 2
|
||||
|
@ -1,8 +1,6 @@
|
||||
#![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.");
|
||||
|
||||
@ -83,17 +81,14 @@ macro_rules! todo {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::core::unreachable!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unreachable!($($x)*)
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -228,31 +223,3 @@ impl<T, E> Try for Result<T, E> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
|
@ -3,28 +3,35 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{
|
||||
AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater,
|
||||
FirmwareUpdaterConfig, State,
|
||||
};
|
||||
use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE};
|
||||
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
|
||||
use embassy_rp::flash::{Flash, ERASE_SIZE};
|
||||
use embassy_rp::peripherals::{FLASH, WATCHDOG};
|
||||
use embassy_rp::watchdog::Watchdog;
|
||||
use embassy_time::Duration;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
|
||||
/// A bootloader for RP2040 devices.
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>;
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
|
||||
boot: embassy_boot::BootLoader,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
|
||||
pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
|
||||
config: BootLoaderConfig<ACTIVE, DFU, STATE>,
|
||||
) -> Self {
|
||||
let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
|
||||
let mut boot = embassy_boot::BootLoader::new(config);
|
||||
boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
|
||||
Self
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
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!"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Boots the application.
|
||||
@ -32,28 +39,67 @@ 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(self, start: u32) -> ! {
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
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);
|
||||
p.SCB.vtor.write(start as u32);
|
||||
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
}
|
||||
|
||||
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, Blocking, SIZE>,
|
||||
flash: Flash<'d, FLASH, SIZE>,
|
||||
watchdog: Watchdog,
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
|
||||
/// Start a new watchdog with a given flash and watchdog peripheral and a timeout
|
||||
pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self {
|
||||
let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash);
|
||||
let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash);
|
||||
let mut watchdog = Watchdog::new(watchdog);
|
||||
watchdog.start(timeout);
|
||||
Self { flash, watchdog }
|
||||
@ -61,28 +107,28 @@ impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> {
|
||||
type Error = <Flash<'d, FLASH, Blocking, SIZE> as ErrorType>::Error;
|
||||
type Error = <Flash<'d, FLASH, SIZE> as ErrorType>::Error;
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
|
||||
const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::ERASE_SIZE;
|
||||
const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.watchdog.feed();
|
||||
self.flash.blocking_erase(from, to)
|
||||
self.flash.erase(from, to)
|
||||
}
|
||||
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.watchdog.feed();
|
||||
self.flash.blocking_write(offset, data)
|
||||
self.flash.write(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> {
|
||||
const READ_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as ReadNorFlash>::READ_SIZE;
|
||||
const READ_SIZE: usize = <Flash<'d, FLASH, SIZE> as ReadNorFlash>::READ_SIZE;
|
||||
fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.watchdog.feed();
|
||||
self.flash.blocking_read(offset, data)
|
||||
self.flash.read(offset, data)
|
||||
}
|
||||
fn capacity(&self) -> usize {
|
||||
self.flash.capacity()
|
||||
|
@ -4,12 +4,6 @@ name = "embassy-boot-stm32"
|
||||
version = "0.1.0"
|
||||
description = "Bootloader lib for STM32 chips"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/"
|
||||
@ -24,19 +18,32 @@ defmt = { version = "0.3", optional = true }
|
||||
defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[features]
|
||||
defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"]
|
||||
log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"]
|
||||
defmt = [
|
||||
"dep:defmt",
|
||||
"embassy-boot/defmt",
|
||||
"embassy-stm32/defmt",
|
||||
]
|
||||
log = [
|
||||
"dep:log",
|
||||
"embassy-boot/log",
|
||||
"embassy-stm32/log",
|
||||
]
|
||||
debug = ["defmt-rtt"]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-stm32/nightly"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 2
|
||||
|
@ -1,8 +1,6 @@
|
||||
#![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.");
|
||||
|
||||
@ -83,17 +81,14 @@ macro_rules! todo {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::core::unreachable!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unreachable!($($x)*)
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -228,31 +223,3 @@ impl<T, E> Try for Result<T, E> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
|
@ -3,27 +3,30 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{
|
||||
AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater,
|
||||
FirmwareUpdaterConfig, State,
|
||||
};
|
||||
use embedded_storage::nor_flash::NorFlash;
|
||||
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
|
||||
|
||||
/// A bootloader for STM32 devices.
|
||||
pub struct BootLoader {
|
||||
/// The reported state of the bootloader after preparing for boot
|
||||
pub state: State,
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize> {
|
||||
boot: embassy_boot::BootLoader,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
impl BootLoader {
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
|
||||
pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>(
|
||||
config: BootLoaderConfig<ACTIVE, DFU, STATE>,
|
||||
) -> Self {
|
||||
let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
|
||||
let mut boot = embassy_boot::BootLoader::new(config);
|
||||
let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
|
||||
Self { state }
|
||||
impl<const BUFFER_SIZE: usize> BootLoader<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 {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
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!"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Boots the application.
|
||||
@ -31,14 +34,53 @@ impl BootLoader {
|
||||
/// # Safety
|
||||
///
|
||||
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
|
||||
pub unsafe fn load(self, start: u32) -> ! {
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
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);
|
||||
p.SCB.vtor.write(start as u32);
|
||||
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
47
embassy-cortex-m/Cargo.toml
Normal file
47
embassy-cortex-m/Cargo.toml
Normal file
@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "embassy-cortex-m"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-cortex-m-v$VERSION/embassy-cortex-m/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-cortex-m/src/"
|
||||
features = ["prio-bits-3"]
|
||||
flavors = [
|
||||
{ name = "thumbv6m-none-eabi", target = "thumbv6m-none-eabi", features = [] },
|
||||
{ name = "thumbv7m-none-eabi", target = "thumbv7m-none-eabi", features = [] },
|
||||
{ name = "thumbv7em-none-eabi", target = "thumbv7em-none-eabi", features = [] },
|
||||
{ name = "thumbv7em-none-eabihf", target = "thumbv7em-none-eabihf", features = [] },
|
||||
{ name = "thumbv8m.base-none-eabi", target = "thumbv8m.base-none-eabi", features = [] },
|
||||
{ name = "thumbv8m.main-none-eabi", target = "thumbv8m.main-none-eabi", features = [] },
|
||||
{ name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] },
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Define the number of NVIC priority bits.
|
||||
prio-bits-0 = []
|
||||
prio-bits-1 = []
|
||||
prio-bits-2 = []
|
||||
prio-bits-3 = []
|
||||
prio-bits-4 = []
|
||||
prio-bits-5 = []
|
||||
prio-bits-6 = []
|
||||
prio-bits-7 = []
|
||||
prio-bits-8 = []
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-executor = { version = "0.2.0", path = "../embassy-executor"}
|
||||
embassy-macros = { version = "0.2.0", path = "../embassy-macros"}
|
||||
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common"}
|
||||
atomic-polyfill = "1.0.1"
|
||||
critical-section = "1.1"
|
||||
cfg-if = "1.0.0"
|
||||
cortex-m = "0.7.6"
|
||||
|
@ -1,8 +1,6 @@
|
||||
#![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.");
|
||||
|
||||
@ -113,7 +111,7 @@ macro_rules! trace {
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ignored = ($( & $x ),*);
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -126,7 +124,7 @@ macro_rules! debug {
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ignored = ($( & $x ),*);
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -139,7 +137,7 @@ macro_rules! info {
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ignored = ($( & $x ),*);
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -152,7 +150,7 @@ macro_rules! warn {
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ignored = ($( & $x ),*);
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -165,7 +163,7 @@ macro_rules! error {
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ignored = ($( & $x ),*);
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -197,6 +195,9 @@ macro_rules! unwrap {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime")]
|
||||
defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() }
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
@ -225,30 +226,3 @@ impl<T, E> Try for Result<T, E> {
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,235 +1,215 @@
|
||||
//! Interrupt handling for cortex-m devices.
|
||||
use core::mem;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
use core::{mem, ptr};
|
||||
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use atomic_polyfill::{compiler_fence, AtomicPtr, Ordering};
|
||||
use cortex_m::peripheral::NVIC;
|
||||
use critical_section::CriticalSection;
|
||||
use embassy_hal_common::Peripheral;
|
||||
pub use embassy_macros::cortex_m_interrupt_take as take;
|
||||
|
||||
/// Generate a standard `mod interrupt` for a HAL.
|
||||
#[macro_export]
|
||||
macro_rules! interrupt_mod {
|
||||
($($irqs:ident),* $(,)?) => {
|
||||
#[cfg(feature = "rt")]
|
||||
pub use cortex_m_rt::interrupt;
|
||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||
#[doc(hidden)]
|
||||
pub mod _export {
|
||||
pub use atomic_polyfill as atomic;
|
||||
pub use embassy_macros::{cortex_m_interrupt as interrupt, cortex_m_interrupt_declare as declare};
|
||||
}
|
||||
|
||||
/// Interrupt definitions.
|
||||
pub mod interrupt {
|
||||
pub use $crate::interrupt::{InterruptExt, Priority};
|
||||
pub use crate::pac::Interrupt::*;
|
||||
pub use crate::pac::Interrupt;
|
||||
/// Interrupt handler trait.
|
||||
///
|
||||
/// Drivers that need to handle interrupts implement this trait.
|
||||
/// The user must ensure `on_interrupt()` is called every time the interrupt fires.
|
||||
/// Drivers must use use [`Binding`] to assert at compile time that the user has done so.
|
||||
pub trait Handler<I: Interrupt> {
|
||||
/// Interrupt handler function.
|
||||
///
|
||||
/// Must be called every time the `I` interrupt fires, synchronously from
|
||||
/// the interrupt handler context.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must ONLY be called from the interrupt handler for `I`.
|
||||
unsafe fn on_interrupt();
|
||||
}
|
||||
|
||||
/// Type-level interrupt infrastructure.
|
||||
///
|
||||
/// This module contains one *type* per interrupt. This is used for checking at compile time that
|
||||
/// the interrupts are correctly bound to HAL drivers.
|
||||
///
|
||||
/// As an end user, you shouldn't need to use this module directly. Use the [`crate::bind_interrupts!`] macro
|
||||
/// to bind interrupts, and the [`crate::interrupt`] module to manually register interrupt handlers and manipulate
|
||||
/// interrupts directly (pending/unpending, enabling/disabling, setting the priority, etc...)
|
||||
pub mod typelevel {
|
||||
use super::InterruptExt;
|
||||
/// Compile-time assertion that an interrupt has been bound to a handler.
|
||||
///
|
||||
/// For the vast majority of cases, you should use the `bind_interrupts!`
|
||||
/// macro instead of writing `unsafe impl`s of this trait.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()`
|
||||
/// to be called every time the `I` interrupt fires.
|
||||
///
|
||||
/// This allows drivers to check bindings at compile-time.
|
||||
pub unsafe trait Binding<I: Interrupt, H: Handler<I>> {}
|
||||
|
||||
mod sealed {
|
||||
pub trait Interrupt {}
|
||||
}
|
||||
/// Implementation detail, do not use outside embassy crates.
|
||||
#[doc(hidden)]
|
||||
pub struct DynHandler {
|
||||
pub func: AtomicPtr<()>,
|
||||
pub ctx: AtomicPtr<()>,
|
||||
}
|
||||
|
||||
/// Type-level interrupt.
|
||||
///
|
||||
/// This trait is implemented for all typelevel interrupt types in this module.
|
||||
pub trait Interrupt: sealed::Interrupt {
|
||||
|
||||
/// Interrupt enum variant.
|
||||
///
|
||||
/// This allows going from typelevel interrupts (one type per interrupt) to
|
||||
/// non-typelevel interrupts (a single `Interrupt` enum type, with one variant per interrupt).
|
||||
const IRQ: super::Interrupt;
|
||||
|
||||
/// Enable the interrupt.
|
||||
#[inline]
|
||||
unsafe fn enable() {
|
||||
Self::IRQ.enable()
|
||||
}
|
||||
|
||||
/// Disable the interrupt.
|
||||
#[inline]
|
||||
fn disable() {
|
||||
Self::IRQ.disable()
|
||||
}
|
||||
|
||||
/// Check if interrupt is enabled.
|
||||
#[inline]
|
||||
fn is_enabled() -> bool {
|
||||
Self::IRQ.is_enabled()
|
||||
}
|
||||
|
||||
/// Check if interrupt is pending.
|
||||
#[inline]
|
||||
fn is_pending() -> bool {
|
||||
Self::IRQ.is_pending()
|
||||
}
|
||||
|
||||
/// Set interrupt pending.
|
||||
#[inline]
|
||||
fn pend() {
|
||||
Self::IRQ.pend()
|
||||
}
|
||||
|
||||
/// Unset interrupt pending.
|
||||
#[inline]
|
||||
fn unpend() {
|
||||
Self::IRQ.unpend()
|
||||
}
|
||||
|
||||
/// Get the priority of the interrupt.
|
||||
#[inline]
|
||||
fn get_priority() -> crate::interrupt::Priority {
|
||||
Self::IRQ.get_priority()
|
||||
}
|
||||
|
||||
/// Set the interrupt priority.
|
||||
#[inline]
|
||||
fn set_priority(prio: crate::interrupt::Priority) {
|
||||
Self::IRQ.set_priority(prio)
|
||||
}
|
||||
|
||||
/// Set the interrupt priority with an already-acquired critical section
|
||||
#[inline]
|
||||
fn set_priority_with_cs(cs: critical_section::CriticalSection, prio: crate::interrupt::Priority) {
|
||||
Self::IRQ.set_priority_with_cs(cs, prio)
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[allow(non_camel_case_types)]
|
||||
#[doc=stringify!($irqs)]
|
||||
#[doc=" typelevel interrupt."]
|
||||
pub enum $irqs {}
|
||||
impl sealed::Interrupt for $irqs{}
|
||||
impl Interrupt for $irqs {
|
||||
const IRQ: super::Interrupt = super::Interrupt::$irqs;
|
||||
}
|
||||
)*
|
||||
|
||||
/// Interrupt handler trait.
|
||||
///
|
||||
/// Drivers that need to handle interrupts implement this trait.
|
||||
/// The user must ensure `on_interrupt()` is called every time the interrupt fires.
|
||||
/// Drivers must use use [`Binding`] to assert at compile time that the user has done so.
|
||||
pub trait Handler<I: Interrupt> {
|
||||
/// Interrupt handler function.
|
||||
///
|
||||
/// Must be called every time the `I` interrupt fires, synchronously from
|
||||
/// the interrupt handler context.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must ONLY be called from the interrupt handler for `I`.
|
||||
unsafe fn on_interrupt();
|
||||
}
|
||||
|
||||
/// Compile-time assertion that an interrupt has been bound to a handler.
|
||||
///
|
||||
/// For the vast majority of cases, you should use the `bind_interrupts!`
|
||||
/// macro instead of writing `unsafe impl`s of this trait.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()`
|
||||
/// to be called every time the `I` interrupt fires.
|
||||
///
|
||||
/// This allows drivers to check bindings at compile-time.
|
||||
pub unsafe trait Binding<I: Interrupt, H: Handler<I>> {}
|
||||
}
|
||||
impl DynHandler {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
func: AtomicPtr::new(ptr::null_mut()),
|
||||
ctx: AtomicPtr::new(ptr::null_mut()),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct NrWrap(pub(crate) u16);
|
||||
unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap {
|
||||
fn number(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an interrupt type that can be configured by embassy to handle
|
||||
/// interrupts.
|
||||
pub unsafe trait InterruptExt: InterruptNumber + Copy {
|
||||
/// Enable the interrupt.
|
||||
#[inline]
|
||||
unsafe fn enable(self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
NVIC::unmask(self)
|
||||
}
|
||||
pub unsafe trait Interrupt: Peripheral<P = Self> {
|
||||
/// Return the NVIC interrupt number for this interrupt.
|
||||
fn number(&self) -> u16;
|
||||
/// Steal an instance of this interrupt
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This may panic if the interrupt has already been stolen and configured.
|
||||
unsafe fn steal() -> Self;
|
||||
|
||||
/// Implementation detail, do not use outside embassy crates.
|
||||
#[doc(hidden)]
|
||||
unsafe fn __handler(&self) -> &'static DynHandler;
|
||||
}
|
||||
|
||||
/// Represents additional behavior for all interrupts.
|
||||
pub trait InterruptExt: Interrupt {
|
||||
/// Configure the interrupt handler for this interrupt.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It is the responsibility of the caller to ensure the handler
|
||||
/// points to a valid handler as long as interrupts are enabled.
|
||||
fn set_handler(&self, func: unsafe fn(*mut ()));
|
||||
|
||||
/// Remove the interrupt handler for this interrupt.
|
||||
fn remove_handler(&self);
|
||||
|
||||
/// Set point to a context that is passed to the interrupt handler when
|
||||
/// an interrupt is pending.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It is the responsibility of the caller to ensure the context
|
||||
/// points to a valid handler as long as interrupts are enabled.
|
||||
fn set_handler_context(&self, ctx: *mut ());
|
||||
|
||||
/// Enable the interrupt. Once enabled, the interrupt handler may
|
||||
/// be called "any time".
|
||||
fn enable(&self);
|
||||
|
||||
/// Disable the interrupt.
|
||||
#[inline]
|
||||
fn disable(self) {
|
||||
NVIC::mask(self);
|
||||
fn disable(&self);
|
||||
|
||||
/// Check if interrupt is being handled.
|
||||
#[cfg(not(armv6m))]
|
||||
fn is_active(&self) -> bool;
|
||||
|
||||
/// Check if interrupt is enabled.
|
||||
fn is_enabled(&self) -> bool;
|
||||
|
||||
/// Check if interrupt is pending.
|
||||
fn is_pending(&self) -> bool;
|
||||
|
||||
/// Set interrupt pending.
|
||||
fn pend(&self);
|
||||
|
||||
/// Unset interrupt pending.
|
||||
fn unpend(&self);
|
||||
|
||||
/// Get the priority of the interrupt.
|
||||
fn get_priority(&self) -> Priority;
|
||||
|
||||
/// Set the interrupt priority.
|
||||
fn set_priority(&self, prio: Priority);
|
||||
}
|
||||
|
||||
impl<T: Interrupt + ?Sized> InterruptExt for T {
|
||||
fn set_handler(&self, func: unsafe fn(*mut ())) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
let handler = unsafe { self.__handler() };
|
||||
handler.func.store(func as *mut (), Ordering::Relaxed);
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Check if interrupt is being handled.
|
||||
#[inline]
|
||||
#[cfg(not(armv6m))]
|
||||
fn is_active(self) -> bool {
|
||||
NVIC::is_active(self)
|
||||
fn remove_handler(&self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
let handler = unsafe { self.__handler() };
|
||||
handler.func.store(ptr::null_mut(), Ordering::Relaxed);
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Check if interrupt is enabled.
|
||||
#[inline]
|
||||
fn is_enabled(self) -> bool {
|
||||
NVIC::is_enabled(self)
|
||||
fn set_handler_context(&self, ctx: *mut ()) {
|
||||
let handler = unsafe { self.__handler() };
|
||||
handler.ctx.store(ctx, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Check if interrupt is pending.
|
||||
#[inline]
|
||||
fn is_pending(self) -> bool {
|
||||
NVIC::is_pending(self)
|
||||
}
|
||||
|
||||
/// Set interrupt pending.
|
||||
#[inline]
|
||||
fn pend(self) {
|
||||
NVIC::pend(self)
|
||||
}
|
||||
|
||||
/// Unset interrupt pending.
|
||||
#[inline]
|
||||
fn unpend(self) {
|
||||
NVIC::unpend(self)
|
||||
}
|
||||
|
||||
/// Get the priority of the interrupt.
|
||||
#[inline]
|
||||
fn get_priority(self) -> Priority {
|
||||
Priority::from(NVIC::get_priority(self))
|
||||
}
|
||||
|
||||
/// Set the interrupt priority.
|
||||
#[inline]
|
||||
fn set_priority(self, prio: Priority) {
|
||||
fn enable(&self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
unsafe {
|
||||
let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(());
|
||||
|
||||
// On thumbv6, set_priority must do a RMW to change 8bit in a 32bit reg.
|
||||
#[cfg(armv6m)]
|
||||
critical_section::with(|_| nvic.set_priority(self, prio.into()));
|
||||
// On thumbv7+, set_priority does an atomic 8bit write, so no CS needed.
|
||||
#[cfg(not(armv6m))]
|
||||
nvic.set_priority(self, prio.into());
|
||||
NVIC::unmask(NrWrap(self.number()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the interrupt priority with an already-acquired critical section
|
||||
///
|
||||
/// Equivalent to `set_priority`, except you pass a `CriticalSection` to prove
|
||||
/// you've already acquired a critical section. This prevents acquiring another
|
||||
/// one, which saves code size.
|
||||
#[inline]
|
||||
fn set_priority_with_cs(self, _cs: CriticalSection, prio: Priority) {
|
||||
fn disable(&self) {
|
||||
NVIC::mask(NrWrap(self.number()));
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(armv6m))]
|
||||
fn is_active(&self) -> bool {
|
||||
NVIC::is_active(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_enabled(&self) -> bool {
|
||||
NVIC::is_enabled(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_pending(&self) -> bool {
|
||||
NVIC::is_pending(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pend(&self) {
|
||||
NVIC::pend(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unpend(&self) {
|
||||
NVIC::unpend(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_priority(&self) -> Priority {
|
||||
Priority::from(NVIC::get_priority(NrWrap(self.number())))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_priority(&self, prio: Priority) {
|
||||
unsafe {
|
||||
let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(());
|
||||
nvic.set_priority(self, prio.into());
|
||||
nvic.set_priority(NrWrap(self.number()), prio.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: InterruptNumber + Copy> InterruptExt for T {}
|
||||
|
||||
impl From<u8> for Priority {
|
||||
fn from(priority: u8) -> Self {
|
||||
unsafe { mem::transmute(priority & PRIO_MASK) }
|
10
embassy-cortex-m/src/lib.rs
Normal file
10
embassy-cortex-m/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! Embassy executor and interrupt handling specific to cortex-m devices.
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
pub use embassy_executor as executor;
|
||||
pub mod interrupt;
|
||||
pub mod peripheral;
|
144
embassy-cortex-m/src/peripheral.rs
Normal file
144
embassy-cortex-m/src/peripheral.rs
Normal file
@ -0,0 +1,144 @@
|
||||
//! Peripheral interrupt handling specific to cortex-m devices.
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use cortex_m::peripheral::scb::VectActive;
|
||||
use cortex_m::peripheral::{NVIC, SCB};
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
use crate::interrupt::{Interrupt, InterruptExt, Priority};
|
||||
|
||||
/// A type which can be used as state with `PeripheralMutex`.
|
||||
///
|
||||
/// It needs to be `Send` because `&mut` references are sent back and forth between the 'thread' which owns the `PeripheralMutex` and the interrupt,
|
||||
/// and `&mut T` is only `Send` where `T: Send`.
|
||||
pub trait PeripheralState: Send {
|
||||
/// The interrupt that is used for this peripheral.
|
||||
type Interrupt: Interrupt;
|
||||
|
||||
/// The interrupt handler that should be invoked for the peripheral. Implementations need to clear the appropriate interrupt flags to ensure the handle will not be called again.
|
||||
fn on_interrupt(&mut self);
|
||||
}
|
||||
|
||||
/// A type for storing the state of a peripheral that can be stored in a static.
|
||||
pub struct StateStorage<S>(MaybeUninit<S>);
|
||||
|
||||
impl<S> StateStorage<S> {
|
||||
/// Create a new instance for storing peripheral state.
|
||||
pub const fn new() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type for a peripheral that keeps the state of a peripheral that can be accessed from thread mode and an interrupt handler in
|
||||
/// a safe way.
|
||||
pub struct PeripheralMutex<'a, S: PeripheralState> {
|
||||
state: *mut S,
|
||||
irq: PeripheralRef<'a, S::Interrupt>,
|
||||
}
|
||||
|
||||
/// Whether `irq` can be preempted by the current interrupt.
|
||||
pub(crate) fn can_be_preempted(irq: &impl Interrupt) -> bool {
|
||||
match SCB::vect_active() {
|
||||
// Thread mode can't preempt anything.
|
||||
VectActive::ThreadMode => false,
|
||||
// Exceptions don't always preempt interrupts,
|
||||
// but there isn't much of a good reason to be keeping a `PeripheralMutex` in an exception anyway.
|
||||
VectActive::Exception(_) => true,
|
||||
VectActive::Interrupt { irqn } => {
|
||||
#[derive(Clone, Copy)]
|
||||
struct NrWrap(u16);
|
||||
unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap {
|
||||
fn number(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
NVIC::get_priority(NrWrap(irqn.into())) < irq.get_priority().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: PeripheralState> PeripheralMutex<'a, S> {
|
||||
/// Create a new `PeripheralMutex` wrapping `irq`, with `init` initializing the initial state.
|
||||
///
|
||||
/// Registers `on_interrupt` as the `irq`'s handler, and enables it.
|
||||
pub fn new(
|
||||
irq: impl Peripheral<P = S::Interrupt> + 'a,
|
||||
storage: &'a mut StateStorage<S>,
|
||||
init: impl FnOnce() -> S,
|
||||
) -> Self {
|
||||
into_ref!(irq);
|
||||
|
||||
if can_be_preempted(&*irq) {
|
||||
panic!(
|
||||
"`PeripheralMutex` cannot be created in an interrupt with higher priority than the interrupt it wraps"
|
||||
);
|
||||
}
|
||||
|
||||
let state_ptr = storage.0.as_mut_ptr();
|
||||
|
||||
// Safety: The pointer is valid and not used by anyone else
|
||||
// because we have the `&mut StateStorage`.
|
||||
unsafe { state_ptr.write(init()) };
|
||||
|
||||
irq.disable();
|
||||
irq.set_handler(|p| unsafe {
|
||||
// Safety: it's OK to get a &mut to the state, since
|
||||
// - We checked that the thread owning the `PeripheralMutex` can't preempt us in `new`.
|
||||
// Interrupts' priorities can only be changed with raw embassy `Interrupts`,
|
||||
// which can't safely store a `PeripheralMutex` across invocations.
|
||||
// - We can't have preempted a with() call because the irq is disabled during it.
|
||||
let state = &mut *(p as *mut S);
|
||||
state.on_interrupt();
|
||||
});
|
||||
irq.set_handler_context(state_ptr as *mut ());
|
||||
irq.enable();
|
||||
|
||||
Self { irq, state: state_ptr }
|
||||
}
|
||||
|
||||
/// Access the peripheral state ensuring interrupts are disabled so that the state can be
|
||||
/// safely accessed.
|
||||
pub fn with<R>(&mut self, f: impl FnOnce(&mut S) -> R) -> R {
|
||||
self.irq.disable();
|
||||
|
||||
// Safety: it's OK to get a &mut to the state, since the irq is disabled.
|
||||
let state = unsafe { &mut *self.state };
|
||||
let r = f(state);
|
||||
|
||||
self.irq.enable();
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
/// Returns whether the wrapped interrupt is currently in a pending state.
|
||||
pub fn is_pending(&self) -> bool {
|
||||
self.irq.is_pending()
|
||||
}
|
||||
|
||||
/// Forces the wrapped interrupt into a pending state.
|
||||
pub fn pend(&self) {
|
||||
self.irq.pend()
|
||||
}
|
||||
|
||||
/// Forces the wrapped interrupt out of a pending state.
|
||||
pub fn unpend(&self) {
|
||||
self.irq.unpend()
|
||||
}
|
||||
|
||||
/// Gets the priority of the wrapped interrupt.
|
||||
pub fn priority(&self) -> Priority {
|
||||
self.irq.get_priority()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: PeripheralState> Drop for PeripheralMutex<'a, S> {
|
||||
fn drop(&mut self) {
|
||||
self.irq.disable();
|
||||
self.irq.remove_handler();
|
||||
|
||||
// safety:
|
||||
// - we initialized the state in `new`, so we know it's initialized.
|
||||
// - the irq is disabled, so it won't preempt us while dropping.
|
||||
unsafe { self.state.drop_in_place() }
|
||||
}
|
||||
}
|
@ -8,29 +8,21 @@ license = "MIT OR Apache-2.0"
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/"
|
||||
features = ["std"]
|
||||
features = ["nightly", "std"]
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[features]
|
||||
std = []
|
||||
time = ["dep:embassy-time"]
|
||||
default = ["time"]
|
||||
# Enable nightly-only features
|
||||
nightly = ["embedded-hal-async", "embedded-storage-async"]
|
||||
|
||||
[dependencies]
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-sync = { version = "0.5.0", path = "../embassy-sync" }
|
||||
embassy-time = { version = "0.2", path = "../embassy-time", optional = true }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||
"unproven",
|
||||
] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.3" }
|
||||
embedded-hal-async = { version = "=1.0.0-rc.3" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
nb = "1.0.0"
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
critical-section = { version = "1.1.1", features = ["std"] }
|
||||
futures-test = "0.3.17"
|
||||
|
@ -1,18 +0,0 @@
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
|
||||
|
||||
let output = Command::new(rustc)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect("failed to run `rustc --version`");
|
||||
|
||||
if String::from_utf8_lossy(&output.stdout).contains("nightly") {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
use embedded_hal_02::blocking;
|
||||
//! Adapters between embedded-hal traits.
|
||||
|
||||
use embedded_hal_02::{blocking, serial};
|
||||
|
||||
/// Wrapper that implements async traits using blocking implementations.
|
||||
///
|
||||
@ -74,21 +76,7 @@ where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
|
||||
// Ensure we write the expected bytes
|
||||
for i in 0..core::cmp::min(read.len(), write.len()) {
|
||||
read[i] = write[i].clone();
|
||||
@ -97,12 +85,52 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBusFlush for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBusWrite<u8> for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBusRead<u8> for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Uart implementatinos
|
||||
impl<T, E> embedded_hal_1::serial::ErrorType for BlockingAsync<T>
|
||||
where
|
||||
T: serial::Read<u8, Error = E>,
|
||||
E: embedded_hal_1::serial::Error + 'static,
|
||||
{
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
/// NOR flash wrapper
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
@ -1,7 +0,0 @@
|
||||
//! Adapters between embedded-hal traits.
|
||||
|
||||
mod blocking_async;
|
||||
mod yielding_async;
|
||||
|
||||
pub use blocking_async::BlockingAsync;
|
||||
pub use yielding_async::YieldingAsync;
|
@ -1,169 +0,0 @@
|
||||
use embassy_futures::yield_now;
|
||||
|
||||
/// Wrapper that yields for each operation to the wrapped instance
|
||||
///
|
||||
/// This can be used in combination with BlockingAsync<T> to enforce yields
|
||||
/// between long running blocking operations.
|
||||
pub struct YieldingAsync<T> {
|
||||
wrapped: T,
|
||||
}
|
||||
|
||||
impl<T> YieldingAsync<T> {
|
||||
/// Create a new instance of a wrapper that yields after each operation.
|
||||
pub fn new(wrapped: T) -> Self {
|
||||
Self { wrapped }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// I2C implementations
|
||||
//
|
||||
impl<T> embedded_hal_1::i2c::ErrorType for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_1::i2c::ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::i2c::I2c for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::i2c::I2c,
|
||||
{
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(address, read).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(address, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write_read(address, write, read).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
self.wrapped.transaction(address, operations).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SPI implementations
|
||||
//
|
||||
|
||||
impl<T> embedded_hal_async::spi::ErrorType for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBus<Word>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.wrapped.flush().await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(read, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer_in_place(words).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// NOR flash implementations
|
||||
///
|
||||
impl<T: embedded_storage::nor_flash::ErrorType> embedded_storage::nor_flash::ErrorType for YieldingAsync<T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T: embedded_storage_async::nor_flash::ReadNorFlash> embedded_storage_async::nor_flash::ReadNorFlash
|
||||
for YieldingAsync<T>
|
||||
{
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(offset, bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.wrapped.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: embedded_storage_async::nor_flash::NorFlash> embedded_storage_async::nor_flash::NorFlash for YieldingAsync<T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(offset, bytes).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
// Yield between each actual erase
|
||||
for from in (from..to).step_by(T::ERASE_SIZE) {
|
||||
let to = core::cmp::min(from + T::ERASE_SIZE as u32, to);
|
||||
self.wrapped.erase(from, to).await?;
|
||||
yield_now().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embedded_storage_async::nor_flash::NorFlash;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
let mut yielding = YieldingAsync::new(flash);
|
||||
|
||||
yielding.erase(0, 256).await.unwrap();
|
||||
|
||||
let flash = yielding.wrapped;
|
||||
assert_eq!(2, flash.erases.len());
|
||||
assert_eq!((0, 128), flash.erases[0]);
|
||||
assert_eq!((128, 256), flash.erases[1]);
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
/// Convenience helper for concatenating two consecutive flashes into one.
|
||||
/// This is especially useful if used with "flash regions", where one may
|
||||
/// want to concatenate multiple regions into one larger region.
|
||||
pub struct ConcatFlash<First, Second>(First, Second);
|
||||
|
||||
impl<First, Second> ConcatFlash<First, Second> {
|
||||
/// Create a new flash that concatenates two consecutive flashes.
|
||||
pub fn new(first: First, second: Second) -> Self {
|
||||
Self(first, second)
|
||||
}
|
||||
}
|
||||
|
||||
const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize {
|
||||
if first_read_size != second_read_size {
|
||||
panic!("The read size for the concatenated flashes must be the same");
|
||||
}
|
||||
first_read_size
|
||||
}
|
||||
|
||||
const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize {
|
||||
if first_write_size != second_write_size {
|
||||
panic!("The write size for the concatenated flashes must be the same");
|
||||
}
|
||||
first_write_size
|
||||
}
|
||||
|
||||
const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize {
|
||||
let max_erase_size = if first_erase_size > second_erase_size {
|
||||
first_erase_size
|
||||
} else {
|
||||
second_erase_size
|
||||
};
|
||||
if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 {
|
||||
panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size");
|
||||
}
|
||||
max_erase_size
|
||||
}
|
||||
|
||||
impl<First, Second, E> ErrorType for ConcatFlash<First, Second>
|
||||
where
|
||||
First: ErrorType<Error = E>,
|
||||
Second: ErrorType<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
impl<First, Second, E> ReadNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: ReadNorFlash<Error = E>,
|
||||
Second: ReadNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
|
||||
|
||||
fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.read(offset, &mut bytes[..len])?;
|
||||
offset += len as u32;
|
||||
bytes = &mut bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.read(offset - self.0.capacity() as u32, bytes)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.0.capacity() + self.1.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> NorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: NorFlash<Error = E>,
|
||||
Second: NorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
|
||||
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
|
||||
|
||||
fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.write(offset, &bytes[..len])?;
|
||||
offset += len as u32;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.write(offset - self.0.capacity() as u32, bytes)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
|
||||
if from < self.0.capacity() as u32 {
|
||||
let to = core::cmp::min(self.0.capacity() as u32, to);
|
||||
self.0.erase(from, to)?;
|
||||
from = self.0.capacity() as u32;
|
||||
}
|
||||
|
||||
if from < to {
|
||||
self.1
|
||||
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> AsyncReadNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: AsyncReadNorFlash<Error = E>,
|
||||
Second: AsyncReadNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
|
||||
|
||||
async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.read(offset, &mut bytes[..len]).await?;
|
||||
offset += len as u32;
|
||||
bytes = &mut bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.read(offset - self.0.capacity() as u32, bytes).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.0.capacity() + self.1.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> AsyncNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: AsyncNorFlash<Error = E>,
|
||||
Second: AsyncNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
|
||||
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
|
||||
|
||||
async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.write(offset, &bytes[..len]).await?;
|
||||
offset += len as u32;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.write(offset - self.0.capacity() as u32, bytes).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
|
||||
if from < self.0.capacity() as u32 {
|
||||
let to = core::cmp::min(self.0.capacity() as u32, to);
|
||||
self.0.erase(from, to).await?;
|
||||
from = self.0.capacity() as u32;
|
||||
}
|
||||
|
||||
if from < to {
|
||||
self.1
|
||||
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
|
||||
use super::ConcatFlash;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_write_and_read_across_flashes() {
|
||||
let first = MemFlash::<64, 16, 4>::default();
|
||||
let second = MemFlash::<64, 64, 4>::default();
|
||||
let mut f = ConcatFlash::new(first, second);
|
||||
|
||||
f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap();
|
||||
|
||||
assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]);
|
||||
assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
f.read(60, &mut read_buf).unwrap();
|
||||
|
||||
assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_erase_across_flashes() {
|
||||
let first = MemFlash::<128, 16, 4>::new(0x00);
|
||||
let second = MemFlash::<128, 64, 4>::new(0x00);
|
||||
let mut f = ConcatFlash::new(first, second);
|
||||
|
||||
f.erase(64, 192).unwrap();
|
||||
|
||||
assert_eq!(&[0x00; 64], &f.0.mem[0..64]);
|
||||
assert_eq!(&[0xff; 64], &f.0.mem[64..128]);
|
||||
assert_eq!(&[0xff; 64], &f.1.mem[0..64]);
|
||||
assert_eq!(&[0x00; 64], &f.1.mem[64..128]);
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub(crate) struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
|
||||
pub mem: [u8; SIZE],
|
||||
pub writes: Vec<(u32, usize)>,
|
||||
pub erases: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
|
||||
#[allow(unused)]
|
||||
pub const fn new(fill: u8) -> Self {
|
||||
Self {
|
||||
mem: [fill; SIZE],
|
||||
writes: Vec::new(),
|
||||
erases: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) {
|
||||
let len = bytes.len();
|
||||
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) {
|
||||
self.writes.push((offset, bytes.len()));
|
||||
let offset = offset as usize;
|
||||
assert_eq!(0, bytes.len() % WRITE_SIZE);
|
||||
assert_eq!(0, offset % WRITE_SIZE);
|
||||
assert!(offset + bytes.len() <= SIZE);
|
||||
|
||||
self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) {
|
||||
self.erases.push((from, to));
|
||||
let from = from as usize;
|
||||
let to = to as usize;
|
||||
assert_eq!(0, from % ERASE_SIZE);
|
||||
assert_eq!(0, to % ERASE_SIZE);
|
||||
self.mem[from..to].fill(0xff);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(0xff)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
type Error = core::convert::Infallible;
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
//! Utilities related to flash.
|
||||
|
||||
mod concat_flash;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mem_flash;
|
||||
pub mod partition;
|
||||
|
||||
pub use concat_flash::ConcatFlash;
|
@ -1,139 +0,0 @@
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_storage::nor_flash::ErrorType;
|
||||
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// A logical partition of an underlying shared flash
|
||||
///
|
||||
/// A partition holds an offset and a size of the flash,
|
||||
/// and is restricted to operate with that range.
|
||||
/// There is no guarantee that muliple partitions on the same flash
|
||||
/// operate on mutually exclusive ranges - such a separation is up to
|
||||
/// the user to guarantee.
|
||||
pub struct Partition<'a, M: RawMutex, T: NorFlash> {
|
||||
flash: &'a Mutex<M, T>,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> {
|
||||
/// Create a new partition
|
||||
pub const fn new(flash: &'a Mutex<M, T>, offset: u32, size: u32) -> Self {
|
||||
if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0
|
||||
{
|
||||
panic!("Partition offset must be a multiple of read, write and erase size");
|
||||
}
|
||||
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
|
||||
panic!("Partition size must be a multiple of read, write and erase size");
|
||||
}
|
||||
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> {
|
||||
type Error = Error<T::Error>;
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ReadNorFlash for Partition<'_, M, T> {
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash.read(self.offset + offset, bytes).await.map_err(Error::Flash)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.size as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> NorFlash for Partition<'_, M, T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash.write(self.offset + offset, bytes).await.map_err(Error::Flash)
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
if to > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash
|
||||
.erase(self.offset + from, self.offset + to)
|
||||
.await
|
||||
.map_err(Error::Flash)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_read() {
|
||||
let mut flash = MemFlash::<1024, 128, 4>::default();
|
||||
flash.mem[132..132 + 8].fill(0xAA);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
partition.read(4, &mut read_buf).await.unwrap();
|
||||
|
||||
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_write() {
|
||||
let flash = MemFlash::<1024, 128, 4>::default();
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
let write_buf = [0xAA; 8];
|
||||
partition.write(4, &write_buf).await.unwrap();
|
||||
|
||||
let flash = flash.try_lock().unwrap();
|
||||
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
partition.erase(0, 128).await.unwrap();
|
||||
|
||||
let flash = flash.try_lock().unwrap();
|
||||
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user