mirror of
https://github.com/jhbruhn/moonboot.git
synced 2025-03-14 17:45:50 +00:00
Initial commit
This commit is contained in:
commit
a9ca925b97
18 changed files with 1560 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target/
|
499
Cargo.lock
generated
Normal file
499
Cargo.lock
generated
Normal 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
39
Cargo.toml
Normal 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
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# TODO
|
||||
* in the future: golden bank
|
||||
* CRC / Signature check in manager
|
7
TODO.md
Normal file
7
TODO.md
Normal 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
9
codegen/Cargo.toml
Normal 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
4
codegen/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod linker;
|
||||
|
||||
|
||||
|
82
codegen/src/linker.rs
Normal file
82
codegen/src/linker.rs
Normal 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
14
macros/Cargo.toml
Normal 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"] }
|
1
macros/src/attributes/mod.rs
Normal file
1
macros/src/attributes/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod pre_jump_handler;
|
79
macros/src/attributes/pre_jump_handler.rs
Normal file
79
macros/src/attributes/pre_jump_handler.rs
Normal 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
11
macros/src/lib.rs
Normal 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
309
src/boot/mod.rs
Normal 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
61
src/hardware/mod.rs
Normal 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
39
src/hardware/processor.rs
Normal 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
47
src/lib.rs
Normal 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
201
src/manager/mod.rs
Normal 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
154
src/state.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue