mirror of
https://github.com/jhbruhn/moonboot.git
synced 2025-03-15 01:55:50 +00:00
Merge a301aaa9ea
into efadc79b9e
This commit is contained in:
commit
afb21b7a02
12 changed files with 536 additions and 356 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
194
src/boot/mod.rs
194
src/boot/mod.rs
|
@ -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
22
src/exchange/mod.rs
Normal 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
129
src/exchange/ram.rs
Normal 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
154
src/exchange/scratch.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -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 {
|
||||||
|
|
|
@ -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(¤t_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(¤t_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(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
57
src/state/ram.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue