Use Context trait

This commit is contained in:
Wouter Geraedts 2022-07-11 19:46:45 +02:00
parent 1cd3c4f5be
commit 3d7a4b7cfb
6 changed files with 160 additions and 137 deletions

View file

@ -1,11 +1,10 @@
use crate::{
hardware::processor::Processor,
hardware::{Bank, Config},
state::{Exchange, ExchangeError, ExchangeProgress, ExchangeStep, State, Update, UpdateError},
state::{Exchange, ExchangeProgress, ExchangeStep, State, Update, UpdateError},
Context,
};
use embedded_storage::Storage;
use crate::log;
#[cfg(feature = "defmt")]
@ -13,36 +12,23 @@ use defmt::Format;
/// 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
pub struct MoonbootBoot<
STORAGE: Storage,
STATE: State,
PROCESSOR: Processor, // TODO: Wrap these into a context struct like rubble?
EXCHANGE: Exchange,
const INTERNAL_PAGE_SIZE: usize,
> {
pub struct MoonbootBoot<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> {
config: Config,
storage: STORAGE,
state: STATE,
processor: PROCESSOR,
exchange: EXCHANGE,
storage: CONTEXT::Storage,
state: CONTEXT::State,
processor: CONTEXT::Processor,
exchange: CONTEXT::Exchange,
}
impl<
STORAGE: Storage,
STATE: State,
PROCESSOR: Processor,
EXCHANGE: Exchange,
const INTERNAL_PAGE_SIZE: usize,
> MoonbootBoot<STORAGE, STATE, PROCESSOR, EXCHANGE, INTERNAL_PAGE_SIZE>
{
impl<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> MoonbootBoot<CONTEXT, INTERNAL_PAGE_SIZE> {
/// create a new instance of the bootloader
pub fn new(
config: Config,
storage: STORAGE,
state: STATE,
processor: PROCESSOR,
exchange: EXCHANGE,
) -> MoonbootBoot<STORAGE, STATE, PROCESSOR, EXCHANGE, INTERNAL_PAGE_SIZE> {
storage: CONTEXT::Storage,
state: CONTEXT::State,
processor: CONTEXT::Processor,
exchange: CONTEXT::Exchange,
) -> Self {
Self {
config,
storage,
@ -53,12 +39,12 @@ impl<
}
/// Destroy this instance of the bootloader and return access to the hardware peripheral
pub fn destroy(self) -> (STORAGE, STATE, PROCESSOR) {
pub fn destroy(self) -> (CONTEXT::Storage, CONTEXT::State, CONTEXT::Processor) {
(self.storage, self.state, self.processor)
}
/// Execute the update and boot logic of the bootloader
pub fn boot(&mut self) -> Result<void::Void, STATE::Error> {
pub fn boot(&mut self) -> Result<void::Void, <CONTEXT::State as State>::Error> {
// TODO: consider error handling
log::info!("Booting with moonboot!");
@ -116,19 +102,20 @@ impl<
// Handle a case of power interruption or similar, which lead to a exchange_banks being
// interrupted.
fn handle_exchanging(&mut self, progress: ExchangeProgress) -> Result<Update, STATE::Error> {
fn handle_exchanging(
&mut self,
progress: ExchangeProgress,
) -> Result<Update, <CONTEXT::State as State>::Error> {
log::error!(
"Firmware Update was interrupted! Trying to recover with exchange operation: {:?}",
progress
);
let exchange_result = self
.exchange
.exchange::<STORAGE, STATE, INTERNAL_PAGE_SIZE>(
&mut self.storage,
&mut self.state,
progress,
);
let exchange_result = self.exchange.exchange::<INTERNAL_PAGE_SIZE>(
&mut self.storage,
&mut self.state,
progress,
);
Ok(if exchange_result.is_ok() {
let state = self.state.read()?.update;
@ -168,7 +155,17 @@ impl<
);
// Try to exchange the firmware images
let exchange_result = self.exchange_banks(new, old);
let exchange_result = self.exchange.exchange::<INTERNAL_PAGE_SIZE>(
&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 with_failsafe_revert {
// Update Firmware Update State to revert. The Application will set this to
@ -193,25 +190,6 @@ impl<
}
}
fn exchange_banks(
&mut self,
a: Bank,
b: Bank,
) -> Result<(), ExchangeError<STORAGE::Error, STATE::Error, EXCHANGE::OtherError>> {
self.exchange
.exchange::<STORAGE, STATE, INTERNAL_PAGE_SIZE>(
&mut self.storage,
&mut self.state,
ExchangeProgress {
a,
b,
page_index: 0,
recovering: false,
step: ExchangeStep::AToScratch,
},
)
}
// Jump to the firmware image marked as bootable
fn jump_to_firmware(&mut self) -> ! {
let app_exec_image = self.config.boot_bank;

View file

@ -45,6 +45,13 @@ pub(crate) use defmt as log;
#[cfg(feature = "use-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: state::Exchange<Self::Storage, Self::State>;
}
#[cfg(not(any(feature = "use-log", feature = "use-defmt")))]
pub(crate) mod log {
macro_rules! info {

View file

@ -1,24 +1,18 @@
use crate::{
hardware::{processor::Processor, Config},
state::{State, Update},
Context,
};
use embedded_storage::Storage;
use crate::log;
/// Instantiate this in your application to enable mutation of the State specified in this and jump
/// to the bootloader to apply any updates.
pub struct MoonbootManager<
STORAGE: Storage,
STATE: State,
PROCESSOR: Processor,
const INTERNAL_PAGE_SIZE: usize,
> {
pub struct MoonbootManager<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize> {
config: Config,
storage: STORAGE,
state: STATE,
processor: PROCESSOR,
storage: CONTEXT::Storage,
state: CONTEXT::State,
processor: CONTEXT::Processor,
}
pub struct InitError;
@ -28,24 +22,29 @@ pub enum MarkError<E> {
State(E),
}
impl<STORAGE: Storage, STATE: State, PROCESSOR: Processor, const INTERNAL_PAGE_SIZE: usize>
MoonbootManager<STORAGE, STATE, PROCESSOR, INTERNAL_PAGE_SIZE>
impl<CONTEXT: Context, const INTERNAL_PAGE_SIZE: usize>
MoonbootManager<CONTEXT, INTERNAL_PAGE_SIZE>
{
pub fn new(
config: Config,
storage: STORAGE,
state: STATE,
processor: PROCESSOR,
) -> Result<MoonbootManager<STORAGE, STATE, PROCESSOR, INTERNAL_PAGE_SIZE>, InitError> {
storage: CONTEXT::Storage,
state: CONTEXT::State,
processor: CONTEXT::Processor,
) -> Result<Self, InitError> {
if config.update_bank.size > config.boot_bank.size {
log::error!(
"Requested update bank {:?} is larger than boot bank {:?}",
bank,
self.config.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,
storage,
@ -55,14 +54,16 @@ impl<STORAGE: Storage, STATE: State, PROCESSOR: Processor, const INTERNAL_PAGE_S
}
/// Destroy this instance of the boot manager and return access to the hardware peripheral
pub fn destroy(self) -> (STORAGE, STATE, PROCESSOR) {
pub fn destroy(self) -> (CONTEXT::Storage, CONTEXT::State, CONTEXT::Processor) {
(self.storage, self.state, self.processor)
}
/// 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
/// previous firmware image.
pub fn mark_boot_successful(&mut self) -> Result<(), MarkError<STATE::Error>> {
pub fn mark_boot_successful(
&mut self,
) -> Result<(), MarkError<<CONTEXT::State as State>::Error>> {
let mut current_state = self.state.read().map_err(MarkError::State)?;
log::info!(
@ -92,7 +93,7 @@ impl<STORAGE: Storage, STATE: State, PROCESSOR: Processor, const INTERNAL_PAGE_S
// 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)
pub fn update(&mut self) -> Result<void::Void, STATE::Error> {
pub fn update(&mut self) -> Result<void::Void, <CONTEXT::State as State>::Error> {
// Apply the update stored in the update bank
let bank = self.config.update_bank;

View file

@ -4,28 +4,11 @@ pub mod ram;
#[cfg(feature = "scratch-state")]
pub mod scratch;
use embedded_storage::Storage;
pub enum ExchangeError<STORAGE, STATE, OTHER> {
Storage(STORAGE),
State(STATE),
Other(OTHER),
}
/// Abstraction for the exchange operation of the current state.
pub trait Exchange {
type OtherError;
fn exchange<STORAGE: Storage, STATE: State, const INTERNAL_PAGE_SIZE: usize>(
&mut self,
internal_memory: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), ExchangeError<STORAGE::Error, STATE::Error, Self::OtherError>>;
}
use crate::hardware::Bank;
use core::fmt::Debug;
use embedded_storage::Storage;
use crc::{Crc, CRC_32_CKSUM};
#[cfg(feature = "defmt")]
use defmt::Format;
@ -34,6 +17,18 @@ use desse::{Desse, DesseSized};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Abstraction for the exchange operation of the current state.
pub trait Exchange<STORAGE: Storage, STATE: State> {
type Error: Debug;
fn exchange<const INTERNAL_PAGE_SIZE: usize>(
&mut self,
storage: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), Self::Error>;
}
/// Decision making states for the bootloader
// TODO: Hash + Signature? Should be done on download I think! This way, the algorithms can be
// exchanged via software updates easily

View file

@ -58,15 +58,37 @@ impl State for RamState {
}
}
impl Exchange for RamState {
type OtherError = void::Void;
pub enum ExchangeError<STORAGE, STATE> {
Storage(STORAGE),
State(STATE),
}
fn exchange<STORAGE: Storage, STATE: State, const INTERNAL_PAGE_SIZE: usize>(
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(),
}
}
}
impl<STORAGE: Storage, STATE: State> Exchange<STORAGE, STATE> for RamState
where
STORAGE::Error: Debug,
STATE::Error: Debug,
{
type Error = ExchangeError<STORAGE::Error, STATE::Error>;
fn exchange<const INTERNAL_PAGE_SIZE: usize>(
&mut self,
storage: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), ExchangeError<STORAGE::Error, STATE::Error, Self::OtherError>> {
) -> Result<(), Self::Error> {
let ExchangeProgress {
a,
b,
@ -75,6 +97,10 @@ impl Exchange for RamState {
..
} = 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;

View file

@ -1,11 +1,10 @@
use core::ops::Range;
use core::{fmt::Debug, ops::Range};
use embedded_storage::Storage;
use crate::{
log,
state::{ExchangeProgress, ExchangeStep, State, Update},
swap::{MemoryError, Swap},
state::{Exchange, ExchangeProgress, ExchangeStep, State, Update},
Address,
};
@ -13,29 +12,48 @@ pub struct Scratch<'a> {
pub pages: &'a [Range<Address>],
}
impl<'a> Swap for Scratch<'a> {
fn exchange<InternalMemory: Storage, HardwareState: State, const INTERNAL_PAGE_SIZE: usize>(
pub enum ExchangeError<STORAGE, STATE> {
Storage(STORAGE),
State(STATE),
}
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(),
}
}
}
impl<'a, STORAGE: Storage, STATE: State> Exchange<STORAGE, STATE> for Scratch<'a>
where
STORAGE::Error: Debug,
STATE::Error: Debug,
{
type Error = ExchangeError<STORAGE::Error, STATE::Error>;
fn exchange<const INTERNAL_PAGE_SIZE: usize>(
&mut self,
internal_memory: &mut InternalMemory,
state: &mut HardwareState,
exchange: ExchangeProgress,
) -> Result<(), MemoryError> {
storage: &mut STORAGE,
state: &mut STATE,
progress: ExchangeProgress,
) -> Result<(), Self::Error> {
let ExchangeProgress {
a,
b,
page_index,
mut step,
..
} = exchange;
} = progress;
// 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);
}
assert_eq!(a.size, b.size);
assert_ne!(a.size, 0);
assert_ne!(b.size, 0);
let size = a.size; // Both are equal
@ -46,7 +64,7 @@ impl<'a> Swap for Scratch<'a> {
let mut ram_buf = [0_u8; INTERNAL_PAGE_SIZE];
let mut last_state = state.read();
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
@ -85,37 +103,35 @@ impl<'a> Swap for Scratch<'a> {
page_index,
step,
});
state
.write(&last_state)
.map_err(|_| MemoryError::WriteFailure)?;
state.write(&last_state).map_err(ExchangeError::State)?;
}
match step {
ExchangeStep::AToScratch => {
internal_memory
storage
.read(a_location, &mut ram_buf)
.map_err(|_| MemoryError::ReadFailure)?;
internal_memory
.map_err(ExchangeError::Storage)?;
storage
.write(scratch_location, &ram_buf)
.map_err(|_| MemoryError::WriteFailure)?;
.map_err(ExchangeError::Storage)?;
step = ExchangeStep::BToA;
}
ExchangeStep::BToA => {
internal_memory
storage
.read(b_location, &mut ram_buf)
.map_err(|_| MemoryError::ReadFailure)?;
internal_memory
.map_err(ExchangeError::Storage)?;
storage
.write(a_location, &ram_buf)
.map_err(|_| MemoryError::WriteFailure)?;
.map_err(ExchangeError::Storage)?;
step = ExchangeStep::ScratchToB;
}
ExchangeStep::ScratchToB => {
internal_memory
storage
.read(scratch_location, &mut ram_buf)
.map_err(|_| MemoryError::ReadFailure)?;
internal_memory
.map_err(ExchangeError::Storage)?;
storage
.write(b_location, &ram_buf)
.map_err(|_| MemoryError::WriteFailure)?;
.map_err(ExchangeError::Storage)?;
step = ExchangeStep::AToScratch;
break;
}