Initial commit

This commit is contained in:
Jan-Henrik 2022-04-19 10:36:20 +02:00
commit a9ca925b97
18 changed files with 1560 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

499
Cargo.lock generated Normal file
View file

@ -0,0 +1,499 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "atomic-polyfill"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14bf7b4f565e5e717d7a7a65b2a05c0b8c96e4db636d6f780f03b15108cdd1b"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version",
]
[[package]]
name = "bare-metal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[package]]
name = "bit_field"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cortex-m"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ff967e867ca14eba0c34ac25cd71ea98c678e741e3915d923999bb2fe7c826"
dependencies = [
"bare-metal 0.2.5",
"bitfield",
"embedded-hal",
"volatile-register",
]
[[package]]
name = "crc"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
[[package]]
name = "critical-section"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95da181745b56d4bd339530ec393508910c909c784e8962d15d722bacf0bcbcd"
dependencies = [
"bare-metal 1.0.0",
"cfg-if",
"cortex-m",
"riscv",
]
[[package]]
name = "defmt"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15fe96f5d208164afa70583ff8f062e7697cbbb0b98e5076fbf8ac6da9edff0f"
dependencies = [
"defmt-macros",
"semver 1.0.7",
]
[[package]]
name = "defmt-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd2c3949cb76c25f48c363e61b97f05b317efe3c12fa45d54a6599c3949c85e"
dependencies = [
"defmt-parser",
"proc-macro2 1.0.37",
"quote 1.0.18",
"syn 1.0.91",
]
[[package]]
name = "defmt-parser"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc621c2b4f5f5635e34021c38af2ccb0c1dae38ba11ebee25258de8bb1cee9fe"
[[package]]
name = "desse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4653d7e3dbbf1799510a844266e5841510f8df4b4b29abb4eb09e0523b95780"
dependencies = [
"desse-derive",
]
[[package]]
name = "desse-derive"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3321ed740015e8e1bed6b1af99050f2a68799bd70f453f4f8045cd6bd53667a8"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-storage"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723dce4e9f25b6e6c5f35628e144794e5b459216ed7da97b7c4b66cdb3fa82ca"
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d076121838e03f862871315477528debffdb7462fb229216ecef91b1a3eb31eb"
dependencies = [
"atomic-polyfill",
"hash32",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lock_api"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "moonboot"
version = "0.1.0"
dependencies = [
"cortex-m",
"crc",
"defmt",
"desse",
"embedded-storage",
"heapless",
"log",
"moonboot-macros",
"serde",
"void",
]
[[package]]
name = "moonboot-codegen"
version = "0.1.0"
dependencies = [
"moonboot",
]
[[package]]
name = "moonboot-macros"
version = "0.1.0"
dependencies = [
"proc-macro-error",
"quote 1.0.18",
"syn 1.0.91",
]
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.0.0",
]
[[package]]
name = "nb"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.37",
"quote 1.0.18",
"syn 1.0.91",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2 1.0.37",
"quote 1.0.18",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
"unicode-xid 0.1.0",
]
[[package]]
name = "proc-macro2"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid 0.2.2",
]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
dependencies = [
"proc-macro2 0.4.30",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2 1.0.37",
]
[[package]]
name = "regex"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "riscv"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6907ccdd7a31012b70faf2af85cd9e5ba97657cc3987c4f13f8e4d2c2a088aba"
dependencies = [
"bare-metal 1.0.0",
"bit_field",
"riscv-target",
]
[[package]]
name = "riscv-target"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88aa938cda42a0cf62a20cfe8d139ff1af20c2e681212b5b34adb5a58333f222"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2 1.0.37",
"quote 1.0.18",
"syn 1.0.91",
]
[[package]]
name = "spin"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"unicode-xid 0.1.0",
]
[[package]]
name = "syn"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2 1.0.37",
"quote 1.0.18",
"unicode-xid 0.2.2",
]
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6"
dependencies = [
"vcell",
]

39
Cargo.toml Normal file
View file

@ -0,0 +1,39 @@
[package]
name = "moonboot"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
moonboot-macros = { path = "./macros" }
heapless = {version = "0.7", features = ["serde"] }
serde = { version = "1.0", features = ["derive"], default-features = false, optional = true }
cortex-m = { version = "0.7", optional = true }
defmt = { version = "0.2", optional = true }
logger-crate = { version = "0.4", optional = true, package = "log" }
crc = "2.0"
desse = { version = "0.2.1", optional = true }
void = { version = "1.0", default-features = false }
embedded-storage = "0.2"
[features]
default = ["ram-state"]
use-log = ["logger-crate"]
use-defmt = ["defmt"]
ram-state = ["desse"]
derive = ["serde"]
defmt-default = []
defmt-trace = []
defmt-debug = []
defmt-info = []
defmt-warn = []
defmt-error = []
[workspace]
members = [
".",
"codegen",
]

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# TODO
* in the future: golden bank
* CRC / Signature check in manager

