Merge pull request #1498 from rmja/remove-bootloader-partitions

Remove bootloader partitions
This commit is contained in:
Dario Nieuwenhuis 2023-05-30 20:08:01 +00:00 committed by GitHub
commit f5d0d28ac3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 929 additions and 994 deletions

View File

@ -27,9 +27,10 @@ defmt = { version = "0.3", optional = true }
digest = "0.10"
log = { version = "0.4", optional = true }
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.4.0", optional = true}
embedded-storage-async = { version = "0.4.0", optional = true }
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
signature = { version = "1.6.4", default-features = false }
@ -39,6 +40,7 @@ env_logger = "0.9"
rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
futures = { version = "0.3", features = ["executor"] }
sha1 = "0.10.5"
critical-section = { version = "1.1.1", features = ["std"] }
[dev-dependencies.ed25519-dalek]
default_features = false
@ -48,7 +50,7 @@ features = ["rand", "std", "u32_backend"]
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
ed25519-salty = ["dep:salty", "_verify"]
nightly = ["dep:embedded-storage-async"]
nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"]
#Internal features
_verify = []

View File

@ -1,6 +1,11 @@
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
use core::cell::RefCell;
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
use embassy_embedded_hal::flash::partition::BlockingPartition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
/// Errors returned by bootloader
#[derive(PartialEq, Eq, Debug)]
@ -30,63 +35,96 @@ where
}
}
/// Trait defining the flash handles used for active and DFU partition.
pub trait FlashConfig {
/// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
const STATE_ERASE_VALUE: u8 = 0xFF;
/// Bootloader flash configuration holding the three flashes used by the bootloader
///
/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use.
/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition
/// the provided flash according to symbols defined in the linkerfile.
pub struct BootLoaderConfig<ACTIVE, DFU, STATE> {
/// Flash type used for the active partition - the partition which will be booted from.
pub active: ACTIVE,
/// Flash type used for the dfu partition - the partition which will be swapped in when requested.
pub dfu: DFU,
/// Flash type used for the state partition.
type STATE: NorFlash;
/// Flash type used for the active partition.
type ACTIVE: NorFlash;
/// Flash type used for the dfu partition.
type DFU: NorFlash;
/// Return flash instance used to write/read to/from active partition.
fn active(&mut self) -> &mut Self::ACTIVE;
/// Return flash instance used to write/read to/from dfu partition.
fn dfu(&mut self) -> &mut Self::DFU;
/// Return flash instance used to write/read to/from bootloader state.
fn state(&mut self) -> &mut Self::STATE;
pub state: STATE,
}
trait FlashConfigEx {
fn page_size() -> u32;
}
impl<'a, FLASH: NorFlash>
BootLoaderConfig<
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, FLASH>,
>
{
/// Create a bootloader config from the flash and address symbols defined in the linkerfile
// #[cfg(target_os = "none")]
pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
impl<T: FlashConfig> FlashConfigEx for T {
/// Get the page size which is the "unit of operation" within the bootloader.
fn page_size() -> u32 {
core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32
let active = unsafe {
let start = &__bootloader_active_start as *const u32 as u32;
let end = &__bootloader_active_end as *const u32 as u32;
trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
Self { active, dfu, state }
}
}
/// BootLoader works with any flash implementing embedded_storage.
pub struct BootLoader {
// Page with current state of bootloader. The state partition has the following format:
// All ranges are in multiples of WRITE_SIZE bytes.
// | Range | Description |
// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
// | 2..2 + N | Progress index used while swapping or reverting |
state: Partition,
// Location of the partition which will be booted from
active: Partition,
// Location of the partition which will be swapped in when requested
dfu: Partition,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> {
active: ACTIVE,
dfu: DFU,
/// The state partition has the following format:
/// All ranges are in multiples of WRITE_SIZE bytes.
/// | Range | Description |
/// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
/// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
/// | 2..2 + N | Progress index used while swapping or reverting
state: STATE,
}
impl BootLoader {
/// Create a new instance of a bootloader with the given partitions.
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> {
/// Get the page size which is the "unit of operation" within the bootloader.
const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE {
ACTIVE::ERASE_SIZE as u32
} else {
DFU::ERASE_SIZE as u32
};
/// Create a new instance of a bootloader with the flash partitions.
///
/// - 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 {
Self { active, dfu, state }
}
/// Return the offset of the active partition into the active flash.
pub fn boot_address(&self) -> usize {
self.active.from as usize
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
active: config.active,
dfu: config.dfu,
state: config.state,
}
}
/// Perform necessary boot preparations like swapping images.
@ -175,195 +213,174 @@ impl BootLoader {
/// | DFU | 3 | 3 | 2 | 1 | 3 |
/// +-----------+--------------+--------+--------+--------+--------+
///
pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
// Ensure we have enough progress pages to store copy progress
assert_eq!(0, P::page_size() % aligned_buf.len() as u32);
assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32);
assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32);
assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32);
assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32);
assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32);
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32);
assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32);
assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32);
assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
// Copy contents from partition N to active
let state = self.read_state(p, aligned_buf)?;
let state = self.read_state(aligned_buf)?;
if state == State::Swap {
//
// Check if we already swapped. If we're in the swap state, this means we should revert
// since the app has failed to mark boot as successful
//
if !self.is_swapped(p, aligned_buf)? {
if !self.is_swapped(aligned_buf)? {
trace!("Swapping");
self.swap(p, aligned_buf)?;
self.swap(aligned_buf)?;
trace!("Swapping done");
} else {
trace!("Reverting");
self.revert(p, aligned_buf)?;
self.revert(aligned_buf)?;
let state_flash = p.state();
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
// Invalidate progress
state_word.fill(!P::STATE_ERASE_VALUE);
self.state
.write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
state_word.fill(!STATE_ERASE_VALUE);
self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
// Clear magic and progress
self.state.wipe_blocking(state_flash)?;
self.state.erase(0, self.state.capacity() as u32)?;
// Set magic
state_word.fill(BOOT_MAGIC);
self.state.write_blocking(state_flash, 0, state_word)?;
self.state.write(0, state_word)?;
}
}
Ok(state)
}
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
let page_count = (self.active.size() / P::page_size()) as usize;
let progress = self.current_progress(p, aligned_buf)?;
fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
let page_count = self.active.capacity() / Self::PAGE_SIZE as usize;
let progress = self.current_progress(aligned_buf)?;
Ok(progress >= page_count * 2)
}
fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
let write_size = P::STATE::WRITE_SIZE as u32;
let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize;
let state_flash = config.state();
fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
let write_size = STATE::WRITE_SIZE as u32;
let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2;
let state_word = &mut aligned_buf[..write_size as usize];
self.state.read_blocking(state_flash, write_size, state_word)?;
if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
self.state.read(write_size, state_word)?;
if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) {
// Progress is invalid
return Ok(max_index);
}
for index in 0..max_index {
self.state
.read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
self.state.read((2 + index) as u32 * write_size, state_word)?;
if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
return Ok(index);
}
}
Ok(max_index)
}
fn update_progress<P: FlashConfig>(
&mut self,
progress_index: usize,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
state_word.fill(!P::STATE_ERASE_VALUE);
self.state.write_blocking(
p.state(),
(2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32,
state_word,
)?;
fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
state_word.fill(!STATE_ERASE_VALUE);
self.state
.write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?;
Ok(())
}
fn copy_page_once_to_active<P: FlashConfig>(
fn copy_page_once_to_active(
&mut self,
progress_index: usize,
from_offset: u32,
to_offset: u32,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= progress_index {
let page_size = P::page_size() as u32;
if self.current_progress(aligned_buf)? <= progress_index {
let page_size = Self::PAGE_SIZE as u32;
self.active
.erase_blocking(p.active(), to_offset, to_offset + page_size)?;
self.active.erase(to_offset, to_offset + page_size)?;
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
self.dfu
.read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
self.active
.write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?;
self.active.write(to_offset + offset_in_page as u32, aligned_buf)?;
}
self.update_progress(progress_index, p, aligned_buf)?;
self.update_progress(progress_index, aligned_buf)?;
}
Ok(())
}
fn copy_page_once_to_dfu<P: FlashConfig>(
fn copy_page_once_to_dfu(
&mut self,
progress_index: usize,
from_offset: u32,
to_offset: u32,
p: &mut P,
aligned_buf: &mut [u8],
) -> Result<(), BootError> {
if self.current_progress(p, aligned_buf)? <= progress_index {
let page_size = P::page_size() as u32;
if self.current_progress(aligned_buf)? <= progress_index {
let page_size = Self::PAGE_SIZE as u32;
self.dfu
.erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
self.dfu.erase(to_offset as u32, to_offset + page_size)?;
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
self.active
.read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
self.dfu
.write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
self.active.read(from_offset + offset_in_page as u32, aligned_buf)?;
self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?;
}
self.update_progress(progress_index, p, aligned_buf)?;
self.update_progress(progress_index, aligned_buf)?;
}
Ok(())
}
fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_size = P::page_size();
let page_count = self.active.size() / page_size;
fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
for page_num in 0..page_count {
let progress_index = (page_num * 2) as usize;
// Copy active page to the 'next' DFU page.
let active_from_offset = (page_count - 1 - page_num) * page_size;
let dfu_to_offset = (page_count - page_num) * page_size;
let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE;
//trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
// Copy DFU page to the active page
let active_to_offset = (page_count - 1 - page_num) * page_size;
let dfu_from_offset = (page_count - 1 - page_num) * page_size;
let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
//trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
}
Ok(())
}
fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_size = P::page_size();
let page_count = self.active.size() / page_size;
fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
for page_num in 0..page_count {
let progress_index = (page_count * 2 + page_num * 2) as usize;
// Copy the bad active page to the DFU page
let active_from_offset = page_num * page_size;
let dfu_to_offset = page_num * page_size;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
let active_from_offset = page_num * Self::PAGE_SIZE;
let dfu_to_offset = page_num * Self::PAGE_SIZE;
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
// Copy the DFU page back to the active page
let active_to_offset = page_num * page_size;
let dfu_from_offset = (page_num + 1) * page_size;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
let active_to_offset = page_num * Self::PAGE_SIZE;
let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE;
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
}
Ok(())
}
fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
self.state.read_blocking(config.state(), 0, state_word)?;
fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
self.state.read(0, state_word)?;
if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
@ -373,161 +390,32 @@ impl BootLoader {
}
}
fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) {
assert_eq!(active.size() % page_size, 0);
assert_eq!(dfu.size() % page_size, 0);
assert!(dfu.size() - active.size() >= page_size);
assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32);
}
/// A flash wrapper implementing the Flash and embedded_storage traits.
pub struct BootFlash<F>
where
F: NorFlash,
{
flash: F,
}
impl<F> BootFlash<F>
where
F: NorFlash,
{
/// Create a new instance of a bootable flash
pub fn new(flash: F) -> Self {
Self { flash }
}
}
impl<F> ErrorType for BootFlash<F>
where
F: NorFlash,
{
type Error = F::Error;
}
impl<F> NorFlash for BootFlash<F>
where
F: NorFlash,
{
const WRITE_SIZE: usize = F::WRITE_SIZE;
const ERASE_SIZE: usize = F::ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
F::erase(&mut self.flash, from, to)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
F::write(&mut self.flash, offset, bytes)
}
}
impl<F> ReadNorFlash for BootFlash<F>
where
F: NorFlash,
{
const READ_SIZE: usize = F::READ_SIZE;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
F::read(&mut self.flash, offset, bytes)
}
fn capacity(&self) -> usize {
F::capacity(&self.flash)
}
}
/// Convenience provider that uses a single flash for all partitions.
pub struct SingleFlashConfig<'a, F>
where
F: NorFlash,
{
flash: &'a mut F,
}
impl<'a, F> SingleFlashConfig<'a, F>
where
F: NorFlash,
{
/// Create a provider for a single flash.
pub fn new(flash: &'a mut F) -> Self {
Self { flash }
}
}
impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
where
F: NorFlash,
{
type STATE = F;
type ACTIVE = F;
type DFU = F;
fn active(&mut self) -> &mut Self::STATE {
self.flash
}
fn dfu(&mut self) -> &mut Self::ACTIVE {
self.flash
}
fn state(&mut self) -> &mut Self::DFU {
self.flash
}
}
/// Convenience flash provider that uses separate flash instances for each partition.
pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
active: &'a mut ACTIVE,
state: &'a mut STATE,
dfu: &'a mut DFU,
}
impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
/// Create a new flash provider with separate configuration for all three partitions.
pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
Self { active, state, dfu }
}
}
impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
where
ACTIVE: NorFlash,
STATE: NorFlash,
DFU: NorFlash,
{
type STATE = STATE;
type ACTIVE = ACTIVE;
type DFU = DFU;
fn active(&mut self) -> &mut Self::ACTIVE {
self.active
}
fn dfu(&mut self) -> &mut Self::DFU {
self.dfu
}
fn state(&mut self) -> &mut Self::STATE {
self.state
}
fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
active: &ACTIVE,
dfu: &DFU,
state: &STATE,
page_size: u32,
) {
assert_eq!(active.capacity() as u32 % page_size, 0);
assert_eq!(dfu.capacity() as u32 % page_size, 0);
assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size);
assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mem_flash::MemFlash;
#[test]
#[should_panic]
fn test_range_asserts() {
const ACTIVE: Partition = Partition::new(4096, 4194304);
const DFU: Partition = Partition::new(4194304, 2 * 4194304);
const STATE: Partition = Partition::new(0, 4096);
assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
const ACTIVE_SIZE: usize = 4194304 - 4096;
const DFU_SIZE: usize = 4194304;
const STATE_SIZE: usize = 4096;
static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF);
static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF);
static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF);
assert_partitions(&ACTIVE, &DFU, &STATE, 4096);
}
}

