aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot
diff options
context:
space:
mode:
authorjrmoulton <[email protected]>2025-06-10 15:47:54 -0600
committerjrmoulton <[email protected]>2025-06-10 15:48:36 -0600
commitcfad9798ff99d4de0571a512d156b5fe1ef1d427 (patch)
treefc3bf670f82d139de19466cddad1e909db7f3d2e /embassy-boot
parentfc342915e6155dec7bafa3e135da7f37a9a07f5c (diff)
parent6186d111a5c150946ee5b7e9e68d987a38c1a463 (diff)
merge new embassy changes
Diffstat (limited to 'embassy-boot')
-rw-r--r--embassy-boot/Cargo.toml13
-rw-r--r--embassy-boot/src/boot_loader.rs6
-rw-r--r--embassy-boot/src/firmware_updater/asynch.rs33
-rw-r--r--embassy-boot/src/firmware_updater/blocking.rs35
-rw-r--r--embassy-boot/src/firmware_updater/mod.rs2
-rw-r--r--embassy-boot/src/lib.rs28
6 files changed, 84 insertions, 33 deletions
diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml
index 85b3695a1..f12e8e304 100644
--- a/embassy-boot/Cargo.toml
+++ b/embassy-boot/Cargo.toml
@@ -1,7 +1,7 @@
1[package] 1[package]
2edition = "2021" 2edition = "2021"
3name = "embassy-boot" 3name = "embassy-boot"
4version = "0.3.0" 4version = "0.4.0"
5description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." 5description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
6license = "MIT OR Apache-2.0" 6license = "MIT OR Apache-2.0"
7repository = "https://github.com/embassy-rs/embassy" 7repository = "https://github.com/embassy-rs/embassy"
@@ -24,12 +24,12 @@ features = ["defmt"]
24[lib] 24[lib]
25 25
26[dependencies] 26[dependencies]
27defmt = { version = "0.3", optional = true } 27defmt = { version = "1.0.1", optional = true }
28digest = "0.10" 28digest = "0.10"
29log = { version = "0.4", optional = true } 29log = { version = "0.4", optional = true }
30ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } 30ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true }
31embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } 31embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" }
32embassy-sync = { version = "0.6.0", path = "../embassy-sync" } 32embassy-sync = { version = "0.7.0", path = "../embassy-sync" }
33embedded-storage = "0.3.1" 33embedded-storage = "0.3.1"
34embedded-storage-async = { version = "0.4.1" } 34embedded-storage-async = { version = "0.4.1" }
35salty = { version = "0.3", optional = true } 35salty = { version = "0.3", optional = true }
@@ -42,11 +42,12 @@ rand = "0.8"
42futures = { version = "0.3", features = ["executor"] } 42futures = { version = "0.3", features = ["executor"] }
43sha1 = "0.10.5" 43sha1 = "0.10.5"
44critical-section = { version = "1.1.1", features = ["std"] } 44critical-section = { version = "1.1.1", features = ["std"] }
45ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } 45ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] }
46 46
47[features] 47[features]
48ed25519-dalek = ["dep:ed25519-dalek", "_verify"] 48ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
49ed25519-salty = ["dep:salty", "_verify"] 49ed25519-salty = ["dep:salty", "_verify"]
50flash-erase-zero = []
50 51
51#Internal features 52#Internal features
52_verify = [] 53_verify = []
diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs
index 61d61b96e..5bffdc5ea 100644
--- a/embassy-boot/src/boot_loader.rs
+++ b/embassy-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, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 8use crate::{State, DFU_DETACH_MAGIC, REVERT_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)]
@@ -276,7 +276,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
276 self.state.erase(0, self.state.capacity() as u32)?; 276 self.state.erase(0, self.state.capacity() as u32)?;
277 277
278 // Set magic 278 // Set magic
279 state_word.fill(BOOT_MAGIC); 279 state_word.fill(REVERT_MAGIC);
280 self.state.write(0, state_word)?; 280 self.state.write(0, state_word)?;
281 } 281 }
282 } 282 }
@@ -411,6 +411,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
411 Ok(State::Swap) 411 Ok(State::Swap)
412 } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { 412 } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
413 Ok(State::DfuDetach) 413 Ok(State::DfuDetach)
414 } else if !state_word.iter().any(|&b| b != REVERT_MAGIC) {
415 Ok(State::Revert)
414 } else { 416 } else {
415 Ok(State::Boot) 417 Ok(State::Boot)
416 } 418 }
diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index 26f65f295..66e311e38 100644
--- a/embassy-boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -107,7 +107,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
107 let mut message = [0; 64]; 107 let mut message = [0; 64];
108 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; 108 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
109 109
110 public_key.verify(&message, &signature).map_err(into_signature_error)? 110 public_key.verify(&message, &signature).map_err(into_signature_error)?;
111 return self.state.mark_updated().await;
111 } 112 }
112 #[cfg(feature = "ed25519-salty")] 113 #[cfg(feature = "ed25519-salty")]
113 { 114 {
@@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
134 message, 135 message,
135 r.is_ok() 136 r.is_ok()
136 ); 137 );
137 r.map_err(into_signature_error)? 138 r.map_err(into_signature_error)?;
139 return self.state.mark_updated().await;
140 }
141 #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
142 {
143 Err(FirmwareUpdaterError::Signature(signature::Error::new()))
138 } 144 }
139
140 self.state.mark_updated().await
141 } 145 }
142 146
143 /// Verify the update in DFU with any digest. 147 /// Verify the update in DFU with any digest.
@@ -157,6 +161,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
157 Ok(()) 161 Ok(())
158 } 162 }
159 163
164 /// Read a slice of data from the DFU storage peripheral, starting the read
165 /// operation at the given address offset, and reading `buf.len()` bytes.
166 ///
167 /// # Errors
168 ///
169 /// Returns an error if the arguments are not aligned or out of bounds.
170 pub async fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
171 self.dfu.read(offset, buf).await?;
172 Ok(())
173 }
174
160 /// Mark to trigger firmware swap on next boot. 175 /// Mark to trigger firmware swap on next boot.
161 #[cfg(not(feature = "_verify"))] 176 #[cfg(not(feature = "_verify"))]
162 pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { 177 pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
@@ -285,7 +300,8 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
285 300
286 // Make sure we are running a booted firmware to avoid reverting to a bad state. 301 // Make sure we are running a booted firmware to avoid reverting to a bad state.
287 async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 302 async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
288 if self.get_state().await? == State::Boot { 303 let state = self.get_state().await?;
304 if state == State::Boot || state == State::DfuDetach || state == State::Revert {
289 Ok(()) 305 Ok(())
290 } else { 306 } else {
291 Err(FirmwareUpdaterError::BadState) 307 Err(FirmwareUpdaterError::BadState)
@@ -299,12 +315,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
299 /// `mark_booted`. 315 /// `mark_booted`.
300 pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { 316 pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
301 self.state.read(0, &mut self.aligned).await?; 317 self.state.read(0, &mut self.aligned).await?;
302 318 Ok(State::from(&self.aligned[..STATE::WRITE_SIZE]))
303 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
304 Ok(State::Swap)
305 } else {
306 Ok(State::Boot)
307 }
308 } 319 }
309 320
310 /// Mark to trigger firmware swap on next boot. 321 /// Mark to trigger firmware swap on next boot.
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 35772a856..0fedac1ea 100644
--- a/embassy-boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
142 let mut chunk_buf = [0; 2]; 142 let mut chunk_buf = [0; 2];
143 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?; 143 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
144 144
145 public_key.verify(&message, &signature).map_err(into_signature_error)? 145 public_key.verify(&message, &signature).map_err(into_signature_error)?;
146 return self.state.mark_updated();
146 } 147 }
147 #[cfg(feature = "ed25519-salty")] 148 #[cfg(feature = "ed25519-salty")]
148 { 149 {
@@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
169 message, 170 message,
170 r.is_ok() 171 r.is_ok()
171 ); 172 );
172 r.map_err(into_signature_error)? 173 r.map_err(into_signature_error)?;
174 return self.state.mark_updated();
175 }
176 #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
177 {
178 Err(FirmwareUpdaterError::Signature(signature::Error::new()))
173 } 179 }
174
175 self.state.mark_updated()
176 } 180 }
177 181
178 /// Verify the update in DFU with any digest. 182 /// Verify the update in DFU with any digest.
@@ -192,6 +196,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
192 Ok(()) 196 Ok(())
193 } 197 }
194 198
199 /// Read a slice of data from the DFU storage peripheral, starting the read
200 /// operation at the given address offset, and reading `buf.len()` bytes.
201 ///
202 /// # Errors
203 ///
204 /// Returns an error if the arguments are not aligned or out of bounds.
205 pub fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
206 self.dfu.read(offset, buf)?;
207 Ok(())
208 }
209
195 /// Mark to trigger firmware swap on next boot. 210 /// Mark to trigger firmware swap on next boot.
196 #[cfg(not(feature = "_verify"))] 211 #[cfg(not(feature = "_verify"))]
197 pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { 212 pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
@@ -320,7 +335,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
320 335
321 // Make sure we are running a booted firmware to avoid reverting to a bad state. 336 // Make sure we are running a booted firmware to avoid reverting to a bad state.
322 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 337 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
323 if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { 338 let state = self.get_state()?;
339 if state == State::Boot || state == State::DfuDetach || state == State::Revert {
324 Ok(()) 340 Ok(())
325 } else { 341 } else {
326 Err(FirmwareUpdaterError::BadState) 342 Err(FirmwareUpdaterError::BadState)
@@ -334,14 +350,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
334 /// `mark_booted`. 350 /// `mark_booted`.
335 pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { 351 pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
336 self.state.read(0, &mut self.aligned)?; 352 self.state.read(0, &mut self.aligned)?;
337 353 Ok(State::from(&self.aligned))
338 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
339 Ok(State::Swap)
340 } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
341 Ok(State::DfuDetach)
342 } else {
343 Ok(State::Boot)
344 }
345 } 354 }
346 355
347 /// Mark to trigger firmware swap on next boot. 356 /// Mark to trigger firmware swap on next boot.
diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs
index 4c4f4f10b..4814786bf 100644
--- a/embassy-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_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition 11/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] 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.
13pub struct FirmwareUpdaterConfig<DFU, STATE> { 13pub struct FirmwareUpdaterConfig<DFU, STATE> {
14 /// The dfu flash partition 14 /// The dfu flash partition
diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs
index b4f03e01e..e2c4cf771 100644
--- a/embassy-boot/src/lib.rs
+++ b/embassy-boot/src/lib.rs
@@ -14,13 +14,18 @@ mod test_flash;
14 14
15// The expected value of the flash after an erase 15// The expected value of the flash after an erase
16// TODO: Use the value provided by NorFlash when available 16// TODO: Use the value provided by NorFlash when available
17#[cfg(not(feature = "flash-erase-zero"))]
17pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; 18pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
19#[cfg(feature = "flash-erase-zero")]
20pub(crate) const STATE_ERASE_VALUE: u8 = 0x00;
21
18pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; 22pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
19pub use firmware_updater::{ 23pub use firmware_updater::{
20 BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, 24 BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
21 FirmwareUpdaterError, 25 FirmwareUpdaterError,
22}; 26};
23 27
28pub(crate) const REVERT_MAGIC: u8 = 0xC0;
24pub(crate) const BOOT_MAGIC: u8 = 0xD0; 29pub(crate) const BOOT_MAGIC: u8 = 0xD0;
25pub(crate) const SWAP_MAGIC: u8 = 0xF0; 30pub(crate) const SWAP_MAGIC: u8 = 0xF0;
26pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; 31pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
@@ -33,10 +38,30 @@ pub enum State {
33 Boot, 38 Boot,
34 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. 39 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
35 Swap, 40 Swap,
41 /// Bootloader has reverted the active partition with the dfu partition and will attempt boot.
42 Revert,
36 /// Application has received a request to reboot into DFU mode to apply an update. 43 /// Application has received a request to reboot into DFU mode to apply an update.
37 DfuDetach, 44 DfuDetach,
38} 45}
39 46
47impl<T> From<T> for State
48where
49 T: AsRef<[u8]>,
50{
51 fn from(magic: T) -> State {
52 let magic = magic.as_ref();
53 if !magic.iter().any(|&b| b != SWAP_MAGIC) {
54 State::Swap
55 } else if !magic.iter().any(|&b| b != REVERT_MAGIC) {
56 State::Revert
57 } else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) {
58 State::DfuDetach
59 } else {
60 State::Boot
61 }
62 }
63}
64
40/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. 65/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
41#[repr(align(32))] 66#[repr(align(32))]
42pub struct AlignedBuffer<const N: usize>(pub [u8; N]); 67pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
@@ -153,6 +178,9 @@ mod tests {
153 // Running again should cause a revert 178 // Running again should cause a revert
154 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); 179 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
155 180
181 // Next time we know it was reverted
182 assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap());
183
156 let mut read_buf = [0; FIRMWARE_SIZE]; 184 let mut read_buf = [0; FIRMWARE_SIZE];
157 flash.active().read(0, &mut read_buf).unwrap(); 185 flash.active().read(0, &mut read_buf).unwrap();
158 assert_eq!(ORIGINAL, read_buf); 186 assert_eq!(ORIGINAL, read_buf);