Merge branch 'main' of https://github.com/embassy-rs/embassy into uart
This commit is contained in:
commit
68441a74c2
16
.github/ci/build-stable.sh
vendored
Executable file
16
.github/ci/build-stable.sh
vendored
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## on push branch~=gh-readonly-queue/main/.*
|
||||||
|
## on pull_request
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export RUSTUP_HOME=/ci/cache/rustup
|
||||||
|
export CARGO_HOME=/ci/cache/cargo
|
||||||
|
export CARGO_TARGET_DIR=/ci/cache/target
|
||||||
|
|
||||||
|
hashtime restore /ci/cache/filetime.json || true
|
||||||
|
hashtime save /ci/cache/filetime.json
|
||||||
|
|
||||||
|
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
|
||||||
|
|
||||||
|
./ci_stable.sh
|
18
.github/ci/build.sh
vendored
Executable file
18
.github/ci/build.sh
vendored
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/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_TOKEN=$(cat /ci/secrets/teleprobe-token.txt)
|
||||||
|
fi
|
||||||
|
|
||||||
|
hashtime restore /ci/cache/filetime.json || true
|
||||||
|
hashtime save /ci/cache/filetime.json
|
||||||
|
|
||||||
|
./ci.sh
|
28
.github/ci/test.sh
vendored
Executable file
28
.github/ci/test.sh
vendored
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
## on push branch~=gh-readonly-queue/main/.*
|
||||||
|
## on pull_request
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export RUSTUP_HOME=/ci/cache/rustup
|
||||||
|
export CARGO_HOME=/ci/cache/cargo
|
||||||
|
export CARGO_TARGET_DIR=/ci/cache/target
|
||||||
|
|
||||||
|
hashtime restore /ci/cache/filetime.json || true
|
||||||
|
hashtime save /ci/cache/filetime.json
|
||||||
|
|
||||||
|
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||||
|
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||||
|
cargo test --manifest-path ./embassy-hal-common/Cargo.toml
|
||||||
|
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue
|
||||||
|
|
||||||
|
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
|
||||||
|
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly
|
||||||
|
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek
|
||||||
|
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty
|
||||||
|
|
||||||
|
#cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1 ## broken doctests
|
||||||
|
|
||||||
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti
|
||||||
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti
|
||||||
|
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti
|
2
.github/workflows/doc.yml
vendored
2
.github/workflows/doc.yml
vendored
@ -2,7 +2,7 @@ name: Docs
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [main]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BUILDER_THREADS: '1'
|
BUILDER_THREADS: '1'
|
||||||
|
80
.github/workflows/rust.yml
vendored
80
.github/workflows/rust.yml
vendored
@ -1,80 +0,0 @@
|
|||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [staging, trying, master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
all:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [build-nightly, build-stable, test]
|
|
||||||
steps:
|
|
||||||
- name: Done
|
|
||||||
run: exit 0
|
|
||||||
build-nightly:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Cache multiple paths
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/bin/
|
|
||||||
~/.cargo/registry/index/
|
|
||||||
~/.cargo/registry/cache/
|
|
||||||
~/.cargo/git/db/
|
|
||||||
target_ci
|
|
||||||
key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
|
|
||||||
- name: build
|
|
||||||
env:
|
|
||||||
TELEPROBE_TOKEN: ${{ secrets.TELEPROBE_TOKEN }}
|
|
||||||
run: |
|
|
||||||
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
|
|
||||||
chmod +x /usr/local/bin/cargo-batch
|
|
||||||
./ci.sh
|
|
||||||
rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
|
|
||||||
build-stable:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Cache multiple paths
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/bin/
|
|
||||||
~/.cargo/registry/index/
|
|
||||||
~/.cargo/registry/cache/
|
|
||||||
~/.cargo/git/db/
|
|
||||||
target_ci_stable
|
|
||||||
key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
|
|
||||||
- name: build
|
|
||||||
run: |
|
|
||||||
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
|
|
||||||
chmod +x /usr/local/bin/cargo-batch
|
|
||||||
./ci_stable.sh
|
|
||||||
rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
|
|
||||||
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Test boot
|
|
||||||
working-directory: ./embassy-boot/boot
|
|
||||||
run: cargo test && cargo test --features nightly && cargo test --features "ed25519-dalek,nightly" && cargo test --features "ed25519-salty,nightly"
|
|
||||||
|
|
||||||
- name: Test sync
|
|
||||||
working-directory: ./embassy-sync
|
|
||||||
run: cargo test
|
|
1
ci.sh
1
ci.sh
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
export CARGO_TARGET_DIR=$PWD/target_ci
|
|
||||||
export RUSTFLAGS=-Dwarnings
|
export RUSTFLAGS=-Dwarnings
|
||||||
export DEFMT_LOG=trace
|
export DEFMT_LOG=trace
|
||||||
|
|
||||||
|
@ -2,12 +2,9 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
export CARGO_TARGET_DIR=$PWD/target_ci_stable
|
|
||||||
export RUSTFLAGS=-Dwarnings
|
export RUSTFLAGS=-Dwarnings
|
||||||
export DEFMT_LOG=trace
|
export DEFMT_LOG=trace
|
||||||
|
|
||||||
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
|
|
||||||
|
|
||||||
cargo batch \
|
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 thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||||
|
@ -1,543 +0,0 @@
|
|||||||
use digest::Digest;
|
|
||||||
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
|
|
||||||
|
|
||||||
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
|
||||||
|
|
||||||
/// Errors returned by FirmwareUpdater
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum FirmwareUpdaterError {
|
|
||||||
/// Error from flash.
|
|
||||||
Flash(NorFlashErrorKind),
|
|
||||||
/// Signature errors.
|
|
||||||
Signature(signature::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
|
||||||
impl defmt::Format for FirmwareUpdaterError {
|
|
||||||
fn format(&self, fmt: defmt::Formatter) {
|
|
||||||
match self {
|
|
||||||
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
|
|
||||||
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E> From<E> for FirmwareUpdaterError
|
|
||||||
where
|
|
||||||
E: NorFlashError,
|
|
||||||
{
|
|
||||||
fn from(error: E) -> Self {
|
|
||||||
FirmwareUpdaterError::Flash(error.kind())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
|
||||||
/// 'mess up' the internal bootloader state
|
|
||||||
pub struct FirmwareUpdater {
|
|
||||||
state: Partition,
|
|
||||||
dfu: Partition,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "none")]
|
|
||||||
impl Default for FirmwareUpdater {
|
|
||||||
fn default() -> Self {
|
|
||||||
extern "C" {
|
|
||||||
static __bootloader_state_start: u32;
|
|
||||||
static __bootloader_state_end: u32;
|
|
||||||
static __bootloader_dfu_start: u32;
|
|
||||||
static __bootloader_dfu_end: u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dfu = unsafe {
|
|
||||||
Partition::new(
|
|
||||||
&__bootloader_dfu_start as *const u32 as u32,
|
|
||||||
&__bootloader_dfu_end as *const u32 as u32,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let state = unsafe {
|
|
||||||
Partition::new(
|
|
||||||
&__bootloader_state_start as *const u32 as u32,
|
|
||||||
&__bootloader_state_end as *const u32 as u32,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
|
||||||
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
|
||||||
FirmwareUpdater::new(dfu, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FirmwareUpdater {
|
|
||||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
|
||||||
pub const fn new(dfu: Partition, state: Partition) -> Self {
|
|
||||||
Self { dfu, state }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtain the current state.
|
|
||||||
///
|
|
||||||
/// This is useful to check if the bootloader has just done a swap, in order
|
|
||||||
/// to do verifications and self-tests of the new image before calling
|
|
||||||
/// `mark_booted`.
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub async fn get_state<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
state_flash: &mut F,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
) -> Result<State, FirmwareUpdaterError> {
|
|
||||||
self.state.read(state_flash, 0, aligned).await?;
|
|
||||||
|
|
||||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
|
||||||
Ok(State::Swap)
|
|
||||||
} else {
|
|
||||||
Ok(State::Boot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
|
||||||
/// proceed with updating the firmware as it must be signed with a
|
|
||||||
/// corresponding private key (otherwise it could be malicious firmware).
|
|
||||||
///
|
|
||||||
/// Mark to trigger firmware swap on next boot if verify suceeds.
|
|
||||||
///
|
|
||||||
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
|
|
||||||
/// been generated from a SHA-512 digest of the firmware bytes.
|
|
||||||
///
|
|
||||||
/// If no signature feature is set then this method will always return a
|
|
||||||
/// signature error.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
|
||||||
/// and written to.
|
|
||||||
#[cfg(all(feature = "_verify", feature = "nightly"))]
|
|
||||||
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
_state_and_dfu_flash: &mut F,
|
|
||||||
_public_key: &[u8],
|
|
||||||
_signature: &[u8],
|
|
||||||
_update_len: u32,
|
|
||||||
_aligned: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert_eq!(_aligned.len(), F::WRITE_SIZE);
|
|
||||||
assert!(_update_len <= self.dfu.size());
|
|
||||||
|
|
||||||
#[cfg(feature = "ed25519-dalek")]
|
|
||||||
{
|
|
||||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
|
||||||
|
|
||||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
|
||||||
|
|
||||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
|
||||||
|
|
||||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
|
||||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
|
||||||
|
|
||||||
let mut message = [0; 64];
|
|
||||||
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ed25519-salty")]
|
|
||||||
{
|
|
||||||
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
|
|
||||||
use salty::{PublicKey, Signature};
|
|
||||||
|
|
||||||
use crate::digest_adapters::salty::Sha512;
|
|
||||||
|
|
||||||
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
|
|
||||||
FirmwareUpdaterError::Signature(signature::Error::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
|
|
||||||
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
|
|
||||||
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
|
|
||||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
|
||||||
|
|
||||||
let mut message = [0; 64];
|
|
||||||
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let r = public_key.verify(&message, &signature);
|
|
||||||
trace!(
|
|
||||||
"Verifying with public key {}, signature {} and message {} yields ok: {}",
|
|
||||||
public_key.to_bytes(),
|
|
||||||
signature.to_bytes(),
|
|
||||||
message,
|
|
||||||
r.is_ok()
|
|
||||||
);
|
|
||||||
r.map_err(into_signature_error)?
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify the update in DFU with any digest.
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub async fn hash<F: AsyncNorFlash, D: Digest>(
|
|
||||||
&mut self,
|
|
||||||
dfu_flash: &mut F,
|
|
||||||
update_len: u32,
|
|
||||||
chunk_buf: &mut [u8],
|
|
||||||
output: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
let mut digest = D::new();
|
|
||||||
for offset in (0..update_len).step_by(chunk_buf.len()) {
|
|
||||||
self.dfu.read(dfu_flash, offset, chunk_buf).await?;
|
|
||||||
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
|
|
||||||
digest.update(&chunk_buf[..len]);
|
|
||||||
}
|
|
||||||
output.copy_from_slice(digest.finalize().as_slice());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark to trigger firmware swap on next boot.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
|
||||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
|
||||||
pub async fn mark_updated<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
state_flash: &mut F,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
|
||||||
self.set_magic(aligned, SWAP_MAGIC, state_flash).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark firmware boot successful and stop rollback on reset.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub async fn mark_booted<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
state_flash: &mut F,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
|
||||||
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
async fn set_magic<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
magic: u8,
|
|
||||||
state_flash: &mut F,
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
self.state.read(state_flash, 0, aligned).await?;
|
|
||||||
|
|
||||||
if aligned.iter().any(|&b| b != magic) {
|
|
||||||
// Read progress validity
|
|
||||||
self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
|
|
||||||
|
|
||||||
// FIXME: Do not make this assumption.
|
|
||||||
const STATE_ERASE_VALUE: u8 = 0xFF;
|
|
||||||
|
|
||||||
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
|
||||||
// The current progress validity marker is invalid
|
|
||||||
} else {
|
|
||||||
// Invalidate progress
|
|
||||||
aligned.fill(!STATE_ERASE_VALUE);
|
|
||||||
self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear magic and progress
|
|
||||||
self.state.wipe(state_flash).await?;
|
|
||||||
|
|
||||||
// Set magic
|
|
||||||
aligned.fill(magic);
|
|
||||||
self.state.write(state_flash, 0, aligned).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write data to a flash page.
|
|
||||||
///
|
|
||||||
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Failing to meet alignment and size requirements may result in a panic.
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub async fn write_firmware<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
offset: usize,
|
|
||||||
data: &[u8],
|
|
||||||
dfu_flash: &mut F,
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert!(data.len() >= F::ERASE_SIZE);
|
|
||||||
|
|
||||||
self.dfu
|
|
||||||
.erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.dfu.write(dfu_flash, offset as u32, data).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare for an incoming DFU update by erasing the entire DFU area and
|
|
||||||
/// returning its `Partition`.
|
|
||||||
///
|
|
||||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
|
||||||
/// exchange for added complexity.
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub async fn prepare_update<F: AsyncNorFlash>(
|
|
||||||
&mut self,
|
|
||||||
dfu_flash: &mut F,
|
|
||||||
) -> Result<Partition, FirmwareUpdaterError> {
|
|
||||||
self.dfu.wipe(dfu_flash).await?;
|
|
||||||
|
|
||||||
Ok(self.dfu)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Blocking API
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Obtain the current state.
|
|
||||||
///
|
|
||||||
/// This is useful to check if the bootloader has just done a swap, in order
|
|
||||||
/// to do verifications and self-tests of the new image before calling
|
|
||||||
/// `mark_booted`.
|
|
||||||
pub fn get_state_blocking<F: NorFlash>(
|
|
||||||
&mut self,
|
|
||||||
state_flash: &mut F,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
) -> Result<State, FirmwareUpdaterError> {
|
|
||||||
self.state.read_blocking(state_flash, 0, aligned)?;
|
|
||||||
|
|
||||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
|
||||||
Ok(State::Swap)
|
|
||||||
} else {
|
|
||||||
Ok(State::Boot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
|
||||||
/// proceed with updating the firmware as it must be signed with a
|
|
||||||
/// corresponding private key (otherwise it could be malicious firmware).
|
|
||||||
///
|
|
||||||
/// Mark to trigger firmware swap on next boot if verify suceeds.
|
|
||||||
///
|
|
||||||
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
|
|
||||||
/// been generated from a SHA-512 digest of the firmware bytes.
|
|
||||||
///
|
|
||||||
/// If no signature feature is set then this method will always return a
|
|
||||||
/// signature error.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
|
||||||
/// and written to.
|
|
||||||
#[cfg(feature = "_verify")]
|
|
||||||
pub fn verify_and_mark_updated_blocking<F: NorFlash>(
|
|
||||||
&mut self,
|
|
||||||
_state_and_dfu_flash: &mut F,
|
|
||||||
_public_key: &[u8],
|
|
||||||
_signature: &[u8],
|
|
||||||
_update_len: u32,
|
|
||||||
_aligned: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert_eq!(_aligned.len(), F::WRITE_SIZE);
|
|
||||||
assert!(_update_len <= self.dfu.size());
|
|
||||||
|
|
||||||
#[cfg(feature = "ed25519-dalek")]
|
|
||||||
{
|
|
||||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
|
||||||
|
|
||||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
|
||||||
|
|
||||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
|
||||||
|
|
||||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
|
||||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
|
||||||
|
|
||||||
let mut message = [0; 64];
|
|
||||||
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
|
|
||||||
|
|
||||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ed25519-salty")]
|
|
||||||
{
|
|
||||||
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
|
|
||||||
use salty::{PublicKey, Signature};
|
|
||||||
|
|
||||||
use crate::digest_adapters::salty::Sha512;
|
|
||||||
|
|
||||||
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
|
|
||||||
FirmwareUpdaterError::Signature(signature::Error::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
|
|
||||||
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
|
|
||||||
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
|
|
||||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
|
||||||
|
|
||||||
let mut message = [0; 64];
|
|
||||||
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
|
|
||||||
|
|
||||||
let r = public_key.verify(&message, &signature);
|
|
||||||
trace!(
|
|
||||||
"Verifying with public key {}, signature {} and message {} yields ok: {}",
|
|
||||||
public_key.to_bytes(),
|
|
||||||
signature.to_bytes(),
|
|
||||||
message,
|
|
||||||
r.is_ok()
|
|
||||||
);
|
|
||||||
r.map_err(into_signature_error)?
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify the update in DFU with any digest.
|
|
||||||
pub fn hash_blocking<F: NorFlash, D: Digest>(
|
|
||||||
&mut self,
|
|
||||||
dfu_flash: &mut F,
|
|
||||||
update_len: u32,
|
|
||||||
chunk_buf: &mut [u8],
|
|
||||||
output: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
let mut digest = D::new();
|
|
||||||
for offset in (0..update_len).step_by(chunk_buf.len()) {
|
|
||||||
self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
|
|
||||||
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
|
|
||||||
digest.update(&chunk_buf[..len]);
|
|
||||||
}
|
|
||||||
output.copy_from_slice(digest.finalize().as_slice());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark to trigger firmware swap on next boot.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
|
||||||
#[cfg(not(feature = "_verify"))]
|
|
||||||
pub fn mark_updated_blocking<F: NorFlash>(
|
|
||||||
&mut self,
|
|
||||||
state_flash: &mut F,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
|
||||||
self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark firmware boot successful and stop rollback on reset.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
|
||||||
pub fn mark_booted_blocking<F: NorFlash>(
|
|
||||||
&mut self,
|
|
||||||
state_flash: &mut F,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
|
||||||
self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_magic_blocking<F: NorFlash>(
|
|
||||||
&mut self,
|
|
||||||
aligned: &mut [u8],
|
|
||||||
magic: u8,
|
|
||||||
state_flash: &mut F,
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
self.state.read_blocking(state_flash, 0, aligned)?;
|
|
||||||
|
|
||||||
if aligned.iter().any(|&b| b != magic) {
|
|
||||||
// Read progress validity
|
|
||||||
self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
|
|
||||||
|
|
||||||
// FIXME: Do not make this assumption.
|
|
||||||
const STATE_ERASE_VALUE: u8 = 0xFF;
|
|
||||||
|
|
||||||
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
|
||||||
// The current progress validity marker is invalid
|
|
||||||
} else {
|
|
||||||
// Invalidate progress
|
|
||||||
aligned.fill(!STATE_ERASE_VALUE);
|
|
||||||
self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear magic and progress
|
|
||||||
self.state.wipe_blocking(state_flash)?;
|
|
||||||
|
|
||||||
// Set magic
|
|
||||||
aligned.fill(magic);
|
|
||||||
self.state.write_blocking(state_flash, 0, aligned)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write data to a flash page.
|
|
||||||
///
|
|
||||||
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Failing to meet alignment and size requirements may result in a panic.
|
|
||||||
pub fn write_firmware_blocking<F: NorFlash>(
|
|
||||||
&mut self,
|
|
||||||
offset: usize,
|
|
||||||
data: &[u8],
|
|
||||||
dfu_flash: &mut F,
|
|
||||||
) -> Result<(), FirmwareUpdaterError> {
|
|
||||||
assert!(data.len() >= F::ERASE_SIZE);
|
|
||||||
|
|
||||||
self.dfu
|
|
||||||
.erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
|
|
||||||
|
|
||||||
self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare for an incoming DFU update by erasing the entire DFU area and
|
|
||||||
/// returning its `Partition`.
|
|
||||||
///
|
|
||||||
/// Using this instead of `write_firmware_blocking` allows for an optimized
|
|
||||||
/// API in exchange for added complexity.
|
|
||||||
pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
|
|
||||||
self.dfu.wipe_blocking(flash)?;
|
|
||||||
|
|
||||||
Ok(self.dfu)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use futures::executor::block_on;
|
|
||||||
use sha1::{Digest, Sha1};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::mem_flash::MemFlash;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
fn can_verify_sha1() {
|
|
||||||
const STATE: Partition = Partition::new(0, 4096);
|
|
||||||
const DFU: Partition = Partition::new(65536, 131072);
|
|
||||||
|
|
||||||
let mut flash = MemFlash::<131072, 4096, 8>::default();
|
|
||||||
|
|
||||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
|
||||||
let mut to_write = [0; 4096];
|
|
||||||
to_write[..7].copy_from_slice(update.as_slice());
|
|
||||||
|
|
||||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
|
||||||
block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
|
|
||||||
let mut chunk_buf = [0; 2];
|
|
||||||
let mut hash = [0; 20];
|
|
||||||
block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(Sha1::digest(update).as_slice(), hash);
|
|
||||||
}
|
|
||||||
}
|
|
251
embassy-boot/boot/src/firmware_updater/asynch.rs
Normal file
251
embassy-boot/boot/src/firmware_updater/asynch.rs
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
use digest::Digest;
|
||||||
|
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
|
||||||
|
|
||||||
|
use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
||||||
|
|
||||||
|
impl FirmwareUpdater {
|
||||||
|
/// 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<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.
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use futures::executor::block_on;
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::mem_flash::MemFlash;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
221
embassy-boot/boot/src/firmware_updater/blocking.rs
Normal file
221
embassy-boot/boot/src/firmware_updater/blocking.rs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
use digest::Digest;
|
||||||
|
use embedded_storage::nor_flash::NorFlash;
|
||||||
|
|
||||||
|
use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
||||||
|
|
||||||
|
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`.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
71
embassy-boot/boot/src/firmware_updater/mod.rs
Normal file
71
embassy-boot/boot/src/firmware_updater/mod.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
mod asynch;
|
||||||
|
mod blocking;
|
||||||
|
|
||||||
|
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||||
|
|
||||||
|
use crate::Partition;
|
||||||
|
|
||||||
|
/// Errors returned by FirmwareUpdater
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FirmwareUpdaterError {
|
||||||
|
/// Error from flash.
|
||||||
|
Flash(NorFlashErrorKind),
|
||||||
|
/// Signature errors.
|
||||||
|
Signature(signature::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
impl defmt::Format for FirmwareUpdaterError {
|
||||||
|
fn format(&self, fmt: defmt::Formatter) {
|
||||||
|
match self {
|
||||||
|
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
|
||||||
|
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> From<E> for FirmwareUpdaterError
|
||||||
|
where
|
||||||
|
E: NorFlashError,
|
||||||
|
{
|
||||||
|
fn from(error: E) -> Self {
|
||||||
|
FirmwareUpdaterError::Flash(error.kind())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||||
|
/// 'mess up' the internal bootloader state
|
||||||
|
pub struct FirmwareUpdater {
|
||||||
|
state: Partition,
|
||||||
|
dfu: Partition,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "none")]
|
||||||
|
impl Default for FirmwareUpdater {
|
||||||
|
fn default() -> Self {
|
||||||
|
extern "C" {
|
||||||
|
static __bootloader_state_start: u32;
|
||||||
|
static __bootloader_state_end: u32;
|
||||||
|
static __bootloader_dfu_start: u32;
|
||||||
|
static __bootloader_dfu_end: u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dfu = unsafe {
|
||||||
|
Partition::new(
|
||||||
|
&__bootloader_dfu_start as *const u32 as u32,
|
||||||
|
&__bootloader_dfu_end as *const u32 as u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let state = unsafe {
|
||||||
|
Partition::new(
|
||||||
|
&__bootloader_state_start as *const u32 as u32,
|
||||||
|
&__bootloader_state_end as *const u32 as u32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
||||||
|
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
||||||
|
FirmwareUpdater::new(dfu, state)
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,12 @@
|
|||||||
//!
|
//!
|
||||||
//! # Example (nrf52)
|
//! # Example (nrf52)
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust,ignore
|
||||||
//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||||
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
||||||
//!
|
//!
|
||||||
//! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new();
|
//! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new();
|
||||||
//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
|
//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default());
|
||||||
//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default());
|
|
||||||
//! let i2c_bus = NoopMutex::new(RefCell::new(i2c));
|
//! let i2c_bus = NoopMutex::new(RefCell::new(i2c));
|
||||||
//! let i2c_bus = I2C_BUS.init(i2c_bus);
|
//! let i2c_bus = I2C_BUS.init(i2c_bus);
|
||||||
//!
|
//!
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
//!
|
//!
|
||||||
//! # Example (nrf52)
|
//! # Example (nrf52)
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust,ignore
|
||||||
//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
||||||
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
||||||
//!
|
//!
|
||||||
//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
|
//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
|
||||||
//! let irq = interrupt::take!(SPIM3);
|
//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default());
|
||||||
//! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default());
|
|
||||||
//! let spi_bus = NoopMutex::new(RefCell::new(spi));
|
//! let spi_bus = NoopMutex::new(RefCell::new(spi));
|
||||||
//! let spi_bus = SPI_BUS.init(spi_bus);
|
//! let spi_bus = SPI_BUS.init(spi_bus);
|
||||||
//!
|
//!
|
||||||
|
@ -458,8 +458,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn push_slices() {
|
fn push_slices() {
|
||||||
init();
|
|
||||||
|
|
||||||
let mut b = [0; 4];
|
let mut b = [0; 4];
|
||||||
let rb = RingBuffer::new();
|
let rb = RingBuffer::new();
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -67,7 +67,7 @@ fn compare_n(n: usize) -> u32 {
|
|||||||
1 << (n + 16)
|
1 << (n + 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(tests)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -912,6 +912,16 @@ fn main() {
|
|||||||
println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
|
println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========
|
||||||
|
// stm32wb tl_mbox link sections
|
||||||
|
|
||||||
|
if chip_name.starts_with("stm32wb") {
|
||||||
|
let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string();
|
||||||
|
fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap();
|
||||||
|
println!("cargo:rustc-link-search={}", out_dir.display());
|
||||||
|
println!("cargo:rerun-if-changed=tl_mbox.x.in");
|
||||||
|
}
|
||||||
|
|
||||||
// =======
|
// =======
|
||||||
// Features for targeting groups of chips
|
// Features for targeting groups of chips
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ pub(super) fn get_sector(address: u32, regions: &[&FlashRegion]) -> FlashSector
|
|||||||
bank_offset = 0;
|
bank_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if address < region.end() {
|
if address >= region.base && address < region.end() {
|
||||||
let index_in_region = (address - region.base) / region.erase_size;
|
let index_in_region = (address - region.base) / region.erase_size;
|
||||||
return FlashSector {
|
return FlashSector {
|
||||||
bank: region.bank,
|
bank: region.bank,
|
||||||
|
@ -41,8 +41,6 @@ pub mod crc;
|
|||||||
pub mod flash;
|
pub mod flash;
|
||||||
#[cfg(all(spi_v1, rcc_f4))]
|
#[cfg(all(spi_v1, rcc_f4))]
|
||||||
pub mod i2s;
|
pub mod i2s;
|
||||||
#[cfg(stm32wb)]
|
|
||||||
pub mod ipcc;
|
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
#[cfg(quadspi)]
|
#[cfg(quadspi)]
|
||||||
pub mod qspi;
|
pub mod qspi;
|
||||||
|
@ -209,39 +209,39 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compute_dead_time_value() {
|
fn test_compute_dead_time_value() {
|
||||||
struct test_run {
|
struct TestRun {
|
||||||
value: u16,
|
value: u16,
|
||||||
ckd: Ckd,
|
ckd: Ckd,
|
||||||
bits: u8,
|
bits: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
let fn_results = [
|
let fn_results = [
|
||||||
test_run {
|
TestRun {
|
||||||
value: 1,
|
value: 1,
|
||||||
ckd: Ckd::DIV1,
|
ckd: Ckd::DIV1,
|
||||||
bits: 1,
|
bits: 1,
|
||||||
},
|
},
|
||||||
test_run {
|
TestRun {
|
||||||
value: 125,
|
value: 125,
|
||||||
ckd: Ckd::DIV1,
|
ckd: Ckd::DIV1,
|
||||||
bits: 125,
|
bits: 125,
|
||||||
},
|
},
|
||||||
test_run {
|
TestRun {
|
||||||
value: 245,
|
value: 245,
|
||||||
ckd: Ckd::DIV1,
|
ckd: Ckd::DIV1,
|
||||||
bits: 64 + 245 / 2,
|
bits: 64 + 245 / 2,
|
||||||
},
|
},
|
||||||
test_run {
|
TestRun {
|
||||||
value: 255,
|
value: 255,
|
||||||
ckd: Ckd::DIV2,
|
ckd: Ckd::DIV2,
|
||||||
bits: 127,
|
bits: 127,
|
||||||
},
|
},
|
||||||
test_run {
|
TestRun {
|
||||||
value: 400,
|
value: 400,
|
||||||
ckd: Ckd::DIV1,
|
ckd: Ckd::DIV1,
|
||||||
bits: 32 + (400u16 / 8) as u8,
|
bits: 32 + (400u16 / 8) as u8,
|
||||||
},
|
},
|
||||||
test_run {
|
TestRun {
|
||||||
value: 600,
|
value: 600,
|
||||||
ckd: Ckd::DIV4,
|
ckd: Ckd::DIV4,
|
||||||
bits: 64 + (600u16 / 8) as u8,
|
bits: 64 + (600u16 / 8) as u8,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use core::mem::MaybeUninit;
|
|
||||||
|
|
||||||
use embassy_futures::block_on;
|
use embassy_futures::block_on;
|
||||||
|
|
||||||
use super::cmd::CmdSerial;
|
use super::cmd::CmdSerial;
|
||||||
@ -10,17 +8,17 @@ use super::{
|
|||||||
channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE, TL_CHANNEL,
|
channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE, TL_CHANNEL,
|
||||||
TL_REF_TABLE,
|
TL_REF_TABLE,
|
||||||
};
|
};
|
||||||
use crate::ipcc::Ipcc;
|
|
||||||
use crate::tl_mbox::cmd::CmdPacket;
|
use crate::tl_mbox::cmd::CmdPacket;
|
||||||
|
use crate::tl_mbox::ipcc::Ipcc;
|
||||||
|
|
||||||
pub struct Ble;
|
pub struct Ble;
|
||||||
|
|
||||||
impl Ble {
|
impl Ble {
|
||||||
pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
|
pub fn enable() {
|
||||||
unsafe {
|
unsafe {
|
||||||
LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr());
|
LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr());
|
||||||
|
|
||||||
TL_BLE_TABLE = MaybeUninit::new(BleTable {
|
TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable {
|
||||||
pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(),
|
pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(),
|
||||||
pcs_buffer: CS_BUFFER.as_mut_ptr().cast(),
|
pcs_buffer: CS_BUFFER.as_mut_ptr().cast(),
|
||||||
pevt_queue: EVT_QUEUE.as_ptr().cast(),
|
pevt_queue: EVT_QUEUE.as_ptr().cast(),
|
||||||
@ -28,12 +26,10 @@ impl Ble {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcc.c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true);
|
Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true);
|
||||||
|
|
||||||
Ble
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
|
pub fn evt_handler() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut node_ptr = core::ptr::null_mut();
|
let mut node_ptr = core::ptr::null_mut();
|
||||||
let node_ptr_ptr: *mut _ = &mut node_ptr;
|
let node_ptr_ptr: *mut _ = &mut node_ptr;
|
||||||
@ -48,10 +44,10 @@ impl Ble {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
|
Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
|
pub fn send_cmd(buf: &[u8]) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer;
|
let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer;
|
||||||
let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial;
|
let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial;
|
||||||
@ -63,6 +59,6 @@ impl Ble {
|
|||||||
cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8;
|
cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL);
|
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
pub mod cpu1 {
|
pub mod cpu1 {
|
||||||
use crate::ipcc::IpccChannel;
|
use crate::tl_mbox::ipcc::IpccChannel;
|
||||||
|
|
||||||
// Not used currently but reserved
|
// Not used currently but reserved
|
||||||
pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1;
|
pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1;
|
||||||
@ -75,7 +75,7 @@ pub mod cpu1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod cpu2 {
|
pub mod cpu2 {
|
||||||
use crate::ipcc::IpccChannel;
|
use crate::tl_mbox::ipcc::IpccChannel;
|
||||||
|
|
||||||
pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1;
|
pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1;
|
||||||
pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2;
|
pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2;
|
||||||
|
@ -3,7 +3,7 @@ use core::mem::MaybeUninit;
|
|||||||
use super::cmd::{AclDataPacket, AclDataSerial};
|
use super::cmd::{AclDataPacket, AclDataSerial};
|
||||||
use super::consts::TlPacketType;
|
use super::consts::TlPacketType;
|
||||||
use super::{PacketHeader, TL_EVT_HEADER_SIZE};
|
use super::{PacketHeader, TL_EVT_HEADER_SIZE};
|
||||||
use crate::tl_mbox::mm;
|
use crate::tl_mbox::mm::MemoryManager;
|
||||||
|
|
||||||
/// the payload of [`Evt`] for a command status event
|
/// the payload of [`Evt`] for a command status event
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
@ -131,9 +131,6 @@ impl EvtBox {
|
|||||||
|
|
||||||
impl Drop for EvtBox {
|
impl Drop for EvtBox {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
use crate::ipcc::Ipcc;
|
MemoryManager::evt_drop(self.ptr);
|
||||||
|
|
||||||
let mut ipcc = Ipcc::new_inner(unsafe { crate::Peripherals::steal() }.IPCC);
|
|
||||||
mm::MemoryManager::evt_drop(self.ptr, &mut ipcc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
use self::sealed::Instance;
|
||||||
|
|
||||||
use crate::ipcc::sealed::Instance;
|
|
||||||
use crate::peripherals::IPCC;
|
use crate::peripherals::IPCC;
|
||||||
use crate::rcc::sealed::RccPeripheral;
|
use crate::rcc::sealed::RccPeripheral;
|
||||||
|
|
||||||
@ -22,29 +20,17 @@ pub enum IpccChannel {
|
|||||||
Channel6 = 5,
|
Channel6 = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod sealed {
|
pub mod sealed {
|
||||||
pub trait Instance: crate::rcc::RccPeripheral {
|
pub trait Instance: crate::rcc::RccPeripheral {
|
||||||
fn regs() -> crate::pac::ipcc::Ipcc;
|
fn regs() -> crate::pac::ipcc::Ipcc;
|
||||||
fn set_cpu2(enabled: bool);
|
fn set_cpu2(enabled: bool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ipcc<'d> {
|
pub struct Ipcc;
|
||||||
_peri: PeripheralRef<'d, IPCC>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'d> Ipcc<'d> {
|
impl Ipcc {
|
||||||
pub fn new(peri: impl Peripheral<P = IPCC> + 'd, _config: Config) -> Self {
|
pub fn enable(_config: Config) {
|
||||||
Self::new_inner(peri)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new_inner(peri: impl Peripheral<P = IPCC> + 'd) -> Self {
|
|
||||||
into_ref!(peri);
|
|
||||||
|
|
||||||
Self { _peri: peri }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(&mut self) {
|
|
||||||
IPCC::enable();
|
IPCC::enable();
|
||||||
IPCC::reset();
|
IPCC::reset();
|
||||||
IPCC::set_cpu2(true);
|
IPCC::set_cpu2(true);
|
||||||
@ -61,56 +47,60 @@ impl<'d> Ipcc<'d> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
pub fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
|
unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool {
|
pub fn c1_get_rx_channel(channel: IpccChannel) -> bool {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
|
unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
#[allow(dead_code)]
|
||||||
|
pub fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
|
unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool {
|
#[allow(dead_code)]
|
||||||
|
pub fn c2_get_rx_channel(channel: IpccChannel) -> bool {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
|
unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
pub fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
|
unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool {
|
pub fn c1_get_tx_channel(channel: IpccChannel) -> bool {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
|
unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
|
#[allow(dead_code)]
|
||||||
|
pub fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
|
unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool {
|
#[allow(dead_code)]
|
||||||
|
pub fn c2_get_tx_channel(channel: IpccChannel) -> bool {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
// If bit is set to 1 then interrupt is disabled
|
// If bit is set to 1 then interrupt is disabled
|
||||||
@ -118,53 +108,51 @@ impl<'d> Ipcc<'d> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// clears IPCC receive channel status for CPU1
|
/// clears IPCC receive channel status for CPU1
|
||||||
pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) {
|
pub fn c1_clear_flag_channel(channel: IpccChannel) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
|
unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
/// clears IPCC receive channel status for CPU2
|
/// clears IPCC receive channel status for CPU2
|
||||||
pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) {
|
pub fn c2_clear_flag_channel(channel: IpccChannel) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
|
unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) {
|
pub fn c1_set_flag_channel(channel: IpccChannel) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
|
unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) {
|
#[allow(dead_code)]
|
||||||
|
pub fn c2_set_flag_channel(channel: IpccChannel) {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
|
unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool {
|
pub fn c1_is_active_flag(channel: IpccChannel) -> bool {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
|
unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool {
|
pub fn c2_is_active_flag(channel: IpccChannel) -> bool {
|
||||||
let regs = IPCC::regs();
|
let regs = IPCC::regs();
|
||||||
|
|
||||||
unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
|
unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_tx_pending(&self, channel: IpccChannel) -> bool {
|
pub fn is_tx_pending(channel: IpccChannel) -> bool {
|
||||||
!self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel)
|
!Self::c1_is_active_flag(channel) && Self::c1_get_tx_channel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_rx_pending(&self, channel: IpccChannel) -> bool {
|
pub fn is_rx_pending(channel: IpccChannel) -> bool {
|
||||||
self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel)
|
Self::c2_is_active_flag(channel) && Self::c1_get_rx_channel(channel)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_mut_ptr(&self) -> *mut Self {
|
|
||||||
unsafe { &mut core::ptr::read(self) as *mut _ }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,22 +1,20 @@
|
|||||||
use core::mem::MaybeUninit;
|
|
||||||
|
|
||||||
use super::evt::EvtPacket;
|
use super::evt::EvtPacket;
|
||||||
use super::unsafe_linked_list::LinkedListNode;
|
use super::unsafe_linked_list::LinkedListNode;
|
||||||
use super::{
|
use super::{
|
||||||
channels, MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE,
|
channels, MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE,
|
||||||
SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, TL_REF_TABLE,
|
SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, TL_REF_TABLE,
|
||||||
};
|
};
|
||||||
use crate::ipcc::Ipcc;
|
use crate::tl_mbox::ipcc::Ipcc;
|
||||||
|
|
||||||
pub struct MemoryManager;
|
pub struct MemoryManager;
|
||||||
|
|
||||||
impl MemoryManager {
|
impl MemoryManager {
|
||||||
pub fn new() -> Self {
|
pub fn enable() {
|
||||||
unsafe {
|
unsafe {
|
||||||
LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr());
|
LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr());
|
||||||
LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr());
|
LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr());
|
||||||
|
|
||||||
TL_MEM_MANAGER_TABLE = MaybeUninit::new(MemManagerTable {
|
TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable {
|
||||||
spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(),
|
spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(),
|
||||||
spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(),
|
spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(),
|
||||||
ble_pool: EVT_POOL.as_ptr().cast(),
|
ble_pool: EVT_POOL.as_ptr().cast(),
|
||||||
@ -26,31 +24,29 @@ impl MemoryManager {
|
|||||||
traces_pool_size: 0,
|
traces_pool_size: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evt_handler(ipcc: &mut Ipcc) {
|
pub fn evt_handler() {
|
||||||
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false);
|
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false);
|
||||||
Self::send_free_buf();
|
Self::send_free_buf();
|
||||||
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) {
|
pub fn evt_drop(evt: *mut EvtPacket) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let list_node = evt.cast();
|
let list_node = evt.cast();
|
||||||
|
|
||||||
LinkedListNode::remove_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node);
|
LinkedListNode::remove_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
let channel_is_busy = ipcc.c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
let channel_is_busy = Ipcc::c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||||
|
|
||||||
// postpone event buffer freeing to IPCC interrupt handler
|
// postpone event buffer freeing to IPCC interrupt handler
|
||||||
if channel_is_busy {
|
if channel_is_busy {
|
||||||
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true);
|
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true);
|
||||||
} else {
|
} else {
|
||||||
Self::send_free_buf();
|
Self::send_free_buf();
|
||||||
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
|
use atomic_polyfill::{compiler_fence, Ordering};
|
||||||
use bit_field::BitField;
|
use bit_field::BitField;
|
||||||
|
use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
|
||||||
|
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::channel::Channel;
|
use embassy_sync::channel::Channel;
|
||||||
|
|
||||||
@ -12,13 +15,16 @@ use self::shci::{shci_ble_init, ShciBleInitCmdParam};
|
|||||||
use self::sys::Sys;
|
use self::sys::Sys;
|
||||||
use self::unsafe_linked_list::LinkedListNode;
|
use self::unsafe_linked_list::LinkedListNode;
|
||||||
use crate::interrupt;
|
use crate::interrupt;
|
||||||
use crate::ipcc::Ipcc;
|
use crate::peripherals::IPCC;
|
||||||
|
pub use crate::tl_mbox::ipcc::Config;
|
||||||
|
use crate::tl_mbox::ipcc::Ipcc;
|
||||||
|
|
||||||
mod ble;
|
mod ble;
|
||||||
mod channels;
|
mod channels;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod consts;
|
mod consts;
|
||||||
mod evt;
|
mod evt;
|
||||||
|
mod ipcc;
|
||||||
mod mm;
|
mod mm;
|
||||||
mod shci;
|
mod shci;
|
||||||
mod sys;
|
mod sys;
|
||||||
@ -58,13 +64,34 @@ pub struct FusInfoTable {
|
|||||||
pub struct ReceiveInterruptHandler {}
|
pub struct ReceiveInterruptHandler {}
|
||||||
|
|
||||||
impl interrupt::Handler<interrupt::IPCC_C1_RX> for ReceiveInterruptHandler {
|
impl interrupt::Handler<interrupt::IPCC_C1_RX> for ReceiveInterruptHandler {
|
||||||
unsafe fn on_interrupt() {}
|
unsafe fn on_interrupt() {
|
||||||
|
// info!("ipcc rx interrupt");
|
||||||
|
|
||||||
|
if Ipcc::is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
|
||||||
|
sys::Sys::evt_handler();
|
||||||
|
} else if Ipcc::is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) {
|
||||||
|
ble::Ble::evt_handler();
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TransmitInterruptHandler {}
|
pub struct TransmitInterruptHandler {}
|
||||||
|
|
||||||
impl interrupt::Handler<interrupt::IPCC_C1_TX> for TransmitInterruptHandler {
|
impl interrupt::Handler<interrupt::IPCC_C1_TX> for TransmitInterruptHandler {
|
||||||
unsafe fn on_interrupt() {}
|
unsafe fn on_interrupt() {
|
||||||
|
// info!("ipcc tx interrupt");
|
||||||
|
|
||||||
|
if Ipcc::is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
|
||||||
|
// TODO: handle this case
|
||||||
|
let _ = sys::Sys::cmd_evt_handler();
|
||||||
|
} else if Ipcc::is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) {
|
||||||
|
mm::MemoryManager::evt_handler();
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Version
|
/// # Version
|
||||||
@ -289,21 +316,24 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251
|
|||||||
// TODO: get a better size, this is a placeholder
|
// TODO: get a better size, this is a placeholder
|
||||||
pub(crate) static TL_CHANNEL: Channel<CriticalSectionRawMutex, EvtBox, 5> = Channel::new();
|
pub(crate) static TL_CHANNEL: Channel<CriticalSectionRawMutex, EvtBox, 5> = Channel::new();
|
||||||
|
|
||||||
pub struct TlMbox {
|
pub struct TlMbox<'d> {
|
||||||
_sys: Sys,
|
_ipcc: PeripheralRef<'d, IPCC>,
|
||||||
_ble: Ble,
|
|
||||||
_mm: MemoryManager,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlMbox {
|
impl<'d> TlMbox<'d> {
|
||||||
/// initializes low-level transport between CPU1 and BLE stack on CPU2
|
/// initializes low-level transport between CPU1 and BLE stack on CPU2
|
||||||
pub fn init(
|
pub fn new(
|
||||||
ipcc: &mut Ipcc,
|
ipcc: impl Peripheral<P = IPCC> + 'd,
|
||||||
_irqs: impl interrupt::Binding<interrupt::IPCC_C1_RX, ReceiveInterruptHandler>
|
_irqs: impl interrupt::Binding<interrupt::IPCC_C1_RX, ReceiveInterruptHandler>
|
||||||
+ interrupt::Binding<interrupt::IPCC_C1_TX, TransmitInterruptHandler>,
|
+ interrupt::Binding<interrupt::IPCC_C1_TX, TransmitInterruptHandler>,
|
||||||
) -> TlMbox {
|
config: Config,
|
||||||
|
) -> Self {
|
||||||
|
into_ref!(ipcc);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
TL_REF_TABLE = MaybeUninit::new(RefTable {
|
compiler_fence(Ordering::AcqRel);
|
||||||
|
|
||||||
|
TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable {
|
||||||
device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(),
|
device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(),
|
||||||
ble_table: TL_BLE_TABLE.as_ptr(),
|
ble_table: TL_BLE_TABLE.as_ptr(),
|
||||||
thread_table: TL_THREAD_TABLE.as_ptr(),
|
thread_table: TL_THREAD_TABLE.as_ptr(),
|
||||||
@ -316,6 +346,10 @@ impl TlMbox {
|
|||||||
ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(),
|
ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// info!("TL_REF_TABLE addr: {:x}", TL_REF_TABLE.as_ptr() as usize);
|
||||||
|
|
||||||
|
compiler_fence(Ordering::AcqRel);
|
||||||
|
|
||||||
TL_SYS_TABLE = MaybeUninit::zeroed();
|
TL_SYS_TABLE = MaybeUninit::zeroed();
|
||||||
TL_DEVICE_INFO_TABLE = MaybeUninit::zeroed();
|
TL_DEVICE_INFO_TABLE = MaybeUninit::zeroed();
|
||||||
TL_BLE_TABLE = MaybeUninit::zeroed();
|
TL_BLE_TABLE = MaybeUninit::zeroed();
|
||||||
@ -334,33 +368,24 @@ impl TlMbox {
|
|||||||
CS_BUFFER = MaybeUninit::zeroed();
|
CS_BUFFER = MaybeUninit::zeroed();
|
||||||
BLE_CMD_BUFFER = MaybeUninit::zeroed();
|
BLE_CMD_BUFFER = MaybeUninit::zeroed();
|
||||||
HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed();
|
HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed();
|
||||||
|
|
||||||
|
compiler_fence(Ordering::AcqRel);
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcc.init();
|
Ipcc::enable(config);
|
||||||
|
|
||||||
let _sys = Sys::new(ipcc);
|
Sys::enable();
|
||||||
let _ble = Ble::new(ipcc);
|
Ble::enable();
|
||||||
let _mm = MemoryManager::new();
|
MemoryManager::enable();
|
||||||
|
|
||||||
// rx_irq.disable();
|
// enable interrupts
|
||||||
// tx_irq.disable();
|
unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend();
|
||||||
//
|
unsafe { crate::interrupt::IPCC_C1_TX::steal() }.unpend();
|
||||||
// rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
|
|
||||||
// tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
|
|
||||||
//
|
|
||||||
// rx_irq.set_handler(|ipcc| {
|
|
||||||
// let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
|
|
||||||
// Self::interrupt_ipcc_rx_handler(ipcc);
|
|
||||||
// });
|
|
||||||
// tx_irq.set_handler(|ipcc| {
|
|
||||||
// let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
|
|
||||||
// Self::interrupt_ipcc_tx_handler(ipcc);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// rx_irq.enable();
|
|
||||||
// tx_irq.enable();
|
|
||||||
|
|
||||||
TlMbox { _sys, _ble, _mm }
|
unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable();
|
||||||
|
unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable();
|
||||||
|
|
||||||
|
Self { _ipcc: ipcc }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wireless_fw_info(&self) -> Option<WirelessFwInfoTable> {
|
pub fn wireless_fw_info(&self) -> Option<WirelessFwInfoTable> {
|
||||||
@ -374,42 +399,19 @@ impl TlMbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
|
pub fn shci_ble_init(&self, param: ShciBleInitCmdParam) {
|
||||||
shci_ble_init(ipcc, param);
|
shci_ble_init(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
|
pub fn send_ble_cmd(&self, buf: &[u8]) {
|
||||||
ble::Ble::send_cmd(ipcc, buf);
|
ble::Ble::send_cmd(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
|
// pub fn send_sys_cmd(&self, buf: &[u8]) {
|
||||||
// sys::Sys::send_cmd(ipcc, buf);
|
// sys::Sys::send_cmd(buf);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub async fn read(&self) -> EvtBox {
|
pub async fn read(&self) -> EvtBox {
|
||||||
TL_CHANNEL.recv().await
|
TL_CHANNEL.recv().await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) {
|
|
||||||
if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
|
|
||||||
sys::Sys::evt_handler(ipcc);
|
|
||||||
} else if ipcc.is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) {
|
|
||||||
ble::Ble::evt_handler(ipcc);
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) {
|
|
||||||
if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
|
|
||||||
// TODO: handle this case
|
|
||||||
let _ = sys::Sys::cmd_evt_handler(ipcc);
|
|
||||||
} else if ipcc.is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) {
|
|
||||||
mm::MemoryManager::evt_handler(ipcc);
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use super::cmd::CmdPacket;
|
use super::cmd::CmdPacket;
|
||||||
use super::consts::TlPacketType;
|
use super::consts::TlPacketType;
|
||||||
use super::{channels, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE, TL_SYS_TABLE};
|
use super::{channels, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE, TL_SYS_TABLE};
|
||||||
use crate::ipcc::Ipcc;
|
use crate::tl_mbox::ipcc::Ipcc;
|
||||||
|
|
||||||
const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66;
|
const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66;
|
||||||
pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE;
|
pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE;
|
||||||
@ -76,7 +76,7 @@ pub struct ShciBleInitCmdPacket {
|
|||||||
param: ShciBleInitCmdParam,
|
param: ShciBleInitCmdParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
|
pub fn shci_ble_init(param: ShciBleInitCmdParam) {
|
||||||
let mut packet = ShciBleInitCmdPacket {
|
let mut packet = ShciBleInitCmdPacket {
|
||||||
header: ShciHeader::default(),
|
header: ShciHeader::default(),
|
||||||
param,
|
param,
|
||||||
@ -95,7 +95,7 @@ pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
|
|||||||
|
|
||||||
cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8;
|
cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8;
|
||||||
|
|
||||||
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
|
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
|
||||||
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
|
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use core::mem::MaybeUninit;
|
|
||||||
|
|
||||||
use embassy_futures::block_on;
|
use embassy_futures::block_on;
|
||||||
|
|
||||||
use super::cmd::{CmdPacket, CmdSerial};
|
use super::cmd::{CmdPacket, CmdSerial};
|
||||||
@ -7,27 +5,25 @@ use super::consts::TlPacketType;
|
|||||||
use super::evt::{CcEvt, EvtBox, EvtSerial};
|
use super::evt::{CcEvt, EvtBox, EvtSerial};
|
||||||
use super::unsafe_linked_list::LinkedListNode;
|
use super::unsafe_linked_list::LinkedListNode;
|
||||||
use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE};
|
use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE};
|
||||||
use crate::ipcc::Ipcc;
|
use crate::tl_mbox::ipcc::Ipcc;
|
||||||
|
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
|
|
||||||
impl Sys {
|
impl Sys {
|
||||||
pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
|
pub fn enable() {
|
||||||
unsafe {
|
unsafe {
|
||||||
LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr());
|
LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr());
|
||||||
|
|
||||||
TL_SYS_TABLE = MaybeUninit::new(SysTable {
|
TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable {
|
||||||
pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(),
|
pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(),
|
||||||
sys_queue: SYSTEM_EVT_QUEUE.as_ptr(),
|
sys_queue: SYSTEM_EVT_QUEUE.as_ptr(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcc.c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true);
|
Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true);
|
||||||
|
|
||||||
Sys
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
|
pub fn evt_handler() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut node_ptr = core::ptr::null_mut();
|
let mut node_ptr = core::ptr::null_mut();
|
||||||
let node_ptr_ptr: *mut _ = &mut node_ptr;
|
let node_ptr_ptr: *mut _ = &mut node_ptr;
|
||||||
@ -43,11 +39,11 @@ impl Sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL);
|
Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) -> CcEvt {
|
pub fn cmd_evt_handler() -> CcEvt {
|
||||||
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false);
|
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false);
|
||||||
|
|
||||||
// ST's command response data structure is really convoluted.
|
// ST's command response data structure is really convoluted.
|
||||||
//
|
//
|
||||||
@ -68,11 +64,11 @@ impl Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
|
pub fn send_cmd(buf: &[u8]) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// TODO: check this
|
// TODO: check this
|
||||||
let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
|
let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
|
||||||
let cmd_serial: *mut CmdSerial = &mut (*cmd_buffer).cmd_serial;
|
let cmd_serial: *mut CmdSerial = &mut cmd_buffer.cmd_serial;
|
||||||
let cmd_serial_buf = cmd_serial.cast();
|
let cmd_serial_buf = cmd_serial.cast();
|
||||||
|
|
||||||
core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len());
|
core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len());
|
||||||
@ -80,8 +76,8 @@ impl Sys {
|
|||||||
let cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
|
let cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
|
||||||
cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8;
|
cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8;
|
||||||
|
|
||||||
ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
|
Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
|
||||||
ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
|
Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
/*
|
MEMORY
|
||||||
Memory size for STM32WB55xG with 512K FLASH
|
|
||||||
*/
|
|
||||||
|
|
||||||
MEMORY
|
|
||||||
{
|
{
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
|
|
||||||
RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8
|
|
||||||
RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K
|
RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Place stack at the end of SRAM1 */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scatter the mailbox interface memory sections in shared memory
|
* Scatter the mailbox interface memory sections in shared memory
|
||||||
*/
|
*/
|
||||||
SECTIONS {
|
SECTIONS
|
||||||
|
{
|
||||||
TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED
|
TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED
|
||||||
|
|
||||||
MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED
|
MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED
|
@ -169,4 +169,4 @@ wasm-timer = { version = "0.2.5", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = "0.9"
|
serial_test = "0.9"
|
||||||
critical-section = { version = "1.1", features = ["std"] }
|
critical-section = { version = "1.1", features = ["std"] }
|
||||||
|
embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly"] }
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||||
//! todo!()
|
//! todo!()
|
||||||
//! }
|
//! }
|
||||||
//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
|
//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||||
//! todo!()
|
//! todo!()
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
|
@ -183,7 +183,6 @@ mod tests {
|
|||||||
|
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
use super::InnerQueue;
|
|
||||||
use crate::driver::{AlarmHandle, Driver};
|
use crate::driver::{AlarmHandle, Driver};
|
||||||
use crate::queue_generic::QUEUE;
|
use crate::queue_generic::QUEUE;
|
||||||
use crate::Instant;
|
use crate::Instant;
|
||||||
@ -317,14 +316,18 @@ mod tests {
|
|||||||
|
|
||||||
fn setup() {
|
fn setup() {
|
||||||
DRIVER.reset();
|
DRIVER.reset();
|
||||||
|
critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None);
|
||||||
QUEUE.inner.lock(|inner| {
|
|
||||||
*inner.borrow_mut() = InnerQueue::new();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_len() -> usize {
|
fn queue_len() -> usize {
|
||||||
QUEUE.inner.lock(|inner| inner.borrow().queue.iter().count())
|
critical_section::with(|cs| {
|
||||||
|
QUEUE
|
||||||
|
.inner
|
||||||
|
.borrow_ref(cs)
|
||||||
|
.as_ref()
|
||||||
|
.map(|inner| inner.queue.iter().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -109,7 +109,6 @@ impl Future for Timer {
|
|||||||
/// # #![feature(type_alias_impl_trait)]
|
/// # #![feature(type_alias_impl_trait)]
|
||||||
/// #
|
/// #
|
||||||
/// use embassy_time::{Duration, Ticker};
|
/// use embassy_time::{Duration, Ticker};
|
||||||
/// use futures::StreamExt;
|
|
||||||
/// # fn foo(){}
|
/// # fn foo(){}
|
||||||
///
|
///
|
||||||
/// #[embassy_executor::task]
|
/// #[embassy_executor::task]
|
||||||
|
@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
|
embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
|
||||||
embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
|
embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
|
||||||
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
|
||||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "exti"] }
|
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] }
|
||||||
|
|
||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
defmt-rtt = "0.4"
|
defmt-rtt = "0.4"
|
||||||
|
@ -1,35 +1,11 @@
|
|||||||
//! This build script copies the `memory.x` file from the crate root into
|
use std::error::Error;
|
||||||
//! a directory where the linker can always find it at build time.
|
|
||||||
//! For many projects this is optional, as the linker always searches the
|
|
||||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
|
||||||
//! are using a workspace or have a more complicated build setup, this
|
|
||||||
//! build script becomes required. Additionally, by requesting that
|
|
||||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
|
||||||
//! updating `memory.x` ensures a rebuild of the application with the
|
|
||||||
//! new memory settings.
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Put `memory.x` in our output directory and ensure it's
|
|
||||||
// on the linker search path.
|
|
||||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
|
||||||
File::create(out.join("memory.x"))
|
|
||||||
.unwrap()
|
|
||||||
.write_all(include_bytes!("memory.x"))
|
|
||||||
.unwrap();
|
|
||||||
println!("cargo:rustc-link-search={}", out.display());
|
|
||||||
|
|
||||||
// By default, Cargo will re-run a build script whenever
|
|
||||||
// any file in the project changes. By specifying `memory.x`
|
|
||||||
// here, we ensure the build script is only re-run when
|
|
||||||
// `memory.x` is changed.
|
|
||||||
println!("cargo:rerun-if-changed=memory.x");
|
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
println!("cargo:rustc-link-arg-bins=--nmagic");
|
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||||
|
println!("cargo:rerun-if-changed=link.x");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x");
|
||||||
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
The size of this file must be exactly the same as in other memory_xx.x files.
|
|
||||||
Memory size for STM32WB55xC with 256K FLASH
|
|
||||||
*/
|
|
||||||
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K
|
|
||||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
|
|
||||||
RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Memory size for STM32WB55xG with 512K FLASH
|
|
||||||
|
|
||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
|
|
||||||
RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8
|
|
||||||
RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Place stack at the end of SRAM1 */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Scatter the mailbox interface memory sections in shared memory
|
|
||||||
*/
|
|
||||||
SECTIONS {
|
|
||||||
TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED
|
|
||||||
|
|
||||||
MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED
|
|
||||||
MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED
|
|
||||||
}
|
|
@ -4,8 +4,7 @@
|
|||||||
|
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_stm32::ipcc::{Config, Ipcc};
|
use embassy_stm32::tl_mbox::{Config, TlMbox};
|
||||||
use embassy_stm32::tl_mbox::TlMbox;
|
|
||||||
use embassy_stm32::{bind_interrupts, tl_mbox};
|
use embassy_stm32::{bind_interrupts, tl_mbox};
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@ -45,14 +44,12 @@ async fn main(_spawner: Spawner) {
|
|||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let mut ipcc = Ipcc::new(p.IPCC, config);
|
let mbox = TlMbox::new(p.IPCC, Irqs, config);
|
||||||
|
|
||||||
let mbox = TlMbox::init(&mut ipcc, Irqs);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let wireless_fw_info = mbox.wireless_fw_info();
|
let wireless_fw_info = mbox.wireless_fw_info();
|
||||||
match wireless_fw_info {
|
match wireless_fw_info {
|
||||||
None => error!("not yet initialized"),
|
None => info!("not yet initialized"),
|
||||||
Some(fw_info) => {
|
Some(fw_info) => {
|
||||||
let version_major = fw_info.version_major();
|
let version_major = fw_info.version_major();
|
||||||
let version_minor = fw_info.version_minor();
|
let version_minor = fw_info.version_minor();
|
||||||
@ -70,6 +67,9 @@ async fn main(_spawner: Spawner) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer::after(Duration::from_millis(500)).await;
|
Timer::after(Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Test OK");
|
||||||
|
cortex_m::asm::bkpt();
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
|
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_stm32::ipcc::{Config, Ipcc};
|
use embassy_stm32::tl_mbox::{Config, TlMbox};
|
||||||
use embassy_stm32::tl_mbox::TlMbox;
|
|
||||||
use embassy_stm32::{bind_interrupts, tl_mbox};
|
use embassy_stm32::{bind_interrupts, tl_mbox};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -44,12 +43,7 @@ async fn main(_spawner: Spawner) {
|
|||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let mut ipcc = Ipcc::new(p.IPCC, config);
|
let mbox = TlMbox::new(p.IPCC, Irqs, config);
|
||||||
|
|
||||||
let mbox = TlMbox::init(&mut ipcc, Irqs);
|
|
||||||
|
|
||||||
// initialize ble stack, does not return a response
|
|
||||||
mbox.shci_ble_init(&mut ipcc, Default::default());
|
|
||||||
|
|
||||||
info!("waiting for coprocessor to boot");
|
info!("waiting for coprocessor to boot");
|
||||||
let event_box = mbox.read().await;
|
let event_box = mbox.read().await;
|
||||||
@ -74,10 +68,11 @@ async fn main(_spawner: Spawner) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
mbox.shci_ble_init(&mut ipcc, Default::default());
|
// initialize ble stack, does not return a response
|
||||||
|
mbox.shci_ble_init(Default::default());
|
||||||
|
|
||||||
info!("resetting BLE");
|
info!("resetting BLE");
|
||||||
mbox.send_ble_cmd(&mut ipcc, &[0x01, 0x03, 0x0c, 0x00, 0x00]);
|
mbox.send_ble_cmd(&[0x01, 0x03, 0x0c, 0x00, 0x00]);
|
||||||
|
|
||||||
let event_box = mbox.read().await;
|
let event_box = mbox.read().await;
|
||||||
|
|
||||||
@ -92,8 +87,12 @@ async fn main(_spawner: Spawner) {
|
|||||||
|
|
||||||
info!(
|
info!(
|
||||||
"==> kind: {:#04x}, code: {:#04x}, payload_length: {}, payload: {:#04x}",
|
"==> kind: {:#04x}, code: {:#04x}, payload_length: {}, payload: {:#04x}",
|
||||||
kind, code, payload_len, payload
|
kind,
|
||||||
|
code,
|
||||||
|
payload_len,
|
||||||
|
payload[3..]
|
||||||
);
|
);
|
||||||
|
|
||||||
loop {}
|
info!("Test OK");
|
||||||
|
cortex_m::asm::bkpt();
|
||||||
}
|
}
|
||||||
|
@ -17,4 +17,4 @@ rustflags = [
|
|||||||
target = "thumbv7m-none-eabi"
|
target = "thumbv7m-none-eabi"
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
DEFMT_LOG = "trace"
|
DEFMT_LOG = "trace"
|
@ -7,12 +7,12 @@ autobins = false
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill
|
stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill
|
||||||
stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono", "not-gpdma"] # Nucleo
|
stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "not-gpdma"] # Nucleo "sdmmc"
|
||||||
stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo
|
stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo
|
||||||
stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo
|
stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo
|
||||||
stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo
|
stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo
|
||||||
stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
|
stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
|
||||||
stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma"] # Nucleo
|
stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma", "ble"] # Nucleo
|
||||||
stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo
|
stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo
|
||||||
stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board
|
stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board
|
||||||
|
|
||||||
@ -45,8 +45,8 @@ chrono = { version = "^0.4", default-features = false, optional = true}
|
|||||||
# BEGIN TESTS
|
# BEGIN TESTS
|
||||||
# Generated by gen_test.py. DO NOT EDIT.
|
# Generated by gen_test.py. DO NOT EDIT.
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ble"
|
name = "tl_mbox"
|
||||||
path = "src/bin/ble.rs"
|
path = "src/bin/tl_mbox.rs"
|
||||||
required-features = [ "ble",]
|
required-features = [ "ble",]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
@ -9,17 +9,22 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
println!("cargo:rustc-link-arg-bins=--nmagic");
|
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||||
|
|
||||||
// too little RAM to run from RAM.
|
// too little RAM to run from RAM.
|
||||||
if cfg!(any(feature = "stm32f103c8", feature = "stm32c031c6")) {
|
if cfg!(any(
|
||||||
|
feature = "stm32f103c8",
|
||||||
|
feature = "stm32c031c6",
|
||||||
|
feature = "stm32wb55rg"
|
||||||
|
)) {
|
||||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||||
println!("cargo:rerun-if-changed=link.x");
|
println!("cargo:rerun-if-changed=link.x");
|
||||||
} else if cfg!(feature = "stm32wb55rg") {
|
|
||||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
|
||||||
fs::write(out.join("memory.x"), include_bytes!("memory_ble.x")).unwrap();
|
|
||||||
} else {
|
} else {
|
||||||
println!("cargo:rustc-link-arg-bins=-Tlink_ram.x");
|
println!("cargo:rustc-link-arg-bins=-Tlink_ram.x");
|
||||||
println!("cargo:rerun-if-changed=link_ram.x");
|
println!("cargo:rerun-if-changed=link_ram.x");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg!(feature = "stm32wb55rg") {
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x");
|
||||||
|
}
|
||||||
|
|
||||||
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -7,24 +7,23 @@
|
|||||||
#[path = "../example_common.rs"]
|
#[path = "../example_common.rs"]
|
||||||
mod example_common;
|
mod example_common;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_stm32::interrupt;
|
use embassy_stm32::tl_mbox::{Config, TlMbox};
|
||||||
use embassy_stm32::ipcc::{Config, Ipcc};
|
use embassy_stm32::{bind_interrupts, tl_mbox};
|
||||||
use embassy_stm32::tl_mbox::TlMbox;
|
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use example_common::*;
|
use example_common::*;
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs{
|
||||||
|
IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler;
|
||||||
|
IPCC_C1_TX => tl_mbox::TransmitInterruptHandler;
|
||||||
|
});
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_stm32::init(config());
|
let p = embassy_stm32::init(config());
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let mut ipcc = Ipcc::new(p.IPCC, config);
|
let mbox = TlMbox::new(p.IPCC, Irqs, config);
|
||||||
|
|
||||||
let rx_irq = interrupt::take!(IPCC_C1_RX);
|
|
||||||
let tx_irq = interrupt::take!(IPCC_C1_TX);
|
|
||||||
|
|
||||||
let mbox = TlMbox::init(&mut ipcc, rx_irq, tx_irq);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let wireless_fw_info = mbox.wireless_fw_info();
|
let wireless_fw_info = mbox.wireless_fw_info();
|
Loading…
Reference in New Issue
Block a user