7
TODO.md Normal file
View file

@ -0,0 +1,7 @@
* Add optional copy which is protected against power failure without spearate flash bank
* Implement power-interrupt safe exchange operation with a temporary flash page sdtorage
* More hooks?
* Properly rename to moonboot from moonshine/moonboots
* Use MPU to protect memory regions from invalid access https://github.com/helium/cortex-mpu
* => use pow2::Pow2 for linker scripts?
* Implement signature check in Bootloader

9
codegen/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "moonboot-codegen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
moonboot = { path = "../" }

4
codegen/src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod linker;

82
codegen/src/linker.rs Normal file
View file

@ -0,0 +1,82 @@
use moonboots::{
hardware::{Config, LinkerConfig},
state::{StateCrcType, STATE_SERIALIZED_MAX_SIZE},
Address,
};
fn generate_linker_script(
flash_origin: Address,
flash_length: Address,
ram_origin: Address,
ram_length: Address,
with_ram_state: bool,
) -> String {
let ram_length = ram_length as usize;
if with_ram_state {
let crc_length = core::mem::size_of::<StateCrcType>(); // 4 byte crc
let data_len_length = core::mem::size_of::<u32>();
let state_length = STATE_SERIALIZED_MAX_SIZE;
let ram_length = ram_length as usize - state_length - crc_length - data_len_length;
let state_origin = ram_origin as usize + ram_length;
format!(
"
MEMORY {{
FLASH : ORIGIN = 0x{flash_origin:08x}, LENGTH = {flash_length}
RAM : ORIGIN = 0x{ram_origin:08x}, LENGTH = {ram_length}
MOONSHINE_STATE: ORIGIN = 0x{state_origin:08x}, LENGTH = {state_section_length}
}}
_moonshine_state_crc_start = ORIGIN(MOONSHINE_STATE);
_moonshine_state_len_start = ORIGIN(MOONSHINE_STATE) + {crc_length};
_moonshine_state_data_start = ORIGIN(MOONSHINE_STATE) + {crc_length} + {data_len_length};
PROVIDE(_moonboots_pre_jump = __moonboots_default_pre_jump);
",
flash_origin = flash_origin,
flash_length = flash_length,
ram_origin = ram_origin,
ram_length = ram_length,
state_origin = state_origin,
state_section_length = state_length + crc_length + data_len_length,
data_len_length = data_len_length,
crc_length = crc_length,
)
} else {
format!(
"
MEMORY {{
FLASH : ORIGIN = 0x{flash_origin:08x}, LENGTH = {flash_length}
RAM : ORIGIN = 0x{ram_origin:08x}, LENGTH = {ram_length}
}}
PROVIDE(_moonboots_pre_jump = __moonboots_default_pre_jump);
",
flash_origin = flash_origin,
flash_length = flash_length,
ram_origin = ram_origin,
ram_length = ram_length,
)
}
}
pub fn generate_bootloader_script(config: Config, linker_config: LinkerConfig) -> String {
generate_linker_script(
linker_config.flash_origin + config.bootloader_bank.location,
config.bootloader_bank.size,
linker_config.ram_origin + config.ram_bank.location,
config.ram_bank.size,
linker_config.has_ram_state,
)
}
pub fn generate_application_script(config: Config, linker_config: LinkerConfig) -> String {
// find the bootable image
let bootable_firmware = config.boot_bank;
generate_linker_script(
linker_config.flash_origin + bootable_firmware.location,
bootable_firmware.size,
linker_config.ram_origin + config.ram_bank.location,
config.ram_bank.size,
linker_config.has_ram_state,
)
}

14
macros/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
authors = ["Jan-Henrik Bruhn <hi@jhbruhn.de>"]
description = "moonboot macros"
edition = "2018"
name = "moonboot-macros"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
proc-macro-error = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["full"] }

View file

@ -0,0 +1 @@
pub(crate) mod pre_jump_handler;

View file

