diff --git a/.github/ci/build-stable.sh b/.github/ci/build-stable.sh new file mode 100755 index 00000000..0dadd610 --- /dev/null +++ b/.github/ci/build-stable.sh @@ -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 diff --git a/.github/ci/build.sh b/.github/ci/build.sh new file mode 100755 index 00000000..1c3a7f3b --- /dev/null +++ b/.github/ci/build.sh @@ -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 diff --git a/.github/ci/test.sh b/.github/ci/test.sh new file mode 100755 index 00000000..a7140cfd --- /dev/null +++ b/.github/ci/test.sh @@ -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 diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index b4e225e6..a69a4971 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -2,7 +2,7 @@ name: Docs on: push: - branches: [master] + branches: [main] env: BUILDER_THREADS: '1' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 0cbca31b..00000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -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 diff --git a/ci.sh b/ci.sh index 6d906f5f..11c56932 100755 --- a/ci.sh +++ b/ci.sh @@ -2,7 +2,6 @@ set -euo pipefail -export CARGO_TARGET_DIR=$PWD/target_ci export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace diff --git a/ci_stable.sh b/ci_stable.sh index 55a2f89a..a67087ff 100755 --- a/ci_stable.sh +++ b/ci_stable.sh @@ -2,12 +2,9 @@ set -euo pipefail -export CARGO_TARGET_DIR=$PWD/target_ci_stable export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace -sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml - cargo batch \ --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs deleted file mode 100644 index aeea206f..00000000 --- a/embassy-boot/boot/src/firmware_updater.rs +++ /dev/null @@ -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 From 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( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - 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( - &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) -> 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( - &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( - &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( - &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( - &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( - &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( - &mut self, - dfu_flash: &mut F, - ) -> Result { - 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( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - 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( - &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) -> 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( - &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( - &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( - &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( - &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( - &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(&mut self, flash: &mut F) -> Result { - 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); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs new file mode 100644 index 00000000..bdd03bff --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -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( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result { + 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( + &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) -> 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( + &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( + &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( + &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( + &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( + &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( + &mut self, + dfu_flash: &mut F, + ) -> Result { + 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); + } +} diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs new file mode 100644 index 00000000..50caaf08 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -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( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result { + 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( + &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) -> 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( + &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( + &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( + &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( + &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( + &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(&mut self, flash: &mut F) -> Result { + self.dfu.wipe_blocking(flash)?; + + Ok(self.dfu) + } +} diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs new file mode 100644 index 00000000..e09f5eb6 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -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 From 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) + } +} diff --git a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs index 1fe520e6..af73df05 100644 --- a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs +++ b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs @@ -2,13 +2,12 @@ //! //! # Example (nrf52) //! -//! ```rust +//! ```rust,ignore //! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; //! //! static I2C_BUS: StaticCell>>> = StaticCell::new(); -//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default()); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default()); //! let i2c_bus = NoopMutex::new(RefCell::new(i2c)); //! let i2c_bus = I2C_BUS.init(i2c_bus); //! diff --git a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs index 7982ffb6..22e013be 100644 --- a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs +++ b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs @@ -2,13 +2,12 @@ //! //! # Example (nrf52) //! -//! ```rust +//! ```rust,ignore //! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; //! //! static SPI_BUS: StaticCell>>> = StaticCell::new(); -//! let irq = interrupt::take!(SPIM3); -//! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default()); +//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default()); //! let spi_bus = NoopMutex::new(RefCell::new(spi)); //! let spi_bus = SPI_BUS.init(spi_bus); //! diff --git a/embassy-hal-common/src/atomic_ring_buffer.rs b/embassy-hal-common/src/atomic_ring_buffer.rs index 0eb39cb3..ea84925c 100644 --- a/embassy-hal-common/src/atomic_ring_buffer.rs +++ b/embassy-hal-common/src/atomic_ring_buffer.rs @@ -458,8 +458,6 @@ mod tests { #[test] fn push_slices() { - init(); - let mut b = [0; 4]; let rb = RingBuffer::new(); unsafe { diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index bc2c8a3c..c82c238c 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -67,7 +67,7 @@ fn compare_n(n: usize) -> u32 { 1 << (n + 16) } -#[cfg(tests)] +#[cfg(test)] mod test { use super::*; diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index b766f073..730c78f5 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -912,6 +912,16 @@ fn main() { println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]); } + // ======== + // stm32wb tl_mbox link sections + + if chip_name.starts_with("stm32wb") { + let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string(); + fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap(); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rerun-if-changed=tl_mbox.x.in"); + } + // ======= // Features for targeting groups of chips diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index 6c191290..c6cdc574 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -163,7 +163,7 @@ pub(super) fn get_sector(address: u32, regions: &[&FlashRegion]) -> FlashSector bank_offset = 0; } - if address < region.end() { + if address >= region.base && address < region.end() { let index_in_region = (address - region.base) / region.erase_size; return FlashSector { bank: region.bank, diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 1258a65f..6533509e 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -41,8 +41,6 @@ pub mod crc; pub mod flash; #[cfg(all(spi_v1, rcc_f4))] pub mod i2s; -#[cfg(stm32wb)] -pub mod ipcc; pub mod pwm; #[cfg(quadspi)] pub mod qspi; diff --git a/embassy-stm32/src/pwm/complementary_pwm.rs b/embassy-stm32/src/pwm/complementary_pwm.rs index 3f8b43cf..cfb79947 100644 --- a/embassy-stm32/src/pwm/complementary_pwm.rs +++ b/embassy-stm32/src/pwm/complementary_pwm.rs @@ -209,39 +209,39 @@ mod tests { #[test] fn test_compute_dead_time_value() { - struct test_run { + struct TestRun { value: u16, ckd: Ckd, bits: u8, } let fn_results = [ - test_run { + TestRun { value: 1, ckd: Ckd::DIV1, bits: 1, }, - test_run { + TestRun { value: 125, ckd: Ckd::DIV1, bits: 125, }, - test_run { + TestRun { value: 245, ckd: Ckd::DIV1, bits: 64 + 245 / 2, }, - test_run { + TestRun { value: 255, ckd: Ckd::DIV2, bits: 127, }, - test_run { + TestRun { value: 400, ckd: Ckd::DIV1, bits: 32 + (400u16 / 8) as u8, }, - test_run { + TestRun { value: 600, ckd: Ckd::DIV4, bits: 64 + (600u16 / 8) as u8, diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs index d8bf14d4..06237799 100644 --- a/embassy-stm32/src/tl_mbox/ble.rs +++ b/embassy-stm32/src/tl_mbox/ble.rs @@ -1,5 +1,3 @@ -use core::mem::MaybeUninit; - use embassy_futures::block_on; use super::cmd::CmdSerial; @@ -10,17 +8,17 @@ use super::{ channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE, TL_CHANNEL, TL_REF_TABLE, }; -use crate::ipcc::Ipcc; use crate::tl_mbox::cmd::CmdPacket; +use crate::tl_mbox::ipcc::Ipcc; pub struct Ble; impl Ble { - pub(crate) fn new(ipcc: &mut Ipcc) -> Self { + pub fn enable() { unsafe { LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); - TL_BLE_TABLE = MaybeUninit::new(BleTable { + TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable { pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(), pcs_buffer: CS_BUFFER.as_mut_ptr().cast(), pevt_queue: EVT_QUEUE.as_ptr().cast(), @@ -28,12 +26,10 @@ impl Ble { }); } - ipcc.c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true); - - Ble + Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true); } - pub(crate) fn evt_handler(ipcc: &mut Ipcc) { + pub fn evt_handler() { unsafe { let mut node_ptr = core::ptr::null_mut(); let node_ptr_ptr: *mut _ = &mut node_ptr; @@ -48,10 +44,10 @@ impl Ble { } } - ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL); + Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL); } - pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { + pub fn send_cmd(buf: &[u8]) { unsafe { let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer; let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial; @@ -63,6 +59,6 @@ impl Ble { cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8; } - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL); } } diff --git a/embassy-stm32/src/tl_mbox/channels.rs b/embassy-stm32/src/tl_mbox/channels.rs index aaa6ce17..25a065ba 100644 --- a/embassy-stm32/src/tl_mbox/channels.rs +++ b/embassy-stm32/src/tl_mbox/channels.rs @@ -50,7 +50,7 @@ //! pub mod cpu1 { - use crate::ipcc::IpccChannel; + use crate::tl_mbox::ipcc::IpccChannel; // Not used currently but reserved pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1; @@ -75,7 +75,7 @@ pub mod cpu1 { } pub mod cpu2 { - use crate::ipcc::IpccChannel; + use crate::tl_mbox::ipcc::IpccChannel; pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1; pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2; diff --git a/embassy-stm32/src/tl_mbox/evt.rs b/embassy-stm32/src/tl_mbox/evt.rs index 770133f2..47a8b72f 100644 --- a/embassy-stm32/src/tl_mbox/evt.rs +++ b/embassy-stm32/src/tl_mbox/evt.rs @@ -3,7 +3,7 @@ use core::mem::MaybeUninit; use super::cmd::{AclDataPacket, AclDataSerial}; use super::consts::TlPacketType; use super::{PacketHeader, TL_EVT_HEADER_SIZE}; -use crate::tl_mbox::mm; +use crate::tl_mbox::mm::MemoryManager; /// the payload of [`Evt`] for a command status event #[derive(Copy, Clone)] @@ -131,9 +131,6 @@ impl EvtBox { impl Drop for EvtBox { fn drop(&mut self) { - use crate::ipcc::Ipcc; - - let mut ipcc = Ipcc::new_inner(unsafe { crate::Peripherals::steal() }.IPCC); - mm::MemoryManager::evt_drop(self.ptr, &mut ipcc); + MemoryManager::evt_drop(self.ptr); } } diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/tl_mbox/ipcc.rs similarity index 65% rename from embassy-stm32/src/ipcc.rs rename to embassy-stm32/src/tl_mbox/ipcc.rs index 2b9caf8e..d1ac731e 100644 --- a/embassy-stm32/src/ipcc.rs +++ b/embassy-stm32/src/tl_mbox/ipcc.rs @@ -1,6 +1,4 @@ -use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; - -use crate::ipcc::sealed::Instance; +use self::sealed::Instance; use crate::peripherals::IPCC; use crate::rcc::sealed::RccPeripheral; @@ -22,29 +20,17 @@ pub enum IpccChannel { Channel6 = 5, } -pub(crate) mod sealed { +pub mod sealed { pub trait Instance: crate::rcc::RccPeripheral { fn regs() -> crate::pac::ipcc::Ipcc; fn set_cpu2(enabled: bool); } } -pub struct Ipcc<'d> { - _peri: PeripheralRef<'d, IPCC>, -} +pub struct Ipcc; -impl<'d> Ipcc<'d> { - pub fn new(peri: impl Peripheral

+ 'd, _config: Config) -> Self { - Self::new_inner(peri) - } - - pub(crate) fn new_inner(peri: impl Peripheral

+ 'd) -> Self { - into_ref!(peri); - - Self { _peri: peri } - } - - pub fn init(&mut self) { +impl Ipcc { + pub fn enable(_config: Config) { IPCC::enable(); IPCC::reset(); IPCC::set_cpu2(true); @@ -61,56 +47,60 @@ impl<'d> Ipcc<'d> { } } - pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) { + pub fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) } } - pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool { + pub fn c1_get_rx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(0).mr().read().chom(channel as usize) } } - pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) { + #[allow(dead_code)] + pub fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) } } - pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool { + #[allow(dead_code)] + pub fn c2_get_rx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(1).mr().read().chom(channel as usize) } } - pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) { + pub fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) } } - pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool { + pub fn c1_get_tx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) } } - pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) { + #[allow(dead_code)] + pub fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) } } - pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool { + #[allow(dead_code)] + pub fn c2_get_tx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -118,53 +108,51 @@ impl<'d> Ipcc<'d> { } /// clears IPCC receive channel status for CPU1 - pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) { + pub fn c1_clear_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) } } + #[allow(dead_code)] /// clears IPCC receive channel status for CPU2 - pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) { + pub fn c2_clear_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) } } - pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) { + pub fn c1_set_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) } } - pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) { + #[allow(dead_code)] + pub fn c2_set_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) } } - pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool { + pub fn c1_is_active_flag(channel: IpccChannel) -> bool { let regs = IPCC::regs(); unsafe { regs.cpu(0).sr().read().chf(channel as usize) } } - pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool { + pub fn c2_is_active_flag(channel: IpccChannel) -> bool { let regs = IPCC::regs(); unsafe { regs.cpu(1).sr().read().chf(channel as usize) } } - pub fn is_tx_pending(&self, channel: IpccChannel) -> bool { - !self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel) + pub fn is_tx_pending(channel: IpccChannel) -> bool { + !Self::c1_is_active_flag(channel) && Self::c1_get_tx_channel(channel) } - pub fn is_rx_pending(&self, channel: IpccChannel) -> bool { - self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel) - } - - pub fn as_mut_ptr(&self) -> *mut Self { - unsafe { &mut core::ptr::read(self) as *mut _ } + pub fn is_rx_pending(channel: IpccChannel) -> bool { + Self::c2_is_active_flag(channel) && Self::c1_get_rx_channel(channel) } } diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs index f99ffa39..e28a6aa0 100644 --- a/embassy-stm32/src/tl_mbox/mm.rs +++ b/embassy-stm32/src/tl_mbox/mm.rs @@ -1,22 +1,20 @@ -use core::mem::MaybeUninit; - use super::evt::EvtPacket; use super::unsafe_linked_list::LinkedListNode; use super::{ channels, MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, TL_REF_TABLE, }; -use crate::ipcc::Ipcc; +use crate::tl_mbox::ipcc::Ipcc; pub struct MemoryManager; impl MemoryManager { - pub fn new() -> Self { + pub fn enable() { unsafe { LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr()); LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()); - TL_MEM_MANAGER_TABLE = MaybeUninit::new(MemManagerTable { + TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable { spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(), spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(), ble_pool: EVT_POOL.as_ptr().cast(), @@ -26,31 +24,29 @@ impl MemoryManager { traces_pool_size: 0, }); } - - MemoryManager } - pub fn evt_handler(ipcc: &mut Ipcc) { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false); + pub fn evt_handler() { + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false); Self::send_free_buf(); - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); } - pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) { + pub fn evt_drop(evt: *mut EvtPacket) { unsafe { let list_node = evt.cast(); LinkedListNode::remove_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node); } - let channel_is_busy = ipcc.c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + let channel_is_busy = Ipcc::c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); // postpone event buffer freeing to IPCC interrupt handler if channel_is_busy { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true); + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true); } else { Self::send_free_buf(); - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); } } diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs index dc6104cc..616f7dc5 100644 --- a/embassy-stm32/src/tl_mbox/mod.rs +++ b/embassy-stm32/src/tl_mbox/mod.rs @@ -1,6 +1,9 @@ use core::mem::MaybeUninit; +use atomic_polyfill::{compiler_fence, Ordering}; use bit_field::BitField; +use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; @@ -12,13 +15,16 @@ use self::shci::{shci_ble_init, ShciBleInitCmdParam}; use self::sys::Sys; use self::unsafe_linked_list::LinkedListNode; use crate::interrupt; -use crate::ipcc::Ipcc; +use crate::peripherals::IPCC; +pub use crate::tl_mbox::ipcc::Config; +use crate::tl_mbox::ipcc::Ipcc; mod ble; mod channels; mod cmd; mod consts; mod evt; +mod ipcc; mod mm; mod shci; mod sys; @@ -58,13 +64,34 @@ pub struct FusInfoTable { pub struct ReceiveInterruptHandler {} impl interrupt::Handler for ReceiveInterruptHandler { - unsafe fn on_interrupt() {} + unsafe fn on_interrupt() { + // info!("ipcc rx interrupt"); + + if Ipcc::is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) { + sys::Sys::evt_handler(); + } else if Ipcc::is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) { + ble::Ble::evt_handler(); + } else { + todo!() + } + } } pub struct TransmitInterruptHandler {} impl interrupt::Handler for TransmitInterruptHandler { - unsafe fn on_interrupt() {} + unsafe fn on_interrupt() { + // info!("ipcc tx interrupt"); + + if Ipcc::is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) { + // TODO: handle this case + let _ = sys::Sys::cmd_evt_handler(); + } else if Ipcc::is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) { + mm::MemoryManager::evt_handler(); + } else { + todo!() + } + } } /// # Version @@ -289,21 +316,24 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251 // TODO: get a better size, this is a placeholder pub(crate) static TL_CHANNEL: Channel = Channel::new(); -pub struct TlMbox { - _sys: Sys, - _ble: Ble, - _mm: MemoryManager, +pub struct TlMbox<'d> { + _ipcc: PeripheralRef<'d, IPCC>, } -impl TlMbox { +impl<'d> TlMbox<'d> { /// initializes low-level transport between CPU1 and BLE stack on CPU2 - pub fn init( - ipcc: &mut Ipcc, + pub fn new( + ipcc: impl Peripheral

+ 'd, _irqs: impl interrupt::Binding + interrupt::Binding, - ) -> TlMbox { + config: Config, + ) -> Self { + into_ref!(ipcc); + unsafe { - TL_REF_TABLE = MaybeUninit::new(RefTable { + compiler_fence(Ordering::AcqRel); + + TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable { device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(), ble_table: TL_BLE_TABLE.as_ptr(), thread_table: TL_THREAD_TABLE.as_ptr(), @@ -316,6 +346,10 @@ impl TlMbox { ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(), }); + // info!("TL_REF_TABLE addr: {:x}", TL_REF_TABLE.as_ptr() as usize); + + compiler_fence(Ordering::AcqRel); + TL_SYS_TABLE = MaybeUninit::zeroed(); TL_DEVICE_INFO_TABLE = MaybeUninit::zeroed(); TL_BLE_TABLE = MaybeUninit::zeroed(); @@ -334,33 +368,24 @@ impl TlMbox { CS_BUFFER = MaybeUninit::zeroed(); BLE_CMD_BUFFER = MaybeUninit::zeroed(); HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed(); + + compiler_fence(Ordering::AcqRel); } - ipcc.init(); + Ipcc::enable(config); - let _sys = Sys::new(ipcc); - let _ble = Ble::new(ipcc); - let _mm = MemoryManager::new(); + Sys::enable(); + Ble::enable(); + MemoryManager::enable(); - // rx_irq.disable(); - // tx_irq.disable(); - // - // rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ()); - // tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ()); - // - // rx_irq.set_handler(|ipcc| { - // let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() }; - // Self::interrupt_ipcc_rx_handler(ipcc); - // }); - // tx_irq.set_handler(|ipcc| { - // let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() }; - // Self::interrupt_ipcc_tx_handler(ipcc); - // }); - // - // rx_irq.enable(); - // tx_irq.enable(); + // enable interrupts + unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend(); + unsafe { crate::interrupt::IPCC_C1_TX::steal() }.unpend(); - TlMbox { _sys, _ble, _mm } + unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable(); + unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable(); + + Self { _ipcc: ipcc } } pub fn wireless_fw_info(&self) -> Option { @@ -374,42 +399,19 @@ impl TlMbox { } } - pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { - shci_ble_init(ipcc, param); + pub fn shci_ble_init(&self, param: ShciBleInitCmdParam) { + shci_ble_init(param); } - pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) { - ble::Ble::send_cmd(ipcc, buf); + pub fn send_ble_cmd(&self, buf: &[u8]) { + ble::Ble::send_cmd(buf); } - // pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) { - // sys::Sys::send_cmd(ipcc, buf); + // pub fn send_sys_cmd(&self, buf: &[u8]) { + // sys::Sys::send_cmd(buf); // } pub async fn read(&self) -> EvtBox { TL_CHANNEL.recv().await } - - #[allow(dead_code)] - fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) { - if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) { - sys::Sys::evt_handler(ipcc); - } else if ipcc.is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) { - ble::Ble::evt_handler(ipcc); - } else { - todo!() - } - } - - #[allow(dead_code)] - fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) { - if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) { - // TODO: handle this case - let _ = sys::Sys::cmd_evt_handler(ipcc); - } else if ipcc.is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) { - mm::MemoryManager::evt_handler(ipcc); - } else { - todo!() - } - } } diff --git a/embassy-stm32/src/tl_mbox/shci.rs b/embassy-stm32/src/tl_mbox/shci.rs index 61fd9e4a..6b5b2dd1 100644 --- a/embassy-stm32/src/tl_mbox/shci.rs +++ b/embassy-stm32/src/tl_mbox/shci.rs @@ -3,7 +3,7 @@ use super::cmd::CmdPacket; use super::consts::TlPacketType; use super::{channels, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE, TL_SYS_TABLE}; -use crate::ipcc::Ipcc; +use crate::tl_mbox::ipcc::Ipcc; const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66; pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; @@ -76,7 +76,7 @@ pub struct ShciBleInitCmdPacket { param: ShciBleInitCmdParam, } -pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { +pub fn shci_ble_init(param: ShciBleInitCmdParam) { let mut packet = ShciBleInitCmdPacket { header: ShciHeader::default(), param, @@ -95,7 +95,7 @@ pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8; - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); } } diff --git a/embassy-stm32/src/tl_mbox/sys.rs b/embassy-stm32/src/tl_mbox/sys.rs index 31ebde72..9685fb92 100644 --- a/embassy-stm32/src/tl_mbox/sys.rs +++ b/embassy-stm32/src/tl_mbox/sys.rs @@ -1,5 +1,3 @@ -use core::mem::MaybeUninit; - use embassy_futures::block_on; use super::cmd::{CmdPacket, CmdSerial}; @@ -7,27 +5,25 @@ use super::consts::TlPacketType; use super::evt::{CcEvt, EvtBox, EvtSerial}; use super::unsafe_linked_list::LinkedListNode; use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE}; -use crate::ipcc::Ipcc; +use crate::tl_mbox::ipcc::Ipcc; pub struct Sys; impl Sys { - pub(crate) fn new(ipcc: &mut Ipcc) -> Self { + pub fn enable() { unsafe { LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr()); - TL_SYS_TABLE = MaybeUninit::new(SysTable { + TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable { pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(), sys_queue: SYSTEM_EVT_QUEUE.as_ptr(), }); } - ipcc.c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true); - - Sys + Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true); } - pub(crate) fn evt_handler(ipcc: &mut Ipcc) { + pub fn evt_handler() { unsafe { let mut node_ptr = core::ptr::null_mut(); let node_ptr_ptr: *mut _ = &mut node_ptr; @@ -43,11 +39,11 @@ impl Sys { } } - ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL); + Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL); } - pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) -> CcEvt { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false); + pub fn cmd_evt_handler() -> CcEvt { + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false); // ST's command response data structure is really convoluted. // @@ -68,11 +64,11 @@ impl Sys { } #[allow(dead_code)] - pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { + pub fn send_cmd(buf: &[u8]) { unsafe { // TODO: check this let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; - let cmd_serial: *mut CmdSerial = &mut (*cmd_buffer).cmd_serial; + let cmd_serial: *mut CmdSerial = &mut cmd_buffer.cmd_serial; let cmd_serial_buf = cmd_serial.cast(); core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len()); @@ -80,8 +76,8 @@ impl Sys { let cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8; - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); } } } diff --git a/tests/stm32/memory_ble.x b/embassy-stm32/tl_mbox.x.in similarity index 58% rename from tests/stm32/memory_ble.x rename to embassy-stm32/tl_mbox.x.in index 4332e2c7..b6eecb42 100644 --- a/tests/stm32/memory_ble.x +++ b/embassy-stm32/tl_mbox.x.in @@ -1,21 +1,13 @@ -/* - Memory size for STM32WB55xG with 512K FLASH -*/ - -MEMORY +MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K - RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K } -/* Place stack at the end of SRAM1 */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); - /* * Scatter the mailbox interface memory sections in shared memory */ -SECTIONS { +SECTIONS +{ TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index c4edbd38..857da546 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -169,4 +169,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } - +embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly"] } diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs index 5c2ad3b2..d6436369 100644 --- a/embassy-time/src/driver.rs +++ b/embassy-time/src/driver.rs @@ -49,7 +49,7 @@ //! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { //! todo!() //! } -//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { +//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { //! todo!() //! } //! } diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs index 64a8af4b..4795eb2f 100644 --- a/embassy-time/src/queue_generic.rs +++ b/embassy-time/src/queue_generic.rs @@ -183,7 +183,6 @@ mod tests { use serial_test::serial; - use super::InnerQueue; use crate::driver::{AlarmHandle, Driver}; use crate::queue_generic::QUEUE; use crate::Instant; @@ -317,14 +316,18 @@ mod tests { fn setup() { DRIVER.reset(); - - QUEUE.inner.lock(|inner| { - *inner.borrow_mut() = InnerQueue::new(); - }); + critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None); } fn queue_len() -> usize { - QUEUE.inner.lock(|inner| inner.borrow().queue.iter().count()) + critical_section::with(|cs| { + QUEUE + .inner + .borrow_ref(cs) + .as_ref() + .map(|inner| inner.queue.iter().count()) + .unwrap_or(0) + }) } #[test] diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs index 52620d23..d3d1f9f5 100644 --- a/embassy-time/src/timer.rs +++ b/embassy-time/src/timer.rs @@ -109,7 +109,6 @@ impl Future for Timer { /// # #![feature(type_alias_impl_trait)] /// # /// use embassy_time::{Duration, Ticker}; -/// use futures::StreamExt; /// # fn foo(){} /// /// #[embassy_executor::task] diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 3c7e3e87..8cfac772 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" 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-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-rtt = "0.4" diff --git a/examples/stm32wb/build.rs b/examples/stm32wb/build.rs index 30691aa9..29b3a9b2 100644 --- a/examples/stm32wb/build.rs +++ b/examples/stm32wb/build.rs @@ -1,35 +1,11 @@ -//! This build script copies the `memory.x` file from the crate root into -//! 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"); +use std::error::Error; +fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); 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"); + + Ok(()) } diff --git a/examples/stm32wb/memory.x b/examples/stm32wb/memory.x deleted file mode 100644 index e1f0530b..00000000 --- a/examples/stm32wb/memory.x +++ /dev/null @@ -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 -} diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs index 326e4be8..8f4e70af 100644 --- a/examples/stm32wb/src/bin/tl_mbox.rs +++ b/examples/stm32wb/src/bin/tl_mbox.rs @@ -4,8 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::ipcc::{Config, Ipcc}; -use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::tl_mbox::{Config, TlMbox}; use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -45,14 +44,12 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let config = Config::default(); - let mut ipcc = Ipcc::new(p.IPCC, config); - - let mbox = TlMbox::init(&mut ipcc, Irqs); + let mbox = TlMbox::new(p.IPCC, Irqs, config); loop { let wireless_fw_info = mbox.wireless_fw_info(); match wireless_fw_info { - None => error!("not yet initialized"), + None => info!("not yet initialized"), Some(fw_info) => { let version_major = fw_info.version_major(); 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(); } diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs index 7a69f26b..1724d946 100644 --- a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs +++ b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs @@ -4,8 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::ipcc::{Config, Ipcc}; -use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::tl_mbox::{Config, TlMbox}; use embassy_stm32::{bind_interrupts, tl_mbox}; use {defmt_rtt as _, panic_probe as _}; @@ -44,12 +43,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let config = Config::default(); - let mut ipcc = Ipcc::new(p.IPCC, config); - - let mbox = TlMbox::init(&mut ipcc, Irqs); - - // initialize ble stack, does not return a response - mbox.shci_ble_init(&mut ipcc, Default::default()); + let mbox = TlMbox::new(p.IPCC, Irqs, config); info!("waiting for coprocessor to boot"); 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"); - 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; @@ -92,8 +87,12 @@ async fn main(_spawner: Spawner) { info!( "==> 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(); } diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 29c4799a..426d6e67 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -17,4 +17,4 @@ rustflags = [ target = "thumbv7m-none-eabi" [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "trace" \ No newline at end of file diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index bd8d90ab..4c059774 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -7,12 +7,12 @@ autobins = false [features] 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 stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo stm32g491re = ["embassy-stm32/stm32g491re", "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 stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board @@ -45,8 +45,8 @@ chrono = { version = "^0.4", default-features = false, optional = true} # BEGIN TESTS # Generated by gen_test.py. DO NOT EDIT. [[bin]] -name = "ble" -path = "src/bin/ble.rs" +name = "tl_mbox" +path = "src/bin/tl_mbox.rs" required-features = [ "ble",] [[bin]] diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index ca76b70b..b4583147 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -9,17 +9,22 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); // 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: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 { println!("cargo:rustc-link-arg-bins=-Tlink_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"); Ok(()) diff --git a/tests/stm32/src/bin/ble.rs b/tests/stm32/src/bin/tl_mbox.rs similarity index 79% rename from tests/stm32/src/bin/ble.rs rename to tests/stm32/src/bin/tl_mbox.rs index aedf9a38..626e7ac6 100644 --- a/tests/stm32/src/bin/ble.rs +++ b/tests/stm32/src/bin/tl_mbox.rs @@ -7,24 +7,23 @@ #[path = "../example_common.rs"] mod example_common; use embassy_executor::Spawner; -use embassy_stm32::interrupt; -use embassy_stm32::ipcc::{Config, Ipcc}; -use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::tl_mbox::{Config, TlMbox}; +use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; use example_common::*; +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler; + IPCC_C1_TX => tl_mbox::TransmitInterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config()); info!("Hello World!"); let config = Config::default(); - let mut ipcc = Ipcc::new(p.IPCC, 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); + let mbox = TlMbox::new(p.IPCC, Irqs, config); loop { let wireless_fw_info = mbox.wireless_fw_info();