This commit is contained in:
Wouter Geraedts 2022-07-12 15:52:54 +02:00 committed by GitHub
commit afb21b7a02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 536 additions and 356 deletions

29
Cargo.lock generated
View file

@ -53,6 +53,12 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -106,21 +112,22 @@ dependencies = [
[[package]] [[package]]
name = "defmt" name = "defmt"
version = "0.2.3" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15fe96f5d208164afa70583ff8f062e7697cbbb0b98e5076fbf8ac6da9edff0f" checksum = "d3a0ae7494d9bff013d7b89471f4c424356a71e9752e0c78abe7e6c608a16bb3"
dependencies = [ dependencies = [
"bitflags",
"defmt-macros", "defmt-macros",
"semver 1.0.7",
] ]
[[package]] [[package]]
name = "defmt-macros" name = "defmt-macros"
version = "0.2.3" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd2c3949cb76c25f48c363e61b97f05b317efe3c12fa45d54a6599c3949c85e" checksum = "6d944432e281084511691b36e5e9c794c19c33675822c9019e3b64f5b89e10da"
dependencies = [ dependencies = [
"defmt-parser", "defmt-parser",
"proc-macro-error",
"proc-macro2 1.0.37", "proc-macro2 1.0.37",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.91", "syn 1.0.91",
@ -128,9 +135,9 @@ dependencies = [
[[package]] [[package]]
name = "defmt-parser" name = "defmt-parser"
version = "0.2.2" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc621c2b4f5f5635e34021c38af2ccb0c1dae38ba11ebee25258de8bb1cee9fe" checksum = "0db23d29972d99baa3de2ee2ae3f104c10564a6d05a346eb3f4c4f2c0525a06e"
[[package]] [[package]]
name = "desse" name = "desse"
@ -372,7 +379,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [ dependencies = [
"semver 0.9.0", "semver",
] ]
[[package]] [[package]]
@ -390,12 +397,6 @@ dependencies = [
"semver-parser", "semver-parser",
] ]
[[package]]
name = "semver"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
[[package]] [[package]]
name = "semver-parser" name = "semver-parser"
version = "0.7.0" version = "0.7.0"

View file

@ -15,7 +15,7 @@ moonboot-macros = { path = "./macros", version = "0.1.2" }
heapless = {version = "0.7", features = ["serde"] } heapless = {version = "0.7", features = ["serde"] }
serde = { version = "1.0", features = ["derive"], default-features = false, optional = true } serde = { version = "1.0", features = ["derive"], default-features = false, optional = true }
cortex-m = { version = "0.7", optional = true } cortex-m = { version = "0.7", optional = true }
defmt = { version = "0.2", optional = true } defmt = { version = "0.3", optional = true }
logger-crate = { version = "0.4", optional = true, package = "log" } logger-crate = { version = "0.4", optional = true, package = "log" }
crc = "2.0" crc = "2.0"
desse = { version = "0.2.1", optional = true } desse = { version = "0.2.1", optional = true }
@ -38,7 +38,7 @@ defmt-warn = []
defmt-error = [] defmt-error = []
[package.metadata.release] [package.metadata.release]
enable-features = ["ram-state", "ram-state", "cortex-m"] enable-features = ["ram-state", "cortex-m"]
shared-version = true shared-version = true
dependent-version = "upgrade" dependent-version = "upgrade"
pre-release-replacements = [ pre-release-replacements = [

View file

@ -1,77 +1,57 @@
use crate::{ use crate::{
exchange::Exchange,
hardware::processor::Processor, hardware::processor::Processor,
hardware::{Bank, Config}, hardware::{Bank, Config},
state::{ExchangeProgress, State, Update, UpdateError}, state::{ExchangeProgress, ExchangeStep, State, Update, UpdateError},
Address, Context,
}; };
use embedded_storage::Storage;
use crate::log; use crate::log;
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
use defmt::Format; use defmt::Format;
/// Error occured during nemory access
#[cfg_attr(feature = "use-defmt", derive(Format))]
#[derive(Debug)]
enum MemoryError {
BankSizeNotEqual,
BankSizeZero,
ReadFailure,
WriteFailure,
}
/// Use this from your bootloader application and call boot() to do the magic, reading the current /// Use this from your bootloader application and call boot() to do the magic, reading the current
/// state via the State type and then jumping to the new image using the Jumper specified /// state via the State type and then jumping to the new image using the Jumper specified
pub struct MoonbootBoot< pub struct MoonbootBoot<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> {
InternalMemory: Storage,
HardwareState: State,
CPU: Processor, // TODO: Wrap these into a context struct like rubble?
const INTERNAL_PAGE_SIZE: usize,
> {
config: Config, config: Config,
internal_memory: InternalMemory, storage: CONTEXT::Storage,
state: HardwareState, state: CONTEXT::State,
processor: CPU, processor: CONTEXT::Processor,
exchange: CONTEXT::Exchange,
} }
impl< impl<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> MoonbootBoot<CONTEXT, INTERNAL_PAGE_SIZE> {
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> MoonbootBoot<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE>
{
/// create a new instance of the bootloader /// create a new instance of the bootloader
pub fn new( pub fn new(
config: Config, config: Config,
internal_memory: InternalMemory, storage: CONTEXT::Storage,
state: HardwareState, state: CONTEXT::State,
processor: CPU, processor: CONTEXT::Processor,
) -> MoonbootBoot<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE> { exchange: CONTEXT::Exchange,
) -> Self {
Self { Self {
config, config,
internal_memory, storage,
state, state,
processor, processor,
exchange,
} }
} }
/// Destroy this instance of the bootloader and return access to the hardware peripheral /// Destroy this instance of the bootloader and return access to the hardware peripheral
pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) { pub fn destroy(self) -> (CONTEXT::Storage, CONTEXT::State, CONTEXT::Processor) {
(self.internal_memory, self.state, self.processor) (self.storage, self.state, self.processor)
} }
/// Execute the update and boot logic of the bootloader /// Execute the update and boot logic of the bootloader
pub fn boot(&mut self) -> Result<void::Void, ()> { pub fn boot(&mut self) -> Result<void::Void, <CONTEXT::State as State>::Error> {
// TODO: consider error handling // TODO: consider error handling
log::info!("Booting with moonboot!"); log::info!("Booting with moonboot!");
self.processor.setup(&self.config); self.processor.setup(&self.config);
let mut state = self.state.read(); let mut state = self.state.read()?;
log::info!("Old State: {:?}", state); log::info!("Old State: {:?}", state);
@ -80,16 +60,14 @@ impl<
Update::None => self.handle_none(), Update::None => self.handle_none(),
Update::Request(bank) => self.handle_request(bank), Update::Request(bank) => self.handle_request(bank),
Update::Revert(bank) => self.handle_revert(bank), Update::Revert(bank) => self.handle_revert(bank),
Update::Exchanging(progress) => self.handle_exchanging(progress), Update::Exchanging(progress) => self.handle_exchanging(progress)?,
Update::Error(err) => Update::Error(err), Update::Error(err) => Update::Error(err),
}; };
// TODO: Handle Progress Variable in state to recover from power loss
log::info!("New State: {:?}", state); log::info!("New State: {:?}", state);
// Step 2: Update state of Bootloader // Step 2: Update state of Bootloader
self.state.write(state)?; self.state.write(&state)?;
// Step 3: Jump to new or unchanged firmware // Step 3: Jump to new or unchanged firmware
self.jump_to_firmware(); self.jump_to_firmware();
@ -125,17 +103,24 @@ impl<
// Handle a case of power interruption or similar, which lead to a exchange_banks being // Handle a case of power interruption or similar, which lead to a exchange_banks being
// interrupted. // interrupted.
fn handle_exchanging(&mut self, progress: ExchangeProgress) -> Update { fn handle_exchanging(
&mut self,
progress: ExchangeProgress,
) -> Result<Update, <CONTEXT::State as State>::Error> {
log::error!( log::error!(
"Firmware Update was interrupted! Trying to recover with exchange operation: {:?}", "Firmware Update was interrupted! Trying to recover with exchange operation: {:?}",
progress progress
); );
let exchange_result = let exchange_result = self.exchange.exchange::<INTERNAL_PAGE_SIZE>(
self.exchange_banks_with_start(progress.a, progress.b, progress.page_index); &self.config,
&mut self.storage,
&mut self.state,
progress,
);
if exchange_result.is_ok() { Ok(if exchange_result.is_ok() {
let state = self.state.read().update; let state = self.state.read()?.update;
match state { match state {
Update::Exchanging(progress) => { Update::Exchanging(progress) => {
if progress.recovering { if progress.recovering {
@ -152,7 +137,7 @@ impl<
exchange_result exchange_result
); );
Update::Error(UpdateError::ImageExchangeFailed) Update::Error(UpdateError::ImageExchangeFailed)
} })
} }
// Revert the bootable image with the image in index new_firmware. Returns Revert on success if // Revert the bootable image with the image in index new_firmware. Returns Revert on success if
@ -172,7 +157,18 @@ impl<
); );
// Try to exchange the firmware images // Try to exchange the firmware images
let exchange_result = self.exchange_banks(new, old); let exchange_result = self.exchange.exchange::<INTERNAL_PAGE_SIZE>(
&self.config,
&mut self.storage,
&mut self.state,
ExchangeProgress {
a: new,
b: old,
page_index: 0,
recovering: false,
step: ExchangeStep::AToScratch,
},
);
if exchange_result.is_ok() { if exchange_result.is_ok() {
if with_failsafe_revert { if with_failsafe_revert {
// Update Firmware Update State to revert. The Application will set this to // Update Firmware Update State to revert. The Application will set this to
@ -197,102 +193,6 @@ impl<
} }
} }
fn exchange_banks(&mut self, a: Bank, b: Bank) -> Result<(), MemoryError> {
self.exchange_banks_with_start(a, b, 0)
}
fn exchange_banks_with_start(
&mut self,
a: Bank,
b: Bank,
start_index: u32,
) -> Result<(), MemoryError> {
// TODO: Sanity Check start_index
if a.size != b.size {
return Err(MemoryError::BankSizeNotEqual);
}
if a.size == 0 || b.size == 0 {
return Err(MemoryError::BankSizeZero);
}
let size = a.size; // Both are equal
let full_pages = size / INTERNAL_PAGE_SIZE as Address;
let remaining_page_length = size as usize % INTERNAL_PAGE_SIZE;
let mut page_a_buf = [0_u8; INTERNAL_PAGE_SIZE];
let mut page_b_buf = [0_u8; INTERNAL_PAGE_SIZE];
// can we reduce this to 1 buf and fancy operations?
// probably not with the read/write API.
// classic memory exchange problem :)
// Set this in the exchanging part to know whether we are in a recovery process from a
// failed update or on the initial update
let recovering = matches!(self.state.read().update, Update::Revert(_));
// TODO: Fix
let a_location = a.location;
let b_location = b.location;
for page_index in start_index..full_pages {
let offset = page_index * INTERNAL_PAGE_SIZE as u32;
log::trace!(
"Exchange: Page {}, from a ({}) to b ({})",
page_index,
a_location + offset,
b_location + offset
);
self.internal_memory
.read(a_location + offset, &mut page_a_buf)
.map_err(|_| MemoryError::ReadFailure)?;
self.internal_memory
.read(b_location + offset, &mut page_b_buf)
.map_err(|_| MemoryError::ReadFailure)?;
self.internal_memory
.write(a_location + offset, &page_b_buf)
.map_err(|_| MemoryError::WriteFailure)?;
self.internal_memory
.write(b_location + offset, &page_a_buf)
.map_err(|_| MemoryError::WriteFailure)?;
// Store the exchange progress
let mut state = self.state.read();
state.update = Update::Exchanging(ExchangeProgress {
a,
b,
recovering,
page_index,
});
// TODO: Ignore the error here?
let _ = self.state.write(state);
}
// TODO: Fit this into the while loop
if remaining_page_length > 0 {
let offset = full_pages * INTERNAL_PAGE_SIZE as u32;
self.internal_memory
.read(
a.location + offset,
&mut page_a_buf[0..remaining_page_length],
)
.map_err(|_| MemoryError::ReadFailure)?;
self.internal_memory
.read(
b.location + offset,
&mut page_b_buf[0..remaining_page_length],
)
.map_err(|_| MemoryError::ReadFailure)?;
self.internal_memory
.write(a.location + offset, &page_a_buf[0..remaining_page_length])
.map_err(|_| MemoryError::WriteFailure)?;
self.internal_memory
.write(b.location + offset, &page_b_buf[0..remaining_page_length])
.map_err(|_| MemoryError::WriteFailure)?;
}
Ok(())
}
// Jump to the firmware image marked as bootable // Jump to the firmware image marked as bootable
fn jump_to_firmware(&mut self) -> ! { fn jump_to_firmware(&mut self) -> ! {
let app_exec_image = self.config.boot_bank; let app_exec_image = self.config.boot_bank;

22
src/exchange/mod.rs Normal file
View file

@ -0,0 +1,22 @@
pub mod ram;
pub mod scratch;
use crate::{
hardware::Config,
state::{ExchangeProgress, State},
};
use embedded_storage::Storage;
/// Abstraction for the exchange operation of the current state.
pub trait Exchange<STORAGE: Storage, STATE: State> {
type Error: core::fmt::Debug;
fn exchange<const INTERNAL_PAGE_SIZE: usize>(
&mut self,
config: &Config,
storage: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), Self::Error>;
}

129
src/exchange/ram.rs Normal file
View file

@ -0,0 +1,129 @@
use embedded_storage::Storage;
use crate::{
exchange::Exchange,
hardware::Config,
log,
state::{ExchangeProgress, State, Update},
Address,
};
pub struct Ram;
pub enum ExchangeError<STORAGE, STATE> {
Storage(STORAGE),
State(STATE),
}
impl<STORAGE, STATE> core::fmt::Debug for ExchangeError<STORAGE, STATE>
where
STORAGE: core::fmt::Debug,
STATE: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Storage(arg0) => f.debug_tuple("Storage").field(arg0).finish(),
Self::State(arg0) => f.debug_tuple("State").field(arg0).finish(),
}
}
}
impl<STORAGE: Storage, STATE: State> Exchange<STORAGE, STATE> for Ram
where
STORAGE::Error: core::fmt::Debug,
STATE::Error: core::fmt::Debug,
{
type Error = ExchangeError<STORAGE::Error, STATE::Error>;
fn exchange<const INTERNAL_PAGE_SIZE: usize>(
&mut self,
_config: &Config,
storage: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), Self::Error> {
let ExchangeProgress {
a,
b,
page_index,
step,
..
} = progress;
assert_eq!(a.size, b.size);
assert_ne!(a.size, 0);
assert_ne!(b.size, 0);
let size = a.size; // Both are equal
let full_pages = size / INTERNAL_PAGE_SIZE as Address;
let remaining_page_length = size as usize % INTERNAL_PAGE_SIZE;
let mut page_a_buf = [0_u8; INTERNAL_PAGE_SIZE];
let mut page_b_buf = [0_u8; INTERNAL_PAGE_SIZE];
let mut last_state = state.read().map_err(ExchangeError::State)?;
// Set this in the exchanging part to know whether we are in a recovery process from a
// failed update or on the initial update
let recovering = matches!(last_state.update, Update::Revert(_));
for page_index in page_index..full_pages {
let offset = page_index * INTERNAL_PAGE_SIZE as u32;
let a_location = a.location + offset;
let b_location = b.location + offset;
log::trace!(
"Exchange: Page {}, from a ({}) to b ({})",
page_index,
a_location,
b_location
);
storage
.read(a_location, &mut page_a_buf)
.map_err(ExchangeError::Storage)?;
storage
.read(b_location, &mut page_b_buf)
.map_err(ExchangeError::Storage)?;
storage
.write(a_location, &page_b_buf)
.map_err(ExchangeError::Storage)?;
storage
.write(b_location, &page_a_buf)
.map_err(ExchangeError::Storage)?;
// Store the exchange progress
last_state.update = Update::Exchanging(ExchangeProgress {
a,
b,
recovering,
page_index,
step,
});
state.write(&last_state).map_err(ExchangeError::State)?;
}
// TODO: Fit this into the while loop
if remaining_page_length > 0 {
let offset = full_pages * INTERNAL_PAGE_SIZE as u32;
let a_location = a.location + offset;
let b_location = b.location + offset;
storage
.read(a_location, &mut page_a_buf[0..remaining_page_length])
.map_err(ExchangeError::Storage)?;
storage
.read(b_location, &mut page_b_buf[0..remaining_page_length])
.map_err(ExchangeError::Storage)?;
storage
.write(a_location, &page_a_buf[0..remaining_page_length])
.map_err(ExchangeError::Storage)?;
storage
.write(b_location + offset, &page_b_buf[0..remaining_page_length])
.map_err(ExchangeError::Storage)?;
}
Ok(())
}
}

154
src/exchange/scratch.rs Normal file
View file

@ -0,0 +1,154 @@
use core::fmt::Debug;
use embedded_storage::Storage;
use crate::{
exchange::Exchange,
hardware::Config,
log,
state::{ExchangeProgress, ExchangeStep, State, Update},
Address,
};
pub struct Scratch;
pub enum ExchangeError<STORAGE, STATE> {
Storage(STORAGE),
State(STATE),
ScratchInsufficient,
}
impl<STORAGE, STATE> Debug for ExchangeError<STORAGE, STATE>
where
STORAGE: Debug,
STATE: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Storage(arg0) => f.debug_tuple("Storage").field(arg0).finish(),
Self::State(arg0) => f.debug_tuple("State").field(arg0).finish(),
Self::ScratchInsufficient => f.write_str("ScratchInsufficient"),
}
}
}
impl<STORAGE: Storage, STATE: State> Exchange<STORAGE, STATE> for Scratch
where
STORAGE::Error: Debug,
STATE::Error: Debug,
{
type Error = ExchangeError<STORAGE::Error, STATE::Error>;
fn exchange<const INTERNAL_PAGE_SIZE: usize>(
&mut self,
config: &Config,
storage: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), Self::Error> {
let ExchangeProgress {
a,
b,
page_index,
mut step,
..
} = progress;
assert_eq!(a.size, b.size);
assert_ne!(a.size, 0);
assert_ne!(b.size, 0);
if config.scratch_bank.size < INTERNAL_PAGE_SIZE as u32 {
return Err(ExchangeError::ScratchInsufficient);
}
let size = a.size; // Both are equal
let full_pages = size / INTERNAL_PAGE_SIZE as Address;
let remaining_page_length = size as usize % INTERNAL_PAGE_SIZE;
assert_eq!(remaining_page_length, 0);
let mut ram_buf = [0_u8; INTERNAL_PAGE_SIZE];
let mut last_state = state.read().map_err(ExchangeError::State)?;
// Set this in the exchanging part to know whether we are in a recovery process from a
// failed update or on the initial update
let recovering = matches!(last_state.update, Update::Revert(_));
let a_location = a.location;
let b_location = b.location;
let scratch_bank_pages = config.scratch_bank.size / INTERNAL_PAGE_SIZE as u32;
let mut first = true;
for page_index in page_index..full_pages {
let offset = page_index * INTERNAL_PAGE_SIZE as u32;
let a_location = a_location + offset;
let b_location = b_location + offset;
let scratch_index = page_index % scratch_bank_pages;
let scratch_offset = scratch_index * INTERNAL_PAGE_SIZE as u32;
let scratch_location = config.scratch_bank.location + scratch_offset;
log::trace!(
"Exchange: Page {}, from a ({}) to b ({}) using scratch ({})",
page_index,
a_location,
b_location,
scratch_location
);
loop {
if first {
// Do not write the state to flash, as it is already recent.
first = false;
} else {
last_state.update = Update::Exchanging(ExchangeProgress {
a,
b,
recovering,
page_index,
step,
});
state.write(&last_state).map_err(ExchangeError::State)?;
}
match step {
ExchangeStep::AToScratch => {
storage
.read(a_location, &mut ram_buf)
.map_err(ExchangeError::Storage)?;
storage
.write(scratch_location, &ram_buf)
.map_err(ExchangeError::Storage)?;
step = ExchangeStep::BToA;
}
ExchangeStep::BToA => {
storage
.read(b_location, &mut ram_buf)
.map_err(ExchangeError::Storage)?;
storage
.write(a_location, &ram_buf)
.map_err(ExchangeError::Storage)?;
step = ExchangeStep::ScratchToB;
}
ExchangeStep::ScratchToB => {
storage
.read(scratch_location, &mut ram_buf)
.map_err(ExchangeError::Storage)?;
storage
.write(b_location, &ram_buf)
.map_err(ExchangeError::Storage)?;
step = ExchangeStep::AToScratch;
break;
}
}
}
}
Ok(())
}
}

View file

@ -47,6 +47,8 @@ pub struct Config {
pub update_bank: Bank, pub update_bank: Bank,
/// bank the bootloader is contained in, switching between banks /// bank the bootloader is contained in, switching between banks
pub bootloader_bank: Bank, pub bootloader_bank: Bank,
/// bank the pages are temporarily stored when using the `Scratch` exchange method
pub scratch_bank: Bank,
// Initial Image is stored to this bank after first update, restore on failure // Initial Image is stored to this bank after first update, restore on failure
// pub golden_bank: Bank, // pub golden_bank: Bank,
/// section of RAM of this device /// section of RAM of this device

View file

@ -29,15 +29,14 @@ pub mod cortex_m {
fn do_jump(&mut self, address: super::Address) -> ! { fn do_jump(&mut self, address: super::Address) -> ! {
unsafe { unsafe {
// Set Vector Table to new vector table (unsafe but okay here) // Set Vector Table to new vector table (unsafe but okay here)
(*cortex_m::peripheral::SCB::ptr()).vtor.write(address); (*cortex_m::peripheral::SCB::PTR).vtor.write(address);
cortex_m::asm::bootload(address as *const u32); cortex_m::asm::bootload(address as *const u32);
} }
} }
fn setup(&mut self, config: &crate::hardware::Config) { fn setup(&mut self, _config: &crate::hardware::Config) {
// Nothing to do! // Nothing to do!
} }
} }
} }

View file

@ -11,13 +11,17 @@
//!* Automatic Linker Script generation based on a Section/Parition Description in Rust Code //!* Automatic Linker Script generation based on a Section/Parition Description in Rust Code
mod boot; mod boot;
/// Implementations for use in the bootloader /// Implementations for use in the bootloader
pub use boot::MoonbootBoot; pub use boot::MoonbootBoot;
mod manager; mod manager;
/// Implementations for use in the firmware /// Implementations for use in the firmware
pub use manager::MoonbootManager; pub use manager::MoonbootManager;
/// Various processes for exchanging pages
pub mod exchange;
/// Common hardware abstractions and associated implementations /// Common hardware abstractions and associated implementations
pub mod hardware; pub mod hardware;
/// Shared state management between firmware and bootloader /// Shared state management between firmware and bootloader
@ -43,6 +47,13 @@ pub(crate) use defmt as log;
#[cfg(feature = "use-log")] #[cfg(feature = "use-log")]
pub(crate) use logger_crate as log; pub(crate) use logger_crate as log;
pub trait Context {
type Storage: embedded_storage::Storage;
type State: state::State;
type Processor: hardware::processor::Processor;
type Exchange: exchange::Exchange<Self::Storage, Self::State>;
}
#[cfg(not(any(feature = "use-log", feature = "use-defmt")))] #[cfg(not(any(feature = "use-log", feature = "use-defmt")))]
pub(crate) mod log { pub(crate) mod log {
macro_rules! info { macro_rules! info {

View file

@ -1,57 +1,74 @@
use crate::{ use crate::{
hardware::{processor::Processor, Config}, hardware::{processor::Processor, Config},
state::{State, Update}, state::{State, Update},
Context,
}; };
use embedded_storage::{ReadStorage, Storage};
use crate::log; use crate::log;
/// Instantiate this in your application to enable mutation of the State specified in this and jump /// Instantiate this in your application to enable mutation of the State specified in this and jump
/// to the bootloader to apply any updates. /// to the bootloader to apply any updates.
pub struct MoonbootManager< pub struct MoonbootManager<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> {
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> {
config: Config, config: Config,
internal_memory: InternalMemory, storage: CONTEXT::Storage,
state: HardwareState, state: CONTEXT::State,
processor: CPU, processor: CONTEXT::Processor,
} }
impl< #[cfg_attr(feature = "use-defmt", derive(Format))]
InternalMemory: Storage, #[derive(Debug)]
HardwareState: State, pub struct InitError;
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize, #[cfg_attr(feature = "use-defmt", derive(Format))]
> MoonbootManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE> #[derive(Debug)]
pub enum MarkError<E: core::fmt::Debug> {
UpdateQueuedButNotInstalled,
State(E),
}
impl<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize>
MoonbootManager<CONTEXT, INTERNAL_PAGE_SIZE>
{ {
pub fn new( pub fn new(
config: Config, config: Config,
internal_memory: InternalMemory, storage: CONTEXT::Storage,
state: HardwareState, state: CONTEXT::State,
processor: CPU, processor: CONTEXT::Processor,
) -> MoonbootManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE> { ) -> Result<Self, InitError> {
Self { if config.update_bank.size > config.boot_bank.size {
log::error!(
"Requested update bank {:?} is larger than boot bank {:?}",
config.update_bank,
config.boot_bank
);
return Err(InitError);
}
if config.update_bank.size == 0 || config.boot_bank.size == 0 {
log::error!("Requested banks are of zero size");
return Err(InitError);
}
Ok(Self {
config, config,
internal_memory, storage,
state, state,
processor, processor,
} })
} }
/// Destroy this instance of the boot manager and return access to the hardware peripheral /// Destroy this instance of the boot manager and return access to the hardware peripheral
pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) { pub fn destroy(self) -> (CONTEXT::Storage, CONTEXT::State, CONTEXT::Processor) {
(self.internal_memory, self.state, self.processor) (self.storage, self.state, self.processor)
} }
/// Run this immediately after booting your new image successfully to mark the boot as /// Run this immediately after booting your new image successfully to mark the boot as
/// succesful. If you do not do this, any reset will cause the bootloader to restore to the /// succesful. If you do not do this, any reset will cause the bootloader to restore to the
/// previous firmware image. /// previous firmware image.
pub fn mark_boot_successful(&mut self) -> Result<(), ()> { pub fn mark_boot_successful(
let mut current_state = self.state.read(); &mut self,
) -> Result<(), MarkError<<CONTEXT::State as State>::Error>> {
let mut current_state = self.state.read().map_err(MarkError::State)?;
log::info!( log::info!(
"Application running, marking boot as successful. Current state: {:?}", "Application running, marking boot as successful. Current state: {:?}",
@ -69,34 +86,24 @@ impl<
} }
_ => { _ => {
log::error!("There is an update queued, but it has not been installed yet. Did you skip the bootloader?"); log::error!("There is an update queued, but it has not been installed yet. Did you skip the bootloader?");
return Err(()); return Err(MarkError::UpdateQueuedButNotInstalled);
} }
}; };
log::trace!("New state: {:?}", current_state); log::trace!("New state: {:?}", current_state);
self.state.write(current_state) self.state.write(&current_state).map_err(MarkError::State)
} }
// Upgrade firmware verifiying the given signature over the size of size. // Upgrade firmware verifiying the given signature over the size of size.
// Can only return an error or diverge (!, represented by Void while ! is not a type yet) // Can only return an error or diverge (!, represented by Void while ! is not a type yet)
pub fn update(&mut self) -> Result<void::Void, ()> { pub fn update(&mut self) -> Result<void::Void, <CONTEXT::State as State>::Error> {
// Apply the update stored in the update bank // Apply the update stored in the update bank
let bank = self.config.update_bank; let bank = self.config.update_bank;
// TODO: Check size value!
log::info!("Update requested on slot {:?}", bank); log::info!("Update requested on slot {:?}", bank);
if bank.size > self.config.boot_bank.size { let mut current_state = self.state.read()?;
log::error!(
"Requested update bank {:?} is larger than boot bank {:?}",
bank,
self.config.boot_bank
);
return Err(());
}
let mut current_state = self.state.read();
if current_state.update != Update::None { if current_state.update != Update::None {
log::warn!( log::warn!(
@ -107,7 +114,7 @@ impl<
current_state.update = Update::Request(bank); current_state.update = Update::Request(bank);
self.state.write(current_state)?; self.state.write(&current_state)?;
log::info!("Stored update request, jumping to bootloader! Geronimo!"); log::info!("Stored update request, jumping to bootloader! Geronimo!");
@ -124,79 +131,3 @@ impl<
self.processor.do_jump(bootloader_address) self.processor.do_jump(bootloader_address)
} }
} }
/// Easily get read access to the update bank
impl<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> core::convert::AsRef<[u8]>
for MoonbootManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE>
{
#[inline]
fn as_ref(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
self.config.update_bank.location as *const u8,
self.config.update_bank.size as usize,
)
}
}
}
/// Read Access to the current update target slot
impl<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> ReadStorage for MoonbootManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE>
{
type Error = (); // TODO
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let bank = self.config.update_bank; // For now we always write updates to this bank.
if offset > bank.size || offset + bytes.len() as u32 > bank.size {
Err(()) // TODO: We want better error types!
} else {
// TODO! fix
let bank_start = bank.location;
log::info!("Writing at {:x}[{:x}]", bank_start, offset);
match bank.memory_unit {
crate::hardware::MemoryUnit::Internal => {
{ self.internal_memory.read(bank_start + offset, bytes) }.map_err(|_| ())
}
}
}
}
fn capacity(&self) -> usize {
self.config.update_bank.size as usize
}
}
/// Write Access to the current update target slot
impl<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> Storage for MoonbootManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE>
{
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
let bank = self.config.update_bank; // For now we always write updates to this bank.
if offset > bank.size || offset + bytes.len() as u32 > bank.size {
Err(()) // TODO: We want better error types!
} else {
// TODO! fix
let bank_start = bank.location;
log::info!("Writing at {:x}[{:x}]", bank_start, offset);
match bank.memory_unit {
crate::hardware::MemoryUnit::Internal => {
{ self.internal_memory.write(bank_start + offset, bytes) }.map_err(|_| ())
}
}
}
}
}

View file

@ -1,10 +1,14 @@
#[cfg(feature = "ram-state")]
pub mod ram;
use crate::hardware::Bank; use crate::hardware::Bank;
use crate::log;
use core::fmt::Debug;
use crc::{Crc, CRC_32_CKSUM}; use crc::{Crc, CRC_32_CKSUM};
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
use defmt::Format; use defmt::Format;
#[cfg(feature = "ram-state")] #[cfg(any(feature = "ram-state", feature = "scratch-state"))]
use desse::{Desse, DesseSized}; use desse::{Desse, DesseSized};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -14,7 +18,10 @@ use serde::{Deserialize, Serialize};
// exchanged via software updates easily // exchanged via software updates easily
#[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] #[cfg_attr(
any(feature = "ram-state", feature = "scratch-state"),
derive(Desse, DesseSized)
)]
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Update { pub enum Update {
// No update requested, just jump to the application // No update requested, just jump to the application
@ -33,7 +40,10 @@ pub enum Update {
#[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] #[cfg_attr(
any(feature = "ram-state", feature = "scratch-state"),
derive(Desse, DesseSized)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Errors that can occur during update /// Errors that can occur during update
pub enum UpdateError { pub enum UpdateError {
@ -47,10 +57,30 @@ pub enum UpdateError {
InvalidSignature, InvalidSignature,
} }
/// Upcoming operation of the exchange process for a given page index.
#[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(
any(feature = "ram-state", feature = "scratch-state"),
derive(Desse, DesseSized)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExchangeStep {
/// Copy page A to the scratch page.
AToScratch,
/// Copy page B to page A.
BToA,
/// Copy the scratch page to page B.
ScratchToB,
}
/// Store the progress of the current exchange operation /// Store the progress of the current exchange operation
#[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] #[cfg_attr(
any(feature = "ram-state", feature = "scratch-state"),
derive(Desse, DesseSized)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExchangeProgress { pub struct ExchangeProgress {
/// Bank the update is coming from /// Bank the update is coming from
@ -59,6 +89,8 @@ pub struct ExchangeProgress {
pub(crate) b: Bank, pub(crate) b: Bank,
/// Page the operation has last copied /// Page the operation has last copied
pub(crate) page_index: u32, pub(crate) page_index: u32,
/// Upcoming operation of the exchange process for a given page index.
pub(crate) step: ExchangeStep,
/// Whether this exchange resulted from a Request (false) or a Revert (true) /// Whether this exchange resulted from a Request (false) or a Revert (true)
pub(crate) recovering: bool, pub(crate) recovering: bool,
} }
@ -66,7 +98,10 @@ pub struct ExchangeProgress {
/// Struct used to store the state of the bootloader situation in NVM /// Struct used to store the state of the bootloader situation in NVM
#[cfg_attr(feature = "use-defmt", derive(Format))] #[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))] #[cfg_attr(
any(feature = "ram-state", feature = "scratch-state"),
derive(Desse, DesseSized)
)]
#[derive(Debug)] #[derive(Debug)]
pub struct MoonbootState { pub struct MoonbootState {
/// If set Request, an Update is requested. This will exchange the two images, set the update /// If set Request, an Update is requested. This will exchange the two images, set the update
@ -81,10 +116,12 @@ pub struct MoonbootState {
/// RAM. As long as you don't want to perform update download, power cycle the device, and then /// RAM. As long as you don't want to perform update download, power cycle the device, and then
/// apply the update, storing it in volatile memory is fine. /// apply the update, storing it in volatile memory is fine.
pub trait State { pub trait State {
type Error: Debug;
/// Read the shared state /// Read the shared state
fn read(&mut self) -> MoonbootState; fn read(&mut self) -> Result<MoonbootState, Self::Error>;
/// Write the new state to the shared state /// Write the new state to the shared state
fn write(&mut self, data: MoonbootState) -> Result<(), ()>; fn write(&mut self, data: &MoonbootState) -> Result<(), Self::Error>;
} }
/// Size of the serialized state /// Size of the serialized state
@ -96,66 +133,3 @@ const CRC: Crc<StateCrcType> = Crc::<StateCrcType>::new(&CRC_32_CKSUM);
fn checksum(bytes: &[u8]) -> StateCrcType { fn checksum(bytes: &[u8]) -> StateCrcType {
CRC.checksum(bytes) CRC.checksum(bytes)
} }
/// State stored in the RAM
/// TODO: Move to hardware folder together with state trait?
#[cfg(feature = "ram-state")]
pub mod ram {
use super::*;
/// State read and written to RAM. This assumes the device is never powered off / the ram is never
/// reset!
pub struct RamState;
extern "C" {
static mut _moonboot_state_crc_start: StateCrcType;
static mut _moonboot_state_data_start: [u8; STATE_SERIALIZED_MAX_SIZE];
// TODO: Move these as normal variables to linker sections via #[link] macro?
}
impl State for RamState {
fn read(&mut self) -> MoonbootState {
let crc = unsafe { _moonboot_state_crc_start };
log::info!(
"Reading data with len: {}, CRC: {}",
STATE_SERIALIZED_MAX_SIZE,
crc
);
let checksum = checksum(unsafe { &_moonboot_state_data_start });
if crc == checksum {
let data =
MoonbootState::deserialize_from(unsafe { &_moonboot_state_data_start });
log::trace!("CRC Match! {}: {:?}", crc, data);
return data;
} else {
log::trace!("CRC Mismatch! {} vs {}", crc, checksum);
}
MoonbootState {
update: Update::None,
}
}
fn write(&mut self, data: MoonbootState) -> Result<(), ()> {
log::trace!("Writing data {:?}", data);
unsafe { _moonboot_state_data_start = data.serialize() };
log::trace!("Written data: {:?}", unsafe {
&_moonboot_state_data_start
});
unsafe {
_moonboot_state_crc_start = checksum(&_moonboot_state_data_start);
}
log::info!(
"Written len: {}, checksum: {}",
STATE_SERIALIZED_MAX_SIZE,
unsafe { _moonboot_state_crc_start }
);
Ok(())
}
}
}

57
src/state/ram.rs Normal file
View file

@ -0,0 +1,57 @@
use crate::log;
use super::*;
/// State read and written to RAM. This assumes the device is never powered off / the ram is never
/// reset!
pub struct RamState;
extern "C" {
static mut _moonboot_state_crc_start: StateCrcType;
static mut _moonboot_state_data_start: [u8; STATE_SERIALIZED_MAX_SIZE];
// TODO: Move these as normal variables to linker sections via #[link] macro?
}
impl State for RamState {
type Error = void::Void;
fn read(&mut self) -> Result<MoonbootState, void::Void> {
let crc = unsafe { _moonboot_state_crc_start };
log::info!(
"Reading data with len: {}, CRC: {}",
STATE_SERIALIZED_MAX_SIZE,
crc
);
let checksum = checksum(unsafe { &_moonboot_state_data_start });
if crc == checksum {
let data = MoonbootState::deserialize_from(unsafe { &_moonboot_state_data_start });
log::trace!("CRC Match! {}: {:?}", crc, data);
Ok(data)
} else {
log::trace!("CRC Mismatch! {} vs {}", crc, checksum);
Ok(MoonbootState {
update: Update::None,
})
}
}
fn write(&mut self, data: &MoonbootState) -> Result<(), Self::Error> {
log::trace!("Writing data {:?}", data);
unsafe { _moonboot_state_data_start = data.serialize() };
log::trace!("Written data: {:?}", unsafe { &_moonboot_state_data_start });
unsafe {
_moonboot_state_crc_start = checksum(&_moonboot_state_data_start);
}
log::info!(
"Written len: {}, checksum: {}",
STATE_SERIALIZED_MAX_SIZE,
unsafe { _moonboot_state_crc_start }
);
Ok(())
}
}