This commit is contained in:
xoviat 2023-05-29 15:07:21 -05:00
commit 68441a74c2
41 changed files with 822 additions and 927 deletions

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

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

18
.github/ci/build.sh vendored Executable file
View 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
View 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

View File

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

View File

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

1
ci.sh
View File

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

View File

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

View File

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

View File

@ -0,0 +1,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);
}
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,4 +17,4 @@ rustflags = [
target = "thumbv7m-none-eabi" target = "thumbv7m-none-eabi"
[env] [env]
DEFMT_LOG = "trace" DEFMT_LOG = "trace"

View File

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

View File

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

View File

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