@ -0,0 +1,79 @@
use proc_macro::TokenStream;
use proc_macro_error::{abort, abort_call_site};
use quote::quote;
use syn::{parse_macro_input, Attribute, ItemFn, ReturnType};
pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
if !args.is_empty() {
abort_call_site!("`#[moonboots::pre_jump_handler]` attribute takes no arguments");
}
let fun = parse_macro_input!(item as ItemFn);
validate(&fun);
codegen(&fun)
}
fn validate(fun: &ItemFn) {
let is_default_return = matches!(&fun.sig.output, ReturnType::Default);
if fun.sig.constness.is_some()
|| fun.sig.asyncness.is_some()
|| fun.sig.unsafety.is_some()
|| fun.sig.abi.is_some()
|| !fun.sig.generics.params.is_empty()
|| fun.sig.generics.where_clause.is_some()
|| fun.sig.variadic.is_some()
|| !fun.sig.inputs.is_empty()
|| !is_default_return
{
abort!(fun.sig.ident, "function must have signature `fn() -> ()`");
}
check_for_attribute_conflicts(
"pre_jump_handler",
&fun.attrs,
&["export_name", "no_mangle"],
);
}
/// Checks if any attribute in `attrs_to_check` is in `reject_list` and returns a compiler error if there's a match
///
/// The compiler error will indicate that the attribute conflicts with `attr_name`
fn check_for_attribute_conflicts(
attr_name: &str,
attrs_to_check: &[Attribute],
reject_list: &[&str],
) {
for attr in attrs_to_check {
if let Some(ident) = attr.path.get_ident() {
let ident = ident.to_string();
if reject_list.contains(&ident.as_str()) {
abort!(
attr,
"`#[{}]` attribute cannot be used together with `#[{}]`",
attr_name,
ident
)
}
}
}
}
fn codegen(fun: &ItemFn) -> TokenStream {
let attrs = &fun.attrs;
let block = &fun.block;
let ident = &fun.sig.ident;
quote!(
#(#attrs)*
#[export_name = "_moonboots_pre_jump"]
#[inline(never)]
fn #ident() -> () {
#block
}
)
.into()
}

11
macros/src/lib.rs Normal file
View file

@ -0,0 +1,11 @@
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
mod attributes;
#[proc_macro_attribute]
#[proc_macro_error]
pub fn pre_jump_handler(args: TokenStream, item: TokenStream) -> TokenStream {
attributes::pre_jump_handler::expand(args, item)
}

309
src/boot/mod.rs Normal file
View file

