Merge #740
740: Allow using separate page sizes for state and dfu r=lulf a=lulf * Less generics on bootloader. Keep PAGE_SIZE as a common multiple of DFU and ACTIVE page sizes. * Document restriction * Add unit tests for different page sizes Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
This commit is contained in:
commit
bd488ef0d5
@ -27,6 +27,7 @@ The bootloader divides the storage into 4 main partitions, configured by a linke
|
|||||||
* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application.
|
* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application.
|
||||||
* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped.
|
* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped.
|
||||||
|
|
||||||
The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash, but they have to support compatible page sizes.
|
The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash. The page size used by the bootloader is determined by the lowest common multiple of the ACTIVE and DFU page sizes.
|
||||||
|
The BOOTLOADER_STATE partition must be big enough to store one word per page in the ACTIVE and DFU partitions combined.
|
||||||
|
|
||||||
The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
|
The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
|
||||||
|
@ -62,6 +62,7 @@ where
|
|||||||
|
|
||||||
pub trait FlashConfig {
|
pub trait FlashConfig {
|
||||||
const BLOCK_SIZE: usize;
|
const BLOCK_SIZE: usize;
|
||||||
|
const ERASE_VALUE: u8;
|
||||||
type FLASH: NorFlash + ReadNorFlash;
|
type FLASH: NorFlash + ReadNorFlash;
|
||||||
|
|
||||||
fn flash(&mut self) -> &mut Self::FLASH;
|
fn flash(&mut self) -> &mut Self::FLASH;
|
||||||
@ -83,7 +84,9 @@ pub trait FlashProvider {
|
|||||||
|
|
||||||
/// BootLoader works with any flash implementing embedded_storage and can also work with
|
/// BootLoader works with any flash implementing embedded_storage and can also work with
|
||||||
/// different page sizes and flash write sizes.
|
/// different page sizes and flash write sizes.
|
||||||
pub struct BootLoader<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8> {
|
///
|
||||||
|
/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes.
|
||||||
|
pub struct BootLoader<const PAGE_SIZE: usize> {
|
||||||
// Page with current state of bootloader. The state partition has the following format:
|
// Page with current state of bootloader. The state partition has the following format:
|
||||||
// | Range | Description |
|
// | Range | Description |
|
||||||
// | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
|
// | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
|
||||||
@ -95,16 +98,12 @@ pub struct BootLoader<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERA
|
|||||||
dfu: Partition,
|
dfu: Partition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
|
||||||
BootLoader<PAGE_SIZE, WRITE_SIZE, ERASE_VALUE>
|
|
||||||
{
|
|
||||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||||
assert_eq!(active.len() % PAGE_SIZE, 0);
|
assert_eq!(active.len() % PAGE_SIZE, 0);
|
||||||
assert_eq!(dfu.len() % PAGE_SIZE, 0);
|
assert_eq!(dfu.len() % PAGE_SIZE, 0);
|
||||||
// DFU partition must have an extra page
|
// DFU partition must have an extra page
|
||||||
assert!(dfu.len() - active.len() >= PAGE_SIZE);
|
assert!(dfu.len() - active.len() >= PAGE_SIZE);
|
||||||
// Ensure we have enough progress pages to store copy progress
|
|
||||||
assert!(active.len() / PAGE_SIZE >= (state.len() - WRITE_SIZE) / PAGE_SIZE);
|
|
||||||
Self { active, dfu, state }
|
Self { active, dfu, state }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +194,19 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
/// | DFU | 3 | 3 | 2 | 1 | 3 |
|
/// | DFU | 3 | 3 | 2 | 1 | 3 |
|
||||||
/// +-----------+--------------+--------+--------+--------+--------+
|
/// +-----------+--------------+--------+--------+--------+--------+
|
||||||
///
|
///
|
||||||
pub fn prepare_boot<P: FlashProvider>(&mut self, p: &mut P) -> Result<State, BootError> {
|
pub fn prepare_boot<P: FlashProvider>(&mut self, p: &mut P) -> Result<State, BootError>
|
||||||
|
where
|
||||||
|
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
[(); <<P as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
|
||||||
|
{
|
||||||
|
// Ensure we have enough progress pages to store copy progress
|
||||||
|
assert!(
|
||||||
|
self.active.len() / PAGE_SIZE
|
||||||
|
<= (self.state.len()
|
||||||
|
- <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE)
|
||||||
|
/ <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
// Copy contents from partition N to active
|
// Copy contents from partition N to active
|
||||||
let state = self.read_state(p.state())?;
|
let state = self.read_state(p.state())?;
|
||||||
match state {
|
match state {
|
||||||
@ -214,10 +225,16 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
|
|
||||||
// Overwrite magic and reset progress
|
// Overwrite magic and reset progress
|
||||||
let fstate = p.state().flash();
|
let fstate = p.state().flash();
|
||||||
let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
|
let aligned = Aligned(
|
||||||
|
[!P::STATE::ERASE_VALUE;
|
||||||
|
<<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE],
|
||||||
|
);
|
||||||
fstate.write(self.state.from as u32, &aligned.0)?;
|
fstate.write(self.state.from as u32, &aligned.0)?;
|
||||||
fstate.erase(self.state.from as u32, self.state.to as u32)?;
|
fstate.erase(self.state.from as u32, self.state.to as u32)?;
|
||||||
let aligned = Aligned([BOOT_MAGIC; WRITE_SIZE]);
|
let aligned = Aligned(
|
||||||
|
[BOOT_MAGIC;
|
||||||
|
<<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE],
|
||||||
|
);
|
||||||
fstate.write(self.state.from as u32, &aligned.0)?;
|
fstate.write(self.state.from as u32, &aligned.0)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,33 +243,44 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P) -> Result<bool, BootError> {
|
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P) -> Result<bool, BootError>
|
||||||
let page_count = self.active.len() / PAGE_SIZE;
|
where
|
||||||
|
[(); P::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
|
let page_count = self.active.len() / P::FLASH::ERASE_SIZE;
|
||||||
let progress = self.current_progress(p)?;
|
let progress = self.current_progress(p)?;
|
||||||
|
|
||||||
Ok(progress >= page_count * 2)
|
Ok(progress >= page_count * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> {
|
fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError>
|
||||||
let max_index = ((self.state.len() - WRITE_SIZE) / WRITE_SIZE) - 1;
|
where
|
||||||
|
[(); P::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
|
let write_size = P::FLASH::WRITE_SIZE;
|
||||||
|
let max_index = ((self.state.len() - write_size) / write_size) - 1;
|
||||||
let flash = p.flash();
|
let flash = p.flash();
|
||||||
let mut aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
|
let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
|
||||||
for i in 0..max_index {
|
for i in 0..max_index {
|
||||||
flash.read(
|
flash.read(
|
||||||
(self.state.from + WRITE_SIZE + i * WRITE_SIZE) as u32,
|
(self.state.from + write_size + i * write_size) as u32,
|
||||||
&mut aligned.0,
|
&mut aligned.0,
|
||||||
)?;
|
)?;
|
||||||
if aligned.0 == [ERASE_VALUE; WRITE_SIZE] {
|
if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] {
|
||||||
return Ok(i);
|
return Ok(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(max_index)
|
Ok(max_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> {
|
fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError>
|
||||||
|
where
|
||||||
|
[(); P::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
let flash = p.flash();
|
let flash = p.flash();
|
||||||
let w = self.state.from + WRITE_SIZE + idx * WRITE_SIZE;
|
let write_size = P::FLASH::WRITE_SIZE;
|
||||||
let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
|
let w = self.state.from + write_size + idx * write_size;
|
||||||
|
let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
|
||||||
flash.write(w as u32, &aligned.0)?;
|
flash.write(w as u32, &aligned.0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -271,7 +299,10 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
from_page: usize,
|
from_page: usize,
|
||||||
to_page: usize,
|
to_page: usize,
|
||||||
p: &mut P,
|
p: &mut P,
|
||||||
) -> Result<(), BootError> {
|
) -> Result<(), BootError>
|
||||||
|
where
|
||||||
|
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
|
let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
|
||||||
if self.current_progress(p.state())? <= idx {
|
if self.current_progress(p.state())? <= idx {
|
||||||
let mut offset = from_page;
|
let mut offset = from_page;
|
||||||
@ -300,7 +331,10 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
from_page: usize,
|
from_page: usize,
|
||||||
to_page: usize,
|
to_page: usize,
|
||||||
p: &mut P,
|
p: &mut P,
|
||||||
) -> Result<(), BootError> {
|
) -> Result<(), BootError>
|
||||||
|
where
|
||||||
|
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
|
let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
|
||||||
if self.current_progress(p.state())? <= idx {
|
if self.current_progress(p.state())? <= idx {
|
||||||
let mut offset = from_page;
|
let mut offset = from_page;
|
||||||
@ -323,7 +357,10 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> {
|
fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError>
|
||||||
|
where
|
||||||
|
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
let page_count = self.active.len() / PAGE_SIZE;
|
let page_count = self.active.len() / PAGE_SIZE;
|
||||||
trace!("Page count: {}", page_count);
|
trace!("Page count: {}", page_count);
|
||||||
for page in 0..page_count {
|
for page in 0..page_count {
|
||||||
@ -344,7 +381,10 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revert<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> {
|
fn revert<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError>
|
||||||
|
where
|
||||||
|
[(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
let page_count = self.active.len() / PAGE_SIZE;
|
let page_count = self.active.len() / PAGE_SIZE;
|
||||||
for page in 0..page_count {
|
for page in 0..page_count {
|
||||||
// Copy the bad active page to the DFU page
|
// Copy the bad active page to the DFU page
|
||||||
@ -361,12 +401,15 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> {
|
fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError>
|
||||||
let mut magic: [u8; WRITE_SIZE] = [0; WRITE_SIZE];
|
where
|
||||||
|
[(); P::FLASH::WRITE_SIZE]:,
|
||||||
|
{
|
||||||
|
let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE];
|
||||||
let flash = p.flash();
|
let flash = p.flash();
|
||||||
flash.read(self.state.from as u32, &mut magic)?;
|
flash.read(self.state.from as u32, &mut magic)?;
|
||||||
|
|
||||||
if magic == [SWAP_MAGIC; WRITE_SIZE] {
|
if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] {
|
||||||
Ok(State::Swap)
|
Ok(State::Swap)
|
||||||
} else {
|
} else {
|
||||||
Ok(State::Boot)
|
Ok(State::Boot)
|
||||||
@ -375,14 +418,14 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize, const ERASE_VALUE: u8>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience provider that uses a single flash for everything
|
/// Convenience provider that uses a single flash for everything
|
||||||
pub struct SingleFlashProvider<'a, F>
|
pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF>
|
||||||
where
|
where
|
||||||
F: NorFlash + ReadNorFlash,
|
F: NorFlash + ReadNorFlash,
|
||||||
{
|
{
|
||||||
config: SingleFlashConfig<'a, F>,
|
config: SingleFlashConfig<'a, F, ERASE_VALUE>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, F> SingleFlashProvider<'a, F>
|
impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE>
|
||||||
where
|
where
|
||||||
F: NorFlash + ReadNorFlash,
|
F: NorFlash + ReadNorFlash,
|
||||||
{
|
{
|
||||||
@ -393,7 +436,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SingleFlashConfig<'a, F>
|
pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF>
|
||||||
where
|
where
|
||||||
F: NorFlash + ReadNorFlash,
|
F: NorFlash + ReadNorFlash,
|
||||||
{
|
{
|
||||||
@ -419,17 +462,66 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
|
impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE>
|
||||||
where
|
where
|
||||||
F: NorFlash + ReadNorFlash,
|
F: NorFlash + ReadNorFlash,
|
||||||
{
|
{
|
||||||
const BLOCK_SIZE: usize = F::ERASE_SIZE;
|
const BLOCK_SIZE: usize = F::ERASE_SIZE;
|
||||||
|
const ERASE_VALUE: u8 = ERASE_VALUE;
|
||||||
type FLASH = F;
|
type FLASH = F;
|
||||||
fn flash(&mut self) -> &mut F {
|
fn flash(&mut self) -> &mut F {
|
||||||
self.flash
|
self.flash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience provider that uses a single flash for everything
|
||||||
|
pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU>
|
||||||
|
where
|
||||||
|
ACTIVE: NorFlash + ReadNorFlash,
|
||||||
|
STATE: NorFlash + ReadNorFlash,
|
||||||
|
DFU: NorFlash + ReadNorFlash,
|
||||||
|
{
|
||||||
|
active: SingleFlashConfig<'a, ACTIVE>,
|
||||||
|
state: SingleFlashConfig<'a, STATE>,
|
||||||
|
dfu: SingleFlashConfig<'a, DFU>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU>
|
||||||
|
where
|
||||||
|
ACTIVE: NorFlash + ReadNorFlash,
|
||||||
|
STATE: NorFlash + ReadNorFlash,
|
||||||
|
DFU: NorFlash + ReadNorFlash,
|
||||||
|
{
|
||||||
|
pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
|
||||||
|
Self {
|
||||||
|
active: SingleFlashConfig { flash: active },
|
||||||
|
state: SingleFlashConfig { flash: state },
|
||||||
|
dfu: SingleFlashConfig { flash: dfu },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU>
|
||||||
|
where
|
||||||
|
ACTIVE: NorFlash + ReadNorFlash,
|
||||||
|
STATE: NorFlash + ReadNorFlash,
|
||||||
|
DFU: NorFlash + ReadNorFlash,
|
||||||
|
{
|
||||||
|
type STATE = SingleFlashConfig<'a, STATE>;
|
||||||
|
type ACTIVE = SingleFlashConfig<'a, ACTIVE>;
|
||||||
|
type DFU = SingleFlashConfig<'a, DFU>;
|
||||||
|
|
||||||
|
fn active(&mut self) -> &mut Self::ACTIVE {
|
||||||
|
&mut self.active
|
||||||
|
}
|
||||||
|
fn dfu(&mut self) -> &mut Self::DFU {
|
||||||
|
&mut self.dfu
|
||||||
|
}
|
||||||
|
fn state(&mut self) -> &mut Self::STATE {
|
||||||
|
&mut self.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||||
/// 'mess up' the internal bootloader state
|
/// 'mess up' the internal bootloader state
|
||||||
pub struct FirmwareUpdater {
|
pub struct FirmwareUpdater {
|
||||||
@ -481,7 +573,7 @@ impl FirmwareUpdater {
|
|||||||
|
|
||||||
/// Instruct bootloader that DFU should commence at next boot.
|
/// Instruct bootloader that DFU should commence at next boot.
|
||||||
/// Must be provided with an aligned buffer to use for reading and writing magic;
|
/// Must be provided with an aligned buffer to use for reading and writing magic;
|
||||||
pub async fn mark_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error>
|
pub async fn update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error>
|
||||||
where
|
where
|
||||||
[(); F::WRITE_SIZE]:,
|
[(); F::WRITE_SIZE]:,
|
||||||
{
|
{
|
||||||
@ -592,10 +684,6 @@ mod tests {
|
|||||||
use embedded_storage_async::nor_flash::AsyncReadNorFlash;
|
use embedded_storage_async::nor_flash::AsyncReadNorFlash;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
|
||||||
const STATE: Partition = Partition::new(0, 4096);
|
|
||||||
const ACTIVE: Partition = Partition::new(4096, 61440);
|
|
||||||
const DFU: Partition = Partition::new(61440, 122880);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bad_magic() {
|
fn test_bad_magic() {
|
||||||
@ -613,19 +701,25 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_boot_state() {
|
fn test_boot_state() {
|
||||||
let mut flash = MemFlash([0xff; 131072]);
|
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>([0xff; 131072]);
|
||||||
flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
|
flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
|
||||||
let mut flash = SingleFlashProvider::new(&mut flash);
|
let mut flash = SingleFlashProvider::new(&mut flash);
|
||||||
|
|
||||||
let mut bootloader = BootLoader::<4096, 4, 0xFF>::new(ACTIVE, DFU, STATE);
|
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
|
||||||
|
|
||||||
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
|
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_swap_state() {
|
fn test_swap_state() {
|
||||||
env_logger::init();
|
const STATE: Partition = Partition::new(0, 4096);
|
||||||
let mut flash = MemFlash([0xff; 131072]);
|
const ACTIVE: Partition = Partition::new(4096, 61440);
|
||||||
|
const DFU: Partition = Partition::new(61440, 122880);
|
||||||
|
let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]);
|
||||||
|
|
||||||
let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
|
let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
|
||||||
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
|
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
|
||||||
@ -634,14 +728,14 @@ mod tests {
|
|||||||
flash.0[i] = original[i - ACTIVE.from];
|
flash.0[i] = original[i - ACTIVE.from];
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bootloader = BootLoader::<4096, 4, 0xFF>::new(ACTIVE, DFU, STATE);
|
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
|
||||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
for chunk in update.chunks(4096) {
|
for chunk in update.chunks(4096) {
|
||||||
block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap();
|
block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap();
|
||||||
offset += chunk.len();
|
offset += chunk.len();
|
||||||
}
|
}
|
||||||
block_on(updater.mark_update(&mut flash)).unwrap();
|
block_on(updater.update(&mut flash)).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
State::Swap,
|
State::Swap,
|
||||||
@ -686,27 +780,131 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MemFlash([u8; 131072]);
|
#[test]
|
||||||
|
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);
|
||||||
|
|
||||||
impl NorFlash for MemFlash {
|
let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]);
|
||||||
const WRITE_SIZE: usize = 4;
|
let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]);
|
||||||
const ERASE_SIZE: usize = 4096;
|
let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
|
||||||
|
|
||||||
|
let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
|
||||||
|
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
|
||||||
|
|
||||||
|
for i in ACTIVE.from..ACTIVE.to {
|
||||||
|
active.0[i] = original[i - ACTIVE.from];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
for chunk in update.chunks(2048) {
|
||||||
|
block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap();
|
||||||
|
offset += chunk.len();
|
||||||
|
}
|
||||||
|
block_on(updater.update(&mut state)).unwrap();
|
||||||
|
|
||||||
|
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
|
||||||
|
assert_eq!(
|
||||||
|
State::Swap,
|
||||||
|
bootloader
|
||||||
|
.prepare_boot(&mut MultiFlashProvider::new(
|
||||||
|
&mut active,
|
||||||
|
&mut state,
|
||||||
|
&mut dfu,
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
for i in ACTIVE.from..ACTIVE.to {
|
||||||
|
assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First DFU page is untouched
|
||||||
|
for i in DFU.from + 4096..DFU.to {
|
||||||
|
assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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);
|
||||||
|
|
||||||
|
let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]);
|
||||||
|
let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]);
|
||||||
|
let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
|
||||||
|
|
||||||
|
let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
|
||||||
|
let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
|
||||||
|
|
||||||
|
for i in ACTIVE.from..ACTIVE.to {
|
||||||
|
active.0[i] = original[i - ACTIVE.from];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
for chunk in update.chunks(4096) {
|
||||||
|
block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap();
|
||||||
|
offset += chunk.len();
|
||||||
|
}
|
||||||
|
block_on(updater.update(&mut state)).unwrap();
|
||||||
|
|
||||||
|
let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
|
||||||
|
assert_eq!(
|
||||||
|
State::Swap,
|
||||||
|
bootloader
|
||||||
|
.prepare_boot(&mut MultiFlashProvider::new(
|
||||||
|
&mut active,
|
||||||
|
&mut state,
|
||||||
|
&mut dfu,
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
for i in ACTIVE.from..ACTIVE.to {
|
||||||
|
assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First DFU page is untouched
|
||||||
|
for i in DFU.from + 4096..DFU.to {
|
||||||
|
assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize>(
|
||||||
|
[u8; SIZE],
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
|
||||||
|
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||||
|
{
|
||||||
|
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||||
|
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||||
let from = from as usize;
|
let from = from as usize;
|
||||||
let to = to 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 {
|
for i in from..to {
|
||||||
self.0[i] = 0xFF;
|
self.0[i] = 0xFF;
|
||||||
self.0[i] = 0xFF;
|
|
||||||
self.0[i] = 0xFF;
|
|
||||||
self.0[i] = 0xFF;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
|
fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
|
||||||
assert!(data.len() % 4 == 0);
|
assert!(data.len() % WRITE_SIZE == 0);
|
||||||
assert!(offset % 4 == 0);
|
assert!(offset as usize % WRITE_SIZE == 0);
|
||||||
assert!(offset as usize + data.len() < 131072);
|
assert!(offset as usize + data.len() <= SIZE);
|
||||||
|
|
||||||
self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
|
self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
|
||||||
|
|
||||||
@ -714,11 +912,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorType for MemFlash {
|
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
|
||||||
|
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||||
|
{
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadNorFlash for MemFlash {
|
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
|
||||||
|
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||||
|
{
|
||||||
const READ_SIZE: usize = 4;
|
const READ_SIZE: usize = 4;
|
||||||
|
|
||||||
fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
|
fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
@ -728,11 +930,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> usize {
|
fn capacity(&self) -> usize {
|
||||||
131072
|
SIZE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncReadNorFlash for MemFlash {
|
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||||
|
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||||
|
{
|
||||||
const READ_SIZE: usize = 4;
|
const READ_SIZE: usize = 4;
|
||||||
|
|
||||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
|
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
|
||||||
@ -745,24 +949,25 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn capacity(&self) -> usize {
|
fn capacity(&self) -> usize {
|
||||||
131072
|
SIZE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncNorFlash for MemFlash {
|
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||||
const WRITE_SIZE: usize = 4;
|
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||||
const ERASE_SIZE: usize = 4096;
|
{
|
||||||
|
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||||
|
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||||
|
|
||||||
type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
|
type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
|
||||||
fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
|
fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
|
||||||
async move {
|
async move {
|
||||||
let from = from as usize;
|
let from = from as usize;
|
||||||
let to = to as usize;
|
let to = to as usize;
|
||||||
|
assert!(from % ERASE_SIZE == 0);
|
||||||
|
assert!(to % ERASE_SIZE == 0);
|
||||||
for i in from..to {
|
for i in from..to {
|
||||||
self.0[i] = 0xFF;
|
self.0[i] = 0xFF;
|
||||||
self.0[i] = 0xFF;
|
|
||||||
self.0[i] = 0xFF;
|
|
||||||
self.0[i] = 0xFF;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -770,10 +975,17 @@ mod tests {
|
|||||||
|
|
||||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
|
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
|
||||||
fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
|
fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||||
|
info!("Writing {} bytes to 0x{:x}", data.len(), offset);
|
||||||
async move {
|
async move {
|
||||||
assert!(data.len() % 4 == 0);
|
assert!(data.len() % WRITE_SIZE == 0);
|
||||||
assert!(offset % 4 == 0);
|
assert!(offset as usize % WRITE_SIZE == 0);
|
||||||
assert!(offset as usize + data.len() < 131072);
|
assert!(
|
||||||
|
offset as usize + data.len() <= SIZE,
|
||||||
|
"OFFSET: {}, LEN: {}, FLASH SIZE: {}",
|
||||||
|
offset,
|
||||||
|
data.len(),
|
||||||
|
SIZE
|
||||||
|
);
|
||||||
|
|
||||||
self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
|
self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(generic_associated_types)]
|
#![feature(generic_associated_types)]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(generic_const_exprs)]
|
||||||
|
|
||||||
mod fmt;
|
mod fmt;
|
||||||
|
|
||||||
pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider};
|
pub use embassy_boot::{
|
||||||
|
FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider,
|
||||||
|
};
|
||||||
use embassy_nrf::{
|
use embassy_nrf::{
|
||||||
nvmc::{Nvmc, PAGE_SIZE},
|
nvmc::{Nvmc, PAGE_SIZE},
|
||||||
peripherals::WDT,
|
peripherals::WDT,
|
||||||
@ -13,7 +17,7 @@ use embassy_nrf::{
|
|||||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||||
|
|
||||||
pub struct BootLoader {
|
pub struct BootLoader {
|
||||||
boot: embassy_boot::BootLoader<PAGE_SIZE, 4, 0xFF>,
|
boot: embassy_boot::BootLoader<PAGE_SIZE>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BootLoader {
|
impl BootLoader {
|
||||||
@ -62,7 +66,11 @@ impl BootLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Boots the application without softdevice mechanisms
|
/// Boots the application without softdevice mechanisms
|
||||||
pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize {
|
pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize
|
||||||
|
where
|
||||||
|
[(); <<F as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
[(); <<F as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
|
||||||
|
{
|
||||||
match self.boot.prepare_boot(flash) {
|
match self.boot.prepare_boot(flash) {
|
||||||
Ok(_) => self.boot.boot_address(),
|
Ok(_) => self.boot.boot_address(),
|
||||||
Err(_) => panic!("boot prepare error!"),
|
Err(_) => panic!("boot prepare error!"),
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(generic_associated_types)]
|
#![feature(generic_associated_types)]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(generic_const_exprs)]
|
||||||
|
|
||||||
mod fmt;
|
mod fmt;
|
||||||
|
|
||||||
pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider, State};
|
pub use embassy_boot::{
|
||||||
use embassy_stm32::flash::{ERASE_SIZE, ERASE_VALUE, WRITE_SIZE};
|
FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider, State,
|
||||||
|
};
|
||||||
|
use embedded_storage::nor_flash::NorFlash;
|
||||||
|
|
||||||
pub struct BootLoader {
|
pub struct BootLoader<const PAGE_SIZE: usize> {
|
||||||
boot: embassy_boot::BootLoader<ERASE_SIZE, WRITE_SIZE, ERASE_VALUE>,
|
boot: embassy_boot::BootLoader<PAGE_SIZE>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BootLoader {
|
impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
|
||||||
/// Create a new bootloader instance using parameters from linker script
|
/// Create a new bootloader instance using parameters from linker script
|
||||||
pub fn default() -> Self {
|
pub fn default() -> Self {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -57,7 +61,11 @@ impl BootLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Boots the application
|
/// Boots the application
|
||||||
pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize {
|
pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize
|
||||||
|
where
|
||||||
|
[(); <<F as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
|
||||||
|
[(); <<F as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
|
||||||
|
{
|
||||||
match self.boot.prepare_boot(flash) {
|
match self.boot.prepare_boot(flash) {
|
||||||
Ok(_) => self.boot.boot_address(),
|
Ok(_) => self.boot.boot_address(),
|
||||||
Err(_) => panic!("boot prepare error!"),
|
Err(_) => panic!("boot prepare error!"),
|
||||||
|
@ -7,7 +7,7 @@ use cortex_m_rt::{entry, exception};
|
|||||||
use defmt_rtt as _;
|
use defmt_rtt as _;
|
||||||
|
|
||||||
use embassy_boot_stm32::*;
|
use embassy_boot_stm32::*;
|
||||||
use embassy_stm32::flash::Flash;
|
use embassy_stm32::flash::{Flash, ERASE_SIZE};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
@ -21,7 +21,7 @@ fn main() -> ! {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let mut bl = BootLoader::default();
|
let mut bl: BootLoader<ERASE_SIZE> = BootLoader::default();
|
||||||
let mut flash = Flash::unlock(p.FLASH);
|
let mut flash = Flash::unlock(p.FLASH);
|
||||||
let start = bl.prepare(&mut SingleFlashProvider::new(&mut flash));
|
let start = bl.prepare(&mut SingleFlashProvider::new(&mut flash));
|
||||||
core::mem::drop(flash);
|
core::mem::drop(flash);
|
||||||
|
@ -40,7 +40,7 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
offset += chunk.len();
|
offset += chunk.len();
|
||||||
}
|
}
|
||||||
updater.mark_update(&mut nvmc).await.unwrap();
|
updater.update(&mut nvmc).await.unwrap();
|
||||||
led.set_high();
|
led.set_high();
|
||||||
cortex_m::peripheral::SCB::sys_reset();
|
cortex_m::peripheral::SCB::sys_reset();
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
|
|||||||
offset += chunk.len();
|
offset += chunk.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
updater.mark_update(&mut flash).await.unwrap();
|
updater.update(&mut flash).await.unwrap();
|
||||||
led.set_low();
|
led.set_low();
|
||||||
Timer::after(Duration::from_secs(1)).await;
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
cortex_m::peripheral::SCB::sys_reset();
|
cortex_m::peripheral::SCB::sys_reset();
|
||||||
|
@ -41,7 +41,7 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
|
|||||||
offset += chunk.len();
|
offset += chunk.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
updater.mark_update(&mut flash).await.unwrap();
|
updater.update(&mut flash).await.unwrap();
|
||||||
led.set_low();
|
led.set_low();
|
||||||
Timer::after(Duration::from_secs(1)).await;
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
cortex_m::peripheral::SCB::sys_reset();
|
cortex_m::peripheral::SCB::sys_reset();
|
||||||
|
@ -38,7 +38,7 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
offset += chunk.len();
|
offset += chunk.len();
|
||||||
}
|
}
|
||||||
updater.mark_update(&mut flash).await.unwrap();
|
updater.update(&mut flash).await.unwrap();
|
||||||
led.set_low();
|
led.set_low();
|
||||||
cortex_m::peripheral::SCB::sys_reset();
|
cortex_m::peripheral::SCB::sys_reset();
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
offset += chunk.len();
|
offset += chunk.len();
|
||||||
}
|
}
|
||||||
updater.mark_update(&mut flash).await.unwrap();
|
updater.update(&mut flash).await.unwrap();
|
||||||
//defmt::info!("Marked as updated");
|
//defmt::info!("Marked as updated");
|
||||||
led.set_low();
|
led.set_low();
|
||||||
cortex_m::peripheral::SCB::sys_reset();
|
cortex_m::peripheral::SCB::sys_reset();
|
||||||
|
Loading…
Reference in New Issue
Block a user