Remove generic const expressions from embassy-boot

* Remove the need for generic const expressions and use buffers provided in the flash config.
* Extend embedded-storage traits to simplify generics.
* Document all public APIs
* Add toplevel README
* Expose AlignedBuffer type for convenience.
* Update examples
This commit is contained in:
Ulf Lilleengen 2022-08-30 13:07:35 +02:00
parent 7542505cf9
commit 3ca7314476
16 changed files with 431 additions and 293 deletions

30
embassy-boot/README.md Normal file
View File

@ -0,0 +1,30 @@
# embassy-boot
An [Embassy](https://embassy.dev) project.
A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks.
The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts.
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
## Hardware support
The bootloader supports different hardware in separate crates:
* `embassy-boot-nrf` - for the nRF microcontrollers.
* `embassy-boot-stm32` - for the STM32 microcontrollers.
## Minimum supported Rust version (MSRV)
`embassy-boot` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
## License
This work is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.

View File

@ -1,53 +1,54 @@
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(generic_associated_types)] #![feature(generic_associated_types)]
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
#![no_std] #![no_std]
///! embassy-boot is a bootloader and firmware updater for embedded devices with flash #![warn(missing_docs)]
///! storage implemented using embedded-storage #![doc = include_str!("../../README.md")]
///!
///! The bootloader works in conjunction with the firmware application, and only has the
///! ability to manage two flash banks with an active and a updatable part. It implements
///! a swap algorithm that is power-failure safe, and allows reverting to the previous
///! version of the firmware, should the application crash and fail to mark itself as booted.
///!
///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf,
///! which defines the limits and flash type for that particular platform.
///!
mod fmt; mod fmt;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
use embedded_storage_async::nor_flash::AsyncNorFlash; use embedded_storage_async::nor_flash::AsyncNorFlash;
const BOOT_MAGIC: u8 = 0xD0; const BOOT_MAGIC: u8 = 0xD0;
const SWAP_MAGIC: u8 = 0xF0; const SWAP_MAGIC: u8 = 0xF0;
/// A region in flash used by the bootloader.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Partition { pub struct Partition {
/// Start of the flash region.
pub from: usize, pub from: usize,
/// End of the flash region.
pub to: usize, pub to: usize,
} }
impl Partition { impl Partition {
/// Create a new partition with the provided range
pub const fn new(from: usize, to: usize) -> Self { pub const fn new(from: usize, to: usize) -> Self {
Self { from, to } Self { from, to }
} }
/// Return the length of the partition
pub const fn len(&self) -> usize { pub const fn len(&self) -> usize {
self.to - self.from self.to - self.from
} }
} }
#[derive(PartialEq, Debug)] /// The state of the bootloader after running prepare.
#[derive(PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State { pub enum State {
/// Bootloader is ready to boot the active partition.
Boot, Boot,
/// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
Swap, Swap,
} }
#[derive(PartialEq, Debug)] /// Errors returned by bootloader
#[derive(PartialEq, Eq, Debug)]
pub enum BootError { pub enum BootError {
/// Error from flash.
Flash(NorFlashErrorKind), Flash(NorFlashErrorKind),
/// Invalid bootloader magic
BadMagic, BadMagic,
} }
@ -60,19 +61,39 @@ where
} }
} }
pub trait FlashConfig { /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
const BLOCK_SIZE: usize; #[repr(align(32))]
const ERASE_VALUE: u8; pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
type FLASH: NorFlash + ReadNorFlash;
fn flash(&mut self) -> &mut Self::FLASH; impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
/// Extension of the embedded-storage flash type information with block size and erase value.
pub trait Flash: NorFlash + ReadNorFlash {
/// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase
/// size of the flash, but for external QSPI flash modules, this can be lower.
const BLOCK_SIZE: usize;
/// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value.
const ERASE_VALUE: u8 = 0xFF;
} }
/// Trait defining the flash handles used for active and DFU partition /// Trait defining the flash handles used for active and DFU partition
pub trait FlashProvider { pub trait FlashConfig {
type STATE: FlashConfig; /// Flash type used for the state partition.
type ACTIVE: FlashConfig; type STATE: Flash;
type DFU: FlashConfig; /// Flash type used for the active partition.
type ACTIVE: Flash;
/// Flash type used for the dfu partition.
type DFU: Flash;
/// Return flash instance used to write/read to/from active partition. /// Return flash instance used to write/read to/from active partition.
fn active(&mut self) -> &mut Self::ACTIVE; fn active(&mut self) -> &mut Self::ACTIVE;
@ -84,9 +105,7 @@ pub trait FlashProvider {
/// BootLoader works with any flash implementing embedded_storage and can also work with /// BootLoader works with any flash implementing embedded_storage and can also work with
/// different page sizes and flash write sizes. /// different page sizes and flash write sizes.
/// pub struct BootLoader {
/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes.
pub struct BootLoader<const PAGE_SIZE: usize> {
// Page with current state of bootloader. The state partition has the following format: // Page with current state of bootloader. The state partition has the following format:
// | Range | Description | // | Range | Description |
// | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
@ -98,15 +117,16 @@ pub struct BootLoader<const PAGE_SIZE: usize> {
dfu: Partition, dfu: Partition,
} }
impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { impl BootLoader {
/// Create a new instance of a bootloader with the given partitions.
///
/// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
/// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
assert_eq!(active.len() % PAGE_SIZE, 0);
assert_eq!(dfu.len() % PAGE_SIZE, 0);
// DFU partition must have an extra page
assert!(dfu.len() - active.len() >= PAGE_SIZE);
Self { active, dfu, state } Self { active, dfu, state }
} }
/// Return the boot address for the active partition.
pub fn boot_address(&self) -> usize { pub fn boot_address(&self) -> usize {
self.active.from self.active.from
} }
@ -194,44 +214,43 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
/// | DFU | 3 | 3 | 2 | 1 | 3 | /// | DFU | 3 | 3 | 2 | 1 | 3 |
/// +-----------+--------------+--------+--------+--------+--------+ /// +-----------+--------------+--------+--------+--------+--------+
/// ///
pub fn prepare_boot<P: FlashProvider>(&mut self, p: &mut P) -> Result<State, BootError> pub fn prepare_boot<P: FlashConfig>(
where &mut self,
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, p: &mut P,
[(); <<P as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, magic: &mut [u8],
{ page: &mut [u8],
) -> Result<State, BootError> {
// Ensure we have enough progress pages to store copy progress // Ensure we have enough progress pages to store copy progress
assert!( assert_eq!(self.active.len() % page.len(), 0);
self.active.len() / PAGE_SIZE assert_eq!(self.dfu.len() % page.len(), 0);
<= (self.state.len() - <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE) assert!(self.dfu.len() - self.active.len() >= page.len());
/ <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE assert!(self.active.len() / page.len() <= (self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE);
); assert_eq!(magic.len(), P::STATE::WRITE_SIZE);
// Copy contents from partition N to active // Copy contents from partition N to active
let state = self.read_state(p.state())?; let state = self.read_state(p, magic)?;
match state { match state {
State::Swap => { State::Swap => {
// //
// Check if we already swapped. If we're in the swap state, this means we should revert // Check if we already swapped. If we're in the swap state, this means we should revert
// since the app has failed to mark boot as successful // since the app has failed to mark boot as successful
// //
if !self.is_swapped(p.state())? { if !self.is_swapped(p, magic, page)? {
trace!("Swapping"); trace!("Swapping");
self.swap(p)?; self.swap(p, magic, page)?;
trace!("Swapping done"); trace!("Swapping done");
} else { } else {
trace!("Reverting"); trace!("Reverting");
self.revert(p)?; self.revert(p, magic, page)?;
// Overwrite magic and reset progress // Overwrite magic and reset progress
let fstate = p.state().flash(); let fstate = p.state();
let aligned = Aligned( magic.fill(!P::STATE::ERASE_VALUE);
[!P::STATE::ERASE_VALUE; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE], fstate.write(self.state.from as u32, magic)?;
);
fstate.write(self.state.from as u32, &aligned.0)?;
fstate.erase(self.state.from as u32, self.state.to as u32)?; fstate.erase(self.state.from as u32, self.state.to as u32)?;
let aligned =
Aligned([BOOT_MAGIC; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]); magic.fill(BOOT_MAGIC);
fstate.write(self.state.from as u32, &aligned.0)?; fstate.write(self.state.from as u32, magic)?;
} }
} }
_ => {} _ => {}
@ -239,166 +258,152 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
Ok(state) Ok(state)
} }
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P) -> Result<bool, BootError> fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<bool, BootError> {
where let page_size = page.len();
[(); P::FLASH::WRITE_SIZE]:, let page_count = self.active.len() / page_size;
{ let progress = self.current_progress(p, magic)?;
let page_count = self.active.len() / P::FLASH::ERASE_SIZE;
let progress = self.current_progress(p)?;
Ok(progress >= page_count * 2) Ok(progress >= page_count * 2)
} }
fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> {
where let write_size = aligned.len();
[(); P::FLASH::WRITE_SIZE]:,
{
let write_size = P::FLASH::WRITE_SIZE;
let max_index = ((self.state.len() - write_size) / write_size) - 1; let max_index = ((self.state.len() - write_size) / write_size) - 1;
let flash = p.flash(); aligned.fill(!P::STATE::ERASE_VALUE);
let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
let flash = config.state();
for i in 0..max_index { for i in 0..max_index {
flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?; flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?;
if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] {
if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) {
return Ok(i); return Ok(i);
} }
} }
Ok(max_index) Ok(max_index)
} }
fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> {
where let flash = p.state();
[(); P::FLASH::WRITE_SIZE]:, let write_size = magic.len();
{
let flash = p.flash();
let write_size = P::FLASH::WRITE_SIZE;
let w = self.state.from + write_size + idx * write_size; let w = self.state.from + write_size + idx * write_size;
let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
flash.write(w as u32, &aligned.0)?; let aligned = magic;
aligned.fill(!P::STATE::ERASE_VALUE);
flash.write(w as u32, aligned)?;
Ok(()) Ok(())
} }
fn active_addr(&self, n: usize) -> usize { fn active_addr(&self, n: usize, page_size: usize) -> usize {
self.active.from + n * PAGE_SIZE self.active.from + n * page_size
} }
fn dfu_addr(&self, n: usize) -> usize { fn dfu_addr(&self, n: usize, page_size: usize) -> usize {
self.dfu.from + n * PAGE_SIZE self.dfu.from + n * page_size
} }
fn copy_page_once_to_active<P: FlashProvider>( fn copy_page_once_to_active<P: FlashConfig>(
&mut self, &mut self,
idx: usize, idx: usize,
from_page: usize, from_page: usize,
to_page: usize, to_page: usize,
p: &mut P, p: &mut P,
) -> Result<(), BootError> magic: &mut [u8],
where page: &mut [u8],
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, ) -> Result<(), BootError> {
{ let buf = page;
let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; if self.current_progress(p, magic)? <= idx {
if self.current_progress(p.state())? <= idx {
let mut offset = from_page; let mut offset = from_page;
for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
p.dfu().flash().read(offset as u32, chunk)?; p.dfu().read(offset as u32, chunk)?;
offset += chunk.len(); offset += chunk.len();
} }
p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?;
let mut offset = to_page; let mut offset = to_page;
for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
p.active().flash().write(offset as u32, &chunk)?; p.active().write(offset as u32, chunk)?;
offset += chunk.len(); offset += chunk.len();
} }
self.update_progress(idx, p.state())?; self.update_progress(idx, p, magic)?;
} }
Ok(()) Ok(())
} }
fn copy_page_once_to_dfu<P: FlashProvider>( fn copy_page_once_to_dfu<P: FlashConfig>(
&mut self, &mut self,
idx: usize, idx: usize,
from_page: usize, from_page: usize,
to_page: usize, to_page: usize,
p: &mut P, p: &mut P,
) -> Result<(), BootError> magic: &mut [u8],
where page: &mut [u8],
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, ) -> Result<(), BootError> {
{ let buf = page;
let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; if self.current_progress(p, magic)? <= idx {
if self.current_progress(p.state())? <= idx {
let mut offset = from_page; let mut offset = from_page;
for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
p.active().flash().read(offset as u32, chunk)?; p.active().read(offset as u32, chunk)?;
offset += chunk.len(); offset += chunk.len();
} }
p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?;
let mut offset = to_page; let mut offset = to_page;
for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
p.dfu().flash().write(offset as u32, chunk)?; p.dfu().write(offset as u32, chunk)?;
offset += chunk.len(); offset += chunk.len();
} }
self.update_progress(idx, p.state())?; self.update_progress(idx, p, magic)?;
} }
Ok(()) Ok(())
} }
fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> fn swap<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
where let page_size = page.len();
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, let page_count = self.active.len() / page_size;
{
let page_count = self.active.len() / PAGE_SIZE;
trace!("Page count: {}", page_count); trace!("Page count: {}", page_count);
for page in 0..page_count { for page_num in 0..page_count {
trace!("COPY PAGE {}", page); trace!("COPY PAGE {}", page_num);
// Copy active page to the 'next' DFU page. // Copy active page to the 'next' DFU page.
let active_page = self.active_addr(page_count - 1 - page); let active_page = self.active_addr(page_count - 1 - page_num, page_size);
let dfu_page = self.dfu_addr(page_count - page); let dfu_page = self.dfu_addr(page_count - page_num, page_size);
//trace!("Copy active {} to dfu {}", active_page, dfu_page); //trace!("Copy active {} to dfu {}", active_page, dfu_page);
self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?;
// Copy DFU page to the active page // Copy DFU page to the active page
let active_page = self.active_addr(page_count - 1 - page); let active_page = self.active_addr(page_count - 1 - page_num, page_size);
let dfu_page = self.dfu_addr(page_count - 1 - page); let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size);
//trace!("Copy dfy {} to active {}", dfu_page, active_page); //trace!("Copy dfy {} to active {}", dfu_page, active_page);
self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
} }
Ok(()) Ok(())
} }
fn revert<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> fn revert<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
where let page_size = page.len();
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, let page_count = self.active.len() / page_size;
{ for page_num in 0..page_count {
let page_count = self.active.len() / PAGE_SIZE;
for page in 0..page_count {
// Copy the bad active page to the DFU page // Copy the bad active page to the DFU page
let active_page = self.active_addr(page); let active_page = self.active_addr(page_num, page_size);
let dfu_page = self.dfu_addr(page); let dfu_page = self.dfu_addr(page_num, page_size);
self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?; self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?;
// Copy the DFU page back to the active page // Copy the DFU page back to the active page
let active_page = self.active_addr(page); let active_page = self.active_addr(page_num, page_size);
let dfu_page = self.dfu_addr(page + 1); let dfu_page = self.dfu_addr(page_num + 1, page_size);
self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?; self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
} }
Ok(()) Ok(())
} }
fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> fn read_state<P: FlashConfig>(&mut self, config: &mut P, magic: &mut [u8]) -> Result<State, BootError> {
where let flash = config.state();
[(); P::FLASH::WRITE_SIZE]:, flash.read(self.state.from as u32, magic)?;
{
let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE];
let flash = p.flash();
flash.read(self.state.from as u32, &mut magic)?;
if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] { if !magic.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap) Ok(State::Swap)
} else { } else {
Ok(State::Boot) Ok(State::Boot)
@ -406,108 +411,149 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
} }
} }
/// Convenience provider that uses a single flash for everything /// Convenience provider that uses a single flash for all partitions.
pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF> pub struct SingleFlashConfig<'a, F>
where where
F: NorFlash + ReadNorFlash, F: Flash,
{ {
config: SingleFlashConfig<'a, F, ERASE_VALUE>, flash: &'a mut F,
} }
impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE> impl<'a, F> SingleFlashConfig<'a, F>
where where
F: NorFlash + ReadNorFlash, F: Flash,
{ {
/// Create a provider for a single flash.
pub fn new(flash: &'a mut F) -> Self { pub fn new(flash: &'a mut F) -> Self {
Self { Self { flash }
config: SingleFlashConfig { flash },
}
} }
} }
pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF> impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
where
F: Flash,
{
type STATE = F;
type ACTIVE = F;
type DFU = F;
fn active(&mut self) -> &mut Self::STATE {
self.flash
}
fn dfu(&mut self) -> &mut Self::ACTIVE {
self.flash
}
fn state(&mut self) -> &mut Self::DFU {
self.flash
}
}
/// A flash wrapper implementing the Flash and embedded_storage traits.
pub struct BootFlash<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
where where
F: NorFlash + ReadNorFlash, F: NorFlash + ReadNorFlash,
{ {
flash: &'a mut F, flash: &'a mut F,
} }
impl<'a, F> FlashProvider for SingleFlashProvider<'a, F> impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
where where
F: NorFlash + ReadNorFlash, F: NorFlash + ReadNorFlash,
{ {
type STATE = SingleFlashConfig<'a, F>; /// Create a new instance of a bootable flash
type ACTIVE = SingleFlashConfig<'a, F>; pub fn new(flash: &'a mut F) -> Self {
type DFU = SingleFlashConfig<'a, F>; Self { flash }
fn active(&mut self) -> &mut Self::STATE {
&mut self.config
}
fn dfu(&mut self) -> &mut Self::ACTIVE {
&mut self.config
}
fn state(&mut self) -> &mut Self::DFU {
&mut self.config
} }
} }
impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE> impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
where where
F: NorFlash + ReadNorFlash, F: NorFlash + ReadNorFlash,
{ {
const BLOCK_SIZE: usize = F::ERASE_SIZE; const BLOCK_SIZE: usize = BLOCK_SIZE;
const ERASE_VALUE: u8 = ERASE_VALUE; const ERASE_VALUE: u8 = ERASE_VALUE;
type FLASH = F; }
fn flash(&mut self) -> &mut F {
self.flash impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
where
F: ReadNorFlash + NorFlash,
{
type Error = F::Error;
}
impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
where
F: ReadNorFlash + NorFlash,
{
const WRITE_SIZE: usize = F::WRITE_SIZE;
const ERASE_SIZE: usize = F::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
F::erase(self.flash, from, to)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
F::write(self.flash, offset, bytes)
} }
} }
/// Convenience provider that uses a single flash for everything impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU>
where where
ACTIVE: NorFlash + ReadNorFlash, F: ReadNorFlash + NorFlash,
STATE: NorFlash + ReadNorFlash,
DFU: NorFlash + ReadNorFlash,
{ {
active: SingleFlashConfig<'a, ACTIVE>, const READ_SIZE: usize = F::READ_SIZE;
state: SingleFlashConfig<'a, STATE>,
dfu: SingleFlashConfig<'a, DFU>, fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
F::read(self.flash, offset, bytes)
}
fn capacity(&self) -> usize {
F::capacity(self.flash)
}
} }
impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU> /// Convenience flash provider that uses separate flash instances for each partition.
pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where where
ACTIVE: NorFlash + ReadNorFlash, ACTIVE: Flash,
STATE: NorFlash + ReadNorFlash, STATE: Flash,
DFU: NorFlash + ReadNorFlash, DFU: Flash,
{ {
active: &'a mut ACTIVE,
state: &'a mut STATE,
dfu: &'a mut DFU,
}
impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: Flash,
STATE: Flash,
DFU: Flash,
{
/// Create a new flash provider with separate configuration for all three partitions.
pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
Self { Self { active, state, dfu }
active: SingleFlashConfig { flash: active },
state: SingleFlashConfig { flash: state },
dfu: SingleFlashConfig { flash: dfu },
}
} }
} }
impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU> impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where where
ACTIVE: NorFlash + ReadNorFlash, ACTIVE: Flash,
STATE: NorFlash + ReadNorFlash, STATE: Flash,
DFU: NorFlash + ReadNorFlash, DFU: Flash,
{ {
type STATE = SingleFlashConfig<'a, STATE>; type STATE = STATE;
type ACTIVE = SingleFlashConfig<'a, ACTIVE>; type ACTIVE = ACTIVE;
type DFU = SingleFlashConfig<'a, DFU>; type DFU = DFU;
fn active(&mut self) -> &mut Self::ACTIVE { fn active(&mut self) -> &mut Self::ACTIVE {
&mut self.active self.active
} }
fn dfu(&mut self) -> &mut Self::DFU { fn dfu(&mut self) -> &mut Self::DFU {
&mut self.dfu self.dfu
} }
fn state(&mut self) -> &mut Self::STATE { fn state(&mut self) -> &mut Self::STATE {
&mut self.state self.state
} }
} }
@ -518,10 +564,6 @@ pub struct FirmwareUpdater {
dfu: Partition, dfu: Partition,
} }
// NOTE: Aligned to the largest write size supported by flash
#[repr(align(32))]
pub struct Aligned<const N: usize>([u8; N]);
impl Default for FirmwareUpdater { impl Default for FirmwareUpdater {
fn default() -> Self { fn default() -> Self {
extern "C" { extern "C" {
@ -551,6 +593,7 @@ impl Default for FirmwareUpdater {
} }
impl FirmwareUpdater { 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 { pub const fn new(dfu: Partition, state: Partition) -> Self {
Self { dfu, state } Self { dfu, state }
} }
@ -560,23 +603,24 @@ impl FirmwareUpdater {
self.dfu.len() self.dfu.len()
} }
/// Instruct bootloader that DFU should commence at next boot. /// Mark to trigger firmware swap on next boot.
/// Must be provided with an aligned buffer to use for reading and writing magic; ///
pub async fn update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> /// # Safety
where ///
[(); F::WRITE_SIZE]:, /// 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_updated<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
let mut aligned = Aligned([0; { F::WRITE_SIZE }]); assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await self.set_magic(aligned, SWAP_MAGIC, flash).await
} }
/// Mark firmware boot successfully /// Mark firmware boot successful and stop rollback on reset.
pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> ///
where /// # Safety
[(); F::WRITE_SIZE]:, ///
{ /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
let mut aligned = Aligned([0; { F::WRITE_SIZE }]); pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC, flash).await
} }
async fn set_magic<F: AsyncNorFlash>( async fn set_magic<F: AsyncNorFlash>(
@ -587,7 +631,7 @@ impl FirmwareUpdater {
) -> Result<(), F::Error> { ) -> Result<(), F::Error> {
flash.read(self.state.from as u32, aligned).await?; flash.read(self.state.from as u32, aligned).await?;
if aligned.iter().find(|&&b| b != magic).is_some() { if aligned.iter().any(|&b| b != magic) {
aligned.fill(0); aligned.fill(0);
flash.write(self.state.from as u32, aligned).await?; flash.write(self.state.from as u32, aligned).await?;
@ -599,7 +643,13 @@ impl FirmwareUpdater {
Ok(()) Ok(())
} }
// Write to a region of the DFU page /// 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>( pub async fn write_firmware<F: AsyncNorFlash>(
&mut self, &mut self,
offset: usize, offset: usize,
@ -668,7 +718,7 @@ mod tests {
#[test] #[test]
fn test_bad_magic() { fn test_bad_magic() {
let mut flash = MemFlash([0xff; 131072]); let mut flash = MemFlash([0xff; 131072]);
let mut flash = SingleFlashProvider::new(&mut flash); let mut flash = SingleFlashConfig::new(&mut flash);
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
@ -687,11 +737,16 @@ mod tests {
let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]);
flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
let mut flash = SingleFlashProvider::new(&mut flash); let mut flash = SingleFlashConfig::new(&mut flash);
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); let mut magic = [0; 4];
let mut page = [0; 4096];
assert_eq!(
State::Boot,
bootloader.prepare_boot(&mut flash, &mut magic, &mut page).unwrap()
);
} }
#[test] #[test]
@ -703,24 +758,27 @@ mod tests {
let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
let mut aligned = [0; 4];
for i in ACTIVE.from..ACTIVE.to { for i in ACTIVE.from..ACTIVE.to {
flash.0[i] = original[i - ACTIVE.from]; flash.0[i] = original[i - ACTIVE.from];
} }
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut updater = FirmwareUpdater::new(DFU, STATE); let mut updater = FirmwareUpdater::new(DFU, STATE);
let mut offset = 0; let mut offset = 0;
for chunk in update.chunks(4096) { for chunk in update.chunks(4096) {
block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); block_on(updater.write_firmware(offset, chunk, &mut flash, 4096)).unwrap();
offset += chunk.len(); offset += chunk.len();
} }
block_on(updater.update(&mut flash)).unwrap(); block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
let mut magic = [0; 4];
let mut page = [0; 4096];
assert_eq!( assert_eq!(
State::Swap, State::Swap,
bootloader bootloader
.prepare_boot(&mut SingleFlashProvider::new(&mut flash)) .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page)
.unwrap() .unwrap()
); );
@ -737,7 +795,7 @@ mod tests {
assert_eq!( assert_eq!(
State::Swap, State::Swap,
bootloader bootloader
.prepare_boot(&mut SingleFlashProvider::new(&mut flash)) .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page)
.unwrap() .unwrap()
); );
@ -751,11 +809,11 @@ mod tests {
} }
// Mark as booted // Mark as booted
block_on(updater.mark_booted(&mut flash)).unwrap(); block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap();
assert_eq!( assert_eq!(
State::Boot, State::Boot,
bootloader bootloader
.prepare_boot(&mut SingleFlashProvider::new(&mut flash)) .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page)
.unwrap() .unwrap()
); );
} }
@ -769,6 +827,7 @@ mod tests {
let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]);
let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]);
let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
let mut aligned = [0; 4];
let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
@ -781,16 +840,23 @@ mod tests {
let mut offset = 0; let mut offset = 0;
for chunk in update.chunks(2048) { for chunk in update.chunks(2048) {
block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap();
offset += chunk.len(); offset += chunk.len();
} }
block_on(updater.update(&mut state)).unwrap(); block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut magic = [0; 4];
let mut page = [0; 4096];
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
assert_eq!( assert_eq!(
State::Swap, State::Swap,
bootloader bootloader
.prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) .prepare_boot(
&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu),
&mut magic,
&mut page
)
.unwrap() .unwrap()
); );
@ -810,6 +876,7 @@ mod tests {
const ACTIVE: Partition = Partition::new(4096, 16384); const ACTIVE: Partition = Partition::new(4096, 16384);
const DFU: Partition = Partition::new(0, 16384); const DFU: Partition = Partition::new(0, 16384);
let mut aligned = [0; 4];
let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]);
let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]);
let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
@ -825,16 +892,22 @@ mod tests {
let mut offset = 0; let mut offset = 0;
for chunk in update.chunks(4096) { for chunk in update.chunks(4096) {
block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap();
offset += chunk.len(); offset += chunk.len();
} }
block_on(updater.update(&mut state)).unwrap(); block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut magic = [0; 4];
let mut page = [0; 4096];
assert_eq!( assert_eq!(
State::Swap, State::Swap,
bootloader bootloader
.prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) .prepare_boot(
&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,),
&mut magic,
&mut page
)
.unwrap() .unwrap()
); );
@ -899,6 +972,13 @@ mod tests {
} }
} }
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> super::Flash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const BLOCK_SIZE: usize = ERASE_SIZE;
const ERASE_VALUE: u8 = 0xFF;
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{ {

View File

@ -1,19 +1,21 @@
#![no_std] #![no_std]
#![feature(generic_associated_types)] #![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![allow(incomplete_features)] #![warn(missing_docs)]
#![feature(generic_const_exprs)] #![doc = include_str!("../../README.md")]
mod fmt; mod fmt;
pub use embassy_boot::{FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider}; pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE};
use embassy_nrf::peripherals::WDT; use embassy_nrf::peripherals::WDT;
use embassy_nrf::wdt; use embassy_nrf::wdt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for nRF devices.
pub struct BootLoader { pub struct BootLoader {
boot: embassy_boot::BootLoader<PAGE_SIZE>, boot: embassy_boot::BootLoader,
magic: AlignedBuffer<4>,
page: AlignedBuffer<PAGE_SIZE>,
} }
impl BootLoader { impl BootLoader {
@ -58,21 +60,25 @@ impl BootLoader {
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self { Self {
boot: embassy_boot::BootLoader::new(active, dfu, state), boot: embassy_boot::BootLoader::new(active, dfu, state),
magic: AlignedBuffer([0; 4]),
page: AlignedBuffer([0; PAGE_SIZE]),
} }
} }
/// Boots the application without softdevice mechanisms /// Inspect the bootloader state and perform actions required before booting, such as swapping
pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize /// firmware.
where pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
[(); <<F as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, match self.boot.prepare_boot(flash, &mut self.magic.0, &mut self.page.0) {
[(); <<F as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
{
match self.boot.prepare_boot(flash) {
Ok(_) => self.boot.boot_address(), Ok(_) => self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"), Err(_) => panic!("boot prepare error!"),
} }
} }
/// Boots the application without softdevice mechanisms.
///
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(not(feature = "softdevice"))] #[cfg(not(feature = "softdevice"))]
pub unsafe fn load(&mut self, start: usize) -> ! { pub unsafe fn load(&mut self, start: usize) -> ! {
let mut p = cortex_m::Peripherals::steal(); let mut p = cortex_m::Peripherals::steal();
@ -81,6 +87,11 @@ impl BootLoader {
cortex_m::asm::bootload(start as *const u32) cortex_m::asm::bootload(start as *const u32)
} }
/// Boots the application assuming softdevice is present.
///
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(feature = "softdevice")] #[cfg(feature = "softdevice")]
pub unsafe fn load(&mut self, _app: usize) -> ! { pub unsafe fn load(&mut self, _app: usize) -> ! {
use nrf_softdevice_mbr as mbr; use nrf_softdevice_mbr as mbr;

View File

@ -1,19 +1,20 @@
#![no_std] #![no_std]
#![feature(generic_associated_types)] #![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![allow(incomplete_features)] #![warn(missing_docs)]
#![feature(generic_const_exprs)] #![doc = include_str!("../../README.md")]
mod fmt; mod fmt;
pub use embassy_boot::{FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider, State}; pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
use embedded_storage::nor_flash::NorFlash;
pub struct BootLoader<const PAGE_SIZE: usize> { /// A bootloader for STM32 devices.
boot: embassy_boot::BootLoader<PAGE_SIZE>, pub struct BootLoader<const PAGE_SIZE: usize, const WRITE_SIZE: usize> {
boot: embassy_boot::BootLoader,
magic: AlignedBuffer<WRITE_SIZE>,
page: AlignedBuffer<PAGE_SIZE>,
} }
impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRITE_SIZE> {
/// Create a new bootloader instance using parameters from linker script /// Create a new bootloader instance using parameters from linker script
pub fn default() -> Self { pub fn default() -> Self {
extern "C" { extern "C" {
@ -55,21 +56,25 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
Self { Self {
boot: embassy_boot::BootLoader::new(active, dfu, state), boot: embassy_boot::BootLoader::new(active, dfu, state),
magic: AlignedBuffer([0; WRITE_SIZE]),
page: AlignedBuffer([0; PAGE_SIZE]),
} }
} }
/// Boots the application /// Inspect the bootloader state and perform actions required before booting, such as swapping
pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize /// firmware.
where pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
[(); <<F as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, match self.boot.prepare_boot(flash, self.magic.as_mut(), self.page.as_mut()) {
[(); <<F as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
{
match self.boot.prepare_boot(flash) {
Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"), Err(_) => panic!("boot prepare error!"),
} }
} }
/// Boots the application.
///
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! { pub unsafe fn load(&mut self, start: usize) -> ! {
trace!("Loading app at 0x{:x}", start); trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)] #[allow(unused_mut)]

View File

@ -36,7 +36,8 @@ async fn main(_spawner: Spawner) {
updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap(); updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap();
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut nvmc).await.unwrap(); let mut magic = [0; 4];
updater.mark_updated(&mut nvmc, &mut magic).await.unwrap();
led.set_high(); led.set_high();
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();
} }

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use panic_reset as _; use panic_reset as _;
@ -35,7 +35,8 @@ async fn main(_spawner: Spawner) {
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
led.set_low(); led.set_low();
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();
} }

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use panic_reset as _; use panic_reset as _;
@ -35,7 +35,8 @@ async fn main(_spawner: Spawner) {
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
led.set_low(); led.set_low();
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();
} }

View File

@ -4,7 +4,7 @@ name = "embassy-boot-stm32h7-examples"
version = "0.1.0" version = "0.1.0"
[dependencies] [dependencies]
embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync" }
embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] }
embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] }
embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32h743zi", "time-driver-any", "exti"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32h743zi", "time-driver-any", "exti"] }

View File

@ -1,8 +1,9 @@
#!/bin/bash #!/bin/bash
probe-rs-cli erase --chip STM32H743ZITx
mv ../../bootloader/stm32/memory.x ../../bootloader/stm32/memory-old.x mv ../../bootloader/stm32/memory.x ../../bootloader/stm32/memory-old.x
cp memory-bl.x ../../bootloader/stm32/memory.x cp memory-bl.x ../../bootloader/stm32/memory.x
cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32f767zi --chip STM32F767ZITx --target thumbv7em-none-eabihf cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32h743zi --chip STM32H743ZITx --target thumbv7em-none-eabihf
rm ../../bootloader/stm32/memory.x rm ../../bootloader/stm32/memory.x
mv ../../bootloader/stm32/memory-old.x ../../bootloader/stm32/memory.x mv ../../bootloader/stm32/memory-old.x ../../bootloader/stm32/memory.x

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use panic_reset as _; use panic_reset as _;
@ -29,13 +29,17 @@ async fn main(_spawner: Spawner) {
let mut updater = FirmwareUpdater::default(); let mut updater = FirmwareUpdater::default();
button.wait_for_rising_edge().await; button.wait_for_rising_edge().await;
let mut offset = 0; let mut offset = 0;
let mut buf: [u8; 128 * 1024] = [0; 128 * 1024]; let mut buf = AlignedBuffer([0; 128 * 1024]);
for chunk in APP_B.chunks(128 * 1024) { for chunk in APP_B.chunks(128 * 1024) {
buf[..chunk.len()].copy_from_slice(chunk); buf.as_mut()[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); updater
.write_firmware(offset, buf.as_ref(), &mut flash, 2048)
.await
.unwrap();
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
led.set_low(); led.set_low();
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();
} }

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use panic_reset as _; use panic_reset as _;
@ -38,7 +38,8 @@ async fn main(_spawner: Spawner) {
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
led.set_low(); led.set_low();
Timer::after(Duration::from_secs(1)).await; Timer::after(Duration::from_secs(1)).await;
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use panic_reset as _; use panic_reset as _;
@ -38,7 +38,8 @@ async fn main(_spawner: Spawner) {
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
led.set_low(); led.set_low();
Timer::after(Duration::from_secs(1)).await; Timer::after(Duration::from_secs(1)).await;
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use panic_reset as _; use panic_reset as _;
@ -35,7 +35,8 @@ async fn main(_spawner: Spawner) {
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
led.set_low(); led.set_low();
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();
} }

View File

@ -4,11 +4,11 @@
#[cfg(feature = "defmt-rtt")] #[cfg(feature = "defmt-rtt")]
use defmt_rtt::*; use defmt_rtt::*;
use embassy_boot_stm32::FirmwareUpdater; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput; use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::Flash; use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use panic_reset as _; use panic_reset as _;
@ -37,7 +37,8 @@ async fn main(_spawner: Spawner) {
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
offset += chunk.len(); offset += chunk.len();
} }
updater.update(&mut flash).await.unwrap(); let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
//defmt::info!("Marked as updated"); //defmt::info!("Marked as updated");
led.set_low(); led.set_low();
cortex_m::peripheral::SCB::sys_reset(); cortex_m::peripheral::SCB::sys_reset();

View File

@ -20,10 +20,8 @@ fn main() -> ! {
*/ */
let mut bl = BootLoader::default(); let mut bl = BootLoader::default();
let start = bl.prepare(&mut SingleFlashProvider::new(&mut WatchdogFlash::start( let start = bl.prepare(&mut SingleFlashConfig::new(&mut BootFlash::<_, 4096>::new(
Nvmc::new(p.NVMC), &mut WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, 5),
p.WDT,
5,
))); )));
unsafe { bl.load(start) } unsafe { bl.load(start) }
} }

View File

@ -5,7 +5,7 @@ use cortex_m_rt::{entry, exception};
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
use defmt_rtt as _; use defmt_rtt as _;
use embassy_boot_stm32::*; use embassy_boot_stm32::*;
use embassy_stm32::flash::{Flash, ERASE_SIZE}; use embassy_stm32::flash::{Flash, ERASE_SIZE, ERASE_VALUE, WRITE_SIZE};
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
@ -19,9 +19,11 @@ fn main() -> ! {
} }
*/ */
let mut bl: BootLoader<ERASE_SIZE> = BootLoader::default(); let mut bl: BootLoader<ERASE_SIZE, WRITE_SIZE> = BootLoader::default();
let mut flash = Flash::unlock(p.FLASH); let mut flash = Flash::unlock(p.FLASH);
let start = bl.prepare(&mut SingleFlashProvider::new(&mut flash)); let start = bl.prepare(&mut SingleFlashConfig::new(
&mut BootFlash::<_, ERASE_SIZE, ERASE_VALUE>::new(&mut flash),
));
core::mem::drop(flash); core::mem::drop(flash);
unsafe { bl.load(start) } unsafe { bl.load(start) }
} }