@ -0,0 +1,309 @@
use crate::{
hardware::processor::Processor,
hardware::{Bank, Config},
state::{ExchangeProgress, State, Update, UpdateError},
Address,
};
use embedded_storage::Storage;
use crate::log;
#[cfg(feature = "defmt")]
use defmt::Format;
#[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
/// state via the State type and then jumping to the new image using the Jumper specified
pub struct MoonshineBoot<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor, // TODO: Wrap these into a context struct like rubble?
const INTERNAL_PAGE_SIZE: usize,
> {
config: Config,
internal_memory: InternalMemory,
state: HardwareState,
processor: CPU,
}
impl<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> MoonshineBoot<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE>
{
/// create a new instance of the bootloader
pub fn new(
config: Config,
internal_memory: InternalMemory,
state: HardwareState,
processor: CPU,
) -> MoonshineBoot<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE> {
Self {
config,
internal_memory,
state,
processor,
}
}
/// Destroy this instance of the bootloader and return access to the hardware peripheral
pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) {
(self.internal_memory, self.state, self.processor)
}
/// Execute the update and boot logic of the bootloader
pub fn boot(&mut self) -> Result<void::Void, ()> {
// TODO: consider error handling
log::info!("Booting with moonshine!");
self.processor.setup(&self.config);
let mut state = self.state.read();
log::info!("Old State: {:?}", state);
// Step 1: Do things according to update state
state.update = match state.update {
Update::None => self.handle_none(),
Update::Request(bank) => self.handle_request(bank),
Update::Revert(bank) => self.handle_revert(bank),
Update::Exchanging(progress) => self.handle_exchanging(progress),
Update::Error(err) => Update::Error(err),
};
// TODO: Handle Progress Variable in state to recover from power loss
log::info!("New State: {:?}", state);
// Step 2: Update state of Bootloader
self.state.write(state)?;
// Step 3: Jump to new or unchanged firmware
self.jump_to_firmware();
}
// Handle a Update::None Request (no op effectively)
fn handle_none(&mut self) -> Update {
// No update requested -> Nothing to do
log::info!("Nothing to do, jumping straight to firmware!");
Update::None
}
// Handle an Update::Request state, replacing the old firmware with the new one
fn handle_request(&mut self, new_firmware: Bank) -> Update {
log::info!("Update to firmware image {:?} requested.", new_firmware);
self.exchange_firmwares(new_firmware, true)
}
// Handle a revert request because booting of the new firmware failed
fn handle_revert(&mut self, revert_firmware: Bank) -> Update {
// Exchange failed update firmware with old firmware image, on success return None so
// firmware and bootloader functions as usual
log::warn!(
"Firmware did not reset state from Revert to None, something went wrong after update!"
);
log::info!(
"Reverting to previous firmware image {:?}.",
revert_firmware
);
self.exchange_firmwares(revert_firmware, false)
}
// Handle a case of power interruption or similar, which lead to a exchange_banks being
// interrupted.
fn handle_exchanging(&mut self, progress: ExchangeProgress) -> Update {
log::error!(
"Firmware Update was interrupted! Trying to recover with exchange operation: {:?}",
progress
);
let exchange_result =
self.exchange_banks_with_start(progress.a, progress.b, progress.page_index);
if exchange_result.is_ok() {
let state = self.state.read().update;
match state {
Update::Exchanging(progress) => {
if progress.recovering {
Update::None
} else {
Update::Revert(progress.a)
}
}
_ => Update::Error(UpdateError::InvalidState),
}
} else {
log::error!(
"Could not recover from failed update, Error: {:?}",
exchange_result
);
Update::Error(UpdateError::ImageExchangeFailed)
}
}
// Revert the bootable image with the image in index new_firmware. Returns Revert on success if
// with_failsafe_revert is true, returns None if with_failsafe_revert ist false
fn exchange_firmwares(&mut self, new: Bank, with_failsafe_revert: bool) -> Update {
let old = self.config.boot_bank;
// An update from new_firmware to the bootable firmware was requested.
if new != old
// TODO: sanity check firmware is not updated with itself && new_firmware != self.hardware.bootable_image
{
log::info!(
"Exchanging bootable firmware image slot (address: 0x{:x}, size: {}K) with image (address: 0x{:x}, size: {}K).",
old.location,
old.size / 1024,
new.location,
new.size / 1024
);
// Try to exchange the firmware images
let exchange_result = self.exchange_banks(new, old);
if exchange_result.is_ok() {
if with_failsafe_revert {
// Update Firmware Update State to revert. The Application will set this to
// None on successful boot. If we go into the bootloader again and this is still
// set, something is wrong with the new application, so we will revert!
Update::Revert(new)
} else {
// Reverting to the new firmware, boot as usual to let the firmware try an
// update again
Update::None
}
} else {
log::error!(
"Failed to exchange firmware images due to a hardware error: {:?}",
exchange_result
);
Update::Error(UpdateError::ImageExchangeFailed)
}
} else {
log::error!("An invalid image index has been specified during update or revert!");
Update::Error(UpdateError::InvalidImageIndex)
}
}
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
fn jump_to_firmware(&mut self) -> ! {
let app_exec_image = self.config.boot_bank;
let app_address = app_exec_image.location;
log::info!("Jumping to firmware at {:x}", app_address);
log::info!("Executing pre jump handler.");
extern "Rust" {
fn _moonboots_pre_jump();
}
unsafe {
_moonboots_pre_jump();
}
self.processor.do_jump(app_address)
}
}

61
src/hardware/mod.rs Normal file
View file

@ -0,0 +1,61 @@
pub mod processor;
use crate::Address;
#[cfg(feature = "defmt")]
use defmt::Format;
#[cfg(feature = "ram-state")]
use desse::{Desse, DesseSized};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum MemoryUnit {
Internal,
// External(usize) // nth external unit
}
#[cfg_attr(feature = "defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Bank {
// TODO: Hide members?
/// Starting address of this Bank
pub location: Address,
/// Size of this Bank
pub size: Address, // TODO: Use NonZeroU32 to remove checks?
/// In which memory unit this bank is stored
pub memory_unit: MemoryUnit,
}
#[cfg_attr(feature = "defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Config {
/// bank this bootloader jumps to
pub boot_bank: Bank,
/// alternative bank we write the new firmware image to
pub update_bank: Bank,
/// bank the bootloader is contained in
pub bootloader_bank: Bank,
// Initial Image is stored to this bank after first update, restore on failure
// pub golden_bank: Bank,
/// section of RAM of this device
pub ram_bank: Bank,
}
#[cfg_attr(feature = "defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct LinkerConfig {
pub flash_origin: Address,
pub ram_origin: Address,
// TODO enable via feature flag?
pub has_ram_state: bool,
}

