aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/src
diff options
context:
space:
mode:
authorFrostie314159 <[email protected]>2024-03-31 20:48:05 +0200
committerGitHub <[email protected]>2024-03-31 20:48:05 +0200
commit67c9cc2c4b886e6962ecdd6eff8794b14c1accdc (patch)
treef176ab269949d26f48e04c950cebc5489bae8c56 /embassy-boot/src
parenta2f9aa592ec61beb247065003016515f0d423c13 (diff)
parent6634cc90bcd3eb25b64712688920f383584b2964 (diff)
Merge branch 'embassy-rs:main' into ticker_send_sync
Diffstat (limited to 'embassy-boot/src')
-rw-r--r--embassy-boot/src/boot_loader.rs446
-rw-r--r--embassy-boot/src/digest_adapters/ed25519_dalek.rs30
-rw-r--r--embassy-boot/src/digest_adapters/mod.rs5
-rw-r--r--embassy-boot/src/digest_adapters/salty.rs29
-rw-r--r--embassy-boot/src/firmware_updater/asynch.rs462
-rw-r--r--embassy-boot/src/firmware_updater/blocking.rs499
-rw-r--r--embassy-boot/src/firmware_updater/mod.rs49
-rw-r--r--embassy-boot/src/fmt.rs257
-rw-r--r--embassy-boot/src/lib.rs323
-rw-r--r--embassy-boot/src/mem_flash.rs170
-rw-r--r--embassy-boot/src/test_flash/asynch.rs64
-rw-r--r--embassy-boot/src/test_flash/blocking.rs68
-rw-r--r--embassy-boot/src/test_flash/mod.rs5
13 files changed, 2407 insertions, 0 deletions
diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs
new file mode 100644
index 000000000..a38558056
--- /dev/null
+++ b/embassy-boot/src/boot_loader.rs
@@ -0,0 +1,446 @@
1use core::cell::RefCell;
2
3use embassy_embedded_hal::flash::partition::BlockingPartition;
4use embassy_sync::blocking_mutex::raw::NoopRawMutex;
5use embassy_sync::blocking_mutex::Mutex;
6use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
7
8use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
9
10/// Errors returned by bootloader
11#[derive(PartialEq, Eq, Debug)]
12pub enum BootError {
13 /// Error from flash.
14 Flash(NorFlashErrorKind),
15 /// Invalid bootloader magic
16 BadMagic,
17}
18
19#[cfg(feature = "defmt")]
20impl defmt::Format for BootError {
21 fn format(&self, fmt: defmt::Formatter) {
22 match self {
23 BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
24 BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
25 }
26 }
27}
28
29impl<E> From<E> for BootError
30where
31 E: NorFlashError,
32{
33 fn from(error: E) -> Self {
34 BootError::Flash(error.kind())
35 }
36}
37
38/// Bootloader flash configuration holding the three flashes used by the bootloader
39///
40/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use.
41/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition
42/// the provided flash according to symbols defined in the linkerfile.
43pub struct BootLoaderConfig<ACTIVE, DFU, STATE> {
44 /// Flash type used for the active partition - the partition which will be booted from.
45 pub active: ACTIVE,
46 /// Flash type used for the dfu partition - the partition which will be swapped in when requested.
47 pub dfu: DFU,
48 /// Flash type used for the state partition.
49 pub state: STATE,
50}
51
52impl<'a, ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>
53 BootLoaderConfig<
54 BlockingPartition<'a, NoopRawMutex, ACTIVE>,
55 BlockingPartition<'a, NoopRawMutex, DFU>,
56 BlockingPartition<'a, NoopRawMutex, STATE>,
57 >
58{
59 /// Constructs a `BootLoaderConfig` instance from flash memory and address symbols defined in the linker file.
60 ///
61 /// This method initializes `BlockingPartition` instances for the active, DFU (Device Firmware Update),
62 /// and state partitions, leveraging start and end addresses specified by the linker. These partitions
63 /// are critical for managing firmware updates, application state, and boot operations within the bootloader.
64 ///
65 /// # Parameters
66 /// - `active_flash`: A reference to a mutex-protected `RefCell` for the active partition's flash interface.
67 /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface.
68 /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface.
69 ///
70 /// # Safety
71 /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses
72 /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined
73 /// in the memory.x file to prevent undefined behavior.
74 ///
75 /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory
76 /// interfaces provided are compatible with these regions.
77 ///
78 /// # Returns
79 /// A `BootLoaderConfig` instance with `BlockingPartition` instances for the active, DFU, and state partitions.
80 ///
81 /// # Example
82 /// ```ignore
83 /// // Assume `active_flash`, `dfu_flash`, and `state_flash` all share the same flash memory interface.
84 /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
85 /// let flash = Mutex::new(RefCell::new(layout.bank1_region));
86 ///
87 /// let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
88 /// // `config` can now be used to create a `BootLoader` instance for managing boot operations.
89 /// ```
90 /// Working examples can be found in the bootloader examples folder.
91 // #[cfg(target_os = "none")]
92 pub fn from_linkerfile_blocking(
93 active_flash: &'a Mutex<NoopRawMutex, RefCell<ACTIVE>>,
94 dfu_flash: &'a Mutex<NoopRawMutex, RefCell<DFU>>,
95 state_flash: &'a Mutex<NoopRawMutex, RefCell<STATE>>,
96 ) -> Self {
97 extern "C" {
98 static __bootloader_state_start: u32;
99 static __bootloader_state_end: u32;
100 static __bootloader_active_start: u32;
101 static __bootloader_active_end: u32;
102 static __bootloader_dfu_start: u32;
103 static __bootloader_dfu_end: u32;
104 }
105
106 let active = unsafe {
107 let start = &__bootloader_active_start as *const u32 as u32;
108 let end = &__bootloader_active_end as *const u32 as u32;
109 trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end);
110
111 BlockingPartition::new(active_flash, start, end - start)
112 };
113 let dfu = unsafe {
114 let start = &__bootloader_dfu_start as *const u32 as u32;
115 let end = &__bootloader_dfu_end as *const u32 as u32;
116 trace!("DFU: 0x{:x} - 0x{:x}", start, end);
117
118 BlockingPartition::new(dfu_flash, start, end - start)
119 };
120 let state = unsafe {
121 let start = &__bootloader_state_start as *const u32 as u32;
122 let end = &__bootloader_state_end as *const u32 as u32;
123 trace!("STATE: 0x{:x} - 0x{:x}", start, end);
124
125 BlockingPartition::new(state_flash, start, end - start)
126 };
127
128 Self { active, dfu, state }
129 }
130}
131
132/// BootLoader works with any flash implementing embedded_storage.
133pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> {
134 active: ACTIVE,
135 dfu: DFU,
136 /// The state partition has the following format:
137 /// All ranges are in multiples of WRITE_SIZE bytes.
138 /// | Range | Description |
139 /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
140 /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
141 /// | 2..2 + N | Progress index used while swapping or reverting
142 state: STATE,
143}
144
145impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> {
146 /// Get the page size which is the "unit of operation" within the bootloader.
147 const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE {
148 ACTIVE::ERASE_SIZE as u32
149 } else {
150 DFU::ERASE_SIZE as u32
151 };
152
153 /// Create a new instance of a bootloader with the flash partitions.
154 ///
155 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
156 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
157 pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
158 Self {
159 active: config.active,
160 dfu: config.dfu,
161 state: config.state,
162 }
163 }
164
165 /// Perform necessary boot preparations like swapping images.
166 ///
167 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
168 /// algorithm to work correctly.
169 ///
170 /// The provided aligned_buf argument must satisfy any alignment requirements
171 /// given by the partition flashes. All flash operations will use this buffer.
172 ///
173 /// ## SWAPPING
174 ///
175 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
176 /// The swap index contains the copy progress, as to allow continuation of the copy process on
177 /// power failure. The index counter is represented within 1 or more pages (depending on total
178 /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`)
179 /// contains a zero value. This ensures that index updates can be performed atomically and
180 /// avoid a situation where the wrong index value is set (page write size is "atomic").
181 ///
182 ///
183 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
184 /// |-----------|------------|--------|--------|--------|--------|
185 /// | Active | 0 | 1 | 2 | 3 | - |
186 /// | DFU | 0 | 4 | 5 | 6 | X |
187 ///
188 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
189 /// as follows:
190 ///
191 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
192 /// |-----------|------------|--------|--------|--------|--------|
193 /// | Active | 1 | 1 | 2 | 6 | - |
194 /// | DFU | 1 | 4 | 5 | 6 | 3 |
195 ///
196 /// The next iteration performs the same steps
197 ///
198 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
199 /// |-----------|------------|--------|--------|--------|--------|
200 /// | Active | 2 | 1 | 5 | 6 | - |
201 /// | DFU | 2 | 4 | 5 | 2 | 3 |
202 ///
203 /// And again until we're done
204 ///
205 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
206 /// |-----------|------------|--------|--------|--------|--------|
207 /// | Active | 3 | 4 | 5 | 6 | - |
208 /// | DFU | 3 | 4 | 1 | 2 | 3 |
209 ///
210 /// ## REVERTING
211 ///
212 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
213 /// the application failed to mark the boot successful. In this case, the revert algorithm will
214 /// run.
215 ///
216 /// The revert index is located separately from the swap index, to ensure that revert can continue
217 /// on power failure.
218 ///
219 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
220 ///
221 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
222 /// |-----------|--------------|--------|--------|--------|--------|
223 /// | Active | 3 | 1 | 5 | 6 | - |
224 /// | DFU | 3 | 4 | 1 | 2 | 3 |
225 ///
226 ///
227 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
228 /// |-----------|--------------|--------|--------|--------|--------|
229 /// | Active | 3 | 1 | 2 | 6 | - |
230 /// | DFU | 3 | 4 | 5 | 2 | 3 |
231 ///
232 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
233 /// |-----------|--------------|--------|--------|--------|--------|
234 /// | Active | 3 | 1 | 2 | 3 | - |
235 /// | DFU | 3 | 4 | 5 | 6 | 3 |
236 ///
237 pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
238 // Ensure we have enough progress pages to store copy progress
239 assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
240 assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32);
241 assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32);
242 assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32);
243 assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32);
244 assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
245 assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
246 assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
247
248 // Ensure our partitions are able to handle boot operations
249 assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
250
251 // Copy contents from partition N to active
252 let state = self.read_state(aligned_buf)?;
253 if state == State::Swap {
254 //
255 // Check if we already swapped. If we're in the swap state, this means we should revert
256 // since the app has failed to mark boot as successful
257 //
258 if !self.is_swapped(aligned_buf)? {
259 trace!("Swapping");
260 self.swap(aligned_buf)?;
261 trace!("Swapping done");
262 } else {
263 trace!("Reverting");
264 self.revert(aligned_buf)?;
265
266 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
267
268 // Invalidate progress
269 state_word.fill(!STATE_ERASE_VALUE);
270 self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
271
272 // Clear magic and progress
273 self.state.erase(0, self.state.capacity() as u32)?;
274
275 // Set magic
276 state_word.fill(BOOT_MAGIC);
277 self.state.write(0, state_word)?;
278 }
279 }
280 Ok(state)
281 }
282
283 fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
284 let page_count = self.active.capacity() / Self::PAGE_SIZE as usize;
285 let progress = self.current_progress(aligned_buf)?;
286
287 Ok(progress >= page_count * 2)
288 }
289
290 fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
291 let write_size = STATE::WRITE_SIZE as u32;
292 let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2;
293 let state_word = &mut aligned_buf[..write_size as usize];
294
295 self.state.read(write_size, state_word)?;
296 if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) {
297 // Progress is invalid
298 return Ok(max_index);
299 }
300
301 for index in 0..max_index {
302 self.state.read((2 + index) as u32 * write_size, state_word)?;
303
304 if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
305 return Ok(index);
306 }
307 }
308 Ok(max_index)
309 }
310
311 fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> {
312 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
313 state_word.fill(!STATE_ERASE_VALUE);
314 self.state
315 .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?;
316 Ok(())
317 }
318
319 fn copy_page_once_to_active(
320 &mut self,
321 progress_index: usize,
322 from_offset: u32,
323 to_offset: u32,
324 aligned_buf: &mut [u8],
325 ) -> Result<(), BootError> {
326 if self.current_progress(aligned_buf)? <= progress_index {
327 let page_size = Self::PAGE_SIZE as u32;
328
329 self.active.erase(to_offset, to_offset + page_size)?;
330
331 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
332 self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?;
333 self.active.write(to_offset + offset_in_page as u32, aligned_buf)?;
334 }
335
336 self.update_progress(progress_index, aligned_buf)?;
337 }
338 Ok(())
339 }
340
341 fn copy_page_once_to_dfu(
342 &mut self,
343 progress_index: usize,
344 from_offset: u32,
345 to_offset: u32,
346 aligned_buf: &mut [u8],
347 ) -> Result<(), BootError> {
348 if self.current_progress(aligned_buf)? <= progress_index {
349 let page_size = Self::PAGE_SIZE as u32;
350
351 self.dfu.erase(to_offset as u32, to_offset + page_size)?;
352
353 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
354 self.active.read(from_offset + offset_in_page as u32, aligned_buf)?;
355 self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?;
356 }
357
358 self.update_progress(progress_index, aligned_buf)?;
359 }
360 Ok(())
361 }
362
363 fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
364 let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
365 for page_num in 0..page_count {
366 let progress_index = (page_num * 2) as usize;
367
368 // Copy active page to the 'next' DFU page.
369 let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
370 let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE;
371 //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
372 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
373
374 // Copy DFU page to the active page
375 let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
376 let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
377 //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
378 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
379 }
380
381 Ok(())
382 }
383
384 fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
385 let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
386 for page_num in 0..page_count {
387 let progress_index = (page_count * 2 + page_num * 2) as usize;
388
389 // Copy the bad active page to the DFU page
390 let active_from_offset = page_num * Self::PAGE_SIZE;
391 let dfu_to_offset = page_num * Self::PAGE_SIZE;
392 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
393
394 // Copy the DFU page back to the active page
395 let active_to_offset = page_num * Self::PAGE_SIZE;
396 let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE;
397 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
398 }
399
400 Ok(())
401 }
402
403 fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
404 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
405 self.state.read(0, state_word)?;
406
407 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
408 Ok(State::Swap)
409 } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
410 Ok(State::DfuDetach)
411 } else {
412 Ok(State::Boot)
413 }
414 }
415}
416
417fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
418 active: &ACTIVE,
419 dfu: &DFU,
420 state: &STATE,
421 page_size: u32,
422) {
423 assert_eq!(active.capacity() as u32 % page_size, 0);
424 assert_eq!(dfu.capacity() as u32 % page_size, 0);
425 // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm
426 assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size);
427 assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32);
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use crate::mem_flash::MemFlash;
434
435 #[test]
436 #[should_panic]
437 fn test_range_asserts() {
438 const ACTIVE_SIZE: usize = 4194304 - 4096;
439 const DFU_SIZE: usize = 4194304;
440 const STATE_SIZE: usize = 4096;
441 static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF);
442 static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF);
443 static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF);
444 assert_partitions(&ACTIVE, &DFU, &STATE, 4096);
445 }
446}
diff --git a/embassy-boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs
new file mode 100644
index 000000000..2e4e03da3
--- /dev/null
+++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs
@@ -0,0 +1,30 @@
1use digest::typenum::U64;
2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
3use ed25519_dalek::Digest;
4
5pub struct Sha512(ed25519_dalek::Sha512);
6
7impl Default for Sha512 {
8 fn default() -> Self {
9 Self(ed25519_dalek::Sha512::new())
10 }
11}
12
13impl Update for Sha512 {
14 fn update(&mut self, data: &[u8]) {
15 Digest::update(&mut self.0, data)
16 }
17}
18
19impl FixedOutput for Sha512 {
20 fn finalize_into(self, out: &mut digest::Output<Self>) {
21 let result = self.0.finalize();
22 out.as_mut_slice().copy_from_slice(result.as_slice())
23 }
24}
25
26impl OutputSizeUser for Sha512 {
27 type OutputSize = U64;
28}
29
30impl HashMarker for Sha512 {}
diff --git a/embassy-boot/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs
new file mode 100644
index 000000000..9b4b4b60c
--- /dev/null
+++ b/embassy-boot/src/digest_adapters/mod.rs
@@ -0,0 +1,5 @@
1#[cfg(feature = "ed25519-dalek")]
2pub(crate) mod ed25519_dalek;
3
4#[cfg(feature = "ed25519-salty")]
5pub(crate) mod salty;
diff --git a/embassy-boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs
new file mode 100644
index 000000000..2b5dcf3af
--- /dev/null
+++ b/embassy-boot/src/digest_adapters/salty.rs
@@ -0,0 +1,29 @@
1use digest::typenum::U64;
2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
3
4pub struct Sha512(salty::Sha512);
5
6impl Default for Sha512 {
7 fn default() -> Self {
8 Self(salty::Sha512::new())
9 }
10}
11
12impl Update for Sha512 {
13 fn update(&mut self, data: &[u8]) {
14 self.0.update(data)
15 }
16}
17
18impl FixedOutput for Sha512 {
19 fn finalize_into(self, out: &mut digest::Output<Self>) {
20 let result = self.0.finalize();
21 out.as_mut_slice().copy_from_slice(result.as_slice())
22 }
23}
24
25impl OutputSizeUser for Sha512 {
26 type OutputSize = U64;
27}
28
29impl HashMarker for Sha512 {}
diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
new file mode 100644
index 000000000..26f65f295
--- /dev/null
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -0,0 +1,462 @@
1use digest::Digest;
2#[cfg(target_os = "none")]
3use embassy_embedded_hal::flash::partition::Partition;
4#[cfg(target_os = "none")]
5use embassy_sync::blocking_mutex::raw::NoopRawMutex;
6use embedded_storage_async::nor_flash::NorFlash;
7
8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
10
11/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
12/// 'mess up' the internal bootloader state
13pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU,
15 state: FirmwareState<'d, STATE>,
16 last_erased_dfu_sector_index: Option<usize>,
17}
18
19#[cfg(target_os = "none")]
20impl<'a, DFU: NorFlash, STATE: NorFlash>
21 FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, DFU>, Partition<'a, NoopRawMutex, STATE>>
22{
23 /// Create a firmware updater config from the flash and address symbols defined in the linkerfile
24 pub fn from_linkerfile(
25 dfu_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, DFU>,
26 state_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, STATE>,
27 ) -> Self {
28 extern "C" {
29 static __bootloader_state_start: u32;
30 static __bootloader_state_end: u32;
31 static __bootloader_dfu_start: u32;
32 static __bootloader_dfu_end: u32;
33 }
34
35 let dfu = unsafe {
36 let start = &__bootloader_dfu_start as *const u32 as u32;
37 let end = &__bootloader_dfu_end as *const u32 as u32;
38 trace!("DFU: 0x{:x} - 0x{:x}", start, end);
39
40 Partition::new(dfu_flash, start, end - start)
41 };
42 let state = unsafe {
43 let start = &__bootloader_state_start as *const u32 as u32;
44 let end = &__bootloader_state_end as *const u32 as u32;
45 trace!("STATE: 0x{:x} - 0x{:x}", start, end);
46
47 Partition::new(state_flash, start, end - start)
48 };
49
50 Self { dfu, state }
51 }
52}
53
54impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
55 /// Create a firmware updater instance with partition ranges for the update and state partitions.
56 pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
57 Self {
58 dfu: config.dfu,
59 state: FirmwareState::new(config.state, aligned),
60 last_erased_dfu_sector_index: None,
61 }
62 }
63
64 /// Obtain the current state.
65 ///
66 /// This is useful to check if the bootloader has just done a swap, in order
67 /// to do verifications and self-tests of the new image before calling
68 /// `mark_booted`.
69 pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
70 self.state.get_state().await
71 }
72
73 /// Verify the DFU given a public key. If there is an error then DO NOT
74 /// proceed with updating the firmware as it must be signed with a
75 /// corresponding private key (otherwise it could be malicious firmware).
76 ///
77 /// Mark to trigger firmware swap on next boot if verify succeeds.
78 ///
79 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
80 /// been generated from a SHA-512 digest of the firmware bytes.
81 ///
82 /// If no signature feature is set then this method will always return a
83 /// signature error.
84 #[cfg(feature = "_verify")]
85 pub async fn verify_and_mark_updated(
86 &mut self,
87 _public_key: &[u8; 32],
88 _signature: &[u8; 64],
89 _update_len: u32,
90 ) -> Result<(), FirmwareUpdaterError> {
91 assert!(_update_len <= self.dfu.capacity() as u32);
92
93 self.state.verify_booted().await?;
94
95 #[cfg(feature = "ed25519-dalek")]
96 {
97 use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey};
98
99 use crate::digest_adapters::ed25519_dalek::Sha512;
100
101 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
102
103 let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?;
104 let signature = Signature::from_bytes(_signature);
105
106 let mut chunk_buf = [0; 2];
107 let mut message = [0; 64];
108 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
109
110 public_key.verify(&message, &signature).map_err(into_signature_error)?
111 }
112 #[cfg(feature = "ed25519-salty")]
113 {
114 use salty::{PublicKey, Signature};
115
116 use crate::digest_adapters::salty::Sha512;
117
118 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
119 FirmwareUpdaterError::Signature(signature::Error::default())
120 }
121
122 let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?;
123 let signature = Signature::try_from(_signature).map_err(into_signature_error)?;
124
125 let mut message = [0; 64];
126 let mut chunk_buf = [0; 2];
127 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
128
129 let r = public_key.verify(&message, &signature);
130 trace!(
131 "Verifying with public key {}, signature {} and message {} yields ok: {}",
132 public_key.to_bytes(),
133 signature.to_bytes(),
134 message,
135 r.is_ok()
136 );
137 r.map_err(into_signature_error)?
138 }
139
140 self.state.mark_updated().await
141 }
142
143 /// Verify the update in DFU with any digest.
144 pub async fn hash<D: Digest>(
145 &mut self,
146 update_len: u32,
147 chunk_buf: &mut [u8],
148 output: &mut [u8],
149 ) -> Result<(), FirmwareUpdaterError> {
150 let mut digest = D::new();
151 for offset in (0..update_len).step_by(chunk_buf.len()) {
152 self.dfu.read(offset, chunk_buf).await?;
153 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
154 digest.update(&chunk_buf[..len]);
155 }
156 output.copy_from_slice(digest.finalize().as_slice());
157 Ok(())
158 }
159
160 /// Mark to trigger firmware swap on next boot.
161 #[cfg(not(feature = "_verify"))]
162 pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
163 self.state.mark_updated().await
164 }
165
166 /// Mark to trigger USB DFU on next boot.
167 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
168 self.state.verify_booted().await?;
169 self.state.mark_dfu().await
170 }
171
172 /// Mark firmware boot successful and stop rollback on reset.
173 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
174 self.state.mark_booted().await
175 }
176
177 /// Writes firmware data to the device.
178 ///
179 /// This function writes the given data to the firmware area starting at the specified offset.
180 /// It handles sector erasures and data writes while verifying the device is in a proper state
181 /// for firmware updates. The function ensures that only unerased sectors are erased before
182 /// writing and efficiently handles the writing process across sector boundaries and in
183 /// various configurations (data size, sector size, etc.).
184 ///
185 /// # Arguments
186 ///
187 /// * `offset` - The starting offset within the firmware area where data writing should begin.
188 /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
189 /// multiple of NorFlash WRITE_SIZE.
190 ///
191 /// # Returns
192 ///
193 /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
194 ///
195 /// # Errors
196 ///
197 /// This function will return an error if:
198 ///
199 /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
200 /// - There is a failure erasing a sector before writing.
201 /// - There is a failure writing data to the device.
202 pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
203 // Make sure we are running a booted firmware to avoid reverting to a bad state.
204 self.state.verify_booted().await?;
205
206 // Initialize variables to keep track of the remaining data and the current offset.
207 let mut remaining_data = data;
208 let mut offset = offset;
209
210 // Continue writing as long as there is data left to write.
211 while !remaining_data.is_empty() {
212 // Compute the current sector and its boundaries.
213 let current_sector = offset / DFU::ERASE_SIZE;
214 let sector_start = current_sector * DFU::ERASE_SIZE;
215 let sector_end = sector_start + DFU::ERASE_SIZE;
216 // Determine if the current sector needs to be erased before writing.
217 let need_erase = self
218 .last_erased_dfu_sector_index
219 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
220
221 // If the sector needs to be erased, erase it and update the last erased sector index.
222 if need_erase {
223 self.dfu.erase(sector_start as u32, sector_end as u32).await?;
224 self.last_erased_dfu_sector_index = Some(current_sector);
225 }
226
227 // Calculate the size of the data chunk that can be written in the current iteration.
228 let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
229 // Split the data to get the current chunk to be written and the remaining data.
230 let (data_chunk, rest) = remaining_data.split_at(write_size);
231
232 // Write the current data chunk.
233 self.dfu.write(offset as u32, data_chunk).await?;
234
235 // Update the offset and remaining data for the next iteration.
236 remaining_data = rest;
237 offset += write_size;
238 }
239
240 Ok(())
241 }
242
243 /// Prepare for an incoming DFU update by erasing the entire DFU area and
244 /// returning its `Partition`.
245 ///
246 /// Using this instead of `write_firmware` allows for an optimized API in
247 /// exchange for added complexity.
248 pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
249 self.state.verify_booted().await?;
250 self.dfu.erase(0, self.dfu.capacity() as u32).await?;
251
252 Ok(&mut self.dfu)
253 }
254}
255
256/// Manages the state partition of the firmware update.
257///
258/// Can be used standalone for more fine grained control, or as part of the updater.
259pub struct FirmwareState<'d, STATE> {
260 state: STATE,
261 aligned: &'d mut [u8],
262}
263
264impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
265 /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition.
266 ///
267 /// # Safety
268 ///
269 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
270 /// and written to.
271 pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
272 Self::new(config.state, aligned)
273 }
274
275 /// Create a firmware state instance with a buffer for magic content and state partition.
276 ///
277 /// # Safety
278 ///
279 /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE,
280 /// and follow the alignment rules for the flash being read from and written to.
281 pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
282 assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE));
283 Self { state, aligned }
284 }
285
286 // Make sure we are running a booted firmware to avoid reverting to a bad state.
287 async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
288 if self.get_state().await? == State::Boot {
289 Ok(())
290 } else {
291 Err(FirmwareUpdaterError::BadState)
292 }
293 }
294
295 /// Obtain the current state.
296 ///
297 /// This is useful to check if the bootloader has just done a swap, in order
298 /// to do verifications and self-tests of the new image before calling
299 /// `mark_booted`.
300 pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
301 self.state.read(0, &mut self.aligned).await?;
302
303 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
304 Ok(State::Swap)
305 } else {
306 Ok(State::Boot)
307 }
308 }
309
310 /// Mark to trigger firmware swap on next boot.
311 pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
312 self.set_magic(SWAP_MAGIC).await
313 }
314
315 /// Mark to trigger USB DFU on next boot.
316 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
317 self.set_magic(DFU_DETACH_MAGIC).await
318 }
319
320 /// Mark firmware boot successful and stop rollback on reset.
321 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
322 self.set_magic(BOOT_MAGIC).await
323 }
324
325 async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
326 self.state.read(0, &mut self.aligned).await?;
327
328 if self.aligned[..STATE::WRITE_SIZE].iter().any(|&b| b != magic) {
329 // Read progress validity
330 if STATE::READ_SIZE <= 2 * STATE::WRITE_SIZE {
331 self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?;
332 } else {
333 self.aligned.rotate_left(STATE::WRITE_SIZE);
334 }
335
336 if self.aligned[..STATE::WRITE_SIZE]
337 .iter()
338 .any(|&b| b != STATE_ERASE_VALUE)
339 {
340 // The current progress validity marker is invalid
341 } else {
342 // Invalidate progress
343 self.aligned.fill(!STATE_ERASE_VALUE);
344 self.state
345 .write(STATE::WRITE_SIZE as u32, &self.aligned[..STATE::WRITE_SIZE])
346 .await?;
347 }
348
349 // Clear magic and progress
350 self.state.erase(0, self.state.capacity() as u32).await?;
351
352 // Set magic
353 self.aligned.fill(magic);
354 self.state.write(0, &self.aligned[..STATE::WRITE_SIZE]).await?;
355 }
356 Ok(())
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use embassy_embedded_hal::flash::partition::Partition;
363 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
364 use embassy_sync::mutex::Mutex;
365 use futures::executor::block_on;
366 use sha1::{Digest, Sha1};
367
368 use super::*;
369 use crate::mem_flash::MemFlash;
370
371 #[test]
372 fn can_verify_sha1() {
373 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
374 let state = Partition::new(&flash, 0, 4096);
375 let dfu = Partition::new(&flash, 65536, 65536);
376 let mut aligned = [0; 8];
377
378 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
379 let mut to_write = [0; 4096];
380 to_write[..7].copy_from_slice(update.as_slice());
381
382 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
383 block_on(updater.write_firmware(0, to_write.as_slice())).unwrap();
384 let mut chunk_buf = [0; 2];
385 let mut hash = [0; 20];
386 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
387
388 assert_eq!(Sha1::digest(update).as_slice(), hash);
389 }
390
391 #[test]
392 fn can_verify_sha1_sector_bigger_than_chunk() {
393 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
394 let state = Partition::new(&flash, 0, 4096);
395 let dfu = Partition::new(&flash, 65536, 65536);
396 let mut aligned = [0; 8];
397
398 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
399 let mut to_write = [0; 4096];
400 to_write[..7].copy_from_slice(update.as_slice());
401
402 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
403 let mut offset = 0;
404 for chunk in to_write.chunks(1024) {
405 block_on(updater.write_firmware(offset, chunk)).unwrap();
406 offset += chunk.len();
407 }
408 let mut chunk_buf = [0; 2];
409 let mut hash = [0; 20];
410 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
411
412 assert_eq!(Sha1::digest(update).as_slice(), hash);
413 }
414
415 #[test]
416 fn can_verify_sha1_sector_smaller_than_chunk() {
417 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
418 let state = Partition::new(&flash, 0, 4096);
419 let dfu = Partition::new(&flash, 65536, 65536);
420 let mut aligned = [0; 8];
421
422 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
423 let mut to_write = [0; 4096];
424 to_write[..7].copy_from_slice(update.as_slice());
425
426 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
427 let mut offset = 0;
428 for chunk in to_write.chunks(2048) {
429 block_on(updater.write_firmware(offset, chunk)).unwrap();
430 offset += chunk.len();
431 }
432 let mut chunk_buf = [0; 2];
433 let mut hash = [0; 20];
434 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
435
436 assert_eq!(Sha1::digest(update).as_slice(), hash);
437 }
438
439 #[test]
440 fn can_verify_sha1_cross_sector_boundary() {
441 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
442 let state = Partition::new(&flash, 0, 4096);
443 let dfu = Partition::new(&flash, 65536, 65536);
444 let mut aligned = [0; 8];
445
446 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
447 let mut to_write = [0; 4096];
448 to_write[..7].copy_from_slice(update.as_slice());
449
450 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
451 let mut offset = 0;
452 for chunk in to_write.chunks(896) {
453 block_on(updater.write_firmware(offset, chunk)).unwrap();
454 offset += chunk.len();
455 }
456 let mut chunk_buf = [0; 2];
457 let mut hash = [0; 20];
458 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
459
460 assert_eq!(Sha1::digest(update).as_slice(), hash);
461 }
462}
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
new file mode 100644
index 000000000..35772a856
--- /dev/null
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -0,0 +1,499 @@
1use digest::Digest;
2#[cfg(target_os = "none")]
3use embassy_embedded_hal::flash::partition::BlockingPartition;
4#[cfg(target_os = "none")]
5use embassy_sync::blocking_mutex::raw::NoopRawMutex;
6use embedded_storage::nor_flash::NorFlash;
7
8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
10
11/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
12/// 'mess up' the internal bootloader state
13pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU,
15 state: BlockingFirmwareState<'d, STATE>,
16 last_erased_dfu_sector_index: Option<usize>,
17}
18
19#[cfg(target_os = "none")]
20impl<'a, DFU: NorFlash, STATE: NorFlash>
21 FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, DFU>, BlockingPartition<'a, NoopRawMutex, STATE>>
22{
23 /// Constructs a `FirmwareUpdaterConfig` instance from flash memory and address symbols defined in the linker file.
24 ///
25 /// This method initializes `BlockingPartition` instances for the DFU (Device Firmware Update), and state
26 /// partitions, leveraging start and end addresses specified by the linker. These partitions are critical
27 /// for managing firmware updates, application state, and boot operations within the bootloader.
28 ///
29 /// # Parameters
30 /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface.
31 /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface.
32 ///
33 /// # Safety
34 /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses
35 /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined
36 /// in the memory.x file to prevent undefined behavior.
37 ///
38 /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory
39 /// interfaces provided are compatible with these regions.
40 ///
41 /// # Returns
42 /// A `FirmwareUpdaterConfig` instance with `BlockingPartition` instances for the DFU, and state partitions.
43 ///
44 /// # Example
45 /// ```ignore
46 /// // Assume `dfu_flash`, and `state_flash` share the same flash memory interface.
47 /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
48 /// let flash = Mutex::new(RefCell::new(layout.bank1_region));
49 ///
50 /// let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
51 /// // `config` can now be used to create a `FirmwareUpdater` instance for managing boot operations.
52 /// ```
53 /// Working examples can be found in the bootloader examples folder.
54 pub fn from_linkerfile_blocking(
55 dfu_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<DFU>>,
56 state_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<STATE>>,
57 ) -> Self {
58 extern "C" {
59 static __bootloader_state_start: u32;
60 static __bootloader_state_end: u32;
61 static __bootloader_dfu_start: u32;
62 static __bootloader_dfu_end: u32;
63 }
64
65 let dfu = unsafe {
66 let start = &__bootloader_dfu_start as *const u32 as u32;
67 let end = &__bootloader_dfu_end as *const u32 as u32;
68 trace!("DFU: 0x{:x} - 0x{:x}", start, end);
69
70 BlockingPartition::new(dfu_flash, start, end - start)
71 };
72 let state = unsafe {
73 let start = &__bootloader_state_start as *const u32 as u32;
74 let end = &__bootloader_state_end as *const u32 as u32;
75 trace!("STATE: 0x{:x} - 0x{:x}", start, end);
76
77 BlockingPartition::new(state_flash, start, end - start)
78 };
79
80 Self { dfu, state }
81 }
82}
83
84impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> {
85 /// Create a firmware updater instance with partition ranges for the update and state partitions.
86 ///
87 /// # Safety
88 ///
89 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
90 /// and written to.
91 pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
92 Self {
93 dfu: config.dfu,
94 state: BlockingFirmwareState::new(config.state, aligned),
95 last_erased_dfu_sector_index: None,
96 }
97 }
98
99 /// Obtain the current state.
100 ///
101 /// This is useful to check if the bootloader has just done a swap, in order
102 /// to do verifications and self-tests of the new image before calling
103 /// `mark_booted`.
104 pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
105 self.state.get_state()
106 }
107
108 /// Verify the DFU given a public key. If there is an error then DO NOT
109 /// proceed with updating the firmware as it must be signed with a
110 /// corresponding private key (otherwise it could be malicious firmware).
111 ///
112 /// Mark to trigger firmware swap on next boot if verify succeeds.
113 ///
114 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
115 /// been generated from a SHA-512 digest of the firmware bytes.
116 ///
117 /// If no signature feature is set then this method will always return a
118 /// signature error.
119 #[cfg(feature = "_verify")]
120 pub fn verify_and_mark_updated(
121 &mut self,
122 _public_key: &[u8; 32],
123 _signature: &[u8; 64],
124 _update_len: u32,
125 ) -> Result<(), FirmwareUpdaterError> {
126 assert!(_update_len <= self.dfu.capacity() as u32);
127
128 self.state.verify_booted()?;
129
130 #[cfg(feature = "ed25519-dalek")]
131 {
132 use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey};
133
134 use crate::digest_adapters::ed25519_dalek::Sha512;
135
136 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
137
138 let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?;
139 let signature = Signature::from_bytes(_signature);
140
141 let mut message = [0; 64];
142 let mut chunk_buf = [0; 2];
143 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
144
145 public_key.verify(&message, &signature).map_err(into_signature_error)?
146 }
147 #[cfg(feature = "ed25519-salty")]
148 {
149 use salty::{PublicKey, Signature};
150
151 use crate::digest_adapters::salty::Sha512;
152
153 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
154 FirmwareUpdaterError::Signature(signature::Error::default())
155 }
156
157 let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?;
158 let signature = Signature::try_from(_signature).map_err(into_signature_error)?;
159
160 let mut message = [0; 64];
161 let mut chunk_buf = [0; 2];
162 self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
163
164 let r = public_key.verify(&message, &signature);
165 trace!(
166 "Verifying with public key {}, signature {} and message {} yields ok: {}",
167 public_key.to_bytes(),
168 signature.to_bytes(),
169 message,
170 r.is_ok()
171 );
172 r.map_err(into_signature_error)?
173 }
174
175 self.state.mark_updated()
176 }
177
178 /// Verify the update in DFU with any digest.
179 pub fn hash<D: Digest>(
180 &mut self,
181 update_len: u32,
182 chunk_buf: &mut [u8],
183 output: &mut [u8],
184 ) -> Result<(), FirmwareUpdaterError> {
185 let mut digest = D::new();
186 for offset in (0..update_len).step_by(chunk_buf.len()) {
187 self.dfu.read(offset, chunk_buf)?;
188 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
189 digest.update(&chunk_buf[..len]);
190 }
191 output.copy_from_slice(digest.finalize().as_slice());
192 Ok(())
193 }
194
195 /// Mark to trigger firmware swap on next boot.
196 #[cfg(not(feature = "_verify"))]
197 pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
198 self.state.mark_updated()
199 }
200
201 /// Mark to trigger USB DFU device on next boot.
202 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
203 self.state.verify_booted()?;
204 self.state.mark_dfu()
205 }
206
207 /// Mark firmware boot successful and stop rollback on reset.
208 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
209 self.state.mark_booted()
210 }
211
212 /// Writes firmware data to the device.
213 ///
214 /// This function writes the given data to the firmware area starting at the specified offset.
215 /// It handles sector erasures and data writes while verifying the device is in a proper state
216 /// for firmware updates. The function ensures that only unerased sectors are erased before
217 /// writing and efficiently handles the writing process across sector boundaries and in
218 /// various configurations (data size, sector size, etc.).
219 ///
220 /// # Arguments
221 ///
222 /// * `offset` - The starting offset within the firmware area where data writing should begin.
223 /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
224 /// multiple of NorFlash WRITE_SIZE.
225 ///
226 /// # Returns
227 ///
228 /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
229 ///
230 /// # Errors
231 ///
232 /// This function will return an error if:
233 ///
234 /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
235 /// - There is a failure erasing a sector before writing.
236 /// - There is a failure writing data to the device.
237 pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
238 // Make sure we are running a booted firmware to avoid reverting to a bad state.
239 self.state.verify_booted()?;
240
241 // Initialize variables to keep track of the remaining data and the current offset.
242 let mut remaining_data = data;
243 let mut offset = offset;
244
245 // Continue writing as long as there is data left to write.
246 while !remaining_data.is_empty() {
247 // Compute the current sector and its boundaries.
248 let current_sector = offset / DFU::ERASE_SIZE;
249 let sector_start = current_sector * DFU::ERASE_SIZE;
250 let sector_end = sector_start + DFU::ERASE_SIZE;
251 // Determine if the current sector needs to be erased before writing.
252 let need_erase = self
253 .last_erased_dfu_sector_index
254 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
255
256 // If the sector needs to be erased, erase it and update the last erased sector index.
257 if need_erase {
258 self.dfu.erase(sector_start as u32, sector_end as u32)?;
259 self.last_erased_dfu_sector_index = Some(current_sector);
260 }
261
262 // Calculate the size of the data chunk that can be written in the current iteration.
263 let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
264 // Split the data to get the current chunk to be written and the remaining data.
265 let (data_chunk, rest) = remaining_data.split_at(write_size);
266
267 // Write the current data chunk.
268 self.dfu.write(offset as u32, data_chunk)?;
269
270 // Update the offset and remaining data for the next iteration.
271 remaining_data = rest;
272 offset += write_size;
273 }
274
275 Ok(())
276 }
277
278 /// Prepare for an incoming DFU update by erasing the entire DFU area and
279 /// returning its `Partition`.
280 ///
281 /// Using this instead of `write_firmware` allows for an optimized API in
282 /// exchange for added complexity.
283 pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> {
284 self.state.verify_booted()?;
285 self.dfu.erase(0, self.dfu.capacity() as u32)?;
286
287 Ok(&mut self.dfu)
288 }
289}
290
291/// Manages the state partition of the firmware update.
292///
293/// Can be used standalone for more fine grained control, or as part of the updater.
294pub struct BlockingFirmwareState<'d, STATE> {
295 state: STATE,
296 aligned: &'d mut [u8],
297}
298
299impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
300 /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition.
301 ///
302 /// # Safety
303 ///
304 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
305 /// and written to.
306 pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
307 Self::new(config.state, aligned)
308 }
309
310 /// Create a firmware state instance with a buffer for magic content and state partition.
311 ///
312 /// # Safety
313 ///
314 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
315 /// and written to.
316 pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
317 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
318 Self { state, aligned }
319 }
320
321 // Make sure we are running a booted firmware to avoid reverting to a bad state.
322 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
323 if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
324 Ok(())
325 } else {
326 Err(FirmwareUpdaterError::BadState)
327 }
328 }
329
330 /// Obtain the current state.
331 ///
332 /// This is useful to check if the bootloader has just done a swap, in order
333 /// to do verifications and self-tests of the new image before calling
334 /// `mark_booted`.
335 pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
336 self.state.read(0, &mut self.aligned)?;
337
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 }
346
347 /// Mark to trigger firmware swap on next boot.
348 pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> {
349 self.set_magic(SWAP_MAGIC)
350 }
351
352 /// Mark to trigger USB DFU on next boot.
353 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
354 self.set_magic(DFU_DETACH_MAGIC)
355 }
356
357 /// Mark firmware boot successful and stop rollback on reset.
358 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
359 self.set_magic(BOOT_MAGIC)
360 }
361
362 fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
363 self.state.read(0, &mut self.aligned)?;
364
365 if self.aligned.iter().any(|&b| b != magic) {
366 // Read progress validity
367 self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?;
368
369 if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
370 // The current progress validity marker is invalid
371 } else {
372 // Invalidate progress
373 self.aligned.fill(!STATE_ERASE_VALUE);
374 self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?;
375 }
376
377 // Clear magic and progress
378 self.state.erase(0, self.state.capacity() as u32)?;
379
380 // Set magic
381 self.aligned.fill(magic);
382 self.state.write(0, &self.aligned)?;
383 }
384 Ok(())
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use core::cell::RefCell;
391
392 use embassy_embedded_hal::flash::partition::BlockingPartition;
393 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
394 use embassy_sync::blocking_mutex::Mutex;
395 use sha1::{Digest, Sha1};
396
397 use super::*;
398 use crate::mem_flash::MemFlash;
399
400 #[test]
401 fn can_verify_sha1() {
402 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
403 let state = BlockingPartition::new(&flash, 0, 4096);
404 let dfu = BlockingPartition::new(&flash, 65536, 65536);
405 let mut aligned = [0; 8];
406
407 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
408 let mut to_write = [0; 4096];
409 to_write[..7].copy_from_slice(update.as_slice());
410
411 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
412 updater.write_firmware(0, to_write.as_slice()).unwrap();
413 let mut chunk_buf = [0; 2];
414 let mut hash = [0; 20];
415 updater
416 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
417 .unwrap();
418
419 assert_eq!(Sha1::digest(update).as_slice(), hash);
420 }
421
422 #[test]
423 fn can_verify_sha1_sector_bigger_than_chunk() {
424 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
425 let state = BlockingPartition::new(&flash, 0, 4096);
426 let dfu = BlockingPartition::new(&flash, 65536, 65536);
427 let mut aligned = [0; 8];
428
429 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
430 let mut to_write = [0; 4096];
431 to_write[..7].copy_from_slice(update.as_slice());
432
433 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
434 let mut offset = 0;
435 for chunk in to_write.chunks(1024) {
436 updater.write_firmware(offset, chunk).unwrap();
437 offset += chunk.len();
438 }
439 let mut chunk_buf = [0; 2];
440 let mut hash = [0; 20];
441 updater
442 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
443 .unwrap();
444
445 assert_eq!(Sha1::digest(update).as_slice(), hash);
446 }
447
448 #[test]
449 fn can_verify_sha1_sector_smaller_than_chunk() {
450 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
451 let state = BlockingPartition::new(&flash, 0, 4096);
452 let dfu = BlockingPartition::new(&flash, 65536, 65536);
453 let mut aligned = [0; 8];
454
455 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
456 let mut to_write = [0; 4096];
457 to_write[..7].copy_from_slice(update.as_slice());
458
459 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
460 let mut offset = 0;
461 for chunk in to_write.chunks(2048) {
462 updater.write_firmware(offset, chunk).unwrap();
463 offset += chunk.len();
464 }
465 let mut chunk_buf = [0; 2];
466 let mut hash = [0; 20];
467 updater
468 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
469 .unwrap();
470
471 assert_eq!(Sha1::digest(update).as_slice(), hash);
472 }
473
474 #[test]
475 fn can_verify_sha1_cross_sector_boundary() {
476 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
477 let state = BlockingPartition::new(&flash, 0, 4096);
478 let dfu = BlockingPartition::new(&flash, 65536, 65536);
479 let mut aligned = [0; 8];
480
481 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
482 let mut to_write = [0; 4096];
483 to_write[..7].copy_from_slice(update.as_slice());
484
485 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
486 let mut offset = 0;
487 for chunk in to_write.chunks(896) {
488 updater.write_firmware(offset, chunk).unwrap();
489 offset += chunk.len();
490 }
491 let mut chunk_buf = [0; 2];
492 let mut hash = [0; 20];
493 updater
494 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
495 .unwrap();
496
497 assert_eq!(Sha1::digest(update).as_slice(), hash);
498 }
499}
diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs
new file mode 100644
index 000000000..4c4f4f10b
--- /dev/null
+++ b/embassy-boot/src/firmware_updater/mod.rs
@@ -0,0 +1,49 @@
1mod asynch;
2mod blocking;
3
4pub use asynch::{FirmwareState, FirmwareUpdater};
5pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater};
6use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
7
8/// Firmware updater flash configuration holding the two flashes used by the updater
9///
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
12/// the provided flash according to symbols defined in the linkerfile.
13pub struct FirmwareUpdaterConfig<DFU, STATE> {
14 /// The dfu flash partition
15 pub dfu: DFU,
16 /// The state flash partition
17 pub state: STATE,
18}
19
20/// Errors returned by FirmwareUpdater
21#[derive(Debug)]
22pub enum FirmwareUpdaterError {
23 /// Error from flash.
24 Flash(NorFlashErrorKind),
25 /// Signature errors.
26 Signature(signature::Error),
27 /// Bad state.
28 BadState,
29}
30
31#[cfg(feature = "defmt")]
32impl defmt::Format for FirmwareUpdaterError {
33 fn format(&self, fmt: defmt::Formatter) {
34 match self {
35 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
36 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
37 FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"),
38 }
39 }
40}
41
42impl<E> From<E> for FirmwareUpdaterError
43where
44 E: NorFlashError,
45{
46 fn from(error: E) -> Self {
47 FirmwareUpdaterError::Flash(error.kind())
48 }
49}
diff --git a/embassy-boot/src/fmt.rs b/embassy-boot/src/fmt.rs
new file mode 100644
index 000000000..2ac42c557
--- /dev/null
+++ b/embassy-boot/src/fmt.rs
@@ -0,0 +1,257 @@
1#![macro_use]
2#![allow(unused)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*)
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232pub(crate) struct Bytes<'a>(pub &'a [u8]);
233
234impl<'a> Debug for Bytes<'a> {
235 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
236 write!(f, "{:#02x?}", self.0)
237 }
238}
239
240impl<'a> Display for Bytes<'a> {
241 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
242 write!(f, "{:#02x?}", self.0)
243 }
244}
245
246impl<'a> LowerHex for Bytes<'a> {
247 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248 write!(f, "{:#02x?}", self.0)
249 }
250}
251
252#[cfg(feature = "defmt")]
253impl<'a> defmt::Format for Bytes<'a> {
254 fn format(&self, fmt: defmt::Formatter) {
255 defmt::write!(fmt, "{:02x}", self.0)
256 }
257}
diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs
new file mode 100644
index 000000000..b4f03e01e
--- /dev/null
+++ b/embassy-boot/src/lib.rs
@@ -0,0 +1,323 @@
1#![no_std]
2#![allow(async_fn_in_trait)]
3#![warn(missing_docs)]
4#![doc = include_str!("../README.md")]
5mod fmt;
6
7mod boot_loader;
8mod digest_adapters;
9mod firmware_updater;
10#[cfg(test)]
11mod mem_flash;
12#[cfg(test)]
13mod test_flash;
14
15// The expected value of the flash after an erase
16// TODO: Use the value provided by NorFlash when available
17pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
18pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
19pub use firmware_updater::{
20 BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
21 FirmwareUpdaterError,
22};
23
24pub(crate) const BOOT_MAGIC: u8 = 0xD0;
25pub(crate) const SWAP_MAGIC: u8 = 0xF0;
26pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
27
28/// The state of the bootloader after running prepare.
29#[derive(PartialEq, Eq, Debug)]
30#[cfg_attr(feature = "defmt", derive(defmt::Format))]
31pub enum State {
32 /// Bootloader is ready to boot the active partition.
33 Boot,
34 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
35 Swap,
36 /// Application has received a request to reboot into DFU mode to apply an update.
37 DfuDetach,
38}
39
40/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
41#[repr(align(32))]
42pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
43
44impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> {
45 fn as_ref(&self) -> &[u8] {
46 &self.0
47 }
48}
49
50impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
51 fn as_mut(&mut self) -> &mut [u8] {
52 &mut self.0
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 #![allow(unused_imports)]
59
60 use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
61 use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
62 use futures::executor::block_on;
63
64 use super::*;
65 use crate::boot_loader::BootLoaderConfig;
66 use crate::firmware_updater::FirmwareUpdaterConfig;
67 use crate::mem_flash::MemFlash;
68 use crate::test_flash::{AsyncTestFlash, BlockingTestFlash};
69
70 /*
71 #[test]
72 fn test_bad_magic() {
73 let mut flash = MemFlash([0xff; 131072]);
74 let mut flash = SingleFlashConfig::new(&mut flash);
75
76 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
77
78 assert_eq!(
79 bootloader.prepare_boot(&mut flash),
80 Err(BootError::BadMagic)
81 );
82 }
83 */
84
85 #[test]
86 fn test_boot_state() {
87 let flash = BlockingTestFlash::new(BootLoaderConfig {
88 active: MemFlash::<57344, 4096, 4>::default(),
89 dfu: MemFlash::<61440, 4096, 4>::default(),
90 state: MemFlash::<4096, 4096, 4>::default(),
91 });
92
93 flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
94
95 let mut bootloader = BootLoader::new(BootLoaderConfig {
96 active: flash.active(),
97 dfu: flash.dfu(),
98 state: flash.state(),
99 });
100
101 let mut page = [0; 4096];
102 assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
103 }
104
105 #[test]
106 #[cfg(not(feature = "_verify"))]
107 fn test_swap_state() {
108 const FIRMWARE_SIZE: usize = 57344;
109 let flash = AsyncTestFlash::new(BootLoaderConfig {
110 active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(),
111 dfu: MemFlash::<61440, 4096, 4>::default(),
112 state: MemFlash::<4096, 4096, 4>::default(),
113 });
114
115 const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
116 const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
117 let mut aligned = [0; 4];
118
119 block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
120 block_on(flash.active().write(0, &ORIGINAL)).unwrap();
121
122 let mut updater = FirmwareUpdater::new(
123 FirmwareUpdaterConfig {
124 dfu: flash.dfu(),
125 state: flash.state(),
126 },
127 &mut aligned,
128 );
129 block_on(updater.write_firmware(0, &UPDATE)).unwrap();
130 block_on(updater.mark_updated()).unwrap();
131
132 // Writing after marking updated is not allowed until marked as booted.
133 let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE));
134 assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
135
136 let flash = flash.into_blocking();
137 let mut bootloader = BootLoader::new(BootLoaderConfig {
138 active: flash.active(),
139 dfu: flash.dfu(),
140 state: flash.state(),
141 });
142
143 let mut page = [0; 1024];
144 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
145
146 let mut read_buf = [0; FIRMWARE_SIZE];
147 flash.active().read(0, &mut read_buf).unwrap();
148 assert_eq!(UPDATE, read_buf);
149 // First DFU page is untouched
150 flash.dfu().read(4096, &mut read_buf).unwrap();
151 assert_eq!(ORIGINAL, read_buf);
152
153 // Running again should cause a revert
154 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
155
156 let mut read_buf = [0; FIRMWARE_SIZE];
157 flash.active().read(0, &mut read_buf).unwrap();
158 assert_eq!(ORIGINAL, read_buf);
159 // Last DFU page is untouched
160 flash.dfu().read(0, &mut read_buf).unwrap();
161 assert_eq!(UPDATE, read_buf);
162
163 // Mark as booted
164 let flash = flash.into_async();
165 let mut updater = FirmwareUpdater::new(
166 FirmwareUpdaterConfig {
167 dfu: flash.dfu(),
168 state: flash.state(),
169 },
170 &mut aligned,
171 );
172 block_on(updater.mark_booted()).unwrap();
173
174 let flash = flash.into_blocking();
175 let mut bootloader = BootLoader::new(BootLoaderConfig {
176 active: flash.active(),
177 dfu: flash.dfu(),
178 state: flash.state(),
179 });
180 assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
181 }
182
183 #[test]
184 #[cfg(not(feature = "_verify"))]
185 fn test_swap_state_active_page_biggest() {
186 const FIRMWARE_SIZE: usize = 12288;
187 let flash = AsyncTestFlash::new(BootLoaderConfig {
188 active: MemFlash::<12288, 4096, 8>::random(),
189 dfu: MemFlash::<16384, 2048, 8>::random(),
190 state: MemFlash::<2048, 128, 4>::random(),
191 });
192
193 const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
194 const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
195 let mut aligned = [0; 4];
196
197 block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
198 block_on(flash.active().write(0, &ORIGINAL)).unwrap();
199
200 let mut updater = FirmwareUpdater::new(
201 FirmwareUpdaterConfig {
202 dfu: flash.dfu(),
203 state: flash.state(),
204 },
205 &mut aligned,
206 );
207 block_on(updater.write_firmware(0, &UPDATE)).unwrap();
208 block_on(updater.mark_updated()).unwrap();
209
210 let flash = flash.into_blocking();
211 let mut bootloader = BootLoader::new(BootLoaderConfig {
212 active: flash.active(),
213 dfu: flash.dfu(),
214 state: flash.state(),
215 });
216
217 let mut page = [0; 4096];
218 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
219
220 let mut read_buf = [0; FIRMWARE_SIZE];
221 flash.active().read(0, &mut read_buf).unwrap();
222 assert_eq!(UPDATE, read_buf);
223 // First DFU page is untouched
224 flash.dfu().read(4096, &mut read_buf).unwrap();
225 assert_eq!(ORIGINAL, read_buf);
226 }
227
228 #[test]
229 #[cfg(not(feature = "_verify"))]
230 fn test_swap_state_dfu_page_biggest() {
231 const FIRMWARE_SIZE: usize = 12288;
232 let flash = AsyncTestFlash::new(BootLoaderConfig {
233 active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(),
234 dfu: MemFlash::<16384, 4096, 8>::random(),
235 state: MemFlash::<2048, 128, 4>::random(),
236 });
237
238 const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
239 const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
240 let mut aligned = [0; 4];
241
242 block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
243 block_on(flash.active().write(0, &ORIGINAL)).unwrap();
244
245 let mut updater = FirmwareUpdater::new(
246 FirmwareUpdaterConfig {
247 dfu: flash.dfu(),
248 state: flash.state(),
249 },
250 &mut aligned,
251 );
252 block_on(updater.write_firmware(0, &UPDATE)).unwrap();
253 block_on(updater.mark_updated()).unwrap();
254
255 let flash = flash.into_blocking();
256 let mut bootloader = BootLoader::new(BootLoaderConfig {
257 active: flash.active(),
258 dfu: flash.dfu(),
259 state: flash.state(),
260 });
261 let mut page = [0; 4096];
262 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
263
264 let mut read_buf = [0; FIRMWARE_SIZE];
265 flash.active().read(0, &mut read_buf).unwrap();
266 assert_eq!(UPDATE, read_buf);
267 // First DFU page is untouched
268 flash.dfu().read(4096, &mut read_buf).unwrap();
269 assert_eq!(ORIGINAL, read_buf);
270 }
271
272 #[test]
273 #[cfg(feature = "_verify")]
274 fn test_verify() {
275 // The following key setup is based on:
276 // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
277
278 use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey};
279 use rand::rngs::OsRng;
280
281 let mut csprng = OsRng {};
282 let keypair = SigningKey::generate(&mut csprng);
283
284 let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU.";
285 let mut digest = Sha512::new();
286 digest.update(&firmware);
287 let message = digest.finalize();
288 let signature: Signature = keypair.sign(&message);
289
290 let public_key = keypair.verifying_key();
291
292 // Setup flash
293 let flash = BlockingTestFlash::new(BootLoaderConfig {
294 active: MemFlash::<0, 0, 0>::default(),
295 dfu: MemFlash::<4096, 4096, 4>::default(),
296 state: MemFlash::<4096, 4096, 4>::default(),
297 });
298
299 let firmware_len = firmware.len();
300
301 let mut write_buf = [0; 4096];
302 write_buf[0..firmware_len].copy_from_slice(firmware);
303 flash.dfu().write(0, &write_buf).unwrap();
304
305 // On with the test
306 let flash = flash.into_async();
307 let mut aligned = [0; 4];
308 let mut updater = FirmwareUpdater::new(
309 FirmwareUpdaterConfig {
310 dfu: flash.dfu(),
311 state: flash.state(),
312 },
313 &mut aligned,
314 );
315
316 assert!(block_on(updater.verify_and_mark_updated(
317 &public_key.to_bytes(),
318 &signature.to_bytes(),
319 firmware_len as u32,
320 ))
321 .is_ok());
322 }
323}
diff --git a/embassy-boot/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs
new file mode 100644
index 000000000..40f352c8d
--- /dev/null
+++ b/embassy-boot/src/mem_flash.rs
@@ -0,0 +1,170 @@
1#![allow(unused)]
2
3use core::ops::{Bound, Range, RangeBounds};
4
5use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
6use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
7
8pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
9 pub mem: [u8; SIZE],
10 pub pending_write_successes: Option<usize>,
11}
12
13#[derive(Debug)]
14pub struct MemFlashError;
15
16impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
17 pub const fn new(fill: u8) -> Self {
18 Self {
19 mem: [fill; SIZE],
20 pending_write_successes: None,
21 }
22 }
23
24 #[cfg(test)]
25 pub fn random() -> Self {
26 let mut mem = [0; SIZE];
27 for byte in mem.iter_mut() {
28 *byte = rand::random::<u8>();
29 }
30 Self {
31 mem,
32 pending_write_successes: None,
33 }
34 }
35
36 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> {
37 let len = bytes.len();
38 bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
39 Ok(())
40 }
41
42 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
43 let offset = offset as usize;
44 assert!(bytes.len() % WRITE_SIZE == 0);
45 assert!(offset % WRITE_SIZE == 0);
46 assert!(offset + bytes.len() <= SIZE);
47
48 if let Some(pending_successes) = self.pending_write_successes {
49 if pending_successes > 0 {
50 self.pending_write_successes = Some(pending_successes - 1);
51 } else {
52 return Err(MemFlashError);
53 }
54 }
55
56 for ((offset, mem_byte), new_byte) in self
57 .mem
58 .iter_mut()
59 .enumerate()
60 .skip(offset)
61 .take(bytes.len())
62 .zip(bytes)
63 {
64 assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
65 *mem_byte = *new_byte;
66 }
67
68 Ok(())
69 }
70
71 fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> {
72 let from = from as usize;
73 let to = to as usize;
74 assert!(from % ERASE_SIZE == 0);
75 assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
76 for i in from..to {
77 self.mem[i] = 0xFF;
78 }
79 Ok(())
80 }
81
82 pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
83 let offset = offset as usize;
84 assert!(bytes.len() % WRITE_SIZE == 0);
85 assert!(offset % WRITE_SIZE == 0);
86 assert!(offset + bytes.len() <= SIZE);
87
88 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
89
90 Ok(())
91 }
92}
93
94impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
95 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
96{
97 fn default() -> Self {
98 Self::new(0xFF)
99 }
100}
101
102impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
103 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
104{
105 type Error = MemFlashError;
106}
107
108impl NorFlashError for MemFlashError {
109 fn kind(&self) -> NorFlashErrorKind {
110 NorFlashErrorKind::Other
111 }
112}
113
114impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
115 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
116{
117 const READ_SIZE: usize = 1;
118
119 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
120 self.read(offset, bytes)
121 }
122
123 fn capacity(&self) -> usize {
124 SIZE
125 }
126}
127
128impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
129 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
130{
131 const WRITE_SIZE: usize = WRITE_SIZE;
132 const ERASE_SIZE: usize = ERASE_SIZE;
133
134 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
135 self.write(offset, bytes)
136 }
137
138 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
139 self.erase(from, to)
140 }
141}
142
143impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
144 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
145{
146 const READ_SIZE: usize = 1;
147
148 async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
149 self.read(offset, bytes)
150 }
151
152 fn capacity(&self) -> usize {
153 SIZE
154 }
155}
156
157impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
158 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
159{
160 const WRITE_SIZE: usize = WRITE_SIZE;
161 const ERASE_SIZE: usize = ERASE_SIZE;
162
163 async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
164 self.write(offset, bytes)
165 }
166
167 async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
168 self.erase(from, to)
169 }
170}
diff --git a/embassy-boot/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs
new file mode 100644
index 000000000..3ac9e71ab
--- /dev/null
+++ b/embassy-boot/src/test_flash/asynch.rs
@@ -0,0 +1,64 @@
1use embassy_embedded_hal::flash::partition::Partition;
2use embassy_sync::blocking_mutex::raw::NoopRawMutex;
3use embassy_sync::mutex::Mutex;
4use embedded_storage_async::nor_flash::NorFlash;
5
6use crate::BootLoaderConfig;
7
8pub struct AsyncTestFlash<ACTIVE, DFU, STATE>
9where
10 ACTIVE: NorFlash,
11 DFU: NorFlash,
12 STATE: NorFlash,
13{
14 active: Mutex<NoopRawMutex, ACTIVE>,
15 dfu: Mutex<NoopRawMutex, DFU>,
16 state: Mutex<NoopRawMutex, STATE>,
17}
18
19impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
20where
21 ACTIVE: NorFlash,
22 DFU: NorFlash,
23 STATE: NorFlash,
24{
25 pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
26 Self {
27 active: Mutex::new(config.active),
28 dfu: Mutex::new(config.dfu),
29 state: Mutex::new(config.state),
30 }
31 }
32
33 pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> {
34 Self::create_partition(&self.active)
35 }
36
37 pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> {
38 Self::create_partition(&self.dfu)
39 }
40
41 pub fn state(&self) -> Partition<NoopRawMutex, STATE> {
42 Self::create_partition(&self.state)
43 }
44
45 fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> {
46 Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32)
47 }
48}
49
50impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
51where
52 ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash,
53 DFU: NorFlash + embedded_storage::nor_flash::NorFlash,
54 STATE: NorFlash + embedded_storage::nor_flash::NorFlash,
55{
56 pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> {
57 let config = BootLoaderConfig {
58 active: self.active.into_inner(),
59 dfu: self.dfu.into_inner(),
60 state: self.state.into_inner(),
61 };
62 super::BlockingTestFlash::new(config)
63 }
64}
diff --git a/embassy-boot/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs
new file mode 100644
index 000000000..5ec476c65
--- /dev/null
+++ b/embassy-boot/src/test_flash/blocking.rs
@@ -0,0 +1,68 @@
1use core::cell::RefCell;
2
3use embassy_embedded_hal::flash::partition::BlockingPartition;
4use embassy_sync::blocking_mutex::raw::NoopRawMutex;
5use embassy_sync::blocking_mutex::Mutex;
6use embedded_storage::nor_flash::NorFlash;
7
8use crate::BootLoaderConfig;
9
10pub struct BlockingTestFlash<ACTIVE, DFU, STATE>
11where
12 ACTIVE: NorFlash,
13 DFU: NorFlash,
14 STATE: NorFlash,
15{
16 active: Mutex<NoopRawMutex, RefCell<ACTIVE>>,
17 dfu: Mutex<NoopRawMutex, RefCell<DFU>>,
18 state: Mutex<NoopRawMutex, RefCell<STATE>>,
19}
20
21impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
22where
23 ACTIVE: NorFlash,
24 DFU: NorFlash,
25 STATE: NorFlash,
26{
27 pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
28 Self {
29 active: Mutex::new(RefCell::new(config.active)),
30 dfu: Mutex::new(RefCell::new(config.dfu)),
31 state: Mutex::new(RefCell::new(config.state)),
32 }
33 }
34
35 pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> {
36 Self::create_partition(&self.active)
37 }
38
39 pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> {
40 Self::create_partition(&self.dfu)
41 }
42
43 pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> {
44 Self::create_partition(&self.state)
45 }
46
47 pub fn create_partition<T: NorFlash>(
48 mutex: &Mutex<NoopRawMutex, RefCell<T>>,
49 ) -> BlockingPartition<NoopRawMutex, T> {
50 BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32)
51 }
52}
53
54impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
55where
56 ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
57 DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash,
58 STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
59{
60 pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> {
61 let config = BootLoaderConfig {
62 active: self.active.into_inner().into_inner(),
63 dfu: self.dfu.into_inner().into_inner(),
64 state: self.state.into_inner().into_inner(),
65 };
66 super::AsyncTestFlash::new(config)
67 }
68}
diff --git a/embassy-boot/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs
new file mode 100644
index 000000000..79b15a081
--- /dev/null
+++ b/embassy-boot/src/test_flash/mod.rs
@@ -0,0 +1,5 @@
1mod asynch;
2mod blocking;
3
4pub(crate) use asynch::AsyncTestFlash;
5pub(crate) use blocking::BlockingTestFlash;