View File

@ -1,20 +1,68 @@
use digest::Digest;
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
#[cfg(target_os = "none")]
use embassy_embedded_hal::flash::partition::Partition;
#[cfg(target_os = "none")]
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embedded_storage_async::nor_flash::NorFlash;
use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
use super::FirmwareUpdaterConfig;
use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
dfu: DFU,
state: STATE,
}
#[cfg(target_os = "none")]
impl<'a, FLASH: NorFlash>
FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>>
{
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
Partition::new(flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
Partition::new(flash, start, end - start)
};
Self { dfu, state }
}
}
impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
Self {
dfu: config.dfu,
state: config.state,
}
}
impl FirmwareUpdater {
/// Obtain the current state.
///
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
pub async fn get_state<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
self.state.read(state_flash, 0, aligned).await?;
pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, aligned).await?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
@ -37,19 +85,18 @@ impl FirmwareUpdater {
///
/// # Safety
///
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(all(feature = "_verify", feature = "nightly"))]
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
#[cfg(feature = "_verify")]
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());
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
assert!(_update_len <= self.dfu.capacity() as u32);
#[cfg(feature = "ed25519-dalek")]
{
@ -63,8 +110,7 @@ impl FirmwareUpdater {
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?;
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
@ -85,8 +131,7 @@ impl FirmwareUpdater {
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?;
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
let r = public_key.verify(&message, &signature);
trace!(
@ -99,20 +144,19 @@ impl FirmwareUpdater {
r.map_err(into_signature_error)?
}
self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
self.set_magic(_aligned, SWAP_MAGIC).await
}
/// Verify the update in DFU with any digest.
pub async fn hash<F: AsyncNorFlash, D: Digest>(
pub async fn hash<D: Digest>(
&mut self,
dfu_flash: &mut F,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read(dfu_flash, offset, chunk_buf).await?;
self.dfu.read(offset, chunk_buf).await?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
@ -124,60 +168,44 @@ impl FirmwareUpdater {
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
pub async fn mark_updated<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC, state_flash).await
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC).await
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub async fn mark_booted<F: AsyncNorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC).await
}
async fn set_magic<F: AsyncNorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
state_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
self.state.read(state_flash, 0, aligned).await?;
async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
self.state.read(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;
self.state.read(STATE::WRITE_SIZE as u32, aligned).await?;
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?;
self.state.write(STATE::WRITE_SIZE as u32, aligned).await?;
}
// Clear magic and progress
self.state.wipe(state_flash).await?;
self.state.erase(0, self.state.capacity() as u32).await?;
// Set magic
aligned.fill(magic);
self.state.write(state_flash, 0, aligned).await?;
self.state.write(0, aligned).await?;
}
Ok(())
}
@ -189,19 +217,12 @@ impl FirmwareUpdater {
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
pub async fn write_firmware<F: AsyncNorFlash>(
&mut self,
offset: usize,
data: &[u8],
dfu_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= DFU::ERASE_SIZE);
self.dfu
.erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
.await?;
self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
self.dfu.write(dfu_flash, offset as u32, data).await?;
self.dfu.write(offset as u32, data).await?;
Ok(())
}
@ -211,18 +232,18 @@ impl FirmwareUpdater {
///
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
pub async fn prepare_update<F: AsyncNorFlash>(
&mut self,
dfu_flash: &mut F,
) -> Result<Partition, FirmwareUpdaterError> {
self.dfu.wipe(dfu_flash).await?;
pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
self.dfu.erase(0, self.dfu.capacity() as u32).await?;
Ok(self.dfu)
Ok(&mut self.dfu)
}
}
#[cfg(test)]
mod tests {
use embassy_embedded_hal::flash::partition::Partition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::mutex::Mutex;
use futures::executor::block_on;
use sha1::{Digest, Sha1};
@ -231,20 +252,19 @@ mod tests {
#[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 flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
let state = Partition::new(&flash, 0, 4096);
let dfu = Partition::new(&flash, 65536, 65536);
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 updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
block_on(updater.write_firmware(0, to_write.as_slice())).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
assert_eq!(Sha1::digest(update).as_slice(), hash);
}

View File

@ -1,25 +1,70 @@
use digest::Digest;
#[cfg(target_os = "none")]
use embassy_embedded_hal::flash::partition::BlockingPartition;
#[cfg(target_os = "none")]
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embedded_storage::nor_flash::NorFlash;
use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
use super::FirmwareUpdaterConfig;
use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
dfu: DFU,
state: STATE,
}
#[cfg(target_os = "none")]
impl<'a, FLASH: NorFlash>
FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>>
{
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
pub fn from_linkerfile_blocking(
flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>,
) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
};
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 }
}
}
impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
/// Create a firmware updater instance with partition ranges for the update and state partitions.
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
Self {
dfu: config.dfu,
state: config.state,
}
}
/// Obtain the current state.
///
/// This is useful to check if the bootloader has just done a swap, in order
/// to do verifications and self-tests of the new image before calling
/// `mark_booted`.
pub fn get_state_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<State, FirmwareUpdaterError> {
self.state.read_blocking(state_flash, 0, aligned)?;
pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, aligned)?;
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
@ -42,19 +87,18 @@ impl FirmwareUpdater {
///
/// # Safety
///
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
/// and written to.
#[cfg(feature = "_verify")]
pub fn verify_and_mark_updated_blocking<F: NorFlash>(
pub 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());
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
assert!(_update_len <= self.dfu.capacity() as u32);
#[cfg(feature = "ed25519-dalek")]
{
@ -68,7 +112,7 @@ impl FirmwareUpdater {
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)?;
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
}
@ -89,7 +133,7 @@ impl FirmwareUpdater {
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)?;
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
let r = public_key.verify(&message, &signature);
trace!(
@ -102,20 +146,19 @@ impl FirmwareUpdater {
r.map_err(into_signature_error)?
}
self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
self.set_magic(_aligned, SWAP_MAGIC)
}
/// Verify the update in DFU with any digest.
pub fn hash_blocking<F: NorFlash, D: Digest>(
pub fn hash<D: Digest>(
&mut self,
dfu_flash: &mut F,
update_len: u32,
chunk_buf: &mut [u8],
output: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
let mut digest = D::new();
for offset in (0..update_len).step_by(chunk_buf.len()) {
self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
self.dfu.read(offset, chunk_buf)?;
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
digest.update(&chunk_buf[..len]);
}
@ -127,60 +170,44 @@ impl FirmwareUpdater {
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
#[cfg(not(feature = "_verify"))]
pub fn mark_updated_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, SWAP_MAGIC)
}
/// Mark firmware boot successful and stop rollback on reset.
///
/// # Safety
///
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub fn mark_booted_blocking<F: NorFlash>(
&mut self,
state_flash: &mut F,
aligned: &mut [u8],
) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), F::WRITE_SIZE);
self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
/// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
self.set_magic(aligned, BOOT_MAGIC)
}
fn set_magic_blocking<F: NorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
state_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
self.state.read_blocking(state_flash, 0, aligned)?;
fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
self.state.read(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;
self.state.read(STATE::WRITE_SIZE as u32, aligned)?;
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)?;
self.state.write(STATE::WRITE_SIZE as u32, aligned)?;
}
// Clear magic and progress
self.state.wipe_blocking(state_flash)?;
self.state.erase(0, self.state.capacity() as u32)?;
// Set magic
aligned.fill(magic);
self.state.write_blocking(state_flash, 0, aligned)?;
self.state.write(0, aligned)?;
}
Ok(())
}
@ -192,18 +219,12 @@ impl FirmwareUpdater {
/// # Safety
///
/// Failing to meet alignment and size requirements may result in a panic.
pub fn write_firmware_blocking<F: NorFlash>(
&mut self,
offset: usize,
data: &[u8],
dfu_flash: &mut F,
) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= F::ERASE_SIZE);
pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
assert!(data.len() >= DFU::ERASE_SIZE);
self.dfu
.erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
self.dfu.write(offset as u32, data)?;
Ok(())
}
@ -211,11 +232,45 @@ impl FirmwareUpdater {
/// Prepare for an incoming DFU update by erasing the entire DFU area and
/// returning its `Partition`.
///
/// Using this instead of `write_firmware_blocking` allows for an optimized
/// API in exchange for added complexity.
pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
self.dfu.wipe_blocking(flash)?;
/// Using this instead of `write_firmware` allows for an optimized API in
/// exchange for added complexity.
pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
self.dfu.erase(0, self.dfu.capacity() as u32)?;
Ok(self.dfu)
Ok(&mut self.dfu)
}
}
#[cfg(test)]
mod tests {
use core::cell::RefCell;
use embassy_embedded_hal::flash::partition::BlockingPartition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use sha1::{Digest, Sha1};
use super::*;
use crate::mem_flash::MemFlash;
#[test]
fn can_verify_sha1() {
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
let state = BlockingPartition::new(&flash, 0, 4096);
let dfu = BlockingPartition::new(&flash, 65536, 65536);
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let mut to_write = [0; 4096];
to_write[..7].copy_from_slice(update.as_slice());
let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
updater.write_firmware(0, to_write.as_slice()).unwrap();
let mut chunk_buf = [0; 2];
let mut hash = [0; 20];
updater
.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
.unwrap();
assert_eq!(Sha1::digest(update).as_slice(), hash);
}
}