39
src/hardware/processor.rs Normal file
View file

@ -0,0 +1,39 @@
use crate::Address;
// This trait executes an execution jump to the specified startign address.
// The implementation is ISA-dependent.
pub trait Processor {
fn do_jump(&mut self, address: Address) -> !;
fn setup(&mut self, config: &crate::hardware::Config);
}
#[cfg(feature = "cortex-m")]
mod cortex_m {
use super::Processor;
pub struct CortexM {}
impl CortexM {
pub fn new() -> Self {
Self {}
}
}
impl Processor for CortexM {
fn do_jump(&mut self, address: super::Address) -> ! {
unsafe {
// Set Vector Table to new vector table (unsafe but okay here)
(*cortex_m::peripheral::SCB::ptr()).vtor.write(address);
cortex_m::asm::bootload(address as *const u32);
}
}
fn setup(&mut self, config: &crate::hardware::Config) {
// Nothing to do!
}
}
}
#[cfg(feature = "cortex-m")]
// A Jumper implementation for use with cortex-m processors
pub use cortex_m::CortexM;

47
src/lib.rs Normal file
View file

@ -0,0 +1,47 @@
#![no_std]
pub mod boot;
pub mod hardware;
pub mod manager;
pub mod state;
pub use embedded_storage; // TODO
/// Because most of the time, ...
pub use boot as left_boot;
/// ... there's two boots involved.
pub use manager as right_boot;
// Address type in RAM or ROM
pub type Address = u32;
pub use moonboot_macros::pre_jump_handler;
#[cfg(feature = "use-defmt")]
pub(crate) use defmt as log;
#[cfg(feature = "use-log")]
pub(crate) use logger_crate as log;
#[cfg(not(any(feature = "use-log", feature = "use-defmt")))]
pub(crate) mod log {
macro_rules! info {
( $( $x:expr ),* ) => {};
}
pub(crate) use info;
macro_rules! trace {
( $( $x:expr ),* ) => {};
}
pub(crate) use trace;
macro_rules! error {
( $( $x:expr ),* ) => {};
}
pub(crate) use error;
macro_rules! warner {
( $( $x:expr ),* ) => {};
}
pub(crate) use warner as warn;
}
#[export_name = "__moonboots_default_pre_jump"]
fn default_pre_jump() {}

201
src/manager/mod.rs Normal file
View file

@ -0,0 +1,201 @@
use crate::{
hardware::{processor::Processor, Config},
state::{State, Update},
};
use embedded_storage::{ReadStorage, 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 MoonshineManager<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> {
config: Config,
internal_memory: InternalMemory,
state: HardwareState,
processor: CPU,
}
impl<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> MoonshineManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE>
{
pub fn new(
config: Config,
internal_memory: InternalMemory,
state: HardwareState,
processor: CPU,
) -> MoonshineManager<InternalMemory, HardwareState, CPU, INTERNAL_PAGE_SIZE> {
Self {
config,
internal_memory,
state,
processor,
}
}
/// Destroy this instance of the boot manager and return access to the hardware peripheral
pub fn destroy(self) -> (InternalMemory, HardwareState, CPU) {
(self.internal_memory, 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<(), ()> {
let mut current_state = self.state.read();
log::info!(
"Application running, marking boot as successful. Current state: {:?}",
current_state
);
current_state.update = match current_state.update {
Update::None => {
log::info!("No Update was done.");
Update::None
}
Update::Revert(_) => {
log::info!("Software was updated, marking as successful.");
Update::None
}
_ => {
log::error!("There is an update queued, but it has not been installed yet. Did you skip the bootloader?");
return Err(());
}
};
log::trace!("New state: {:?}", current_state);
self.state.write(current_state)
}
// 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, ()> {
// Apply the update stored in the update bank
let bank = self.config.update_bank;
// TODO: Check size value!
log::info!("Update requested on slot {:?}", bank);
if bank.size > self.config.boot_bank.size {
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 {
log::warn!(
"There is already an update in progress or queued: {:?}",
current_state.update
);
}
current_state.update = Update::Request(bank);
self.state.write(current_state)?;
log::info!("Stored update request, jumping to bootloader! Geronimo!");
let bootloader_address = self.config.bootloader_bank.location;
log::info!("Executing pre jump handler.");
extern "Rust" {
fn _moonboots_pre_jump();
}
unsafe {
_moonboots_pre_jump();
}
self.processor.do_jump(bootloader_address)
}
}
impl<
InternalMemory: Storage,
HardwareState: State,
CPU: Processor,
const INTERNAL_PAGE_SIZE: usize,
> core::convert::AsRef<[u8]>
for MoonshineManager<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 MoonshineManager<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 MoonshineManager<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(|_| ())
}
}
}
}
}

