diff options
| author | Frostie314159 <[email protected]> | 2024-03-31 20:48:05 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-03-31 20:48:05 +0200 |
| commit | 67c9cc2c4b886e6962ecdd6eff8794b14c1accdc (patch) | |
| tree | f176ab269949d26f48e04c950cebc5489bae8c56 /embassy-boot | |
| parent | a2f9aa592ec61beb247065003016515f0d423c13 (diff) | |
| parent | 6634cc90bcd3eb25b64712688920f383584b2964 (diff) | |
Merge branch 'embassy-rs:main' into ticker_send_sync
Diffstat (limited to 'embassy-boot')
| -rw-r--r-- | embassy-boot/Cargo.toml (renamed from embassy-boot/boot/Cargo.toml) | 24 | ||||
| -rw-r--r-- | embassy-boot/README.md | 35 | ||||
| -rw-r--r-- | embassy-boot/boot/README.md | 31 | ||||
| -rw-r--r-- | embassy-boot/nrf/Cargo.toml | 38 | ||||
| -rw-r--r-- | embassy-boot/nrf/README.md | 26 | ||||
| -rw-r--r-- | embassy-boot/nrf/src/fmt.rs | 258 | ||||
| -rw-r--r-- | embassy-boot/nrf/src/lib.rs | 145 | ||||
| -rw-r--r-- | embassy-boot/rp/Cargo.toml | 73 | ||||
| -rw-r--r-- | embassy-boot/rp/README.md | 26 | ||||
| -rw-r--r-- | embassy-boot/rp/build.rs | 8 | ||||
| -rw-r--r-- | embassy-boot/rp/src/fmt.rs | 258 | ||||
| -rw-r--r-- | embassy-boot/rp/src/lib.rs | 90 | ||||
| -rw-r--r-- | embassy-boot/src/boot_loader.rs (renamed from embassy-boot/boot/src/boot_loader.rs) | 119 | ||||
| -rw-r--r-- | embassy-boot/src/digest_adapters/ed25519_dalek.rs (renamed from embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs) | 4 | ||||
| -rw-r--r-- | embassy-boot/src/digest_adapters/mod.rs (renamed from embassy-boot/boot/src/digest_adapters/mod.rs) | 0 | ||||
| -rw-r--r-- | embassy-boot/src/digest_adapters/salty.rs (renamed from embassy-boot/boot/src/digest_adapters/salty.rs) | 0 | ||||
| -rw-r--r-- | embassy-boot/src/firmware_updater/asynch.rs (renamed from embassy-boot/boot/src/firmware_updater/asynch.rs) | 215 | ||||
| -rw-r--r-- | embassy-boot/src/firmware_updater/blocking.rs (renamed from embassy-boot/boot/src/firmware_updater/blocking.rs) | 231 | ||||
| -rw-r--r-- | embassy-boot/src/firmware_updater/mod.rs (renamed from embassy-boot/boot/src/firmware_updater/mod.rs) | 2 | ||||
| -rw-r--r-- | embassy-boot/src/fmt.rs (renamed from embassy-boot/boot/src/fmt.rs) | 3 | ||||
| -rw-r--r-- | embassy-boot/src/lib.rs (renamed from embassy-boot/boot/src/lib.rs) | 11 | ||||
| -rw-r--r-- | embassy-boot/src/mem_flash.rs (renamed from embassy-boot/boot/src/mem_flash.rs) | 0 | ||||
| -rw-r--r-- | embassy-boot/src/test_flash/asynch.rs (renamed from embassy-boot/boot/src/test_flash/asynch.rs) | 0 | ||||
| -rw-r--r-- | embassy-boot/src/test_flash/blocking.rs (renamed from embassy-boot/boot/src/test_flash/blocking.rs) | 0 | ||||
| -rw-r--r-- | embassy-boot/src/test_flash/mod.rs (renamed from embassy-boot/boot/src/test_flash/mod.rs) | 0 | ||||
| -rw-r--r-- | embassy-boot/stm32/Cargo.toml | 64 | ||||
| -rw-r--r-- | embassy-boot/stm32/README.md | 24 | ||||
| -rw-r--r-- | embassy-boot/stm32/build.rs | 8 | ||||
| -rw-r--r-- | embassy-boot/stm32/src/fmt.rs | 258 | ||||
| -rw-r--r-- | embassy-boot/stm32/src/lib.rs | 41 |
30 files changed, 516 insertions, 1476 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/Cargo.toml index dd2ff8158..242caa229 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/Cargo.toml | |||
| @@ -1,10 +1,11 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | edition = "2021" | 2 | edition = "2021" |
| 3 | name = "embassy-boot" | 3 | name = "embassy-boot" |
| 4 | version = "0.1.1" | 4 | version = "0.2.0" |
| 5 | description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." | 5 | description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." |
| 6 | license = "MIT OR Apache-2.0" | 6 | license = "MIT OR Apache-2.0" |
| 7 | repository = "https://github.com/embassy-rs/embassy" | 7 | repository = "https://github.com/embassy-rs/embassy" |
| 8 | documentation = "https://docs.embassy.dev/embassy-boot" | ||
| 8 | categories = [ | 9 | categories = [ |
| 9 | "embedded", | 10 | "embedded", |
| 10 | "no-std", | 11 | "no-std", |
| @@ -12,8 +13,8 @@ categories = [ | |||
| 12 | ] | 13 | ] |
| 13 | 14 | ||
| 14 | [package.metadata.embassy_docs] | 15 | [package.metadata.embassy_docs] |
| 15 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" | 16 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/src/" |
| 16 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" | 17 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/src/" |
| 17 | target = "thumbv7em-none-eabi" | 18 | target = "thumbv7em-none-eabi" |
| 18 | features = ["defmt"] | 19 | features = ["defmt"] |
| 19 | 20 | ||
| @@ -26,25 +27,22 @@ features = ["defmt"] | |||
| 26 | defmt = { version = "0.3", optional = true } | 27 | defmt = { version = "0.3", optional = true } |
| 27 | digest = "0.10" | 28 | digest = "0.10" |
| 28 | log = { version = "0.4", optional = true } | 29 | log = { version = "0.4", optional = true } |
| 29 | ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } | 30 | ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } |
| 30 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } | 31 | embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } |
| 31 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } | 32 | embassy-sync = { version = "0.5.0", path = "../embassy-sync" } |
| 32 | embedded-storage = "0.3.1" | 33 | embedded-storage = "0.3.1" |
| 33 | embedded-storage-async = { version = "0.4.1" } | 34 | embedded-storage-async = { version = "0.4.1" } |
| 34 | salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } | 35 | salty = { version = "0.3", optional = true } |
| 35 | signature = { version = "1.6.4", default-features = false } | 36 | signature = { version = "2.0", default-features = false } |
| 36 | 37 | ||
| 37 | [dev-dependencies] | 38 | [dev-dependencies] |
| 38 | log = "0.4" | 39 | log = "0.4" |
| 39 | env_logger = "0.9" | 40 | env_logger = "0.9" |
| 40 | rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version | 41 | rand = "0.8" |
| 41 | futures = { version = "0.3", features = ["executor"] } | 42 | futures = { version = "0.3", features = ["executor"] } |
| 42 | sha1 = "0.10.5" | 43 | sha1 = "0.10.5" |
| 43 | critical-section = { version = "1.1.1", features = ["std"] } | 44 | critical-section = { version = "1.1.1", features = ["std"] } |
| 44 | 45 | ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } | |
| 45 | [dev-dependencies.ed25519-dalek] | ||
| 46 | default_features = false | ||
| 47 | features = ["rand", "std", "u32_backend"] | ||
| 48 | 46 | ||
| 49 | [features] | 47 | [features] |
| 50 | ed25519-dalek = ["dep:ed25519-dalek", "_verify"] | 48 | ed25519-dalek = ["dep:ed25519-dalek", "_verify"] |
diff --git a/embassy-boot/README.md b/embassy-boot/README.md new file mode 100644 index 000000000..3c2d45e96 --- /dev/null +++ b/embassy-boot/README.md | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | # embassy-boot | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. | ||
| 6 | |||
| 7 | The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. | ||
| 8 | |||
| 9 | By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. | ||
| 10 | |||
| 11 | ## Overview | ||
| 12 | |||
| 13 | The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: | ||
| 14 | |||
| 15 | * BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. | ||
| 16 | * ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application. | ||
| 17 | * DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition. | ||
| 18 | * BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. | ||
| 19 | |||
| 20 | For any partition, the following preconditions are required: | ||
| 21 | |||
| 22 | * Partitions must be aligned on the page size. | ||
| 23 | * Partitions must be a multiple of the page size. | ||
| 24 | |||
| 25 | The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application. | ||
| 26 | |||
| 27 | For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html). | ||
| 28 | |||
| 29 | ## Hardware support | ||
| 30 | |||
| 31 | The bootloader supports different hardware in separate crates: | ||
| 32 | |||
| 33 | * `embassy-boot-nrf` - for the nRF microcontrollers. | ||
| 34 | * `embassy-boot-rp` - for the RP2040 microcontrollers. | ||
| 35 | * `embassy-boot-stm32` - for the STM32 microcontrollers. | ||
diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md deleted file mode 100644 index 07755bc6c..000000000 --- a/embassy-boot/boot/README.md +++ /dev/null | |||
| @@ -1,31 +0,0 @@ | |||
| 1 | # embassy-boot | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. | ||
| 6 | |||
| 7 | The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. | ||
| 8 | |||
| 9 | By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. | ||
| 10 | |||
| 11 | ## Hardware support | ||
| 12 | |||
| 13 | The bootloader supports different hardware in separate crates: | ||
| 14 | |||
| 15 | * `embassy-boot-nrf` - for the nRF microcontrollers. | ||
| 16 | * `embassy-boot-rp` - for the RP2040 microcontrollers. | ||
| 17 | * `embassy-boot-stm32` - for the STM32 microcontrollers. | ||
| 18 | |||
| 19 | ## Minimum supported Rust version (MSRV) | ||
| 20 | |||
| 21 | `embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 22 | |||
| 23 | ## License | ||
| 24 | |||
| 25 | This work is licensed under either of | ||
| 26 | |||
| 27 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 28 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 29 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 30 | |||
| 31 | at your option. | ||
diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml deleted file mode 100644 index eea29cf2e..000000000 --- a/embassy-boot/nrf/Cargo.toml +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-boot-nrf" | ||
| 4 | version = "0.1.0" | ||
| 5 | description = "Bootloader lib for nRF chips" | ||
| 6 | license = "MIT OR Apache-2.0" | ||
| 7 | |||
| 8 | [package.metadata.embassy_docs] | ||
| 9 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" | ||
| 10 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/nrf/src/" | ||
| 11 | features = ["embassy-nrf/nrf52840"] | ||
| 12 | target = "thumbv7em-none-eabi" | ||
| 13 | |||
| 14 | [lib] | ||
| 15 | |||
| 16 | [dependencies] | ||
| 17 | defmt = { version = "0.3", optional = true } | ||
| 18 | |||
| 19 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } | ||
| 20 | embassy-nrf = { path = "../../embassy-nrf" } | ||
| 21 | embassy-boot = { path = "../boot", default-features = false } | ||
| 22 | cortex-m = { version = "0.7.6" } | ||
| 23 | cortex-m-rt = { version = "0.7" } | ||
| 24 | embedded-storage = "0.3.1" | ||
| 25 | embedded-storage-async = { version = "0.4.1" } | ||
| 26 | cfg-if = "1.0.0" | ||
| 27 | |||
| 28 | nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true } | ||
| 29 | |||
| 30 | [features] | ||
| 31 | defmt = [ | ||
| 32 | "dep:defmt", | ||
| 33 | "embassy-boot/defmt", | ||
| 34 | "embassy-nrf/defmt", | ||
| 35 | ] | ||
| 36 | softdevice = [ | ||
| 37 | "nrf-softdevice-mbr", | ||
| 38 | ] | ||
diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md deleted file mode 100644 index fe581823d..000000000 --- a/embassy-boot/nrf/README.md +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | # embassy-boot-nrf | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | An adaptation of `embassy-boot` for nRF. | ||
| 6 | |||
| 7 | ## Features | ||
| 8 | |||
| 9 | * Load applications with or without the softdevice. | ||
| 10 | * Configure bootloader partitions based on linker script. | ||
| 11 | * Using watchdog timer to detect application failure. | ||
| 12 | |||
| 13 | |||
| 14 | ## Minimum supported Rust version (MSRV) | ||
| 15 | |||
| 16 | `embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 17 | |||
| 18 | ## License | ||
| 19 | |||
| 20 | This work is licensed under either of | ||
| 21 | |||
| 22 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 23 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 24 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 25 | |||
| 26 | at your option. | ||
diff --git a/embassy-boot/nrf/src/fmt.rs b/embassy-boot/nrf/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/nrf/src/fmt.rs +++ /dev/null | |||
| @@ -1,258 +0,0 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | ||
| 5 | |||
| 6 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 7 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 8 | |||
| 9 | macro_rules! assert { | ||
| 10 | ($($x:tt)*) => { | ||
| 11 | { | ||
| 12 | #[cfg(not(feature = "defmt"))] | ||
| 13 | ::core::assert!($($x)*); | ||
| 14 | #[cfg(feature = "defmt")] | ||
| 15 | ::defmt::assert!($($x)*); | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 20 | macro_rules! assert_eq { | ||
| 21 | ($($x:tt)*) => { | ||
| 22 | { | ||
| 23 | #[cfg(not(feature = "defmt"))] | ||
| 24 | ::core::assert_eq!($($x)*); | ||
| 25 | #[cfg(feature = "defmt")] | ||
| 26 | ::defmt::assert_eq!($($x)*); | ||
| 27 | } | ||
| 28 | }; | ||
| 29 | } | ||
| 30 | |||
| 31 | macro_rules! assert_ne { | ||
| 32 | ($($x:tt)*) => { | ||
| 33 | { | ||
| 34 | #[cfg(not(feature = "defmt"))] | ||
| 35 | ::core::assert_ne!($($x)*); | ||
| 36 | #[cfg(feature = "defmt")] | ||
| 37 | ::defmt::assert_ne!($($x)*); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | } | ||
| 41 | |||
| 42 | macro_rules! debug_assert { | ||
| 43 | ($($x:tt)*) => { | ||
| 44 | { | ||
| 45 | #[cfg(not(feature = "defmt"))] | ||
| 46 | ::core::debug_assert!($($x)*); | ||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | ::defmt::debug_assert!($($x)*); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | } | ||
| 52 | |||
| 53 | macro_rules! debug_assert_eq { | ||
| 54 | ($($x:tt)*) => { | ||
| 55 | { | ||
| 56 | #[cfg(not(feature = "defmt"))] | ||
| 57 | ::core::debug_assert_eq!($($x)*); | ||
| 58 | #[cfg(feature = "defmt")] | ||
| 59 | ::defmt::debug_assert_eq!($($x)*); | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | } | ||
| 63 | |||
| 64 | macro_rules! debug_assert_ne { | ||
| 65 | ($($x:tt)*) => { | ||
| 66 | { | ||
| 67 | #[cfg(not(feature = "defmt"))] | ||
| 68 | ::core::debug_assert_ne!($($x)*); | ||
| 69 | #[cfg(feature = "defmt")] | ||
| 70 | ::defmt::debug_assert_ne!($($x)*); | ||
| 71 | } | ||
| 72 | }; | ||
| 73 | } | ||
| 74 | |||
| 75 | macro_rules! todo { | ||
| 76 | ($($x:tt)*) => { | ||
| 77 | { | ||
| 78 | #[cfg(not(feature = "defmt"))] | ||
| 79 | ::core::todo!($($x)*); | ||
| 80 | #[cfg(feature = "defmt")] | ||
| 81 | ::defmt::todo!($($x)*); | ||
| 82 | } | ||
| 83 | }; | ||
| 84 | } | ||
| 85 | |||
| 86 | #[cfg(not(feature = "defmt"))] | ||
| 87 | macro_rules! unreachable { | ||
| 88 | ($($x:tt)*) => { | ||
| 89 | ::core::unreachable!($($x)*) | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | #[cfg(feature = "defmt")] | ||
| 94 | macro_rules! unreachable { | ||
| 95 | ($($x:tt)*) => { | ||
| 96 | ::defmt::unreachable!($($x)*) | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | macro_rules! panic { | ||
| 101 | ($($x:tt)*) => { | ||
| 102 | { | ||
| 103 | #[cfg(not(feature = "defmt"))] | ||
| 104 | ::core::panic!($($x)*); | ||
| 105 | #[cfg(feature = "defmt")] | ||
| 106 | ::defmt::panic!($($x)*); | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | macro_rules! trace { | ||
| 112 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 113 | { | ||
| 114 | #[cfg(feature = "log")] | ||
| 115 | ::log::trace!($s $(, $x)*); | ||
| 116 | #[cfg(feature = "defmt")] | ||
| 117 | ::defmt::trace!($s $(, $x)*); | ||
| 118 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 119 | let _ = ($( & $x ),*); | ||
| 120 | } | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 124 | macro_rules! debug { | ||
| 125 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 126 | { | ||
| 127 | #[cfg(feature = "log")] | ||
| 128 | ::log::debug!($s $(, $x)*); | ||
| 129 | #[cfg(feature = "defmt")] | ||
| 130 | ::defmt::debug!($s $(, $x)*); | ||
| 131 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 132 | let _ = ($( & $x ),*); | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | } | ||
| 136 | |||
| 137 | macro_rules! info { | ||
| 138 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 139 | { | ||
| 140 | #[cfg(feature = "log")] | ||
| 141 | ::log::info!($s $(, $x)*); | ||
| 142 | #[cfg(feature = "defmt")] | ||
| 143 | ::defmt::info!($s $(, $x)*); | ||
| 144 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 145 | let _ = ($( & $x ),*); | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | } | ||
| 149 | |||
| 150 | macro_rules! warn { | ||
| 151 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 152 | { | ||
| 153 | #[cfg(feature = "log")] | ||
| 154 | ::log::warn!($s $(, $x)*); | ||
| 155 | #[cfg(feature = "defmt")] | ||
| 156 | ::defmt::warn!($s $(, $x)*); | ||
| 157 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 158 | let _ = ($( & $x ),*); | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | |||
| 163 | macro_rules! error { | ||
| 164 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 165 | { | ||
| 166 | #[cfg(feature = "log")] | ||
| 167 | ::log::error!($s $(, $x)*); | ||
| 168 | #[cfg(feature = "defmt")] | ||
| 169 | ::defmt::error!($s $(, $x)*); | ||
| 170 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 171 | let _ = ($( & $x ),*); | ||
| 172 | } | ||
| 173 | }; | ||
| 174 | } | ||
| 175 | |||
| 176 | #[cfg(feature = "defmt")] | ||
| 177 | macro_rules! unwrap { | ||
| 178 | ($($x:tt)*) => { | ||
| 179 | ::defmt::unwrap!($($x)*) | ||
| 180 | }; | ||
| 181 | } | ||
| 182 | |||
| 183 | #[cfg(not(feature = "defmt"))] | ||
| 184 | macro_rules! unwrap { | ||
| 185 | ($arg:expr) => { | ||
| 186 | match $crate::fmt::Try::into_result($arg) { | ||
| 187 | ::core::result::Result::Ok(t) => t, | ||
| 188 | ::core::result::Result::Err(e) => { | ||
| 189 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | }; | ||
| 193 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 194 | match $crate::fmt::Try::into_result($arg) { | ||
| 195 | ::core::result::Result::Ok(t) => t, | ||
| 196 | ::core::result::Result::Err(e) => { | ||
| 197 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 204 | pub struct NoneError; | ||
| 205 | |||
| 206 | pub trait Try { | ||
| 207 | type Ok; | ||
| 208 | type Error; | ||
| 209 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<T> Try for Option<T> { | ||
| 213 | type Ok = T; | ||
| 214 | type Error = NoneError; | ||
| 215 | |||
| 216 | #[inline] | ||
| 217 | fn into_result(self) -> Result<T, NoneError> { | ||
| 218 | self.ok_or(NoneError) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | impl<T, E> Try for Result<T, E> { | ||
| 223 | type Ok = T; | ||
| 224 | type Error = E; | ||
| 225 | |||
| 226 | #[inline] | ||
| 227 | fn into_result(self) -> Self { | ||
| 228 | self | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | #[allow(unused)] | ||
| 233 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||
| 234 | |||
| 235 | impl<'a> Debug for Bytes<'a> { | ||
| 236 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 237 | write!(f, "{:#02x?}", self.0) | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'a> Display for Bytes<'a> { | ||
| 242 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 243 | write!(f, "{:#02x?}", self.0) | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | impl<'a> LowerHex for Bytes<'a> { | ||
| 248 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 249 | write!(f, "{:#02x?}", self.0) | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | #[cfg(feature = "defmt")] | ||
| 254 | impl<'a> defmt::Format for Bytes<'a> { | ||
| 255 | fn format(&self, fmt: defmt::Formatter) { | ||
| 256 | defmt::write!(fmt, "{:02x}", self.0) | ||
| 257 | } | ||
| 258 | } | ||
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs deleted file mode 100644 index 5b20a93c6..000000000 --- a/embassy-boot/nrf/src/lib.rs +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![warn(missing_docs)] | ||
| 3 | #![doc = include_str!("../README.md")] | ||
| 4 | mod fmt; | ||
| 5 | |||
| 6 | pub use embassy_boot::{ | ||
| 7 | AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, | ||
| 8 | FirmwareUpdaterConfig, | ||
| 9 | }; | ||
| 10 | use embassy_nrf::nvmc::PAGE_SIZE; | ||
| 11 | use embassy_nrf::peripherals::WDT; | ||
| 12 | use embassy_nrf::wdt; | ||
| 13 | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||
| 14 | |||
| 15 | /// A bootloader for nRF devices. | ||
| 16 | pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE>; | ||
| 17 | |||
| 18 | impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||
| 19 | /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. | ||
| 20 | pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||
| 21 | config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||
| 22 | ) -> Self { | ||
| 23 | let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||
| 24 | let mut boot = embassy_boot::BootLoader::new(config); | ||
| 25 | boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); | ||
| 26 | Self | ||
| 27 | } | ||
| 28 | |||
| 29 | /// Boots the application without softdevice mechanisms. | ||
| 30 | /// | ||
| 31 | /// # Safety | ||
| 32 | /// | ||
| 33 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 34 | #[cfg(not(feature = "softdevice"))] | ||
| 35 | pub unsafe fn load(self, start: u32) -> ! { | ||
| 36 | let mut p = cortex_m::Peripherals::steal(); | ||
| 37 | p.SCB.invalidate_icache(); | ||
| 38 | p.SCB.vtor.write(start); | ||
| 39 | cortex_m::asm::bootload(start as *const u32) | ||
| 40 | } | ||
| 41 | |||
| 42 | /// Boots the application assuming softdevice is present. | ||
| 43 | /// | ||
| 44 | /// # Safety | ||
| 45 | /// | ||
| 46 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 47 | #[cfg(feature = "softdevice")] | ||
| 48 | pub unsafe fn load(self, _app: u32) -> ! { | ||
| 49 | use nrf_softdevice_mbr as mbr; | ||
| 50 | const NRF_SUCCESS: u32 = 0; | ||
| 51 | |||
| 52 | // Address of softdevice which we'll forward interrupts to | ||
| 53 | let addr = 0x1000; | ||
| 54 | let mut cmd = mbr::sd_mbr_command_t { | ||
| 55 | command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET, | ||
| 56 | params: mbr::sd_mbr_command_t__bindgen_ty_1 { | ||
| 57 | irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr }, | ||
| 58 | }, | ||
| 59 | }; | ||
| 60 | let ret = mbr::sd_mbr_command(&mut cmd); | ||
| 61 | assert_eq!(ret, NRF_SUCCESS); | ||
| 62 | |||
| 63 | let msp = *(addr as *const u32); | ||
| 64 | let rv = *((addr + 4) as *const u32); | ||
| 65 | |||
| 66 | trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv); | ||
| 67 | |||
| 68 | // These instructions perform the following operations: | ||
| 69 | // | ||
| 70 | // * Modify control register to use MSP as stack pointer (clear spsel bit) | ||
| 71 | // * Synchronize instruction barrier | ||
| 72 | // * Initialize stack pointer (0x1000) | ||
| 73 | // * Set link register to not return (0xFF) | ||
| 74 | // * Jump to softdevice reset vector | ||
| 75 | core::arch::asm!( | ||
| 76 | "mrs {tmp}, CONTROL", | ||
| 77 | "bics {tmp}, {spsel}", | ||
| 78 | "msr CONTROL, {tmp}", | ||
| 79 | "isb", | ||
| 80 | "msr MSP, {msp}", | ||
| 81 | "mov lr, {new_lr}", | ||
| 82 | "bx {rv}", | ||
| 83 | // `out(reg) _` is not permitted in a `noreturn` asm! call, | ||
| 84 | // so instead use `in(reg) 0` and don't restore it afterwards. | ||
| 85 | tmp = in(reg) 0, | ||
| 86 | spsel = in(reg) 2, | ||
| 87 | new_lr = in(reg) 0xFFFFFFFFu32, | ||
| 88 | msp = in(reg) msp, | ||
| 89 | rv = in(reg) rv, | ||
| 90 | options(noreturn), | ||
| 91 | ); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | /// A flash implementation that wraps any flash and will pet a watchdog when touching flash. | ||
| 96 | pub struct WatchdogFlash<FLASH> { | ||
| 97 | flash: FLASH, | ||
| 98 | wdt: wdt::WatchdogHandle, | ||
| 99 | } | ||
| 100 | |||
| 101 | impl<FLASH> WatchdogFlash<FLASH> { | ||
| 102 | /// Start a new watchdog with a given flash and WDT peripheral and a timeout | ||
| 103 | pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { | ||
| 104 | let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { | ||
| 105 | Ok(x) => x, | ||
| 106 | Err(_) => { | ||
| 107 | // In case the watchdog is already running, just spin and let it expire, since | ||
| 108 | // we can't configure it anyway. This usually happens when we first program | ||
| 109 | // the device and the watchdog was previously active | ||
| 110 | info!("Watchdog already active with wrong config, waiting for it to timeout..."); | ||
| 111 | loop {} | ||
| 112 | } | ||
| 113 | }; | ||
| 114 | Self { flash, wdt } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> { | ||
| 119 | type Error = FLASH::Error; | ||
| 120 | } | ||
| 121 | |||
| 122 | impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> { | ||
| 123 | const WRITE_SIZE: usize = FLASH::WRITE_SIZE; | ||
| 124 | const ERASE_SIZE: usize = FLASH::ERASE_SIZE; | ||
| 125 | |||
| 126 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 127 | self.wdt.pet(); | ||
| 128 | self.flash.erase(from, to) | ||
| 129 | } | ||
| 130 | fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { | ||
| 131 | self.wdt.pet(); | ||
| 132 | self.flash.write(offset, data) | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> { | ||
| 137 | const READ_SIZE: usize = FLASH::READ_SIZE; | ||
| 138 | fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||
| 139 | self.wdt.pet(); | ||
| 140 | self.flash.read(offset, data) | ||
| 141 | } | ||
| 142 | fn capacity(&self) -> usize { | ||
| 143 | self.flash.capacity() | ||
| 144 | } | ||
| 145 | } | ||
diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml deleted file mode 100644 index 0f2dc4628..000000000 --- a/embassy-boot/rp/Cargo.toml +++ /dev/null | |||
| @@ -1,73 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-boot-rp" | ||
| 4 | version = "0.1.0" | ||
| 5 | description = "Bootloader lib for RP2040 chips" | ||
| 6 | license = "MIT OR Apache-2.0" | ||
| 7 | |||
| 8 | [package.metadata.embassy_docs] | ||
| 9 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" | ||
| 10 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/" | ||
| 11 | target = "thumbv6m-none-eabi" | ||
| 12 | |||
| 13 | [lib] | ||
| 14 | |||
| 15 | [dependencies] | ||
| 16 | defmt = { version = "0.3", optional = true } | ||
| 17 | defmt-rtt = { version = "0.4", optional = true } | ||
| 18 | log = { version = "0.4", optional = true } | ||
| 19 | |||
| 20 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } | ||
| 21 | embassy-rp = { path = "../../embassy-rp", default-features = false } | ||
| 22 | embassy-boot = { path = "../boot", default-features = false } | ||
| 23 | embassy-time = { path = "../../embassy-time" } | ||
| 24 | |||
| 25 | cortex-m = { version = "0.7.6" } | ||
| 26 | cortex-m-rt = { version = "0.7" } | ||
| 27 | embedded-storage = "0.3.1" | ||
| 28 | embedded-storage-async = { version = "0.4.1" } | ||
| 29 | cfg-if = "1.0.0" | ||
| 30 | |||
| 31 | [features] | ||
| 32 | defmt = [ | ||
| 33 | "dep:defmt", | ||
| 34 | "embassy-boot/defmt", | ||
| 35 | "embassy-rp/defmt", | ||
| 36 | ] | ||
| 37 | log = [ | ||
| 38 | "dep:log", | ||
| 39 | "embassy-boot/log", | ||
| 40 | "embassy-rp/log", | ||
| 41 | ] | ||
| 42 | debug = ["defmt-rtt"] | ||
| 43 | |||
| 44 | [profile.dev] | ||
| 45 | debug = 2 | ||
| 46 | debug-assertions = true | ||
| 47 | incremental = false | ||
| 48 | opt-level = 'z' | ||
| 49 | overflow-checks = true | ||
| 50 | |||
| 51 | [profile.release] | ||
| 52 | codegen-units = 1 | ||
| 53 | debug = 2 | ||
| 54 | debug-assertions = false | ||
| 55 | incremental = false | ||
| 56 | lto = 'fat' | ||
| 57 | opt-level = 'z' | ||
| 58 | overflow-checks = false | ||
| 59 | |||
| 60 | # do not optimize proc-macro crates = faster builds from scratch | ||
| 61 | [profile.dev.build-override] | ||
| 62 | codegen-units = 8 | ||
| 63 | debug = false | ||
| 64 | debug-assertions = false | ||
| 65 | opt-level = 0 | ||
| 66 | overflow-checks = false | ||
| 67 | |||
| 68 | [profile.release.build-override] | ||
| 69 | codegen-units = 8 | ||
| 70 | debug = false | ||
| 71 | debug-assertions = false | ||
| 72 | opt-level = 0 | ||
| 73 | overflow-checks = false | ||
diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md deleted file mode 100644 index 315d655e3..000000000 --- a/embassy-boot/rp/README.md +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | # embassy-boot-rp | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | An adaptation of `embassy-boot` for RP2040. | ||
| 6 | |||
| 7 | NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. | ||
| 8 | |||
| 9 | ## Features | ||
| 10 | |||
| 11 | * Configure bootloader partitions based on linker script. | ||
| 12 | * Load applications from active partition. | ||
| 13 | |||
| 14 | ## Minimum supported Rust version (MSRV) | ||
| 15 | |||
| 16 | `embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 17 | |||
| 18 | ## License | ||
| 19 | |||
| 20 | This work is licensed under either of | ||
| 21 | |||
| 22 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 23 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 24 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 25 | |||
| 26 | at your option. | ||
diff --git a/embassy-boot/rp/build.rs b/embassy-boot/rp/build.rs deleted file mode 100644 index 2cbc7ef5e..000000000 --- a/embassy-boot/rp/build.rs +++ /dev/null | |||
| @@ -1,8 +0,0 @@ | |||
| 1 | use std::env; | ||
| 2 | |||
| 3 | fn main() { | ||
| 4 | let target = env::var("TARGET").unwrap(); | ||
| 5 | if target.starts_with("thumbv6m-") { | ||
| 6 | println!("cargo:rustc-cfg=armv6m"); | ||
| 7 | } | ||
| 8 | } | ||
diff --git a/embassy-boot/rp/src/fmt.rs b/embassy-boot/rp/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/rp/src/fmt.rs +++ /dev/null | |||
| @@ -1,258 +0,0 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | ||
| 5 | |||
| 6 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 7 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 8 | |||
| 9 | macro_rules! assert { | ||
| 10 | ($($x:tt)*) => { | ||
| 11 | { | ||
| 12 | #[cfg(not(feature = "defmt"))] | ||
| 13 | ::core::assert!($($x)*); | ||
| 14 | #[cfg(feature = "defmt")] | ||
| 15 | ::defmt::assert!($($x)*); | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 20 | macro_rules! assert_eq { | ||
| 21 | ($($x:tt)*) => { | ||
| 22 | { | ||
| 23 | #[cfg(not(feature = "defmt"))] | ||
| 24 | ::core::assert_eq!($($x)*); | ||
| 25 | #[cfg(feature = "defmt")] | ||
| 26 | ::defmt::assert_eq!($($x)*); | ||
| 27 | } | ||
| 28 | }; | ||
| 29 | } | ||
| 30 | |||
| 31 | macro_rules! assert_ne { | ||
| 32 | ($($x:tt)*) => { | ||
| 33 | { | ||
| 34 | #[cfg(not(feature = "defmt"))] | ||
| 35 | ::core::assert_ne!($($x)*); | ||
| 36 | #[cfg(feature = "defmt")] | ||
| 37 | ::defmt::assert_ne!($($x)*); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | } | ||
| 41 | |||
| 42 | macro_rules! debug_assert { | ||
| 43 | ($($x:tt)*) => { | ||
| 44 | { | ||
| 45 | #[cfg(not(feature = "defmt"))] | ||
| 46 | ::core::debug_assert!($($x)*); | ||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | ::defmt::debug_assert!($($x)*); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | } | ||
| 52 | |||
| 53 | macro_rules! debug_assert_eq { | ||
| 54 | ($($x:tt)*) => { | ||
| 55 | { | ||
| 56 | #[cfg(not(feature = "defmt"))] | ||
| 57 | ::core::debug_assert_eq!($($x)*); | ||
| 58 | #[cfg(feature = "defmt")] | ||
| 59 | ::defmt::debug_assert_eq!($($x)*); | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | } | ||
| 63 | |||
| 64 | macro_rules! debug_assert_ne { | ||
| 65 | ($($x:tt)*) => { | ||
| 66 | { | ||
| 67 | #[cfg(not(feature = "defmt"))] | ||
| 68 | ::core::debug_assert_ne!($($x)*); | ||
| 69 | #[cfg(feature = "defmt")] | ||
| 70 | ::defmt::debug_assert_ne!($($x)*); | ||
| 71 | } | ||
| 72 | }; | ||
| 73 | } | ||
| 74 | |||
| 75 | macro_rules! todo { | ||
| 76 | ($($x:tt)*) => { | ||
| 77 | { | ||
| 78 | #[cfg(not(feature = "defmt"))] | ||
| 79 | ::core::todo!($($x)*); | ||
| 80 | #[cfg(feature = "defmt")] | ||
| 81 | ::defmt::todo!($($x)*); | ||
| 82 | } | ||
| 83 | }; | ||
| 84 | } | ||
| 85 | |||
| 86 | #[cfg(not(feature = "defmt"))] | ||
| 87 | macro_rules! unreachable { | ||
| 88 | ($($x:tt)*) => { | ||
| 89 | ::core::unreachable!($($x)*) | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | #[cfg(feature = "defmt")] | ||
| 94 | macro_rules! unreachable { | ||
| 95 | ($($x:tt)*) => { | ||
| 96 | ::defmt::unreachable!($($x)*) | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | macro_rules! panic { | ||
| 101 | ($($x:tt)*) => { | ||
| 102 | { | ||
| 103 | #[cfg(not(feature = "defmt"))] | ||
| 104 | ::core::panic!($($x)*); | ||
| 105 | #[cfg(feature = "defmt")] | ||
| 106 | ::defmt::panic!($($x)*); | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | macro_rules! trace { | ||
| 112 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 113 | { | ||
| 114 | #[cfg(feature = "log")] | ||
| 115 | ::log::trace!($s $(, $x)*); | ||
| 116 | #[cfg(feature = "defmt")] | ||
| 117 | ::defmt::trace!($s $(, $x)*); | ||
| 118 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 119 | let _ = ($( & $x ),*); | ||
| 120 | } | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 124 | macro_rules! debug { | ||
| 125 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 126 | { | ||
| 127 | #[cfg(feature = "log")] | ||
| 128 | ::log::debug!($s $(, $x)*); | ||
| 129 | #[cfg(feature = "defmt")] | ||
| 130 | ::defmt::debug!($s $(, $x)*); | ||
| 131 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 132 | let _ = ($( & $x ),*); | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | } | ||
| 136 | |||
| 137 | macro_rules! info { | ||
| 138 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 139 | { | ||
| 140 | #[cfg(feature = "log")] | ||
| 141 | ::log::info!($s $(, $x)*); | ||
| 142 | #[cfg(feature = "defmt")] | ||
| 143 | ::defmt::info!($s $(, $x)*); | ||
| 144 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 145 | let _ = ($( & $x ),*); | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | } | ||
| 149 | |||
| 150 | macro_rules! warn { | ||
| 151 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 152 | { | ||
| 153 | #[cfg(feature = "log")] | ||
| 154 | ::log::warn!($s $(, $x)*); | ||
| 155 | #[cfg(feature = "defmt")] | ||
| 156 | ::defmt::warn!($s $(, $x)*); | ||
| 157 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 158 | let _ = ($( & $x ),*); | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | |||
| 163 | macro_rules! error { | ||
| 164 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 165 | { | ||
| 166 | #[cfg(feature = "log")] | ||
| 167 | ::log::error!($s $(, $x)*); | ||
| 168 | #[cfg(feature = "defmt")] | ||
| 169 | ::defmt::error!($s $(, $x)*); | ||
| 170 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 171 | let _ = ($( & $x ),*); | ||
| 172 | } | ||
| 173 | }; | ||
| 174 | } | ||
| 175 | |||
| 176 | #[cfg(feature = "defmt")] | ||
| 177 | macro_rules! unwrap { | ||
| 178 | ($($x:tt)*) => { | ||
| 179 | ::defmt::unwrap!($($x)*) | ||
| 180 | }; | ||
| 181 | } | ||
| 182 | |||
| 183 | #[cfg(not(feature = "defmt"))] | ||
| 184 | macro_rules! unwrap { | ||
| 185 | ($arg:expr) => { | ||
| 186 | match $crate::fmt::Try::into_result($arg) { | ||
| 187 | ::core::result::Result::Ok(t) => t, | ||
| 188 | ::core::result::Result::Err(e) => { | ||
| 189 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | }; | ||
| 193 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 194 | match $crate::fmt::Try::into_result($arg) { | ||
| 195 | ::core::result::Result::Ok(t) => t, | ||
| 196 | ::core::result::Result::Err(e) => { | ||
| 197 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 204 | pub struct NoneError; | ||
| 205 | |||
| 206 | pub trait Try { | ||
| 207 | type Ok; | ||
| 208 | type Error; | ||
| 209 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<T> Try for Option<T> { | ||
| 213 | type Ok = T; | ||
| 214 | type Error = NoneError; | ||
| 215 | |||
| 216 | #[inline] | ||
| 217 | fn into_result(self) -> Result<T, NoneError> { | ||
| 218 | self.ok_or(NoneError) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | impl<T, E> Try for Result<T, E> { | ||
| 223 | type Ok = T; | ||
| 224 | type Error = E; | ||
| 225 | |||
| 226 | #[inline] | ||
| 227 | fn into_result(self) -> Self { | ||
| 228 | self | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | #[allow(unused)] | ||
| 233 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||
| 234 | |||
| 235 | impl<'a> Debug for Bytes<'a> { | ||
| 236 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 237 | write!(f, "{:#02x?}", self.0) | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'a> Display for Bytes<'a> { | ||
| 242 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 243 | write!(f, "{:#02x?}", self.0) | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | impl<'a> LowerHex for Bytes<'a> { | ||
| 248 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 249 | write!(f, "{:#02x?}", self.0) | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | #[cfg(feature = "defmt")] | ||
| 254 | impl<'a> defmt::Format for Bytes<'a> { | ||
| 255 | fn format(&self, fmt: defmt::Formatter) { | ||
| 256 | defmt::write!(fmt, "{:02x}", self.0) | ||
| 257 | } | ||
| 258 | } | ||
diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs deleted file mode 100644 index 07a5b3f4d..000000000 --- a/embassy-boot/rp/src/lib.rs +++ /dev/null | |||
| @@ -1,90 +0,0 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![warn(missing_docs)] | ||
| 3 | #![doc = include_str!("../README.md")] | ||
| 4 | mod fmt; | ||
| 5 | |||
| 6 | pub use embassy_boot::{ | ||
| 7 | AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, | ||
| 8 | FirmwareUpdaterConfig, State, | ||
| 9 | }; | ||
| 10 | use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; | ||
| 11 | use embassy_rp::peripherals::{FLASH, WATCHDOG}; | ||
| 12 | use embassy_rp::watchdog::Watchdog; | ||
| 13 | use embassy_time::Duration; | ||
| 14 | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||
| 15 | |||
| 16 | /// A bootloader for RP2040 devices. | ||
| 17 | pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>; | ||
| 18 | |||
| 19 | impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||
| 20 | /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware | ||
| 21 | pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||
| 22 | config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||
| 23 | ) -> Self { | ||
| 24 | let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||
| 25 | let mut boot = embassy_boot::BootLoader::new(config); | ||
| 26 | boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | ||
| 27 | Self | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Boots the application. | ||
| 31 | /// | ||
| 32 | /// # Safety | ||
| 33 | /// | ||
| 34 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 35 | pub unsafe fn load(self, start: u32) -> ! { | ||
| 36 | trace!("Loading app at 0x{:x}", start); | ||
| 37 | #[allow(unused_mut)] | ||
| 38 | let mut p = cortex_m::Peripherals::steal(); | ||
| 39 | #[cfg(not(armv6m))] | ||
| 40 | p.SCB.invalidate_icache(); | ||
| 41 | p.SCB.vtor.write(start); | ||
| 42 | |||
| 43 | cortex_m::asm::bootload(start as *const u32) | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | /// A flash implementation that will feed a watchdog when touching flash. | ||
| 48 | pub struct WatchdogFlash<'d, const SIZE: usize> { | ||
| 49 | flash: Flash<'d, FLASH, Blocking, SIZE>, | ||
| 50 | watchdog: Watchdog, | ||
| 51 | } | ||
| 52 | |||
| 53 | impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | ||
| 54 | /// Start a new watchdog with a given flash and watchdog peripheral and a timeout | ||
| 55 | pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { | ||
| 56 | let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); | ||
| 57 | let mut watchdog = Watchdog::new(watchdog); | ||
| 58 | watchdog.start(timeout); | ||
| 59 | Self { flash, watchdog } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { | ||
| 64 | type Error = <Flash<'d, FLASH, Blocking, SIZE> as ErrorType>::Error; | ||
| 65 | } | ||
| 66 | |||
| 67 | impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { | ||
| 68 | const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE; | ||
| 69 | const ERASE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::ERASE_SIZE; | ||
| 70 | |||
| 71 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 72 | self.watchdog.feed(); | ||
| 73 | self.flash.blocking_erase(from, to) | ||
| 74 | } | ||
| 75 | fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { | ||
| 76 | self.watchdog.feed(); | ||
| 77 | self.flash.blocking_write(offset, data) | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { | ||
| 82 | const READ_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as ReadNorFlash>::READ_SIZE; | ||
| 83 | fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||
| 84 | self.watchdog.feed(); | ||
| 85 | self.flash.blocking_read(offset, data) | ||
| 86 | } | ||
| 87 | fn capacity(&self) -> usize { | ||
| 88 | self.flash.capacity() | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index a8c19197b..a38558056 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs | |||
| @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; | |||
| 5 | use embassy_sync::blocking_mutex::Mutex; | 5 | use embassy_sync::blocking_mutex::Mutex; |
| 6 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | 6 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; |
| 7 | 7 | ||
| 8 | use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | 8 | use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; |
| 9 | 9 | ||
| 10 | /// Errors returned by bootloader | 10 | /// Errors returned by bootloader |
| 11 | #[derive(PartialEq, Eq, Debug)] | 11 | #[derive(PartialEq, Eq, Debug)] |
| @@ -49,16 +49,51 @@ pub struct BootLoaderConfig<ACTIVE, DFU, STATE> { | |||
| 49 | pub state: STATE, | 49 | pub state: STATE, |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | impl<'a, FLASH: NorFlash> | 52 | impl<'a, ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> |
| 53 | BootLoaderConfig< | 53 | BootLoaderConfig< |
| 54 | BlockingPartition<'a, NoopRawMutex, FLASH>, | 54 | BlockingPartition<'a, NoopRawMutex, ACTIVE>, |
| 55 | BlockingPartition<'a, NoopRawMutex, FLASH>, | 55 | BlockingPartition<'a, NoopRawMutex, DFU>, |
| 56 | BlockingPartition<'a, NoopRawMutex, FLASH>, | 56 | BlockingPartition<'a, NoopRawMutex, STATE>, |
| 57 | > | 57 | > |
| 58 | { | 58 | { |
| 59 | /// Create a bootloader config from the flash and address symbols defined in the linkerfile | 59 | /// Constructs a `BootLoaderConfig` instance from flash memory and address symbols defined in the linker file. |
| 60 | /// | ||
| 61 | /// This method initializes `BlockingPartition` instances for the active, DFU (Device Firmware Update), | ||
| 62 | /// and state partitions, leveraging start and end addresses specified by the linker. These partitions | ||
| 63 | /// are critical for managing firmware updates, application state, and boot operations within the bootloader. | ||
| 64 | /// | ||
| 65 | /// # Parameters | ||
| 66 | /// - `active_flash`: A reference to a mutex-protected `RefCell` for the active partition's flash interface. | ||
| 67 | /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface. | ||
| 68 | /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface. | ||
| 69 | /// | ||
| 70 | /// # Safety | ||
| 71 | /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses | ||
| 72 | /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined | ||
| 73 | /// in the memory.x file to prevent undefined behavior. | ||
| 74 | /// | ||
| 75 | /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory | ||
| 76 | /// interfaces provided are compatible with these regions. | ||
| 77 | /// | ||
| 78 | /// # Returns | ||
| 79 | /// A `BootLoaderConfig` instance with `BlockingPartition` instances for the active, DFU, and state partitions. | ||
| 80 | /// | ||
| 81 | /// # Example | ||
| 82 | /// ```ignore | ||
| 83 | /// // Assume `active_flash`, `dfu_flash`, and `state_flash` all share the same flash memory interface. | ||
| 84 | /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); | ||
| 85 | /// let flash = Mutex::new(RefCell::new(layout.bank1_region)); | ||
| 86 | /// | ||
| 87 | /// let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); | ||
| 88 | /// // `config` can now be used to create a `BootLoader` instance for managing boot operations. | ||
| 89 | /// ``` | ||
| 90 | /// Working examples can be found in the bootloader examples folder. | ||
| 60 | // #[cfg(target_os = "none")] | 91 | // #[cfg(target_os = "none")] |
| 61 | pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { | 92 | pub fn from_linkerfile_blocking( |
| 93 | active_flash: &'a Mutex<NoopRawMutex, RefCell<ACTIVE>>, | ||
| 94 | dfu_flash: &'a Mutex<NoopRawMutex, RefCell<DFU>>, | ||
| 95 | state_flash: &'a Mutex<NoopRawMutex, RefCell<STATE>>, | ||
| 96 | ) -> Self { | ||
| 62 | extern "C" { | 97 | extern "C" { |
| 63 | static __bootloader_state_start: u32; | 98 | static __bootloader_state_start: u32; |
| 64 | static __bootloader_state_end: u32; | 99 | static __bootloader_state_end: u32; |
| @@ -73,21 +108,21 @@ impl<'a, FLASH: NorFlash> | |||
| 73 | let end = &__bootloader_active_end as *const u32 as u32; | 108 | let end = &__bootloader_active_end as *const u32 as u32; |
| 74 | trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); | 109 | trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); |
| 75 | 110 | ||
| 76 | BlockingPartition::new(flash, start, end - start) | 111 | BlockingPartition::new(active_flash, start, end - start) |
| 77 | }; | 112 | }; |
| 78 | let dfu = unsafe { | 113 | let dfu = unsafe { |
| 79 | let start = &__bootloader_dfu_start as *const u32 as u32; | 114 | let start = &__bootloader_dfu_start as *const u32 as u32; |
| 80 | let end = &__bootloader_dfu_end as *const u32 as u32; | 115 | let end = &__bootloader_dfu_end as *const u32 as u32; |
| 81 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | 116 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); |
| 82 | 117 | ||
| 83 | BlockingPartition::new(flash, start, end - start) | 118 | BlockingPartition::new(dfu_flash, start, end - start) |
| 84 | }; | 119 | }; |
| 85 | let state = unsafe { | 120 | let state = unsafe { |
| 86 | let start = &__bootloader_state_start as *const u32 as u32; | 121 | let start = &__bootloader_state_start as *const u32 as u32; |
| 87 | let end = &__bootloader_state_end as *const u32 as u32; | 122 | let end = &__bootloader_state_end as *const u32 as u32; |
| 88 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | 123 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); |
| 89 | 124 | ||
| 90 | BlockingPartition::new(flash, start, end - start) | 125 | BlockingPartition::new(state_flash, start, end - start) |
| 91 | }; | 126 | }; |
| 92 | 127 | ||
| 93 | Self { active, dfu, state } | 128 | Self { active, dfu, state } |
| @@ -135,51 +170,44 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 135 | /// The provided aligned_buf argument must satisfy any alignment requirements | 170 | /// The provided aligned_buf argument must satisfy any alignment requirements |
| 136 | /// given by the partition flashes. All flash operations will use this buffer. | 171 | /// given by the partition flashes. All flash operations will use this buffer. |
| 137 | /// | 172 | /// |
| 138 | /// SWAPPING | 173 | /// ## SWAPPING |
| 139 | /// | 174 | /// |
| 140 | /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. | 175 | /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. |
| 141 | /// The swap index contains the copy progress, as to allow continuation of the copy process on | 176 | /// The swap index contains the copy progress, as to allow continuation of the copy process on |
| 142 | /// power failure. The index counter is represented within 1 or more pages (depending on total | 177 | /// power failure. The index counter is represented within 1 or more pages (depending on total |
| 143 | /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) | 178 | /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) |
| 144 | /// contains a zero value. This ensures that index updates can be performed atomically and | 179 | /// contains a zero value. This ensures that index updates can be performed atomically and |
| 145 | /// avoid a situation where the wrong index value is set (page write size is "atomic"). | 180 | /// avoid a situation where the wrong index value is set (page write size is "atomic"). |
| 146 | /// | 181 | /// |
| 147 | /// +-----------+------------+--------+--------+--------+--------+ | 182 | /// |
| 148 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 183 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 149 | /// +-----------+------------+--------+--------+--------+--------+ | 184 | /// |-----------|------------|--------|--------|--------|--------| |
| 150 | /// | Active | 0 | 1 | 2 | 3 | - | | 185 | /// | Active | 0 | 1 | 2 | 3 | - | |
| 151 | /// | DFU | 0 | 3 | 2 | 1 | X | | 186 | /// | DFU | 0 | 4 | 5 | 6 | X | |
| 152 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 153 | /// | 187 | /// |
| 154 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is | 188 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is |
| 155 | /// as follows: | 189 | /// as follows: |
| 156 | /// | 190 | /// |
| 157 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 158 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 191 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 159 | /// +-----------+------------+--------+--------+--------+--------+ | 192 | /// |-----------|------------|--------|--------|--------|--------| |
| 160 | /// | Active | 1 | 1 | 2 | 1 | - | | 193 | /// | Active | 1 | 1 | 2 | 6 | - | |
| 161 | /// | DFU | 1 | 3 | 2 | 1 | 3 | | 194 | /// | DFU | 1 | 4 | 5 | 6 | 3 | |
| 162 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 163 | /// | 195 | /// |
| 164 | /// The next iteration performs the same steps | 196 | /// The next iteration performs the same steps |
| 165 | /// | 197 | /// |
| 166 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 167 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 198 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 168 | /// +-----------+------------+--------+--------+--------+--------+ | 199 | /// |-----------|------------|--------|--------|--------|--------| |
| 169 | /// | Active | 2 | 1 | 2 | 1 | - | | 200 | /// | Active | 2 | 1 | 5 | 6 | - | |
| 170 | /// | DFU | 2 | 3 | 2 | 2 | 3 | | 201 | /// | DFU | 2 | 4 | 5 | 2 | 3 | |
| 171 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 172 | /// | 202 | /// |
| 173 | /// And again until we're done | 203 | /// And again until we're done |
| 174 | /// | 204 | /// |
| 175 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 176 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 205 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 177 | /// +-----------+------------+--------+--------+--------+--------+ | 206 | /// |-----------|------------|--------|--------|--------|--------| |
| 178 | /// | Active | 3 | 3 | 2 | 1 | - | | 207 | /// | Active | 3 | 4 | 5 | 6 | - | |
| 179 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | 208 | /// | DFU | 3 | 4 | 1 | 2 | 3 | |
| 180 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 181 | /// | 209 | /// |
| 182 | /// REVERTING | 210 | /// ## REVERTING |
| 183 | /// | 211 | /// |
| 184 | /// The reverting algorithm uses the swap index to discover that images were swapped, but that | 212 | /// The reverting algorithm uses the swap index to discover that images were swapped, but that |
| 185 | /// the application failed to mark the boot successful. In this case, the revert algorithm will | 213 | /// the application failed to mark the boot successful. In this case, the revert algorithm will |
| @@ -190,28 +218,21 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 190 | /// | 218 | /// |
| 191 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. | 219 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. |
| 192 | /// | 220 | /// |
| 193 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 194 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | 221 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 195 | //*/ | 222 | /// |-----------|--------------|--------|--------|--------|--------| |
| 196 | /// +-----------+--------------+--------+--------+--------+--------+ | 223 | /// | Active | 3 | 1 | 5 | 6 | - | |
| 197 | /// | Active | 3 | 1 | 2 | 1 | - | | 224 | /// | DFU | 3 | 4 | 1 | 2 | 3 | |
| 198 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 199 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 200 | /// | 225 | /// |
| 201 | /// | 226 | /// |
| 202 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 203 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | 227 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 204 | /// +-----------+--------------+--------+--------+--------+--------+ | 228 | /// |-----------|--------------|--------|--------|--------|--------| |
| 205 | /// | Active | 3 | 1 | 2 | 1 | - | | 229 | /// | Active | 3 | 1 | 2 | 6 | - | |
| 206 | /// | DFU | 3 | 3 | 2 | 2 | 3 | | 230 | /// | DFU | 3 | 4 | 5 | 2 | 3 | |
| 207 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 208 | /// | 231 | /// |
| 209 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 210 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | 232 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 211 | /// +-----------+--------------+--------+--------+--------+--------+ | 233 | /// |-----------|--------------|--------|--------|--------|--------| |
| 212 | /// | Active | 3 | 1 | 2 | 3 | - | | 234 | /// | Active | 3 | 1 | 2 | 3 | - | |
| 213 | /// | DFU | 3 | 3 | 2 | 1 | 3 | | 235 | /// | DFU | 3 | 4 | 5 | 6 | 3 | |
| 214 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 215 | /// | 236 | /// |
| 216 | pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | 237 | pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { |
| 217 | // Ensure we have enough progress pages to store copy progress | 238 | // Ensure we have enough progress pages to store copy progress |
| @@ -224,6 +245,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 224 | assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); | 245 | assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); |
| 225 | assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); | 246 | assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); |
| 226 | 247 | ||
| 248 | // Ensure our partitions are able to handle boot operations | ||
| 227 | assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); | 249 | assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); |
| 228 | 250 | ||
| 229 | // Copy contents from partition N to active | 251 | // Copy contents from partition N to active |
| @@ -384,6 +406,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 384 | 406 | ||
| 385 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { | 407 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { |
| 386 | Ok(State::Swap) | 408 | Ok(State::Swap) |
| 409 | } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 410 | Ok(State::DfuDetach) | ||
| 387 | } else { | 411 | } else { |
| 388 | Ok(State::Boot) | 412 | Ok(State::Boot) |
| 389 | } | 413 | } |
| @@ -398,6 +422,7 @@ fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | |||
| 398 | ) { | 422 | ) { |
| 399 | assert_eq!(active.capacity() as u32 % page_size, 0); | 423 | assert_eq!(active.capacity() as u32 % page_size, 0); |
| 400 | assert_eq!(dfu.capacity() as u32 % page_size, 0); | 424 | assert_eq!(dfu.capacity() as u32 % page_size, 0); |
| 425 | // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm | ||
| 401 | assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); | 426 | assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); |
| 402 | assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); | 427 | assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); |
| 403 | } | 428 | } |
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs index a184d1c51..2e4e03da3 100644 --- a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs +++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | use digest::typenum::U64; | 1 | use digest::typenum::U64; |
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; |
| 3 | use ed25519_dalek::Digest as _; | 3 | use ed25519_dalek::Digest; |
| 4 | 4 | ||
| 5 | pub struct Sha512(ed25519_dalek::Sha512); | 5 | pub struct Sha512(ed25519_dalek::Sha512); |
| 6 | 6 | ||
| @@ -12,7 +12,7 @@ impl Default for Sha512 { | |||
| 12 | 12 | ||
| 13 | impl Update for Sha512 { | 13 | impl Update for Sha512 { |
| 14 | fn update(&mut self, data: &[u8]) { | 14 | fn update(&mut self, data: &[u8]) { |
| 15 | self.0.update(data) | 15 | Digest::update(&mut self.0, data) |
| 16 | } | 16 | } |
| 17 | } | 17 | } |
| 18 | 18 | ||
diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs index 9b4b4b60c..9b4b4b60c 100644 --- a/embassy-boot/boot/src/digest_adapters/mod.rs +++ b/embassy-boot/src/digest_adapters/mod.rs | |||
diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs index 2b5dcf3af..2b5dcf3af 100644 --- a/embassy-boot/boot/src/digest_adapters/salty.rs +++ b/embassy-boot/src/digest_adapters/salty.rs | |||
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs index ae713bb6f..26f65f295 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/src/firmware_updater/asynch.rs | |||
| @@ -6,21 +6,25 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; | |||
| 6 | use embedded_storage_async::nor_flash::NorFlash; | 6 | use embedded_storage_async::nor_flash::NorFlash; |
| 7 | 7 | ||
| 8 | use super::FirmwareUpdaterConfig; | 8 | use super::FirmwareUpdaterConfig; |
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; |
| 10 | 10 | ||
| 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to |
| 12 | /// 'mess up' the internal bootloader state | 12 | /// 'mess up' the internal bootloader state |
| 13 | pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | 13 | pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { |
| 14 | dfu: DFU, | 14 | dfu: DFU, |
| 15 | state: FirmwareState<'d, STATE>, | 15 | state: FirmwareState<'d, STATE>, |
| 16 | last_erased_dfu_sector_index: Option<usize>, | ||
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | #[cfg(target_os = "none")] | 19 | #[cfg(target_os = "none")] |
| 19 | impl<'a, FLASH: NorFlash> | 20 | impl<'a, DFU: NorFlash, STATE: NorFlash> |
| 20 | FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> | 21 | FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, DFU>, Partition<'a, NoopRawMutex, STATE>> |
| 21 | { | 22 | { |
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | 23 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile |
| 23 | pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self { | 24 | pub fn from_linkerfile( |
| 25 | dfu_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, DFU>, | ||
| 26 | state_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, STATE>, | ||
| 27 | ) -> Self { | ||
| 24 | extern "C" { | 28 | extern "C" { |
| 25 | static __bootloader_state_start: u32; | 29 | static __bootloader_state_start: u32; |
| 26 | static __bootloader_state_end: u32; | 30 | static __bootloader_state_end: u32; |
| @@ -33,14 +37,14 @@ impl<'a, FLASH: NorFlash> | |||
| 33 | let end = &__bootloader_dfu_end as *const u32 as u32; | 37 | let end = &__bootloader_dfu_end as *const u32 as u32; |
| 34 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | 38 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); |
| 35 | 39 | ||
| 36 | Partition::new(flash, start, end - start) | 40 | Partition::new(dfu_flash, start, end - start) |
| 37 | }; | 41 | }; |
| 38 | let state = unsafe { | 42 | let state = unsafe { |
| 39 | let start = &__bootloader_state_start as *const u32 as u32; | 43 | let start = &__bootloader_state_start as *const u32 as u32; |
| 40 | let end = &__bootloader_state_end as *const u32 as u32; | 44 | let end = &__bootloader_state_end as *const u32 as u32; |
| 41 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | 45 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); |
| 42 | 46 | ||
| 43 | Partition::new(flash, start, end - start) | 47 | Partition::new(state_flash, start, end - start) |
| 44 | }; | 48 | }; |
| 45 | 49 | ||
| 46 | Self { dfu, state } | 50 | Self { dfu, state } |
| @@ -53,6 +57,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 53 | Self { | 57 | Self { |
| 54 | dfu: config.dfu, | 58 | dfu: config.dfu, |
| 55 | state: FirmwareState::new(config.state, aligned), | 59 | state: FirmwareState::new(config.state, aligned), |
| 60 | last_erased_dfu_sector_index: None, | ||
| 56 | } | 61 | } |
| 57 | } | 62 | } |
| 58 | 63 | ||
| @@ -69,7 +74,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 69 | /// proceed with updating the firmware as it must be signed with a | 74 | /// proceed with updating the firmware as it must be signed with a |
| 70 | /// corresponding private key (otherwise it could be malicious firmware). | 75 | /// corresponding private key (otherwise it could be malicious firmware). |
| 71 | /// | 76 | /// |
| 72 | /// Mark to trigger firmware swap on next boot if verify suceeds. | 77 | /// Mark to trigger firmware swap on next boot if verify succeeds. |
| 73 | /// | 78 | /// |
| 74 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | 79 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have |
| 75 | /// been generated from a SHA-512 digest of the firmware bytes. | 80 | /// been generated from a SHA-512 digest of the firmware bytes. |
| @@ -79,8 +84,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 79 | #[cfg(feature = "_verify")] | 84 | #[cfg(feature = "_verify")] |
| 80 | pub async fn verify_and_mark_updated( | 85 | pub async fn verify_and_mark_updated( |
| 81 | &mut self, | 86 | &mut self, |
| 82 | _public_key: &[u8], | 87 | _public_key: &[u8; 32], |
| 83 | _signature: &[u8], | 88 | _signature: &[u8; 64], |
| 84 | _update_len: u32, | 89 | _update_len: u32, |
| 85 | ) -> Result<(), FirmwareUpdaterError> { | 90 | ) -> Result<(), FirmwareUpdaterError> { |
| 86 | assert!(_update_len <= self.dfu.capacity() as u32); | 91 | assert!(_update_len <= self.dfu.capacity() as u32); |
| @@ -89,14 +94,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 89 | 94 | ||
| 90 | #[cfg(feature = "ed25519-dalek")] | 95 | #[cfg(feature = "ed25519-dalek")] |
| 91 | { | 96 | { |
| 92 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | 97 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; |
| 93 | 98 | ||
| 94 | use crate::digest_adapters::ed25519_dalek::Sha512; | 99 | use crate::digest_adapters::ed25519_dalek::Sha512; |
| 95 | 100 | ||
| 96 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | 101 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); |
| 97 | 102 | ||
| 98 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | 103 | let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; |
| 99 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | 104 | let signature = Signature::from_bytes(_signature); |
| 100 | 105 | ||
| 101 | let mut chunk_buf = [0; 2]; | 106 | let mut chunk_buf = [0; 2]; |
| 102 | let mut message = [0; 64]; | 107 | let mut message = [0; 64]; |
| @@ -106,7 +111,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 106 | } | 111 | } |
| 107 | #[cfg(feature = "ed25519-salty")] | 112 | #[cfg(feature = "ed25519-salty")] |
| 108 | { | 113 | { |
| 109 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||
| 110 | use salty::{PublicKey, Signature}; | 114 | use salty::{PublicKey, Signature}; |
| 111 | 115 | ||
| 112 | use crate::digest_adapters::salty::Sha512; | 116 | use crate::digest_adapters::salty::Sha512; |
| @@ -115,10 +119,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 115 | FirmwareUpdaterError::Signature(signature::Error::default()) | 119 | FirmwareUpdaterError::Signature(signature::Error::default()) |
| 116 | } | 120 | } |
| 117 | 121 | ||
| 118 | let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | 122 | let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; |
| 119 | let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | 123 | let signature = Signature::try_from(_signature).map_err(into_signature_error)?; |
| 120 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||
| 121 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||
| 122 | 124 | ||
| 123 | let mut message = [0; 64]; | 125 | let mut message = [0; 64]; |
| 124 | let mut chunk_buf = [0; 2]; | 126 | let mut chunk_buf = [0; 2]; |
| @@ -161,26 +163,79 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 161 | self.state.mark_updated().await | 163 | self.state.mark_updated().await |
| 162 | } | 164 | } |
| 163 | 165 | ||
| 166 | /// Mark to trigger USB DFU on next boot. | ||
| 167 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 168 | self.state.verify_booted().await?; | ||
| 169 | self.state.mark_dfu().await | ||
| 170 | } | ||
| 171 | |||
| 164 | /// Mark firmware boot successful and stop rollback on reset. | 172 | /// Mark firmware boot successful and stop rollback on reset. |
| 165 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 173 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 166 | self.state.mark_booted().await | 174 | self.state.mark_booted().await |
| 167 | } | 175 | } |
| 168 | 176 | ||
| 169 | /// Write data to a flash page. | 177 | /// Writes firmware data to the device. |
| 170 | /// | 178 | /// |
| 171 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | 179 | /// This function writes the given data to the firmware area starting at the specified offset. |
| 180 | /// It handles sector erasures and data writes while verifying the device is in a proper state | ||
| 181 | /// for firmware updates. The function ensures that only unerased sectors are erased before | ||
| 182 | /// writing and efficiently handles the writing process across sector boundaries and in | ||
| 183 | /// various configurations (data size, sector size, etc.). | ||
| 172 | /// | 184 | /// |
| 173 | /// # Safety | 185 | /// # Arguments |
| 186 | /// | ||
| 187 | /// * `offset` - The starting offset within the firmware area where data writing should begin. | ||
| 188 | /// * `data` - A slice of bytes representing the firmware data to be written. It must be a | ||
| 189 | /// multiple of NorFlash WRITE_SIZE. | ||
| 174 | /// | 190 | /// |
| 175 | /// Failing to meet alignment and size requirements may result in a panic. | 191 | /// # Returns |
| 192 | /// | ||
| 193 | /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation. | ||
| 194 | /// | ||
| 195 | /// # Errors | ||
| 196 | /// | ||
| 197 | /// This function will return an error if: | ||
| 198 | /// | ||
| 199 | /// - The device is not in a proper state to receive firmware updates (e.g., not booted). | ||
| 200 | /// - There is a failure erasing a sector before writing. | ||
| 201 | /// - There is a failure writing data to the device. | ||
| 176 | pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | 202 | pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { |
| 177 | assert!(data.len() >= DFU::ERASE_SIZE); | 203 | // Make sure we are running a booted firmware to avoid reverting to a bad state. |
| 178 | |||
| 179 | self.state.verify_booted().await?; | 204 | self.state.verify_booted().await?; |
| 180 | 205 | ||
| 181 | self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; | 206 | // Initialize variables to keep track of the remaining data and the current offset. |
| 207 | let mut remaining_data = data; | ||
| 208 | let mut offset = offset; | ||
| 209 | |||
| 210 | // Continue writing as long as there is data left to write. | ||
| 211 | while !remaining_data.is_empty() { | ||
| 212 | // Compute the current sector and its boundaries. | ||
| 213 | let current_sector = offset / DFU::ERASE_SIZE; | ||
| 214 | let sector_start = current_sector * DFU::ERASE_SIZE; | ||
| 215 | let sector_end = sector_start + DFU::ERASE_SIZE; | ||
| 216 | // Determine if the current sector needs to be erased before writing. | ||
| 217 | let need_erase = self | ||
| 218 | .last_erased_dfu_sector_index | ||
| 219 | .map_or(true, |last_erased_sector| current_sector != last_erased_sector); | ||
| 220 | |||
| 221 | // If the sector needs to be erased, erase it and update the last erased sector index. | ||
| 222 | if need_erase { | ||
| 223 | self.dfu.erase(sector_start as u32, sector_end as u32).await?; | ||
| 224 | self.last_erased_dfu_sector_index = Some(current_sector); | ||
| 225 | } | ||
| 226 | |||
| 227 | // Calculate the size of the data chunk that can be written in the current iteration. | ||
| 228 | let write_size = core::cmp::min(remaining_data.len(), sector_end - offset); | ||
| 229 | // Split the data to get the current chunk to be written and the remaining data. | ||
| 230 | let (data_chunk, rest) = remaining_data.split_at(write_size); | ||
| 182 | 231 | ||
| 183 | self.dfu.write(offset as u32, data).await?; | 232 | // Write the current data chunk. |
| 233 | self.dfu.write(offset as u32, data_chunk).await?; | ||
| 234 | |||
| 235 | // Update the offset and remaining data for the next iteration. | ||
| 236 | remaining_data = rest; | ||
| 237 | offset += write_size; | ||
| 238 | } | ||
| 184 | 239 | ||
| 185 | Ok(()) | 240 | Ok(()) |
| 186 | } | 241 | } |
| @@ -207,14 +262,24 @@ pub struct FirmwareState<'d, STATE> { | |||
| 207 | } | 262 | } |
| 208 | 263 | ||
| 209 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | 264 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { |
| 210 | /// Create a firmware state instance with a buffer for magic content and state partition. | 265 | /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. |
| 211 | /// | 266 | /// |
| 212 | /// # Safety | 267 | /// # Safety |
| 213 | /// | 268 | /// |
| 214 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | 269 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from |
| 215 | /// and written to. | 270 | /// and written to. |
| 271 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 272 | Self::new(config.state, aligned) | ||
| 273 | } | ||
| 274 | |||
| 275 | /// Create a firmware state instance with a buffer for magic content and state partition. | ||
| 276 | /// | ||
| 277 | /// # Safety | ||
| 278 | /// | ||
| 279 | /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, | ||
| 280 | /// and follow the alignment rules for the flash being read from and written to. | ||
| 216 | pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { | 281 | pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { |
| 217 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | 282 | assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); |
| 218 | Self { state, aligned } | 283 | Self { state, aligned } |
| 219 | } | 284 | } |
| 220 | 285 | ||
| @@ -247,6 +312,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | |||
| 247 | self.set_magic(SWAP_MAGIC).await | 312 | self.set_magic(SWAP_MAGIC).await |
| 248 | } | 313 | } |
| 249 | 314 | ||
| 315 | /// Mark to trigger USB DFU on next boot. | ||
| 316 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 317 | self.set_magic(DFU_DETACH_MAGIC).await | ||
| 318 | } | ||
| 319 | |||
| 250 | /// Mark firmware boot successful and stop rollback on reset. | 320 | /// Mark firmware boot successful and stop rollback on reset. |
| 251 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 321 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 252 | self.set_magic(BOOT_MAGIC).await | 322 | self.set_magic(BOOT_MAGIC).await |
| @@ -255,16 +325,25 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | |||
| 255 | async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { | 325 | async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { |
| 256 | self.state.read(0, &mut self.aligned).await?; | 326 | self.state.read(0, &mut self.aligned).await?; |
| 257 | 327 | ||
| 258 | if self.aligned.iter().any(|&b| b != magic) { | 328 | if self.aligned[..STATE::WRITE_SIZE].iter().any(|&b| b != magic) { |
| 259 | // Read progress validity | 329 | // Read progress validity |
| 260 | self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; | 330 | if STATE::READ_SIZE <= 2 * STATE::WRITE_SIZE { |
| 331 | self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; | ||
| 332 | } else { | ||
| 333 | self.aligned.rotate_left(STATE::WRITE_SIZE); | ||
| 334 | } | ||
| 261 | 335 | ||
| 262 | if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | 336 | if self.aligned[..STATE::WRITE_SIZE] |
| 337 | .iter() | ||
| 338 | .any(|&b| b != STATE_ERASE_VALUE) | ||
| 339 | { | ||
| 263 | // The current progress validity marker is invalid | 340 | // The current progress validity marker is invalid |
| 264 | } else { | 341 | } else { |
| 265 | // Invalidate progress | 342 | // Invalidate progress |
| 266 | self.aligned.fill(!STATE_ERASE_VALUE); | 343 | self.aligned.fill(!STATE_ERASE_VALUE); |
| 267 | self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; | 344 | self.state |
| 345 | .write(STATE::WRITE_SIZE as u32, &self.aligned[..STATE::WRITE_SIZE]) | ||
| 346 | .await?; | ||
| 268 | } | 347 | } |
| 269 | 348 | ||
| 270 | // Clear magic and progress | 349 | // Clear magic and progress |
| @@ -272,7 +351,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | |||
| 272 | 351 | ||
| 273 | // Set magic | 352 | // Set magic |
| 274 | self.aligned.fill(magic); | 353 | self.aligned.fill(magic); |
| 275 | self.state.write(0, &self.aligned).await?; | 354 | self.state.write(0, &self.aligned[..STATE::WRITE_SIZE]).await?; |
| 276 | } | 355 | } |
| 277 | Ok(()) | 356 | Ok(()) |
| 278 | } | 357 | } |
| @@ -308,4 +387,76 @@ mod tests { | |||
| 308 | 387 | ||
| 309 | assert_eq!(Sha1::digest(update).as_slice(), hash); | 388 | assert_eq!(Sha1::digest(update).as_slice(), hash); |
| 310 | } | 389 | } |
| 390 | |||
| 391 | #[test] | ||
| 392 | fn can_verify_sha1_sector_bigger_than_chunk() { | ||
| 393 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default()); | ||
| 394 | let state = Partition::new(&flash, 0, 4096); | ||
| 395 | let dfu = Partition::new(&flash, 65536, 65536); | ||
| 396 | let mut aligned = [0; 8]; | ||
| 397 | |||
| 398 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 399 | let mut to_write = [0; 4096]; | ||
| 400 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 401 | |||
| 402 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 403 | let mut offset = 0; | ||
| 404 | for chunk in to_write.chunks(1024) { | ||
| 405 | block_on(updater.write_firmware(offset, chunk)).unwrap(); | ||
| 406 | offset += chunk.len(); | ||
| 407 | } | ||
| 408 | let mut chunk_buf = [0; 2]; | ||
| 409 | let mut hash = [0; 20]; | ||
| 410 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 411 | |||
| 412 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 413 | } | ||
| 414 | |||
| 415 | #[test] | ||
| 416 | fn can_verify_sha1_sector_smaller_than_chunk() { | ||
| 417 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default()); | ||
| 418 | let state = Partition::new(&flash, 0, 4096); | ||
| 419 | let dfu = Partition::new(&flash, 65536, 65536); | ||
| 420 | let mut aligned = [0; 8]; | ||
| 421 | |||
| 422 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 423 | let mut to_write = [0; 4096]; | ||
| 424 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 425 | |||
| 426 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 427 | let mut offset = 0; | ||
| 428 | for chunk in to_write.chunks(2048) { | ||
| 429 | block_on(updater.write_firmware(offset, chunk)).unwrap(); | ||
| 430 | offset += chunk.len(); | ||
| 431 | } | ||
| 432 | let mut chunk_buf = [0; 2]; | ||
| 433 | let mut hash = [0; 20]; | ||
| 434 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 435 | |||
| 436 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 437 | } | ||
| 438 | |||
| 439 | #[test] | ||
| 440 | fn can_verify_sha1_cross_sector_boundary() { | ||
| 441 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default()); | ||
| 442 | let state = Partition::new(&flash, 0, 4096); | ||
| 443 | let dfu = Partition::new(&flash, 65536, 65536); | ||
| 444 | let mut aligned = [0; 8]; | ||
| 445 | |||
| 446 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 447 | let mut to_write = [0; 4096]; | ||
| 448 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 449 | |||
| 450 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 451 | let mut offset = 0; | ||
| 452 | for chunk in to_write.chunks(896) { | ||
| 453 | block_on(updater.write_firmware(offset, chunk)).unwrap(); | ||
| 454 | offset += chunk.len(); | ||
| 455 | } | ||
| 456 | let mut chunk_buf = [0; 2]; | ||
| 457 | let mut hash = [0; 20]; | ||
| 458 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 459 | |||
| 460 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 461 | } | ||
| 311 | } | 462 | } |
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index 76e4264a0..35772a856 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs | |||
| @@ -6,22 +6,54 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; | |||
| 6 | use embedded_storage::nor_flash::NorFlash; | 6 | use embedded_storage::nor_flash::NorFlash; |
| 7 | 7 | ||
| 8 | use super::FirmwareUpdaterConfig; | 8 | use super::FirmwareUpdaterConfig; |
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; |
| 10 | 10 | ||
| 11 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | 11 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to |
| 12 | /// 'mess up' the internal bootloader state | 12 | /// 'mess up' the internal bootloader state |
| 13 | pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | 13 | pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { |
| 14 | dfu: DFU, | 14 | dfu: DFU, |
| 15 | state: BlockingFirmwareState<'d, STATE>, | 15 | state: BlockingFirmwareState<'d, STATE>, |
| 16 | last_erased_dfu_sector_index: Option<usize>, | ||
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | #[cfg(target_os = "none")] | 19 | #[cfg(target_os = "none")] |
| 19 | impl<'a, FLASH: NorFlash> | 20 | impl<'a, DFU: NorFlash, STATE: NorFlash> |
| 20 | FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> | 21 | FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, DFU>, BlockingPartition<'a, NoopRawMutex, STATE>> |
| 21 | { | 22 | { |
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | 23 | /// Constructs a `FirmwareUpdaterConfig` instance from flash memory and address symbols defined in the linker file. |
| 24 | /// | ||
| 25 | /// This method initializes `BlockingPartition` instances for the DFU (Device Firmware Update), and state | ||
| 26 | /// partitions, leveraging start and end addresses specified by the linker. These partitions are critical | ||
| 27 | /// for managing firmware updates, application state, and boot operations within the bootloader. | ||
| 28 | /// | ||
| 29 | /// # Parameters | ||
| 30 | /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface. | ||
| 31 | /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface. | ||
| 32 | /// | ||
| 33 | /// # Safety | ||
| 34 | /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses | ||
| 35 | /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined | ||
| 36 | /// in the memory.x file to prevent undefined behavior. | ||
| 37 | /// | ||
| 38 | /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory | ||
| 39 | /// interfaces provided are compatible with these regions. | ||
| 40 | /// | ||
| 41 | /// # Returns | ||
| 42 | /// A `FirmwareUpdaterConfig` instance with `BlockingPartition` instances for the DFU, and state partitions. | ||
| 43 | /// | ||
| 44 | /// # Example | ||
| 45 | /// ```ignore | ||
| 46 | /// // Assume `dfu_flash`, and `state_flash` share the same flash memory interface. | ||
| 47 | /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); | ||
| 48 | /// let flash = Mutex::new(RefCell::new(layout.bank1_region)); | ||
| 49 | /// | ||
| 50 | /// let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); | ||
| 51 | /// // `config` can now be used to create a `FirmwareUpdater` instance for managing boot operations. | ||
| 52 | /// ``` | ||
| 53 | /// Working examples can be found in the bootloader examples folder. | ||
| 23 | pub fn from_linkerfile_blocking( | 54 | pub fn from_linkerfile_blocking( |
| 24 | flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>, | 55 | dfu_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<DFU>>, |
| 56 | state_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<STATE>>, | ||
| 25 | ) -> Self { | 57 | ) -> Self { |
| 26 | extern "C" { | 58 | extern "C" { |
| 27 | static __bootloader_state_start: u32; | 59 | static __bootloader_state_start: u32; |
| @@ -35,14 +67,14 @@ impl<'a, FLASH: NorFlash> | |||
| 35 | let end = &__bootloader_dfu_end as *const u32 as u32; | 67 | let end = &__bootloader_dfu_end as *const u32 as u32; |
| 36 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | 68 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); |
| 37 | 69 | ||
| 38 | BlockingPartition::new(flash, start, end - start) | 70 | BlockingPartition::new(dfu_flash, start, end - start) |
| 39 | }; | 71 | }; |
| 40 | let state = unsafe { | 72 | let state = unsafe { |
| 41 | let start = &__bootloader_state_start as *const u32 as u32; | 73 | let start = &__bootloader_state_start as *const u32 as u32; |
| 42 | let end = &__bootloader_state_end as *const u32 as u32; | 74 | let end = &__bootloader_state_end as *const u32 as u32; |
| 43 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | 75 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); |
| 44 | 76 | ||
| 45 | BlockingPartition::new(flash, start, end - start) | 77 | BlockingPartition::new(state_flash, start, end - start) |
| 46 | }; | 78 | }; |
| 47 | 79 | ||
| 48 | Self { dfu, state } | 80 | Self { dfu, state } |
| @@ -60,6 +92,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 60 | Self { | 92 | Self { |
| 61 | dfu: config.dfu, | 93 | dfu: config.dfu, |
| 62 | state: BlockingFirmwareState::new(config.state, aligned), | 94 | state: BlockingFirmwareState::new(config.state, aligned), |
| 95 | last_erased_dfu_sector_index: None, | ||
| 63 | } | 96 | } |
| 64 | } | 97 | } |
| 65 | 98 | ||
| @@ -76,7 +109,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 76 | /// proceed with updating the firmware as it must be signed with a | 109 | /// proceed with updating the firmware as it must be signed with a |
| 77 | /// corresponding private key (otherwise it could be malicious firmware). | 110 | /// corresponding private key (otherwise it could be malicious firmware). |
| 78 | /// | 111 | /// |
| 79 | /// Mark to trigger firmware swap on next boot if verify suceeds. | 112 | /// Mark to trigger firmware swap on next boot if verify succeeds. |
| 80 | /// | 113 | /// |
| 81 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | 114 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have |
| 82 | /// been generated from a SHA-512 digest of the firmware bytes. | 115 | /// been generated from a SHA-512 digest of the firmware bytes. |
| @@ -86,8 +119,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 86 | #[cfg(feature = "_verify")] | 119 | #[cfg(feature = "_verify")] |
| 87 | pub fn verify_and_mark_updated( | 120 | pub fn verify_and_mark_updated( |
| 88 | &mut self, | 121 | &mut self, |
| 89 | _public_key: &[u8], | 122 | _public_key: &[u8; 32], |
| 90 | _signature: &[u8], | 123 | _signature: &[u8; 64], |
| 91 | _update_len: u32, | 124 | _update_len: u32, |
| 92 | ) -> Result<(), FirmwareUpdaterError> { | 125 | ) -> Result<(), FirmwareUpdaterError> { |
| 93 | assert!(_update_len <= self.dfu.capacity() as u32); | 126 | assert!(_update_len <= self.dfu.capacity() as u32); |
| @@ -96,14 +129,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 96 | 129 | ||
| 97 | #[cfg(feature = "ed25519-dalek")] | 130 | #[cfg(feature = "ed25519-dalek")] |
| 98 | { | 131 | { |
| 99 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | 132 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; |
| 100 | 133 | ||
| 101 | use crate::digest_adapters::ed25519_dalek::Sha512; | 134 | use crate::digest_adapters::ed25519_dalek::Sha512; |
| 102 | 135 | ||
| 103 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | 136 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); |
| 104 | 137 | ||
| 105 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | 138 | let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; |
| 106 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | 139 | let signature = Signature::from_bytes(_signature); |
| 107 | 140 | ||
| 108 | let mut message = [0; 64]; | 141 | let mut message = [0; 64]; |
| 109 | let mut chunk_buf = [0; 2]; | 142 | let mut chunk_buf = [0; 2]; |
| @@ -113,7 +146,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 113 | } | 146 | } |
| 114 | #[cfg(feature = "ed25519-salty")] | 147 | #[cfg(feature = "ed25519-salty")] |
| 115 | { | 148 | { |
| 116 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||
| 117 | use salty::{PublicKey, Signature}; | 149 | use salty::{PublicKey, Signature}; |
| 118 | 150 | ||
| 119 | use crate::digest_adapters::salty::Sha512; | 151 | use crate::digest_adapters::salty::Sha512; |
| @@ -122,10 +154,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 122 | FirmwareUpdaterError::Signature(signature::Error::default()) | 154 | FirmwareUpdaterError::Signature(signature::Error::default()) |
| 123 | } | 155 | } |
| 124 | 156 | ||
| 125 | let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | 157 | let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; |
| 126 | let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | 158 | let signature = Signature::try_from(_signature).map_err(into_signature_error)?; |
| 127 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||
| 128 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||
| 129 | 159 | ||
| 130 | let mut message = [0; 64]; | 160 | let mut message = [0; 64]; |
| 131 | let mut chunk_buf = [0; 2]; | 161 | let mut chunk_buf = [0; 2]; |
| @@ -168,25 +198,79 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 168 | self.state.mark_updated() | 198 | self.state.mark_updated() |
| 169 | } | 199 | } |
| 170 | 200 | ||
| 201 | /// Mark to trigger USB DFU device on next boot. | ||
| 202 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 203 | self.state.verify_booted()?; | ||
| 204 | self.state.mark_dfu() | ||
| 205 | } | ||
| 206 | |||
| 171 | /// Mark firmware boot successful and stop rollback on reset. | 207 | /// Mark firmware boot successful and stop rollback on reset. |
| 172 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 208 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 173 | self.state.mark_booted() | 209 | self.state.mark_booted() |
| 174 | } | 210 | } |
| 175 | 211 | ||
| 176 | /// Write data to a flash page. | 212 | /// Writes firmware data to the device. |
| 177 | /// | 213 | /// |
| 178 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | 214 | /// This function writes the given data to the firmware area starting at the specified offset. |
| 215 | /// It handles sector erasures and data writes while verifying the device is in a proper state | ||
| 216 | /// for firmware updates. The function ensures that only unerased sectors are erased before | ||
| 217 | /// writing and efficiently handles the writing process across sector boundaries and in | ||
| 218 | /// various configurations (data size, sector size, etc.). | ||
| 179 | /// | 219 | /// |
| 180 | /// # Safety | 220 | /// # Arguments |
| 181 | /// | 221 | /// |
| 182 | /// Failing to meet alignment and size requirements may result in a panic. | 222 | /// * `offset` - The starting offset within the firmware area where data writing should begin. |
| 223 | /// * `data` - A slice of bytes representing the firmware data to be written. It must be a | ||
| 224 | /// multiple of NorFlash WRITE_SIZE. | ||
| 225 | /// | ||
| 226 | /// # Returns | ||
| 227 | /// | ||
| 228 | /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation. | ||
| 229 | /// | ||
| 230 | /// # Errors | ||
| 231 | /// | ||
| 232 | /// This function will return an error if: | ||
| 233 | /// | ||
| 234 | /// - The device is not in a proper state to receive firmware updates (e.g., not booted). | ||
| 235 | /// - There is a failure erasing a sector before writing. | ||
| 236 | /// - There is a failure writing data to the device. | ||
| 183 | pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | 237 | pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { |
| 184 | assert!(data.len() >= DFU::ERASE_SIZE); | 238 | // Make sure we are running a booted firmware to avoid reverting to a bad state. |
| 185 | self.state.verify_booted()?; | 239 | self.state.verify_booted()?; |
| 186 | 240 | ||
| 187 | self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; | 241 | // Initialize variables to keep track of the remaining data and the current offset. |
| 242 | let mut remaining_data = data; | ||
| 243 | let mut offset = offset; | ||
| 244 | |||
| 245 | // Continue writing as long as there is data left to write. | ||
| 246 | while !remaining_data.is_empty() { | ||
| 247 | // Compute the current sector and its boundaries. | ||
| 248 | let current_sector = offset / DFU::ERASE_SIZE; | ||
| 249 | let sector_start = current_sector * DFU::ERASE_SIZE; | ||
| 250 | let sector_end = sector_start + DFU::ERASE_SIZE; | ||
| 251 | // Determine if the current sector needs to be erased before writing. | ||
| 252 | let need_erase = self | ||
| 253 | .last_erased_dfu_sector_index | ||
| 254 | .map_or(true, |last_erased_sector| current_sector != last_erased_sector); | ||
| 255 | |||
| 256 | // If the sector needs to be erased, erase it and update the last erased sector index. | ||
| 257 | if need_erase { | ||
| 258 | self.dfu.erase(sector_start as u32, sector_end as u32)?; | ||
| 259 | self.last_erased_dfu_sector_index = Some(current_sector); | ||
| 260 | } | ||
| 188 | 261 | ||
| 189 | self.dfu.write(offset as u32, data)?; | 262 | // Calculate the size of the data chunk that can be written in the current iteration. |
| 263 | let write_size = core::cmp::min(remaining_data.len(), sector_end - offset); | ||
| 264 | // Split the data to get the current chunk to be written and the remaining data. | ||
| 265 | let (data_chunk, rest) = remaining_data.split_at(write_size); | ||
| 266 | |||
| 267 | // Write the current data chunk. | ||
| 268 | self.dfu.write(offset as u32, data_chunk)?; | ||
| 269 | |||
| 270 | // Update the offset and remaining data for the next iteration. | ||
| 271 | remaining_data = rest; | ||
| 272 | offset += write_size; | ||
| 273 | } | ||
| 190 | 274 | ||
| 191 | Ok(()) | 275 | Ok(()) |
| 192 | } | 276 | } |
| @@ -213,6 +297,16 @@ pub struct BlockingFirmwareState<'d, STATE> { | |||
| 213 | } | 297 | } |
| 214 | 298 | ||
| 215 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | 299 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { |
| 300 | /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. | ||
| 301 | /// | ||
| 302 | /// # Safety | ||
| 303 | /// | ||
| 304 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 305 | /// and written to. | ||
| 306 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 307 | Self::new(config.state, aligned) | ||
| 308 | } | ||
| 309 | |||
| 216 | /// Create a firmware state instance with a buffer for magic content and state partition. | 310 | /// Create a firmware state instance with a buffer for magic content and state partition. |
| 217 | /// | 311 | /// |
| 218 | /// # Safety | 312 | /// # Safety |
| @@ -226,7 +320,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 226 | 320 | ||
| 227 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | 321 | // Make sure we are running a booted firmware to avoid reverting to a bad state. |
| 228 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 322 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 229 | if self.get_state()? == State::Boot { | 323 | if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { |
| 230 | Ok(()) | 324 | Ok(()) |
| 231 | } else { | 325 | } else { |
| 232 | Err(FirmwareUpdaterError::BadState) | 326 | Err(FirmwareUpdaterError::BadState) |
| @@ -243,6 +337,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 243 | 337 | ||
| 244 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | 338 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { |
| 245 | Ok(State::Swap) | 339 | Ok(State::Swap) |
| 340 | } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 341 | Ok(State::DfuDetach) | ||
| 246 | } else { | 342 | } else { |
| 247 | Ok(State::Boot) | 343 | Ok(State::Boot) |
| 248 | } | 344 | } |
| @@ -253,6 +349,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 253 | self.set_magic(SWAP_MAGIC) | 349 | self.set_magic(SWAP_MAGIC) |
| 254 | } | 350 | } |
| 255 | 351 | ||
| 352 | /// Mark to trigger USB DFU on next boot. | ||
| 353 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 354 | self.set_magic(DFU_DETACH_MAGIC) | ||
| 355 | } | ||
| 356 | |||
| 256 | /// Mark firmware boot successful and stop rollback on reset. | 357 | /// Mark firmware boot successful and stop rollback on reset. |
| 257 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 358 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 258 | self.set_magic(BOOT_MAGIC) | 359 | self.set_magic(BOOT_MAGIC) |
| @@ -317,4 +418,82 @@ mod tests { | |||
| 317 | 418 | ||
| 318 | assert_eq!(Sha1::digest(update).as_slice(), hash); | 419 | assert_eq!(Sha1::digest(update).as_slice(), hash); |
| 319 | } | 420 | } |
| 421 | |||
| 422 | #[test] | ||
| 423 | fn can_verify_sha1_sector_bigger_than_chunk() { | ||
| 424 | let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); | ||
| 425 | let state = BlockingPartition::new(&flash, 0, 4096); | ||
| 426 | let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||
| 427 | let mut aligned = [0; 8]; | ||
| 428 | |||
| 429 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 430 | let mut to_write = [0; 4096]; | ||
| 431 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 432 | |||
| 433 | let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 434 | let mut offset = 0; | ||
| 435 | for chunk in to_write.chunks(1024) { | ||
| 436 | updater.write_firmware(offset, chunk).unwrap(); | ||
| 437 | offset += chunk.len(); | ||
| 438 | } | ||
| 439 | let mut chunk_buf = [0; 2]; | ||
| 440 | let mut hash = [0; 20]; | ||
| 441 | updater | ||
| 442 | .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||
| 443 | .unwrap(); | ||
| 444 | |||
| 445 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 446 | } | ||
| 447 | |||
| 448 | #[test] | ||
| 449 | fn can_verify_sha1_sector_smaller_than_chunk() { | ||
| 450 | let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default())); | ||
| 451 | let state = BlockingPartition::new(&flash, 0, 4096); | ||
| 452 | let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||
| 453 | let mut aligned = [0; 8]; | ||
| 454 | |||
| 455 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 456 | let mut to_write = [0; 4096]; | ||
| 457 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 458 | |||
| 459 | let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 460 | let mut offset = 0; | ||
| 461 | for chunk in to_write.chunks(2048) { | ||
| 462 | updater.write_firmware(offset, chunk).unwrap(); | ||
| 463 | offset += chunk.len(); | ||
| 464 | } | ||
| 465 | let mut chunk_buf = [0; 2]; | ||
| 466 | let mut hash = [0; 20]; | ||
| 467 | updater | ||
| 468 | .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||
| 469 | .unwrap(); | ||
| 470 | |||
| 471 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 472 | } | ||
| 473 | |||
| 474 | #[test] | ||
| 475 | fn can_verify_sha1_cross_sector_boundary() { | ||
| 476 | let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default())); | ||
| 477 | let state = BlockingPartition::new(&flash, 0, 4096); | ||
| 478 | let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||
| 479 | let mut aligned = [0; 8]; | ||
| 480 | |||
| 481 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 482 | let mut to_write = [0; 4096]; | ||
| 483 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 484 | |||
| 485 | let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 486 | let mut offset = 0; | ||
| 487 | for chunk in to_write.chunks(896) { | ||
| 488 | updater.write_firmware(offset, chunk).unwrap(); | ||
| 489 | offset += chunk.len(); | ||
| 490 | } | ||
| 491 | let mut chunk_buf = [0; 2]; | ||
| 492 | let mut hash = [0; 20]; | ||
| 493 | updater | ||
| 494 | .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||
| 495 | .unwrap(); | ||
| 496 | |||
| 497 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 498 | } | ||
| 320 | } | 499 | } |
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs index 4814786bf..4c4f4f10b 100644 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ b/embassy-boot/src/firmware_updater/mod.rs | |||
| @@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | |||
| 8 | /// Firmware updater flash configuration holding the two flashes used by the updater | 8 | /// Firmware updater flash configuration holding the two flashes used by the updater |
| 9 | /// | 9 | /// |
| 10 | /// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. | 10 | /// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. |
| 11 | /// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition | 11 | /// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition |
| 12 | /// the provided flash according to symbols defined in the linkerfile. | 12 | /// the provided flash according to symbols defined in the linkerfile. |
| 13 | pub struct FirmwareUpdaterConfig<DFU, STATE> { | 13 | pub struct FirmwareUpdaterConfig<DFU, STATE> { |
| 14 | /// The dfu flash partition | 14 | /// The dfu flash partition |
diff --git a/embassy-boot/boot/src/fmt.rs b/embassy-boot/src/fmt.rs index 78e583c1c..2ac42c557 100644 --- a/embassy-boot/boot/src/fmt.rs +++ b/embassy-boot/src/fmt.rs | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | #![macro_use] | 1 | #![macro_use] |
| 2 | #![allow(unused_macros)] | 2 | #![allow(unused)] |
| 3 | 3 | ||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | 4 | use core::fmt::{Debug, Display, LowerHex}; |
| 5 | 5 | ||
| @@ -229,7 +229,6 @@ impl<T, E> Try for Result<T, E> { | |||
| 229 | } | 229 | } |
| 230 | } | 230 | } |
| 231 | 231 | ||
| 232 | #[allow(unused)] | ||
| 233 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | 232 | pub(crate) struct Bytes<'a>(pub &'a [u8]); |
| 234 | 233 | ||
| 235 | impl<'a> Debug for Bytes<'a> { | 234 | impl<'a> Debug for Bytes<'a> { |
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/src/lib.rs index 9e70a4dca..b4f03e01e 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/src/lib.rs | |||
| @@ -23,6 +23,7 @@ pub use firmware_updater::{ | |||
| 23 | 23 | ||
| 24 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; | 24 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; |
| 25 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; | 25 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; |
| 26 | pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; | ||
| 26 | 27 | ||
| 27 | /// The state of the bootloader after running prepare. | 28 | /// The state of the bootloader after running prepare. |
| 28 | #[derive(PartialEq, Eq, Debug)] | 29 | #[derive(PartialEq, Eq, Debug)] |
| @@ -32,6 +33,8 @@ pub enum State { | |||
| 32 | Boot, | 33 | Boot, |
| 33 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. | 34 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. |
| 34 | Swap, | 35 | Swap, |
| 36 | /// Application has received a request to reboot into DFU mode to apply an update. | ||
| 37 | DfuDetach, | ||
| 35 | } | 38 | } |
| 36 | 39 | ||
| 37 | /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. | 40 | /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. |
| @@ -272,21 +275,19 @@ mod tests { | |||
| 272 | // The following key setup is based on: | 275 | // The following key setup is based on: |
| 273 | // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example | 276 | // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example |
| 274 | 277 | ||
| 275 | use ed25519_dalek::Keypair; | 278 | use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; |
| 276 | use rand::rngs::OsRng; | 279 | use rand::rngs::OsRng; |
| 277 | 280 | ||
| 278 | let mut csprng = OsRng {}; | 281 | let mut csprng = OsRng {}; |
| 279 | let keypair: Keypair = Keypair::generate(&mut csprng); | 282 | let keypair = SigningKey::generate(&mut csprng); |
| 280 | 283 | ||
| 281 | use ed25519_dalek::{Digest, Sha512, Signature, Signer}; | ||
| 282 | let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; | 284 | let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; |
| 283 | let mut digest = Sha512::new(); | 285 | let mut digest = Sha512::new(); |
| 284 | digest.update(&firmware); | 286 | digest.update(&firmware); |
| 285 | let message = digest.finalize(); | 287 | let message = digest.finalize(); |
| 286 | let signature: Signature = keypair.sign(&message); | 288 | let signature: Signature = keypair.sign(&message); |
| 287 | 289 | ||
| 288 | use ed25519_dalek::PublicKey; | 290 | let public_key = keypair.verifying_key(); |
| 289 | let public_key: PublicKey = keypair.public; | ||
| 290 | 291 | ||
| 291 | // Setup flash | 292 | // Setup flash |
| 292 | let flash = BlockingTestFlash::new(BootLoaderConfig { | 293 | let flash = BlockingTestFlash::new(BootLoaderConfig { |
diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs index 40f352c8d..40f352c8d 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/src/mem_flash.rs | |||
diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs index 3ac9e71ab..3ac9e71ab 100644 --- a/embassy-boot/boot/src/test_flash/asynch.rs +++ b/embassy-boot/src/test_flash/asynch.rs | |||
diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs index 5ec476c65..5ec476c65 100644 --- a/embassy-boot/boot/src/test_flash/blocking.rs +++ b/embassy-boot/src/test_flash/blocking.rs | |||
diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs index 79b15a081..79b15a081 100644 --- a/embassy-boot/boot/src/test_flash/mod.rs +++ b/embassy-boot/src/test_flash/mod.rs | |||
diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml deleted file mode 100644 index bc8da6738..000000000 --- a/embassy-boot/stm32/Cargo.toml +++ /dev/null | |||
| @@ -1,64 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-boot-stm32" | ||
| 4 | version = "0.1.0" | ||
| 5 | description = "Bootloader lib for STM32 chips" | ||
| 6 | license = "MIT OR Apache-2.0" | ||
| 7 | |||
| 8 | [package.metadata.embassy_docs] | ||
| 9 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" | ||
| 10 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/stm32/src/" | ||
| 11 | features = ["embassy-stm32/stm32f429zi"] | ||
| 12 | target = "thumbv7em-none-eabi" | ||
| 13 | |||
| 14 | [lib] | ||
| 15 | |||
| 16 | [dependencies] | ||
| 17 | defmt = { version = "0.3", optional = true } | ||
| 18 | defmt-rtt = { version = "0.4", optional = true } | ||
| 19 | log = { version = "0.4", optional = true } | ||
| 20 | |||
| 21 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } | ||
| 22 | embassy-stm32 = { path = "../../embassy-stm32", default-features = false } | ||
| 23 | embassy-boot = { path = "../boot", default-features = false } | ||
| 24 | cortex-m = { version = "0.7.6" } | ||
| 25 | cortex-m-rt = { version = "0.7" } | ||
| 26 | embedded-storage = "0.3.1" | ||
| 27 | embedded-storage-async = { version = "0.4.1" } | ||
| 28 | cfg-if = "1.0.0" | ||
| 29 | |||
| 30 | [features] | ||
| 31 | defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"] | ||
| 32 | log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"] | ||
| 33 | debug = ["defmt-rtt"] | ||
| 34 | |||
| 35 | [profile.dev] | ||
| 36 | debug = 2 | ||
| 37 | debug-assertions = true | ||
| 38 | incremental = false | ||
| 39 | opt-level = 'z' | ||
| 40 | overflow-checks = true | ||
| 41 | |||
| 42 | [profile.release] | ||
| 43 | codegen-units = 1 | ||
| 44 | debug = 2 | ||
| 45 | debug-assertions = false | ||
| 46 | incremental = false | ||
| 47 | lto = 'fat' | ||
| 48 | opt-level = 'z' | ||
| 49 | overflow-checks = false | ||
| 50 | |||
| 51 | # do not optimize proc-macro crates = faster builds from scratch | ||
| 52 | [profile.dev.build-override] | ||
| 53 | codegen-units = 8 | ||
| 54 | debug = false | ||
| 55 | debug-assertions = false | ||
| 56 | opt-level = 0 | ||
| 57 | overflow-checks = false | ||
| 58 | |||
| 59 | [profile.release.build-override] | ||
| 60 | codegen-units = 8 | ||
| 61 | debug = false | ||
| 62 | debug-assertions = false | ||
| 63 | opt-level = 0 | ||
| 64 | overflow-checks = false | ||
diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md deleted file mode 100644 index b4d7ba5a4..000000000 --- a/embassy-boot/stm32/README.md +++ /dev/null | |||
| @@ -1,24 +0,0 @@ | |||
| 1 | # embassy-boot-stm32 | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | An adaptation of `embassy-boot` for STM32. | ||
| 6 | |||
| 7 | ## Features | ||
| 8 | |||
| 9 | * Configure bootloader partitions based on linker script. | ||
| 10 | * Load applications from active partition. | ||
| 11 | |||
| 12 | ## Minimum supported Rust version (MSRV) | ||
| 13 | |||
| 14 | `embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 15 | |||
| 16 | ## License | ||
| 17 | |||
| 18 | This work is licensed under either of | ||
| 19 | |||
| 20 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 21 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 22 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 23 | |||
| 24 | at your option. | ||
diff --git a/embassy-boot/stm32/build.rs b/embassy-boot/stm32/build.rs deleted file mode 100644 index 2cbc7ef5e..000000000 --- a/embassy-boot/stm32/build.rs +++ /dev/null | |||
| @@ -1,8 +0,0 @@ | |||
| 1 | use std::env; | ||
| 2 | |||
| 3 | fn main() { | ||
| 4 | let target = env::var("TARGET").unwrap(); | ||
| 5 | if target.starts_with("thumbv6m-") { | ||
| 6 | println!("cargo:rustc-cfg=armv6m"); | ||
| 7 | } | ||
| 8 | } | ||
diff --git a/embassy-boot/stm32/src/fmt.rs b/embassy-boot/stm32/src/fmt.rs deleted file mode 100644 index 78e583c1c..000000000 --- a/embassy-boot/stm32/src/fmt.rs +++ /dev/null | |||
| @@ -1,258 +0,0 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | ||
| 5 | |||
| 6 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 7 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 8 | |||
| 9 | macro_rules! assert { | ||
| 10 | ($($x:tt)*) => { | ||
| 11 | { | ||
| 12 | #[cfg(not(feature = "defmt"))] | ||
| 13 | ::core::assert!($($x)*); | ||
| 14 | #[cfg(feature = "defmt")] | ||
| 15 | ::defmt::assert!($($x)*); | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 20 | macro_rules! assert_eq { | ||
| 21 | ($($x:tt)*) => { | ||
| 22 | { | ||
| 23 | #[cfg(not(feature = "defmt"))] | ||
| 24 | ::core::assert_eq!($($x)*); | ||
| 25 | #[cfg(feature = "defmt")] | ||
| 26 | ::defmt::assert_eq!($($x)*); | ||
| 27 | } | ||
| 28 | }; | ||
| 29 | } | ||
| 30 | |||
| 31 | macro_rules! assert_ne { | ||
| 32 | ($($x:tt)*) => { | ||
| 33 | { | ||
| 34 | #[cfg(not(feature = "defmt"))] | ||
| 35 | ::core::assert_ne!($($x)*); | ||
| 36 | #[cfg(feature = "defmt")] | ||
| 37 | ::defmt::assert_ne!($($x)*); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | } | ||
| 41 | |||
| 42 | macro_rules! debug_assert { | ||
| 43 | ($($x:tt)*) => { | ||
| 44 | { | ||
| 45 | #[cfg(not(feature = "defmt"))] | ||
| 46 | ::core::debug_assert!($($x)*); | ||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | ::defmt::debug_assert!($($x)*); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | } | ||
| 52 | |||
| 53 | macro_rules! debug_assert_eq { | ||
| 54 | ($($x:tt)*) => { | ||
| 55 | { | ||
| 56 | #[cfg(not(feature = "defmt"))] | ||
| 57 | ::core::debug_assert_eq!($($x)*); | ||
| 58 | #[cfg(feature = "defmt")] | ||
| 59 | ::defmt::debug_assert_eq!($($x)*); | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | } | ||
| 63 | |||
| 64 | macro_rules! debug_assert_ne { | ||
| 65 | ($($x:tt)*) => { | ||
| 66 | { | ||
| 67 | #[cfg(not(feature = "defmt"))] | ||
| 68 | ::core::debug_assert_ne!($($x)*); | ||
| 69 | #[cfg(feature = "defmt")] | ||
| 70 | ::defmt::debug_assert_ne!($($x)*); | ||
| 71 | } | ||
| 72 | }; | ||
| 73 | } | ||
| 74 | |||
| 75 | macro_rules! todo { | ||
| 76 | ($($x:tt)*) => { | ||
| 77 | { | ||
| 78 | #[cfg(not(feature = "defmt"))] | ||
| 79 | ::core::todo!($($x)*); | ||
| 80 | #[cfg(feature = "defmt")] | ||
| 81 | ::defmt::todo!($($x)*); | ||
| 82 | } | ||
| 83 | }; | ||
| 84 | } | ||
| 85 | |||
| 86 | #[cfg(not(feature = "defmt"))] | ||
| 87 | macro_rules! unreachable { | ||
| 88 | ($($x:tt)*) => { | ||
| 89 | ::core::unreachable!($($x)*) | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | #[cfg(feature = "defmt")] | ||
| 94 | macro_rules! unreachable { | ||
| 95 | ($($x:tt)*) => { | ||
| 96 | ::defmt::unreachable!($($x)*) | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | macro_rules! panic { | ||
| 101 | ($($x:tt)*) => { | ||
| 102 | { | ||
| 103 | #[cfg(not(feature = "defmt"))] | ||
| 104 | ::core::panic!($($x)*); | ||
| 105 | #[cfg(feature = "defmt")] | ||
| 106 | ::defmt::panic!($($x)*); | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | macro_rules! trace { | ||
| 112 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 113 | { | ||
| 114 | #[cfg(feature = "log")] | ||
| 115 | ::log::trace!($s $(, $x)*); | ||
| 116 | #[cfg(feature = "defmt")] | ||
| 117 | ::defmt::trace!($s $(, $x)*); | ||
| 118 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 119 | let _ = ($( & $x ),*); | ||
| 120 | } | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 124 | macro_rules! debug { | ||
| 125 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 126 | { | ||
| 127 | #[cfg(feature = "log")] | ||
| 128 | ::log::debug!($s $(, $x)*); | ||
| 129 | #[cfg(feature = "defmt")] | ||
| 130 | ::defmt::debug!($s $(, $x)*); | ||
| 131 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 132 | let _ = ($( & $x ),*); | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | } | ||
| 136 | |||
| 137 | macro_rules! info { | ||
| 138 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 139 | { | ||
| 140 | #[cfg(feature = "log")] | ||
| 141 | ::log::info!($s $(, $x)*); | ||
| 142 | #[cfg(feature = "defmt")] | ||
| 143 | ::defmt::info!($s $(, $x)*); | ||
| 144 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 145 | let _ = ($( & $x ),*); | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | } | ||
| 149 | |||
| 150 | macro_rules! warn { | ||
| 151 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 152 | { | ||
| 153 | #[cfg(feature = "log")] | ||
| 154 | ::log::warn!($s $(, $x)*); | ||
| 155 | #[cfg(feature = "defmt")] | ||
| 156 | ::defmt::warn!($s $(, $x)*); | ||
| 157 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 158 | let _ = ($( & $x ),*); | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | |||
| 163 | macro_rules! error { | ||
| 164 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 165 | { | ||
| 166 | #[cfg(feature = "log")] | ||
| 167 | ::log::error!($s $(, $x)*); | ||
| 168 | #[cfg(feature = "defmt")] | ||
| 169 | ::defmt::error!($s $(, $x)*); | ||
| 170 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 171 | let _ = ($( & $x ),*); | ||
| 172 | } | ||
| 173 | }; | ||
| 174 | } | ||
| 175 | |||
| 176 | #[cfg(feature = "defmt")] | ||
| 177 | macro_rules! unwrap { | ||
| 178 | ($($x:tt)*) => { | ||
| 179 | ::defmt::unwrap!($($x)*) | ||
| 180 | }; | ||
| 181 | } | ||
| 182 | |||
| 183 | #[cfg(not(feature = "defmt"))] | ||
| 184 | macro_rules! unwrap { | ||
| 185 | ($arg:expr) => { | ||
| 186 | match $crate::fmt::Try::into_result($arg) { | ||
| 187 | ::core::result::Result::Ok(t) => t, | ||
| 188 | ::core::result::Result::Err(e) => { | ||
| 189 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | }; | ||
| 193 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 194 | match $crate::fmt::Try::into_result($arg) { | ||
| 195 | ::core::result::Result::Ok(t) => t, | ||
| 196 | ::core::result::Result::Err(e) => { | ||
| 197 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 204 | pub struct NoneError; | ||
| 205 | |||
| 206 | pub trait Try { | ||
| 207 | type Ok; | ||
| 208 | type Error; | ||
| 209 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<T> Try for Option<T> { | ||
| 213 | type Ok = T; | ||
| 214 | type Error = NoneError; | ||
| 215 | |||
| 216 | #[inline] | ||
| 217 | fn into_result(self) -> Result<T, NoneError> { | ||
| 218 | self.ok_or(NoneError) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | impl<T, E> Try for Result<T, E> { | ||
| 223 | type Ok = T; | ||
| 224 | type Error = E; | ||
| 225 | |||
| 226 | #[inline] | ||
| 227 | fn into_result(self) -> Self { | ||
| 228 | self | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | #[allow(unused)] | ||
| 233 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||
| 234 | |||
| 235 | impl<'a> Debug for Bytes<'a> { | ||
| 236 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 237 | write!(f, "{:#02x?}", self.0) | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'a> Display for Bytes<'a> { | ||
| 242 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 243 | write!(f, "{:#02x?}", self.0) | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | impl<'a> LowerHex for Bytes<'a> { | ||
| 248 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 249 | write!(f, "{:#02x?}", self.0) | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | #[cfg(feature = "defmt")] | ||
| 254 | impl<'a> defmt::Format for Bytes<'a> { | ||
| 255 | fn format(&self, fmt: defmt::Formatter) { | ||
| 256 | defmt::write!(fmt, "{:02x}", self.0) | ||
| 257 | } | ||
| 258 | } | ||
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs deleted file mode 100644 index c418cb262..000000000 --- a/embassy-boot/stm32/src/lib.rs +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![warn(missing_docs)] | ||
| 3 | #![doc = include_str!("../README.md")] | ||
| 4 | mod fmt; | ||
| 5 | |||
| 6 | pub use embassy_boot::{ | ||
| 7 | AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, | ||
| 8 | FirmwareUpdaterConfig, State, | ||
| 9 | }; | ||
| 10 | use embedded_storage::nor_flash::NorFlash; | ||
| 11 | |||
| 12 | /// A bootloader for STM32 devices. | ||
| 13 | pub struct BootLoader; | ||
| 14 | |||
| 15 | impl BootLoader { | ||
| 16 | /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware | ||
| 17 | pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>( | ||
| 18 | config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||
| 19 | ) -> Self { | ||
| 20 | let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||
| 21 | let mut boot = embassy_boot::BootLoader::new(config); | ||
| 22 | boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | ||
| 23 | Self | ||
| 24 | } | ||
| 25 | |||
| 26 | /// Boots the application. | ||
| 27 | /// | ||
| 28 | /// # Safety | ||
| 29 | /// | ||
| 30 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 31 | pub unsafe fn load(self, start: u32) -> ! { | ||
| 32 | trace!("Loading app at 0x{:x}", start); | ||
| 33 | #[allow(unused_mut)] | ||
| 34 | let mut p = cortex_m::Peripherals::steal(); | ||
| 35 | #[cfg(not(armv6m))] | ||
| 36 | p.SCB.invalidate_icache(); | ||
| 37 | p.SCB.vtor.write(start); | ||
| 38 | |||
| 39 | cortex_m::asm::bootload(start as *const u32) | ||
| 40 | } | ||
| 41 | } | ||
