aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot
diff options
context:
space:
mode:
authorsander <[email protected]>2023-04-11 13:48:34 +0200
committersander <[email protected]>2023-04-11 13:48:34 +0200
commitc309797488a4f33dfab659fdb2908eff76e16e40 (patch)
tree2ee72042d9f06bf694fea8dd4cd2ba3e2e2cb10b /embassy-boot
parent6b2aaacf830d69fcb05f9611d3780f56b4ae82bc (diff)
parentb150b9506b2f0502065dc1b22eccc6448f611bff (diff)
merge embassy/master
Diffstat (limited to 'embassy-boot')
-rw-r--r--embassy-boot/boot/Cargo.toml4
-rw-r--r--embassy-boot/boot/src/boot_loader.rs533
-rw-r--r--embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs30
-rw-r--r--embassy-boot/boot/src/digest_adapters/mod.rs5
-rw-r--r--embassy-boot/boot/src/digest_adapters/salty.rs29
-rw-r--r--embassy-boot/boot/src/firmware_updater.rs534
-rw-r--r--embassy-boot/boot/src/lib.rs1427
-rw-r--r--embassy-boot/boot/src/mem_flash.rs164
-rw-r--r--embassy-boot/boot/src/partition.rs139
-rw-r--r--embassy-boot/nrf/src/lib.rs26
-rw-r--r--embassy-boot/rp/src/lib.rs28
-rw-r--r--embassy-boot/stm32/src/lib.rs26
12 files changed, 1523 insertions, 1422 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml
index 04409cdc7..39f501570 100644
--- a/embassy-boot/boot/Cargo.toml
+++ b/embassy-boot/boot/Cargo.toml
@@ -24,6 +24,7 @@ features = ["defmt"]
24 24
25[dependencies] 25[dependencies]
26defmt = { version = "0.3", optional = true } 26defmt = { version = "0.3", optional = true }
27digest = "0.10"
27log = { version = "0.4", optional = true } 28log = { version = "0.4", optional = true }
28ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } 29ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
29embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } 30embassy-sync = { version = "0.1.0", path = "../../embassy-sync" }
@@ -37,6 +38,7 @@ log = "0.4"
37env_logger = "0.9" 38env_logger = "0.9"
38rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version 39rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
39futures = { version = "0.3", features = ["executor"] } 40futures = { version = "0.3", features = ["executor"] }
41sha1 = "0.10.5"
40 42
41[dev-dependencies.ed25519-dalek] 43[dev-dependencies.ed25519-dalek]
42default_features = false 44default_features = false
@@ -50,4 +52,4 @@ ed25519-salty = ["dep:salty", "_verify"]
50nightly = ["dep:embedded-storage-async"] 52nightly = ["dep:embedded-storage-async"]
51 53
52#Internal features 54#Internal features
53_verify = [] \ No newline at end of file 55_verify = []
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
new file mode 100644
index 000000000..b959de2c4
--- /dev/null
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -0,0 +1,533 @@
1use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
2
3use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
4
5/// Errors returned by bootloader
6#[derive(PartialEq, Eq, Debug)]
7pub enum BootError {
8 /// Error from flash.
9 Flash(NorFlashErrorKind),
10 /// Invalid bootloader magic
11 BadMagic,
12}
13
14#[cfg(feature = "defmt")]
15impl defmt::Format for BootError {
16 fn format(&self, fmt: defmt::Formatter) {
17 match self {
18 BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
19 BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
20 }
21 }
22}
23
24impl<E> From<E> for BootError
25where
26 E: NorFlashError,
27{
28 fn from(error: E) -> Self {
29 BootError::Flash(error.kind())
30 }
31}
32
33/// Trait defining the flash handles used for active and DFU partition.
34pub trait FlashConfig {
35 /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
36 const STATE_ERASE_VALUE: u8 = 0xFF;
37 /// Flash type used for the state partition.
38 type STATE: NorFlash;
39 /// Flash type used for the active partition.
40 type ACTIVE: NorFlash;
41 /// Flash type used for the dfu partition.
42 type DFU: NorFlash;
43
44 /// Return flash instance used to write/read to/from active partition.
45 fn active(&mut self) -> &mut Self::ACTIVE;
46 /// Return flash instance used to write/read to/from dfu partition.
47 fn dfu(&mut self) -> &mut Self::DFU;
48 /// Return flash instance used to write/read to/from bootloader state.
49 fn state(&mut self) -> &mut Self::STATE;
50}
51
52trait FlashConfigEx {
53 fn page_size() -> u32;
54}
55
56impl<T: FlashConfig> FlashConfigEx for T {
57 /// Get the page size which is the "unit of operation" within the bootloader.
58 fn page_size() -> u32 {
59 core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32
60 }
61}
62
63/// BootLoader works with any flash implementing embedded_storage.
64pub struct BootLoader {
65 // Page with current state of bootloader. The state partition has the following format:
66 // All ranges are in multiples of WRITE_SIZE bytes.
67 // | Range | Description |
68 // | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
69 // | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
70 // | 2..2 + N | Progress index used while swapping or reverting |
71 state: Partition,
72 // Location of the partition which will be booted from
73 active: Partition,
74 // Location of the partition which will be swapped in when requested
75 dfu: Partition,
76}
77
78impl BootLoader {
79 /// Create a new instance of a bootloader with the given partitions.
80 ///
81 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
82 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
83 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
84 Self { active, dfu, state }
85 }
86
87 /// Return the offset of the active partition into the active flash.
88 pub fn boot_address(&self) -> usize {
89 self.active.from as usize
90 }
91
92 /// Perform necessary boot preparations like swapping images.
93 ///
94 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
95 /// algorithm to work correctly.
96 ///
97 /// The provided aligned_buf argument must satisfy any alignment requirements
98 /// given by the partition flashes. All flash operations will use this buffer.
99 ///
100 /// SWAPPING
101 ///
102 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
103 /// The swap index contains the copy progress, as to allow continuation of the copy process on
104 /// power failure. The index counter is represented within 1 or more pages (depending on total
105 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
106 /// contains a zero value. This ensures that index updates can be performed atomically and
107 /// avoid a situation where the wrong index value is set (page write size is "atomic").
108 ///
109 /// +-----------+------------+--------+--------+--------+--------+
110 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
111 /// +-----------+------------+--------+--------+--------+--------+
112 /// | Active | 0 | 1 | 2 | 3 | - |
113 /// | DFU | 0 | 3 | 2 | 1 | X |
114 /// +-----------+------------+--------+--------+--------+--------+
115 ///
116 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
117 /// as follows:
118 ///
119 /// +-----------+------------+--------+--------+--------+--------+
120 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
121 /// +-----------+------------+--------+--------+--------+--------+
122 /// | Active | 1 | 1 | 2 | 1 | - |
123 /// | DFU | 1 | 3 | 2 | 1 | 3 |
124 /// +-----------+------------+--------+--------+--------+--------+
125 ///
126 /// The next iteration performs the same steps
127 ///
128 /// +-----------+------------+--------+--------+--------+--------+
129 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
130 /// +-----------+------------+--------+--------+--------+--------+
131 /// | Active | 2 | 1 | 2 | 1 | - |
132 /// | DFU | 2 | 3 | 2 | 2 | 3 |
133 /// +-----------+------------+--------+--------+--------+--------+
134 ///
135 /// And again until we're done
136 ///
137 /// +-----------+------------+--------+--------+--------+--------+
138 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
139 /// +-----------+------------+--------+--------+--------+--------+
140 /// | Active | 3 | 3 | 2 | 1 | - |
141 /// | DFU | 3 | 3 | 1 | 2 | 3 |
142 /// +-----------+------------+--------+--------+--------+--------+
143 ///
144 /// REVERTING
145 ///
146 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
147 /// the application failed to mark the boot successful. In this case, the revert algorithm will
148 /// run.
149 ///
150 /// The revert index is located separately from the swap index, to ensure that revert can continue
151 /// on power failure.
152 ///
153 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
154 ///
155 /// +-----------+--------------+--------+--------+--------+--------+
156 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
157 //*/
158 /// +-----------+--------------+--------+--------+--------+--------+
159 /// | Active | 3 | 1 | 2 | 1 | - |
160 /// | DFU | 3 | 3 | 1 | 2 | 3 |
161 /// +-----------+--------------+--------+--------+--------+--------+
162 ///
163 ///
164 /// +-----------+--------------+--------+--------+--------+--------+
165 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
166 /// +-----------+--------------+--------+--------+--------+--------+
167 /// | Active | 3 | 1 | 2 | 1 | - |
168 /// | DFU | 3 | 3 | 2 | 2 | 3 |
169 /// +-----------+--------------+--------+--------+--------+--------+
170 ///
171 /// +-----------+--------------+--------+--------+--------+--------+
172 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
173 /// +-----------+--------------+--------+--------+--------+--------+
174 /// | Active | 3 | 1 | 2 | 3 | - |
175 /// | DFU | 3 | 3 | 2 | 1 | 3 |
176 /// +-----------+--------------+--------+--------+--------+--------+
177 ///
178 pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
179 // Ensure we have enough progress pages to store copy progress
180 assert_eq!(0, P::page_size() % aligned_buf.len() as u32);
181 assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32);
182 assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32);
183 assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32);
184 assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32);
185 assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
186 assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
187 assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
188 assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
189
190 // Copy contents from partition N to active
191 let state = self.read_state(p, aligned_buf)?;
192 if state == State::Swap {
193 //
194 // Check if we already swapped. If we're in the swap state, this means we should revert
195 // since the app has failed to mark boot as successful
196 //
197 if !self.is_swapped(p, aligned_buf)? {
198 trace!("Swapping");
199 self.swap(p, aligned_buf)?;
200 trace!("Swapping done");
201 } else {
202 trace!("Reverting");
203 self.revert(p, aligned_buf)?;
204
205 let state_flash = p.state();
206 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
207
208 // Invalidate progress
209 state_word.fill(!P::STATE_ERASE_VALUE);
210 self.state
211 .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
212
213 // Clear magic and progress
214 self.state.wipe_blocking(state_flash)?;
215
216 // Set magic
217 state_word.fill(BOOT_MAGIC);
218 self.state.write_blocking(state_flash, 0, state_word)?;
219 }
220 }
221 Ok(state)
222 }
223
224 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
225 let page_count = (self.active.size() / P::page_size()) as usize;
226 let progress = self.current_progress(p, aligned_buf)?;
227
228 Ok(progress >= page_count * 2)
229 }
230
231 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
232 let write_size = P::STATE::WRITE_SIZE as u32;
233 let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize;
234 let state_flash = config.state();
235 let state_word = &mut aligned_buf[..write_size as usize];
236
237 self.state.read_blocking(state_flash, write_size, state_word)?;
238 if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
239 // Progress is invalid
240 return Ok(max_index);
241 }
242
243 for index in 0..max_index {
244 self.state
245 .read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
246
247 if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
248 return Ok(index);
249 }
250 }
251 Ok(max_index)
252 }
253
254 fn update_progress<P: FlashConfig>(
255 &mut self,
256 progress_index: usize,
257 p: &mut P,
258 aligned_buf: &mut [u8],
259 ) -> Result<(), BootError> {
260 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
261 state_word.fill(!P::STATE_ERASE_VALUE);
262 self.state.write_blocking(
263 p.state(),
264 (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32,
265 state_word,
266 )?;
267 Ok(())
268 }
269
270 fn copy_page_once_to_active<P: FlashConfig>(
271 &mut self,
272 progress_index: usize,
273 from_offset: u32,
274 to_offset: u32,
275 p: &mut P,
276 aligned_buf: &mut [u8],
277 ) -> Result<(), BootError> {
278 if self.current_progress(p, aligned_buf)? <= progress_index {
279 let page_size = P::page_size() as u32;
280
281 self.active
282 .erase_blocking(p.active(), to_offset, to_offset + page_size)?;
283
284 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
285 self.dfu
286 .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
287 self.active
288 .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
289 }
290
291 self.update_progress(progress_index, p, aligned_buf)?;
292 }
293 Ok(())
294 }
295
296 fn copy_page_once_to_dfu<P: FlashConfig>(
297 &mut self,
298 progress_index: usize,
299 from_offset: u32,
300 to_offset: u32,
301 p: &mut P,
302 aligned_buf: &mut [u8],
303 ) -> Result<(), BootError> {
304 if self.current_progress(p, aligned_buf)? <= progress_index {
305 let page_size = P::page_size() as u32;
306
307 self.dfu
308 .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
309
310 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
311 self.active
312 .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
313 self.dfu
314 .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
315 }
316
317 self.update_progress(progress_index, p, aligned_buf)?;
318 }
319 Ok(())
320 }
321
322 fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
323 let page_size = P::page_size();
324 let page_count = self.active.size() / page_size;
325 for page_num in 0..page_count {
326 let progress_index = (page_num * 2) as usize;
327
328 // Copy active page to the 'next' DFU page.
329 let active_from_offset = (page_count - 1 - page_num) * page_size;
330 let dfu_to_offset = (page_count - page_num) * page_size;
331 //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
332 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
333
334 // Copy DFU page to the active page
335 let active_to_offset = (page_count - 1 - page_num) * page_size;
336 let dfu_from_offset = (page_count - 1 - page_num) * page_size;
337 //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
338 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
339 }
340
341 Ok(())
342 }
343
344 fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
345 let page_size = P::page_size();
346 let page_count = self.active.size() / page_size;
347 for page_num in 0..page_count {
348 let progress_index = (page_count * 2 + page_num * 2) as usize;
349
350 // Copy the bad active page to the DFU page
351 let active_from_offset = page_num * page_size;
352 let dfu_to_offset = page_num * page_size;
353 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
354
355 // Copy the DFU page back to the active page
356 let active_to_offset = page_num * page_size;
357 let dfu_from_offset = (page_num + 1) * page_size;
358 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
359 }
360
361 Ok(())
362 }
363
364 fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
365 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
366 self.state.read_blocking(config.state(), 0, state_word)?;
367
368 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
369 Ok(State::Swap)
370 } else {
371 Ok(State::Boot)
372 }
373 }
374}
375
376fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) {
377 assert_eq!(active.size() % page_size, 0);
378 assert_eq!(dfu.size() % page_size, 0);
379 assert!(dfu.size() - active.size() >= page_size);
380 assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32);
381}
382
383/// A flash wrapper implementing the Flash and embedded_storage traits.
384pub struct BootFlash<F>
385where
386 F: NorFlash,
387{
388 flash: F,
389}
390
391impl<F> BootFlash<F>
392where
393 F: NorFlash,
394{
395 /// Create a new instance of a bootable flash
396 pub fn new(flash: F) -> Self {
397 Self { flash }
398 }
399}
400
401impl<F> ErrorType for BootFlash<F>
402where
403 F: NorFlash,
404{
405 type Error = F::Error;
406}
407
408impl<F> NorFlash for BootFlash<F>
409where
410 F: NorFlash,
411{
412 const WRITE_SIZE: usize = F::WRITE_SIZE;
413 const ERASE_SIZE: usize = F::ERASE_SIZE;
414
415 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
416 F::erase(&mut self.flash, from, to)
417 }
418
419 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
420 F::write(&mut self.flash, offset, bytes)
421 }
422}
423
424impl<F> ReadNorFlash for BootFlash<F>
425where
426 F: NorFlash,
427{
428 const READ_SIZE: usize = F::READ_SIZE;
429
430 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
431 F::read(&mut self.flash, offset, bytes)
432 }
433
434 fn capacity(&self) -> usize {
435 F::capacity(&self.flash)
436 }
437}
438
439/// Convenience provider that uses a single flash for all partitions.
440pub struct SingleFlashConfig<'a, F>
441where
442 F: NorFlash,
443{
444 flash: &'a mut F,
445}
446
447impl<'a, F> SingleFlashConfig<'a, F>
448where
449 F: NorFlash,
450{
451 /// Create a provider for a single flash.
452 pub fn new(flash: &'a mut F) -> Self {
453 Self { flash }
454 }
455}
456
457impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
458where
459 F: NorFlash,
460{
461 type STATE = F;
462 type ACTIVE = F;
463 type DFU = F;
464
465 fn active(&mut self) -> &mut Self::STATE {
466 self.flash
467 }
468 fn dfu(&mut self) -> &mut Self::ACTIVE {
469 self.flash
470 }
471 fn state(&mut self) -> &mut Self::DFU {
472 self.flash
473 }
474}
475
476/// Convenience flash provider that uses separate flash instances for each partition.
477pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
478where
479 ACTIVE: NorFlash,
480 STATE: NorFlash,
481 DFU: NorFlash,
482{
483 active: &'a mut ACTIVE,
484 state: &'a mut STATE,
485 dfu: &'a mut DFU,
486}
487
488impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
489where
490 ACTIVE: NorFlash,
491 STATE: NorFlash,
492 DFU: NorFlash,
493{
494 /// Create a new flash provider with separate configuration for all three partitions.
495 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
496 Self { active, state, dfu }
497 }
498}
499
500impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
501where
502 ACTIVE: NorFlash,
503 STATE: NorFlash,
504 DFU: NorFlash,
505{
506 type STATE = STATE;
507 type ACTIVE = ACTIVE;
508 type DFU = DFU;
509
510 fn active(&mut self) -> &mut Self::ACTIVE {
511 self.active
512 }
513 fn dfu(&mut self) -> &mut Self::DFU {
514 self.dfu
515 }
516 fn state(&mut self) -> &mut Self::STATE {
517 self.state
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 #[should_panic]
527 fn test_range_asserts() {
528 const ACTIVE: Partition = Partition::new(4096, 4194304);
529 const DFU: Partition = Partition::new(4194304, 2 * 4194304);
530 const STATE: Partition = Partition::new(0, 4096);
531 assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
532 }
533}
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs
new file mode 100644
index 000000000..a184d1c51
--- /dev/null
+++ b/embassy-boot/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 as _;
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 self.0.update(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/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs
new file mode 100644
index 000000000..9b4b4b60c
--- /dev/null
+++ b/embassy-boot/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/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs
new file mode 100644
index 000000000..2b5dcf3af
--- /dev/null
+++ b/embassy-boot/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/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs
new file mode 100644
index 000000000..a2f822f4a
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater.rs
@@ -0,0 +1,534 @@
1use digest::Digest;
2use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
3use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
4
5use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
6
7/// Errors returned by FirmwareUpdater
8#[derive(Debug)]
9pub enum FirmwareUpdaterError {
10 /// Error from flash.
11 Flash(NorFlashErrorKind),
12 /// Signature errors.
13 Signature(signature::Error),
14}
15
16#[cfg(feature = "defmt")]
17impl defmt::Format for FirmwareUpdaterError {
18 fn format(&self, fmt: defmt::Formatter) {
19 match self {
20 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
21 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
22 }
23 }
24}
25
26impl<E> From<E> for FirmwareUpdaterError
27where
28 E: NorFlashError,
29{
30 fn from(error: E) -> Self {
31 FirmwareUpdaterError::Flash(error.kind())
32 }
33}
34
35/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
36/// 'mess up' the internal bootloader state
37pub struct FirmwareUpdater {
38 state: Partition,
39 dfu: Partition,
40}
41
42impl Default for FirmwareUpdater {
43 fn default() -> Self {
44 extern "C" {
45 static __bootloader_state_start: u32;
46 static __bootloader_state_end: u32;
47 static __bootloader_dfu_start: u32;
48 static __bootloader_dfu_end: u32;
49 }
50
51 let dfu = unsafe {
52 Partition::new(
53 &__bootloader_dfu_start as *const u32 as u32,
54 &__bootloader_dfu_end as *const u32 as u32,
55 )
56 };
57 let state = unsafe {
58 Partition::new(
59 &__bootloader_state_start as *const u32 as u32,
60 &__bootloader_state_end as *const u32 as u32,
61 )
62 };
63
64 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
65 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
66 FirmwareUpdater::new(dfu, state)
67 }
68}
69
70impl FirmwareUpdater {
71 /// Create a firmware updater instance with partition ranges for the update and state partitions.
72 pub const fn new(dfu: Partition, state: Partition) -> Self {
73 Self { dfu, state }
74 }
75
76 /// Obtain the current state.
77 ///
78 /// This is useful to check if the bootloader has just done a swap, in order
79 /// to do verifications and self-tests of the new image before calling
80 /// `mark_booted`.
81 pub async fn get_state<F: AsyncNorFlash>(
82 &mut self,
83 state_flash: &mut F,
84 aligned: &mut [u8],
85 ) -> Result<State, FirmwareUpdaterError> {
86 self.state.read(state_flash, 0, aligned).await?;
87
88 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
89 Ok(State::Swap)
90 } else {
91 Ok(State::Boot)
92 }
93 }
94
95 /// Verify the DFU given a public key. If there is an error then DO NOT
96 /// proceed with updating the firmware as it must be signed with a
97 /// corresponding private key (otherwise it could be malicious firmware).
98 ///
99 /// Mark to trigger firmware swap on next boot if verify suceeds.
100 ///
101 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
102 /// been generated from a SHA-512 digest of the firmware bytes.
103 ///
104 /// If no signature feature is set then this method will always return a
105 /// signature error.
106 ///
107 /// # Safety
108 ///
109 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
110 /// and written to.
111 #[cfg(feature = "_verify")]
112 pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
113 &mut self,
114 _state_and_dfu_flash: &mut F,
115 _public_key: &[u8],
116 _signature: &[u8],
117 _update_len: u32,
118 _aligned: &mut [u8],
119 ) -> Result<(), FirmwareUpdaterError> {
120 assert_eq!(_aligned.len(), F::WRITE_SIZE);
121 assert!(_update_len <= self.dfu.size());
122
123 #[cfg(feature = "ed25519-dalek")]
124 {
125 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
126
127 use crate::digest_adapters::ed25519_dalek::Sha512;
128
129 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
130
131 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
132 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
133
134 let mut message = [0; 64];
135 self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
136 .await?;
137
138 public_key.verify(&message, &signature).map_err(into_signature_error)?
139 }
140 #[cfg(feature = "ed25519-salty")]
141 {
142 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
143 use salty::{PublicKey, Signature};
144
145 use crate::digest_adapters::salty::Sha512;
146
147 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
148 FirmwareUpdaterError::Signature(signature::Error::default())
149 }
150
151 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
152 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
153 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
154 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
155
156 let mut message = [0; 64];
157 self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
158 .await?;
159
160 let r = public_key.verify(&message, &signature);
161 trace!(
162 "Verifying with public key {}, signature {} and message {} yields ok: {}",
163 public_key.to_bytes(),
164 signature.to_bytes(),
165 message,
166 r.is_ok()
167 );
168 r.map_err(into_signature_error)?
169 }
170
171 self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
172 }
173
174 /// Verify the update in DFU with any digest.
175 pub async fn hash<F: AsyncNorFlash, D: Digest>(
176 &mut self,
177 dfu_flash: &mut F,
178 update_len: u32,
179 chunk_buf: &mut [u8],
180 output: &mut [u8],
181 ) -> Result<(), FirmwareUpdaterError> {
182 let mut digest = D::new();
183 for offset in (0..update_len).step_by(chunk_buf.len()) {
184 self.dfu.read(dfu_flash, offset, chunk_buf).await?;
185 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
186 digest.update(&chunk_buf[..len]);
187 }
188 output.copy_from_slice(digest.finalize().as_slice());
189 Ok(())
190 }
191
192 /// Mark to trigger firmware swap on next boot.
193 ///
194 /// # Safety
195 ///
196 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
197 #[cfg(not(feature = "_verify"))]
198 pub async fn mark_updated<F: AsyncNorFlash>(
199 &mut self,
200 state_flash: &mut F,
201 aligned: &mut [u8],
202 ) -> Result<(), FirmwareUpdaterError> {
203 assert_eq!(aligned.len(), F::WRITE_SIZE);
204 self.set_magic(aligned, SWAP_MAGIC, state_flash).await
205 }
206
207 /// Mark firmware boot successful and stop rollback on reset.
208 ///
209 /// # Safety
210 ///
211 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
212 pub async fn mark_booted<F: AsyncNorFlash>(
213 &mut self,
214 state_flash: &mut F,
215 aligned: &mut [u8],
216 ) -> Result<(), FirmwareUpdaterError> {
217 assert_eq!(aligned.len(), F::WRITE_SIZE);
218 self.set_magic(aligned, BOOT_MAGIC, state_flash).await
219 }
220
221 async fn set_magic<F: AsyncNorFlash>(
222 &mut self,
223 aligned: &mut [u8],
224 magic: u8,
225 state_flash: &mut F,
226 ) -> Result<(), FirmwareUpdaterError> {
227 self.state.read(state_flash, 0, aligned).await?;
228
229 if aligned.iter().any(|&b| b != magic) {
230 // Read progress validity
231 self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
232
233 // FIXME: Do not make this assumption.
234 const STATE_ERASE_VALUE: u8 = 0xFF;
235
236 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
237 // The current progress validity marker is invalid
238 } else {
239 // Invalidate progress
240 aligned.fill(!STATE_ERASE_VALUE);
241 self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
242 }
243
244 // Clear magic and progress
245 self.state.wipe(state_flash).await?;
246
247 // Set magic
248 aligned.fill(magic);
249 self.state.write(state_flash, 0, aligned).await?;
250 }
251 Ok(())
252 }
253
254 /// Write data to a flash page.
255 ///
256 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
257 ///
258 /// # Safety
259 ///
260 /// Failing to meet alignment and size requirements may result in a panic.
261 pub async fn write_firmware<F: AsyncNorFlash>(
262 &mut self,
263 offset: usize,
264 data: &[u8],
265 dfu_flash: &mut F,
266 ) -> Result<(), FirmwareUpdaterError> {
267 assert!(data.len() >= F::ERASE_SIZE);
268
269 self.dfu
270 .erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
271 .await?;
272
273 self.dfu.write(dfu_flash, offset as u32, data).await?;
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 async fn prepare_update<F: AsyncNorFlash>(
284 &mut self,
285 dfu_flash: &mut F,
286 ) -> Result<Partition, FirmwareUpdaterError> {
287 self.dfu.wipe(dfu_flash).await?;
288
289 Ok(self.dfu)
290 }
291
292 //
293 // Blocking API
294 //
295
296 /// Obtain the current state.
297 ///
298 /// This is useful to check if the bootloader has just done a swap, in order
299 /// to do verifications and self-tests of the new image before calling
300 /// `mark_booted`.
301 pub fn get_state_blocking<F: NorFlash>(
302 &mut self,
303 state_flash: &mut F,
304 aligned: &mut [u8],
305 ) -> Result<State, FirmwareUpdaterError> {
306 self.state.read_blocking(state_flash, 0, aligned)?;
307
308 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
309 Ok(State::Swap)
310 } else {
311 Ok(State::Boot)
312 }
313 }
314
315 /// Verify the DFU given a public key. If there is an error then DO NOT
316 /// proceed with updating the firmware as it must be signed with a
317 /// corresponding private key (otherwise it could be malicious firmware).
318 ///
319 /// Mark to trigger firmware swap on next boot if verify suceeds.
320 ///
321 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
322 /// been generated from a SHA-512 digest of the firmware bytes.
323 ///
324 /// If no signature feature is set then this method will always return a
325 /// signature error.
326 ///
327 /// # Safety
328 ///
329 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
330 /// and written to.
331 #[cfg(feature = "_verify")]
332 pub fn verify_and_mark_updated_blocking<F: NorFlash>(
333 &mut self,
334 _state_and_dfu_flash: &mut F,
335 _public_key: &[u8],
336 _signature: &[u8],
337 _update_len: u32,
338 _aligned: &mut [u8],
339 ) -> Result<(), FirmwareUpdaterError> {
340 assert_eq!(_aligned.len(), F::WRITE_SIZE);
341 assert!(_update_len <= self.dfu.size());
342
343 #[cfg(feature = "ed25519-dalek")]
344 {
345 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
346
347 use crate::digest_adapters::ed25519_dalek::Sha512;
348
349 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
350
351 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
352 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
353
354 let mut message = [0; 64];
355 self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
356
357 public_key.verify(&message, &signature).map_err(into_signature_error)?
358 }
359 #[cfg(feature = "ed25519-salty")]
360 {
361 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
362 use salty::{PublicKey, Signature};
363
364 use crate::digest_adapters::salty::Sha512;
365
366 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
367 FirmwareUpdaterError::Signature(signature::Error::default())
368 }
369
370 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
371 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
372 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
373 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
374
375 let mut message = [0; 64];
376 self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
377
378 let r = public_key.verify(&message, &signature);
379 trace!(
380 "Verifying with public key {}, signature {} and message {} yields ok: {}",
381 public_key.to_bytes(),
382 signature.to_bytes(),
383 message,
384 r.is_ok()
385 );
386 r.map_err(into_signature_error)?
387 }
388
389 self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
390 }
391
392 /// Verify the update in DFU with any digest.
393 pub fn hash_blocking<F: NorFlash, D: Digest>(
394 &mut self,
395 dfu_flash: &mut F,
396 update_len: u32,
397 chunk_buf: &mut [u8],
398 output: &mut [u8],
399 ) -> Result<(), FirmwareUpdaterError> {
400 let mut digest = D::new();
401 for offset in (0..update_len).step_by(chunk_buf.len()) {
402 self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
403 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
404 digest.update(&chunk_buf[..len]);
405 }
406 output.copy_from_slice(digest.finalize().as_slice());
407 Ok(())
408 }
409
410 /// Mark to trigger firmware swap on next boot.
411 ///
412 /// # Safety
413 ///
414 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
415 #[cfg(not(feature = "_verify"))]
416 pub fn mark_updated_blocking<F: NorFlash>(
417 &mut self,
418 state_flash: &mut F,
419 aligned: &mut [u8],
420 ) -> Result<(), FirmwareUpdaterError> {
421 assert_eq!(aligned.len(), F::WRITE_SIZE);
422 self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
423 }
424
425 /// Mark firmware boot successful and stop rollback on reset.
426 ///
427 /// # Safety
428 ///
429 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
430 pub fn mark_booted_blocking<F: NorFlash>(
431 &mut self,
432 state_flash: &mut F,
433 aligned: &mut [u8],
434 ) -> Result<(), FirmwareUpdaterError> {
435 assert_eq!(aligned.len(), F::WRITE_SIZE);
436 self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
437 }
438
439 fn set_magic_blocking<F: NorFlash>(
440 &mut self,
441 aligned: &mut [u8],
442 magic: u8,
443 state_flash: &mut F,
444 ) -> Result<(), FirmwareUpdaterError> {
445 self.state.read_blocking(state_flash, 0, aligned)?;
446
447 if aligned.iter().any(|&b| b != magic) {
448 // Read progress validity
449 self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
450
451 // FIXME: Do not make this assumption.
452 const STATE_ERASE_VALUE: u8 = 0xFF;
453
454 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
455 // The current progress validity marker is invalid
456 } else {
457 // Invalidate progress
458 aligned.fill(!STATE_ERASE_VALUE);
459 self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
460 }
461
462 // Clear magic and progress
463 self.state.wipe_blocking(state_flash)?;
464
465 // Set magic
466 aligned.fill(magic);
467 self.state.write_blocking(state_flash, 0, aligned)?;
468 }
469 Ok(())
470 }
471
472 /// Write data to a flash page.
473 ///
474 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
475 ///
476 /// # Safety
477 ///
478 /// Failing to meet alignment and size requirements may result in a panic.
479 pub fn write_firmware_blocking<F: NorFlash>(
480 &mut self,
481 offset: usize,
482 data: &[u8],
483 dfu_flash: &mut F,
484 ) -> Result<(), FirmwareUpdaterError> {
485 assert!(data.len() >= F::ERASE_SIZE);
486
487 self.dfu
488 .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
489
490 self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
491
492 Ok(())
493 }
494
495 /// Prepare for an incoming DFU update by erasing the entire DFU area and
496 /// returning its `Partition`.
497 ///
498 /// Using this instead of `write_firmware_blocking` allows for an optimized
499 /// API in exchange for added complexity.
500 pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
501 self.dfu.wipe_blocking(flash)?;
502
503 Ok(self.dfu)
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use futures::executor::block_on;
510 use sha1::{Digest, Sha1};
511
512 use super::*;
513 use crate::mem_flash::MemFlash;
514
515 #[test]
516 fn can_verify_sha1() {
517 const STATE: Partition = Partition::new(0, 4096);
518 const DFU: Partition = Partition::new(65536, 131072);
519
520 let mut flash = MemFlash::<131072, 4096, 8>::default();
521
522 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
523 let mut to_write = [0; 4096];
524 to_write[..7].copy_from_slice(update.as_slice());
525
526 let mut updater = FirmwareUpdater::new(DFU, STATE);
527 block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
528 let mut chunk_buf = [0; 2];
529 let mut hash = [0; 20];
530 block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
531
532 assert_eq!(Sha1::digest(update).as_slice(), hash);
533 }
534}
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 7ce0c664a..e268d8883 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -5,36 +5,18 @@
5#![doc = include_str!("../README.md")] 5#![doc = include_str!("../README.md")]
6mod fmt; 6mod fmt;
7 7
8use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; 8mod boot_loader;
9mod digest_adapters;
10mod firmware_updater;
11mod mem_flash;
12mod partition;
9 13
10#[cfg(feature = "nightly")] 14pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig};
11use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; 15pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError};
16pub use partition::Partition;
12 17
13const BOOT_MAGIC: u8 = 0xD0; 18pub(crate) const BOOT_MAGIC: u8 = 0xD0;
14const SWAP_MAGIC: u8 = 0xF0; 19pub(crate) const SWAP_MAGIC: u8 = 0xF0;
15
16/// A region in flash used by the bootloader.
17#[derive(Copy, Clone, Debug)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19pub struct Partition {
20 /// Start of the flash region.
21 pub from: usize,
22 /// End of the flash region.
23 pub to: usize,
24}
25
26impl Partition {
27 /// Create a new partition with the provided range
28 pub const fn new(from: usize, to: usize) -> Self {
29 Self { from, to }
30 }
31
32 /// Return the length of the partition
33 #[allow(clippy::len_without_is_empty)]
34 pub const fn len(&self) -> usize {
35 self.to - self.from
36 }
37}
38 20
39/// The state of the bootloader after running prepare. 21/// The state of the bootloader after running prepare.
40#[derive(PartialEq, Eq, Debug)] 22#[derive(PartialEq, Eq, Debug)]
@@ -46,34 +28,6 @@ pub enum State {
46 Swap, 28 Swap,
47} 29}
48 30
49/// Errors returned by bootloader
50#[derive(PartialEq, Eq, Debug)]
51pub enum BootError {
52 /// Error from flash.
53 Flash(NorFlashErrorKind),
54 /// Invalid bootloader magic
55 BadMagic,
56}
57
58#[cfg(feature = "defmt")]
59impl defmt::Format for BootError {
60 fn format(&self, fmt: defmt::Formatter) {
61 match self {
62 BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
63 BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
64 }
65 }
66}
67
68impl<E> From<E> for BootError
69where
70 E: NorFlashError,
71{
72 fn from(error: E) -> Self {
73 BootError::Flash(error.kind())
74 }
75}
76
77/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. 31/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
78#[repr(align(32))] 32#[repr(align(32))]
79pub struct AlignedBuffer<const N: usize>(pub [u8; N]); 33pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
@@ -90,1128 +44,12 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
90 } 44 }
91} 45}
92 46
93/// Extension of the embedded-storage flash type information with block size and erase value.
94pub trait Flash: NorFlash + ReadNorFlash {
95 /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase
96 /// size of the flash, but for external QSPI flash modules, this can be lower.
97 const BLOCK_SIZE: usize;
98 /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value.
99 const ERASE_VALUE: u8 = 0xFF;
100}
101
102/// Trait defining the flash handles used for active and DFU partition
103pub trait FlashConfig {
104 /// Flash type used for the state partition.
105 type STATE: Flash;
106 /// Flash type used for the active partition.
107 type ACTIVE: Flash;
108 /// Flash type used for the dfu partition.
109 type DFU: Flash;
110
111 /// Return flash instance used to write/read to/from active partition.
112 fn active(&mut self) -> &mut Self::ACTIVE;
113 /// Return flash instance used to write/read to/from dfu partition.
114 fn dfu(&mut self) -> &mut Self::DFU;
115 /// Return flash instance used to write/read to/from bootloader state.
116 fn state(&mut self) -> &mut Self::STATE;
117}
118
119/// BootLoader works with any flash implementing embedded_storage and can also work with
120/// different page sizes and flash write sizes.
121pub struct BootLoader {
122 // Page with current state of bootloader. The state partition has the following format:
123 // | Range | Description |
124 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
125 // | WRITE_SIZE - N | Progress index used while swapping or reverting |
126 state: Partition,
127 // Location of the partition which will be booted from
128 active: Partition,
129 // Location of the partition which will be swapped in when requested
130 dfu: Partition,
131}
132
133impl BootLoader {
134 /// Create a new instance of a bootloader with the given partitions.
135 ///
136 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
137 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
138 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
139 Self { active, dfu, state }
140 }
141
142 /// Return the boot address for the active partition.
143 pub fn boot_address(&self) -> usize {
144 self.active.from
145 }
146
147 /// Perform necessary boot preparations like swapping images.
148 ///
149 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
150 /// algorithm to work correctly.
151 ///
152 /// SWAPPING
153 ///
154 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
155 /// The swap index contains the copy progress, as to allow continuation of the copy process on
156 /// power failure. The index counter is represented within 1 or more pages (depending on total
157 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
158 /// contains a zero value. This ensures that index updates can be performed atomically and
159 /// avoid a situation where the wrong index value is set (page write size is "atomic").
160 ///
161 /// +-----------+------------+--------+--------+--------+--------+
162 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
163 /// +-----------+------------+--------+--------+--------+--------+
164 /// | Active | 0 | 1 | 2 | 3 | - |
165 /// | DFU | 0 | 3 | 2 | 1 | X |
166 /// +-----------+------------+--------+--------+--------+--------+
167 ///
168 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
169 /// as follows:
170 ///
171 /// +-----------+------------+--------+--------+--------+--------+
172 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
173 /// +-----------+------------+--------+--------+--------+--------+
174 /// | Active | 1 | 1 | 2 | 1 | - |
175 /// | DFU | 1 | 3 | 2 | 1 | 3 |
176 /// +-----------+------------+--------+--------+--------+--------+
177 ///
178 /// The next iteration performs the same steps
179 ///
180 /// +-----------+------------+--------+--------+--------+--------+
181 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
182 /// +-----------+------------+--------+--------+--------+--------+
183 /// | Active | 2 | 1 | 2 | 1 | - |
184 /// | DFU | 2 | 3 | 2 | 2 | 3 |
185 /// +-----------+------------+--------+--------+--------+--------+
186 ///
187 /// And again until we're done
188 ///
189 /// +-----------+------------+--------+--------+--------+--------+
190 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
191 /// +-----------+------------+--------+--------+--------+--------+
192 /// | Active | 3 | 3 | 2 | 1 | - |
193 /// | DFU | 3 | 3 | 1 | 2 | 3 |
194 /// +-----------+------------+--------+--------+--------+--------+
195 ///
196 /// REVERTING
197 ///
198 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
199 /// the application failed to mark the boot successful. In this case, the revert algorithm will
200 /// run.
201 ///
202 /// The revert index is located separately from the swap index, to ensure that revert can continue
203 /// on power failure.
204 ///
205 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
206 ///
207 /// +-----------+--------------+--------+--------+--------+--------+
208 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
209 //*/
210 /// +-----------+--------------+--------+--------+--------+--------+
211 /// | Active | 3 | 1 | 2 | 1 | - |
212 /// | DFU | 3 | 3 | 1 | 2 | 3 |
213 /// +-----------+--------------+--------+--------+--------+--------+
214 ///
215 ///
216 /// +-----------+--------------+--------+--------+--------+--------+
217 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
218 /// +-----------+--------------+--------+--------+--------+--------+
219 /// | Active | 3 | 1 | 2 | 1 | - |
220 /// | DFU | 3 | 3 | 2 | 2 | 3 |
221 /// +-----------+--------------+--------+--------+--------+--------+
222 ///
223 /// +-----------+--------------+--------+--------+--------+--------+
224 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
225 /// +-----------+--------------+--------+--------+--------+--------+
226 /// | Active | 3 | 1 | 2 | 3 | - |
227 /// | DFU | 3 | 3 | 2 | 1 | 3 |
228 /// +-----------+--------------+--------+--------+--------+--------+
229 ///
230 pub fn prepare_boot<P: FlashConfig>(
231 &mut self,
232 p: &mut P,
233 magic: &mut [u8],
234 page: &mut [u8],
235 ) -> Result<State, BootError> {
236 // Ensure we have enough progress pages to store copy progress
237 assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE);
238 assert_eq!(magic.len(), P::STATE::WRITE_SIZE);
239
240 // Copy contents from partition N to active
241 let state = self.read_state(p, magic)?;
242 if state == State::Swap {
243 //
244 // Check if we already swapped. If we're in the swap state, this means we should revert
245 // since the app has failed to mark boot as successful
246 //
247 if !self.is_swapped(p, magic, page)? {
248 trace!("Swapping");
249 self.swap(p, magic, page)?;
250 trace!("Swapping done");
251 } else {
252 trace!("Reverting");
253 self.revert(p, magic, page)?;
254
255 // Overwrite magic and reset progress
256 let fstate = p.state();
257 magic.fill(!P::STATE::ERASE_VALUE);
258 fstate.write(self.state.from as u32, magic)?;
259 fstate.erase(self.state.from as u32, self.state.to as u32)?;
260
261 magic.fill(BOOT_MAGIC);
262 fstate.write(self.state.from as u32, magic)?;
263 }
264 }
265 Ok(state)
266 }
267
268 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<bool, BootError> {
269 let page_size = page.len();
270 let page_count = self.active.len() / page_size;
271 let progress = self.current_progress(p, magic)?;
272
273 Ok(progress >= page_count * 2)
274 }
275
276 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> {
277 let write_size = aligned.len();
278 let max_index = ((self.state.len() - write_size) / write_size) - 1;
279 aligned.fill(!P::STATE::ERASE_VALUE);
280
281 let flash = config.state();
282 for i in 0..max_index {
283 flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?;
284
285 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) {
286 return Ok(i);
287 }
288 }
289 Ok(max_index)
290 }
291
292 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> {
293 let flash = p.state();
294 let write_size = magic.len();
295 let w = self.state.from + write_size + idx * write_size;
296
297 let aligned = magic;
298 aligned.fill(!P::STATE::ERASE_VALUE);
299 flash.write(w as u32, aligned)?;
300 Ok(())
301 }
302
303 fn active_addr(&self, n: usize, page_size: usize) -> usize {
304 self.active.from + n * page_size
305 }
306
307 fn dfu_addr(&self, n: usize, page_size: usize) -> usize {
308 self.dfu.from + n * page_size
309 }
310
311 fn copy_page_once_to_active<P: FlashConfig>(
312 &mut self,
313 idx: usize,
314 from_page: usize,
315 to_page: usize,
316 p: &mut P,
317 magic: &mut [u8],
318 page: &mut [u8],
319 ) -> Result<(), BootError> {
320 let buf = page;
321 if self.current_progress(p, magic)? <= idx {
322 let mut offset = from_page;
323 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
324 p.dfu().read(offset as u32, chunk)?;
325 offset += chunk.len();
326 }
327
328 p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?;
329
330 let mut offset = to_page;
331 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
332 p.active().write(offset as u32, chunk)?;
333 offset += chunk.len();
334 }
335 self.update_progress(idx, p, magic)?;
336 }
337 Ok(())
338 }
339
340 fn copy_page_once_to_dfu<P: FlashConfig>(
341 &mut self,
342 idx: usize,
343 from_page: usize,
344 to_page: usize,
345 p: &mut P,
346 magic: &mut [u8],
347 page: &mut [u8],
348 ) -> Result<(), BootError> {
349 let buf = page;
350 if self.current_progress(p, magic)? <= idx {
351 let mut offset = from_page;
352 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
353 p.active().read(offset as u32, chunk)?;
354 offset += chunk.len();
355 }
356
357 p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?;
358
359 let mut offset = to_page;
360 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
361 p.dfu().write(offset as u32, chunk)?;
362 offset += chunk.len();
363 }
364 self.update_progress(idx, p, magic)?;
365 }
366 Ok(())
367 }
368
369 fn swap<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
370 let page_size = page.len();
371 let page_count = self.active.len() / page_size;
372 trace!("Page count: {}", page_count);
373 for page_num in 0..page_count {
374 trace!("COPY PAGE {}", page_num);
375 // Copy active page to the 'next' DFU page.
376 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
377 let dfu_page = self.dfu_addr(page_count - page_num, page_size);
378 //trace!("Copy active {} to dfu {}", active_page, dfu_page);
379 self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?;
380
381 // Copy DFU page to the active page
382 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
383 let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size);
384 //trace!("Copy dfy {} to active {}", dfu_page, active_page);
385 self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
386 }
387
388 Ok(())
389 }
390
391 fn revert<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
392 let page_size = page.len();
393 let page_count = self.active.len() / page_size;
394 for page_num in 0..page_count {
395 // Copy the bad active page to the DFU page
396 let active_page = self.active_addr(page_num, page_size);
397 let dfu_page = self.dfu_addr(page_num, page_size);
398 self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?;
399
400 // Copy the DFU page back to the active page
401 let active_page = self.active_addr(page_num, page_size);
402 let dfu_page = self.dfu_addr(page_num + 1, page_size);
403 self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
404 }
405
406 Ok(())
407 }
408
409 fn read_state<P: FlashConfig>(&mut self, config: &mut P, magic: &mut [u8]) -> Result<State, BootError> {
410 let flash = config.state();
411 flash.read(self.state.from as u32, magic)?;
412
413 if !magic.iter().any(|&b| b != SWAP_MAGIC) {
414 Ok(State::Swap)
415 } else {
416 Ok(State::Boot)
417 }
418 }
419}
420
421fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) {
422 assert_eq!(active.len() % page_size, 0);
423 assert_eq!(dfu.len() % page_size, 0);
424 assert!(dfu.len() - active.len() >= page_size);
425 assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size);
426}
427
428/// Convenience provider that uses a single flash for all partitions.
429pub struct SingleFlashConfig<'a, F>
430where
431 F: Flash,
432{
433 flash: &'a mut F,
434}
435
436impl<'a, F> SingleFlashConfig<'a, F>
437where
438 F: Flash,
439{
440 /// Create a provider for a single flash.
441 pub fn new(flash: &'a mut F) -> Self {
442 Self { flash }
443 }
444}
445
446impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
447where
448 F: Flash,
449{
450 type STATE = F;
451 type ACTIVE = F;
452 type DFU = F;
453
454 fn active(&mut self) -> &mut Self::STATE {
455 self.flash
456 }
457 fn dfu(&mut self) -> &mut Self::ACTIVE {
458 self.flash
459 }
460 fn state(&mut self) -> &mut Self::DFU {
461 self.flash
462 }
463}
464
465/// A flash wrapper implementing the Flash and embedded_storage traits.
466pub struct BootFlash<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
467where
468 F: NorFlash + ReadNorFlash,
469{
470 flash: F,
471}
472
473impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
474where
475 F: NorFlash + ReadNorFlash,
476{
477 /// Create a new instance of a bootable flash
478 pub fn new(flash: F) -> Self {
479 Self { flash }
480 }
481}
482
483impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
484where
485 F: NorFlash + ReadNorFlash,
486{
487 const BLOCK_SIZE: usize = BLOCK_SIZE;
488 const ERASE_VALUE: u8 = ERASE_VALUE;
489}
490
491impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
492where
493 F: ReadNorFlash + NorFlash,
494{
495 type Error = F::Error;
496}
497
498impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
499where
500 F: ReadNorFlash + NorFlash,
501{
502 const WRITE_SIZE: usize = F::WRITE_SIZE;
503 const ERASE_SIZE: usize = F::ERASE_SIZE;
504
505 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
506 F::erase(&mut self.flash, from, to)
507 }
508
509 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
510 F::write(&mut self.flash, offset, bytes)
511 }
512}
513
514impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
515where
516 F: ReadNorFlash + NorFlash,
517{
518 const READ_SIZE: usize = F::READ_SIZE;
519
520 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
521 F::read(&mut self.flash, offset, bytes)
522 }
523
524 fn capacity(&self) -> usize {
525 F::capacity(&self.flash)
526 }
527}
528
529/// Convenience flash provider that uses separate flash instances for each partition.
530pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
531where
532 ACTIVE: Flash,
533 STATE: Flash,
534 DFU: Flash,
535{
536 active: &'a mut ACTIVE,
537 state: &'a mut STATE,
538 dfu: &'a mut DFU,
539}
540
541impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
542where
543 ACTIVE: Flash,
544 STATE: Flash,
545 DFU: Flash,
546{
547 /// Create a new flash provider with separate configuration for all three partitions.
548 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
549 Self { active, state, dfu }
550 }
551}
552
553impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
554where
555 ACTIVE: Flash,
556 STATE: Flash,
557 DFU: Flash,
558{
559 type STATE = STATE;
560 type ACTIVE = ACTIVE;
561 type DFU = DFU;
562
563 fn active(&mut self) -> &mut Self::ACTIVE {
564 self.active
565 }
566 fn dfu(&mut self) -> &mut Self::DFU {
567 self.dfu
568 }
569 fn state(&mut self) -> &mut Self::STATE {
570 self.state
571 }
572}
573/// Errors returned by FirmwareUpdater
574#[derive(Debug)]
575pub enum FirmwareUpdaterError {
576 /// Error from flash.
577 Flash(NorFlashErrorKind),
578 /// Signature errors.
579 Signature(signature::Error),
580}
581
582#[cfg(feature = "defmt")]
583impl defmt::Format for FirmwareUpdaterError {
584 fn format(&self, fmt: defmt::Formatter) {
585 match self {
586 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
587 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
588 }
589 }
590}
591
592impl<E> From<E> for FirmwareUpdaterError
593where
594 E: NorFlashError,
595{
596 fn from(error: E) -> Self {
597 FirmwareUpdaterError::Flash(error.kind())
598 }
599}
600
601/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
602/// 'mess up' the internal bootloader state
603pub struct FirmwareUpdater {
604 state: Partition,
605 dfu: Partition,
606}
607
608impl Default for FirmwareUpdater {
609 fn default() -> Self {
610 extern "C" {
611 static __bootloader_state_start: u32;
612 static __bootloader_state_end: u32;
613 static __bootloader_dfu_start: u32;
614 static __bootloader_dfu_end: u32;
615 }
616
617 let dfu = unsafe {
618 Partition::new(
619 &__bootloader_dfu_start as *const u32 as usize,
620 &__bootloader_dfu_end as *const u32 as usize,
621 )
622 };
623 let state = unsafe {
624 Partition::new(
625 &__bootloader_state_start as *const u32 as usize,
626 &__bootloader_state_end as *const u32 as usize,
627 )
628 };
629
630 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
631 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
632 FirmwareUpdater::new(dfu, state)
633 }
634}
635
636impl FirmwareUpdater {
637 /// Create a firmware updater instance with partition ranges for the update and state partitions.
638 pub const fn new(dfu: Partition, state: Partition) -> Self {
639 Self { dfu, state }
640 }
641
642 /// Return the length of the DFU area
643 pub fn firmware_len(&self) -> usize {
644 self.dfu.len()
645 }
646
647 /// Obtain the current state.
648 ///
649 /// This is useful to check if the bootloader has just done a swap, in order
650 /// to do verifications and self-tests of the new image before calling
651 /// `mark_booted`.
652 #[cfg(feature = "nightly")]
653 pub async fn get_state<F: AsyncNorFlash>(
654 &mut self,
655 flash: &mut F,
656 aligned: &mut [u8],
657 ) -> Result<State, FirmwareUpdaterError> {
658 flash.read(self.state.from as u32, aligned).await?;
659
660 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
661 Ok(State::Swap)
662 } else {
663 Ok(State::Boot)
664 }
665 }
666
667 /// Verify the DFU given a public key. If there is an error then DO NOT
668 /// proceed with updating the firmware as it must be signed with a
669 /// corresponding private key (otherwise it could be malicious firmware).
670 ///
671 /// Mark to trigger firmware swap on next boot if verify suceeds.
672 ///
673 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
674 /// been generated from a SHA-512 digest of the firmware bytes.
675 ///
676 /// If no signature feature is set then this method will always return a
677 /// signature error.
678 ///
679 /// # Safety
680 ///
681 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
682 /// and written to.
683 #[cfg(feature = "_verify")]
684 pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
685 &mut self,
686 _flash: &mut F,
687 _public_key: &[u8],
688 _signature: &[u8],
689 _update_len: usize,
690 _aligned: &mut [u8],
691 ) -> Result<(), FirmwareUpdaterError> {
692 let _end = self.dfu.from + _update_len;
693 let _read_size = _aligned.len();
694
695 assert_eq!(_aligned.len(), F::WRITE_SIZE);
696 assert!(_end <= self.dfu.to);
697
698 #[cfg(feature = "ed25519-dalek")]
699 {
700 use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
701
702 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
703
704 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
705 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
706
707 let mut digest = Sha512::new();
708
709 let mut offset = self.dfu.from;
710 let last_offset = _end / _read_size * _read_size;
711
712 while offset < last_offset {
713 _flash.read(offset as u32, _aligned).await?;
714 digest.update(&_aligned);
715 offset += _read_size;
716 }
717
718 let remaining = _end % _read_size;
719
720 if remaining > 0 {
721 _flash.read(last_offset as u32, _aligned).await?;
722 digest.update(&_aligned[0..remaining]);
723 }
724
725 public_key
726 .verify(&digest.finalize(), &signature)
727 .map_err(into_signature_error)?
728 }
729 #[cfg(feature = "ed25519-salty")]
730 {
731 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
732 use salty::{PublicKey, Sha512, Signature};
733
734 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
735 FirmwareUpdaterError::Signature(signature::Error::default())
736 }
737
738 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
739 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
740 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
741 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
742
743 let mut digest = Sha512::new();
744
745 let mut offset = self.dfu.from;
746 let last_offset = _end / _read_size * _read_size;
747
748 while offset < last_offset {
749 _flash.read(offset as u32, _aligned).await?;
750 digest.update(&_aligned);
751 offset += _read_size;
752 }
753
754 let remaining = _end % _read_size;
755
756 if remaining > 0 {
757 _flash.read(last_offset as u32, _aligned).await?;
758 digest.update(&_aligned[0..remaining]);
759 }
760
761 let message = digest.finalize();
762 let r = public_key.verify(&message, &signature);
763 trace!(
764 "Verifying with public key {}, signature {} and message {} yields ok: {}",
765 public_key.to_bytes(),
766 signature.to_bytes(),
767 message,
768 r.is_ok()
769 );
770 r.map_err(into_signature_error)?
771 }
772
773 self.set_magic(_aligned, SWAP_MAGIC, _flash).await
774 }
775
776 /// Mark to trigger firmware swap on next boot.
777 ///
778 /// # Safety
779 ///
780 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
781 #[cfg(not(feature = "_verify"))]
782 #[cfg(feature = "nightly")]
783 pub async fn mark_updated<F: AsyncNorFlash>(
784 &mut self,
785 flash: &mut F,
786 aligned: &mut [u8],
787 ) -> Result<(), FirmwareUpdaterError> {
788 assert_eq!(aligned.len(), F::WRITE_SIZE);
789 self.set_magic(aligned, SWAP_MAGIC, flash).await
790 }
791
792 /// Mark firmware boot successful and stop rollback on reset.
793 ///
794 /// # Safety
795 ///
796 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
797 #[cfg(feature = "nightly")]
798 pub async fn mark_booted<F: AsyncNorFlash>(
799 &mut self,
800 flash: &mut F,
801 aligned: &mut [u8],
802 ) -> Result<(), FirmwareUpdaterError> {
803 assert_eq!(aligned.len(), F::WRITE_SIZE);
804 self.set_magic(aligned, BOOT_MAGIC, flash).await
805 }
806
807 #[cfg(feature = "nightly")]
808 async fn set_magic<F: AsyncNorFlash>(
809 &mut self,
810 aligned: &mut [u8],
811 magic: u8,
812 flash: &mut F,
813 ) -> Result<(), FirmwareUpdaterError> {
814 flash.read(self.state.from as u32, aligned).await?;
815
816 if aligned.iter().any(|&b| b != magic) {
817 aligned.fill(0);
818
819 flash.write(self.state.from as u32, aligned).await?;
820 flash.erase(self.state.from as u32, self.state.to as u32).await?;
821
822 aligned.fill(magic);
823 flash.write(self.state.from as u32, aligned).await?;
824 }
825 Ok(())
826 }
827
828 /// Write data to a flash page.
829 ///
830 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
831 ///
832 /// # Safety
833 ///
834 /// Failing to meet alignment and size requirements may result in a panic.
835 #[cfg(feature = "nightly")]
836 pub async fn write_firmware<F: AsyncNorFlash>(
837 &mut self,
838 offset: usize,
839 data: &[u8],
840 flash: &mut F,
841 block_size: usize,
842 ) -> Result<(), FirmwareUpdaterError> {
843 assert!(data.len() >= F::ERASE_SIZE);
844
845 flash
846 .erase(
847 (self.dfu.from + offset) as u32,
848 (self.dfu.from + offset + data.len()) as u32,
849 )
850 .await?;
851
852 trace!(
853 "Erased from {} to {}",
854 self.dfu.from + offset,
855 self.dfu.from + offset + data.len()
856 );
857
858 FirmwareWriter(self.dfu)
859 .write_block(offset, data, flash, block_size)
860 .await?;
861
862 Ok(())
863 }
864
865 /// Prepare for an incoming DFU update by erasing the entire DFU area and
866 /// returning a `FirmwareWriter`.
867 ///
868 /// Using this instead of `write_firmware` allows for an optimized API in
869 /// exchange for added complexity.
870 #[cfg(feature = "nightly")]
871 pub async fn prepare_update<F: AsyncNorFlash>(
872 &mut self,
873 flash: &mut F,
874 ) -> Result<FirmwareWriter, FirmwareUpdaterError> {
875 flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?;
876
877 trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
878
879 Ok(FirmwareWriter(self.dfu))
880 }
881
882 //
883 // Blocking API
884 //
885
886 /// Obtain the current state.
887 ///
888 /// This is useful to check if the bootloader has just done a swap, in order
889 /// to do verifications and self-tests of the new image before calling
890 /// `mark_booted`.
891 pub fn get_state_blocking<F: NorFlash>(
892 &mut self,
893 flash: &mut F,
894 aligned: &mut [u8],
895 ) -> Result<State, FirmwareUpdaterError> {
896 flash.read(self.state.from as u32, aligned)?;
897
898 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
899 Ok(State::Swap)
900 } else {
901 Ok(State::Boot)
902 }
903 }
904
905 /// Verify the DFU given a public key. If there is an error then DO NOT
906 /// proceed with updating the firmware as it must be signed with a
907 /// corresponding private key (otherwise it could be malicious firmware).
908 ///
909 /// Mark to trigger firmware swap on next boot if verify suceeds.
910 ///
911 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
912 /// been generated from a SHA-512 digest of the firmware bytes.
913 ///
914 /// If no signature feature is set then this method will always return a
915 /// signature error.
916 ///
917 /// # Safety
918 ///
919 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
920 /// and written to.
921 #[cfg(feature = "_verify")]
922 pub fn verify_and_mark_updated_blocking<F: NorFlash>(
923 &mut self,
924 _flash: &mut F,
925 _public_key: &[u8],
926 _signature: &[u8],
927 _update_len: usize,
928 _aligned: &mut [u8],
929 ) -> Result<(), FirmwareUpdaterError> {
930 let _end = self.dfu.from + _update_len;
931 let _read_size = _aligned.len();
932
933 assert_eq!(_aligned.len(), F::WRITE_SIZE);
934 assert!(_end <= self.dfu.to);
935
936 #[cfg(feature = "ed25519-dalek")]
937 {
938 use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
939
940 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
941
942 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
943 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
944
945 let mut digest = Sha512::new();
946
947 let mut offset = self.dfu.from;
948 let last_offset = _end / _read_size * _read_size;
949
950 while offset < last_offset {
951 _flash.read(offset as u32, _aligned)?;
952 digest.update(&_aligned);
953 offset += _read_size;
954 }
955
956 let remaining = _end % _read_size;
957
958 if remaining > 0 {
959 _flash.read(last_offset as u32, _aligned)?;
960 digest.update(&_aligned[0..remaining]);
961 }
962
963 public_key
964 .verify(&digest.finalize(), &signature)
965 .map_err(into_signature_error)?
966 }
967 #[cfg(feature = "ed25519-salty")]
968 {
969 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
970 use salty::{PublicKey, Sha512, Signature};
971
972 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
973 FirmwareUpdaterError::Signature(signature::Error::default())
974 }
975
976 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
977 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
978 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
979 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
980
981 let mut digest = Sha512::new();
982
983 let mut offset = self.dfu.from;
984 let last_offset = _end / _read_size * _read_size;
985
986 while offset < last_offset {
987 _flash.read(offset as u32, _aligned)?;
988 digest.update(&_aligned);
989 offset += _read_size;
990 }
991
992 let remaining = _end % _read_size;
993
994 if remaining > 0 {
995 _flash.read(last_offset as u32, _aligned)?;
996 digest.update(&_aligned[0..remaining]);
997 }
998
999 let message = digest.finalize();
1000 let r = public_key.verify(&message, &signature);
1001 trace!(
1002 "Verifying with public key {}, signature {} and message {} yields ok: {}",
1003 public_key.to_bytes(),
1004 signature.to_bytes(),
1005 message,
1006 r.is_ok()
1007 );
1008 r.map_err(into_signature_error)?
1009 }
1010
1011 self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash)
1012 }
1013
1014 /// Mark to trigger firmware swap on next boot.
1015 ///
1016 /// # Safety
1017 ///
1018 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
1019 #[cfg(not(feature = "_verify"))]
1020 pub fn mark_updated_blocking<F: NorFlash>(
1021 &mut self,
1022 flash: &mut F,
1023 aligned: &mut [u8],
1024 ) -> Result<(), FirmwareUpdaterError> {
1025 assert_eq!(aligned.len(), F::WRITE_SIZE);
1026 self.set_magic_blocking(aligned, SWAP_MAGIC, flash)
1027 }
1028
1029 /// Mark firmware boot successful and stop rollback on reset.
1030 ///
1031 /// # Safety
1032 ///
1033 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
1034 pub fn mark_booted_blocking<F: NorFlash>(
1035 &mut self,
1036 flash: &mut F,
1037 aligned: &mut [u8],
1038 ) -> Result<(), FirmwareUpdaterError> {
1039 assert_eq!(aligned.len(), F::WRITE_SIZE);
1040 self.set_magic_blocking(aligned, BOOT_MAGIC, flash)
1041 }
1042
1043 fn set_magic_blocking<F: NorFlash>(
1044 &mut self,
1045 aligned: &mut [u8],
1046 magic: u8,
1047 flash: &mut F,
1048 ) -> Result<(), FirmwareUpdaterError> {
1049 flash.read(self.state.from as u32, aligned)?;
1050
1051 if aligned.iter().any(|&b| b != magic) {
1052 aligned.fill(0);
1053
1054 flash.write(self.state.from as u32, aligned)?;
1055 flash.erase(self.state.from as u32, self.state.to as u32)?;
1056
1057 aligned.fill(magic);
1058 flash.write(self.state.from as u32, aligned)?;
1059 }
1060 Ok(())
1061 }
1062
1063 /// Write data to a flash page.
1064 ///
1065 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
1066 ///
1067 /// # Safety
1068 ///
1069 /// Failing to meet alignment and size requirements may result in a panic.
1070 pub fn write_firmware_blocking<F: NorFlash>(
1071 &mut self,
1072 offset: usize,
1073 data: &[u8],
1074 flash: &mut F,
1075 block_size: usize,
1076 ) -> Result<(), FirmwareUpdaterError> {
1077 assert!(data.len() >= F::ERASE_SIZE);
1078
1079 flash.erase(
1080 (self.dfu.from + offset) as u32,
1081 (self.dfu.from + offset + data.len()) as u32,
1082 )?;
1083
1084 trace!(
1085 "Erased from {} to {}",
1086 self.dfu.from + offset,
1087 self.dfu.from + offset + data.len()
1088 );
1089
1090 FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?;
1091
1092 Ok(())
1093 }
1094
1095 /// Prepare for an incoming DFU update by erasing the entire DFU area and
1096 /// returning a `FirmwareWriter`.
1097 ///
1098 /// Using this instead of `write_firmware_blocking` allows for an optimized
1099 /// API in exchange for added complexity.
1100 pub fn prepare_update_blocking<F: NorFlash>(
1101 &mut self,
1102 flash: &mut F,
1103 ) -> Result<FirmwareWriter, FirmwareUpdaterError> {
1104 flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?;
1105
1106 trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
1107
1108 Ok(FirmwareWriter(self.dfu))
1109 }
1110}
1111
1112/// FirmwareWriter allows writing blocks to an already erased flash.
1113pub struct FirmwareWriter(Partition);
1114
1115impl FirmwareWriter {
1116 /// Write data to a flash page.
1117 ///
1118 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
1119 ///
1120 /// # Safety
1121 ///
1122 /// Failing to meet alignment and size requirements may result in a panic.
1123 #[cfg(feature = "nightly")]
1124 pub async fn write_block<F: AsyncNorFlash>(
1125 &mut self,
1126 offset: usize,
1127 data: &[u8],
1128 flash: &mut F,
1129 block_size: usize,
1130 ) -> Result<(), F::Error> {
1131 trace!(
1132 "Writing firmware at offset 0x{:x} len {}",
1133 self.0.from + offset,
1134 data.len()
1135 );
1136
1137 let mut write_offset = self.0.from + offset;
1138 for chunk in data.chunks(block_size) {
1139 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
1140 flash.write(write_offset as u32, chunk).await?;
1141 write_offset += chunk.len();
1142 }
1143 /*
1144 trace!("Wrote data, reading back for verification");
1145
1146 let mut buf: [u8; 4096] = [0; 4096];
1147 let mut data_offset = 0;
1148 let mut read_offset = self.dfu.from + offset;
1149 for chunk in buf.chunks_mut(block_size) {
1150 flash.read(read_offset as u32, chunk).await?;
1151 trace!("Read chunk at {}: {:?}", read_offset, chunk);
1152 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
1153 read_offset += chunk.len();
1154 data_offset += chunk.len();
1155 }
1156 */
1157
1158 Ok(())
1159 }
1160
1161 /// Write data to a flash page.
1162 ///
1163 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
1164 ///
1165 /// # Safety
1166 ///
1167 /// Failing to meet alignment and size requirements may result in a panic.
1168 pub fn write_block_blocking<F: NorFlash>(
1169 &mut self,
1170 offset: usize,
1171 data: &[u8],
1172 flash: &mut F,
1173 block_size: usize,
1174 ) -> Result<(), F::Error> {
1175 trace!(
1176 "Writing firmware at offset 0x{:x} len {}",
1177 self.0.from + offset,
1178 data.len()
1179 );
1180
1181 let mut write_offset = self.0.from + offset;
1182 for chunk in data.chunks(block_size) {
1183 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
1184 flash.write(write_offset as u32, chunk)?;
1185 write_offset += chunk.len();
1186 }
1187 /*
1188 trace!("Wrote data, reading back for verification");
1189
1190 let mut buf: [u8; 4096] = [0; 4096];
1191 let mut data_offset = 0;
1192 let mut read_offset = self.dfu.from + offset;
1193 for chunk in buf.chunks_mut(block_size) {
1194 flash.read(read_offset as u32, chunk).await?;
1195 trace!("Read chunk at {}: {:?}", read_offset, chunk);
1196 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
1197 read_offset += chunk.len();
1198 data_offset += chunk.len();
1199 }
1200 */
1201
1202 Ok(())
1203 }
1204}
1205
1206#[cfg(test)] 47#[cfg(test)]
1207mod tests { 48mod tests {
1208 use core::convert::Infallible;
1209
1210 use embedded_storage::nor_flash::ErrorType;
1211 use embedded_storage_async::nor_flash::ReadNorFlash as AsyncReadNorFlash;
1212 use futures::executor::block_on; 49 use futures::executor::block_on;
1213 50
1214 use super::*; 51 use super::*;
52 use crate::mem_flash::MemFlash;
1215 53
1216 /* 54 /*
1217 #[test] 55 #[test]
@@ -1234,18 +72,14 @@ mod tests {
1234 const ACTIVE: Partition = Partition::new(4096, 61440); 72 const ACTIVE: Partition = Partition::new(4096, 61440);
1235 const DFU: Partition = Partition::new(61440, 122880); 73 const DFU: Partition = Partition::new(61440, 122880);
1236 74
1237 let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); 75 let mut flash = MemFlash::<131072, 4096, 4>::default();
1238 flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); 76 flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
1239 let mut flash = SingleFlashConfig::new(&mut flash); 77 let mut flash = SingleFlashConfig::new(&mut flash);
1240 78
1241 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); 79 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
1242 80
1243 let mut magic = [0; 4];
1244 let mut page = [0; 4096]; 81 let mut page = [0; 4096];
1245 assert_eq!( 82 assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap());
1246 State::Boot,
1247 bootloader.prepare_boot(&mut flash, &mut magic, &mut page).unwrap()
1248 );
1249 } 83 }
1250 84
1251 #[test] 85 #[test]
@@ -1254,66 +88,49 @@ mod tests {
1254 const STATE: Partition = Partition::new(0, 4096); 88 const STATE: Partition = Partition::new(0, 4096);
1255 const ACTIVE: Partition = Partition::new(4096, 61440); 89 const ACTIVE: Partition = Partition::new(4096, 61440);
1256 const DFU: Partition = Partition::new(61440, 122880); 90 const DFU: Partition = Partition::new(61440, 122880);
1257 let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); 91 let mut flash = MemFlash::<131072, 4096, 4>::random();
1258 92
1259 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 93 let original = [rand::random::<u8>(); ACTIVE.size() as usize];
1260 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 94 let update = [rand::random::<u8>(); ACTIVE.size() as usize];
1261 let mut aligned = [0; 4]; 95 let mut aligned = [0; 4];
1262 96
1263 for i in ACTIVE.from..ACTIVE.to { 97 flash.program(ACTIVE.from, &original).unwrap();
1264 flash.0[i] = original[i - ACTIVE.from];
1265 }
1266 98
1267 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); 99 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
1268 let mut updater = FirmwareUpdater::new(DFU, STATE); 100 let mut updater = FirmwareUpdater::new(DFU, STATE);
1269 let mut offset = 0; 101 block_on(updater.write_firmware(0, &update, &mut flash)).unwrap();
1270 for chunk in update.chunks(4096) {
1271 block_on(updater.write_firmware(offset, chunk, &mut flash, 4096)).unwrap();
1272 offset += chunk.len();
1273 }
1274 block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); 102 block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
1275 103
1276 let mut magic = [0; 4]; 104 let mut page = [0; 1024];
1277 let mut page = [0; 4096];
1278 assert_eq!( 105 assert_eq!(
1279 State::Swap, 106 State::Swap,
1280 bootloader 107 bootloader
1281 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) 108 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
1282 .unwrap() 109 .unwrap()
1283 ); 110 );
1284 111
1285 for i in ACTIVE.from..ACTIVE.to { 112 flash.assert_eq(ACTIVE.from, &update);
1286 assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i);
1287 }
1288
1289 // First DFU page is untouched 113 // First DFU page is untouched
1290 for i in DFU.from + 4096..DFU.to { 114 flash.assert_eq(DFU.from + 4096, &original);
1291 assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i);
1292 }
1293 115
1294 // Running again should cause a revert 116 // Running again should cause a revert
1295 assert_eq!( 117 assert_eq!(
1296 State::Swap, 118 State::Swap,
1297 bootloader 119 bootloader
1298 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) 120 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
1299 .unwrap() 121 .unwrap()
1300 ); 122 );
1301 123
1302 for i in ACTIVE.from..ACTIVE.to { 124 flash.assert_eq(ACTIVE.from, &original);
1303 assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i);
1304 }
1305
1306 // Last page is untouched 125 // Last page is untouched
1307 for i in DFU.from..DFU.to - 4096 { 126 flash.assert_eq(DFU.from, &update);
1308 assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i);
1309 }
1310 127
1311 // Mark as booted 128 // Mark as booted
1312 block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap(); 129 block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap();
1313 assert_eq!( 130 assert_eq!(
1314 State::Boot, 131 State::Boot,
1315 bootloader 132 bootloader
1316 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page) 133 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
1317 .unwrap() 134 .unwrap()
1318 ); 135 );
1319 } 136 }
@@ -1325,50 +142,34 @@ mod tests {
1325 const ACTIVE: Partition = Partition::new(4096, 16384); 142 const ACTIVE: Partition = Partition::new(4096, 16384);
1326 const DFU: Partition = Partition::new(0, 16384); 143 const DFU: Partition = Partition::new(0, 16384);
1327 144
1328 let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); 145 let mut active = MemFlash::<16384, 4096, 8>::random();
1329 let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); 146 let mut dfu = MemFlash::<16384, 2048, 8>::random();
1330 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); 147 let mut state = MemFlash::<4096, 128, 4>::random();
1331 let mut aligned = [0; 4]; 148 let mut aligned = [0; 4];
1332 149
1333 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 150 let original = [rand::random::<u8>(); ACTIVE.size() as usize];
1334 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 151 let update = [rand::random::<u8>(); ACTIVE.size() as usize];
1335 152
1336 for i in ACTIVE.from..ACTIVE.to { 153 active.program(ACTIVE.from, &original).unwrap();
1337 active.0[i] = original[i - ACTIVE.from];
1338 }
1339 154
1340 let mut updater = FirmwareUpdater::new(DFU, STATE); 155 let mut updater = FirmwareUpdater::new(DFU, STATE);
1341 156
1342 let mut offset = 0; 157 block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
1343 for chunk in update.chunks(2048) {
1344 block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap();
1345 offset += chunk.len();
1346 }
1347 block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); 158 block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
1348 159
1349 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); 160 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
1350 let mut magic = [0; 4];
1351 let mut page = [0; 4096]; 161 let mut page = [0; 4096];
1352 162
1353 assert_eq!( 163 assert_eq!(
1354 State::Swap, 164 State::Swap,
1355 bootloader 165 bootloader
1356 .prepare_boot( 166 .prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page)
1357 &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu),
1358 &mut magic,
1359 &mut page
1360 )
1361 .unwrap() 167 .unwrap()
1362 ); 168 );
1363 169
1364 for i in ACTIVE.from..ACTIVE.to { 170 active.assert_eq(ACTIVE.from, &update);
1365 assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i);
1366 }
1367
1368 // First DFU page is untouched 171 // First DFU page is untouched
1369 for i in DFU.from + 4096..DFU.to { 172 dfu.assert_eq(DFU.from + 4096, &original);
1370 assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i);
1371 }
1372 } 173 }
1373 174
1374 #[test] 175 #[test]
@@ -1379,57 +180,35 @@ mod tests {
1379 const DFU: Partition = Partition::new(0, 16384); 180 const DFU: Partition = Partition::new(0, 16384);
1380 181
1381 let mut aligned = [0; 4]; 182 let mut aligned = [0; 4];
1382 let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); 183 let mut active = MemFlash::<16384, 2048, 4>::random();
1383 let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); 184 let mut dfu = MemFlash::<16384, 4096, 8>::random();
1384 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); 185 let mut state = MemFlash::<4096, 128, 4>::random();
1385 186
1386 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 187 let original = [rand::random::<u8>(); ACTIVE.size() as usize];
1387 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 188 let update = [rand::random::<u8>(); ACTIVE.size() as usize];
1388 189
1389 for i in ACTIVE.from..ACTIVE.to { 190 active.program(ACTIVE.from, &original).unwrap();
1390 active.0[i] = original[i - ACTIVE.from];
1391 }
1392 191
1393 let mut updater = FirmwareUpdater::new(DFU, STATE); 192 let mut updater = FirmwareUpdater::new(DFU, STATE);
1394 193
1395 let mut offset = 0; 194 block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
1396 for chunk in update.chunks(4096) {
1397 block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap();
1398 offset += chunk.len();
1399 }
1400 block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); 195 block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
1401 196
1402 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); 197 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
1403 let mut magic = [0; 4];
1404 let mut page = [0; 4096]; 198 let mut page = [0; 4096];
1405 assert_eq!( 199 assert_eq!(
1406 State::Swap, 200 State::Swap,
1407 bootloader 201 bootloader
1408 .prepare_boot( 202 .prepare_boot(
1409 &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,), 203 &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,),
1410 &mut magic,
1411 &mut page 204 &mut page
1412 ) 205 )
1413 .unwrap() 206 .unwrap()
1414 ); 207 );
1415 208
1416 for i in ACTIVE.from..ACTIVE.to { 209 active.assert_eq(ACTIVE.from, &update);
1417 assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i);
1418 }
1419
1420 // First DFU page is untouched 210 // First DFU page is untouched
1421 for i in DFU.from + 4096..DFU.to { 211 dfu.assert_eq(DFU.from + 4096, &original);
1422 assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i);
1423 }
1424 }
1425
1426 #[test]
1427 #[should_panic]
1428 fn test_range_asserts() {
1429 const ACTIVE: Partition = Partition::new(4096, 4194304);
1430 const DFU: Partition = Partition::new(4194304, 2 * 4194304);
1431 const STATE: Partition = Partition::new(0, 4096);
1432 assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
1433 } 212 }
1434 213
1435 #[test] 214 #[test]
@@ -1458,13 +237,13 @@ mod tests {
1458 237
1459 const STATE: Partition = Partition::new(0, 4096); 238 const STATE: Partition = Partition::new(0, 4096);
1460 const DFU: Partition = Partition::new(4096, 8192); 239 const DFU: Partition = Partition::new(4096, 8192);
1461 let mut flash = MemFlash::<8192, 4096, 4>([0xff; 8192]); 240 let mut flash = MemFlash::<8192, 4096, 4>::default();
1462 241
1463 let firmware_len = firmware.len(); 242 let firmware_len = firmware.len();
1464 243
1465 let mut write_buf = [0; 4096]; 244 let mut write_buf = [0; 4096];
1466 write_buf[0..firmware_len].copy_from_slice(firmware); 245 write_buf[0..firmware_len].copy_from_slice(firmware);
1467 NorFlash::write(&mut flash, DFU.from as u32, &write_buf).unwrap(); 246 DFU.write_blocking(&mut flash, 0, &write_buf).unwrap();
1468 247
1469 // On with the test 248 // On with the test
1470 249
@@ -1476,117 +255,9 @@ mod tests {
1476 &mut flash, 255 &mut flash,
1477 &public_key.to_bytes(), 256 &public_key.to_bytes(),
1478 &signature.to_bytes(), 257 &signature.to_bytes(),
1479 firmware_len, 258 firmware_len as u32,
1480 &mut aligned, 259 &mut aligned,
1481 )) 260 ))
1482 .is_ok()); 261 .is_ok());
1483 } 262 }
1484 struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize>([u8; SIZE]);
1485
1486 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
1487 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
1488 {
1489 const WRITE_SIZE: usize = WRITE_SIZE;
1490 const ERASE_SIZE: usize = ERASE_SIZE;
1491 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
1492 let from = from as usize;
1493 let to = to as usize;
1494 assert!(from % ERASE_SIZE == 0);
1495 assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
1496 for i in from..to {
1497 self.0[i] = 0xFF;
1498 }
1499 Ok(())
1500 }
1501
1502 fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
1503 assert!(data.len() % WRITE_SIZE == 0);
1504 assert!(offset as usize % WRITE_SIZE == 0);
1505 assert!(offset as usize + data.len() <= SIZE);
1506
1507 self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
1508
1509 Ok(())
1510 }
1511 }
1512
1513 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
1514 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
1515 {
1516 type Error = Infallible;
1517 }
1518
1519 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
1520 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
1521 {
1522 const READ_SIZE: usize = 1;
1523
1524 fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
1525 let len = buf.len();
1526 buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);
1527 Ok(())
1528 }
1529
1530 fn capacity(&self) -> usize {
1531 SIZE
1532 }
1533 }
1534
1535 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> super::Flash
1536 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
1537 {
1538 const BLOCK_SIZE: usize = ERASE_SIZE;
1539 const ERASE_VALUE: u8 = 0xFF;
1540 }
1541
1542 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
1543 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
1544 {
1545 const READ_SIZE: usize = 1;
1546
1547 async fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
1548 let len = buf.len();
1549 buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);
1550 Ok(())
1551 }
1552
1553 fn capacity(&self) -> usize {
1554 SIZE
1555 }
1556 }
1557
1558 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
1559 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
1560 {
1561 const WRITE_SIZE: usize = WRITE_SIZE;
1562 const ERASE_SIZE: usize = ERASE_SIZE;
1563
1564 async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
1565 let from = from as usize;
1566 let to = to as usize;
1567 assert!(from % ERASE_SIZE == 0);
1568 assert!(to % ERASE_SIZE == 0);
1569 for i in from..to {
1570 self.0[i] = 0xFF;
1571 }
1572 Ok(())
1573 }
1574
1575 async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
1576 info!("Writing {} bytes to 0x{:x}", data.len(), offset);
1577 assert!(data.len() % WRITE_SIZE == 0);
1578 assert!(offset as usize % WRITE_SIZE == 0);
1579 assert!(
1580 offset as usize + data.len() <= SIZE,
1581 "OFFSET: {}, LEN: {}, FLASH SIZE: {}",
1582 offset,
1583 data.len(),
1584 SIZE
1585 );
1586
1587 self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
1588
1589 Ok(())
1590 }
1591 }
1592} 263}
diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs
new file mode 100644
index 000000000..c62379b24
--- /dev/null
+++ b/embassy-boot/boot/src/mem_flash.rs
@@ -0,0 +1,164 @@
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 pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
37 let offset = offset as usize;
38 assert!(bytes.len() % WRITE_SIZE == 0);
39 assert!(offset % WRITE_SIZE == 0);
40 assert!(offset + bytes.len() <= SIZE);
41
42 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
43
44 Ok(())
45 }
46
47 pub fn assert_eq(&self, offset: u32, expectation: &[u8]) {
48 for i in 0..expectation.len() {
49 assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i);
50 }
51 }
52}
53
54impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
55 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
56{
57 fn default() -> Self {
58 Self::new(0xFF)
59 }
60}
61
62impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
63 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
64{
65 type Error = MemFlashError;
66}
67
68impl NorFlashError for MemFlashError {
69 fn kind(&self) -> NorFlashErrorKind {
70 NorFlashErrorKind::Other
71 }
72}
73
74impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
75 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
76{
77 const READ_SIZE: usize = 1;
78
79 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
80 let len = bytes.len();
81 bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
82 Ok(())
83 }
84
85 fn capacity(&self) -> usize {
86 SIZE
87 }
88}
89
90impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
91 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
92{
93 const WRITE_SIZE: usize = WRITE_SIZE;
94 const ERASE_SIZE: usize = ERASE_SIZE;
95
96 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
97 let from = from as usize;
98 let to = to as usize;
99 assert!(from % ERASE_SIZE == 0);
100 assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
101 for i in from..to {
102 self.mem[i] = 0xFF;
103 }
104 Ok(())
105 }
106
107 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
108 let offset = offset as usize;
109 assert!(bytes.len() % WRITE_SIZE == 0);
110 assert!(offset % WRITE_SIZE == 0);
111 assert!(offset + bytes.len() <= SIZE);
112
113 if let Some(pending_successes) = self.pending_write_successes {
114 if pending_successes > 0 {
115 self.pending_write_successes = Some(pending_successes - 1);
116 } else {
117 return Err(MemFlashError);
118 }
119 }
120
121 for ((offset, mem_byte), new_byte) in self
122 .mem
123 .iter_mut()
124 .enumerate()
125 .skip(offset)
126 .take(bytes.len())
127 .zip(bytes)
128 {
129 assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
130 *mem_byte = *new_byte;
131 }
132
133 Ok(())
134 }
135}
136
137impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
138 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
139{
140 const READ_SIZE: usize = 1;
141
142 async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
143 <Self as ReadNorFlash>::read(self, offset, bytes)
144 }
145
146 fn capacity(&self) -> usize {
147 <Self as ReadNorFlash>::capacity(self)
148 }
149}
150
151impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
152 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
153{
154 const WRITE_SIZE: usize = WRITE_SIZE;
155 const ERASE_SIZE: usize = ERASE_SIZE;
156
157 async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
158 <Self as NorFlash>::erase(self, from, to)
159 }
160
161 async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
162 <Self as NorFlash>::write(self, offset, bytes)
163 }
164}
diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs
new file mode 100644
index 000000000..7529059b6
--- /dev/null
+++ b/embassy-boot/boot/src/partition.rs
@@ -0,0 +1,139 @@
1use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
2use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
3
4/// A region in flash used by the bootloader.
5#[derive(Copy, Clone, Debug)]
6#[cfg_attr(feature = "defmt", derive(defmt::Format))]
7pub struct Partition {
8 /// The offset into the flash where the partition starts.
9 pub from: u32,
10 /// The offset into the flash where the partition ends.
11 pub to: u32,
12}
13
14impl Partition {
15 /// Create a new partition with the provided range
16 pub const fn new(from: u32, to: u32) -> Self {
17 Self { from, to }
18 }
19
20 /// Return the size of the partition
21 pub const fn size(&self) -> u32 {
22 self.to - self.from
23 }
24
25 /// Read from the partition on the provided flash
26 pub async fn read<F: AsyncReadNorFlash>(
27 &self,
28 flash: &mut F,
29 offset: u32,
30 bytes: &mut [u8],
31 ) -> Result<(), F::Error> {
32 let offset = self.from as u32 + offset;
33 flash.read(offset, bytes).await
34 }
35
36 /// Write to the partition on the provided flash
37 pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
38 let offset = self.from as u32 + offset;
39 flash.write(offset, bytes).await?;
40 trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
41 Ok(())
42 }
43
44 /// Erase part of the partition on the provided flash
45 pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
46 let from = self.from as u32 + from;
47 let to = self.from as u32 + to;
48 flash.erase(from, to).await?;
49 trace!("Erased from 0x{:x} to 0x{:x}", from, to);
50 Ok(())
51 }
52
53 /// Erase the entire partition
54 pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
55 let from = self.from as u32;
56 let to = self.to as u32;
57 flash.erase(from, to).await?;
58 trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
59 Ok(())
60 }
61
62 /// Read from the partition on the provided flash
63 pub fn read_blocking<F: ReadNorFlash>(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> {
64 let offset = self.from as u32 + offset;
65 flash.read(offset, bytes)
66 }
67
68 /// Write to the partition on the provided flash
69 pub fn write_blocking<F: NorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
70 let offset = self.from as u32 + offset;
71 flash.write(offset, bytes)?;
72 trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
73 Ok(())
74 }
75
76 /// Erase part of the partition on the provided flash
77 pub fn erase_blocking<F: NorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
78 let from = self.from as u32 + from;
79 let to = self.from as u32 + to;
80 flash.erase(from, to)?;
81 trace!("Erased from 0x{:x} to 0x{:x}", from, to);
82 Ok(())
83 }
84
85 /// Erase the entire partition
86 pub(crate) fn wipe_blocking<F: NorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
87 let from = self.from as u32;
88 let to = self.to as u32;
89 flash.erase(from, to)?;
90 trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
91 Ok(())
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use crate::mem_flash::MemFlash;
98 use crate::Partition;
99
100 #[test]
101 fn can_erase() {
102 let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
103 let partition = Partition::new(256, 512);
104
105 partition.erase_blocking(&mut flash, 64, 192).unwrap();
106
107 for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) {
108 assert_eq!(0x00, byte, "Index {}", index);
109 }
110
111 for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) {
112 assert_eq!(0xFF, byte, "Index {}", index);
113 }
114
115 for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) {
116 assert_eq!(0x00, byte, "Index {}", index);
117 }
118 }
119
120 #[test]
121 fn can_wipe() {
122 let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
123 let partition = Partition::new(256, 512);
124
125 partition.wipe_blocking(&mut flash).unwrap();
126
127 for (index, byte) in flash.mem.iter().copied().enumerate().take(256) {
128 assert_eq!(0x00, byte, "Index {}", index);
129 }
130
131 for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) {
132 assert_eq!(0xFF, byte, "Index {}", index);
133 }
134
135 for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) {
136 assert_eq!(0x00, byte, "Index {}", index);
137 }
138 }
139}
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs
index 5cc6ba448..48bbd7e2a 100644
--- a/embassy-boot/nrf/src/lib.rs
+++ b/embassy-boot/nrf/src/lib.rs
@@ -11,13 +11,12 @@ use embassy_nrf::wdt;
11use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; 11use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
12 12
13/// A bootloader for nRF devices. 13/// A bootloader for nRF devices.
14pub struct BootLoader { 14pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> {
15 boot: embassy_boot::BootLoader, 15 boot: embassy_boot::BootLoader,
16 magic: AlignedBuffer<4>, 16 aligned_buf: AlignedBuffer<BUFFER_SIZE>,
17 page: AlignedBuffer<PAGE_SIZE>,
18} 17}
19 18
20impl Default for BootLoader { 19impl Default for BootLoader<PAGE_SIZE> {
21 /// Create a new bootloader instance using parameters from linker script 20 /// Create a new bootloader instance using parameters from linker script
22 fn default() -> Self { 21 fn default() -> Self {
23 extern "C" { 22 extern "C" {
@@ -31,20 +30,20 @@ impl Default for BootLoader {
31 30
32 let active = unsafe { 31 let active = unsafe {
33 Partition::new( 32 Partition::new(
34 &__bootloader_active_start as *const u32 as usize, 33 &__bootloader_active_start as *const u32 as u32,
35 &__bootloader_active_end as *const u32 as usize, 34 &__bootloader_active_end as *const u32 as u32,
36 ) 35 )
37 }; 36 };
38 let dfu = unsafe { 37 let dfu = unsafe {
39 Partition::new( 38 Partition::new(
40 &__bootloader_dfu_start as *const u32 as usize, 39 &__bootloader_dfu_start as *const u32 as u32,
41 &__bootloader_dfu_end as *const u32 as usize, 40 &__bootloader_dfu_end as *const u32 as u32,
42 ) 41 )
43 }; 42 };
44 let state = unsafe { 43 let state = unsafe {
45 Partition::new( 44 Partition::new(
46 &__bootloader_state_start as *const u32 as usize, 45 &__bootloader_state_start as *const u32 as u32,
47 &__bootloader_state_end as *const u32 as usize, 46 &__bootloader_state_end as *const u32 as u32,
48 ) 47 )
49 }; 48 };
50 49
@@ -56,20 +55,19 @@ impl Default for BootLoader {
56 } 55 }
57} 56}
58 57
59impl BootLoader { 58impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
60 /// Create a new bootloader instance using the supplied partitions for active, dfu and state. 59 /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
61 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { 60 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
62 Self { 61 Self {
63 boot: embassy_boot::BootLoader::new(active, dfu, state), 62 boot: embassy_boot::BootLoader::new(active, dfu, state),
64 magic: AlignedBuffer([0; 4]), 63 aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
65 page: AlignedBuffer([0; PAGE_SIZE]),
66 } 64 }
67 } 65 }
68 66
69 /// Inspect the bootloader state and perform actions required before booting, such as swapping 67 /// Inspect the bootloader state and perform actions required before booting, such as swapping
70 /// firmware. 68 /// firmware.
71 pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize { 69 pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
72 match self.boot.prepare_boot(flash, &mut self.magic.0, &mut self.page.0) { 70 match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) {
73 Ok(_) => self.boot.boot_address(), 71 Ok(_) => self.boot.boot_address(),
74 Err(_) => panic!("boot prepare error!"), 72 Err(_) => panic!("boot prepare error!"),
75 } 73 }
diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs
index 6df34133e..c3cb22299 100644
--- a/embassy-boot/rp/src/lib.rs
+++ b/embassy-boot/rp/src/lib.rs
@@ -5,33 +5,31 @@
5mod fmt; 5mod fmt;
6 6
7pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; 7pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
8use embassy_rp::flash::{Flash, ERASE_SIZE, WRITE_SIZE}; 8use embassy_rp::flash::{Flash, ERASE_SIZE};
9use embassy_rp::peripherals::{FLASH, WATCHDOG}; 9use embassy_rp::peripherals::{FLASH, WATCHDOG};
10use embassy_rp::watchdog::Watchdog; 10use embassy_rp::watchdog::Watchdog;
11use embassy_time::Duration; 11use embassy_time::Duration;
12use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; 12use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
13 13
14/// A bootloader for RP2040 devices. 14/// A bootloader for RP2040 devices.
15pub struct BootLoader { 15pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
16 boot: embassy_boot::BootLoader, 16 boot: embassy_boot::BootLoader,
17 magic: AlignedBuffer<WRITE_SIZE>, 17 aligned_buf: AlignedBuffer<BUFFER_SIZE>,
18 page: AlignedBuffer<ERASE_SIZE>,
19} 18}
20 19
21impl BootLoader { 20impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
22 /// Create a new bootloader instance using the supplied partitions for active, dfu and state. 21 /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
23 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { 22 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
24 Self { 23 Self {
25 boot: embassy_boot::BootLoader::new(active, dfu, state), 24 boot: embassy_boot::BootLoader::new(active, dfu, state),
26 magic: AlignedBuffer([0; WRITE_SIZE]), 25 aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
27 page: AlignedBuffer([0; ERASE_SIZE]),
28 } 26 }
29 } 27 }
30 28
31 /// Inspect the bootloader state and perform actions required before booting, such as swapping 29 /// Inspect the bootloader state and perform actions required before booting, such as swapping
32 /// firmware. 30 /// firmware.
33 pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize { 31 pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
34 match self.boot.prepare_boot(flash, self.magic.as_mut(), self.page.as_mut()) { 32 match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
35 Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(), 33 Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(),
36 Err(_) => panic!("boot prepare error!"), 34 Err(_) => panic!("boot prepare error!"),
37 } 35 }
@@ -54,7 +52,7 @@ impl BootLoader {
54 } 52 }
55} 53}
56 54
57impl Default for BootLoader { 55impl Default for BootLoader<ERASE_SIZE> {
58 /// Create a new bootloader instance using parameters from linker script 56 /// Create a new bootloader instance using parameters from linker script
59 fn default() -> Self { 57 fn default() -> Self {
60 extern "C" { 58 extern "C" {
@@ -68,20 +66,20 @@ impl Default for BootLoader {
68 66
69 let active = unsafe { 67 let active = unsafe {
70 Partition::new( 68 Partition::new(
71 &__bootloader_active_start as *const u32 as usize, 69 &__bootloader_active_start as *const u32 as u32,
72 &__bootloader_active_end as *const u32 as usize, 70 &__bootloader_active_end as *const u32 as u32,
73 ) 71 )
74 }; 72 };
75 let dfu = unsafe { 73 let dfu = unsafe {
76 Partition::new( 74 Partition::new(
77 &__bootloader_dfu_start as *const u32 as usize, 75 &__bootloader_dfu_start as *const u32 as u32,
78 &__bootloader_dfu_end as *const u32 as usize, 76 &__bootloader_dfu_end as *const u32 as u32,
79 ) 77 )
80 }; 78 };
81 let state = unsafe { 79 let state = unsafe {
82 Partition::new( 80 Partition::new(
83 &__bootloader_state_start as *const u32 as usize, 81 &__bootloader_state_start as *const u32 as u32,
84 &__bootloader_state_end as *const u32 as usize, 82 &__bootloader_state_end as *const u32 as u32,
85 ) 83 )
86 }; 84 };
87 85
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
index 82f712c4d..94404697f 100644
--- a/embassy-boot/stm32/src/lib.rs
+++ b/embassy-boot/stm32/src/lib.rs
@@ -7,26 +7,24 @@ mod fmt;
7pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; 7pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
8 8
9/// A bootloader for STM32 devices. 9/// A bootloader for STM32 devices.
10pub struct BootLoader<const PAGE_SIZE: usize, const WRITE_SIZE: usize> { 10pub struct BootLoader<const BUFFER_SIZE: usize> {
11 boot: embassy_boot::BootLoader, 11 boot: embassy_boot::BootLoader,
12 magic: AlignedBuffer<WRITE_SIZE>, 12 aligned_buf: AlignedBuffer<BUFFER_SIZE>,
13 page: AlignedBuffer<PAGE_SIZE>,
14} 13}
15 14
16impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRITE_SIZE> { 15impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
17 /// Create a new bootloader instance using the supplied partitions for active, dfu and state. 16 /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
18 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { 17 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
19 Self { 18 Self {
20 boot: embassy_boot::BootLoader::new(active, dfu, state), 19 boot: embassy_boot::BootLoader::new(active, dfu, state),
21 magic: AlignedBuffer([0; WRITE_SIZE]), 20 aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
22 page: AlignedBuffer([0; PAGE_SIZE]),
23 } 21 }
24 } 22 }
25 23
26 /// Inspect the bootloader state and perform actions required before booting, such as swapping 24 /// Inspect the bootloader state and perform actions required before booting, such as swapping
27 /// firmware. 25 /// firmware.
28 pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize { 26 pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
29 match self.boot.prepare_boot(flash, self.magic.as_mut(), self.page.as_mut()) { 27 match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
30 Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), 28 Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(),
31 Err(_) => panic!("boot prepare error!"), 29 Err(_) => panic!("boot prepare error!"),
32 } 30 }
@@ -49,7 +47,7 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> BootLoader<PAGE_SIZE, WRIT
49 } 47 }
50} 48}
51 49
52impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> Default for BootLoader<PAGE_SIZE, WRITE_SIZE> { 50impl<const BUFFER_SIZE: usize> Default for BootLoader<BUFFER_SIZE> {
53 /// Create a new bootloader instance using parameters from linker script 51 /// Create a new bootloader instance using parameters from linker script
54 fn default() -> Self { 52 fn default() -> Self {
55 extern "C" { 53 extern "C" {
@@ -63,20 +61,20 @@ impl<const PAGE_SIZE: usize, const WRITE_SIZE: usize> Default for BootLoader<PAG
63 61
64 let active = unsafe { 62 let active = unsafe {
65 Partition::new( 63 Partition::new(
66 &__bootloader_active_start as *const u32 as usize, 64 &__bootloader_active_start as *const u32 as u32,
67 &__bootloader_active_end as *const u32 as usize, 65 &__bootloader_active_end as *const u32 as u32,
68 ) 66 )
69 }; 67 };
70 let dfu = unsafe { 68 let dfu = unsafe {
71 Partition::new( 69 Partition::new(
72 &__bootloader_dfu_start as *const u32 as usize, 70 &__bootloader_dfu_start as *const u32 as u32,
73 &__bootloader_dfu_end as *const u32 as usize, 71 &__bootloader_dfu_end as *const u32 as u32,
74 ) 72 )
75 }; 73 };
76 let state = unsafe { 74 let state = unsafe {
77 Partition::new( 75 Partition::new(
78 &__bootloader_state_start as *const u32 as usize, 76 &__bootloader_state_start as *const u32 as u32,
79 &__bootloader_state_end as *const u32 as usize, 77 &__bootloader_state_end as *const u32 as u32,
80 ) 78 )
81 }; 79 };
82 80