154
src/state.rs Normal file
View file

@ -0,0 +1,154 @@
use crate::hardware::Bank;
use crate::log;
use crc::{Crc, CRC_32_CKSUM};
#[cfg(feature = "defmt")]
use defmt::Format;
#[cfg(feature = "ram-state")]
use desse::{Desse, DesseSized};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
// Decision making for the bootloader
// TODO: Hash + Signature? Should be done on download I think! This way, the algorithms can be
// exchanged via software updates easily
#[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, PartialEq, Eq)]
pub enum Update {
// No update requested, just jump to the application
None,
// Exchange the current boot image with the one from image specified as index, a signature to
// verify against and the size of the firmware image to make the firmware signature
// verification succeed
Request(Bank),
// Revert the current boot image to the from image specified as index
Revert(Bank),
// An Exchange Operation is in Progress or was interrupted
Exchanging(ExchangeProgress),
// An Error during the update has occured!
Error(UpdateError),
}
#[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
// Errors that can occur during update
pub enum UpdateError {
// A wrong Image Index has been specified
InvalidImageIndex,
// Failed to exchange the new image with the old one
ImageExchangeFailed,
// Something f'ed up the internal state
InvalidState,
// The Signature provided does not match the PublicKey or Image.
InvalidSignature,
}
// Store the progress of the current exchange operation
#[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExchangeProgress {
// Bank the update is coming from
pub(crate) a: Bank,
// Bank the update is going to
pub(crate) b: Bank,
// Page the operation has last copied
pub(crate) page_index: u32,
// Whether this exchange resulted from a Request (false) or a Revert (true)
pub(crate) recovering: bool,
}
// Struct used to store the state of the bootloader situation in NVM
#[cfg_attr(feature = "use-defmt", derive(Format))]
#[cfg_attr(feature = "derive", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "ram-state", derive(Desse, DesseSized))]
#[derive(Debug)]
pub struct MoonshineState {
// If set Request, an Update is requested. This will exchange the two images, set the update
// state to Revert and start the application. The application then has to set this state to
// None and store it. If something went wrong and the boot attempt results in a restart of
// the bootloader, the bootloader starts with this variable set to Revert and thus exchanges
// the two images again, doing a downgrade because of a failed boot
pub update: Update,
}
pub trait State {
fn read(&mut self) -> MoonshineState;
fn write(&mut self, data: MoonshineState) -> Result<(), ()>;
}
pub const STATE_SERIALIZED_MAX_SIZE: usize = MoonshineState::SIZE;
pub type StateCrcType = u32;
const CRC: Crc<StateCrcType> = Crc::<StateCrcType>::new(&CRC_32_CKSUM);
fn checksum(bytes: &[u8]) -> StateCrcType {
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 _moonshine_state_crc_start: StateCrcType;
static mut _moonshine_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) -> MoonshineState {
let crc = unsafe { _moonshine_state_crc_start };
log::info!(
"Reading data with len: {}, CRC: {}",
STATE_SERIALIZED_MAX_SIZE,
crc
);
let checksum = checksum(unsafe { &_moonshine_state_data_start });
if crc == checksum {
let data =
MoonshineState::deserialize_from(unsafe { &_moonshine_state_data_start });
log::trace!("CRC Match! {}: {:?}", crc, data);
return data;
} else {
log::trace!("CRC Mismatch! {} vs {}", crc, checksum);
}
MoonshineState {
update: Update::None,
}
}
fn write(&mut self, data: MoonshineState) -> Result<(), ()> {
log::trace!("Writing data {:?}", data);
unsafe { _moonshine_state_data_start = data.serialize() };
log::trace!("Written data: {:?}", unsafe {
&_moonshine_state_data_start
});
unsafe {
_moonshine_state_crc_start = checksum(&_moonshine_state_data_start);
}
log::info!(
"Written len: {}, checksum: {}",
STATE_SERIALIZED_MAX_SIZE,
unsafe { _moonshine_state_crc_start }
);
Ok(())
}
}
}