aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-boot/boot/src/boot_loader.rs526
-rw-r--r--embassy-boot/boot/src/firmware_updater.rs537
-rw-r--r--embassy-boot/boot/src/firmware_writer.rs97
-rw-r--r--embassy-boot/boot/src/lib.rs1183
-rw-r--r--embassy-boot/boot/src/partition.rs22
5 files changed, 1194 insertions, 1171 deletions
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
new file mode 100644
index 000000000..ad6735112
--- /dev/null
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -0,0 +1,526 @@
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/// Extension of the embedded-storage flash type information with block size and erase value.
34pub trait Flash: NorFlash + ReadNorFlash {
35 /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase
36 /// size of the flash, but for external QSPI flash modules, this can be lower.
37 const BLOCK_SIZE: usize;
38 /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value.
39 const ERASE_VALUE: u8 = 0xFF;
40}
41
42/// Trait defining the flash handles used for active and DFU partition
43pub trait FlashConfig {
44 /// Flash type used for the state partition.
45 type STATE: Flash;
46 /// Flash type used for the active partition.
47 type ACTIVE: Flash;
48 /// Flash type used for the dfu partition.
49 type DFU: Flash;
50
51 /// Return flash instance used to write/read to/from active partition.
52 fn active(&mut self) -> &mut Self::ACTIVE;
53 /// Return flash instance used to write/read to/from dfu partition.
54 fn dfu(&mut self) -> &mut Self::DFU;
55 /// Return flash instance used to write/read to/from bootloader state.
56 fn state(&mut self) -> &mut Self::STATE;
57}
58
59/// BootLoader works with any flash implementing embedded_storage and can also work with
60/// different page sizes and flash write sizes.
61pub struct BootLoader {
62 // Page with current state of bootloader. The state partition has the following format:
63 // | Range | Description |
64 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
65 // | WRITE_SIZE - N | Progress index used while swapping or reverting |
66 state: Partition,
67 // Location of the partition which will be booted from
68 active: Partition,
69 // Location of the partition which will be swapped in when requested
70 dfu: Partition,
71}
72
73impl BootLoader {
74 /// Create a new instance of a bootloader with the given partitions.
75 ///
76 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
77 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
78 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
79 Self { active, dfu, state }
80 }
81
82 /// Return the boot address for the active partition.
83 pub fn boot_address(&self) -> usize {
84 self.active.from
85 }
86
87 /// Perform necessary boot preparations like swapping images.
88 ///
89 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
90 /// algorithm to work correctly.
91 ///
92 /// SWAPPING
93 ///
94 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
95 /// The swap index contains the copy progress, as to allow continuation of the copy process on
96 /// power failure. The index counter is represented within 1 or more pages (depending on total
97 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
98 /// contains a zero value. This ensures that index updates can be performed atomically and
99 /// avoid a situation where the wrong index value is set (page write size is "atomic").
100 ///
101 /// +-----------+------------+--------+--------+--------+--------+
102 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
103 /// +-----------+------------+--------+--------+--------+--------+
104 /// | Active | 0 | 1 | 2 | 3 | - |
105 /// | DFU | 0 | 3 | 2 | 1 | X |
106 /// +-----------+------------+--------+--------+--------+--------+
107 ///
108 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
109 /// as follows:
110 ///
111 /// +-----------+------------+--------+--------+--------+--------+
112 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
113 /// +-----------+------------+--------+--------+--------+--------+
114 /// | Active | 1 | 1 | 2 | 1 | - |
115 /// | DFU | 1 | 3 | 2 | 1 | 3 |
116 /// +-----------+------------+--------+--------+--------+--------+
117 ///
118 /// The next iteration performs the same steps
119 ///
120 /// +-----------+------------+--------+--------+--------+--------+
121 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
122 /// +-----------+------------+--------+--------+--------+--------+
123 /// | Active | 2 | 1 | 2 | 1 | - |
124 /// | DFU | 2 | 3 | 2 | 2 | 3 |
125 /// +-----------+------------+--------+--------+--------+--------+
126 ///
127 /// And again until we're done
128 ///
129 /// +-----------+------------+--------+--------+--------+--------+
130 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
131 /// +-----------+------------+--------+--------+--------+--------+
132 /// | Active | 3 | 3 | 2 | 1 | - |
133 /// | DFU | 3 | 3 | 1 | 2 | 3 |
134 /// +-----------+------------+--------+--------+--------+--------+
135 ///
136 /// REVERTING
137 ///
138 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
139 /// the application failed to mark the boot successful. In this case, the revert algorithm will
140 /// run.
141 ///
142 /// The revert index is located separately from the swap index, to ensure that revert can continue
143 /// on power failure.
144 ///
145 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
146 ///
147 /// +-----------+--------------+--------+--------+--------+--------+
148 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
149 //*/
150 /// +-----------+--------------+--------+--------+--------+--------+
151 /// | Active | 3 | 1 | 2 | 1 | - |
152 /// | DFU | 3 | 3 | 1 | 2 | 3 |
153 /// +-----------+--------------+--------+--------+--------+--------+
154 ///
155 ///
156 /// +-----------+--------------+--------+--------+--------+--------+
157 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
158 /// +-----------+--------------+--------+--------+--------+--------+
159 /// | Active | 3 | 1 | 2 | 1 | - |
160 /// | DFU | 3 | 3 | 2 | 2 | 3 |
161 /// +-----------+--------------+--------+--------+--------+--------+
162 ///
163 /// +-----------+--------------+--------+--------+--------+--------+
164 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
165 /// +-----------+--------------+--------+--------+--------+--------+
166 /// | Active | 3 | 1 | 2 | 3 | - |
167 /// | DFU | 3 | 3 | 2 | 1 | 3 |
168 /// +-----------+--------------+--------+--------+--------+--------+
169 ///
170 pub fn prepare_boot<P: FlashConfig>(
171 &mut self,
172 p: &mut P,
173 magic: &mut [u8],
174 page: &mut [u8],
175 ) -> Result<State, BootError> {
176 // Ensure we have enough progress pages to store copy progress
177 assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE);
178 assert_eq!(magic.len(), P::STATE::WRITE_SIZE);
179
180 // Copy contents from partition N to active
181 let state = self.read_state(p, magic)?;
182 if state == State::Swap {
183 //
184 // Check if we already swapped. If we're in the swap state, this means we should revert
185 // since the app has failed to mark boot as successful
186 //
187 if !self.is_swapped(p, magic, page)? {
188 trace!("Swapping");
189 self.swap(p, magic, page)?;
190 trace!("Swapping done");
191 } else {
192 trace!("Reverting");
193 self.revert(p, magic, page)?;
194
195 // Overwrite magic and reset progress
196 let fstate = p.state();
197 magic.fill(!P::STATE::ERASE_VALUE);
198 fstate.write(self.state.from as u32, magic)?;
199 fstate.erase(self.state.from as u32, self.state.to as u32)?;
200
201 magic.fill(BOOT_MAGIC);
202 fstate.write(self.state.from as u32, magic)?;
203 }
204 }
205 Ok(state)
206 }
207
208 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<bool, BootError> {
209 let page_size = page.len();
210 let page_count = self.active.len() / page_size;
211 let progress = self.current_progress(p, magic)?;
212
213 Ok(progress >= page_count * 2)
214 }
215
216 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> {
217 let write_size = aligned.len();
218 let max_index = ((self.state.len() - write_size) / write_size) - 1;
219 aligned.fill(!P::STATE::ERASE_VALUE);
220
221 let flash = config.state();
222 for i in 0..max_index {
223 flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?;
224
225 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) {
226 return Ok(i);
227 }
228 }
229 Ok(max_index)
230 }
231
232 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> {
233 let flash = p.state();
234 let write_size = magic.len();
235 let w = self.state.from + write_size + idx * write_size;
236
237 let aligned = magic;
238 aligned.fill(!P::STATE::ERASE_VALUE);
239 flash.write(w as u32, aligned)?;
240 Ok(())
241 }
242
243 fn active_addr(&self, n: usize, page_size: usize) -> usize {
244 self.active.from + n * page_size
245 }
246
247 fn dfu_addr(&self, n: usize, page_size: usize) -> usize {
248 self.dfu.from + n * page_size
249 }
250
251 fn copy_page_once_to_active<P: FlashConfig>(
252 &mut self,
253 idx: usize,
254 from_page: usize,
255 to_page: usize,
256 p: &mut P,
257 magic: &mut [u8],
258 page: &mut [u8],
259 ) -> Result<(), BootError> {
260 let buf = page;
261 if self.current_progress(p, magic)? <= idx {
262 let mut offset = from_page;
263 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
264 p.dfu().read(offset as u32, chunk)?;
265 offset += chunk.len();
266 }
267
268 p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?;
269
270 let mut offset = to_page;
271 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
272 p.active().write(offset as u32, chunk)?;
273 offset += chunk.len();
274 }
275 self.update_progress(idx, p, magic)?;
276 }
277 Ok(())
278 }
279
280 fn copy_page_once_to_dfu<P: FlashConfig>(
281 &mut self,
282 idx: usize,
283 from_page: usize,
284 to_page: usize,
285 p: &mut P,
286 magic: &mut [u8],
287 page: &mut [u8],
288 ) -> Result<(), BootError> {
289 let buf = page;
290 if self.current_progress(p, magic)? <= idx {
291 let mut offset = from_page;
292 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
293 p.active().read(offset as u32, chunk)?;
294 offset += chunk.len();
295 }
296
297 p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?;
298
299 let mut offset = to_page;
300 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
301 p.dfu().write(offset as u32, chunk)?;
302 offset += chunk.len();
303 }
304 self.update_progress(idx, p, magic)?;
305 }
306 Ok(())
307 }
308
309 fn swap<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
310 let page_size = page.len();
311 let page_count = self.active.len() / page_size;
312 trace!("Page count: {}", page_count);
313 for page_num in 0..page_count {
314 trace!("COPY PAGE {}", page_num);
315 // Copy active page to the 'next' DFU page.
316 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
317 let dfu_page = self.dfu_addr(page_count - page_num, page_size);
318 //trace!("Copy active {} to dfu {}", active_page, dfu_page);
319 self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?;
320
321 // Copy DFU page to the active page
322 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
323 let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size);
324 //trace!("Copy dfy {} to active {}", dfu_page, active_page);
325 self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
326 }
327
328 Ok(())
329 }
330
331 fn revert<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
332 let page_size = page.len();
333 let page_count = self.active.len() / page_size;
334 for page_num in 0..page_count {
335 // Copy the bad active page to the DFU page
336 let active_page = self.active_addr(page_num, page_size);
337 let dfu_page = self.dfu_addr(page_num, page_size);
338 self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?;
339
340 // Copy the DFU page back to the active page
341 let active_page = self.active_addr(page_num, page_size);
342 let dfu_page = self.dfu_addr(page_num + 1, page_size);
343 self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
344 }
345
346 Ok(())
347 }
348
349 fn read_state<P: FlashConfig>(&mut self, config: &mut P, magic: &mut [u8]) -> Result<State, BootError> {
350 let flash = config.state();
351 flash.read(self.state.from as u32, magic)?;
352
353 if !magic.iter().any(|&b| b != SWAP_MAGIC) {
354 Ok(State::Swap)
355 } else {
356 Ok(State::Boot)
357 }
358 }
359}
360
361fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) {
362 assert_eq!(active.len() % page_size, 0);
363 assert_eq!(dfu.len() % page_size, 0);
364 assert!(dfu.len() - active.len() >= page_size);
365 assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size);
366}
367
368/// A flash wrapper implementing the Flash and embedded_storage traits.
369pub struct BootFlash<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
370where
371 F: NorFlash + ReadNorFlash,
372{
373 flash: F,
374}
375
376impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
377where
378 F: NorFlash + ReadNorFlash,
379{
380 /// Create a new instance of a bootable flash
381 pub fn new(flash: F) -> Self {
382 Self { flash }
383 }
384}
385
386impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
387where
388 F: NorFlash + ReadNorFlash,
389{
390 const BLOCK_SIZE: usize = BLOCK_SIZE;
391 const ERASE_VALUE: u8 = ERASE_VALUE;
392}
393
394impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
395where
396 F: ReadNorFlash + NorFlash,
397{
398 type Error = F::Error;
399}
400
401impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
402where
403 F: ReadNorFlash + NorFlash,
404{
405 const WRITE_SIZE: usize = F::WRITE_SIZE;
406 const ERASE_SIZE: usize = F::ERASE_SIZE;
407
408 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
409 F::erase(&mut self.flash, from, to)
410 }
411
412 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
413 F::write(&mut self.flash, offset, bytes)
414 }
415}
416
417impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
418where
419 F: ReadNorFlash + NorFlash,
420{
421 const READ_SIZE: usize = F::READ_SIZE;
422
423 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
424 F::read(&mut self.flash, offset, bytes)
425 }
426
427 fn capacity(&self) -> usize {
428 F::capacity(&self.flash)
429 }
430}
431
432/// Convenience provider that uses a single flash for all partitions.
433pub struct SingleFlashConfig<'a, F>
434where
435 F: Flash,
436{
437 flash: &'a mut F,
438}
439
440impl<'a, F> SingleFlashConfig<'a, F>
441where
442 F: Flash,
443{
444 /// Create a provider for a single flash.
445 pub fn new(flash: &'a mut F) -> Self {
446 Self { flash }
447 }
448}
449
450impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
451where
452 F: Flash,
453{
454 type STATE = F;
455 type ACTIVE = F;
456 type DFU = F;
457
458 fn active(&mut self) -> &mut Self::STATE {
459 self.flash
460 }
461 fn dfu(&mut self) -> &mut Self::ACTIVE {
462 self.flash
463 }
464 fn state(&mut self) -> &mut Self::DFU {
465 self.flash
466 }
467}
468
469/// Convenience flash provider that uses separate flash instances for each partition.
470pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
471where
472 ACTIVE: Flash,
473 STATE: Flash,
474 DFU: Flash,
475{
476 active: &'a mut ACTIVE,
477 state: &'a mut STATE,
478 dfu: &'a mut DFU,
479}
480
481impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
482where
483 ACTIVE: Flash,
484 STATE: Flash,
485 DFU: Flash,
486{
487 /// Create a new flash provider with separate configuration for all three partitions.
488 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
489 Self { active, state, dfu }
490 }
491}
492
493impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
494where
495 ACTIVE: Flash,
496 STATE: Flash,
497 DFU: Flash,
498{
499 type STATE = STATE;
500 type ACTIVE = ACTIVE;
501 type DFU = DFU;
502
503 fn active(&mut self) -> &mut Self::ACTIVE {
504 self.active
505 }
506 fn dfu(&mut self) -> &mut Self::DFU {
507 self.dfu
508 }
509 fn state(&mut self) -> &mut Self::STATE {
510 self.state
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517
518 #[test]
519 #[should_panic]
520 fn test_range_asserts() {
521 const ACTIVE: Partition = Partition::new(4096, 4194304);
522 const DFU: Partition = Partition::new(4194304, 2 * 4194304);
523 const STATE: Partition = Partition::new(0, 4096);
524 assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
525 }
526}
diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs
new file mode 100644
index 000000000..2d8712277
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater.rs
@@ -0,0 +1,537 @@
1use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
2use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
3
4use crate::{FirmwareWriter, Partition, State, BOOT_MAGIC, SWAP_MAGIC};
5
6/// Errors returned by FirmwareUpdater
7#[derive(Debug)]
8pub enum FirmwareUpdaterError {
9 /// Error from flash.
10 Flash(NorFlashErrorKind),
11 /// Signature errors.
12 Signature(signature::Error),
13}
14
15#[cfg(feature = "defmt")]
16impl defmt::Format for FirmwareUpdaterError {
17 fn format(&self, fmt: defmt::Formatter) {
18 match self {
19 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
20 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
21 }
22 }
23}
24
25impl<E> From<E> for FirmwareUpdaterError
26where
27 E: NorFlashError,
28{
29 fn from(error: E) -> Self {
30 FirmwareUpdaterError::Flash(error.kind())
31 }
32}
33
34/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
35/// 'mess up' the internal bootloader state
36pub struct FirmwareUpdater {
37 state: Partition,
38 dfu: Partition,
39}
40
41impl Default for FirmwareUpdater {
42 fn default() -> Self {
43 extern "C" {
44 static __bootloader_state_start: u32;
45 static __bootloader_state_end: u32;
46 static __bootloader_dfu_start: u32;
47 static __bootloader_dfu_end: u32;
48 }
49
50 let dfu = unsafe {
51 Partition::new(
52 &__bootloader_dfu_start as *const u32 as usize,
53 &__bootloader_dfu_end as *const u32 as usize,
54 )
55 };
56 let state = unsafe {
57 Partition::new(
58 &__bootloader_state_start as *const u32 as usize,
59 &__bootloader_state_end as *const u32 as usize,
60 )
61 };
62
63 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
64 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
65 FirmwareUpdater::new(dfu, state)
66 }
67}
68
69impl FirmwareUpdater {
70 /// Create a firmware updater instance with partition ranges for the update and state partitions.
71 pub const fn new(dfu: Partition, state: Partition) -> Self {
72 Self { dfu, state }
73 }
74
75 /// Return the length of the DFU area
76 pub fn firmware_len(&self) -> usize {
77 self.dfu.len()
78 }
79
80 /// Obtain the current state.
81 ///
82 /// This is useful to check if the bootloader has just done a swap, in order
83 /// to do verifications and self-tests of the new image before calling
84 /// `mark_booted`.
85 pub async fn get_state<F: AsyncNorFlash>(
86 &mut self,
87 flash: &mut F,
88 aligned: &mut [u8],
89 ) -> Result<State, FirmwareUpdaterError> {
90 flash.read(self.state.from as u32, aligned).await?;
91
92 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
93 Ok(State::Swap)
94 } else {
95 Ok(State::Boot)
96 }
97 }
98
99 /// Verify the DFU given a public key. If there is an error then DO NOT
100 /// proceed with updating the firmware as it must be signed with a
101 /// corresponding private key (otherwise it could be malicious firmware).
102 ///
103 /// Mark to trigger firmware swap on next boot if verify suceeds.
104 ///
105 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
106 /// been generated from a SHA-512 digest of the firmware bytes.
107 ///
108 /// If no signature feature is set then this method will always return a
109 /// signature error.
110 ///
111 /// # Safety
112 ///
113 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
114 /// and written to.
115 #[cfg(feature = "_verify")]
116 pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
117 &mut self,
118 _flash: &mut F,
119 _public_key: &[u8],
120 _signature: &[u8],
121 _update_len: usize,
122 _aligned: &mut [u8],
123 ) -> Result<(), FirmwareUpdaterError> {
124 let _end = self.dfu.from + _update_len;
125 let _read_size = _aligned.len();
126
127 assert_eq!(_aligned.len(), F::WRITE_SIZE);
128 assert!(_end <= self.dfu.to);
129
130 #[cfg(feature = "ed25519-dalek")]
131 {
132 use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
133
134 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
135
136 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
137 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
138
139 let mut digest = Sha512::new();
140
141 let mut offset = self.dfu.from;
142 let last_offset = _end / _read_size * _read_size;
143
144 while offset < last_offset {
145 _flash.read(offset as u32, _aligned).await?;
146 digest.update(&_aligned);
147 offset += _read_size;
148 }
149
150 let remaining = _end % _read_size;
151
152 if remaining > 0 {
153 _flash.read(last_offset as u32, _aligned).await?;
154 digest.update(&_aligned[0..remaining]);
155 }
156
157 public_key
158 .verify(&digest.finalize(), &signature)
159 .map_err(into_signature_error)?
160 }
161 #[cfg(feature = "ed25519-salty")]
162 {
163 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
164 use salty::{PublicKey, Sha512, Signature};
165
166 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
167 FirmwareUpdaterError::Signature(signature::Error::default())
168 }
169
170 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
171 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
172 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
173 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
174
175 let mut digest = Sha512::new();
176
177 let mut offset = self.dfu.from;
178 let last_offset = _end / _read_size * _read_size;
179
180 while offset < last_offset {
181 _flash.read(offset as u32, _aligned).await?;
182 digest.update(&_aligned);
183 offset += _read_size;
184 }
185
186 let remaining = _end % _read_size;
187
188 if remaining > 0 {
189 _flash.read(last_offset as u32, _aligned).await?;
190 digest.update(&_aligned[0..remaining]);
191 }
192
193 let message = digest.finalize();
194 let r = public_key.verify(&message, &signature);
195 trace!(
196 "Verifying with public key {}, signature {} and message {} yields ok: {}",
197 public_key.to_bytes(),
198 signature.to_bytes(),
199 message,
200 r.is_ok()
201 );
202 r.map_err(into_signature_error)?
203 }
204
205 self.set_magic(_aligned, SWAP_MAGIC, _flash).await
206 }
207
208 /// Mark to trigger firmware swap on next boot.
209 ///
210 /// # Safety
211 ///
212 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
213 #[cfg(not(feature = "_verify"))]
214 pub async fn mark_updated<F: AsyncNorFlash>(
215 &mut self,
216 flash: &mut F,
217 aligned: &mut [u8],
218 ) -> Result<(), FirmwareUpdaterError> {
219 assert_eq!(aligned.len(), F::WRITE_SIZE);
220 self.set_magic(aligned, SWAP_MAGIC, flash).await
221 }
222
223 /// Mark firmware boot successful and stop rollback on reset.
224 ///
225 /// # Safety
226 ///
227 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
228 pub async fn mark_booted<F: AsyncNorFlash>(
229 &mut self,
230 flash: &mut F,
231 aligned: &mut [u8],
232 ) -> Result<(), FirmwareUpdaterError> {
233 assert_eq!(aligned.len(), F::WRITE_SIZE);
234 self.set_magic(aligned, BOOT_MAGIC, flash).await
235 }
236
237 async fn set_magic<F: AsyncNorFlash>(
238 &mut self,
239 aligned: &mut [u8],
240 magic: u8,
241 flash: &mut F,
242 ) -> Result<(), FirmwareUpdaterError> {
243 flash.read(self.state.from as u32, aligned).await?;
244
245 if aligned.iter().any(|&b| b != magic) {
246 aligned.fill(0);
247
248 flash.write(self.state.from as u32, aligned).await?;
249 flash.erase(self.state.from as u32, self.state.to as u32).await?;
250
251 aligned.fill(magic);
252 flash.write(self.state.from as u32, aligned).await?;
253 }
254 Ok(())
255 }
256
257 /// Write data to a flash page.
258 ///
259 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
260 ///
261 /// # Safety
262 ///
263 /// Failing to meet alignment and size requirements may result in a panic.
264 pub async fn write_firmware<F: AsyncNorFlash>(
265 &mut self,
266 offset: usize,
267 data: &[u8],
268 flash: &mut F,
269 block_size: usize,
270 ) -> Result<(), FirmwareUpdaterError> {
271 assert!(data.len() >= F::ERASE_SIZE);
272
273 flash
274 .erase(
275 (self.dfu.from + offset) as u32,
276 (self.dfu.from + offset + data.len()) as u32,
277 )
278 .await?;
279
280 trace!(
281 "Erased from {} to {}",
282 self.dfu.from + offset,
283 self.dfu.from + offset + data.len()
284 );
285
286 FirmwareWriter(self.dfu)
287 .write_block(offset, data, flash, block_size)
288 .await?;
289
290 Ok(())
291 }
292
293 /// Prepare for an incoming DFU update by erasing the entire DFU area and
294 /// returning a `FirmwareWriter`.
295 ///
296 /// Using this instead of `write_firmware` allows for an optimized API in
297 /// exchange for added complexity.
298 pub async fn prepare_update<F: AsyncNorFlash>(
299 &mut self,
300 flash: &mut F,
301 ) -> Result<FirmwareWriter, FirmwareUpdaterError> {
302 flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?;
303
304 trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
305
306 Ok(FirmwareWriter(self.dfu))
307 }
308
309 //
310 // Blocking API
311 //
312
313 /// Obtain the current state.
314 ///
315 /// This is useful to check if the bootloader has just done a swap, in order
316 /// to do verifications and self-tests of the new image before calling
317 /// `mark_booted`.
318 pub fn get_state_blocking<F: NorFlash>(
319 &mut self,
320 flash: &mut F,
321 aligned: &mut [u8],
322 ) -> Result<State, FirmwareUpdaterError> {
323 flash.read(self.state.from as u32, aligned)?;
324
325 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
326 Ok(State::Swap)
327 } else {
328 Ok(State::Boot)
329 }
330 }
331
332 /// Verify the DFU given a public key. If there is an error then DO NOT
333 /// proceed with updating the firmware as it must be signed with a
334 /// corresponding private key (otherwise it could be malicious firmware).
335 ///
336 /// Mark to trigger firmware swap on next boot if verify suceeds.
337 ///
338 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
339 /// been generated from a SHA-512 digest of the firmware bytes.
340 ///
341 /// If no signature feature is set then this method will always return a
342 /// signature error.
343 ///
344 /// # Safety
345 ///
346 /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
347 /// and written to.
348 #[cfg(feature = "_verify")]
349 pub fn verify_and_mark_updated_blocking<F: NorFlash>(
350 &mut self,
351 _flash: &mut F,
352 _public_key: &[u8],
353 _signature: &[u8],
354 _update_len: usize,
355 _aligned: &mut [u8],
356 ) -> Result<(), FirmwareUpdaterError> {
357 let _end = self.dfu.from + _update_len;
358 let _read_size = _aligned.len();
359
360 assert_eq!(_aligned.len(), F::WRITE_SIZE);
361 assert!(_end <= self.dfu.to);
362
363 #[cfg(feature = "ed25519-dalek")]
364 {
365 use ed25519_dalek::{Digest, PublicKey, Sha512, Signature, SignatureError, Verifier};
366
367 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
368
369 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
370 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
371
372 let mut digest = Sha512::new();
373
374 let mut offset = self.dfu.from;
375 let last_offset = _end / _read_size * _read_size;
376
377 while offset < last_offset {
378 _flash.read(offset as u32, _aligned)?;
379 digest.update(&_aligned);
380 offset += _read_size;
381 }
382
383 let remaining = _end % _read_size;
384
385 if remaining > 0 {
386 _flash.read(last_offset as u32, _aligned)?;
387 digest.update(&_aligned[0..remaining]);
388 }
389
390 public_key
391 .verify(&digest.finalize(), &signature)
392 .map_err(into_signature_error)?
393 }
394 #[cfg(feature = "ed25519-salty")]
395 {
396 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
397 use salty::{PublicKey, Sha512, Signature};
398
399 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
400 FirmwareUpdaterError::Signature(signature::Error::default())
401 }
402
403 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
404 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
405 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
406 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
407
408 let mut digest = Sha512::new();
409
410 let mut offset = self.dfu.from;
411 let last_offset = _end / _read_size * _read_size;
412
413 while offset < last_offset {
414 _flash.read(offset as u32, _aligned)?;
415 digest.update(&_aligned);
416 offset += _read_size;
417 }
418
419 let remaining = _end % _read_size;
420
421 if remaining > 0 {
422 _flash.read(last_offset as u32, _aligned)?;
423 digest.update(&_aligned[0..remaining]);
424 }
425
426 let message = digest.finalize();
427 let r = public_key.verify(&message, &signature);
428 trace!(
429 "Verifying with public key {}, signature {} and message {} yields ok: {}",
430 public_key.to_bytes(),
431 signature.to_bytes(),
432 message,
433 r.is_ok()
434 );
435 r.map_err(into_signature_error)?
436 }
437
438 self.set_magic_blocking(_aligned, SWAP_MAGIC, _flash)
439 }
440
441 /// Mark to trigger firmware swap on next boot.
442 ///
443 /// # Safety
444 ///
445 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
446 #[cfg(not(feature = "_verify"))]
447 pub fn mark_updated_blocking<F: NorFlash>(
448 &mut self,
449 flash: &mut F,
450 aligned: &mut [u8],
451 ) -> Result<(), FirmwareUpdaterError> {
452 assert_eq!(aligned.len(), F::WRITE_SIZE);
453 self.set_magic_blocking(aligned, SWAP_MAGIC, flash)
454 }
455
456 /// Mark firmware boot successful and stop rollback on reset.
457 ///
458 /// # Safety
459 ///
460 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
461 pub fn mark_booted_blocking<F: NorFlash>(
462 &mut self,
463 flash: &mut F,
464 aligned: &mut [u8],
465 ) -> Result<(), FirmwareUpdaterError> {
466 assert_eq!(aligned.len(), F::WRITE_SIZE);
467 self.set_magic_blocking(aligned, BOOT_MAGIC, flash)
468 }
469
470 fn set_magic_blocking<F: NorFlash>(
471 &mut self,
472 aligned: &mut [u8],
473 magic: u8,
474 flash: &mut F,
475 ) -> Result<(), FirmwareUpdaterError> {
476 flash.read(self.state.from as u32, aligned)?;
477
478 if aligned.iter().any(|&b| b != magic) {
479 aligned.fill(0);
480
481 flash.write(self.state.from as u32, aligned)?;
482 flash.erase(self.state.from as u32, self.state.to as u32)?;
483
484 aligned.fill(magic);
485 flash.write(self.state.from as u32, aligned)?;
486 }
487 Ok(())
488 }
489
490 /// Write data to a flash page.
491 ///
492 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
493 ///
494 /// # Safety
495 ///
496 /// Failing to meet alignment and size requirements may result in a panic.
497 pub fn write_firmware_blocking<F: NorFlash>(
498 &mut self,
499 offset: usize,
500 data: &[u8],
501 flash: &mut F,
502 block_size: usize,
503 ) -> Result<(), FirmwareUpdaterError> {
504 assert!(data.len() >= F::ERASE_SIZE);
505
506 flash.erase(
507 (self.dfu.from + offset) as u32,
508 (self.dfu.from + offset + data.len()) as u32,
509 )?;
510
511 trace!(
512 "Erased from {} to {}",
513 self.dfu.from + offset,
514 self.dfu.from + offset + data.len()
515 );
516
517 FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?;
518
519 Ok(())
520 }
521
522 /// Prepare for an incoming DFU update by erasing the entire DFU area and
523 /// returning a `FirmwareWriter`.
524 ///
525 /// Using this instead of `write_firmware_blocking` allows for an optimized
526 /// API in exchange for added complexity.
527 pub fn prepare_update_blocking<F: NorFlash>(
528 &mut self,
529 flash: &mut F,
530 ) -> Result<FirmwareWriter, FirmwareUpdaterError> {
531 flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?;
532
533 trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
534
535 Ok(FirmwareWriter(self.dfu))
536 }
537}
diff --git a/embassy-boot/boot/src/firmware_writer.rs b/embassy-boot/boot/src/firmware_writer.rs
new file mode 100644
index 000000000..f992021bb
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_writer.rs
@@ -0,0 +1,97 @@
1use embedded_storage::nor_flash::NorFlash;
2use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
3
4use crate::Partition;
5
6/// FirmwareWriter allows writing blocks to an already erased flash.
7pub struct FirmwareWriter(pub(crate) Partition);
8
9impl FirmwareWriter {
10 /// Write data to a flash page.
11 ///
12 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
13 ///
14 /// # Safety
15 ///
16 /// Failing to meet alignment and size requirements may result in a panic.
17 pub async fn write_block<F: AsyncNorFlash>(
18 &mut self,
19 offset: usize,
20 data: &[u8],
21 flash: &mut F,
22 block_size: usize,
23 ) -> Result<(), F::Error> {
24 trace!(
25 "Writing firmware at offset 0x{:x} len {}",
26 self.0.from + offset,
27 data.len()
28 );
29
30 let mut write_offset = self.0.from + offset;
31 for chunk in data.chunks(block_size) {
32 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
33 flash.write(write_offset as u32, chunk).await?;
34 write_offset += chunk.len();
35 }
36 /*
37 trace!("Wrote data, reading back for verification");
38
39 let mut buf: [u8; 4096] = [0; 4096];
40 let mut data_offset = 0;
41 let mut read_offset = self.dfu.from + offset;
42 for chunk in buf.chunks_mut(block_size) {
43 flash.read(read_offset as u32, chunk).await?;
44 trace!("Read chunk at {}: {:?}", read_offset, chunk);
45 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
46 read_offset += chunk.len();
47 data_offset += chunk.len();
48 }
49 */
50
51 Ok(())
52 }
53
54 /// Write data to a flash page.
55 ///
56 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
57 ///
58 /// # Safety
59 ///
60 /// Failing to meet alignment and size requirements may result in a panic.
61 pub fn write_block_blocking<F: NorFlash>(
62 &mut self,
63 offset: usize,
64 data: &[u8],
65 flash: &mut F,
66 block_size: usize,
67 ) -> Result<(), F::Error> {
68 trace!(
69 "Writing firmware at offset 0x{:x} len {}",
70 self.0.from + offset,
71 data.len()
72 );
73
74 let mut write_offset = self.0.from + offset;
75 for chunk in data.chunks(block_size) {
76 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
77 flash.write(write_offset as u32, chunk)?;
78 write_offset += chunk.len();
79 }
80 /*
81 trace!("Wrote data, reading back for verification");
82
83 let mut buf: [u8; 4096] = [0; 4096];
84 let mut data_offset = 0;
85 let mut read_offset = self.dfu.from + offset;
86 for chunk in buf.chunks_mut(block_size) {
87 flash.read(read_offset as u32, chunk).await?;
88 trace!("Read chunk at {}: {:?}", read_offset, chunk);
89 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
90 read_offset += chunk.len();
91 data_offset += chunk.len();
92 }
93 */
94
95 Ok(())
96 }
97}
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:
diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs
new file mode 100644
index 000000000..46f80a23c
--- /dev/null
+++ b/embassy-boot/boot/src/partition.rs
@@ -0,0 +1,22 @@
1/// A region in flash used by the bootloader.
2#[derive(Copy, Clone, Debug)]
3#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4pub struct Partition {
5 /// Start of the flash region.
6 pub from: usize,
7 /// End of the flash region.
8 pub to: usize,
9}
10
11impl Partition {
12 /// Create a new partition with the provided range
13 pub const fn new(from: usize, to: usize) -> Self {
14 Self { from, to }
15 }
16
17 /// Return the length of the partition
18 #[allow(clippy::len_without_is_empty)]
19 pub const fn len(&self) -> usize {
20 self.to - self.from
21 }
22}