aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-boot')
-rw-r--r--embassy-boot/boot/Cargo.toml13
-rw-r--r--embassy-boot/boot/README.md19
-rw-r--r--embassy-boot/boot/src/boot_loader.rs42
-rw-r--r--embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs4
-rw-r--r--embassy-boot/boot/src/firmware_updater/asynch.rs40
-rw-r--r--embassy-boot/boot/src/firmware_updater/blocking.rs44
-rw-r--r--embassy-boot/boot/src/lib.rs11
-rw-r--r--embassy-boot/nrf/Cargo.toml8
-rw-r--r--embassy-boot/rp/Cargo.toml6
-rw-r--r--embassy-boot/stm32/Cargo.toml6
-rw-r--r--embassy-boot/stm32/src/lib.rs9
11 files changed, 134 insertions, 68 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"]
26defmt = { version = "0.3", optional = true } 26defmt = { version = "0.3", optional = true }
27digest = "0.10" 27digest = "0.10"
28log = { version = "0.4", optional = true } 28log = { version = "0.4", optional = true }
29ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } 29ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true }
30embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } 30embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
31embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } 31embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
32embedded-storage = "0.3.1" 32embedded-storage = "0.3.1"
33embedded-storage-async = { version = "0.4.1" } 33embedded-storage-async = { version = "0.4.1" }
34salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } 34salty = { version = "0.3", optional = true }
35signature = { version = "1.6.4", default-features = false } 35signature = { version = "2.0", default-features = false }
36 36
37[dev-dependencies] 37[dev-dependencies]
38log = "0.4" 38log = "0.4"
39env_logger = "0.9" 39env_logger = "0.9"
40rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version 40rand = "0.8"
41futures = { version = "0.3", features = ["executor"] } 41futures = { version = "0.3", features = ["executor"] }
42sha1 = "0.10.5" 42sha1 = "0.10.5"
43critical-section = { version = "1.1.1", features = ["std"] } 43critical-section = { version = "1.1.1", features = ["std"] }
44 44ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] }
45[dev-dependencies.ed25519-dalek]
46default_features = false
47features = ["rand", "std", "u32_backend"]
48 45
49[features] 46[features]
50ed25519-dalek = ["dep:ed25519-dalek", "_verify"] 47ed25519-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
9By 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. 9By 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
13The 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
20For 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
25The 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
27For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html).
28
11## Hardware support 29## Hardware support
12 30
13The bootloader supports different hardware in separate crates: 31The 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;
5use embassy_sync::blocking_mutex::Mutex; 5use embassy_sync::blocking_mutex::Mutex;
6use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; 6use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
7 7
8use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 8use 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 @@
1use digest::typenum::U64; 1use digest::typenum::U64;
2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; 2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
3use ed25519_dalek::Digest as _; 3use ed25519_dalek::Digest;
4 4
5pub struct Sha512(ed25519_dalek::Sha512); 5pub struct Sha512(ed25519_dalek::Sha512);
6 6
@@ -12,7 +12,7 @@ impl Default for Sha512 {
12 12
13impl Update for Sha512 { 13impl 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;
6use embedded_storage_async::nor_flash::NorFlash; 6use embedded_storage_async::nor_flash::NorFlash;
7 7
8use super::FirmwareUpdaterConfig; 8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 9use 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
209impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { 212impl<'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;
6use embedded_storage::nor_flash::NorFlash; 6use embedded_storage::nor_flash::NorFlash;
7 7
8use super::FirmwareUpdaterConfig; 8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 9use 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
215impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { 218impl<'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
24pub(crate) const BOOT_MAGIC: u8 = 0xD0; 24pub(crate) const BOOT_MAGIC: u8 = 0xD0;
25pub(crate) const SWAP_MAGIC: u8 = 0xF0; 25pub(crate) const SWAP_MAGIC: u8 = 0xF0;
26pub(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/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml
index eea29cf2e..9f74fb126 100644
--- a/embassy-boot/nrf/Cargo.toml
+++ b/embassy-boot/nrf/Cargo.toml
@@ -4,6 +4,12 @@ name = "embassy-boot-nrf"
4version = "0.1.0" 4version = "0.1.0"
5description = "Bootloader lib for nRF chips" 5description = "Bootloader lib for nRF chips"
6license = "MIT OR Apache-2.0" 6license = "MIT OR Apache-2.0"
7repository = "https://github.com/embassy-rs/embassy"
8categories = [
9 "embedded",
10 "no-std",
11 "asynchronous",
12]
7 13
8[package.metadata.embassy_docs] 14[package.metadata.embassy_docs]
9src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" 15src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/"
@@ -25,7 +31,7 @@ embedded-storage = "0.3.1"
25embedded-storage-async = { version = "0.4.1" } 31embedded-storage-async = { version = "0.4.1" }
26cfg-if = "1.0.0" 32cfg-if = "1.0.0"
27 33
28nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true } 34nrf-softdevice-mbr = { version = "0.2.0", optional = true }
29 35
30[features] 36[features]
31defmt = [ 37defmt = [
diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml
index 0f2dc4628..90bab0996 100644
--- a/embassy-boot/rp/Cargo.toml
+++ b/embassy-boot/rp/Cargo.toml
@@ -4,6 +4,12 @@ name = "embassy-boot-rp"
4version = "0.1.0" 4version = "0.1.0"
5description = "Bootloader lib for RP2040 chips" 5description = "Bootloader lib for RP2040 chips"
6license = "MIT OR Apache-2.0" 6license = "MIT OR Apache-2.0"
7repository = "https://github.com/embassy-rs/embassy"
8categories = [
9 "embedded",
10 "no-std",
11 "asynchronous",
12]
7 13
8[package.metadata.embassy_docs] 14[package.metadata.embassy_docs]
9src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" 15src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/"
diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml
index bc8da6738..70919b76d 100644
--- a/embassy-boot/stm32/Cargo.toml
+++ b/embassy-boot/stm32/Cargo.toml
@@ -4,6 +4,12 @@ name = "embassy-boot-stm32"
4version = "0.1.0" 4version = "0.1.0"
5description = "Bootloader lib for STM32 chips" 5description = "Bootloader lib for STM32 chips"
6license = "MIT OR Apache-2.0" 6license = "MIT OR Apache-2.0"
7repository = "https://github.com/embassy-rs/embassy"
8categories = [
9 "embedded",
10 "no-std",
11 "asynchronous",
12]
7 13
8[package.metadata.embassy_docs] 14[package.metadata.embassy_docs]
9src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" 15src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/"
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
index c418cb262..4b4091ac9 100644
--- a/embassy-boot/stm32/src/lib.rs
+++ b/embassy-boot/stm32/src/lib.rs
@@ -10,7 +10,10 @@ pub use embassy_boot::{
10use embedded_storage::nor_flash::NorFlash; 10use embedded_storage::nor_flash::NorFlash;
11 11
12/// A bootloader for STM32 devices. 12/// A bootloader for STM32 devices.
13pub struct BootLoader; 13pub struct BootLoader {
14 /// The reported state of the bootloader after preparing for boot
15 pub state: State,
16}
14 17
15impl BootLoader { 18impl BootLoader {
16 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware 19 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
@@ -19,8 +22,8 @@ impl BootLoader {
19 ) -> Self { 22 ) -> Self {
20 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); 23 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
21 let mut boot = embassy_boot::BootLoader::new(config); 24 let mut boot = embassy_boot::BootLoader::new(config);
22 boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); 25 let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
23 Self 26 Self { state }
24 } 27 }
25 28
26 /// Boots the application. 29 /// Boots the application.