diff options
Diffstat (limited to 'embassy-boot/boot')
| -rw-r--r-- | embassy-boot/boot/Cargo.toml | 13 | ||||
| -rw-r--r-- | embassy-boot/boot/README.md | 19 | ||||
| -rw-r--r-- | embassy-boot/boot/src/boot_loader.rs | 42 | ||||
| -rw-r--r-- | embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs | 4 | ||||
| -rw-r--r-- | embassy-boot/boot/src/firmware_updater/asynch.rs | 40 | ||||
| -rw-r--r-- | embassy-boot/boot/src/firmware_updater/blocking.rs | 44 | ||||
| -rw-r--r-- | embassy-boot/boot/src/lib.rs | 11 |
7 files changed, 109 insertions, 64 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index dd2ff8158..3c84ffcd3 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml | |||
| @@ -26,25 +26,22 @@ features = ["defmt"] | |||
| 26 | defmt = { version = "0.3", optional = true } | 26 | defmt = { version = "0.3", optional = true } |
| 27 | digest = "0.10" | 27 | digest = "0.10" |
| 28 | log = { version = "0.4", optional = true } | 28 | log = { version = "0.4", optional = true } |
| 29 | ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } | 29 | ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } |
| 30 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } | 30 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } |
| 31 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } | 31 | embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } |
| 32 | embedded-storage = "0.3.1" | 32 | embedded-storage = "0.3.1" |
| 33 | embedded-storage-async = { version = "0.4.1" } | 33 | embedded-storage-async = { version = "0.4.1" } |
| 34 | salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } | 34 | salty = { version = "0.3", optional = true } |
| 35 | signature = { version = "1.6.4", default-features = false } | 35 | signature = { version = "2.0", default-features = false } |
| 36 | 36 | ||
| 37 | [dev-dependencies] | 37 | [dev-dependencies] |
| 38 | log = "0.4" | 38 | log = "0.4" |
| 39 | env_logger = "0.9" | 39 | env_logger = "0.9" |
| 40 | rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version | 40 | rand = "0.8" |
| 41 | futures = { version = "0.3", features = ["executor"] } | 41 | futures = { version = "0.3", features = ["executor"] } |
| 42 | sha1 = "0.10.5" | 42 | sha1 = "0.10.5" |
| 43 | critical-section = { version = "1.1.1", features = ["std"] } | 43 | critical-section = { version = "1.1.1", features = ["std"] } |
| 44 | 44 | 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 | 45 | ||
| 49 | [features] | 46 | [features] |
| 50 | ed25519-dalek = ["dep:ed25519-dalek", "_verify"] | 47 | ed25519-dalek = ["dep:ed25519-dalek", "_verify"] |
diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md index 07755bc6c..3fc81f24b 100644 --- a/embassy-boot/boot/README.md +++ b/embassy-boot/boot/README.md | |||
| @@ -8,6 +8,24 @@ The bootloader can be used either as a library or be flashed directly with the d | |||
| 8 | 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. | 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 | 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 | |||
| 11 | ## Hardware support | 29 | ## Hardware support |
| 12 | 30 | ||
| 13 | The bootloader supports different hardware in separate crates: | 31 | The bootloader supports different hardware in separate crates: |
| @@ -16,6 +34,7 @@ The bootloader supports different hardware in separate crates: | |||
| 16 | * `embassy-boot-rp` - for the RP2040 microcontrollers. | 34 | * `embassy-boot-rp` - for the RP2040 microcontrollers. |
| 17 | * `embassy-boot-stm32` - for the STM32 microcontrollers. | 35 | * `embassy-boot-stm32` - for the STM32 microcontrollers. |
| 18 | 36 | ||
| 37 | |||
| 19 | ## Minimum supported Rust version (MSRV) | 38 | ## Minimum supported Rust version (MSRV) |
| 20 | 39 | ||
| 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. | 40 | `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. |
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index a8c19197b..e568001bc 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/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)] |
| @@ -135,51 +135,44 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 135 | /// The provided aligned_buf argument must satisfy any alignment requirements | 135 | /// The provided aligned_buf argument must satisfy any alignment requirements |
| 136 | /// given by the partition flashes. All flash operations will use this buffer. | 136 | /// given by the partition flashes. All flash operations will use this buffer. |
| 137 | /// | 137 | /// |
| 138 | /// SWAPPING | 138 | /// ## SWAPPING |
| 139 | /// | 139 | /// |
| 140 | /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. | 140 | /// 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 | 141 | /// 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 | 142 | /// 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) | 143 | /// 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 | 144 | /// 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"). | 145 | /// avoid a situation where the wrong index value is set (page write size is "atomic"). |
| 146 | /// | 146 | /// |
| 147 | /// +-----------+------------+--------+--------+--------+--------+ | 147 | /// |
| 148 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 148 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 149 | /// +-----------+------------+--------+--------+--------+--------+ | 149 | /// |-----------|------------|--------|--------|--------|--------| |
| 150 | /// | Active | 0 | 1 | 2 | 3 | - | | 150 | /// | Active | 0 | 1 | 2 | 3 | - | |
| 151 | /// | DFU | 0 | 3 | 2 | 1 | X | | 151 | /// | DFU | 0 | 3 | 2 | 1 | X | |
| 152 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 153 | /// | 152 | /// |
| 154 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is | 153 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is |
| 155 | /// as follows: | 154 | /// as follows: |
| 156 | /// | 155 | /// |
| 157 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 158 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 156 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 159 | /// +-----------+------------+--------+--------+--------+--------+ | 157 | /// |-----------|------------|--------|--------|--------|--------| |
| 160 | /// | Active | 1 | 1 | 2 | 1 | - | | 158 | /// | Active | 1 | 1 | 2 | 1 | - | |
| 161 | /// | DFU | 1 | 3 | 2 | 1 | 3 | | 159 | /// | DFU | 1 | 3 | 2 | 1 | 3 | |
| 162 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 163 | /// | 160 | /// |
| 164 | /// The next iteration performs the same steps | 161 | /// The next iteration performs the same steps |
| 165 | /// | 162 | /// |
| 166 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 167 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 163 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 168 | /// +-----------+------------+--------+--------+--------+--------+ | 164 | /// |-----------|------------|--------|--------|--------|--------| |
| 169 | /// | Active | 2 | 1 | 2 | 1 | - | | 165 | /// | Active | 2 | 1 | 2 | 1 | - | |
| 170 | /// | DFU | 2 | 3 | 2 | 2 | 3 | | 166 | /// | DFU | 2 | 3 | 2 | 2 | 3 | |
| 171 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 172 | /// | 167 | /// |
| 173 | /// And again until we're done | 168 | /// And again until we're done |
| 174 | /// | 169 | /// |
| 175 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 176 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | 170 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 177 | /// +-----------+------------+--------+--------+--------+--------+ | 171 | /// |-----------|------------|--------|--------|--------|--------| |
| 178 | /// | Active | 3 | 3 | 2 | 1 | - | | 172 | /// | Active | 3 | 3 | 2 | 1 | - | |
| 179 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | 173 | /// | DFU | 3 | 3 | 1 | 2 | 3 | |
| 180 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 181 | /// | 174 | /// |
| 182 | /// REVERTING | 175 | /// ## REVERTING |
| 183 | /// | 176 | /// |
| 184 | /// The reverting algorithm uses the swap index to discover that images were swapped, but that | 177 | /// 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 | 178 | /// the application failed to mark the boot successful. In this case, the revert algorithm will |
| @@ -190,28 +183,21 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 190 | /// | 183 | /// |
| 191 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. | 184 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. |
| 192 | /// | 185 | /// |
| 193 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 194 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | 186 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 195 | //*/ | 187 | /// |-----------|--------------|--------|--------|--------|--------| |
| 196 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 197 | /// | Active | 3 | 1 | 2 | 1 | - | | 188 | /// | Active | 3 | 1 | 2 | 1 | - | |
| 198 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | 189 | /// | DFU | 3 | 3 | 1 | 2 | 3 | |
| 199 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 200 | /// | 190 | /// |
| 201 | /// | 191 | /// |
| 202 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 203 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | 192 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 204 | /// +-----------+--------------+--------+--------+--------+--------+ | 193 | /// |-----------|--------------|--------|--------|--------|--------| |
| 205 | /// | Active | 3 | 1 | 2 | 1 | - | | 194 | /// | Active | 3 | 1 | 2 | 1 | - | |
| 206 | /// | DFU | 3 | 3 | 2 | 2 | 3 | | 195 | /// | DFU | 3 | 3 | 2 | 2 | 3 | |
| 207 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 208 | /// | 196 | /// |
| 209 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 210 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | 197 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | |
| 211 | /// +-----------+--------------+--------+--------+--------+--------+ | 198 | /// |-----------|--------------|--------|--------|--------|--------| |
| 212 | /// | Active | 3 | 1 | 2 | 3 | - | | 199 | /// | Active | 3 | 1 | 2 | 3 | - | |
| 213 | /// | DFU | 3 | 3 | 2 | 1 | 3 | | 200 | /// | DFU | 3 | 3 | 2 | 1 | 3 | |
| 214 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 215 | /// | 201 | /// |
| 216 | pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | 202 | pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { |
| 217 | // Ensure we have enough progress pages to store copy progress | 203 | // Ensure we have enough progress pages to store copy progress |
| @@ -224,6 +210,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 224 | assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); | 210 | assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); |
| 225 | assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); | 211 | assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); |
| 226 | 212 | ||
| 213 | // Ensure our partitions are able to handle boot operations | ||
| 227 | assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); | 214 | assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); |
| 228 | 215 | ||
| 229 | // Copy contents from partition N to active | 216 | // Copy contents from partition N to active |
| @@ -384,6 +371,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 384 | 371 | ||
| 385 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { | 372 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { |
| 386 | Ok(State::Swap) | 373 | Ok(State::Swap) |
| 374 | } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 375 | Ok(State::DfuDetach) | ||
| 387 | } else { | 376 | } else { |
| 388 | Ok(State::Boot) | 377 | Ok(State::Boot) |
| 389 | } | 378 | } |
| @@ -398,6 +387,7 @@ fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | |||
| 398 | ) { | 387 | ) { |
| 399 | assert_eq!(active.capacity() as u32 % page_size, 0); | 388 | assert_eq!(active.capacity() as u32 % page_size, 0); |
| 400 | assert_eq!(dfu.capacity() as u32 % page_size, 0); | 389 | assert_eq!(dfu.capacity() as u32 % page_size, 0); |
| 390 | // 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); | 391 | 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); | 392 | assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); |
| 403 | } | 393 | } |
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs index a184d1c51..2e4e03da3 100644 --- a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs +++ b/embassy-boot/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/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index ae713bb6f..64a4b32ec 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs | |||
| @@ -6,7 +6,7 @@ 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 |
| @@ -79,8 +79,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 79 | #[cfg(feature = "_verify")] | 79 | #[cfg(feature = "_verify")] |
| 80 | pub async fn verify_and_mark_updated( | 80 | pub async fn verify_and_mark_updated( |
| 81 | &mut self, | 81 | &mut self, |
| 82 | _public_key: &[u8], | 82 | _public_key: &[u8; 32], |
| 83 | _signature: &[u8], | 83 | _signature: &[u8; 64], |
| 84 | _update_len: u32, | 84 | _update_len: u32, |
| 85 | ) -> Result<(), FirmwareUpdaterError> { | 85 | ) -> Result<(), FirmwareUpdaterError> { |
| 86 | assert!(_update_len <= self.dfu.capacity() as u32); | 86 | assert!(_update_len <= self.dfu.capacity() as u32); |
| @@ -89,14 +89,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 89 | 89 | ||
| 90 | #[cfg(feature = "ed25519-dalek")] | 90 | #[cfg(feature = "ed25519-dalek")] |
| 91 | { | 91 | { |
| 92 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | 92 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; |
| 93 | 93 | ||
| 94 | use crate::digest_adapters::ed25519_dalek::Sha512; | 94 | use crate::digest_adapters::ed25519_dalek::Sha512; |
| 95 | 95 | ||
| 96 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | 96 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); |
| 97 | 97 | ||
| 98 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | 98 | 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)?; | 99 | let signature = Signature::from_bytes(_signature); |
| 100 | 100 | ||
| 101 | let mut chunk_buf = [0; 2]; | 101 | let mut chunk_buf = [0; 2]; |
| 102 | let mut message = [0; 64]; | 102 | let mut message = [0; 64]; |
| @@ -106,7 +106,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 106 | } | 106 | } |
| 107 | #[cfg(feature = "ed25519-salty")] | 107 | #[cfg(feature = "ed25519-salty")] |
| 108 | { | 108 | { |
| 109 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||
| 110 | use salty::{PublicKey, Signature}; | 109 | use salty::{PublicKey, Signature}; |
| 111 | 110 | ||
| 112 | use crate::digest_adapters::salty::Sha512; | 111 | use crate::digest_adapters::salty::Sha512; |
| @@ -115,10 +114,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 115 | FirmwareUpdaterError::Signature(signature::Error::default()) | 114 | FirmwareUpdaterError::Signature(signature::Error::default()) |
| 116 | } | 115 | } |
| 117 | 116 | ||
| 118 | let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | 117 | 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)?; | 118 | 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 | 119 | ||
| 123 | let mut message = [0; 64]; | 120 | let mut message = [0; 64]; |
| 124 | let mut chunk_buf = [0; 2]; | 121 | let mut chunk_buf = [0; 2]; |
| @@ -161,6 +158,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 161 | self.state.mark_updated().await | 158 | self.state.mark_updated().await |
| 162 | } | 159 | } |
| 163 | 160 | ||
| 161 | /// Mark to trigger USB DFU on next boot. | ||
| 162 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 163 | self.state.verify_booted().await?; | ||
| 164 | self.state.mark_dfu().await | ||
| 165 | } | ||
| 166 | |||
| 164 | /// Mark firmware boot successful and stop rollback on reset. | 167 | /// Mark firmware boot successful and stop rollback on reset. |
| 165 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 168 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 166 | self.state.mark_booted().await | 169 | self.state.mark_booted().await |
| @@ -207,6 +210,16 @@ pub struct FirmwareState<'d, STATE> { | |||
| 207 | } | 210 | } |
| 208 | 211 | ||
| 209 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | 212 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { |
| 213 | /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. | ||
| 214 | /// | ||
| 215 | /// # Safety | ||
| 216 | /// | ||
| 217 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 218 | /// and written to. | ||
| 219 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 220 | Self::new(config.state, aligned) | ||
| 221 | } | ||
| 222 | |||
| 210 | /// Create a firmware state instance with a buffer for magic content and state partition. | 223 | /// Create a firmware state instance with a buffer for magic content and state partition. |
| 211 | /// | 224 | /// |
| 212 | /// # Safety | 225 | /// # Safety |
| @@ -247,6 +260,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | |||
| 247 | self.set_magic(SWAP_MAGIC).await | 260 | self.set_magic(SWAP_MAGIC).await |
| 248 | } | 261 | } |
| 249 | 262 | ||
| 263 | /// Mark to trigger USB DFU on next boot. | ||
| 264 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 265 | self.set_magic(DFU_DETACH_MAGIC).await | ||
| 266 | } | ||
| 267 | |||
| 250 | /// Mark firmware boot successful and stop rollback on reset. | 268 | /// Mark firmware boot successful and stop rollback on reset. |
| 251 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 269 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 252 | self.set_magic(BOOT_MAGIC).await | 270 | self.set_magic(BOOT_MAGIC).await |
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index 76e4264a0..f1368540d 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs | |||
| @@ -6,7 +6,7 @@ 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 |
| @@ -86,8 +86,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 86 | #[cfg(feature = "_verify")] | 86 | #[cfg(feature = "_verify")] |
| 87 | pub fn verify_and_mark_updated( | 87 | pub fn verify_and_mark_updated( |
| 88 | &mut self, | 88 | &mut self, |
| 89 | _public_key: &[u8], | 89 | _public_key: &[u8; 32], |
| 90 | _signature: &[u8], | 90 | _signature: &[u8; 64], |
| 91 | _update_len: u32, | 91 | _update_len: u32, |
| 92 | ) -> Result<(), FirmwareUpdaterError> { | 92 | ) -> Result<(), FirmwareUpdaterError> { |
| 93 | assert!(_update_len <= self.dfu.capacity() as u32); | 93 | assert!(_update_len <= self.dfu.capacity() as u32); |
| @@ -96,14 +96,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 96 | 96 | ||
| 97 | #[cfg(feature = "ed25519-dalek")] | 97 | #[cfg(feature = "ed25519-dalek")] |
| 98 | { | 98 | { |
| 99 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | 99 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; |
| 100 | 100 | ||
| 101 | use crate::digest_adapters::ed25519_dalek::Sha512; | 101 | use crate::digest_adapters::ed25519_dalek::Sha512; |
| 102 | 102 | ||
| 103 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | 103 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); |
| 104 | 104 | ||
| 105 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | 105 | 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)?; | 106 | let signature = Signature::from_bytes(_signature); |
| 107 | 107 | ||
| 108 | let mut message = [0; 64]; | 108 | let mut message = [0; 64]; |
| 109 | let mut chunk_buf = [0; 2]; | 109 | let mut chunk_buf = [0; 2]; |
| @@ -113,7 +113,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 113 | } | 113 | } |
| 114 | #[cfg(feature = "ed25519-salty")] | 114 | #[cfg(feature = "ed25519-salty")] |
| 115 | { | 115 | { |
| 116 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||
| 117 | use salty::{PublicKey, Signature}; | 116 | use salty::{PublicKey, Signature}; |
| 118 | 117 | ||
| 119 | use crate::digest_adapters::salty::Sha512; | 118 | use crate::digest_adapters::salty::Sha512; |
| @@ -122,10 +121,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 122 | FirmwareUpdaterError::Signature(signature::Error::default()) | 121 | FirmwareUpdaterError::Signature(signature::Error::default()) |
| 123 | } | 122 | } |
| 124 | 123 | ||
| 125 | let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | 124 | 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)?; | 125 | 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 | 126 | ||
| 130 | let mut message = [0; 64]; | 127 | let mut message = [0; 64]; |
| 131 | let mut chunk_buf = [0; 2]; | 128 | let mut chunk_buf = [0; 2]; |
| @@ -168,6 +165,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 168 | self.state.mark_updated() | 165 | self.state.mark_updated() |
| 169 | } | 166 | } |
| 170 | 167 | ||
| 168 | /// Mark to trigger USB DFU device on next boot. | ||
| 169 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 170 | self.state.verify_booted()?; | ||
| 171 | self.state.mark_dfu() | ||
| 172 | } | ||
| 173 | |||
| 171 | /// Mark firmware boot successful and stop rollback on reset. | 174 | /// Mark firmware boot successful and stop rollback on reset. |
| 172 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 175 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 173 | self.state.mark_booted() | 176 | self.state.mark_booted() |
| @@ -213,6 +216,16 @@ pub struct BlockingFirmwareState<'d, STATE> { | |||
| 213 | } | 216 | } |
| 214 | 217 | ||
| 215 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | 218 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { |
| 219 | /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. | ||
| 220 | /// | ||
| 221 | /// # Safety | ||
| 222 | /// | ||
| 223 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 224 | /// and written to. | ||
| 225 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 226 | Self::new(config.state, aligned) | ||
| 227 | } | ||
| 228 | |||
| 216 | /// Create a firmware state instance with a buffer for magic content and state partition. | 229 | /// Create a firmware state instance with a buffer for magic content and state partition. |
| 217 | /// | 230 | /// |
| 218 | /// # Safety | 231 | /// # Safety |
| @@ -226,7 +239,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 226 | 239 | ||
| 227 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | 240 | // Make sure we are running a booted firmware to avoid reverting to a bad state. |
| 228 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 241 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 229 | if self.get_state()? == State::Boot { | 242 | if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { |
| 230 | Ok(()) | 243 | Ok(()) |
| 231 | } else { | 244 | } else { |
| 232 | Err(FirmwareUpdaterError::BadState) | 245 | Err(FirmwareUpdaterError::BadState) |
| @@ -243,6 +256,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 243 | 256 | ||
| 244 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | 257 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { |
| 245 | Ok(State::Swap) | 258 | Ok(State::Swap) |
| 259 | } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 260 | Ok(State::DfuDetach) | ||
| 246 | } else { | 261 | } else { |
| 247 | Ok(State::Boot) | 262 | Ok(State::Boot) |
| 248 | } | 263 | } |
| @@ -253,6 +268,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 253 | self.set_magic(SWAP_MAGIC) | 268 | self.set_magic(SWAP_MAGIC) |
| 254 | } | 269 | } |
| 255 | 270 | ||
| 271 | /// Mark to trigger USB DFU on next boot. | ||
| 272 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 273 | self.set_magic(DFU_DETACH_MAGIC) | ||
| 274 | } | ||
| 275 | |||
| 256 | /// Mark firmware boot successful and stop rollback on reset. | 276 | /// Mark firmware boot successful and stop rollback on reset. |
| 257 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 277 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 258 | self.set_magic(BOOT_MAGIC) | 278 | self.set_magic(BOOT_MAGIC) |
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 9e70a4dca..b4f03e01e 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/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 { |