View File

@ -2,9 +2,22 @@
mod asynch;
mod blocking;
#[cfg(feature = "nightly")]
pub use asynch::FirmwareUpdater;
pub use blocking::BlockingFirmwareUpdater;
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
use crate::Partition;
/// Firmware updater flash configuration holding the two flashes used by the updater
///
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
/// the provided flash according to symbols defined in the linkerfile.
pub struct FirmwareUpdaterConfig<DFU, STATE> {
/// The dfu flash partition
pub dfu: DFU,
/// The state flash partition
pub state: STATE,
}
/// Errors returned by FirmwareUpdater
#[derive(Debug)]
@ -33,39 +46,3 @@ where
FirmwareUpdaterError::Flash(error.kind())
}
}
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
/// 'mess up' the internal bootloader state
pub struct FirmwareUpdater {
state: Partition,
dfu: Partition,
}
#[cfg(target_os = "none")]
impl Default for FirmwareUpdater {
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
FirmwareUpdater::new(dfu, state)
}
}

View File

@ -7,12 +7,18 @@ mod fmt;
mod boot_loader;
mod digest_adapters;
mod firmware_updater;
#[cfg(test)]
mod mem_flash;
mod partition;
#[cfg(test)]
mod test_flash;
pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig};
pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError};
pub use partition::Partition;
// The expected value of the flash after an erase
// TODO: Use the value provided by NorFlash when available
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
#[cfg(feature = "nightly")]
pub use firmware_updater::FirmwareUpdater;
pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
@ -45,10 +51,18 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
#[cfg(test)]
mod tests {
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
use futures::executor::block_on;
use super::*;
use crate::boot_loader::BootLoaderConfig;
use crate::firmware_updater::FirmwareUpdaterConfig;
use crate::mem_flash::MemFlash;
#[cfg(feature = "nightly")]
use crate::test_flash::AsyncTestFlash;
use crate::test_flash::BlockingTestFlash;
/*
#[test]
@ -67,147 +81,173 @@ mod tests {
#[test]
fn test_boot_state() {
const STATE: Partition = Partition::new(0, 4096);
const ACTIVE: Partition = Partition::new(4096, 61440);
const DFU: Partition = Partition::new(61440, 122880);
let flash = BlockingTestFlash::new(BootLoaderConfig {
active: MemFlash::<57344, 4096, 4>::default(),
dfu: MemFlash::<61440, 4096, 4>::default(),
state: MemFlash::<4096, 4096, 4>::default(),
});
let mut flash = MemFlash::<131072, 4096, 4>::default();
flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
let mut flash = SingleFlashConfig::new(&mut flash);
flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
let mut page = [0; 4096];
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap());
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
}
#[test]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_swap_state() {
const STATE: Partition = Partition::new(0, 4096);
const ACTIVE: Partition = Partition::new(4096, 61440);
const DFU: Partition = Partition::new(61440, 122880);
let mut flash = MemFlash::<131072, 4096, 4>::random();
const FIRMWARE_SIZE: usize = 57344;
let flash = AsyncTestFlash::new(BootLoaderConfig {
active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(),
dfu: MemFlash::<61440, 4096, 4>::default(),
state: MemFlash::<4096, 4096, 4>::default(),
});
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
let mut aligned = [0; 4];
flash.program(ACTIVE.from, &original).unwrap();
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut updater = FirmwareUpdater::new(DFU, STATE);
block_on(updater.write_firmware(0, &update, &mut flash)).unwrap();
block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
block_on(updater.mark_updated(&mut aligned)).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
let mut page = [0; 1024];
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
.unwrap()
);
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
flash.assert_eq(ACTIVE.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// First DFU page is untouched
flash.assert_eq(DFU.from + 4096, &original);
flash.dfu().read(4096, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
// Running again should cause a revert
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
.unwrap()
);
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
flash.assert_eq(ACTIVE.from, &original);
// Last page is untouched
flash.assert_eq(DFU.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
// Last DFU page is untouched
flash.dfu().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// Mark as booted
block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap();
assert_eq!(
State::Boot,
bootloader
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
.unwrap()
);
let flash = flash.into_async();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.mark_booted(&mut aligned)).unwrap();
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
}
#[test]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_separate_flash_active_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
const DFU: Partition = Partition::new(0, 16384);
fn test_swap_state_active_page_biggest() {
const FIRMWARE_SIZE: usize = 12288;
let flash = AsyncTestFlash::new(BootLoaderConfig {
active: MemFlash::<12288, 4096, 8>::random(),
dfu: MemFlash::<16384, 2048, 8>::random(),
state: MemFlash::<2048, 128, 4>::random(),
});
let mut active = MemFlash::<16384, 4096, 8>::random();
let mut dfu = MemFlash::<16384, 2048, 8>::random();
let mut state = MemFlash::<4096, 128, 4>::random();
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
let mut aligned = [0; 4];
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
active.program(ACTIVE.from, &original).unwrap();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
block_on(updater.mark_updated(&mut aligned)).unwrap();
let mut updater = FirmwareUpdater::new(DFU, STATE);
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let mut page = [0; 4096];
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page)
.unwrap()
);
active.assert_eq(ACTIVE.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// First DFU page is untouched
dfu.assert_eq(DFU.from + 4096, &original);
flash.dfu().read(4096, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
}
#[test]
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
fn test_separate_flash_dfu_page_biggest() {
const STATE: Partition = Partition::new(2048, 4096);
const ACTIVE: Partition = Partition::new(4096, 16384);
const DFU: Partition = Partition::new(0, 16384);
fn test_swap_state_dfu_page_biggest() {
const FIRMWARE_SIZE: usize = 12288;
let flash = AsyncTestFlash::new(BootLoaderConfig {
active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(),
dfu: MemFlash::<16384, 4096, 8>::random(),
state: MemFlash::<2048, 128, 4>::random(),
});
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
let mut aligned = [0; 4];
let mut active = MemFlash::<16384, 2048, 4>::random();
let mut dfu = MemFlash::<16384, 4096, 8>::random();
let mut state = MemFlash::<4096, 128, 4>::random();
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
active.program(ACTIVE.from, &original).unwrap();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
block_on(updater.write_firmware(0, &UPDATE)).unwrap();
block_on(updater.mark_updated(&mut aligned)).unwrap();
let mut updater = FirmwareUpdater::new(DFU, STATE);
block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
let flash = flash.into_blocking();
let mut bootloader = BootLoader::new(BootLoaderConfig {
active: flash.active(),
dfu: flash.dfu(),
state: flash.state(),
});
let mut page = [0; 4096];
assert_eq!(
State::Swap,
bootloader
.prepare_boot(
&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,),
&mut page
)
.unwrap()
);
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
active.assert_eq(ACTIVE.from, &update);
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(UPDATE, read_buf);
// First DFU page is untouched
dfu.assert_eq(DFU.from + 4096, &original);
flash.dfu().read(4096, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);
}
#[test]
@ -233,25 +273,28 @@ mod tests {
let public_key: PublicKey = keypair.public;
// Setup flash
const STATE: Partition = Partition::new(0, 4096);
const DFU: Partition = Partition::new(4096, 8192);
let mut flash = MemFlash::<8192, 4096, 4>::default();
let flash = BlockingTestFlash::new(BootLoaderConfig {
active: MemFlash::<0, 0, 0>::default(),
dfu: MemFlash::<4096, 4096, 4>::default(),
state: MemFlash::<4096, 4096, 4>::default(),
});
let firmware_len = firmware.len();
let mut write_buf = [0; 4096];
write_buf[0..firmware_len].copy_from_slice(firmware);
DFU.write_blocking(&mut flash, 0, &write_buf).unwrap();
flash.dfu().write(0, &write_buf).unwrap();
// On with the test
let mut updater = FirmwareUpdater::new(DFU, STATE);
let flash = flash.into_async();
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
dfu: flash.dfu(),
state: flash.state(),
});
let mut aligned = [0; 4];
assert!(block_on(updater.verify_and_mark_updated(
&mut flash,
&public_key.to_bytes(),
&signature.to_bytes(),
firmware_len as u32,

View File

@ -34,6 +34,52 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
}
}
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> {
let len = bytes.len();
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
Ok(())
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
assert!(offset % WRITE_SIZE == 0);
assert!(offset + bytes.len() <= SIZE);
if let Some(pending_successes) = self.pending_write_successes {
if pending_successes > 0 {
self.pending_write_successes = Some(pending_successes - 1);
} else {
return Err(MemFlashError);
}
}
for ((offset, mem_byte), new_byte) in self
.mem
.iter_mut()
.enumerate()
.skip(offset)
.take(bytes.len())
.zip(bytes)
{
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
*mem_byte = *new_byte;
}
Ok(())
}
fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> {
let from = from as usize;
let to = to as usize;
assert!(from % ERASE_SIZE == 0);
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
for i in from..to {
self.mem[i] = 0xFF;
}
Ok(())
}
pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
@ -44,12 +90,6 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
Ok(())
}
pub fn assert_eq(&self, offset: u32, expectation: &[u8]) {
for i in 0..expectation.len() {
assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i);
}
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
@ -78,9 +118,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNo
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let len = bytes.len();
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
Ok(())
self.read(offset, bytes)
}
fn capacity(&self) -> usize {
@ -94,44 +132,12 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
let from = from as usize;
let to = to as usize;
assert!(from % ERASE_SIZE == 0);
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
for i in from..to {
self.mem[i] = 0xFF;
}
Ok(())
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.write(offset, bytes)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
let offset = offset as usize;
assert!(bytes.len() % WRITE_SIZE == 0);
assert!(offset % WRITE_SIZE == 0);
assert!(offset + bytes.len() <= SIZE);
if let Some(pending_successes) = self.pending_write_successes {
if pending_successes > 0 {
self.pending_write_successes = Some(pending_successes - 1);
} else {
return Err(MemFlashError);
}
}
for ((offset, mem_byte), new_byte) in self
.mem
.iter_mut()
.enumerate()
.skip(offset)
.take(bytes.len())
.zip(bytes)
{
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
*mem_byte = *new_byte;
}
Ok(())
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.erase(from, to)
}
}
@ -142,11 +148,11 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncR
const READ_SIZE: usize = 1;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
<Self as ReadNorFlash>::read(self, offset, bytes)
self.read(offset, bytes)
}
fn capacity(&self) -> usize {
<Self as ReadNorFlash>::capacity(self)
SIZE
}
}
@ -157,11 +163,11 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncN
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
<Self as NorFlash>::erase(self, from, to)
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.write(offset, bytes)
}
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
<Self as NorFlash>::write(self, offset, bytes)
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.erase(from, to)
}
}

View File

@ -1,144 +0,0 @@
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
#[cfg(feature = "nightly")]
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
/// A region in flash used by the bootloader.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Partition {
/// The offset into the flash where the partition starts.
pub from: u32,
/// The offset into the flash where the partition ends.
pub to: u32,
}
impl Partition {
/// Create a new partition with the provided range
pub const fn new(from: u32, to: u32) -> Self {
Self { from, to }
}
/// Return the size of the partition
pub const fn size(&self) -> u32 {
self.to - self.from
}
/// Read from the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn read<F: AsyncReadNorFlash>(
&self,
flash: &mut F,
offset: u32,
bytes: &mut [u8],
) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.read(offset, bytes).await
}
/// Write to the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.write(offset, bytes).await?;
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
Ok(())
}
/// Erase part of the partition on the provided flash
#[cfg(feature = "nightly")]
pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
let from = self.from as u32 + from;
let to = self.from as u32 + to;
flash.erase(from, to).await?;
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Erase the entire partition
#[cfg(feature = "nightly")]
pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
let from = self.from as u32;
let to = self.to as u32;
flash.erase(from, to).await?;
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Read from the partition on the provided flash
pub fn read_blocking<F: ReadNorFlash>(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.read(offset, bytes)
}
/// Write to the partition on the provided flash
pub fn write_blocking<F: NorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
let offset = self.from as u32 + offset;
flash.write(offset, bytes)?;
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
Ok(())
}
/// Erase part of the partition on the provided flash
pub fn erase_blocking<F: NorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
let from = self.from as u32 + from;
let to = self.from as u32 + to;
flash.erase(from, to)?;
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
/// Erase the entire partition
pub(crate) fn wipe_blocking<F: NorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
let from = self.from as u32;
let to = self.to as u32;
flash.erase(from, to)?;
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::mem_flash::MemFlash;
use crate::Partition;
#[test]
fn can_erase() {
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
let partition = Partition::new(256, 512);
partition.erase_blocking(&mut flash, 64, 192).unwrap();
for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) {
assert_eq!(0x00, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) {
assert_eq!(0xFF, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) {
assert_eq!(0x00, byte, "Index {}", index);
}
}
#[test]
fn can_wipe() {
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
let partition = Partition::new(256, 512);
partition.wipe_blocking(&mut flash).unwrap();
for (index, byte) in flash.mem.iter().copied().enumerate().take(256) {
assert_eq!(0x00, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) {
assert_eq!(0xFF, byte, "Index {}", index);
}
for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) {
assert_eq!(0x00, byte, "Index {}", index);
}
}
}

View File

@ -0,0 +1,64 @@
use embassy_embedded_hal::flash::partition::Partition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::mutex::Mutex;
use embedded_storage_async::nor_flash::NorFlash;
use crate::BootLoaderConfig;
pub struct AsyncTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
active: Mutex<NoopRawMutex, ACTIVE>,
dfu: Mutex<NoopRawMutex, DFU>,
state: Mutex<NoopRawMutex, STATE>,
}
impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
active: Mutex::new(config.active),
dfu: Mutex::new(config.dfu),
state: Mutex::new(config.state),
}
}
pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> {
Self::create_partition(&self.active)
}
pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> {
Self::create_partition(&self.dfu)
}
pub fn state(&self) -> Partition<NoopRawMutex, STATE> {
Self::create_partition(&self.state)
}
fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> {
Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32)
}
}
impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash,
DFU: NorFlash + embedded_storage::nor_flash::NorFlash,
STATE: NorFlash + embedded_storage::nor_flash::NorFlash,
{
pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> {
let config = BootLoaderConfig {
active: self.active.into_inner(),
dfu: self.dfu.into_inner(),
state: self.state.into_inner(),
};
super::BlockingTestFlash::new(config)
}
}

View File

@ -0,0 +1,69 @@
use core::cell::RefCell;
use embassy_embedded_hal::flash::partition::BlockingPartition;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_storage::nor_flash::NorFlash;
use crate::BootLoaderConfig;
pub struct BlockingTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
active: Mutex<NoopRawMutex, RefCell<ACTIVE>>,
dfu: Mutex<NoopRawMutex, RefCell<DFU>>,
state: Mutex<NoopRawMutex, RefCell<STATE>>,
}
impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash,
DFU: NorFlash,
STATE: NorFlash,
{
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
active: Mutex::new(RefCell::new(config.active)),
dfu: Mutex::new(RefCell::new(config.dfu)),
state: Mutex::new(RefCell::new(config.state)),
}
}
pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> {
Self::create_partition(&self.active)
}
pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> {
Self::create_partition(&self.dfu)
}
pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> {
Self::create_partition(&self.state)
}
pub fn create_partition<T: NorFlash>(
mutex: &Mutex<NoopRawMutex, RefCell<T>>,
) -> BlockingPartition<NoopRawMutex, T> {
BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32)
}
}
#[cfg(feature = "nightly")]
impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
where
ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash,
STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
{
pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> {
let config = BootLoaderConfig {
active: self.active.into_inner().into_inner(),
dfu: self.dfu.into_inner().into_inner(),
state: self.state.into_inner().into_inner(),
};
super::AsyncTestFlash::new(config)
}
}

View File

@ -0,0 +1,7 @@
#[cfg(feature = "nightly")]
mod asynch;
mod blocking;
#[cfg(feature = "nightly")]
pub(crate) use asynch::AsyncTestFlash;
pub(crate) use blocking::BlockingTestFlash;

View File

@ -3,74 +3,37 @@
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
#[cfg(feature = "nightly")]
pub use embassy_boot::FirmwareUpdater;
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig};
use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE};
use embassy_nrf::peripherals::WDT;
use embassy_nrf::wdt;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for nRF devices.
pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> {
boot: embassy_boot::BootLoader,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = PAGE_SIZE> {
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
#[cfg(target_os = "none")]
impl Default for BootLoader<PAGE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
{
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
boot: embassy_boot::BootLoader::new(config),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) {
Ok(_) => self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
pub fn prepare(&mut self) {
self.boot
.prepare_boot(&mut self.aligned_buf.0)
.expect("Boot prepare error");
}
/// Boots the application without softdevice mechanisms.
@ -79,10 +42,12 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(not(feature = "softdevice"))]
pub unsafe fn load(&mut self, start: usize) -> ! {
pub unsafe fn load(self, start: u32) -> ! {
core::mem::drop(self.boot);
let mut p = cortex_m::Peripherals::steal();
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
@ -92,7 +57,7 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
#[cfg(feature = "softdevice")]
pub unsafe fn load(&mut self, _app: usize) -> ! {
pub unsafe fn load(&mut self, _app: u32) -> ! {
use nrf_softdevice_mbr as mbr;
const NRF_SUCCESS: u32 = 0;

View File

@ -3,7 +3,9 @@
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
#[cfg(feature = "nightly")]
pub use embassy_boot::FirmwareUpdater;
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
use embassy_rp::flash::{Flash, ERASE_SIZE};
use embassy_rp::peripherals::{FLASH, WATCHDOG};
use embassy_rp::watchdog::Watchdog;
@ -11,27 +13,28 @@ use embassy_time::Duration;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
/// A bootloader for RP2040 devices.
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
boot: embassy_boot::BootLoader,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = ERASE_SIZE> {
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
{
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
boot: embassy_boot::BootLoader::new(config),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
pub fn prepare(&mut self) {
self.boot
.prepare_boot(self.aligned_buf.as_mut())
.expect("Boot prepare error");
}
/// Boots the application.
@ -39,58 +42,20 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! {
pub unsafe fn load(self, start: u32) -> ! {
core::mem::drop(self.boot);
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
#[cfg(not(armv6m))]
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
}
#[cfg(target_os = "none")]
impl Default for BootLoader<ERASE_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}
/// A flash implementation that will feed a watchdog when touching flash.
pub struct WatchdogFlash<'d, const SIZE: usize> {
flash: Flash<'d, FLASH, SIZE>,

View File

@ -3,30 +3,34 @@
#![doc = include_str!("../README.md")]
mod fmt;
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
#[cfg(feature = "nightly")]
pub use embassy_boot::FirmwareUpdater;
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
use embedded_storage::nor_flash::NorFlash;
/// A bootloader for STM32 devices.
pub struct BootLoader<const BUFFER_SIZE: usize> {
boot: embassy_boot::BootLoader,
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> {
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
}
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
{
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
Self {
boot: embassy_boot::BootLoader::new(active, dfu, state),
boot: embassy_boot::BootLoader::new(config),
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
}
}
/// Inspect the bootloader state and perform actions required before booting, such as swapping
/// firmware.
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(),
Err(_) => panic!("boot prepare error!"),
}
pub fn prepare(&mut self) {
self.boot
.prepare_boot(self.aligned_buf.as_mut())
.expect("Boot prepare error");
}
/// Boots the application.
@ -34,54 +38,16 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
/// # Safety
///
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
pub unsafe fn load(&mut self, start: usize) -> ! {
pub unsafe fn load(self, start: u32) -> ! {
core::mem::drop(self.boot);
trace!("Loading app at 0x{:x}", start);
#[allow(unused_mut)]
let mut p = cortex_m::Peripherals::steal();
#[cfg(not(armv6m))]
p.SCB.invalidate_icache();
p.SCB.vtor.write(start as u32);
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
}
#[cfg(target_os = "none")]
impl<const BUFFER_SIZE: usize> Default for BootLoader<BUFFER_SIZE> {
/// Create a new bootloader instance using parameters from linker script
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_active_start: u32;
static __bootloader_active_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let active = unsafe {
Partition::new(
&__bootloader_active_start as *const u32 as u32,
&__bootloader_active_end as *const u32 as u32,
)
};
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as u32,
&__bootloader_dfu_end as *const u32 as u32,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as u32,
&__bootloader_state_end as *const u32 as u32,
)
};
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
Self::new(active, dfu, state)
}
}

View File

@ -30,6 +30,16 @@ impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> {
}
Self { flash, offset, size }
}
/// Get the partition offset within the flash
pub const fn offset(&self) -> u32 {
self.offset
}
/// Get the partition size
pub const fn size(&self) -> u32 {
self.size
}
}
impl<M: RawMutex, T: NorFlash> ErrorType for Partition<'_, M, T> {

View File

@ -31,6 +31,16 @@ impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> {
}
Self { flash, offset, size }
}
/// Get the partition offset within the flash
pub const fn offset(&self) -> u32 {
self.offset
}
/// Get the partition size
pub const fn size(&self) -> u32 {
self.size
}
}
impl<M: RawMutex, T: NorFlash> ErrorType for BlockingPartition<'_, M, T> {

View File

@ -3,12 +3,13 @@
#![macro_use]
#![feature(type_alias_impl_trait)]
use embassy_boot_nrf::FirmwareUpdater;
use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
use embassy_nrf::nvmc::Nvmc;
use embassy_nrf::wdt::{self, Watchdog};
use embassy_sync::mutex::Mutex;
use panic_reset as _;
static APP_B: &[u8] = include_bytes!("../../b.bin");
@ -45,9 +46,10 @@ async fn main(_spawner: Spawner) {
};
let nvmc = Nvmc::new(p.NVMC);
let mut nvmc = BlockingAsync::new(nvmc);
let nvmc = Mutex::new(BlockingAsync::new(nvmc));
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc);
let mut updater = FirmwareUpdater::new(config);
loop {
led.set_low();
button.wait_for_any_edge().await;
@ -56,11 +58,11 @@ async fn main(_spawner: Spawner) {
for chunk in APP_B.chunks(4096) {
let mut buf: [u8; 4096] = [0; 4096];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = [0; 4];
updater.mark_updated(&mut nvmc, &mut magic).await.unwrap();
updater.mark_updated(&mut magic).await.unwrap();
led.set_high();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -20,6 +20,7 @@ embedded-hal = { version = "0.2.6" }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"
embedded-storage = "0.3.0"
[features]
default = ["panic-reset"]

View File

@ -2,13 +2,17 @@
#![no_main]
#![feature(type_alias_impl_trait)]
use core::cell::RefCell;
use defmt_rtt as _;
use embassy_boot_rp::*;
use embassy_executor::Spawner;
use embassy_rp::flash::Flash;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::watchdog::Watchdog;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::{Duration, Timer};
use embedded_storage::nor_flash::NorFlash;
#[cfg(feature = "panic-probe")]
use panic_probe as _;
#[cfg(feature = "panic-reset")]
@ -26,9 +30,11 @@ async fn main(_s: Spawner) {
let mut watchdog = Watchdog::new(p.WATCHDOG);
watchdog.start(Duration::from_secs(8));
let mut flash: Flash<_, FLASH_SIZE> = Flash::new_blocking(p.FLASH);
let flash: Flash<_, FLASH_SIZE> = Flash::new(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut updater = BlockingFirmwareUpdater::new(config);
Timer::after(Duration::from_secs(5)).await;
watchdog.feed();
@ -36,22 +42,20 @@ async fn main(_s: Spawner) {
let mut offset = 0;
let mut buf: AlignedBuffer<4096> = AlignedBuffer([0; 4096]);
defmt::info!("preparing update");
let mut writer = updater
.prepare_update_blocking(&mut flash)
let writer = updater
.prepare_update()
.map_err(|e| defmt::warn!("E: {:?}", defmt::Debug2Format(&e)))
.unwrap();
defmt::info!("writer created, starting write");
for chunk in APP_B.chunks(4096) {
buf.0[..chunk.len()].copy_from_slice(chunk);
defmt::info!("writing block at offset {}", offset);
writer
.write_block_blocking(offset, &buf.0[..], &mut flash, 256)
.unwrap();
offset += chunk.len();
writer.write(offset, &buf.0[..]).unwrap();
offset += chunk.len() as u32;
}
watchdog.feed();
defmt::info!("firmware written, marking update");
updater.mark_updated_blocking(&mut flash, &mut buf.0[..1]).unwrap();
updater.mark_updated(&mut buf.0[..1]).unwrap();
Timer::after(Duration::from_secs(2)).await;
led.set_low();
defmt::info!("update marked, resetting");

View File

@ -4,12 +4,13 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::{Flash, WRITE_SIZE};
use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
use embassy_sync::mutex::Mutex;
use panic_reset as _;
static APP_B: &[u8] = include_bytes!("../../b.bin");
@ -18,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PC13, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -26,17 +27,18 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PA5, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
let mut offset = 0;
for chunk in APP_B.chunks(2048) {
let mut buf: [u8; 2048] = [0; 2048];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
updater.mark_updated(magic.as_mut()).await.unwrap();
led.set_low();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig};
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::{Flash, WRITE_SIZE};
@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let mut flash = Flash::new_blocking(p.FLASH);
let flash = Flash::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let button = Input::new(p.PC13, Pull::Down);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -24,20 +25,19 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB7, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let mut writer = updater.prepare_update_blocking(&mut flash).unwrap();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut updater = BlockingFirmwareUpdater::new(config);
let mut writer = updater.prepare_update().unwrap();
button.wait_for_rising_edge().await;
let mut offset = 0;
let mut buf = AlignedBuffer([0; 4096]);
for chunk in APP_B.chunks(4096) {
buf.as_mut()[..chunk.len()].copy_from_slice(chunk);
writer
.write_block_blocking(offset, buf.as_ref(), &mut flash, chunk.len())
.unwrap();
writer.write(offset, buf.as_ref()).unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap();
updater.mark_updated(magic.as_mut()).unwrap();
led.set_low();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig};
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::flash::{Flash, WRITE_SIZE};
@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let mut flash = Flash::new_blocking(p.FLASH);
let flash = Flash::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let button = Input::new(p.PC13, Pull::Down);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -24,21 +25,19 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let mut writer = updater.prepare_update_blocking(&mut flash).unwrap();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut updater = BlockingFirmwareUpdater::new(config);
let mut writer = updater.prepare_update().unwrap();
button.wait_for_rising_edge().await;
let mut offset = 0;
let mut buf = AlignedBuffer([0; 4096]);
for chunk in APP_B.chunks(4096) {
buf.as_mut()[..chunk.len()].copy_from_slice(chunk);
writer
.write_block_blocking(offset, buf.as_ref(), &mut flash, 4096)
.unwrap();
writer.write(offset, buf.as_ref()).unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap();
updater.mark_updated(magic.as_mut()).unwrap();
led.set_low();
cortex_m::peripheral::SCB::sys_reset();
}

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
@ -19,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PB2, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI2);
@ -28,18 +28,19 @@ async fn main(_spawner: Spawner) {
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
let mut offset = 0;
for chunk in APP_B.chunks(128) {
let mut buf: [u8; 128] = [0; 128];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 128).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
updater.mark_updated(magic.as_mut()).await.unwrap();
led.set_low();
Timer::after(Duration::from_secs(1)).await;
cortex_m::peripheral::SCB::sys_reset();

View File

@ -4,7 +4,7 @@
#[cfg(feature = "defmt-rtt")]
use defmt_rtt::*;
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater};
use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_stm32::exti::ExtiInput;
@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PC13, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI13);
@ -26,13 +26,14 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
let mut offset = 0;
for chunk in APP_B.chunks(2048) {
let mut buf: [u8; 2048] = [0; 2048];
buf[..chunk.len()].copy_from_slice(chunk);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);

View File

@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin");
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let flash = Flash::new_blocking(p.FLASH);
let mut flash = BlockingAsync::new(flash);
let mut flash = Mutex::new(BlockingAsync::new(flash));
let button = Input::new(p.PA0, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI0);
@ -26,7 +26,8 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB9, Level::Low, Speed::Low);
led.set_high();
let mut updater = FirmwareUpdater::default();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let mut updater = FirmwareUpdater::new(config);
button.wait_for_falling_edge().await;
//defmt::info!("Starting update");
let mut offset = 0;
@ -34,11 +35,11 @@ async fn main(_spawner: Spawner) {
let mut buf: [u8; 2048] = [0; 2048];
buf[..chunk.len()].copy_from_slice(chunk);
// defmt::info!("Writing chunk at 0x{:x}", offset);
updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap();
updater.write_firmware(offset, &buf).await.unwrap();
offset += chunk.len();
}
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap();
updater.mark_updated(magic.as_mut()).await.unwrap();
//defmt::info!("Marked as updated");
led.set_low();
cortex_m::peripheral::SCB::sys_reset();

View File

@ -12,6 +12,7 @@ defmt-rtt = { version = "0.4", optional = true }
embassy-nrf = { path = "../../../../embassy-nrf", features = ["nightly"] }
embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
embassy-sync = { path = "../../../../embassy-sync" }
cortex-m-rt = { version = "0.7" }
cfg-if = "1.0.0"

View File

@ -1,12 +1,15 @@
#![no_std]
#![no_main]
use core::cell::RefCell;
use cortex_m_rt::{entry, exception};
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_boot_nrf::*;
use embassy_nrf::nvmc::Nvmc;
use embassy_nrf::wdt;
use embassy_sync::blocking_mutex::Mutex;
#[entry]
fn main() -> ! {
@ -20,19 +23,21 @@ fn main() -> ! {
}
*/
let mut bl = BootLoader::default();
let mut wdt_config = wdt::Config::default();
wdt_config.timeout_ticks = 32768 * 5; // timeout seconds
wdt_config.run_during_sleep = true;
wdt_config.run_during_debug_halt = false;
let start = bl.prepare(&mut SingleFlashConfig::new(&mut BootFlash::new(WatchdogFlash::start(
Nvmc::new(p.NVMC),
p.WDT,
wdt_config,
))));
unsafe { bl.load(start) }
let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config);
let flash = Mutex::new(RefCell::new(flash));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let active_offset = config.active.offset();
let mut bl: BootLoader<_, _, _> = BootLoader::new(config);
bl.prepare();
unsafe { bl.load(active_offset) }
}
#[no_mangle]

View File

@ -11,6 +11,7 @@ defmt-rtt = { version = "0.4", optional = true }
embassy-rp = { path = "../../../../embassy-rp", features = ["nightly"] }
embassy-boot-rp = { path = "../../../../embassy-boot/rp" }
embassy-sync = { path = "../../../../embassy-sync" }
embassy-time = { path = "../../../../embassy-time", features = ["nightly"] }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }

View File

@ -1,10 +1,13 @@
#![no_std]
#![no_main]
use core::cell::RefCell;
use cortex_m_rt::{entry, exception};
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_boot_rp::*;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::Duration;
const FLASH_SIZE: usize = 2 * 1024 * 1024;
@ -21,13 +24,16 @@ fn main() -> ! {
}
*/
let mut bl: BootLoader = BootLoader::default();
let flash = WatchdogFlash::<FLASH_SIZE>::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8));
let mut flash = BootFlash::new(flash);
let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash));
core::mem::drop(flash);
let flash = Mutex::new(RefCell::new(flash));
unsafe { bl.load(start) }
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let active_offset = config.active.offset();
let mut bl: BootLoader<_, _, _> = BootLoader::new(config);
bl.prepare();
unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) }
}
#[no_mangle]

View File

@ -12,6 +12,7 @@ defmt-rtt = { version = "0.4", optional = true }
embassy-stm32 = { path = "../../../../embassy-stm32", features = ["nightly"] }
embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
embassy-sync = { path = "../../../../embassy-sync" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
embedded-storage-async = "0.4.0"

View File

@ -1,11 +1,14 @@
#![no_std]
#![no_main]
use core::cell::RefCell;
use cortex_m_rt::{entry, exception};
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_boot_stm32::*;
use embassy_stm32::flash::Flash;
use embassy_stm32::flash::{Flash, BANK1_REGION};
use embassy_sync::blocking_mutex::Mutex;
#[entry]
fn main() -> ! {
@ -19,12 +22,16 @@ fn main() -> ! {
}
*/
let mut bl: BootLoader<2048> = BootLoader::default();
let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
let mut flash = BootFlash::new(layout.bank1_region);
let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash));
core::mem::drop(flash);
unsafe { bl.load(start) }
let flash = Mutex::new(RefCell::new(layout.bank1_region));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let active_offset = config.active.offset();
let mut bl: BootLoader<_, _, _, 2048> = BootLoader::new(config);
bl.prepare();
unsafe { bl.load(BANK1_REGION.base + active_offset) }
}
#[no_mangle]