From a9ca925b97f08fe02b158452dbad23dc4efa9c21 Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Tue, 19 Apr 2022 10:36:20 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 499 ++++++++++++++++++++++ Cargo.toml | 39 ++ README.md | 3 + TODO.md | 7 + codegen/Cargo.toml | 9 + codegen/src/lib.rs | 4 + codegen/src/linker.rs | 82 ++++ macros/Cargo.toml | 14 + macros/src/attributes/mod.rs | 1 + macros/src/attributes/pre_jump_handler.rs | 79 ++++ macros/src/lib.rs | 11 + src/boot/mod.rs | 309 ++++++++++++++ src/hardware/mod.rs | 61 +++ src/hardware/processor.rs | 39 ++ src/lib.rs | 47 ++ src/manager/mod.rs | 201 +++++++++ src/state.rs | 154 +++++++ 18 files changed, 1560 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 TODO.md create mode 100644 codegen/Cargo.toml create mode 100644 codegen/src/lib.rs create mode 100644 codegen/src/linker.rs create mode 100644 macros/Cargo.toml create mode 100644 macros/src/attributes/mod.rs create mode 100644 macros/src/attributes/pre_jump_handler.rs create mode 100644 macros/src/lib.rs create mode 100644 src/boot/mod.rs create mode 100644 src/hardware/mod.rs create mode 100644 src/hardware/processor.rs create mode 100644 src/lib.rs create mode 100644 src/manager/mod.rs create mode 100644 src/state.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e8fb334 --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9fbea3a --- /dev/null +++ b/Cargo.toml @@ -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", +] diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb2e144 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# TODO +* in the future: golden bank +* CRC / Signature check in manager diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..c1393cf --- /dev/null +++ b/TODO.md @@ -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 diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..5c1a2cb --- /dev/null +++ b/codegen/Cargo.toml @@ -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 = "../" } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..b7af245 --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,4 @@ +pub mod linker; + + + diff --git a/codegen/src/linker.rs b/codegen/src/linker.rs new file mode 100644 index 0000000..c709463 --- /dev/null +++ b/codegen/src/linker.rs @@ -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::(); // 4 byte crc + let data_len_length = core::mem::size_of::(); + 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, + ) +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..cd2ad6a --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["Jan-Henrik Bruhn "] +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"] } diff --git a/macros/src/attributes/mod.rs b/macros/src/attributes/mod.rs new file mode 100644 index 0000000..8512ddc --- /dev/null +++ b/macros/src/attributes/mod.rs @@ -0,0 +1 @@ +pub(crate) mod pre_jump_handler; diff --git a/macros/src/attributes/pre_jump_handler.rs b/macros/src/attributes/pre_jump_handler.rs new file mode 100644 index 0000000..8522600 --- /dev/null +++ b/macros/src/attributes/pre_jump_handler.rs @@ -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() +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..a276dcd --- /dev/null +++ b/macros/src/lib.rs @@ -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) +} + diff --git a/src/boot/mod.rs b/src/boot/mod.rs new file mode 100644 index 0000000..48048f9 --- /dev/null +++ b/src/boot/mod.rs @@ -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 +{ + /// create a new instance of the bootloader + pub fn new( + config: Config, + internal_memory: InternalMemory, + state: HardwareState, + processor: CPU, + ) -> MoonshineBoot { + 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 { + // 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) + } +} diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs new file mode 100644 index 0000000..30704a4 --- /dev/null +++ b/src/hardware/mod.rs @@ -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, +} diff --git a/src/hardware/processor.rs b/src/hardware/processor.rs new file mode 100644 index 0000000..762e7ff --- /dev/null +++ b/src/hardware/processor.rs @@ -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; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8f31de1 --- /dev/null +++ b/src/lib.rs @@ -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() {} diff --git a/src/manager/mod.rs b/src/manager/mod.rs new file mode 100644 index 0000000..b0e2861 --- /dev/null +++ b/src/manager/mod.rs @@ -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 +{ + pub fn new( + config: Config, + internal_memory: InternalMemory, + state: HardwareState, + processor: CPU, + ) -> MoonshineManager { + 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 { + // 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 +{ + #[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 +{ + 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 +{ + 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(|_| ()) + } + } + } + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..291ae79 --- /dev/null +++ b/src/state.rs @@ -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 = Crc::::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(()) + } + } +}