aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/boot/src/lib.rs
diff options
context:
space:
mode:
authorRasmus Melchior Jacobsen <[email protected]>2023-03-31 08:05:37 +0200
committerRasmus Melchior Jacobsen <[email protected]>2023-03-31 08:05:37 +0200
commit373760a56b1bad13ebcec19247ee3ee4ae4cb07c (patch)
tree60122df250ce6a777b723b4d3b3fd86e0f7d2f99 /embassy-boot/boot/src/lib.rs
parent5955d813745e409cecadb1f36cca7c3522e8d915 (diff)
Split bootloader implementation into multiple files
Diffstat (limited to 'embassy-boot/boot/src/lib.rs')
-rw-r--r--embassy-boot/boot/src/lib.rs1183
1 files changed, 12 insertions, 1171 deletions
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 0df44f36e..a2259411f 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -5,34 +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;
9use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; 9mod firmware_updater;
10mod firmware_writer;
11mod partition;
10 12
11const BOOT_MAGIC: u8 = 0xD0; 13pub use boot_loader::{BootError, BootFlash, BootLoader, Flash, FlashConfig, MultiFlashConfig, SingleFlashConfig};
12const SWAP_MAGIC: u8 = 0xF0; 14pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError};
15pub use firmware_writer::FirmwareWriter;
16pub use partition::Partition;
13 17
14/// A region in flash used by the bootloader. 18pub(crate) const BOOT_MAGIC: u8 = 0xD0;
15#[derive(Copy, Clone, Debug)] 19pub(crate) const SWAP_MAGIC: u8 = 0xF0;
16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
17pub struct Partition {
18 /// Start of the flash region.
19 pub from: usize,
20 /// End of the flash region.
21 pub to: usize,
22}
23
24impl Partition {
25 /// Create a new partition with the provided range
26 pub const fn new(from: usize, to: usize) -> Self {
27 Self { from, to }
28 }
29
30 /// Return the length of the partition
31 #[allow(clippy::len_without_is_empty)]
32 pub const fn len(&self) -> usize {
33 self.to - self.from
34 }
35}
36 20
37/// The state of the bootloader after running prepare. 21/// The state of the bootloader after running prepare.
38#[derive(PartialEq, Eq, Debug)] 22#[derive(PartialEq, Eq, Debug)]
@@ -44,34 +28,6 @@ pub enum State {
44 Swap, 28 Swap,
45} 29}
46 30
47/// Errors returned by bootloader
48#[derive(PartialEq, Eq, Debug)]
49pub enum BootError {
50 /// Error from flash.
51 Flash(NorFlashErrorKind),
52 /// Invalid bootloader magic
53 BadMagic,
54}
55
56#[cfg(feature = "defmt")]
57impl defmt::Format for BootError {
58 fn format(&self, fmt: defmt::Formatter) {
59 match self {
60 BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
61 BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
62 }
63 }
64}
65
66impl<E> From<E> for BootError
67where
68 E: NorFlashError,
69{
70 fn from(error: E) -> Self {
71 BootError::Flash(error.kind())
72 }
73}
74
75/// 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.
76#[repr(align(32))] 32#[repr(align(32))]
77pub struct AlignedBuffer<const N: usize>(pub [u8; N]); 33pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
@@ -88,1118 +44,12 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
88 } 44 }
89} 45}
90 46
91/// Extension of the embedded-storage flash type information with block size and erase value.
92pub trait Flash: NorFlash + ReadNorFlash {
93 /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase
94 /// size of the flash, but for external QSPI flash modules, this can be lower.
95 const BLOCK_SIZE: usize;
96 /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value.
97 const ERASE_VALUE: u8 = 0xFF;
98}
99
100/// Trait defining the flash handles used for active and DFU partition
101pub trait FlashConfig {
102 /// Flash type used for the state partition.
103 type STATE: Flash;
104 /// Flash type used for the active partition.
105 type ACTIVE: Flash;
106 /// Flash type used for the dfu partition.
107 type DFU: Flash;
108
109 /// Return flash instance used to write/read to/from active partition.
110 fn active(&mut self) -> &mut Self::ACTIVE;
111 /// Return flash instance used to write/read to/from dfu partition.
112 fn dfu(&mut self) -> &mut Self::DFU;
113 /// Return flash instance used to write/read to/from bootloader state.
114 fn state(&mut self) -> &mut Self::STATE;
115}
116
117/// BootLoader works with any flash implementing embedded_storage and can also work with
118/// different page sizes and flash write sizes.
119pub struct BootLoader {
120 // Page with current state of bootloader. The state partition has the following format:
121 // | Range | Description |
122 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
123 // | WRITE_SIZE - N | Progress index used while swapping or reverting |
124 state: Partition,
125 // Location of the partition which will be booted from
126 active: Partition,
127 // Location of the partition which will be swapped in when requested
128 dfu: Partition,
129}
130
131impl BootLoader {
132 /// Create a new instance of a bootloader with the given partitions.
133 ///
134 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
135 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
136 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
137 Self { active, dfu, state }
138 }
139
140 /// Return the boot address for the active partition.
141 pub fn boot_address(&self) -> usize {
142 self.active.from
143 }
144
145 /// Perform necessary boot preparations like swapping images.
146 ///
147 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
148 /// algorithm to work correctly.
149 ///
150 /// SWAPPING
151 ///
152 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
153 /// The swap index contains the copy progress, as to allow continuation of the copy process on
154 /// power failure. The index counter is represented within 1 or more pages (depending on total
155 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
156 /// contains a zero value. This ensures that index updates can be performed atomically and
157 /// avoid a situation where the wrong index value is set (page write size is "atomic").
158 ///
159 /// +-----------+------------+--------+--------+--------+--------+
160 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
161 /// +-----------+------------+--------+--------+--------+--------+
162 /// | Active | 0 | 1 | 2 | 3 | - |
163 /// | DFU | 0 | 3 | 2 | 1 | X |
164 /// +-----------+------------+--------+--------+--------+--------+
165 ///
166 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
167 /// as follows:
168 ///
169 /// +-----------+------------+--------+--------+--------+--------+
170 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
171 /// +-----------+------------+--------+--------+--------+--------+
172 /// | Active | 1 | 1 | 2 | 1 | - |
173 /// | DFU | 1 | 3 | 2 | 1 | 3 |
174 /// +-----------+------------+--------+--------+--------+--------+
175 ///
176 /// The next iteration performs the same steps
177 ///
178 /// +-----------+------------+--------+--------+--------+--------+
179 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
180 /// +-----------+------------+--------+--------+--------+--------+
181 /// | Active | 2 | 1 | 2 | 1 | - |
182 /// | DFU | 2 | 3 | 2 | 2 | 3 |
183 /// +-----------+------------+--------+--------+--------+--------+
184 ///
185 /// And again until we're done
186 ///
187 /// +-----------+------------+--------+--------+--------+--------+
188 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
189 /// +-----------+------------+--------+--------+--------+--------+
190 /// | Active | 3 | 3 | 2 | 1 | - |
191 /// | DFU | 3 | 3 | 1 | 2 | 3 |
192 /// +-----------+------------+--------+--------+--------+--------+
193 ///
194 /// REVERTING
195 ///
196 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
197 /// the application failed to mark the boot successful. In this case, the revert algorithm will
198 /// run.
199 ///
200 /// The revert index is located separately from the swap index, to ensure that revert can continue
201 /// on power failure.
202 ///
203 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
204 ///
205 /// +-----------+--------------+--------+--------+--------+--------+
206 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
207 //*/
208 /// +-----------+--------------+--------+--------+--------+--------+
209 /// | Active | 3 | 1 | 2 | 1 | - |
210 /// | DFU | 3 | 3 | 1 | 2 | 3 |
211 /// +-----------+--------------+--------+--------+--------+--------+
212 ///
213 ///
214 /// +-----------+--------------+--------+--------+--------+--------+
215 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
216 /// +-----------+--------------+--------+--------+--------+--------+
217 /// | Active | 3 | 1 | 2 | 1 | - |
218 /// | DFU | 3 | 3 | 2 | 2 | 3 |
219 /// +-----------+--------------+--------+--------+--------+--------+
220 ///
221 /// +-----------+--------------+--------+--------+--------+--------+
222 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
223 /// +-----------+--------------+--------+--------+--------+--------+
224 /// | Active | 3 | 1 | 2 | 3 | - |
225 /// | DFU | 3 | 3 | 2 | 1 | 3 |
226 /// +-----------+--------------+--------+--------+--------+--------+
227 ///
228 pub fn prepare_boot<P: FlashConfig>(
229 &mut self,
230 p: &mut P,
231 magic: &mut [u8],
232 page: &mut [u8],
233 ) -> Result<State, BootError> {
234 // Ensure we have enough progress pages to store copy progress
235 assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE);
236 assert_eq!(magic.len(), P::STATE::WRITE_SIZE);
237
238 // Copy contents from partition N to active
239 let state = self.read_state(p, magic)?;
240 if state == State::Swap {
241 //
242 // Check if we already swapped. If we're in the swap state, this means we should revert
243 // since the app has failed to mark boot as successful
244 //
245 if !self.is_swapped(p, magic, page)? {
246 trace!("Swapping");
247 self.swap(p, magic, page)?;
248 trace!("Swapping done");
249 } else {
250 trace!("Reverting");
251 self.revert(p, magic, page)?;
252
253 // Overwrite magic and reset progress
254 let fstate = p.state();
255 magic.fill(!P::STATE::ERASE_VALUE);
256 fstate.write(self.state.from as u32, magic)?;
257 fstate.erase(self.state.from as u32, self.state.to as u32)?;
258
259 magic.fill(BOOT_MAGIC);
260 fstate.write(self.state.from as u32, magic)?;
261 }
262 }
263 Ok(state)
264 }
265
266 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<bool, BootError> {
267 let page_size = page.len();
268 let page_count = self.active.len() / page_size;
269 let progress = self.current_progress(p, magic)?;
270
271 Ok(progress >= page_count * 2)
272 }
273
274 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> {
275 let write_size = aligned.len();
276 let max_index = ((self.state.len() - write_size) / write_size) - 1;
277 aligned.fill(!P::STATE::ERASE_VALUE);
278
279 let flash = config.state();
280 for i in 0..max_index {
281 flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?;
282
283 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) {
284 return Ok(i);
285 }
286 }
287 Ok(max_index)
288 }
289
290 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> {
291 let flash = p.state();
292 let write_size = magic.len();
293 let w = self.state.from + write_size + idx * write_size;
294
295 let aligned = magic;
296 aligned.fill(!P::STATE::ERASE_VALUE);
297 flash.write(w as u32, aligned)?;
298 Ok(())
299 }
300
301 fn active_addr(&self, n: usize, page_size: usize) -> usize {
302 self.active.from + n * page_size
303 }
304
305 fn dfu_addr(&self, n: usize, page_size: usize) -> usize {
306 self.dfu.from + n * page_size
307 }
308
309 fn copy_page_once_to_active<P: FlashConfig>(
310 &mut self,
311 idx: usize,
312 from_page: usize,
313 to_page: usize,
314 p: &mut P,
315 magic: &mut [u8],
316 page: &mut [u8],
317 ) -> Result<(), BootError> {
318 let buf = page;
319 if self.current_progress(p, magic)? <= idx {
320 let mut offset = from_page;
321 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
322 p.dfu().read(offset as u32, chunk)?;
323 offset += chunk.len();
324 }
325
326 p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?;
327
328 let mut offset = to_page;
329 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
330 p.active().write(offset as u32, chunk)?;
331 offset += chunk.len();
332 }
333 self.update_progress(idx, p, magic)?;
334 }
335 Ok(())
336 }
337
338 fn copy_page_once_to_dfu<P: FlashConfig>(
339 &mut self,
340 idx: usize,
341 from_page: usize,
342 to_page: usize,
343 p: &mut P,
344 magic: &mut [u8],
345 page: &mut [u8],
346 ) -> Result<(), BootError> {
347 let buf = page;
348 if self.current_progress(p, magic)? <= idx {
349 let mut offset = from_page;
350 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
351 p.active().read(offset as u32, chunk)?;
352 offset += chunk.len();
353 }
354
355 p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?;
356
357 let mut offset = to_page;
358 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
359 p.dfu().write(offset as u32, chunk)?;
360 offset += chunk.len();
361 }
362 self.update_progress(idx, p, magic)?;
363 }
364 Ok(())
365 }
366
367 fn swap<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
368 let page_size = page.len();
369 let page_count = self.active.len() / page_size;
370 trace!("Page count: {}", page_count);
371 for page_num in 0..page_count {
372 trace!("COPY PAGE {}", page_num);
373 // Copy active page to the 'next' DFU page.
374 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
375 let dfu_page = self.dfu_addr(page_count - page_num, page_size);
376 //trace!("Copy active {} to dfu {}", active_page, dfu_page);
377 self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?;
378
379 // Copy DFU page to the active page
380 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
381 let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size);
382 //trace!("Copy dfy {} to active {}", dfu_page, active_page);
383 self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
384 }
385
386 Ok(())
387 }
388
389 fn revert<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
390 let page_size = page.len();
391 let page_count = self.active.len() / page_size;
392 for page_num in 0..page_count {
393 // Copy the bad active page to the DFU page
394 let active_page = self.active_addr(page_num, page_size);
395 let dfu_page = self.dfu_addr(page_num, page_size);
396 self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?;
397
398 // Copy the DFU page back to the active page
399 let active_page = self.active_addr(page_num, page_size);
400 let dfu_page = self.dfu_addr(page_num + 1, page_size);
401 self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
402 }
403
404 Ok(())
405 }
406
407 fn read_state<P: FlashConfig>(&mut self, config: &mut P, magic: &mut [u8]) -> Result<State, BootError> {
408 let flash = config.state();
409 flash.read(self.state.from as u32, magic)?;
410
411 if !magic.iter().any(|&b| b != SWAP_MAGIC) {
412 Ok(State::Swap)
413 } else {
414 Ok(State::Boot)
415 }
416 }
417}
418
419fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) {
420 assert_eq!(active.len() % page_size, 0);
421 assert_eq!(dfu.len() % page_size, 0);
422 assert!(dfu.len() - active.len() >= page_size);
423 assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size);
424}
425
426/// Convenience provider that uses a single flash for all partitions.
427pub struct SingleFlashConfig<'a, F>
428where
429 F: Flash,
430{
431 flash: &'a mut F,
432}
433
434impl<'a, F> SingleFlashConfig<'a, F>
435where
436 F: Flash,
437{
438 /// Create a provider for a single flash.
439 pub fn new(flash: &'a mut F) -> Self {
440 Self { flash }
441 }
442}
443
444impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
445where
446 F: Flash,
447{
448 type STATE = F;
449 type ACTIVE = F;
450 type DFU = F;
451
452 fn active(&mut self) -> &mut Self::STATE {
453 self.flash
454 }
455 fn dfu(&mut self) -> &mut Self::ACTIVE {
456 self.flash
457 }
458 fn state(&mut self) -> &mut Self::DFU {
459 self.flash
460 }
461}
462
463/// A flash wrapper implementing the Flash and embedded_storage traits.
464pub struct BootFlash<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
465where
466 F: NorFlash + ReadNorFlash,
467{
468 flash: F,
469}
470
471impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
472where
473 F: NorFlash + ReadNorFlash,
474{
475 /// Create a new instance of a bootable flash
476 pub fn new(flash: F) -> Self {
477 Self { flash }
478 }
479}
480
481impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
482where
483 F: NorFlash + ReadNorFlash,
484{
485 const BLOCK_SIZE: usize = BLOCK_SIZE;
486 const ERASE_VALUE: u8 = ERASE_VALUE;
487}
488
489impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
490where
491 F: ReadNorFlash + NorFlash,
492{
493 type Error = F::Error;
494}
495
496impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
497where
498 F: ReadNorFlash + NorFlash,
499{
500 const WRITE_SIZE: usize = F::WRITE_SIZE;
501 const ERASE_SIZE: usize = F::ERASE_SIZE;
502
503 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
504 F::erase(&mut self.flash, from, to)
505 }
506
507 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
508 F::write(&mut self.flash, offset, bytes)
509 }
510}
511
512impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
513where
514 F: ReadNorFlash + NorFlash,
515{
516 const READ_SIZE: usize = F::READ_SIZE;
517
518 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
519 F::read(&mut self.flash, offset, bytes)
520 }
521
522 fn capacity(&self) -> usize {
523 F::capacity(&self.flash)
524 }
525}
526
527/// Convenience flash provider that uses separate flash instances for each partition.
528pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
529where
530 ACTIVE: Flash,
531 STATE: Flash,
532 DFU: Flash,
533{
534 active: &'a mut ACTIVE,
535 state: &'a mut STATE,
536 dfu: &'a mut DFU,
537}
538
539impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
540where
541 ACTIVE: Flash,
542 STATE: Flash,
543 DFU: Flash,
544{
545 /// Create a new flash provider with separate configuration for all three partitions.
546 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
547 Self { active, state, dfu }
548 }
549}
550
551impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
552where
553 ACTIVE: Flash,
554 STATE: Flash,
555 DFU: Flash,
556{
557 type STATE = STATE;
558 type ACTIVE = ACTIVE;
559 type DFU = DFU;
560
561 fn active(&mut self) -> &mut Self::ACTIVE {
562 self.active
563 }
564 fn dfu(&mut self) -> &mut Self::DFU {
565 self.dfu
566 }
567 fn state(&mut self) -> &mut Self::STATE {
568 self.state
569 }
570}
571/// Errors returned by FirmwareUpdater
572#[derive(Debug)]
573pub enum FirmwareUpdaterError {
574 /// Error from flash.
575 Flash(NorFlashErrorKind),
576 /// Signature errors.
577 Signature(signature::Error),
578}
579
580#[cfg(feature = "defmt")]
581impl defmt::Format for FirmwareUpdaterError {
582 fn format(&self, fmt: defmt::Formatter) {
583 match self {
584 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
585 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
586 }
587 }
588}
589
590impl<E> From<E> for FirmwareUpdaterError
591where
592 E: NorFlashError,
593{
594 fn from(error: E) -> Self {
595 FirmwareUpdaterError::Flash(error.kind())
596 }
597}
598
599/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
600/// 'mess up' the internal bootloader state
601pub struct FirmwareUpdater {
602 state: Partition,
603 dfu: Partition,
604}
605
606impl Default for FirmwareUpdater {
607 fn default() -> Self {
608 extern "C" {
609 static __bootloader_state_start: u32;
610 static __bootloader_state_end: u32;
611 static __bootloader_dfu_start: u32;
612 static __bootloader_dfu_end: u32;
613 }
614
615 let dfu = unsafe {
616 Partition::new(
617 &__bootloader_dfu_start as *const u32 as usize,
618 &__bootloader_dfu_end as *const u32 as usize,
619 )
620 };
621 let state = unsafe {
622 Partition::new(
623 &__bootloader_state_start as *const u32 as usize,
624 &__bootloader_state_end as *const u32 as usize,
625 )
626 };
627
628 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
629 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
630 FirmwareUpdater::new(dfu, state)
631 }
632}
633
634impl FirmwareUpdater {
635 /// Create a firmware updater instance with partition ranges for the update and state partitions.
636 pub const fn new(dfu: Partition, state: Partition) -> Self {
637 Self { dfu, state }
638 }
639
640 /// Return the length of the DFU area
641 pub fn firmware_len(&self) -> usize {
642 self.dfu.len()
643 }
644
645 /// Obtain the current state.
646 ///
647 /// This is useful to check if the bootloader has just done a swap, in order
648 /// to do verifications and self-tests of the new image before calling
649 /// `mark_booted`.
650 pub async fn get_state<F: AsyncNorFlash>(
651 &mut self,
652 flash: &mut F,
653 aligned: &mut [u8],
654 ) -> Result<State, FirmwareUpdaterError> {
655 flash.read(self.state.from as u32, aligned).await?;
656
657 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
658 Ok(State::Swap)
659 } else {
660 Ok(State::Boot)
661 }
662 }
663
664 /// Verify the DFU given a public key. If there is an error then DO NOT
665 /// proceed with updating the firmware as it must be signed with a
666 /// corresponding private key (otherwise it could be malicious firmware).
667 ///
668 /// Mark to trigger firmware swap on next boot if verify suceeds.
669 ///
670 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
671 /// been generated from a SHA-512 digest of the firmware bytes.
672 ///
673 /// If no signature feature is set then this method will always return a
674 /// signature error.
675 ///
676 /// # Safety
677 ///
678 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
679 /// and written to.
680 #[cfg(feature = "_verify")]
681 pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
682 &mut self,
683 _flash: &mut F,
684 _public_key: &[u8],
685 _signature: &[u8],
686 _update_len: usize,
687 _aligned: &mut [u8],
688 ) -> Result<(), FirmwareUpdaterError> {
689 let _end = self.dfu.from + _update_len;
690 let _read_size = _aligned.len();
691
692 assert_eq!(_aligned.len(), F::WRITE_SIZE);
693 assert!(_end <= self.dfu.to);
694
695 #[cfg(feature = "ed25519-dalek")]
696 {
697 use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
698
699 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
700
701 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
702 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
703
704 let mut digest = Sha512::new();
705
706 let mut offset = self.dfu.from;
707 let last_offset = _end / _read_size * _read_size;
708
709 while offset < last_offset {
710 _flash.read(offset as u32, _aligned).await?;
711 digest.update(&_aligned);
712 offset += _read_size;
713 }
714
715 let remaining = _end % _read_size;
716
717 if remaining > 0 {
718 _flash.read(last_offset as u32, _aligned).await?;
719 digest.update(&_aligned[0..remaining]);
720 }
721
722 public_key
723 .verify(&digest.finalize(), &signature)
724 .map_err(into_signature_error)?
725 }
726 #[cfg(feature = "ed25519-salty")]
727 {
728 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
729 use salty::{PublicKey, Sha512, Signature};
730
731 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
732 FirmwareUpdaterError::Signature(signature::Error::default())
733 }
734
735 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
736 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
737 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
738 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
739
740 let mut digest = Sha512::new();
741
742 let mut offset = self.dfu.from;
743 let last_offset = _end / _read_size * _read_size;
744
745 while offset < last_offset {
746 _flash.read(offset as u32, _aligned).await?;
747 digest.update(&_aligned);
748 offset += _read_size;
749 }
750
751 let remaining = _end % _read_size;
752
753 if remaining > 0 {
754 _flash.read(last_offset as u32, _aligned).await?;
755 digest.update(&_aligned[0..remaining]);
756 }
757
758 let message = digest.finalize();
759 let r = public_key.verify(&message, &signature);
760 trace!(
761 "Verifying with public key {}, signature {} and message {} yields ok: {}",
762 public_key.to_bytes(),
763 signature.to_bytes(),
764 message,
765 r.is_ok()
766 );
767 r.map_err(into_signature_error)?
768 }
769
770 self.set_magic(_aligned, SWAP_MAGIC, _flash).await
771 }
772
773 /// Mark to trigger firmware swap on next boot.
774 ///
775 /// # Safety
776 ///
777 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
778 #[cfg(not(feature = "_verify"))]
779 pub async fn mark_updated<F: AsyncNorFlash>(
780 &mut self,
781 flash: &mut F,
782 aligned: &mut [u8],
783 ) -> Result<(), FirmwareUpdaterError> {
784 assert_eq!(aligned.len(), F::WRITE_SIZE);
785 self.set_magic(aligned, SWAP_MAGIC, flash).await
786 }
787
788 /// Mark firmware boot successful and stop rollback on reset.
789 ///
790 /// # Safety
791 ///
792 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
793 pub async fn mark_booted<F: AsyncNorFlash>(
794 &mut self,
795 flash: &mut F,
796 aligned: &mut [u8],
797 ) -> Result<(), FirmwareUpdaterError> {
798 assert_eq!(aligned.len(), F::WRITE_SIZE);
799 self.set_magic(aligned, BOOT_MAGIC, flash).await
800 }
801
802 async fn set_magic<F: AsyncNorFlash>(
803 &mut self,
804 aligned: &mut [u8],
805 magic: u8,
806 flash: &mut F,
807 ) -> Result<(), FirmwareUpdaterError> {
808 flash.read(self.state.from as u32, aligned).await?;
809
810 if aligned.iter().any(|&b| b != magic) {
811 aligned.fill(0);
812
813 flash.write(self.state.from as u32, aligned).await?;
814 flash.erase(self.state.from as u32, self.state.to as u32).await?;
815
816 aligned.fill(magic);
817 flash.write(self.state.from as u32, aligned).await?;
818 }
819 Ok(())
820 }
821
822 /// Write data to a flash page.
823 ///
824 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
825 ///
826 /// # Safety
827 ///
828 /// Failing to meet alignment and size requirements may result in a panic.
829 pub async fn write_firmware<F: AsyncNorFlash>(
830 &mut self,
831 offset: usize,
832 data: &[u8],
833 flash: &mut F,
834 block_size: usize,
835 ) -> Result<(), FirmwareUpdaterError> {
836 assert!(data.len() >= F::ERASE_SIZE);
837
838 flash
839 .erase(
840 (self.dfu.from + offset) as u32,
841 (self.dfu.from + offset + data.len()) as u32,
842 )
843 .await?;
844
845 trace!(
846 "Erased from {} to {}",
847 self.dfu.from + offset,
848 self.dfu.from + offset + data.len()
849 );
850
851 FirmwareWriter(self.dfu)
852 .write_block(offset, data, flash, block_size)
853 .await?;
854
855 Ok(())
856 }
857
858 /// Prepare for an incoming DFU update by erasing the entire DFU area and
859 /// returning a `FirmwareWriter`.
860 ///
861 /// Using this instead of `write_firmware` allows for an optimized API in
862 /// exchange for added complexity.
863 pub async fn prepare_update<F: AsyncNorFlash>(
864 &mut self,
865 flash: &mut F,
866 ) -> Result<FirmwareWriter, FirmwareUpdaterError> {
867 flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?;
868
869 trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
870
871 Ok(FirmwareWriter(self.dfu))
872 }
873
874 //
875 // Blocking API
876 //
877
878 /// Obtain the current state.
879 ///
880 /// This is useful to check if the bootloader has just done a swap, in order
881 /// to do verifications and self-tests of the new image before calling
882 /// `mark_booted`.
883 pub fn get_state_blocking<F: NorFlash>(
884 &mut self,
885 flash: &mut F,
886 aligned: &mut [u8],
887 ) -> Result<State, FirmwareUpdaterError> {
888 flash.read(self.state.from as u32, aligned)?;
889
890 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
891 Ok(State::Swap)
892 } else {
893 Ok(State::Boot)
894 }
895 }
896
897 /// Verify the DFU given a public key. If there is an error then DO NOT
898 /// proceed with updating the firmware as it must be signed with a
899 /// corresponding private key (otherwise it could be malicious firmware).
900 ///
901 /// Mark to trigger firmware swap on next boot if verify suceeds.
902 ///
903 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
904 /// been generated from a SHA-512 digest of the firmware bytes.
905 ///
906 /// If no signature feature is set then this method will always return a
907 /// signature error.
908 ///
909 /// # Safety
910 ///
911 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
912 /// and written to.
913 #[cfg(feature = "_verify")]
914 pub fn verify_and_mark_updated_blocking<F: NorFlash>(
915 &mut self,
916 _flash: &mut F,
917 _public_key: &[u8],
918 _signature: &[u8],
919 _update_len: usize,
920 _aligned: &mut [u8],
921 ) -> Result<(), FirmwareUpdaterError> {
922 let _end = self.dfu.from + _update_len;
923 let _read_size = _aligned.len();
924
925 assert_eq!(_aligned.len(), F::WRITE_SIZE);
926 assert!(_end <= self.dfu.to);
927
928 #[cfg(feature = "ed25519-dalek")]
929 {
930 use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
931
932 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
933
934 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
935 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
936
937 let mut digest = Sha512::new();
938
939 let mut offset = self.dfu.from;
940 let last_offset = _end / _read_size * _read_size;
941
942 while offset < last_offset {
943 _flash.read(offset as u32, _aligned)?;
944 digest.update(&_aligned);
945 offset += _read_size;
946 }
947
948 let remaining = _end % _read_size;
949
950 if remaining > 0 {
951 _flash.read(last_offset as u32, _aligned)?;
952 digest.update(&_aligned[0..remaining]);
953 }
954
955 public_key
956 .verify(&digest.finalize(), &signature)
957 .map_err(into_signature_error)?
958 }
959 #[cfg(feature = "ed25519-salty")]
960 {
961 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
962 use salty::{PublicKey, Sha512, Signature};
963
964 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
965 FirmwareUpdaterError::Signature(signature::Error::default())
966 }
967
968 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
969 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
970 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
971 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
972
973 let mut digest = Sha512::new();
974
975 let mut offset = self.dfu.from;
976 let last_offset = _end / _read_size * _read_size;
977
978 while offset < last_offset {
979 _flash.read(offset as u32, _aligned)?;
980 digest.update(&_aligned);
981 offset += _read_size;
982 }
983
984 let remaining = _end % _read_size;
985
986 if remaining > 0 {
987 _flash.read(last_offset as u32, _aligned)?;
988 digest.update(&_aligned[0..remaining]);
989 }
990
991 let message = digest.finalize();
992 let r = public_key.verify(&message, &signature);
993 trace!(
994 "Verifying with public key {}, signature {} and message {} yields ok: {}",
995 public_key.to_bytes(),
996 signature.to_bytes(),
997 message,
998 r.is_ok()
999 );
1000 r.map_err(into_signature_error)?
1001 }
1002
1003 self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash)
1004 }
1005
1006 /// Mark to trigger firmware swap on next boot.
1007 ///
1008 /// # Safety
1009 ///
1010 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
1011 #[cfg(not(feature = "_verify"))]
1012 pub fn mark_updated_blocking<F: NorFlash>(
1013 &mut self,
1014 flash: &mut F,
1015 aligned: &mut [u8],
1016 ) -> Result<(), FirmwareUpdaterError> {
1017 assert_eq!(aligned.len(), F::WRITE_SIZE);
1018 self.set_magic_blocking(aligned, SWAP_MAGIC, flash)
1019 }
1020
1021 /// Mark firmware boot successful and stop rollback on reset.
1022 ///
1023 /// # Safety
1024 ///
1025 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
1026 pub fn mark_booted_blocking<F: NorFlash>(
1027 &mut self,
1028 flash: &mut F,
1029 aligned: &mut [u8],
1030 ) -> Result<(), FirmwareUpdaterError> {
1031 assert_eq!(aligned.len(), F::WRITE_SIZE);
1032 self.set_magic_blocking(aligned, BOOT_MAGIC, flash)
1033 }
1034
1035 fn set_magic_blocking<F: NorFlash>(
1036 &mut self,
1037 aligned: &mut [u8],
1038 magic: u8,
1039 flash: &mut F,
1040 ) -> Result<(), FirmwareUpdaterError> {
1041 flash.read(self.state.from as u32, aligned)?;
1042
1043 if aligned.iter().any(|&b| b != magic) {
1044 aligned.fill(0);
1045
1046 flash.write(self.state.from as u32, aligned)?;
1047 flash.erase(self.state.from as u32, self.state.to as u32)?;
1048
1049 aligned.fill(magic);
1050 flash.write(self.state.from as u32, aligned)?;
1051 }
1052 Ok(())
1053 }
1054
1055 /// Write data to a flash page.
1056 ///
1057 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
1058 ///
1059 /// # Safety
1060 ///
1061 /// Failing to meet alignment and size requirements may result in a panic.
1062 pub fn write_firmware_blocking<F: NorFlash>(
1063 &mut self,
1064 offset: usize,
1065 data: &[u8],
1066 flash: &mut F,
1067 block_size: usize,
1068 ) -> Result<(), FirmwareUpdaterError> {
1069 assert!(data.len() >= F::ERASE_SIZE);
1070
1071 flash.erase(
1072 (self.dfu.from + offset) as u32,
1073 (self.dfu.from + offset + data.len()) as u32,
1074 )?;
1075
1076 trace!(
1077 "Erased from {} to {}",
1078 self.dfu.from + offset,
1079 self.dfu.from + offset + data.len()
1080 );
1081
1082 FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?;
1083
1084 Ok(())
1085 }
1086
1087 /// Prepare for an incoming DFU update by erasing the entire DFU area and
1088 /// returning a `FirmwareWriter`.
1089 ///
1090 /// Using this instead of `write_firmware_blocking` allows for an optimized
1091 /// API in exchange for added complexity.
1092 pub fn prepare_update_blocking<F: NorFlash>(
1093 &mut self,
1094 flash: &mut F,
1095 ) -> Result<FirmwareWriter, FirmwareUpdaterError> {
1096 flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?;
1097
1098 trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
1099
1100 Ok(FirmwareWriter(self.dfu))
1101 }
1102}
1103
1104/// FirmwareWriter allows writing blocks to an already erased flash.
1105pub struct FirmwareWriter(Partition);
1106
1107impl FirmwareWriter {
1108 /// Write data to a flash page.
1109 ///
1110 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
1111 ///
1112 /// # Safety
1113 ///
1114 /// Failing to meet alignment and size requirements may result in a panic.
1115 pub async fn write_block<F: AsyncNorFlash>(
1116 &mut self,
1117 offset: usize,
1118 data: &[u8],
1119 flash: &mut F,
1120 block_size: usize,
1121 ) -> Result<(), F::Error> {
1122 trace!(
1123 "Writing firmware at offset 0x{:x} len {}",
1124 self.0.from + offset,
1125 data.len()
1126 );
1127
1128 let mut write_offset = self.0.from + offset;
1129 for chunk in data.chunks(block_size) {
1130 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
1131 flash.write(write_offset as u32, chunk).await?;
1132 write_offset += chunk.len();
1133 }
1134 /*
1135 trace!("Wrote data, reading back for verification");
1136
1137 let mut buf: [u8; 4096] = [0; 4096];
1138 let mut data_offset = 0;
1139 let mut read_offset = self.dfu.from + offset;
1140 for chunk in buf.chunks_mut(block_size) {
1141 flash.read(read_offset as u32, chunk).await?;
1142 trace!("Read chunk at {}: {:?}", read_offset, chunk);
1143 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
1144 read_offset += chunk.len();
1145 data_offset += chunk.len();
1146 }
1147 */
1148
1149 Ok(())
1150 }
1151
1152 /// Write data to a flash page.
1153 ///
1154 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
1155 ///
1156 /// # Safety
1157 ///
1158 /// Failing to meet alignment and size requirements may result in a panic.
1159 pub fn write_block_blocking<F: NorFlash>(
1160 &mut self,
1161 offset: usize,
1162 data: &[u8],
1163 flash: &mut F,
1164 block_size: usize,
1165 ) -> Result<(), F::Error> {
1166 trace!(
1167 "Writing firmware at offset 0x{:x} len {}",
1168 self.0.from + offset,
1169 data.len()
1170 );
1171
1172 let mut write_offset = self.0.from + offset;
1173 for chunk in data.chunks(block_size) {
1174 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
1175 flash.write(write_offset as u32, chunk)?;
1176 write_offset += chunk.len();
1177 }
1178 /*
1179 trace!("Wrote data, reading back for verification");
1180
1181 let mut buf: [u8; 4096] = [0; 4096];
1182 let mut data_offset = 0;
1183 let mut read_offset = self.dfu.from + offset;
1184 for chunk in buf.chunks_mut(block_size) {
1185 flash.read(read_offset as u32, chunk).await?;
1186 trace!("Read chunk at {}: {:?}", read_offset, chunk);
1187 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
1188 read_offset += chunk.len();
1189 data_offset += chunk.len();
1190 }
1191 */
1192
1193 Ok(())
1194 }
1195}
1196
1197#[cfg(test)] 47#[cfg(test)]
1198mod tests { 48mod tests {
1199 use core::convert::Infallible; 49 use core::convert::Infallible;
1200 50
1201 use embedded_storage::nor_flash::ErrorType; 51 use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
1202 use embedded_storage_async::nor_flash::ReadNorFlash as AsyncReadNorFlash; 52 use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
1203 use futures::executor::block_on; 53 use futures::executor::block_on;
1204 54
1205 use super::*; 55 use super::*;
@@ -1415,15 +265,6 @@ mod tests {
1415 } 265 }
1416 266
1417 #[test] 267 #[test]
1418 #[should_panic]
1419 fn test_range_asserts() {
1420 const ACTIVE: Partition = Partition::new(4096, 4194304);
1421 const DFU: Partition = Partition::new(4194304, 2 * 4194304);
1422 const STATE: Partition = Partition::new(0, 4096);
1423 assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
1424 }
1425
1426 #[test]
1427 #[cfg(feature = "_verify")] 268 #[cfg(feature = "_verify")]
1428 fn test_verify() { 269 fn test_verify() {
1429 // The following key setup is based on: 270 // The following key setup is based on: